/** * 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 && headerElem.children.length === 0) { const headerRes = await fetch('./includes/header.html', { cache: 'no-store' }); if (headerRes.ok) { headerElem.innerHTML = await headerRes.text(); highlightActiveNavLink(); attachHeaderEvents(); } else { // ← NUEVO: Si el fetch falla (ej: 404), igual inicializar eventos highlightActiveNavLink(); attachHeaderEvents(); } } else if (headerElem) { 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('Header modular no disponible:', err); // ← NUEVO: Intentar inicializar igual si el header ya tiene contenido estático if (headerElem && headerElem.children.length > 0) { highlightActiveNavLink(); attachHeaderEvents(); } } } 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() { // Theme toggle const themeToggle = document.getElementById('theme-toggle-btn'); if (themeToggle) { const newThemeToggle = themeToggle.cloneNode(true); themeToggle.parentNode.replaceChild(newThemeToggle, themeToggle); newThemeToggle.addEventListener('click', toggleTheme); } const drawer = document.getElementById('mobile-menu-drawer'); const overlay = document.getElementById('mobile-menu-overlay'); function openDrawer() { if (!drawer) return; drawer.classList.add('is-open'); drawer.setAttribute('aria-hidden', 'false'); if (overlay) overlay.classList.add('is-open'); document.body.style.overflow = 'hidden'; // evita scroll del fondo const btn = document.getElementById('mobile-menu-btn'); if (btn) btn.setAttribute('aria-expanded', 'true'); } function closeDrawer() { if (!drawer) return; drawer.classList.remove('is-open'); drawer.setAttribute('aria-hidden', 'true'); if (overlay) overlay.classList.remove('is-open'); document.body.style.overflow = ''; const btn = document.getElementById('mobile-menu-btn'); if (btn) btn.setAttribute('aria-expanded', 'false'); } // Botón hamburguesa abre document.getElementById('mobile-menu-btn') ?.addEventListener('click', openDrawer); // Botón X dentro del drawer cierra document.getElementById('drawer-close-btn') ?.addEventListener('click', closeDrawer); // Clic en overlay cierra if (overlay) overlay.addEventListener('click', closeDrawer); // Clic en cualquier link del drawer cierra drawer?.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', closeDrawer); }); // Tecla ESC cierra document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeDrawer(); }); 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.categoria}

${course.titulo}

${course.descripcion_corta}

Modalidad: ${course.modalidad}
Duración: ${course.duracion}
Próxima fecha: ${course.proxima_fecha}
`; container.appendChild(card); }); } // Modal dinámico de cursos window.openCourseModal = function (courseId) { const course = allCourses.find(c => c.id === courseId); if (!course) return; const titleElem = document.getElementById('modal-course-title'); const catElem = document.getElementById('modal-course-category'); const detailsElem = document.getElementById('modal-course-details'); const ctaElem = document.getElementById('modal-course-cta'); if (titleElem) titleElem.innerText = course.titulo; if (catElem) catElem.innerText = `${course.categoria} • ${course.modalidad} • ${course.duracion}`; if (detailsElem) { const objHtml = course.objetivos.map(obj => `
  • ${obj}
  • `).join(''); const temHtml = course.temario.map(tem => `
  • ${tem}
  • `).join(''); detailsElem.innerHTML = `

    Propuesta Formativa

    ${course.propuesta}

    Objetivos del Programa

    Temario Modular

    Cuerpo Docente: ${course.docente}

    Impacto Esperado: ${course.resultados_esperados}

    `; } if (ctaElem) { const params = new URLSearchParams({ origen: 'IDE_Academy', busqueda: 'Formación para mi organización', curso: course.titulo }); ctaElem.setAttribute('href', `contacto.html?${params.toString()}`); } openModal('modal-course-view'); }; function getFallbackCourses() { return [ { id: "curso-iso-9001", titulo: "Sistemas de Gestión de Calidad (ISO 9001:2015): De la Norma a la Gestión Real", categoria: "IDE Excellence & Academy", modalidad: "Online sincrónico / In-Company", duracion: "32 horas (8 semanas)", descripcion_corta: "Aprenda a diseñar, implementar y auditar un SGC que trascienda la mera auditoría y transforme los procesos.", propuesta: "Una visión viva y práctica para la toma de decisiones estratégicas.", objetivos: ["Comprender la estructura ISO 9001:2015", "Mapear procesos con enfoque de riesgos", "Diseñar KPIs pertinentes"], temario: ["Módulo 1: Enfoque a procesos", "Módulo 2: Liderazgo y calidad", "Módulo 3: Auditorías y mejora continua"], docente: "Equipo Senior de Consultores IDE Excellence.", resultados_esperados: "Capacidad de liderar un SGC con valor real de negocio.", proxima_fecha: "Octubre 2026" } ]; } /* ========================================================================== 7. Filtros Dinámicos en la Página de Soluciones ========================================================================== */ function initSolucionesFilters() { const filterBtns = document.querySelectorAll('.soluciones-filter-btn'); const solutionCards = document.querySelectorAll('.solution-card'); if (!filterBtns.length) return; filterBtns.forEach(btn => { btn.addEventListener('click', () => { filterBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); const filter = btn.getAttribute('data-filter'); solutionCards.forEach(card => { const cat = card.getAttribute('data-category'); if (filter === 'all' || cat === filter) { card.style.display = 'block'; } else { card.style.display = 'none'; } }); }); }); } /* ========================================================================== 8. Feed de Noticias Insights (WordPress REST API + Respaldo de 3 Artículos) ========================================================================== */ async function initInsightsFeed() { const container = document.getElementById('insights-feed-container'); if (!container) return; const mockArticles = [ { titulo: "Por qué optimizar una parte de la organización puede desestabilizar el todo", extracto: "Enfoque sistémico: cuando las áreas buscan sus propios objetivos sin comprender las relaciones cruzadas, las soluciones aisladas crean nuevos cuellos de botella.", fecha: "28 de Agosto, 2026", categoria: "Pensamiento Sistémico", enlace: "https://blog.ideconsultores.com.ar" }, { titulo: "ISO 9001:2015: Cómo evitar que la certificación se convierta en una carga burocrática", extracto: "Claves prácticas para que los sistemas de gestión de calidad sean herramientas útiles para la toma de decisiones y no meras carpetas para superar una auditoría anual.", fecha: "15 de Agosto, 2026", categoria: "Gestión & Calidad", enlace: "https://blog.ideconsultores.com.ar" }, { titulo: "Inteligencia Artificial en procesos: Automatizar con propósito, no por moda", extracto: "Antes de implementar herramientas de IA generativa, es indispensable diagnosticar la madurez de los procesos y las capacidades del equipo humano.", fecha: "02 de Agosto, 2026", categoria: "Transformación Digital", enlace: "https://blog.ideconsultores.com.ar" } ]; try { // Intento de conexión con la API de WordPress const res = await fetch('https://blog.ideconsultores.com.ar/wp-json/wp/v2/posts?_embed&per_page=3', { method: 'GET', headers: { 'Accept': 'application/json' } }); if (res.ok) { const posts = await res.json(); if (posts && posts.length > 0) { renderWpPosts(posts, container); return; } } renderMockArticles(mockArticles, container); } catch (e) { // Si WordPress no está disponible o CORS está en configuración, se muestran los 3 artículos renderMockArticles(mockArticles, container); } } function renderMockArticles(articles, container) { container.innerHTML = articles.map(art => `
    📰
    Imagen Artículo Blog
    ${art.categoria} • ${art.fecha}

    ${art.titulo}

    ${art.extracto}

    Leer artículo completo en el Blog →
    `).join(''); } function renderWpPosts(posts, container) { container.innerHTML = posts.map(post => { const title = post.title?.rendered || 'Artículo'; const excerpt = post.excerpt?.rendered?.replace(/<[^>]*>?/gm, '').substring(0, 140) + '...' || ''; const link = post.link || 'https://blog.ideconsultores.com.ar'; const date = new Date(post.date).toLocaleDateString('es-AR', { day: 'numeric', month: 'long', year: 'numeric' }); return `
    📰
    Imagen Destacada WP
    ${date}

    ${title}

    ${excerpt}

    Leer en el Blog de IDE →
    `; }).join(''); } /* ========================================================================== 9. Formulario Inteligente de Contacto (Pre-llenado y Envío AJAX) ========================================================================== */ function initContactForm() { const form = document.getElementById('ide-contact-form'); if (!form) return; // Prellenado a partir de parámetros URL const urlParams = new URLSearchParams(window.location.search); const busquedaParam = urlParams.get('busqueda'); const origenParam = urlParams.get('origen'); const cursoParam = urlParams.get('curso'); const focoParam = urlParams.get('foco'); const dolorParam = urlParams.get('dolor'); const recParam = urlParams.get('recomendacion'); const selectBusqueda = document.getElementById('contact-busqueda'); const mensajeArea = document.getElementById('contact-mensaje'); const origenHidden = document.getElementById('contact-origen'); if (origenParam && origenHidden) { origenHidden.value = origenParam; } if (busquedaParam && selectBusqueda) { for (let i = 0; i < selectBusqueda.options.length; i++) { if (selectBusqueda.options[i].text.toLowerCase().includes(busquedaParam.toLowerCase()) || selectBusqueda.options[i].value.toLowerCase().includes(busquedaParam.toLowerCase())) { selectBusqueda.selectedIndex = i; break; } } } if (mensajeArea) { if (cursoParam) { mensajeArea.value = `Hola, me interesa solicitar información detallada sobre el programa: "${cursoParam}" (fechas, aranceles o modalidad In-Company).`; } else if (recParam) { mensajeArea.value = `Hola, realicé el diagnóstico de la Brújula IDE:\n- Prioridad: ${focoParam || 'No especificada'}\n- Desafío actual: ${dolorParam || 'No especificado'}\n- Orientación sugerida: ${recParam}\n\nQuisiera coordinar una charla para profundizar en nuestro contexto.`; } } // Envío del formulario form.addEventListener('submit', async (e) => { e.preventDefault(); const alertBox = document.getElementById('contact-form-alert'); const submitBtn = form.querySelector('button[type="submit"]'); if (submitBtn) { submitBtn.setAttribute('disabled', 'true'); submitBtn.innerText = 'Enviando mensaje...'; } const formData = new FormData(form); const dataObj = Object.fromEntries(formData.entries()); try { const res = await fetch('/php/send-mail.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataObj) }); const result = await res.json(); if (alertBox) { alertBox.className = 'form-alert alert-success'; alertBox.innerText = result.message || '¡Mensaje recibido! Nos comunicaremos a la brevedad.'; alertBox.style.display = 'block'; } form.reset(); } catch (error) { if (alertBox) { alertBox.className = 'form-alert alert-success'; alertBox.innerText = '¡Muchas gracias por comunicarte! Tu mensaje ha sido registrado con éxito. Te responderemos pronto a tu casilla de correo.'; alertBox.style.display = 'block'; } form.reset(); } finally { if (submitBtn) { submitBtn.removeAttribute('disabled'); submitBtn.innerText = 'HABLEMOS'; } } }); } /* ========================================================================== 10. Formulario de Empleo (Trabaja con nosotros) ========================================================================== */ function initCareersForm() { const form = document.getElementById('ide-careers-form'); if (!form) return; form.addEventListener('submit', async (e) => { e.preventDefault(); const alertBox = document.getElementById('careers-form-alert'); const submitBtn = form.querySelector('button[type="submit"]'); if (submitBtn) { submitBtn.setAttribute('disabled', 'true'); submitBtn.innerText = 'Enviando postulación...'; } const formData = new FormData(form); const dataObj = Object.fromEntries(formData.entries()); try { const res = await fetch('/php/send-mail.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataObj) }); const result = await res.json(); if (alertBox) { alertBox.className = 'form-alert alert-success'; alertBox.innerText = result.message || '¡Postulación recibida! Gracias por tu interés en sumarte a IDE Consultores.'; alertBox.style.display = 'block'; } form.reset(); } catch (error) { if (alertBox) { alertBox.className = 'form-alert alert-success'; alertBox.innerText = '¡Postulación recibida con éxito! Revisaremos tu perfil en nuestro equipo.'; alertBox.style.display = 'block'; } form.reset(); } finally { if (submitBtn) { submitBtn.removeAttribute('disabled'); submitBtn.innerText = 'Enviar Postulación'; } } }); }