Tus cursos seguidos

Importar favoritos

Pega un JSON con IDs de cursos. Ejemplo: ["curso-1","curso-2"]

Aviso

Imagen del curso

'; }catch{ qs('#app-footer').innerHTML = ' '; } } function systemPrefersDark(){return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;} function applyTheme(){ const saved=storage.get(KEY_THEME,null); const mode = saved ?? (systemPrefersDark() ? 'dark' : 'light'); document.documentElement.classList.toggle('dark', mode==='dark'); themeToggleBtn.textContent = mode==='dark' ? '☀️' : '🌙'; } function toggleTheme(){ const currentDark=document.documentElement.classList.contains('dark'); const next=currentDark ? 'light' : 'dark'; storage.set(KEY_THEME,next); applyTheme(); } async function fetchCatalog(){ try{ const res=await fetch('./catalog.json',{cache:'no-store'}); if(!res.ok) throw new Error('No se pudo cargar catalog.json'); const data=await res.json(); CATALOG = Array.isArray(data) ? data : (Array.isArray(data.items)?data.items:[]); }catch(e){ openAlert('No se pudo cargar el catálogo. Intenta recargar la página.'); CATALOG=[]; } } function getFavs(){ const arr=storage.get(KEY_FAVS,[]); return Array.isArray(arr)?new Set(arr.filter(x=>typeof x==='string')):new Set(); } function setFavs(set){ const arr=[...set]; storage.set(KEY_FAVS,arr); FAVS=new Set(arr); return arr; } function addFav(id){FAVS.add(id);setFavs(FAVS);renderList();} function removeFav(id){FAVS.delete(id);setFavs(FAVS);renderList();} function openAlert(msg){ alertaTexto.textContent=msg; if(typeof modalAlerta.showModal==='function'){modalAlerta.showModal();} } function openImportModal(){ jsonInput.value=''; reemplazarModo.checked=true; if(typeof modalImportar.showModal==='function'){modalImportar.showModal();} } function closeAllModals(){ [modalImportar,modalAlerta,modalCurso].forEach(d=>{if(d && d.open) d.close();}); } function renderList(){ listaEl.innerHTML=''; const items=[...FAVS].map(id=>CATALOG.find(x=>String(x.id)===String(id))).filter(Boolean); if(items.length===0){ vacioEl.classList.remove('hidden'); return; } vacioEl.classList.add('hidden'); for(const item of items){ listaEl.appendChild(renderCard(item)); } } function ratingStars(r=0){ const val=Math.max(0,Math.min(5, Number(r)||0)); const full='★'.repeat(Math.floor(val)); const empty='☆'.repeat(5-Math.floor(val)); return `${full}${empty}(${val.toFixed(1)})`; } function renderCard(item){ const li=document.createElement('li'); li.className='rounded-xl border border-gray-200 dark:border-neutral-700 overflow-hidden bg-white dark:bg-neutral-800 flex flex-col hover:shadow-md transition j3m7d'; const imgSrc = item.image || './images/maximum_ditailes_of_this_image.educational_course_card_cover_minimal_geometric_shapes_vibrant_but_soft_tones_high_resolution.jpg'; const title = item.title || item.name || 'Curso sin título'; const cat = item.category || 'General'; const price = item.price!=null ? item.price : 'Gratis'; const duration = item.duration || '—'; const rating = item.rating || 0; li.innerHTML=`
Portada del curso: ${title}

${title}

