${art.titulo}
${art.extracto}
/** * IDE CONSULTORES - SCRIPT INSTITUCIONAL (JS ÚNICO) * Maneja includes modulares, tema claro/oscuro, Brújula IDE, modales, catálogo de cursos, filtros y formularios. */ document.addEventListener('DOMContentLoaded', () => { initApp(); }); async function initApp() { initTheme(); await loadIncludes(); initModals(); initBrujulaIDE(); initAcademyCourses(); initSolucionesFilters(); initInsightsFeed(); initContactForm(); initCareersForm(); } /* ========================================================================== 1. Carga Modular de Header y Footer (JS Nativo) ========================================================================== */ async function loadIncludes() { const headerElem = document.getElementById('site-header'); const footerElem = document.getElementById('site-footer'); try { if (headerElem) { const headerRes = await fetch('/includes/header.html', { cache: 'no-store' }); if (headerRes.ok) { headerElem.innerHTML = await headerRes.text(); highlightActiveNavLink(); attachHeaderEvents(); } } if (footerElem) { const footerRes = await fetch('/includes/footer.html', { cache: 'no-store' }); if (footerRes.ok) { footerElem.innerHTML = await footerRes.text(); } } } catch (err) { console.warn('Nota: Inclusión modular de header/footer cargada con respaldo local.'); } } function highlightActiveNavLink() { const currentPath = window.location.pathname; const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { const href = link.getAttribute('href'); if (!href) return; // Comparación de página activa if ( (currentPath === '/' || currentPath.endsWith('index.html')) && (href === '/' || href === 'index.html' || href === './') || (currentPath.includes(href) && href !== '/' && href !== 'index.html') ) { link.classList.add('active'); } else { link.classList.remove('active'); } }); } function attachHeaderEvents() { // Switch de Tema: usar clonación para evitar listeners duplicados const themeToggle = document.getElementById('theme-toggle-btn'); if (themeToggle) { const newThemeToggle = themeToggle.cloneNode(true); themeToggle.parentNode.replaceChild(newThemeToggle, themeToggle); newThemeToggle.addEventListener('click', toggleTheme); } // Botón de menú móvil y cierre del drawer: event delegation global. // Evita depender de un nodo único (el header se inyecta de forma modular), // de modo que funcione aunque el botón sea reemplazado por cloneNode. const mobileDrawer = document.getElementById('mobile-menu-drawer'); document.addEventListener('click', (e) => { const toggleClicked = e.target.closest('#mobile-menu-btn'); const insideDrawer = mobileDrawer && mobileDrawer.contains(e.target); if (toggleClicked) { if (mobileDrawer) { const isOpen = mobileDrawer.classList.toggle('is-open'); toggleClicked.setAttribute('aria-expanded', isOpen); } } else if (mobileDrawer && mobileDrawer.classList.contains('is-open') && !insideDrawer) { mobileDrawer.classList.remove('is-open'); const toggle = document.querySelector('#mobile-menu-btn'); if (toggle) toggle.setAttribute('aria-expanded', 'false'); } }); const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; toggleLogoTheme(currentTheme); updateThemeIcon(currentTheme); } /* ========================================================================== 2. Selector de Modo Claro / Modo Oscuro con Persistencia ========================================================================== */ function initTheme() { const savedTheme = localStorage.getItem('ide_theme') || 'light'; document.documentElement.setAttribute('data-theme', savedTheme); updateThemeIcon(savedTheme); toggleLogoTheme(savedTheme); } function toggleTheme() { const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', newTheme); localStorage.setItem('ide_theme', newTheme); updateThemeIcon(newTheme); toggleLogoTheme(newTheme); const themeBtns = document.querySelectorAll('#theme-toggle-btn'); themeBtns.forEach(btn => { btn.setAttribute('aria-label', newTheme === 'dark' ? 'Cambiar a modo claro' : 'Cambiar a modo oscuro'); }); } function updateThemeIcon(theme) { const themeBtns = document.querySelectorAll('#theme-toggle-btn'); themeBtns.forEach(btn => { btn.setAttribute('aria-label', theme === 'dark' ? 'Cambiar a modo claro' : 'Cambiar a modo oscuro'); }); } function toggleLogoTheme(theme) { const logoLight = document.querySelector('.logo-light-only'); const logoDark = document.querySelector('.logo-dark-only'); if (logoLight && logoDark) { if (theme === 'dark') { logoLight.style.display = 'none'; logoDark.style.display = 'block'; } else { logoLight.style.display = 'block'; logoDark.style.display = 'none'; } } } /* ========================================================================== 4. Sistema de Modales (Pop-ups con Cierre por Clic Externo y ESC) ========================================================================== */ function initModals() { const modalBackdrops = document.querySelectorAll('.modal-backdrop'); modalBackdrops.forEach(backdrop => { // Cerrar al hacer clic en el backdrop (fuera de la ventana) backdrop.addEventListener('click', (e) => { if (e.target === backdrop) { closeModal(backdrop.id); } }); // Botones de cierre interno const closeBtns = backdrop.querySelectorAll('.modal-close-btn, [data-close-modal]'); closeBtns.forEach(btn => { btn.addEventListener('click', () => { closeModal(backdrop.id); }); }); }); // Cerrar con tecla ESC document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { const activeModal = document.querySelector('.modal-backdrop.is-active'); if (activeModal) { closeModal(activeModal.id); } } }); // Triggers de modales genéricos en páginas (ej: desafíos) document.querySelectorAll('[data-open-modal]').forEach(trigger => { trigger.addEventListener('click', (e) => { e.preventDefault(); const modalId = trigger.getAttribute('data-open-modal'); openModal(modalId); }); }); } function openModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.classList.add('is-active'); document.body.style.overflow = 'hidden'; // Evita scroll de fondo } } function closeModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.classList.remove('is-active'); document.body.style.overflow = ''; } } /* ========================================================================== 5. Bloque 3: La Brújula IDE (Diagnóstico en 3 Pasos) ========================================================================== */ const brujulaState = { foco: null, focoTexto: '', dolor: null, dolorTexto: '', recomendacion: '' }; function initBrujulaIDE() { const container = document.getElementById('brujula-interactive-app'); if (!container) return; const step1Options = container.querySelectorAll('.brujula-opt-step1'); const step2Options = container.querySelectorAll('.brujula-opt-step2'); const btnNext1 = document.getElementById('brujula-btn-next1'); const btnNext2 = document.getElementById('brujula-btn-next2'); const btnRestart = document.getElementById('brujula-btn-restart'); // Selección paso 1 step1Options.forEach(opt => { opt.addEventListener('click', () => { step1Options.forEach(o => o.classList.remove('selected')); opt.classList.add('selected'); brujulaState.foco = opt.getAttribute('data-val'); brujulaState.focoTexto = opt.querySelector('.opt-title').innerText; if (btnNext1) btnNext1.removeAttribute('disabled'); }); }); if (btnNext1) { btnNext1.addEventListener('click', () => { if (!brujulaState.foco) return; showBrujulaStep(2); }); } // Selección paso 2 step2Options.forEach(opt => { opt.addEventListener('click', () => { step2Options.forEach(o => o.classList.remove('selected')); opt.classList.add('selected'); brujulaState.dolor = opt.getAttribute('data-val'); brujulaState.dolorTexto = opt.querySelector('.opt-title').innerText; if (btnNext2) btnNext2.removeAttribute('disabled'); }); }); if (btnNext2) { btnNext2.addEventListener('click', () => { if (!brujulaState.dolor) return; calculateBrujulaResult(); showBrujulaStep(3); }); } if (btnRestart) { btnRestart.addEventListener('click', () => { brujulaState.foco = null; brujulaState.dolor = null; step1Options.forEach(o => o.classList.remove('selected')); step2Options.forEach(o => o.classList.remove('selected')); if (btnNext1) btnNext1.setAttribute('disabled', 'true'); if (btnNext2) btnNext2.setAttribute('disabled', 'true'); showBrujulaStep(1); }); } } function showBrujulaStep(stepNumber) { const steps = document.querySelectorAll('.brujula-step'); const fill = document.getElementById('brujula-progress-fill'); const stepLabel = document.getElementById('brujula-step-indicator'); steps.forEach(step => step.classList.remove('active')); const activeStep = document.getElementById(`brujula-step-${stepNumber}`); if (activeStep) activeStep.classList.add('active'); if (fill) { fill.style.width = stepNumber === 1 ? '33.33%' : stepNumber === 2 ? '66.66%' : '100%'; } if (stepLabel) { stepLabel.innerText = stepNumber <= 2 ? `Paso ${stepNumber} de 3` : 'Orientación recomendada'; } } function calculateBrujulaResult() { const resultTitle = document.getElementById('brujula-result-capacidades'); const resultText = document.getElementById('brujula-result-desc'); const resultCta = document.getElementById('brujula-result-cta'); let caps = 'IDE Excellence + IDE Academy'; let desc = 'La implementación de procesos y normas es el primer paso. Para que sea sostenible, es clave trabajar en el desarrollo de capacidades de tu equipo.'; let dropdownValue = 'Implementar o certificar una norma'; // Lógica de cruce sistémico if (brujulaState.foco === 'iso' && brujulaState.dolor === 'adopcion') { caps = 'IDE Excellence + IDE Academy'; desc = 'Implementar normas como ISO 9001 sólo genera valor si las personas comprenden y adoptan el sistema. Recomendamos unir la consultoría de procesos con formación In-Company.'; dropdownValue = 'Implementar o certificar una norma'; } else if (brujulaState.foco === 'proyectos' || brujulaState.dolor === 'desvios') { caps = 'IDE Project + IDE Excellence'; desc = 'Tus desafíos requieren profesionalizar la dirección de proyectos y alinear los flujos operativos con la estrategia de la organización.'; dropdownValue = 'Gestionar o impulsar un proyecto'; } else if (brujulaState.foco === 'tecnologia' || brujulaState.dolor === 'herramientas') { caps = 'IDE Digital + IDE Excellence'; desc = 'La tecnología y la IA deben estar al servicio de procesos claros. Recomendamos mapear primero las oportunidades antes de sumar herramientas aisladas.'; dropdownValue = 'Transformación digital / IA'; } else if (brujulaState.foco === 'equipo' || brujulaState.dolor === 'procesos') { caps = 'IDE Academy + IDE Excellence'; desc = 'Fortalecer el criterio y las competencias del equipo permite que los procesos organizacionales fluyan con menor fricción y mayor autonomía.'; dropdownValue = 'Formación para mi organización'; } brujulaState.recomendacion = `${caps}: ${desc}`; if (resultTitle) resultTitle.innerText = caps; if (resultText) resultText.innerText = desc; if (resultCta) { // Generar enlace pre-cargado para Contacto const params = new URLSearchParams({ origen: 'Brujula_IDE', busqueda: dropdownValue, foco: brujulaState.focoTexto, dolor: brujulaState.dolorTexto, recomendacion: caps }); resultCta.setAttribute('href', `contacto.html?${params.toString()}`); } } /* ========================================================================== 6. Carga Dinámica de Cursos en IDE Academy (cursos.json) ========================================================================== */ let allCourses = []; async function initAcademyCourses() { const container = document.getElementById('academy-courses-container'); if (!container) return; try { const res = await fetch('/data/cursos.json'); if (!res.ok) throw new Error('No se pudo cargar cursos.json'); allCourses = await res.json(); renderCourseCards(allCourses, container); } catch (err) { console.warn('Cargando cursos con datos de respaldo.'); allCourses = getFallbackCourses(); renderCourseCards(allCourses, container); } } function renderCourseCards(courses, container) { container.innerHTML = ''; courses.forEach(course => { const card = document.createElement('div'); card.className = 'card'; card.innerHTML = `
${course.descripcion_corta}
${course.propuesta}
Cuerpo Docente: ${course.docente}
Impacto Esperado: ${course.resultados_esperados}
${art.extracto}
${excerpt}