/** * Configuración global del curso. * @namespace * @property {string} COURSE_CONFIG_URL - Ruta al archivo de configuración JSON del curso. * @property {boolean} DEBUG - Habilita/deshabilita el modo de depuración. */ window.COURSE_CONFIG = { COURSE_CONFIG_URL: 'config.json', DEBUG: true, SHOW_PAGINATION: false, // Bandera para mostrar/ocultar paginación SHOW_TITLE: false, // Bandera para mostrar/ocultar título SHOW_GLOSSARY: false, // Bandera para mostrar/ocultar glosario }; /** * Maneja la lógica del glosario para un botón específico * @param {HTMLElement} button - El botón del glosario */ async function handleGlossaryButton(button) { const offcanvasEl = document.getElementById('offcanvasGlossary'); if (!offcanvasEl) { console.error('Elemento offcanvasGlossary no encontrado'); return; } const offcanvas = new bootstrap.Offcanvas(offcanvasEl); try { const res = await fetch('manual_pld_ft.html'); if (!res.ok) throw new Error('Error al cargar el glosario'); const text = await res.text(); const parser = new DOMParser(); const doc = parser.parseFromString(text, 'text/html'); const main = doc.querySelector('main'); offcanvasEl.querySelector('.offcanvas-body').innerHTML = main ? main.innerHTML : '

No se encontró el contenido.

'; const item = CourseNav.getCurrentSlide(); const tituloActual = item.title; offcanvasEl.querySelectorAll('section[data-title]').forEach((seccion) => { seccion.classList.toggle('d-none', seccion.dataset.title !== tituloActual); }); offcanvas.show(); } catch (err) { console.error('Error en glosario:', err); offcanvasEl.querySelector('.offcanvas-body').innerHTML = '

Error al cargar el glosario. Recargue la página e intente nuevamente.

'; offcanvas.show(); } } /** * Configura los event listeners para el glosario */ function setupGlossaryListeners() { // Event delegation para manejar clicks en cualquier botón con la clase 'btn-glossary' document.body.addEventListener('click', function (e) { const glossaryBtn = e.target.closest('.btn-glossary'); if (glossaryBtn) { e.preventDefault(); handleGlossaryButton(glossaryBtn); } }); } /** * Navega a una sección específica, la muestra y hace scroll hacia ella. * @function gotoSection * @param {string|number} sectionId - ID de la sección (con o sin #) o número de sección. * @param {Object} [options={}] - Opciones de scroll. * @param {string} [options.behavior="smooth"] - Comportamiento del scroll ('auto' o 'smooth'). * @param {string} [options.block="start"] - Alineación vertical ('start', 'center', 'end' o 'nearest'). * @example * // Ejemplos de uso: * gotoSection('sec1'); // Va a #sec1 * gotoSection('#sec2'); // Va a #sec2 * gotoSection(3); // Va a #sec3 */ function gotoSection(sectionId, options = {}) { const defaults = { behavior: 'smooth', block: 'start' }; const opts = Object.assign(defaults, options); // Normalizar el ID de la sección let targetId = sectionId; if (typeof sectionId === 'number') { targetId = `sec${sectionId}`; } else if (typeof sectionId === 'string' && !sectionId.startsWith('#')) { targetId = sectionId.startsWith('sec') ? sectionId : `sec${sectionId}`; } else if (sectionId.startsWith('#')) { targetId = sectionId.substring(1); } const targetElement = document.getElementById(targetId); if (targetElement) { // Mostrar la sección si está oculta if (targetElement.style.display === 'none') { targetElement.style.display = ''; } // Hacer scroll hacia la sección targetElement.scrollIntoView(opts); return true; } console.warn(`Sección ${targetId} no encontrada`); return false; } /** * Desplaza la ventana hasta la parte superior de un elemento. * @function scrollToElementTop * @param {string} selector - Selector CSS del elemento objetivo. * @param {Object} [options={}] - Opciones de scroll. * @param {string} [options.behavior="smooth"] - Comportamiento del scroll ('auto' o 'smooth'). * @param {string} [options.block="start"] - Alineación vertical ('start', 'center', 'end' o 'nearest'). * @param {string} [options.inline="nearest"] - Alineación horizontal ('start', 'center', 'end' o 'nearest'). * @example * // Ejemplo de uso: * scrollToElementTop('#main-content', { behavior: 'smooth', block: 'start' }); */ function scrollToElementTop(selector, options = {}) { const defaults = { behavior: 'smooth', block: 'start', inline: 'nearest' }; const opts = Object.assign(defaults, options); const el = document.querySelector(selector); if (el) el.scrollIntoView(opts); } /** * Observador de intersección para animar elementos cuando son visibles en el viewport. * @function animateOnScroll * @param {string} selector - Selector de los elementos a observar. * @param {string} animationClass - Clase de animación de Animate.css (ej. 'animate__fadeInUp'). * @param {Object} [options={}] - Opciones de configuración. * @param {number} [options.threshold=0.1] - Umbral de visibilidad (0-1). * @param {boolean} [options.animateOnce=true] - Si es true, la animación solo se ejecuta una vez. * @param {string} [options.prefix='animate__animated'] - Prefijo para clases de animación. * @returns {IntersectionObserver} Instancia del observador. * @example * // Ejemplo de uso: * animateOnScroll('.animar', 'animate__fadeIn', { threshold: 0.2 }); */ function animateOnScroll(selector, animationClass, options = {}) { const { threshold = 0.1, animateOnce = true, prefix = 'animate__animated' } = options; const cb = (entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add(prefix, animationClass); if (animateOnce) observer.unobserve(entry.target); } else if (!animateOnce) { entry.target.classList.remove(prefix, animationClass); } }); }; const observer = new IntersectionObserver(cb, { threshold }); document.querySelectorAll(selector).forEach((el) => observer.observe(el)); return observer; } /** * Configura los event listeners cuando el DOM está completamente cargado. * @event DOMContentLoaded */ document.addEventListener('DOMContentLoaded', () => { setupGlossaryListeners(); // Inicializar sal.js if (typeof sal !== 'undefined' && document.querySelectorAll('[data-sal]').length > 0) { setTimeout(() => { document.querySelectorAll('[data-sal]').forEach((el) => (el.style.visibility = 'visible')); sal({ once: false, threshold: 0.3, duration: 600, easing: 'ease-out', distance: '50px', opacity: 0, scale: 0.95, }); }, 200); } /** * Evento antes de cambiar de slide. * @event beforeSlideChange * @property {Object} detail - Detalles del evento. * @property {number} detail.currentIndex - Índice del slide actual. * @property {Array} detail.contentArray - Array completo de contenido. */ document.body.addEventListener('beforeSlideChange', (e) => { // console.log("Antes de cambiar de slide:", e.detail); }); /** * Evento al cambiar de slide. * @event slideChange * @property {Object} detail - Detalles del evento. * @property {number} detail.slideIndex - Índice del nuevo slide. * @property {Array} detail.contentArray - Array completo de contenido. */ document.body.addEventListener('slideChange', (e) => { if (e.detail && typeof e.detail.slideIndex === 'number' && Array.isArray(e.detail.contentArray)) { console.log(e.detail.contentArray[e.detail.slideIndex].content); // Inicializar sal.js si hay elementos con data-sal if (typeof sal !== 'undefined' && document.querySelectorAll('[data-sal]').length > 0) { setTimeout(() => { document.querySelectorAll('[data-sal]').forEach((el) => (el.style.visibility = 'visible')); sal({ once: false, threshold: 0.3, duration: 600, easing: 'ease-out', distance: '50px', opacity: 0, scale: 0.95, }); }, 200); } // Paginación const paginationEl = document.getElementById('pagination'); const paginacionScoEl = document.querySelector('.paginacion_sco'); if (window.COURSE_CONFIG.SHOW_PAGINATION) { if (paginationEl) { paginationEl.innerHTML = e.detail.slideIndex + 1 + ' / ' + e.detail.contentArray.length; } if (paginacionScoEl) { paginacionScoEl.classList.remove('isFalse'); } } else { if (paginacionScoEl) { paginacionScoEl.classList.add('isFalse'); } } // Título de la pantalla actual const titleScoEl = document.getElementById('titleSco'); if (titleScoEl) { if (window.COURSE_CONFIG.SHOW_TITLE) { titleScoEl.classList.remove('d-none'); if (e.detail.contentArray[e.detail.slideIndex]) { titleScoEl.innerHTML = e.detail.contentArray[e.detail.slideIndex].title || ''; } } else { titleScoEl.classList.add('d-none'); } } // Glosario const btnGlossary = document.getElementById('btn-glossary'); if (btnGlossary) { if (window.COURSE_CONFIG.SHOW_GLOSSARY) { const noShowBtnGlossaryIn = [0, 1, e.detail.contentArray.length - 1]; btnGlossary.style.display = noShowBtnGlossaryIn.includes(e.detail.slideIndex) ? 'none' : 'block'; } else { btnGlossary.style.display = 'none'; } } } }); /** * Evento al completar un slide. * @event slideCompleted * @property {Object} detail - Detalles del evento. */ document.body.addEventListener('slideCompleted', (e) => { // console.log("Slide completado:", e.detail); }); // Event listener para botón personalizado const customButtonEvent = document.getElementById('btn-glossary'); if (customButtonEvent) { customButtonEvent.addEventListener('click', async () => { const offcanvasEl = document.getElementById('offcanvasGlossary'); const offcanvas = new bootstrap.Offcanvas(offcanvasEl); try { // Carga el HTML completo const res = await fetch('manual_pld_ft.html'); const text = await res.text(); // Parsea el documento y extrae solo el
const parser = new DOMParser(); const doc = parser.parseFromString(text, 'text/html'); const main = doc.querySelector('main'); // Inserta el contenido dentro del offcanvas-body offcanvasEl.querySelector('.offcanvas-body').innerHTML = main ? main.innerHTML : '

No se encontró el contenido.

'; // **Ocultar todas las secciones del glosario y mostrar sólo la correspondiente** const item = CourseNav.getCurrentSlide(); const tituloActual = item.title; console.log('Slide actual:', tituloActual); // console.warn(tituloActual); // Selecciona únicamente las secciones dentro del offcanvas offcanvasEl.querySelectorAll('section[data-title]').forEach((seccion) => { if (seccion.dataset.title === tituloActual) { seccion.classList.remove('d-none'); } else { seccion.classList.add('d-none'); } }); // Finalmente, muestra el offcanvas offcanvas.show(); } catch (err) { console.error(err); offcanvasEl.querySelector('.offcanvas-body').innerHTML = '

Error al cargar el glosario.

'; offcanvas.show(); } }); } }); /** * Inicializa un Swiper con opciones personalizadas, reproducción opcional de audio por slide y detección de visita a todos los slides. * * @param {HTMLElement|string} swiperContainer - Selector CSS o elemento contenedor del Swiper. * @param {Function} [callback] - Función que se ejecuta al visitar todos los slides. * @param {Object} [options={}] - Opciones personalizadas de inicialización para Swiper (navigation, pagination, etc.). * @param {Array} [audios=[]] - Arreglo opcional de objetos Howler (o similar) con audios asignados por slide. * @returns {Swiper} - Instancia inicializada de Swiper. * @example * initializeSwiper('#mySwiper', () => { * console.log('Todos los slides fueron visitados'); * }, { * effect: 'flip', * nextSelector: '.custom-next', * prevSelector: '.custom-prev', * paginationSelector: '.custom-pagination', * }, [ * new Howl({ src: 'audio1.mp3' }), * null, // Slide sin audio * new Howl({ src: 'audio3.mp3' }), * ]); * */ function initializeSwiper(swiperContainer, callback, options = {}, audios = [], autoPlaySound = true) { const visitedSlides = new Set(); const container = typeof swiperContainer === 'string' ? document.querySelector(swiperContainer) : swiperContainer; const parent = container.parentElement; if (options.navigation && options.navigation.nextEl && options.navigation.prevEl) { options.navigation.nextEl = container.querySelector(options.navigation.nextEl) ? container.querySelector(options.navigation.nextEl) : parent.querySelector(options.navigation.nextEl); options.navigation.prevEl = container.querySelector(options.navigation.prevEl) ? container.querySelector(options.navigation.prevEl) : parent.querySelector(options.navigation.prevEl); } const defaultParams = { effect: 'slide', loop: false, autoHeight: true, }; // Combina opciones personalizadas con las por defecto const params = { ...defaultParams, ...options, on: { init: function () { visitedSlides.add(this.activeIndex); if (audios.length > 0 && audios[this.activeIndex] && autoPlaySound) { CourseNav.audioController.stopAllSoundsAndPlay(audios[this.activeIndex]); } }, slideChange: function () { const index = this.activeIndex; visitedSlides.add(index); if (audios.length > 0) { CourseNav.audioController.stopAudio(); } if (audios.length > 0 && audios[index]) { CourseNav.audioController.stopAllSoundsAndPlay(audios[index]); } if (visitedSlides.size === this.slides.length && typeof callback === 'function') { callback(); } }, }, }; return new Swiper(container, params); }