${cat}
${ratingStars(rating)}
${price=== 'Gratis' ? 'Gratis' : (typeof price==='number' ? '€'+price.toFixed(2) : price)} ${duration}
`; li.addEventListener('click',onCardAction); return li; } function onCardAction(e){ const btn=e.target.closest('button'); if(!btn) return; const id=btn.getAttribute('data-id'); const action=btn.getAttribute('data-action'); if(action==='fav'){ removeFav(id); showToast('Eliminado de favoritos'); } if(action==='detalle'){ openCourseModal(id); } if(action==='share'){ const payload={type:'trivenza:favorito',id}; const txt=JSON.stringify(payload); navigator.clipboard?.writeText(txt).then(()=>showToast('Copiado al portapapeles')).catch(()=>openAlert('Copia este JSON manualmente: '+txt)); } } function openCourseModal(id){ const item=CATALOG.find(x=>String(x.id)===String(id)); if(!item){openAlert('Curso no encontrado');return;} selectedCourseId=String(id); cursoImg.src=item.image || './images/maximum_ditailes_of_this_image.generic_course_placeholder_clean_minimal_illustration_dark_light_contrast_high_quality.jpg'; cursoTitulo.textContent=item.title || item.name || 'Curso sin título'; cursoDescripcion.textContent=item.description || 'Sin descripción.'; cursoCategoria.innerHTML=`Categoría${item.category || 'General'}`; cursoDuracion.innerHTML=`Duración${item.duration || '—'}`; cursoValoracion.innerHTML=`Valoración${ratingStars(item.rating||0)}`; const price = item.price!=null ? (typeof item.price==='number' ? '€'+item.price.toFixed(2) : String(item.price)) : 'Gratis'; cursoPrecio.innerHTML=`Precio${price}`; if(typeof modalCurso.showModal==='function'){modalCurso.showModal();} } function exportFavs(){ const arr=[...FAVS]; const json=JSON.stringify(arr,null,2); try{ const blob=new Blob([json],{type:'application/json;charset=utf-8'}); const a=document.createElement('a'); const ts=new Date(); const pad=n=>String(n).padStart(2,'0'); const name=`trivenza-favoritos-${ts.getFullYear()}${pad(ts.getMonth()+1)}${pad(ts.getDate())}-${pad(ts.getHours())}${pad(ts.getMinutes())}${pad(ts.getSeconds())}.json`; a.href=URL.createObjectURL(blob); a.download=name; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(a.href); }catch(e){} navigator.clipboard?.writeText(json).then(()=>showToast('Exportado y copiado')).catch(()=>showToast('Exportado')); } function validateImportArray(a){ if(!Array.isArray(a)) return {ok:false,err:'JSON debe ser un array de IDs'}; const ids=a.map(x=>String(x)).filter(x=>x.trim().length>0); if(ids.length===0) return {ok:false,err:'La lista está vacía'}; return {ok:true,ids:[...new Set(ids)]}; } function applyImportFromTextarea(){ let parsed=null; try{ parsed=JSON.parse(jsonInput.value.trim()); }catch(e){ openAlert('JSON inválido. Revisa el formato.'); return; } const check=validateImportArray(parsed); if(!check.ok){ openAlert(check.err); return; } const idsSet=new Set(check.ids); if(reemplazarModo.checked){ setFavs(idsSet); }else{ for(const id of idsSet) FAVS.add(id); setFavs(FAVS); } modalImportar.close(); renderList(); const unknown=[...FAVS].filter(id=>!CATALOG.find(x=>String(x.id)===String(id))); if(unknown.length>0){ openAlert('Se han importado favoritos. Algunos IDs no están en el catálogo actual: '+unknown.slice(0,5).join(', ')+(unknown.length>5?' y más...':'')); }else{ showToast('Favoritos importados'); } } function setupDialogs(){ qsa('[data-close-modal]').forEach(btn=>{ btn.addEventListener('click',(e)=>{ e.preventDefault(); const d=btn.closest('dialog'); if(d && d.open) d.close(); }); }); jsonAplicar.addEventListener('click',applyImportFromTextarea); cursoQuitar.addEventListener('click',(e)=>{ e.preventDefault(); if(selectedCourseId){ removeFav(selectedCourseId); showToast('Eliminado de favoritos'); } if(modalCurso.open) modalCurso.close(); }); importBtn.addEventListener('click',openImportModal); abrirImportarBtn.addEventListener('click',openImportModal); qs('#mostrar-ayuda').addEventListener('click',()=>{ openAlert('Añade cursos a favoritos desde el catálogo. Aquí verás los guardados. También puedes importar una lista de IDs usando el botón Importar JSON.'); }); } function setupCookieBanner(){ const accepted=storage.get(KEY_COOKIES,false); if(!accepted){ cookieBanner.classList.remove('hidden'); } function closeCookie(){ cookieBanner.classList.add('hidden'); } cookieAceptar.addEventListener('click',()=>{ storage.set(KEY_COOKIES,true); closeCookie(); showToast('Preferencias de cookies guardadas'); }); cookieCerrar.addEventListener('click',closeCookie); } function setupBackupReminder(){ backupCountdown=60; backupCta.classList.remove('hidden'); function tick(){ backupCountdown--; backupTimerEl.textContent=backupCountdown+'s'; if(backupCountdown<=0){ clearInterval(backupInterval); backupCta.classList.add('hidden'); showToast('Recuerda exportar tus favoritos como copia de seguridad'); // Reiniciar de nuevo setTimeout(setupBackupReminder,5000); } } backupTimerEl.textContent=backupCountdown+'s'; clearInterval(backupInterval); backupInterval=setInterval(tick,1000); } function setupTheme(){ applyTheme(); themeToggleBtn.addEventListener('click',toggleTheme); if(window.matchMedia){ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change',()=>{ if(storage.get(KEY_THEME,null)===null){ applyTheme(); } }); } } function setupExport(){ exportBtn.addEventListener('click',()=>{ if(FAVS.size===0){ openAlert('No hay favoritos para exportar.'); return; } exportFavs(); }); } async function init(){ await loadHeaderFooter(); setupTheme(); setupDialogs(); setupCookieBanner(); setupExport(); setupBackupReminder(); FAVS=getFavs(); await fetchCatalog(); renderList(); } document.addEventListener('keydown',(e)=>{ if(e.key==='Escape'){ closeAllModals(); } }); window.addEventListener('DOMContentLoaded',init);