limpieza de codigo
This commit is contained in:
+165
@@ -17,6 +17,7 @@ async function initApp() {
|
||||
initInsightsFeed();
|
||||
initContactForm();
|
||||
initCareersForm();
|
||||
initSearchPage();
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
@@ -1177,3 +1178,167 @@ function initHeroVideoSpeed() {
|
||||
video.playbackRate = targetSpeed;
|
||||
});
|
||||
}
|
||||
/* ==========================================================================
|
||||
12. BÚSQUEDA EN EL SITIO — Página buscar.html
|
||||
========================================================================== */
|
||||
const SEARCH_INDEX_URL = 'data/search-index.json';
|
||||
|
||||
async function initSearchPage() {
|
||||
const form = document.getElementById('search-page-form');
|
||||
const input = document.getElementById('search-page-input');
|
||||
const results = document.getElementById('search-results');
|
||||
const status = document.getElementById('search-status');
|
||||
const suggestions = document.getElementById('search-suggestions');
|
||||
|
||||
// Si no estamos en buscar.html, salir silenciosamente
|
||||
if (!form || !input || !results) return;
|
||||
|
||||
let index = [];
|
||||
|
||||
// Cargar índice
|
||||
try {
|
||||
const res = await fetch(SEARCH_INDEX_URL, { cache: 'no-store' });
|
||||
if (res.ok) {
|
||||
index = await res.json();
|
||||
} else {
|
||||
console.warn('No se pudo cargar search-index.json:', res.status);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error cargando search-index.json:', e);
|
||||
}
|
||||
|
||||
// Normalizar texto (sin acentos, minúsculas)
|
||||
const normalize = (str) =>
|
||||
(str || '')
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '');
|
||||
|
||||
// Escapar HTML
|
||||
const escapeHtml = (str) =>
|
||||
(str || '').replace(/[&<>"']/g, (c) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}[c]));
|
||||
|
||||
// Scoring por relevancia
|
||||
function scoreItem(item, terms) {
|
||||
const title = normalize(item.titulo);
|
||||
const desc = normalize(item.descripcion);
|
||||
const kw = normalize(item.keywords);
|
||||
let score = 0;
|
||||
|
||||
terms.forEach(term => {
|
||||
if (!term) return;
|
||||
if (title.includes(term)) score += 10;
|
||||
if (kw.includes(term)) score += 6;
|
||||
if (desc.includes(term)) score += 3;
|
||||
});
|
||||
|
||||
// Bonus si la frase completa aparece en el título
|
||||
const fullPhrase = terms.join(' ');
|
||||
if (fullPhrase && title.includes(fullPhrase)) score += 5;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
// Resaltar coincidencias
|
||||
function highlight(text, terms) {
|
||||
let safe = escapeHtml(text);
|
||||
terms.forEach(term => {
|
||||
if (term.length < 2) return;
|
||||
const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const regex = new RegExp(`(${escaped})`, 'gi');
|
||||
safe = safe.replace(regex, '<mark>$1</mark>');
|
||||
});
|
||||
return safe;
|
||||
}
|
||||
|
||||
// Render de resultados
|
||||
function render(term) {
|
||||
results.innerHTML = '';
|
||||
const cleanTerm = (term || '').trim();
|
||||
|
||||
if (!cleanTerm) {
|
||||
if (status) status.textContent = '';
|
||||
if (suggestions) suggestions.hidden = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (suggestions) suggestions.hidden = true;
|
||||
|
||||
const terms = normalize(cleanTerm).split(/\s+/).filter(Boolean);
|
||||
|
||||
const scored = index
|
||||
.map(item => ({ item, score: scoreItem(item, terms) }))
|
||||
.filter(r => r.score > 0)
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
if (scored.length === 0) {
|
||||
if (status) status.textContent = '';
|
||||
results.innerHTML = `
|
||||
<div class="search-empty">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
<line x1="8" y1="11" x2="14" y2="11"></line>
|
||||
</svg>
|
||||
<p>No encontramos resultados para <strong>${escapeHtml(cleanTerm)}</strong>.</p>
|
||||
<p class="search-hint">
|
||||
Probá con otros términos o <a href="contacto.html">contactanos directamente</a>.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
status.textContent = `${scored.length} resultado${scored.length !== 1 ? 's' : ''} para "${cleanTerm}"`;
|
||||
}
|
||||
|
||||
results.innerHTML = scored.map(({ item }) => `
|
||||
<article class="search-result-item">
|
||||
<div class="result-category">${escapeHtml(item.categoria)}</div>
|
||||
<h3><a href="${escapeHtml(item.url)}">${highlight(item.titulo, terms)}</a></h3>
|
||||
<p>${highlight(item.descripcion, terms)}</p>
|
||||
</article>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Leer ?q= de la URL al cargar
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const initialQuery = params.get('q') || '';
|
||||
|
||||
if (initialQuery) {
|
||||
input.value = initialQuery;
|
||||
render(initialQuery);
|
||||
} else {
|
||||
if (suggestions) suggestions.hidden = false;
|
||||
// Foco automático al llegar sin query
|
||||
setTimeout(() => input.focus(), 100);
|
||||
}
|
||||
|
||||
// Submit del form
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const q = input.value.trim();
|
||||
const newUrl = q
|
||||
? `?q=${encodeURIComponent(q)}`
|
||||
: window.location.pathname;
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
render(q);
|
||||
});
|
||||
|
||||
// Búsqueda en vivo con debounce
|
||||
let debounceTimer;
|
||||
input.addEventListener('input', () => {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
render(input.value);
|
||||
}, 220);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user