// frontend/public/bootstrap.js (function () { // El dominio donde se alojan los widgets const WIDGETS_HOST = 'https://elecciones2025.eldia.com'; // Estado interno para evitar recargas y re-fetch innecesarios const __state = { assetsLoaded: false, manifest: null, }; // Función para cargar dinámicamente un script (evita duplicados) function loadScript(src) { return new Promise((resolve, reject) => { if ([...document.scripts].some(s => s.src === src)) return resolve(); const script = document.createElement('script'); script.type = 'module'; script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } // Función para cargar dinámicamente una hoja de estilos (evita duplicados) function loadCSS(href) { if ([...document.querySelectorAll('link[rel="stylesheet"]')].some(l => l.href === href)) return; const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = href; document.head.appendChild(link); } // Carga (una sola vez) JS/CSS definidos por el manifest async function ensureAssetsFromManifest() { if (__state.assetsLoaded) return; // 1) Obtener el manifest.json (cache: no-store por si hay deploys frecuentes) if (!__state.manifest) { const response = await fetch(`${WIDGETS_HOST}/manifest.json`, { cache: 'no-store' }); if (!response.ok) throw new Error('No se pudo cargar el manifest de los widgets.'); __state.manifest = await response.json(); } // 2) Encontrar el entry principal (isEntry=true) const entryKey = Object.keys(__state.manifest).find(key => __state.manifest[key].isEntry); if (!entryKey) throw new Error('No se encontró el punto de entrada en el manifest.'); const entry = __state.manifest[entryKey]; const jsUrl = `${WIDGETS_HOST}/${entry.file}`; // 3) Cargar el CSS si existe (una sola vez) if (entry.css && entry.css.length > 0) { entry.css.forEach(cssFile => loadCSS(`${WIDGETS_HOST}/${cssFile}`)); } // 4) Cargar el JS principal (una sola vez) await loadScript(jsUrl); __state.assetsLoaded = true; } // Render: busca contenedores y llama a la API global del widget function renderWidgetsOnPage() { if (!(window.EleccionesWidgets && typeof window.EleccionesWidgets.render === 'function')) { // La librería aún no expuso la API (puede ocurrir en primeros ms tras cargar) return; } const widgetContainers = document.querySelectorAll('[data-elecciones-widget]'); if (widgetContainers.length === 0) { // En algunas rutas no habrá widgets: no es error. return; } widgetContainers.forEach(container => { window.EleccionesWidgets.render(container, container.dataset); }); } // Función principal (re-usable) para inicializar y renderizar async function initWidgets() { try { await ensureAssetsFromManifest(); renderWidgetsOnPage(); } catch (error) { console.error('Error al inicializar los widgets de elecciones:', error); } } // Exponer para invocación manual (por ejemplo, en hooks del router) window.__eleccionesInit = initWidgets; // Primer render en carga inicial if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initWidgets); } else { initWidgets(); } // --- Reinvocar en cada navegación de SPA --- function dispatchLocationChange() { window.dispatchEvent(new Event('locationchange')); } ['pushState', 'replaceState'].forEach(method => { const orig = history[method]; history[method] = function () { const ret = orig.apply(this, arguments); dispatchLocationChange(); return ret; }; }); window.addEventListener('popstate', dispatchLocationChange); let navDebounce = null; window.addEventListener('locationchange', () => { clearTimeout(navDebounce); navDebounce = setTimeout(() => { initWidgets(); }, 0); }); // --- (Opcional) Re-render si aparecen contenedores luego del montaje de la vista --- const mo = new MutationObserver((mutations) => { for (const m of mutations) { if (m.type === 'childList') { const added = [...m.addedNodes].some(n => n.nodeType === 1 && (n.matches?.('[data-elecciones-widget]') || n.querySelector?.('[data-elecciones-widget]')) ); if (added) { renderWidgetsOnPage(); break; } } } }); mo.observe(document.body, { childList: true, subtree: true }); })();