This commit is contained in:
2026-03-17 10:23:52 -06:00
parent fc2ccc74f1
commit 86391ea412
2 changed files with 86 additions and 78 deletions
+85 -77
View File
@@ -5,7 +5,7 @@
* @property {boolean} DEBUG - Habilita/deshabilita el modo de depuración. * @property {boolean} DEBUG - Habilita/deshabilita el modo de depuración.
*/ */
window.COURSE_CONFIG = { window.COURSE_CONFIG = {
COURSE_CONFIG_URL: 'config.json', COURSE_CONFIG_URL: "config.json",
DEBUG: true, DEBUG: true,
}; };
@@ -19,8 +19,8 @@ window.COURSE_CONFIG = {
* renderPagination(2, courseData.contentArray); * renderPagination(2, courseData.contentArray);
*/ */
function renderPagination(currentIndex, contentArray) { function renderPagination(currentIndex, contentArray) {
const pageNumber = document.getElementById('coursenav-page-number'); const pageNumber = document.getElementById("coursenav-page-number");
const totalPages = document.getElementById('coursenav-total-pages'); const totalPages = document.getElementById("coursenav-total-pages");
if (!Array.isArray(contentArray)) return; if (!Array.isArray(contentArray)) return;
// Gestionar visibilidad de menús primero // Gestionar visibilidad de menús primero
@@ -30,13 +30,17 @@ function renderPagination(currentIndex, contentArray) {
if (!currentSlide) return; if (!currentSlide) return;
// Filtrar todos los slides del mismo módulo // Filtrar todos los slides del mismo módulo
const moduleSlides = contentArray.filter((slide) => slide.moduleTitle === currentSlide.moduleTitle); const moduleSlides = contentArray.filter(
(slide) => slide.moduleTitle === currentSlide.moduleTitle
);
// Encontrar la posición del slide actual dentro del módulo // Encontrar la posición del slide actual dentro del módulo
const moduleSlideIndex = moduleSlides.findIndex((slide) => slide.content === currentSlide.content); const moduleSlideIndex = moduleSlides.findIndex(
(slide) => slide.content === currentSlide.content
);
if (pageNumber) pageNumber.textContent = moduleSlideIndex + 1; if (pageNumber) pageNumber.textContent = moduleSlideIndex + 1;
if (totalPages) totalPages.textContent = ' / ' + moduleSlides.length; if (totalPages) totalPages.textContent = " / " + moduleSlides.length;
//Navegación personalizada //Navegación personalizada
updateNavButtons(moduleSlideIndex, moduleSlides); updateNavButtons(moduleSlideIndex, moduleSlides);
} }
@@ -54,8 +58,8 @@ function renderPagination(currentIndex, contentArray) {
* @param {Array<Object>} moduleSlides - Un array de los objetos de diapositiva que pertenecen al módulo actual. * @param {Array<Object>} moduleSlides - Un array de los objetos de diapositiva que pertenecen al módulo actual.
*/ */
function updateNavButtons(moduleSlideIndex, moduleSlides) { function updateNavButtons(moduleSlideIndex, moduleSlides) {
const nextBtn = document.getElementById('coursenav-next-btn'); const nextBtn = document.getElementById("coursenav-next-btn");
const prevBtn = document.getElementById('coursenav-prev-btn'); const prevBtn = document.getElementById("coursenav-prev-btn");
if (!nextBtn || !prevBtn) return; if (!nextBtn || !prevBtn) return;
@@ -63,8 +67,8 @@ function updateNavButtons(moduleSlideIndex, moduleSlides) {
const isFirstSlide = moduleSlideIndex === 0; const isFirstSlide = moduleSlideIndex === 0;
const isDebugMode = CourseNav.isDebug(); const isDebugMode = CourseNav.isDebug();
nextBtn.classList.toggle('disabled', isLastSlide && !isDebugMode); nextBtn.classList.toggle("disabled", isLastSlide && !isDebugMode);
prevBtn.classList.toggle('disabled', isFirstSlide && !isDebugMode); prevBtn.classList.toggle("disabled", isFirstSlide && !isDebugMode);
} }
/** /**
* Gestiona la visibilidad de los menús del curso en la barra de navegación. * Gestiona la visibilidad de los menús del curso en la barra de navegación.
@@ -80,16 +84,16 @@ function updateNavButtons(moduleSlideIndex, moduleSlides) {
* @param {Array<Object>} contentArray - El array completo de objetos de slide del curso. * @param {Array<Object>} contentArray - El array completo de objetos de slide del curso.
*/ */
function manageMenuVisibility(currentIndex, contentArray) { function manageMenuVisibility(currentIndex, contentArray) {
const courseMenus = document.querySelectorAll('#coursenav-main-menu > ul.course-menu'); const courseMenus = document.querySelectorAll("#coursenav-main-menu > ul.course-menu");
if (!courseMenus.length) return; if (!courseMenus.length) return;
// El primer menú siempre está visible. // El primer menú siempre está visible.
courseMenus[0].style.display = 'block'; courseMenus[0].style.display = "block";
// Si no hay contenido, ocultar los demás menús. // Si no hay contenido, ocultar los demás menús.
if (!Array.isArray(contentArray) || !contentArray.length) { if (!Array.isArray(contentArray) || !contentArray.length) {
for (let i = 1; i < courseMenus.length; i++) { for (let i = 1; i < courseMenus.length; i++) {
courseMenus[i].style.display = 'none'; courseMenus[i].style.display = "none";
} }
return; return;
} }
@@ -98,7 +102,7 @@ function manageMenuVisibility(currentIndex, contentArray) {
// Si el slide actual no existe o no tiene un título de módulo, ocultar los demás. // Si el slide actual no existe o no tiene un título de módulo, ocultar los demás.
if (!currentSlide?.moduleTitle) { if (!currentSlide?.moduleTitle) {
for (let i = 1; i < courseMenus.length; i++) { for (let i = 1; i < courseMenus.length; i++) {
courseMenus[i].style.display = 'none'; courseMenus[i].style.display = "none";
} }
return; return;
} }
@@ -106,12 +110,12 @@ function manageMenuVisibility(currentIndex, contentArray) {
const currentModuleTitle = currentSlide.moduleTitle; const currentModuleTitle = currentSlide.moduleTitle;
for (let i = 1; i < courseMenus.length; i++) { for (let i = 1; i < courseMenus.length; i++) {
const menuItems = Array.from(courseMenus[i].querySelectorAll('.coursenav-link')); const menuItems = Array.from(courseMenus[i].querySelectorAll(".coursenav-link"));
const shouldShow = menuItems.some((item) => { const shouldShow = menuItems.some((item) => {
const itemIndex = parseInt(item.dataset.coursenavindex); const itemIndex = parseInt(item.dataset.coursenavindex);
return itemIndex >= 0 && contentArray[itemIndex]?.moduleTitle === currentModuleTitle; return itemIndex >= 0 && contentArray[itemIndex]?.moduleTitle === currentModuleTitle;
}); });
courseMenus[i].style.display = shouldShow ? 'block' : 'none'; courseMenus[i].style.display = shouldShow ? "block" : "none";
} }
} }
@@ -128,7 +132,7 @@ function manageMenuVisibility(currentIndex, contentArray) {
* scrollToElementTop('#main-content', { behavior: 'smooth', block: 'start' }); * scrollToElementTop('#main-content', { behavior: 'smooth', block: 'start' });
*/ */
function scrollToElementTop(selector, options = {}) { function scrollToElementTop(selector, options = {}) {
const defaults = { behavior: 'smooth', block: 'start', inline: 'nearest' }; const defaults = { behavior: "smooth", block: "start", inline: "nearest" };
const opts = Object.assign(defaults, options); const opts = Object.assign(defaults, options);
const el = document.querySelector(selector); const el = document.querySelector(selector);
if (el) el.scrollIntoView(opts); if (el) el.scrollIntoView(opts);
@@ -149,7 +153,7 @@ function scrollToElementTop(selector, options = {}) {
* animateOnScroll('.animar', 'animate__fadeIn', { threshold: 0.2 }); * animateOnScroll('.animar', 'animate__fadeIn', { threshold: 0.2 });
*/ */
function animateOnScroll(selector, animationClass, options = {}) { function animateOnScroll(selector, animationClass, options = {}) {
const { threshold = 0.1, animateOnce = true, prefix = 'animate__animated' } = options; const { threshold = 0.1, animateOnce = true, prefix = "animate__animated" } = options;
const cb = (entries, observer) => { const cb = (entries, observer) => {
entries.forEach((entry) => { entries.forEach((entry) => {
@@ -168,37 +172,37 @@ function animateOnScroll(selector, animationClass, options = {}) {
} }
function scaleWrapCourseContent() { function scaleWrapCourseContent() {
const content = document.querySelector('.wrap-course-content'); const content = document.querySelector(".wrap-course-content");
if (!content) return; if (!content) return;
const ww = window.innerWidth; const ww = window.innerWidth;
const wh = window.innerHeight; const wh = window.innerHeight;
// Breakpoint a 1500px let baseWidth = ww < 1500 ? 1366 : 1920;
let baseWidth, baseHeight; let baseHeight = ww < 1500 ? 768 : 1080;
if (ww < 1500) { const scaleX = ww / baseWidth;
// Menor a 1500px: usar 1366x768 const scaleY = wh / baseHeight;
baseWidth = 1366;
baseHeight = 768; // Prioriza ancho, ajusta si rompe altura
} else { let scale = scaleX;
// 1500px o más: usar 1920x1080 if (baseHeight * scaleX > wh) {
baseWidth = 1920; scale = scaleY;
baseHeight = 1080;
} }
// Escala simple // Limitar reducción excesiva
const scale = Math.min(ww / baseWidth, wh / baseHeight); const minScale = 0.8;
scale = Math.max(minScale, scale);
content.style.transform = `scale(${scale})`; content.style.transform = `scale(${scale})`;
content.style.transformOrigin = 'top left'; content.style.transformOrigin = "top left";
content.style.width = baseWidth + 'px'; content.style.width = baseWidth + "px";
content.style.height = baseHeight + 'px'; content.style.height = baseHeight + "px";
content.style.position = 'absolute'; content.style.position = "absolute";
content.style.left = (ww - baseWidth * scale) / 2 + 'px'; content.style.left = (ww - baseWidth * scale) / 2 + "px";
content.style.top = (wh - baseHeight * scale) / 2 + 'px'; content.style.top = (wh - baseHeight * scale) / 2 + "px";
content.style.overflow = 'hidden'; content.style.overflow = "hidden";
content.style.zIndex = '1'; content.style.zIndex = "1";
} }
/** /**
@@ -215,14 +219,14 @@ function scaleWrapCourseContent() {
*/ */
function renderStepper(stepperEl, progressPercent, mobileEl, stepsCount = 5) { function renderStepper(stepperEl, progressPercent, mobileEl, stepsCount = 5) {
// Validación y obtención de elementos // Validación y obtención de elementos
if (typeof stepperEl === 'string') stepperEl = document.querySelector(stepperEl); if (typeof stepperEl === "string") stepperEl = document.querySelector(stepperEl);
if (typeof mobileEl === 'string') mobileEl = document.querySelector(mobileEl); if (typeof mobileEl === "string") mobileEl = document.querySelector(mobileEl);
if (!(stepperEl instanceof HTMLElement) || !(mobileEl instanceof HTMLElement)) { if (!(stepperEl instanceof HTMLElement) || !(mobileEl instanceof HTMLElement)) {
throw new Error('renderStepper: elementos inválidos.'); throw new Error("renderStepper: elementos inválidos.");
} }
// Limpiar contenido previo // Limpiar contenido previo
stepperEl.querySelectorAll('.step').forEach((el) => el.remove()); stepperEl.querySelectorAll(".step").forEach((el) => el.remove());
// Calcular posiciones de los pasos // Calcular posiciones de los pasos
const stepPercents = Array.from({ length: stepsCount }, (_, i) => (i / (stepsCount - 1)) * 100); const stepPercents = Array.from({ length: stepsCount }, (_, i) => (i / (stepsCount - 1)) * 100);
@@ -235,22 +239,22 @@ function renderStepper(stepperEl, progressPercent, mobileEl, stepsCount = 5) {
// Crear elementos de los pasos // Crear elementos de los pasos
stepPercents.forEach((pct, i) => { stepPercents.forEach((pct, i) => {
const step = document.createElement('div'); const step = document.createElement("div");
step.classList.add('step'); step.classList.add("step");
if (i < currentIndex) step.classList.add('completed'); if (i < currentIndex) step.classList.add("completed");
if (i === currentIndex) { if (i === currentIndex) {
step.classList.add('completed'); step.classList.add("completed");
step.setAttribute('data-label', Math.round(pct) + '%'); step.setAttribute("data-label", Math.round(pct) + "%");
} }
stepperEl.appendChild(step); stepperEl.appendChild(step);
}); });
// Actualizar estilos y posición // Actualizar estilos y posición
stepperEl.style.setProperty('--pct', progressPercent + '%'); stepperEl.style.setProperty("--pct", progressPercent + "%");
const halfMobile = mobileEl.offsetWidth; const halfMobile = mobileEl.offsetWidth;
mobileEl.style.left = `calc(${progressPercent}% - ${halfMobile}px)`; mobileEl.style.left = `calc(${progressPercent}% - ${halfMobile}px)`;
mobileEl.setAttribute('data-label', progressPercent + '%'); mobileEl.setAttribute("data-label", progressPercent + "%");
} }
/** /**
@@ -266,7 +270,7 @@ function renderStepper(stepperEl, progressPercent, mobileEl, stepsCount = 5) {
*/ */
function gotoFirstMenuToolSlide() { function gotoFirstMenuToolSlide() {
const contentArray = CourseNav.getCourseContentArray(); const contentArray = CourseNav.getCourseContentArray();
const targetTitle = 'Menús de la herramienta'; const targetTitle = "Menús de la herramienta";
const currentSlide = CourseNav.getCurrentSlide(); const currentSlide = CourseNav.getCurrentSlide();
// Filtrar todos los slides del mismo módulo // Filtrar todos los slides del mismo módulo
const targetIndex = contentArray.findIndex( const targetIndex = contentArray.findIndex(
@@ -284,25 +288,25 @@ function gotoFirstMenuToolSlide() {
* Configura los event listeners cuando el DOM está completamente cargado. * Configura los event listeners cuando el DOM está completamente cargado.
* @event DOMContentLoaded * @event DOMContentLoaded
*/ */
document.addEventListener('DOMContentLoaded', () => { document.addEventListener("DOMContentLoaded", () => {
// Escalado inicial y continuo // Escalado inicial y continuo
scaleWrapCourseContent(); scaleWrapCourseContent();
setTimeout(scaleWrapCourseContent, 100); setTimeout(scaleWrapCourseContent, 100);
// Escalado en redimensionamiento con debounce // Escalado en redimensionamiento con debounce
let resizeTimeout; let resizeTimeout;
window.addEventListener('resize', () => { window.addEventListener("resize", () => {
clearTimeout(resizeTimeout); clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(scaleWrapCourseContent, 50); resizeTimeout = setTimeout(scaleWrapCourseContent, 50);
}); });
// Escalado en cambio de orientación (móviles/tablets) // Escalado en cambio de orientación (móviles/tablets)
window.addEventListener('orientationchange', () => { window.addEventListener("orientationchange", () => {
setTimeout(scaleWrapCourseContent, 200); setTimeout(scaleWrapCourseContent, 200);
}); });
// Escalado cuando cambia la visibilidad (cambio de pestaña) // Escalado cuando cambia la visibilidad (cambio de pestaña)
document.addEventListener('visibilitychange', () => { document.addEventListener("visibilitychange", () => {
if (!document.hidden) { if (!document.hidden) {
setTimeout(scaleWrapCourseContent, 100); setTimeout(scaleWrapCourseContent, 100);
} }
@@ -315,8 +319,8 @@ document.addEventListener('DOMContentLoaded', () => {
* @property {number} detail.currentIndex - Índice del slide actual. * @property {number} detail.currentIndex - Índice del slide actual.
* @property {Array} detail.contentArray - Array completo de contenido. * @property {Array} detail.contentArray - Array completo de contenido.
*/ */
document.body.addEventListener('beforeSlideChange', (e) => { document.body.addEventListener("beforeSlideChange", (e) => {
console.log('Antes de cambiar de slide:', e.detail); console.log("Antes de cambiar de slide:", e.detail);
}); });
/** /**
@@ -326,36 +330,40 @@ document.addEventListener('DOMContentLoaded', () => {
* @property {number} detail.slideIndex - Índice del nuevo slide. * @property {number} detail.slideIndex - Índice del nuevo slide.
* @property {Array} detail.contentArray - Array completo de contenido. * @property {Array} detail.contentArray - Array completo de contenido.
*/ */
document.body.addEventListener('slideChange', (e) => { document.body.addEventListener("slideChange", (e) => {
if (e.detail && typeof e.detail.slideIndex === 'number' && Array.isArray(e.detail.contentArray)) { if (
e.detail &&
typeof e.detail.slideIndex === "number" &&
Array.isArray(e.detail.contentArray)
) {
renderPagination(e.detail.slideIndex, e.detail.contentArray); renderPagination(e.detail.slideIndex, e.detail.contentArray);
console.log(e.detail.contentArray[e.detail.slideIndex].content); console.log(e.detail.contentArray[e.detail.slideIndex].content);
const contentArray = e.detail.contentArray; const contentArray = e.detail.contentArray;
const targetTitle = 'Menús de la herramienta'; const targetTitle = "Menús de la herramienta";
const currentSlide = CourseNav.getCurrentSlide(); const currentSlide = CourseNav.getCurrentSlide();
// Filtrar todos los slides del mismo módulo // Filtrar todos los slides del mismo módulo
const targetIndex = contentArray.findIndex( const targetIndex = contentArray.findIndex(
(slide) => slide.title === targetTitle && slide.moduleTitle === currentSlide.moduleTitle (slide) => slide.title === targetTitle && slide.moduleTitle === currentSlide.moduleTitle
); );
const btn = document.getElementById('coursenav-other-btn'); const btn = document.getElementById("coursenav-other-btn");
if (!btn) return; if (!btn) return;
if (targetIndex !== -1) { if (targetIndex !== -1) {
btn.classList.remove('disabled'); btn.classList.remove("disabled");
} else { } else {
btn.classList.add('disabled'); btn.classList.add("disabled");
} }
} }
const titleSlide = document.getElementById('coursenav-course-title'); const titleSlide = document.getElementById("coursenav-course-title");
if (titleSlide) { if (titleSlide) {
const slide = e.detail.contentArray[e.detail.slideIndex]; const slide = e.detail.contentArray[e.detail.slideIndex];
const moduleTitle = slide.moduleTitle ? slide.moduleTitle + ' | ' : ''; const moduleTitle = slide.moduleTitle ? slide.moduleTitle + " | " : "";
const title = slide.title || 'Sin título'; const title = slide.title || "Sin título";
titleSlide.textContent = moduleTitle + title; titleSlide.textContent = moduleTitle + title;
} }
const stepper = document.getElementById('stepper'); const stepper = document.getElementById("stepper");
const movil = document.getElementById('step-movil'); const movil = document.getElementById("step-movil");
const progreso = CourseNav.getProgressPercent(true); const progreso = CourseNav.getProgressPercent(true);
renderStepper(stepper, progreso, movil); renderStepper(stepper, progreso, movil);
}); });
@@ -365,25 +373,25 @@ document.addEventListener('DOMContentLoaded', () => {
* @event slideCompleted * @event slideCompleted
* @property {Object} detail - Detalles del evento. * @property {Object} detail - Detalles del evento.
*/ */
document.body.addEventListener('slideCompleted', (e) => { document.body.addEventListener("slideCompleted", (e) => {
console.log('Slide completado:', e.detail); console.log("Slide completado:", e.detail);
const stepper = document.getElementById('stepper'); const stepper = document.getElementById("stepper");
const movil = document.getElementById('step-movil'); const movil = document.getElementById("step-movil");
const progreso = CourseNav.getProgressPercent(true); const progreso = CourseNav.getProgressPercent(true);
renderStepper(stepper, progreso, movil); renderStepper(stepper, progreso, movil);
renderPagination(e.detail.slideIndex, CourseNav.getCourseContentArray()); renderPagination(e.detail.slideIndex, CourseNav.getCourseContentArray());
}); });
// Event listener para botón personalizado // Event listener para botón personalizado
const customButtonEvent = document.getElementById('coursenav-other-btn'); const customButtonEvent = document.getElementById("coursenav-other-btn");
if (customButtonEvent) { if (customButtonEvent) {
customButtonEvent.addEventListener('click', () => { customButtonEvent.addEventListener("click", () => {
console.log('Botón personalizado clickeado'); console.log("Botón personalizado clickeado");
gotoFirstMenuToolSlide(); gotoFirstMenuToolSlide();
}); });
} }
}); });
// Escalar contenido al cargar y redimensionar // Escalar contenido al cargar y redimensionar
window.addEventListener('DOMContentLoaded', () => scaleWrapCourseContent()); window.addEventListener("DOMContentLoaded", () => scaleWrapCourseContent());
window.addEventListener('resize', () => scaleWrapCourseContent()); window.addEventListener("resize", () => scaleWrapCourseContent());
+1 -1
View File
File diff suppressed because one or more lines are too long