576 lines
25 KiB
JavaScript
576 lines
25 KiB
JavaScript
// ==================== MÓDULO PRINCIPAL (IIFE) ====================
|
|
(function() {
|
|
'use strict';
|
|
|
|
// -------------------- ESTADO PRIVADO --------------------
|
|
const state = {
|
|
menuOpen: false,
|
|
searchOpen: false,
|
|
isLightMode: localStorage.getItem('theme') === 'light',
|
|
isPreloaderActive: true,
|
|
chatbot: {
|
|
isOpen: false,
|
|
isLoading: false,
|
|
sessionId: localStorage.getItem('chatbotSession') || crypto.randomUUID(),
|
|
apiUrl: 'https://n8npix.pixelteo.com/webhook/d9de4096-c21a-41e8-aa2c-30dd1de5c3ba/chat'
|
|
}
|
|
};
|
|
|
|
// Guardar session ID
|
|
localStorage.setItem('chatbotSession', state.chatbot.sessionId);
|
|
|
|
// -------------------- UTILIDADES --------------------
|
|
const utils = {
|
|
// Throttle simple para eventos de alta frecuencia
|
|
throttle(fn, delay) {
|
|
let lastCall = 0;
|
|
return function(...args) {
|
|
const now = Date.now();
|
|
if (now - lastCall >= delay) {
|
|
lastCall = now;
|
|
fn.apply(this, args);
|
|
}
|
|
};
|
|
},
|
|
|
|
// Debounce para búsqueda
|
|
debounce(fn, delay) {
|
|
let timer;
|
|
return function(...args) {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
};
|
|
},
|
|
|
|
// Formateo seguro de mensajes (evita XSS)
|
|
formatMessage(text) {
|
|
if (!text) return '';
|
|
let formatted = text
|
|
.replace(/[&<>]/g, function(m) {
|
|
if (m === '&') return '&';
|
|
if (m === '<') return '<';
|
|
if (m === '>') return '>';
|
|
return m;
|
|
})
|
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
.replace(/`(.*?)`/g, '<code>$1</code>')
|
|
.replace(/\n/g, '<br>')
|
|
.replace(/\$(\d+(?:,\d+)*(?:\.\d+)?)/g, '<span style="color: #10b981; font-weight: bold;">$$$1</span>');
|
|
return formatted;
|
|
},
|
|
|
|
// Obtener vuelos desde el DOM (una sola vez con caché)
|
|
getVuelosFromDOM() {
|
|
if (this._vuelosCache) return this._vuelosCache;
|
|
const vuelos = [];
|
|
document.querySelectorAll('.tour-one__single').forEach((card, index) => {
|
|
const img = card.querySelector('.tour-one__image img');
|
|
const imgSrc = img?.getAttribute('src') || img?.getAttribute('data-src') || '';
|
|
const link = card.querySelector('.tour-one__content h3 a');
|
|
const precioEspecial = parseFloat(card.getAttribute('data-precio-especial') || 0);
|
|
const precioPublico = parseFloat(card.getAttribute('data-precio-publico') || 0);
|
|
const precio = precioEspecial > 0 ? precioEspecial : precioPublico;
|
|
const meta = card.querySelectorAll('.tour-one__meta li a');
|
|
vuelos.push({
|
|
id: card.getAttribute('data-vuelo-id') || index,
|
|
nombre: link?.textContent.trim() || '',
|
|
enlace: link?.getAttribute('href') || '',
|
|
precio,
|
|
precioOriginal: precioEspecial > 0 ? precioPublico : null,
|
|
unidad: card.getAttribute('data-unidad') || '',
|
|
duracion: meta[0]?.textContent.trim() || '',
|
|
ubicacion: meta[1]?.textContent.trim() || '',
|
|
imagen: imgSrc,
|
|
imagenAlt: img?.getAttribute('alt') || 'Vuelo en globo'
|
|
});
|
|
});
|
|
this._vuelosCache = vuelos;
|
|
return vuelos;
|
|
},
|
|
_vuelosCache: null
|
|
};
|
|
|
|
// -------------------- MANIPULACIÓN DEL DOM --------------------
|
|
const dom = {
|
|
elements: {},
|
|
|
|
cacheSelectors() {
|
|
this.elements = {
|
|
preloader: document.getElementById('preloader'),
|
|
progressBar: document.getElementById('preloaderProgressBar'),
|
|
themeToggles: document.querySelectorAll('.theme-toggle'),
|
|
hamburger: document.getElementById('hamburgerMenu'),
|
|
mobileMenu: document.getElementById('mobileMenu'),
|
|
closeMenu: document.getElementById('closeMenu'),
|
|
menuOverlay: document.getElementById('menuOverlay'),
|
|
mobileNavLinks: document.querySelectorAll('.mobile-nav-link'),
|
|
searchBtn: document.getElementById('searchBtn'),
|
|
searchModal: document.getElementById('searchModal'),
|
|
closeSearch: document.getElementById('closeSearch'),
|
|
searchInput: document.getElementById('searchInput'),
|
|
searchResults: document.getElementById('searchResults'),
|
|
searchSuggestions: document.querySelectorAll('.search-suggestion'),
|
|
chatbotToggle: document.getElementById('chatbotToggle'),
|
|
chatbotContainer: document.getElementById('chatbotContainer'),
|
|
chatbotClose: document.getElementById('chatbotClose'),
|
|
chatbotMessages: document.getElementById('chatbotMessages'),
|
|
chatbotInput: document.getElementById('chatbotInput'),
|
|
chatbotSendBtn: document.getElementById('chatbotSendBtn'),
|
|
chatbotStatus: document.getElementById('chatbotStatus'),
|
|
notificationBtn: document.getElementById('notificationBtn'),
|
|
reservarBtn: document.getElementById('reservarBtn'),
|
|
heroVideo: document.getElementById('heroVideo')
|
|
};
|
|
},
|
|
|
|
show(element) {
|
|
if (element) element.classList.add('active');
|
|
},
|
|
hide(element) {
|
|
if (element) element.classList.remove('active');
|
|
},
|
|
setHtml(element, html) {
|
|
if (element) element.innerHTML = html;
|
|
},
|
|
setText(element, text) {
|
|
if (element) element.textContent = text;
|
|
}
|
|
};
|
|
|
|
// -------------------- CHATBOT (CLASE) --------------------
|
|
class ChatbotManager {
|
|
constructor() {
|
|
this.isOpen = false;
|
|
this.isLoading = false;
|
|
this.sessionId = state.chatbot.sessionId;
|
|
this.apiUrl = state.chatbot.apiUrl;
|
|
this.beepAudio = null;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
if (!dom.elements.chatbotToggle) return;
|
|
dom.elements.chatbotToggle.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
this.playBeep();
|
|
this.toggle();
|
|
});
|
|
dom.elements.chatbotClose?.addEventListener('click', () => this.close());
|
|
dom.elements.chatbotSendBtn?.addEventListener('click', () => this.sendMessage());
|
|
dom.elements.chatbotInput?.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
this.sendMessage();
|
|
}
|
|
});
|
|
// Cerrar al hacer clic fuera
|
|
document.addEventListener('click', (e) => {
|
|
if (this.isOpen &&
|
|
dom.elements.chatbotContainer &&
|
|
dom.elements.chatbotToggle &&
|
|
!dom.elements.chatbotContainer.contains(e.target) &&
|
|
!dom.elements.chatbotToggle.contains(e.target)) {
|
|
this.close();
|
|
}
|
|
});
|
|
}
|
|
|
|
playBeep() {
|
|
if (!this.beepAudio) {
|
|
this.beepAudio = new Audio();
|
|
this.beepAudio.src = 'data:audio/wav;base64,U3RlYWx0aCBibG9i...'; // Opcional: puedes mantener el elemento <audio> del DOM
|
|
// Si prefieres usar el elemento existente:
|
|
const beepElement = document.getElementById('chatbotBeep');
|
|
if (beepElement) this.beepAudio = beepElement;
|
|
}
|
|
this.beepAudio?.play().catch(() => {});
|
|
}
|
|
|
|
toggle() {
|
|
this.isOpen ? this.close() : this.open();
|
|
}
|
|
|
|
open() {
|
|
if (!dom.elements.chatbotContainer) return;
|
|
dom.elements.chatbotContainer.classList.add('active');
|
|
this.isOpen = true;
|
|
setTimeout(() => dom.elements.chatbotInput?.focus(), 300);
|
|
}
|
|
|
|
close() {
|
|
if (!dom.elements.chatbotContainer) return;
|
|
dom.elements.chatbotContainer.classList.remove('active');
|
|
this.isOpen = false;
|
|
}
|
|
|
|
addMessage(text, type) {
|
|
const msgDiv = document.createElement('div');
|
|
msgDiv.className = `chatbot-message ${type}`;
|
|
let finalText = text;
|
|
try {
|
|
const parsed = JSON.parse(text);
|
|
if (parsed.message) finalText = parsed.message;
|
|
} catch (e) {}
|
|
msgDiv.innerHTML = utils.formatMessage(finalText);
|
|
dom.elements.chatbotMessages?.appendChild(msgDiv);
|
|
dom.elements.chatbotMessages.scrollTop = dom.elements.chatbotMessages.scrollHeight;
|
|
}
|
|
|
|
showLoading() {
|
|
if (this.isLoading) return;
|
|
this.isLoading = true;
|
|
const loadingDiv = document.createElement('div');
|
|
loadingDiv.className = 'chatbot-loading';
|
|
loadingDiv.id = 'chatbotLoading';
|
|
loadingDiv.innerHTML = '<span></span><span></span><span></span>';
|
|
dom.elements.chatbotMessages?.appendChild(loadingDiv);
|
|
dom.elements.chatbotMessages.scrollTop = dom.elements.chatbotMessages.scrollHeight;
|
|
dom.setText(dom.elements.chatbotStatus, '✍️ Escribiendo...');
|
|
if (dom.elements.chatbotSendBtn) dom.elements.chatbotSendBtn.disabled = true;
|
|
if (dom.elements.chatbotInput) dom.elements.chatbotInput.disabled = true;
|
|
}
|
|
|
|
hideLoading() {
|
|
const loading = document.getElementById('chatbotLoading');
|
|
if (loading) loading.remove();
|
|
this.isLoading = false;
|
|
dom.setText(dom.elements.chatbotStatus, '🟢 Conectado');
|
|
if (dom.elements.chatbotSendBtn) dom.elements.chatbotSendBtn.disabled = false;
|
|
if (dom.elements.chatbotInput) dom.elements.chatbotInput.disabled = false;
|
|
if (this.isOpen) dom.elements.chatbotInput?.focus();
|
|
}
|
|
|
|
async sendMessage() {
|
|
const input = dom.elements.chatbotInput;
|
|
if (!input) return;
|
|
const userText = input.value.trim();
|
|
if (!userText || this.isLoading) return;
|
|
|
|
this.addMessage(userText, 'user');
|
|
input.value = '';
|
|
this.showLoading();
|
|
|
|
try {
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
|
const response = await fetch(this.apiUrl, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
body: JSON.stringify({
|
|
chatInput: userText,
|
|
sessionId: this.sessionId,
|
|
userData: {
|
|
userAgent: navigator.userAgent,
|
|
language: navigator.language,
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
}),
|
|
signal: controller.signal
|
|
});
|
|
clearTimeout(timeoutId);
|
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
const data = await response.json();
|
|
this.hideLoading();
|
|
|
|
let botReply = '';
|
|
if (data.action && data.message) {
|
|
botReply = data.message;
|
|
if (data.data) localStorage.setItem('chatbotUserData', JSON.stringify(data.data));
|
|
} else if (typeof data === 'string') botReply = data;
|
|
else if (data.output) botReply = data.output;
|
|
else if (data.response) botReply = data.response;
|
|
else botReply = 'Lo siento, no pude procesar tu solicitud. Intenta de nuevo.';
|
|
|
|
this.addMessage(botReply, 'bot');
|
|
} catch (error) {
|
|
console.error('Chatbot error:', error);
|
|
this.hideLoading();
|
|
this.addMessage('⚠️ Error de conexión. Por favor, intenta más tarde.', 'error');
|
|
}
|
|
}
|
|
|
|
sendPredefined(message) {
|
|
if (!dom.elements.chatbotInput) return;
|
|
if (!this.isOpen) this.open();
|
|
dom.elements.chatbotInput.value = message;
|
|
this.sendMessage();
|
|
}
|
|
}
|
|
|
|
// -------------------- MÓDULOS DE UI --------------------
|
|
const ui = {
|
|
init() {
|
|
this.setupPreloader();
|
|
this.setupTheme();
|
|
this.setupMobileMenu();
|
|
this.setupSearchModal();
|
|
this.setupAnimations();
|
|
this.setupVideo();
|
|
this.setupBookingButtons();
|
|
this.setupResponsive();
|
|
},
|
|
|
|
setupPreloader() {
|
|
const preloader = dom.elements.preloader;
|
|
const progressBar = dom.elements.progressBar;
|
|
if (!preloader) return;
|
|
|
|
let progress = 0;
|
|
const interval = setInterval(() => {
|
|
progress += Math.random() * 15;
|
|
if (progress >= 100) {
|
|
progress = 100;
|
|
clearInterval(interval);
|
|
this.finishPreloader(preloader);
|
|
}
|
|
if (progressBar) progressBar.style.width = `${Math.min(progress, 100)}%`;
|
|
}, 100);
|
|
|
|
window.addEventListener('load', () => {
|
|
clearInterval(interval);
|
|
this.finishPreloader(preloader);
|
|
});
|
|
},
|
|
|
|
finishPreloader(preloader) {
|
|
if (!state.isPreloaderActive) return;
|
|
state.isPreloaderActive = false;
|
|
preloader.classList.add('loaded');
|
|
setTimeout(() => {
|
|
if (preloader) preloader.style.display = 'none';
|
|
}, 600);
|
|
},
|
|
|
|
setupTheme() {
|
|
if (state.isLightMode) document.body.classList.add('light-mode');
|
|
dom.elements.themeToggles?.forEach(toggle => {
|
|
toggle.addEventListener('click', () => {
|
|
state.isLightMode = !state.isLightMode;
|
|
document.body.classList.toggle('light-mode');
|
|
localStorage.setItem('theme', state.isLightMode ? 'light' : 'dark');
|
|
});
|
|
});
|
|
},
|
|
|
|
setupMobileMenu() {
|
|
const { hamburger, mobileMenu, closeMenu, menuOverlay, mobileNavLinks } = dom.elements;
|
|
const toggle = () => {
|
|
state.menuOpen = !state.menuOpen;
|
|
hamburger?.classList.toggle('active');
|
|
mobileMenu?.classList.toggle('active');
|
|
menuOverlay?.classList.toggle('active');
|
|
document.body.style.overflow = state.menuOpen ? 'hidden' : 'auto';
|
|
};
|
|
hamburger?.addEventListener('click', toggle);
|
|
closeMenu?.addEventListener('click', toggle);
|
|
menuOverlay?.addEventListener('click', toggle);
|
|
mobileNavLinks?.forEach(link => link.addEventListener('click', toggle));
|
|
},
|
|
|
|
setupSearchModal() {
|
|
const { searchBtn, searchModal, closeSearch, searchInput, searchResults, searchSuggestions } = dom.elements;
|
|
if (!searchBtn || !searchModal) return;
|
|
|
|
const open = () => {
|
|
state.searchOpen = true;
|
|
searchModal.classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
setTimeout(() => searchInput?.focus(), 300);
|
|
};
|
|
const close = () => {
|
|
state.searchOpen = false;
|
|
searchModal.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
if (searchInput) searchInput.value = '';
|
|
if (searchResults) {
|
|
searchResults.innerHTML = '';
|
|
searchResults.classList.remove('active');
|
|
}
|
|
};
|
|
|
|
searchBtn.addEventListener('click', open);
|
|
closeSearch?.addEventListener('click', close);
|
|
document.addEventListener('keydown', (e) => e.key === 'Escape' && state.searchOpen && close());
|
|
|
|
const performSearch = utils.debounce(() => {
|
|
const query = searchInput?.value.toLowerCase().trim() || '';
|
|
if (query.length === 0) {
|
|
if (searchResults) {
|
|
searchResults.innerHTML = '';
|
|
searchResults.classList.remove('active');
|
|
}
|
|
return;
|
|
}
|
|
const vuelos = utils.getVuelosFromDOM();
|
|
const filtered = vuelos.filter(v =>
|
|
v.nombre.toLowerCase().includes(query) ||
|
|
v.ubicacion.toLowerCase().includes(query) ||
|
|
v.duracion.toLowerCase().includes(query) ||
|
|
(v.precio && v.precio.toString().includes(query))
|
|
);
|
|
this.displaySearchResults(filtered, query);
|
|
}, 300);
|
|
|
|
searchInput?.addEventListener('input', performSearch);
|
|
searchSuggestions?.forEach(sugg => {
|
|
sugg.addEventListener('click', () => {
|
|
const text = sugg.getAttribute('data-search');
|
|
if (searchInput && text) {
|
|
searchInput.value = text;
|
|
performSearch();
|
|
searchInput.focus();
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
displaySearchResults(results, query) {
|
|
const container = dom.elements.searchResults;
|
|
if (!container) return;
|
|
if (results.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="text-center p-8">
|
|
<i class="fas fa-search fa-3x text-gray-400 mb-4"></i>
|
|
<p class="text-gray-600">No se encontraron vuelos para "${escapeHtml(query)}"</p>
|
|
<p class="text-gray-400 text-sm mt-2">Prueba con: amanecer, familiar, premium, teotihuacán</p>
|
|
</div>
|
|
`;
|
|
} else {
|
|
container.innerHTML = results.map(vuelo => {
|
|
let imgUrl = vuelo.imagen;
|
|
if (imgUrl && !imgUrl.startsWith('http') && !imgUrl.startsWith('//')) {
|
|
imgUrl = window.location.origin + '/' + imgUrl.replace(/^\.?\//, '');
|
|
}
|
|
return `
|
|
<div class="search-result-item" style="display: flex; gap: 1rem; padding: 1rem; cursor: pointer; background: white; border-radius: 16px; margin-bottom: 0.75rem; border: 1px solid #e5e7eb;"
|
|
onclick="window.handleSearchResultClick('${vuelo.id}', '${escapeHtml(vuelo.enlace)}')">
|
|
<div style="flex:0 0 100px; height:100px; border-radius:12px; overflow:hidden; background:#f3f4f6;">
|
|
<img src="${imgUrl}" alt="${escapeHtml(vuelo.imagenAlt)}" style="width:100%; height:100%; object-fit:cover;"
|
|
onerror="this.src='https://via.placeholder.com/100x100?text=Vuelo'">
|
|
</div>
|
|
<div style="flex:1">
|
|
<h4 style="font-size:1.1rem; font-weight:700; margin-bottom:0.5rem;">${escapeHtml(vuelo.nombre)}</h4>
|
|
<div class="flex gap-3 text-sm text-gray-500 mb-2">
|
|
<span><i class="far fa-clock"></i> ${escapeHtml(vuelo.duracion)}</span>
|
|
<span><i class="far fa-map"></i> ${escapeHtml(vuelo.ubicacion)}</span>
|
|
</div>
|
|
<div>
|
|
${vuelo.precioOriginal ? `<span class="line-through text-gray-400 mr-2">$${vuelo.precioOriginal.toLocaleString()}</span>` : ''}
|
|
<span class="bg-gradient-to-r from-indigo-500 to-purple-600 text-white px-3 py-1 rounded-full font-bold">$${vuelo.precio.toLocaleString()}</span>
|
|
${vuelo.unidad ? `<span class="text-gray-400 ml-2">/ ${escapeHtml(vuelo.unidad)}</span>` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
container.classList.add('active');
|
|
},
|
|
|
|
setupAnimations() {
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.style.animation = 'fadeInUp 0.8s ease-out forwards';
|
|
observer.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, { threshold: 0.1, rootMargin: '0px 0px -100px 0px' });
|
|
document.querySelectorAll('.feature-card, .tour-one__single').forEach(el => observer.observe(el));
|
|
},
|
|
|
|
setupVideo() {
|
|
const video = dom.elements.heroVideo;
|
|
if (video) {
|
|
video.play().catch(() => {
|
|
video.style.display = 'none';
|
|
const container = document.querySelector('.video-container');
|
|
if (container) {
|
|
container.style.backgroundImage = 'url("assets/img/banner/globopuertoteotihuacan-video.jpg")';
|
|
container.style.backgroundSize = 'cover';
|
|
container.style.backgroundPosition = 'center';
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
setupResponsive() {
|
|
const handler = utils.throttle(() => {
|
|
const w = window.innerWidth;
|
|
document.body.classList.remove('mobile-small', 'mobile-large', 'tablet', 'desktop');
|
|
if (w <= 480) document.body.classList.add('mobile-small');
|
|
else if (w <= 768) document.body.classList.add('mobile-large');
|
|
else if (w <= 1024) document.body.classList.add('tablet');
|
|
else document.body.classList.add('desktop');
|
|
}, 200);
|
|
window.addEventListener('resize', handler);
|
|
handler();
|
|
},
|
|
|
|
setupBookingButtons() {
|
|
const { notificationBtn, reservarBtn } = dom.elements;
|
|
notificationBtn?.addEventListener('click', () => {
|
|
alert('Notificaciones:\n\n• Tu reserva para el 15 de mayo está confirmada\n• Nuevo destino disponible: Valle de la Luna\n• Oferta especial: 20% de descuento en vuelos nocturnos');
|
|
});
|
|
reservarBtn?.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
chatbot.sendPredefined('Quiero reservar un vuelo en globo');
|
|
});
|
|
|
|
// Agregar botón "Consultar" a cada tarjeta de vuelo
|
|
document.querySelectorAll('.tour-one__single').forEach(card => {
|
|
const nombre = card.getAttribute('data-vuelo-nombre');
|
|
if (!nombre) return;
|
|
const content = card.querySelector('.tour-one__content');
|
|
if (content && !content.querySelector('.consulta-btn')) {
|
|
const btn = document.createElement('button');
|
|
btn.innerHTML = '<i class="fas fa-comment"></i> Consultar';
|
|
btn.className = 'consulta-btn';
|
|
btn.style.cssText = 'background:linear-gradient(135deg,#04cdff,#0099cc);color:white;border:none;padding:8px 16px;border-radius:25px;font-size:0.85rem;cursor:pointer;margin-top:10px;width:100%;transition:transform 0.2s';
|
|
btn.onmouseover = () => btn.style.transform = 'translateY(-2px)';
|
|
btn.onmouseout = () => btn.style.transform = 'translateY(0)';
|
|
btn.onclick = (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
chatbot.sendPredefined(`Quiero información sobre el vuelo "${nombre}"`);
|
|
};
|
|
content.appendChild(btn);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
// Helper para escapar HTML
|
|
function escapeHtml(str) {
|
|
if (!str) return '';
|
|
return str.replace(/[&<>]/g, function(m) {
|
|
if (m === '&') return '&';
|
|
if (m === '<') return '<';
|
|
if (m === '>') return '>';
|
|
return m;
|
|
});
|
|
}
|
|
|
|
// Exponer función global para el clic en resultados de búsqueda (por compatibilidad)
|
|
window.handleSearchResultClick = function(id, enlace) {
|
|
const modal = dom.elements.searchModal;
|
|
if (modal) modal.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
state.searchOpen = false;
|
|
if (enlace) window.location.href = enlace;
|
|
};
|
|
|
|
// -------------------- INICIALIZACIÓN --------------------
|
|
let chatbot;
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
dom.cacheSelectors();
|
|
ui.init();
|
|
chatbot = new ChatbotManager();
|
|
// Exponer chatbot en window solo si es necesario (para depuración)
|
|
window.__chatbot = chatbot; // opcional
|
|
});
|
|
})();
|
|
|