base de cursos desde google sheets

This commit is contained in:
2026-09-27 17:14:52 -03:00
parent 3cf66793ae
commit c5c589b32e
2 changed files with 193 additions and 57 deletions
+3 -1
View File
@@ -146,6 +146,8 @@
<h2>Elegí qué capacidad querés desarrollar.</h2> <h2>Elegí qué capacidad querés desarrollar.</h2>
<p class="lead-text"> <p class="lead-text">
Cursos y programas diseñados para conectar conocimiento, práctica y contexto, con herramientas que podés llevar a tu realidad profesional desde el primer día. Cursos y programas diseñados para conectar conocimiento, práctica y contexto, con herramientas que podés llevar a tu realidad profesional desde el primer día.
</p>
</div>
</div> </div>
<!-- Contenedor inyectado por JS desde /data/cursos.json --> <!-- Contenedor inyectado por JS desde /data/cursos.json -->
@@ -326,7 +328,7 @@
<div id="modal-course-view" class="modal-backdrop" role="dialog" aria-modal="true"> <div id="modal-course-view" class="modal-backdrop" role="dialog" aria-modal="true">
<div class="modal-window"> <div class="modal-window">
<button class="modal-close-btn" aria-label="Cerrar modal">✕</button> <button class="modal-close-btn" aria-label="Cerrar modal">✕</button>
<span id="modal-course-category" class="eyebrow" style="color: #D97706;">PROGRAMA EJECUTIVO</span> <div id="modal-course-category" class="eyebrow">PROGRAMA EJECUTIVO</div>
<h3 id="modal-course-title" style="margin-bottom: 1.25rem; color: var(--ide-primary);">Título del Curso</h3> <h3 id="modal-course-title" style="margin-bottom: 1.25rem; color: var(--ide-primary);">Título del Curso</h3>
<div id="modal-course-details"> <div id="modal-course-details">
+186 -52
View File
@@ -496,6 +496,8 @@ function closeModal(modalId) {
/* ========================================================================== /* ==========================================================================
6. Carga Dinámica de Cursos en IDE Academy (cursos.json) 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 = []; let allCourses = [];
async function initAcademyCourses() { async function initAcademyCourses() {
@@ -503,43 +505,136 @@ async function initAcademyCourses() {
if (!container) return; if (!container) return;
try { try {
const res = await fetch('/data/cursos.json'); // 1. Revisar si tenemos datos cacheados en la sesión para carga ultra rápida
if (!res.ok) throw new Error('No se pudo cargar cursos.json'); const cachedData = sessionStorage.getItem('ide_academy_courses');
allCourses = await res.json(); if (cachedData) {
renderCourseCards(allCourses, container); allCourses = filterVisibleCourses(JSON.parse(cachedData));
} catch (err) {
console.warn('Cargando cursos con datos de respaldo.');
allCourses = getFallbackCourses();
renderCourseCards(allCourses, container); 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) { function renderCourseCards(courses, container) {
container.innerHTML = ''; container.innerHTML = '';
if (courses.length === 0) {
container.innerHTML = `
<div class="card text-center" style="grid-column: 1 / -1; padding: 2.5rem;">
<p style="color: var(--text-muted); font-size: 1rem;">
No hay convocatorias abiertas en este momento. Consultanos por programas a medida o In-Company.
</p>
</div>
`;
return;
}
courses.forEach(course => { courses.forEach(course => {
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'card'; card.className = 'card';
card.innerHTML = ` card.style.display = 'flex';
<div class="ecosystem-badge badge-academy" style="margin-bottom: 0.8rem;">${course.categoria}</div> card.style.flexDirection = 'column';
<h3 style="font-size: 1.15rem; margin-bottom: 0.6rem;">${course.titulo}</h3> card.style.justifyContent = 'space-between';
<p style="font-size: 0.88rem; color: var(--text-muted); margin-bottom: 1.25rem; line-height: 1.5;">${course.descripcion_corta}</p>
<div style="font-size: 0.82rem; color: var(--text-body); display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 1.5rem; border-top: 1px solid var(--border-color); padding-top: 0.85rem;"> const area = course.area_tematica || course.categoria || 'IDE Academy';
<div><strong>Modalidad:</strong> ${course.modalidad}</div> const tipo = course.tipo ? ` • ${course.tipo}` : '';
<div><strong>Duración:</strong> ${course.duracion}</div> const proxima = course.proxima_edicion || course.proxima_fecha || 'A confirmar';
<div><strong>Próxima fecha:</strong> ${course.proxima_fecha}</div> const duracion = course.duracion || 'A definir';
const modalidad = course.modalidad || 'Consultar';
const nivel = course.nivel || 'General';
const descripcion = course.propuesta_formativa || course.descripcion_corta || '';
// Estilo distintivo sutil si está en "Próximamente"
const isProximamente = course.estado.toLowerCase().includes('prox');
const badgeProximo = isProximamente
? `<span style="font-size: 0.72rem; background: var(--bg-subtle); padding: 0.2rem 0.5rem; border-radius: 4px; color: #D97706; font-weight: 600; border: 1px solid rgba(217, 119, 6, 0.2);">Próximamente</span>`
: '';
card.innerHTML = `
<div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.8rem; gap: 0.5rem; flex-wrap: wrap;">
<div class="ecosystem-badge badge-academy">${area}${tipo}</div>
${badgeProximo}
</div>
<h3 style="font-size: 1.15rem; margin-bottom: 0.6rem; line-height: 1.35;">${course.titulo}</h3>
<p style="font-size: 0.88rem; color: var(--text-muted); margin-bottom: 1.25rem; line-height: 1.5;">${descripcion}</p>
</div>
<div>
<div style="font-size: 0.82rem; color: var(--text-body); display: flex; flex-direction: column; gap: 0.4rem; margin-bottom: 1.5rem; border-top: 1px solid var(--border-color); padding-top: 0.85rem;">
<div><strong>Modalidad:</strong> ${modalidad}</div>
<div><strong>Duración:</strong> ${duracion}</div>
<div><strong>Nivel:</strong> ${nivel}</div>
<div><strong>Próxima edición:</strong> <span style="${proxima.toLowerCase() === 'a confirmar' ? 'color: #D97706; font-weight: 600;' : ''}">${proxima}</span></div>
</div> </div>
<button class="btn btn-outline btn-sm w-full" style="width: 100%;" onclick="openCourseModal('${course.id}')"> <button class="btn btn-outline btn-sm w-full" style="width: 100%;" onclick="openCourseModal('${course.id}')">
Ver Ficha Técnica y Programa Ver Ficha Técnica y Programa
</button> </button>
</div>
`; `;
container.appendChild(card); container.appendChild(card);
}); });
} }
// Modal dinámico de cursos // Helper para convertir tanto arrays como textos planos con viñetas en <li> HTML
function renderListItems(data) {
if (!data) return '<li>A consultar con el equipo académico.</li>';
if (Array.isArray(data)) {
return data.map(item => `<li>${item}</li>`).join('');
}
if (typeof data === 'string') {
return data
.split('\n')
.map(line => line.replace(/^[•\-\*\d+\.\s]+/, '').trim())
.filter(line => line.length > 0)
.map(line => `<li>${line}</li>`)
.join('');
}
return `<li>${data}</li>`;
}
// Modal dinámico de cursos adaptado a la ficha de administración
window.openCourseModal = function (courseId) { window.openCourseModal = function (courseId) {
const course = allCourses.find(c => c.id === courseId); const course = allCourses.find(c => c.id === courseId);
if (!course) return; if (!course) return;
@@ -551,10 +646,20 @@ window.openCourseModal = function (courseId) {
if (titleElem) titleElem.innerText = course.titulo; 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) { if (catElem) {
catElem.innerHTML = ` catElem.innerHTML = `
<div class="course-header-top"> <div class="course-header-top" style="margin-bottom: 0.8rem; display: flex; align-items: center; gap: 0.5rem;">
<span class="course-badge-category">${course.categoria}</span> <span class="course-badge-category" style="text-transform: uppercase; font-size: 0.8rem; letter-spacing: 0.05em; font-weight: 700;">
${area} • ${tipo}
</span>
<span style="font-size: 0.75rem; color: var(--text-muted); font-family: monospace;">[${course.id}]</span>
</div> </div>
<div class="course-specs-panel"> <div class="course-specs-panel">
@@ -569,7 +674,7 @@ window.openCourseModal = function (courseId) {
</div> </div>
<div class="spec-text"> <div class="spec-text">
<span class="spec-label">Modalidad</span> <span class="spec-label">Modalidad</span>
<strong class="spec-value">${course.modalidad}</strong> <strong class="spec-value">${modalidad}</strong>
</div> </div>
</div> </div>
@@ -583,25 +688,26 @@ window.openCourseModal = function (courseId) {
</div> </div>
<div class="spec-text"> <div class="spec-text">
<span class="spec-label">Duración</span> <span class="spec-label">Duración</span>
<strong class="spec-value">${course.duracion}</strong> <strong class="spec-value">${duracion}</strong>
</div> </div>
</div> </div>
<!-- Sector --> <!-- Nivel (Reemplaza Sector) -->
<div class="spec-item"> <div class="spec-item">
<div class="spec-icon-box spec-icon-teal"> <div class="spec-icon-box spec-icon-teal">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect> <polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path> <polyline points="2 17 12 22 22 17"></polyline>
<polyline points="2 12 12 17 22 12"></polyline>
</svg> </svg>
</div> </div>
<div class="spec-text"> <div class="spec-text">
<span class="spec-label">Sector</span> <span class="spec-label">Nivel</span>
<strong class="spec-value">${course.sector || '—'}</strong> <strong class="spec-value">${nivel}</strong>
</div> </div>
</div> </div>
<!-- Próxima Fecha --> <!-- Próxima Edición -->
<div class="spec-item"> <div class="spec-item">
<div class="spec-icon-box spec-icon-amber"> <div class="spec-icon-box spec-icon-amber">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -612,8 +718,10 @@ window.openCourseModal = function (courseId) {
</svg> </svg>
</div> </div>
<div class="spec-text"> <div class="spec-text">
<span class="spec-label">Próxima fecha</span> <span class="spec-label">Próxima edición</span>
<strong class="spec-value highlight-date">${course.proxima_fecha || 'A consultar'}</strong> <strong class="spec-value highlight-date" style="${proxima.toLowerCase() === 'a confirmar' ? 'color: #D97706;' : ''}">
${proxima}
</strong>
</div> </div>
</div> </div>
</div> </div>
@@ -621,32 +729,46 @@ window.openCourseModal = function (courseId) {
} }
if (detailsElem) { if (detailsElem) {
const objHtml = course.objetivos.map(obj => `<li>${obj}</li>`).join(''); const objetivosList = course.que_vas_a_desarrollar || course.objetivos;
const temHtml = course.temario.map(tem => `<li>${tem}</li>`).join(''); 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 = ` detailsElem.innerHTML = `
<!-- Propuesta Formativa -->
<div style="margin-bottom: 1.5rem;"> <div style="margin-bottom: 1.5rem;">
<h4 style="font-size: 1rem; color: var(--ide-primary); margin-bottom: 0.4rem;">Propuesta Formativa</h4> <h4 style="font-size: 1rem; color: var(--ide-primary); margin-bottom: 0.4rem;">Propuesta Formativa</h4>
<p style="font-size: 0.92rem; color: var(--text-body);">${course.propuesta}</p> <p style="font-size: 0.92rem; color: var(--text-body); line-height: 1.55;">${propuesta}</p>
</div> </div>
<!-- Destinatarios (Nuevo campo) -->
<div style="margin-bottom: 1.5rem; background: rgba(0,0,0,0.02); padding: 0.85rem; border-left: 3px solid var(--ide-primary); border-radius: 0 var(--radius-sm) var(--radius-sm) 0;">
<h4 style="font-size: 0.92rem; color: var(--ide-primary); margin-bottom: 0.25rem;">Destinatarios</h4>
<p style="font-size: 0.88rem; color: var(--text-body); margin: 0;">${destinatarios}</p>
</div>
<!-- Qué vas a desarrollar -->
<div style="margin-bottom: 1.5rem;"> <div style="margin-bottom: 1.5rem;">
<h4 style="font-size: 1rem; color: var(--ide-primary); margin-bottom: 0.4rem;">Objetivos del Programa</h4> <h4 style="font-size: 1rem; color: var(--ide-primary); margin-bottom: 0.4rem;">Qué vas a desarrollar</h4>
<ul style="padding-left: 1.25rem; font-size: 0.9rem; color: var(--text-body); line-height: 1.6;"> <ul style="padding-left: 1.25rem; font-size: 0.9rem; color: var(--text-body); line-height: 1.6;">
${objHtml} ${renderListItems(objetivosList)}
</ul> </ul>
</div> </div>
<!-- Recorrido de aprendizaje -->
<div style="margin-bottom: 1.5rem;"> <div style="margin-bottom: 1.5rem;">
<h4 style="font-size: 1rem; color: var(--ide-primary); margin-bottom: 0.4rem;">Temario Modular</h4> <h4 style="font-size: 1rem; color: var(--ide-primary); margin-bottom: 0.4rem;">Recorrido de Aprendizaje</h4>
<ul style="padding-left: 1.25rem; font-size: 0.9rem; color: var(--text-body); line-height: 1.6;"> <ul style="padding-left: 1.25rem; font-size: 0.9rem; color: var(--text-body); line-height: 1.6; list-style-type: decimal;">
${temHtml} ${renderListItems(temarioList)}
</ul> </ul>
</div> </div>
<div style="background-color: var(--bg-subtle); padding: 1rem; border-radius: var(--radius-sm); font-size: 0.88rem; margin-bottom: 1.5rem;"> <!-- Docente e Info Comercial -->
<p><strong>Cuerpo Docente:</strong> ${course.docente}</p> <div style="background-color: var(--bg-subtle); padding: 1.1rem; border-radius: var(--radius-sm); font-size: 0.88rem; margin-bottom: 1.5rem; display: flex; flex-direction: column; gap: 0.5rem;">
<p><strong>Impacto Esperado:</strong> ${course.resultados_esperados}</p> <div><strong>Equipo Docente:</strong> ${docente}</div>
<div><strong>Información y Condiciones:</strong> ${comercial}</div>
</div> </div>
`; `;
} }
@@ -655,7 +777,7 @@ window.openCourseModal = function (courseId) {
const params = new URLSearchParams({ const params = new URLSearchParams({
origen: 'IDE_Academy', origen: 'IDE_Academy',
busqueda: 'Formación para mi organización', busqueda: 'Formación para mi organización',
curso: course.titulo curso: `${course.titulo} (${course.id})`
}); });
ctaElem.setAttribute('href', `contacto.html?${params.toString()}`); ctaElem.setAttribute('href', `contacto.html?${params.toString()}`);
} }
@@ -666,18 +788,30 @@ window.openCourseModal = function (courseId) {
function getFallbackCourses() { function getFallbackCourses() {
return [ return [
{ {
id: "curso-iso-9001", id: "ACA-GC-001",
titulo: "Sistemas de Gestión de Calidad (ISO 9001:2015): De la Norma a la Gestión Real", estado: "Próximamente",
categoria: "IDE Excellence & Academy", titulo: "Calidad que genera valor: De la norma a la gestión real",
modalidad: "Online sincrónico / In-Company", area_tematica: "Gestión & Calidad",
duracion: "32 horas (8 semanas)", tipo: "Curso",
descripcion_corta: "Aprenda a diseñar, implementar y auditar un SGC que trascienda la mera auditoría y transforme los procesos.", modalidad: "Online sincrónico",
propuesta: "Una visión viva y práctica para la toma de decisiones estratégicas.", duracion: "12 horas – 4 encuentros",
objetivos: ["Comprender la estructura ISO 9001:2015", "Mapear procesos con enfoque de riesgos", "Diseñar KPIs pertinentes"], nivel: "Intermedio",
temario: ["Módulo 1: Enfoque a procesos", "Módulo 2: Liderazgo y calidad", "Módulo 3: Auditorías y mejora continua"], destinatarios: "Responsables de calidad, líderes de procesos y mandos medios.",
docente: "Equipo Senior de Consultores IDE Excellence.", propuesta_formativa: "Una experiencia práctica y estratégica para transformar el sistema de calidad en una herramienta real de gestión.",
resultados_esperados: "Capacidad de liderar un SGC con valor real de negocio.", que_vas_a_desarrollar: [
proxima_fecha: "Octubre 2026" "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."
} }
]; ];
} }