/** * 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'); const backBtn = document.getElementById('global-quiz-back-btn'); if (backBtn) { if (id == 1 || id === '1' || id === 'result') { backBtn.style.display = 'none'; } else { backBtn.style.display = 'block'; } } 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) { // El indicador de pasos (Paso X de 4) fue removido del HTML a petición del usuario. } 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 = ''; // Limitar a 3 cursos (opción A del brief) const visibleCourses = courses.slice(0, 3); if (visibleCourses.length === 0) { container.innerHTML = `

No hay convocatorias abiertas en este momento. Consultanos por programas a medida o In-Company.

`; return; } // Mapeo de modalidad → icono FontAwesome const getModalityIcon = (modalidad = '') => { const m = modalidad.toLowerCase(); if (m.includes('online') || m.includes('virtual') || m.includes('remoto')) return 'fa-laptop'; if (m.includes('presencial')) return 'fa-building'; if (m.includes('hibr') || m.includes('mix')) return 'fa-rotate'; return 'fa-globe'; }; // Mapeo de nivel → icono FontAwesome (variando intensidad visual) const getLevelIcon = (nivel = '') => { const n = nivel.toLowerCase(); if (n.includes('inicial') || n.includes('básic') || n.includes('basic')) return 'fa-signal'; if (n.includes('intermed')) return 'fa-signal'; if (n.includes('avanz') || n.includes('experto')) return 'fa-signal'; return 'fa-layer-group'; }; visibleCourses.forEach(course => { const card = document.createElement('article'); card.className = 'card-a curso-card'; const area = course.area_tematica || course.categoria || 'IDE Academy'; const tipo = course.tipo || ''; const proxima = course.proxima_edicion || course.proxima_fecha || 'A confirmar'; const duracion = course.duracion || 'A definir'; const modalidad = course.modalidad || 'Consultar'; const nivel = course.nivel || 'General'; const descripcion = course.propuesta_formativa || course.descripcion_corta || ''; const isProximamente = (course.estado || '').toLowerCase().includes('prox'); const proximaEsAConfirmar = proxima.toLowerCase() === 'a confirmar'; card.innerHTML = `
${area}${tipo ? ' • ' + tipo : ''} ${isProximamente ? 'Próximamente' : ''}

${course.titulo}

${descripcion}

`; container.appendChild(card); }); } // Helper para convertir tanto arrays como textos planos con viñetas en
  • HTML function renderListItems(data) { if (!data) return '
  • A consultar con el equipo académico.
  • '; if (Array.isArray(data)) { return data.map(item => `
  • ${item}
  • `).join(''); } if (typeof data === 'string') { return data .split('\n') .map(line => line.replace(/^[•\-\*\d+\.\s]+/, '').trim()) .filter(line => line.length > 0) .map(line => `
  • ${line}
  • `) .join(''); } return `
  • ${data}
  • `; } // Modal dinámico de cursos adaptado a la ficha de administración 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; const area = course.area_tematica || course.categoria || 'IDE Academy'; const tipo = course.tipo || 'Curso'; const modalidad = course.modalidad || 'Online'; const duracion = course.duracion || 'A definir'; const nivel = course.nivel || 'Todos los niveles'; const proxima = course.proxima_edicion || course.proxima_fecha || 'A confirmar'; if (catElem) { catElem.innerHTML = `
    ${area} • ${tipo} [${course.id}]
    Modalidad ${modalidad}
    Duración ${duracion}
    Nivel ${nivel}
    Próxima edición ${proxima}
    `; } if (detailsElem) { const objetivosList = course.que_vas_a_desarrollar || course.objetivos; const temarioList = course.recorrido_aprendizaje || course.temario; const propuesta = course.propuesta_formativa || course.propuesta || ''; const destinatarios = course.destinatarios || 'Profesionales interesados en la materia.'; const docente = course.equipo_docente || course.docente || 'Equipo IDE Consultores'; const comercial = course.informacion_comercial || course.resultados_esperados || 'Disponible para formato abierto o In-Company.'; detailsElem.innerHTML = `

    Propuesta Formativa

    ${propuesta}

    Destinatarios

    ${destinatarios}

    Qué vas a desarrollar

    Recorrido de Aprendizaje

    Equipo Docente: ${docente}
    Información y Condiciones: ${comercial}
    `; } if (ctaElem) { const params = new URLSearchParams({ origen: 'IDE_Academy', busqueda: 'Formación para mi organización', curso: `${course.titulo} (${course.id})` }); ctaElem.setAttribute('href', `contacto.html?${params.toString()}`); } openModal('modal-course-view'); }; function getFallbackCourses() { return [ { id: "ACA-GC-001", estado: "Próximamente", titulo: "Calidad que genera valor: De la norma a la gestión real", area_tematica: "Gestión & Calidad", tipo: "Curso", modalidad: "Online sincrónico", duracion: "12 horas – 4 encuentros", nivel: "Intermedio", destinatarios: "Responsables de calidad, líderes de procesos y mandos medios.", propuesta_formativa: "Una experiencia práctica y estratégica para transformar el sistema de calidad en una herramienta real de gestión.", que_vas_a_desarrollar: [ "Capacidad para diagnosticar cuellos de botella reales.", "Diseño de indicadores con impacto operativo.", "Liderazgo en auditorías con enfoque de valor." ], recorrido_aprendizaje: [ "1. De cumplir normas a gestionar resultados.", "2. Mapeo y rediseño de procesos críticos.", "3. Indicadores clave y tableros de gestión.", "4. Auditoría interna con valor agregado." ], equipo_docente: "Equipo IDE Excellence", proxima_edicion: "A confirmar", informacion_comercial: "Próxima edición con cupos limitados. Consultar por ediciones In-Company." } ]; } /* ========================================================================== 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'); console.log('🔍 Filtros encontrados:', filterBtns.length); console.log('📦 Tarjetas encontradas:', solutionCards.length); if (!filterBtns.length) { console.warn('⚠️ No se encontraron botones de filtro'); return; } filterBtns.forEach(btn => { btn.addEventListener('click', () => { filterBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); const filter = btn.getAttribute('data-filter'); console.log('🎯 Filtro seleccionado:', filter); solutionCards.forEach(card => { const cat = card.getAttribute('data-category'); console.log(` 📌 Tarjeta: ${cat} → ${filter === 'all' || cat === filter ? 'MOSTRAR' : 'OCULTAR'}`); if (filter === 'all' || cat === filter) { card.classList.remove('hidden'); } else { card.classList.add('hidden'); } }); }); }); } /* ========================================================================== 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 extractFirstImage(post) { // 1. Intentar obtener la imagen destacada oficial gracias al ?_embed if (post._embedded && post._embedded['wp:featuredmedia'] && post._embedded['wp:featuredmedia'][0]) { const featuredMedia = post._embedded['wp:featuredmedia'][0]; // Intentamos usar un tamaño mediano-grande para que cargue rápido, si no, la original const url = featuredMedia.media_details?.sizes?.medium_large?.source_url || featuredMedia.source_url; if (url) return url; } // 2. Respaldo: Si el artículo no tiene imagen destacada, busca la primera dentro del texto const contentHtml = post.content?.rendered || ''; const match = contentHtml.match(/]+src=["']([^"']+)["']/i); if (match && match[1]) { let url = match[1]; if (url.startsWith('/')) { url = 'https://blog.ideconsultores.com.ar' + url; } return url; } return null; } 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' }); const imageUrl = extractFirstImage(post); const imageHtml = imageUrl ? `
    ${title}
    ` : `
    📰
    Sin imagen destacada
    `; return `
    ${imageHtml}
    ${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'; } } }); } /* ========================================================================== 11. Video de Insights ========================================================================== */ function initHeroVideoSpeed() { const video = document.getElementById('hero-bg-video'); if (!video) return; const targetSpeed = 0.5; // 50% de la velocidad normal // 1. Forzar mute por propiedad nativa JS (garantiza el autoplay sin bloqueo) video.muted = true; video.defaultMuted = true; video.playsInline = true; const applySpeedAndPlay = () => { video.playbackRate = targetSpeed; // Si quedó en pausa, disparar play explícito atrapando la promesa if (video.paused) { const playPromise = video.play(); if (playPromise !== undefined) { playPromise.catch(err => { console.warn('El navegador requirió interacción previa:', err); }); } } }; // 2. Si el video ya está listo o ya empezó a correr if (video.readyState >= 2) { applySpeedAndPlay(); } else { video.addEventListener('canplay', applySpeedAndPlay, { once: true }); video.addEventListener('loadeddata', applySpeedAndPlay, { once: true }); } // 3. Garantizar que tras el loop o reanudación conserve la velocidad video.addEventListener('play', () => { video.playbackRate = targetSpeed; }); } /* ========================================================================== 12. BÚSQUEDA EN EL SITIO — Página buscar.html ========================================================================== */ const SEARCH_INDEX_URL = 'data/search-index.json'; async function initSearchPage() { const form = document.getElementById('search-page-form'); const input = document.getElementById('search-page-input'); const results = document.getElementById('search-results'); const status = document.getElementById('search-status'); const suggestions = document.getElementById('search-suggestions'); // Si no estamos en buscar.html, salir silenciosamente if (!form || !input || !results) return; let index = []; // Cargar índice try { const res = await fetch(SEARCH_INDEX_URL, { cache: 'no-store' }); if (res.ok) { index = await res.json(); } else { console.warn('No se pudo cargar search-index.json:', res.status); } } catch (e) { console.warn('Error cargando search-index.json:', e); } // Normalizar texto (sin acentos, minúsculas) const normalize = (str) => (str || '') .toString() .toLowerCase() .normalize('NFD') .replace(/[\u0300-\u036f]/g, ''); // Escapar HTML const escapeHtml = (str) => (str || '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); // Scoring por relevancia function scoreItem(item, terms) { const title = normalize(item.titulo); const desc = normalize(item.descripcion); const kw = normalize(item.keywords); let score = 0; terms.forEach(term => { if (!term) return; if (title.includes(term)) score += 10; if (kw.includes(term)) score += 6; if (desc.includes(term)) score += 3; }); // Bonus si la frase completa aparece en el título const fullPhrase = terms.join(' '); if (fullPhrase && title.includes(fullPhrase)) score += 5; return score; } // Resaltar coincidencias function highlight(text, terms) { let safe = escapeHtml(text); terms.forEach(term => { if (term.length < 2) return; const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const regex = new RegExp(`(${escaped})`, 'gi'); safe = safe.replace(regex, '$1'); }); return safe; } // Render de resultados function render(term) { results.innerHTML = ''; const cleanTerm = (term || '').trim(); if (!cleanTerm) { if (status) status.textContent = ''; if (suggestions) suggestions.hidden = false; return; } if (suggestions) suggestions.hidden = true; const terms = normalize(cleanTerm).split(/\s+/).filter(Boolean); const scored = index .map(item => ({ item, score: scoreItem(item, terms) })) .filter(r => r.score > 0) .sort((a, b) => b.score - a.score); if (scored.length === 0) { if (status) status.textContent = ''; results.innerHTML = `

    No encontramos resultados para ${escapeHtml(cleanTerm)}.

    Probá con otros términos o contactanos directamente.

    `; return; } if (status) { status.textContent = `${scored.length} resultado${scored.length !== 1 ? 's' : ''} para "${cleanTerm}"`; } results.innerHTML = scored.map(({ item }) => `
    ${escapeHtml(item.categoria)}

    ${highlight(item.titulo, terms)}

    ${highlight(item.descripcion, terms)}

    `).join(''); } // Leer ?q= de la URL al cargar const params = new URLSearchParams(window.location.search); const initialQuery = params.get('q') || ''; if (initialQuery) { input.value = initialQuery; render(initialQuery); } else { if (suggestions) suggestions.hidden = false; // Foco automático al llegar sin query setTimeout(() => input.focus(), 100); } // Submit del form form.addEventListener('submit', (e) => { e.preventDefault(); const q = input.value.trim(); const newUrl = q ? `?q=${encodeURIComponent(q)}` : window.location.pathname; window.history.replaceState({}, '', newUrl); render(q); }); // Búsqueda en vivo con debounce let debounceTimer; input.addEventListener('input', () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { render(input.value); }, 220); }); } document.addEventListener('DOMContentLoaded', function() { // Seleccionar todas las tarjetas con data-open-modal const tarjetas = document.querySelectorAll('[data-open-modal]'); tarjetas.forEach(function(tarjeta) { const modalId = tarjeta.getAttribute('data-open-modal'); const modal = document.getElementById(modalId); if (!modal) return; const closeBtn = modal.querySelector('.modal-close-btn'); // Abrir modal al hacer click en la tarjeta tarjeta.addEventListener('click', function(e) { // Evita que se abra si el click fue en el botón/link interno if (e.target.closest('.solucion-card__btn')) return; modal.classList.add('is-active'); document.body.style.overflow = 'hidden'; }); // Abrir con Enter o Space (accesibilidad) tarjeta.addEventListener('keydown', function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); modal.classList.add('is-active'); document.body.style.overflow = 'hidden'; } }); // Cerrar con botón X if (closeBtn) { closeBtn.addEventListener('click', function() { modal.classList.remove('is-active'); document.body.style.overflow = ''; }); } // Cerrar al hacer click fuera del modal modal.addEventListener('click', function(e) { if (e.target === modal) { modal.classList.remove('is-active'); document.body.style.overflow = ''; } }); // Cerrar con tecla ESC document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && modal.classList.contains('is-active')) { modal.classList.remove('is-active'); document.body.style.overflow = ''; } }); }); });