${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(); initHeroVideoSpeed(); await loadIncludes(); initModals(); initAcademyCourses(); initSolucionesFilters(); initInsightsFeed(); initContactForm(); initCareersForm(); initSearchPage(); } /* ========================================================================== 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'); // 1. Limpiar estado activo previo navLinks.forEach(link => link.classList.remove('active')); // 2. Evaluar cada enlace navLinks.forEach(link => { const href = link.getAttribute('href'); // Ignoramos enlaces vacíos o anclas "#" if (!href || href === '#') return; const isHome = (currentPath === '/' || currentPath.endsWith('index.html')) && (href === '/' || href === 'index.html' || href === './'); const isCurrentPage = currentPath.includes(href) && href !== '/' && href !== 'index.html'; if (isHome || isCurrentPage) { link.classList.add('active'); // Si este enlace está dentro de un submenú, marcar también al padre const parentDropdown = link.closest('.dropdown'); if (parentDropdown) { const parentToggle = parentDropdown.querySelector('.dropdown-toggle'); if (parentToggle) { parentToggle.classList.add('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 = ''; } } /* ================================================================ JS QUIZ ================================================================ */ (function () { // ----- FLUJO DE NAVEGACIÓN (Opción B) ----- const FLOW = { 1: { desafio: '2a', objetivo: '2b', solucion: '2c', conocimiento: '2d' }, '2a': 3, '2b': 3, '2c': 3, '2d': 3, 3: 4, 4: 'result' }; // ----- MAPEO DE RESULTADOS (Opción B) ----- function buildResult(answers) { const p1 = answers[1]; const p2 = answers['2a'] || answers['2b'] || answers['2c'] || answers['2d']; const p3 = answers[3]; // Perfil 4 · Requisito exigido if (p3 === 'requisito' || p2 === 'iso' || p2 === 'iso-form') { return { title: 'La certificación puede ser el punto de partida.', body: 'Comprendemos que tenés un requisito externo. En IDE no trabajamos para cumplir auditorías: trabajamos para que el sistema refleje cómo funciona tu organización y contribuya a gestionarla mejor.', chips: ['IDE Excellence', 'ISO 9001 / 14001 / 45001', 'Sistemas de gestión'], cta: 'Ver soluciones Excellence', url: 'soluciones.html' }; } // Perfil 5 · Conocimiento o formación if (p1 === 'conocimiento') { const map = { 'iso-form': { chips: ['Auditoría interna', 'ISO 9001', 'Mejora continua'], extra: 'IDE Excellence' }, 'pm-form': { chips: ['Gestión de proyectos', 'Metodologías ágiles', 'PMP'], extra: 'IDE Project' }, 'liderazgo-form': { chips: ['Liderazgo', 'Cambio organizacional', 'Cultura'], extra: 'IDE Academy' }, 'ia-form': { chips: ['IA aplicada', 'Transformación digital', 'Automatización'], extra: 'IDE Digital' } }; const m = map[p2] || { chips: ['Formación', 'Desarrollo', 'Academy'], extra: 'IDE Academy' }; return { title: 'Desarrollamos las capacidades que hacen posible la transformación.', body: 'IDE Academy diseña programas de formación alineados con los desafíos reales de las organizaciones. No dictamos cursos estándar: trabajamos desde el contexto de cada equipo.', chips: [...m.chips, m.extra], cta: 'Conocer IDE Academy', url: 'academy.html' }; } // Perfil 3 · Sabe exactamente qué necesita if (p1 === 'solucion') { const map = { iso: { title: 'Respuesta concreta. Y algo más.', chips: ['ISO 9001', 'ISO 14001', 'IDE Excellence'], cta: 'Ver soluciones ISO', url: 'soluciones.html', body: 'Sí, implementamos y acompañamos certificaciones ISO. Y creemos que un sistema de gestión debe reflejar cómo funciona tu organización, no solo superar una auditoría.' }, pmo: { title: 'Proyectos que llegan a destino.', chips: ['PMO', 'Gestión de proyectos', 'IDE Project'], cta: 'Ver soluciones Project', url: 'soluciones.html', body: 'Diseñamos PMOs y acompañamos la dirección de proyectos para que se entreguen en tiempo, costo y con los resultados esperados.' }, formacion: { title: 'Formación desde el contexto real.', chips: ['In Company', 'Capacitación', 'IDE Academy'], cta: 'Ver IDE Academy', url: 'academy.html', body: 'Programas de formación diseñados para tu organización, sus desafíos y las capacidades que necesita desarrollar.' }, 'ia-digital': { title: 'Tecnología con propósito.', chips: ['Transformación digital', 'IA aplicada', 'IDE Digital'], cta: 'Ver soluciones Digital', url: 'soluciones.html', body: 'Te ayudamos a identificar dónde la IA y la digitalización generan valor real, y acompañamos su implementación de principio a fin.' } }; const m = map[p2] || { title: 'Encontramos lo que buscás.', chips: ['Soluciones concretas', 'Ecosistema IDE'], cta: 'Ver soluciones', url: 'soluciones.html', body: 'Tenemos la respuesta concreta que buscás. Y una mirada que asegura que esa solución genere valor en el conjunto de tu organización.' }; return { icon: './assets/iconos/engranaje_32.svg', profile: 'Perfil · Solución concreta', ...m }; } // Perfil 2 · Sabe qué quiere lograr if (p1 === 'objetivo') { const map = { 'mejorar-gestion': { chips: ['IDE Excellence', 'Mejora de procesos'], body: 'Entendemos dónde querés llegar. Antes de proponer una solución, comprendemos tu operación para intervenir donde hay mayor impacto.' }, 'profesionalizar-proyectos':{ chips: ['IDE Project', 'PMO'], body: 'Traducimos tu objetivo en una estructura de gestión de proyectos adecuada a tu contexto, sin sobreingeniería.' }, 'desarrollar-equipo': { chips: ['IDE Academy', 'Formación In Company'], body: 'Desarrollamos capacidades alineadas con los desafíos reales de tu organización. La formación parte del contexto, no de un catálogo estándar.' }, 'transformar-digital': { chips: ['IDE Digital', 'IA aplicada'], body: 'Empezamos por entender tu organización para identificar dónde la tecnología realmente agrega valor, y avanzamos desde ahí.' } }; const m = map[p2] || { chips: ['Ecosistema IDE'], body: 'Entendemos dónde querés llegar. Comprendemos el sistema para ayudarte a lograrlo.' }; return { title: 'Entendemos dónde querés llegar. Te ayudamos a definir cómo.', body: m.body, chips: m.chips, cta: 'Explorar por desafío', url: 'desafios.html' }; } // Perfil 1 · Desafío sin solución definida const mapD = { gestion: { chips: ['Procesos', 'Gestión', 'IDE Excellence'] }, proyectos: { chips: ['Proyectos', 'PMO', 'IDE Project'] }, personas: { chips: ['Capacidades', 'Equipo', 'IDE Academy'] }, tecnologia: { chips: ['IA', 'Digital', 'IDE Digital'] }, nosabe: { chips: ['Diagnóstico sistémico', 'Ecosistema IDE'] } }; const md = mapD[p2] || mapD.nosabe; return { title: 'No necesitás tener la respuesta para empezar.', body: 'Empezamos por comprender tu situación, no por ofrecerte una solución predeterminada. Eso diferencia una intervención puntual de una transformación real.', chips: md.chips, cta: 'Explorar desafíos', url: 'desafios.html' }; } // ----- ESTADO ----- let currentStep = 1; let stepHistory = []; let answers = {}; const TOTAL = 4; const container = document.getElementById('quiz-interactive-app'); if (!container) return; // ----- FUNCIONES ----- function showStep(id) { const steps = container.querySelectorAll('.quiz-step'); steps.forEach(s => s.classList.remove('active')); const el = document.getElementById('step-' + id); if (el) el.classList.add('active'); updateProgress(id); updateIndicator(id); } function updateProgress(id) { const fill = document.getElementById('quiz-progress-fill'); if (!fill) return; let n = 1; if (id === 'result') n = 4; else if (['2a', '2b', '2c', '2d'].includes(String(id))) n = 2; else n = parseInt(id) || 1; fill.style.width = (n / TOTAL * 100) + '%'; } function updateIndicator(id) { const label = document.getElementById('quiz-step-indicator'); if (!label) return; let text = ''; if (id === 'result') text = '¡Orientación lista!'; else if (['2a', '2b', '2c', '2d'].includes(String(id))) text = 'Paso 2 de 4'; else text = 'Paso ' + id + ' de 4'; label.textContent = text; } function renderResult(ans) { const r = buildResult(ans); document.getElementById('resultTitle').textContent = r.title; document.getElementById('resultBody').textContent = r.body; const chipsContainer = document.getElementById('resultChips'); chipsContainer.innerHTML = r.chips.map(c => `${c}` ).join(''); const cta = document.getElementById('resultCTA'); cta.textContent = r.cta; cta.href = r.url; showStep('result'); } function handleOption(btn) { const step = btn.dataset.step; const val = btn.dataset.val; // Deseleccionar otras opciones del mismo paso const parent = btn.closest('.quiz-options-grid'); if (parent) { parent.querySelectorAll('.quiz-option-card').forEach(b => b.classList.remove('selected')); } btn.classList.add('selected'); answers[step] = val; stepHistory.push(step); const next = FLOW[step]; if (!next) return; if (next === 'result') { renderResult(answers); return; } const nextId = typeof next === 'object' ? (next[val] || '2a') : next; showStep(nextId); } // ----- FUNCIONES GLOBALES (para onclick en HTML) ----- window.goBack = function () { if (!stepHistory.length) return; const prev = stepHistory.pop(); delete answers[prev]; const last = stepHistory[stepHistory.length - 1] || 1; showStep(last); }; window.restartQuiz = function () { answers = {}; stepHistory = []; container.querySelectorAll('.quiz-option-card').forEach(b => b.classList.remove('selected')); showStep(1); }; // ----- INIT ----- function initQuiz() { // Eventos en las cards de opción container.querySelectorAll('.quiz-option-card').forEach(btn => { btn.addEventListener('click', function () { handleOption(this); }); }); showStep(1); } // Ejecutar al cargar el DOM if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initQuiz); } else { initQuiz(); } })(); /* ========================================================================== 6. Carga Dinámica de Cursos en IDE Academy (cursos.json) ========================================================================== */ const GOOGLE_SHEETS_API_URL = 'https://script.google.com/macros/s/AKfycbzsZUvY29i9w0SEPYqL46SJSr8AqG0kBxKP5-RFJmr2ABd6JaF9L5o4iUhOYUwrDiYn/exec'; let allCourses = []; async function initAcademyCourses() { const container = document.getElementById('academy-courses-container'); if (!container) return; try { // 1. Revisar si tenemos datos cacheados en la sesión para carga ultra rápida const cachedData = sessionStorage.getItem('ide_academy_courses'); if (cachedData) { allCourses = filterVisibleCourses(JSON.parse(cachedData)); renderCourseCards(allCourses, container); } // 2. Traer la información fresca desde Google Sheets const res = await fetch(GOOGLE_SHEETS_API_URL); if (!res.ok) throw new Error('Error al conectar con Google Sheets'); const freshData = await res.json(); // Guardar en caché de sesión sessionStorage.setItem('ide_academy_courses', JSON.stringify(freshData)); // Filtrar y renderizar allCourses = filterVisibleCourses(freshData); renderCourseCards(allCourses, container); } catch (err) { console.warn('Error con Google Sheets, recurriendo a respaldo:', err); // Si falla la red o Google, usamos el fallback if (allCourses.length === 0) { allCourses = filterVisibleCourses(getFallbackCourses()); renderCourseCards(allCourses, container); } } } /** * Filtra cursos por estado: solo admite "Publicado" y "Próximamente" * Normaliza acentos y mayúsculas/minúsculas para evitar errores tipográficos */ function filterVisibleCourses(courses) { if (!Array.isArray(courses)) return []; return courses.filter(course => { if (!course.estado) return false; const cleanStatus = course.estado .trim() .toLowerCase() .normalize("NFD") .replace(/[\u0300-\u036f]/g, ""); // "próximamente" -> "proximamente" return cleanStatus === 'publicado' || cleanStatus === 'proximamente'; }); } function renderCourseCards(courses, container) { container.innerHTML = ''; if (courses.length === 0) { container.innerHTML = `
No hay convocatorias abiertas en este momento. Consultanos por programas a medida o In-Company.
${descripcion}
${propuesta}
${destinatarios}
${art.extracto}
${excerpt}
No encontramos resultados para ${escapeHtml(cleanTerm)}.
Probá con otros términos o contactanos directamente.
${highlight(item.descripcion, terms)}