Saltar al contenido
Catálogo interactivo
Busca, filtra por nivel y formato, ordena por precio o valoración, guarda en favoritos y añade al carrito. Abre detalles en un modal con temario y fechas.
Mostrando en la página actual
En base a cursos visibles
Página
Anterior
Siguiente
Sugerencia
Usa para combinar categoría + nivel + formato. Añade a para comparar después.
Arriba
Ver favoritos
Marca cursos con el corazón en el catálogo o desde el modal de detalles.
Se guarda en tu navegador
Usamos cookies para recordar tu tema, favoritos y carrito. Puedes aceptar o rechazar las no esenciales.
Rechazar
';
if(f.status === 'fulfilled') c6p9d.mountFooter.innerHTML = f.value;
else c6p9d.mountFooter.innerHTML = '';
const tgl = document.getElementById('vpThemeToggle');
if(tgl){
tgl.addEventListener('click', ()=>{
const cur = localStorage.getItem('theme') || 'light';
const next = cur === 'dark' ? 'light' : 'dark';
try{ localStorage.setItem('theme', next); }catch(e){}
document.documentElement.classList.toggle('dark', next === 'dark');
t8c2y('Tema actualizado: ' + (next === 'dark' ? 'oscuro' : 'claro'), 'ok', 2200);
});
}
}
function g2h8f(){
const consent = x1y7k();
s4r1v.cookieConsent = consent;
if(consent === 'unknown') c6p9d.cookieBanner.classList.remove('hidden');
c6p9d.cookieAccept.addEventListener('click', ()=>m9n3c('accepted'));
c6p9d.cookieReject.addEventListener('click', ()=>m9n3c('rejected'));
c6p9d.cookieClose.addEventListener('click', ()=>m9n3c('rejected'));
}
function j4d2s(){
if(s4r1v.cookieConsent !== 'accepted') return;
try{
const f = JSON.parse(localStorage.getItem('vp_fav_v1') || '[]');
const c = JSON.parse(localStorage.getItem('vp_cart_v1') || '[]');
if(Array.isArray(f)) s4r1v.fav = new Set(f.map(String));
if(Array.isArray(c)){
s4r1v.cart = new Map();
for(const it of c){
if(it && typeof it.id === 'string'){
const qty = Math.max(1, Math.floor(Number(it.qty || 1)));
s4r1v.cart.set(it.id, qty);
}
}
}
}catch(e){}
}
function u8q2n(){
if(s4r1v.cookieConsent !== 'accepted') return;
try{
localStorage.setItem('vp_fav_v1', JSON.stringify(Array.from(s4r1v.fav)));
localStorage.setItem('vp_cart_v1', JSON.stringify(Array.from(s4r1v.cart.entries()).map(([id,qty])=>({id,qty}))));
}catch(e){}
}
function a9n7z(){
const cats = ["Arreglos","Botánica","Negocio","Eventos","Sostenibilidad"];
const lvls = ["Inicial","Intermedio","Avanzado"];
const fmts = ["Online","Presencial"];
c6p9d.catWrap.innerHTML = '';
for(const c of cats){
const id = 'cat_' + n2f7x();
const w = document.createElement('label');
w.className = "inline-flex items-center justify-between gap-3 rounded-2xl bg-white/70 dark:bg-slate-950/40 ring-1 ring-slate-900/10 dark:ring-white/10 px-4 py-3 cursor-pointer hover:ring-emerald-500/30";
w.innerHTML = ` `;
c6p9d.catWrap.appendChild(w);
}
c6p9d.lvlWrap.innerHTML = '';
for(const l of lvls){
const id = 'lvl_' + n2f7x();
const w = document.createElement('label');
w.className = "inline-flex items-center justify-between gap-3 rounded-2xl bg-white/70 dark:bg-slate-950/40 ring-1 ring-slate-900/10 dark:ring-white/10 px-4 py-3 cursor-pointer hover:ring-emerald-500/30";
w.innerHTML = ` `;
c6p9d.lvlWrap.appendChild(w);
}
c6p9d.fmtWrap.innerHTML = '';
for(const f of fmts){
const id = 'fmt_' + n2f7x();
const w = document.createElement('label');
w.className = "inline-flex items-center justify-between gap-3 rounded-2xl bg-white/70 dark:bg-slate-950/40 ring-1 ring-slate-900/10 dark:ring-white/10 px-4 py-3 cursor-pointer hover:ring-emerald-500/30";
w.innerHTML = ` `;
c6p9d.fmtWrap.appendChild(w);
}
}
function o4p1a(){
const el = c6p9d.mxGlow;
if(!el) return;
window.addEventListener('pointermove', (e)=>{
const x = Math.round((e.clientX / window.innerWidth) * 1000) / 10;
const y = Math.round((e.clientY / window.innerHeight) * 1000) / 10;
el.style.setProperty('--mx', x + '%');
el.style.setProperty('--my', y + '%');
}, { passive:true });
}
async function e1k2r(){
try{
const res = await fetch('./catalog.json', { cache:'no-store' });
if(!res.ok) throw new Error('No se pudo cargar catalog.json');
const js = await res.json();
if(!Array.isArray(js)) throw new Error('Formato inválido del catálogo');
const cleaned = [];
for(const it of js){
if(!it || typeof it !== 'object') continue;
if(typeof it.id !== 'string' || typeof it.title !== 'string') continue;
cleaned.push({
id: it.id,
title: it.title,
category: it.category,
level: it.level,
format: it.format,
durationHours: Number(it.durationHours || 0),
priceEUR: Number(it.priceEUR || 0),
rating: Math.max(0, Math.min(5, Number(it.rating || 0))),
tags: Array.isArray(it.tags) ? it.tags.map(String) : [],
seatsAvailable: Math.max(0, Math.floor(Number(it.seatsAvailable || 0))),
locale: it.locale || 'es-ES',
shortDescription: it.shortDescription || '',
syllabus: Array.isArray(it.syllabus) ? it.syllabus.map(String) : [],
instructor: it.instructor || '—',
startDates: Array.isArray(it.startDates) ? it.startDates.map(String) : []
});
}
s4r1v.dataAll = cleaned;
}catch(err){
s4r1v.dataAll = [];
c6p9d.stateBar.classList.remove('hidden');
c6p9d.stateBar.innerHTML = `
Verifica que existe y cumple el esquema requerido.
`;
const br = g1a2q('#btnRetry', c6p9d.stateBar);
if(br) br.addEventListener('click', async()=>{ c6p9d.stateBar.classList.add('hidden'); await e1k2r(); w2d9l(true); });
t8c2y(err.message || 'Error cargando catálogo', 'err', 4200);
}
}
function f6u1q(){
const q = b7t4m(s4r1v.query);
const tagsNeed = (s4r1v.filters.tags || []).map(b7t4m).filter(Boolean);
const res = [];
for(const it of s4r1v.dataAll){
if(s4r1v.filters.categories.size && !s4r1v.filters.categories.has(it.category)) continue;
if(s4r1v.filters.levels.size && !s4r1v.filters.levels.has(it.level)) continue;
if(s4r1v.filters.formats.size && !s4r1v.filters.formats.has(it.format)) continue;
if(s4r1v.filters.onlySeats && !(it.seatsAvailable > 0)) continue;
const p = it.priceEUR;
const h = it.durationHours;
if(s4r1v.filters.minPrice != null && p < s4r1v.filters.minPrice) continue;
if(s4r1v.filters.maxPrice != null && p > s4r1v.filters.maxPrice) continue;
if(s4r1v.filters.minHours != null && h < s4r1v.filters.minHours) continue;
if(s4r1v.filters.maxHours != null && h > s4r1v.filters.maxHours) continue;
if(tagsNeed.length){
const itTags = it.tags.map(b7t4m);
let ok = true;
for(const tg of tagsNeed){
if(!itTags.some(x=>x.includes(tg))) { ok = false; break; }
}
if(!ok) continue;
}
let score = 0;
if(q){
const hay = [
it.title,
it.category,
it.level,
it.format,
it.shortDescription,
it.instructor,
...(it.tags || [])
].map(b7t4m).join(' ');
if(!hay.includes(q)) continue;
const qt = q.split(/\s+/).filter(Boolean);
for(const w of qt){
if(b7t4m(it.title).includes(w)) score += 6;
if((it.tags||[]).map(b7t4m).some(t=>t.includes(w))) score += 3;
if(b7t4m(it.shortDescription).includes(w)) score += 2;
if(b7t4m(it.category).includes(w)) score += 1;
}
}
res.push({ it, score });
}
s4r1v.dataFiltered = res;
}
function h2c7d(a){
const dates = (a.startDates || []).map(l5z8s).filter(Number.isFinite).sort((x,y)=>x-y);
const now = Date.now();
const next = dates.find(t=>t>=now) ?? dates[0];
return Number.isFinite(next) ? next : Infinity;
}
function y6l3p(){
const mode = s4r1v.sort;
const arr = s4r1v.dataFiltered.slice();
const cmpStr = (x,y)=>String(x).localeCompare(String(y), 'es-ES', { sensitivity:'base' });
arr.sort((A,B)=>{
const a = A.it, b = B.it;
if(mode === 'priceAsc') return u5b1c(a.priceEUR) - u5b1c(b.priceEUR) || cmpStr(a.title,b.title);
if(mode === 'priceDesc') return u5b1c(b.priceEUR) - u5b1c(a.priceEUR) || cmpStr(a.title,b.title);
if(mode === 'ratingDesc') return u5b1c(b.rating) - u5b1c(a.rating) || u5b1c(b.seatsAvailable) - u5b1c(a.seatsAvailable) || cmpStr(a.title,b.title);
if(mode === 'durationAsc') return u5b1c(a.durationHours) - u5b1c(b.durationHours) || u5b1c(a.priceEUR) - u5b1c(b.priceEUR);
if(mode === 'seatsDesc') return u5b1c(b.seatsAvailable) - u5b1c(a.seatsAvailable) || u5b1c(b.rating) - u5b1c(a.rating);
if(mode === 'dateAsc') return h2c7d(a) - h2c7d(b) || cmpStr(a.title,b.title);
if(mode === 'titleAsc') return cmpStr(a.title,b.title);
return u5b1c(B.score) - u5b1c(A.score) || u5b1c(b.rating) - u5b1c(a.rating) || cmpStr(a.title,b.title);
});
return arr;
}
function s1a9t(r){
const full = Math.round(Math.max(0, Math.min(5, r)));
let out = '';
for(let i=1;i<=5;i++){
out += i<=full ? '★' : '☆';
}
return out;
}
function i3r0k(str){
const t = (str||'').toString().trim();
if(!t) return null;
const n = Number(t.replace(',', '.').replace(/[^\d.]/g,''));
if(!Number.isFinite(n)) return null;
return n;
}
function b8k2z(){
c6p9d.activeChips.innerHTML = '';
const chips = [];
const addChip = (label, onRemove)=>{
const b = document.createElement('button');
b.type = 'button';
b.className = "inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-extrabold bg-white text-slate-900 hover:bg-slate-50 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800/70 ring-1 ring-slate-900/10 dark:ring-white/10";
b.innerHTML = `${label}
`;
b.addEventListener('click', onRemove);
c6p9d.activeChips.appendChild(b);
};
if(s4r1v.query) chips.push(['Buscar: ' + s4r1v.query, ()=>{ s4r1v.query=''; c6p9d.qInput.value=''; w2d9l(true); }]);
if(s4r1v.filters.categories.size){
for(const c of Array.from(s4r1v.filters.categories)){
chips.push([`Categoría: ${c}`, ()=>{ s4r1v.filters.categories.delete(c); w2d9l(true); }]);
}
}
if(s4r1v.filters.levels.size){
for(const l of Array.from(s4r1v.filters.levels)){
chips.push([`Nivel: ${l}`, ()=>{ s4r1v.filters.levels.delete(l); w2d9l(true); }]);
}
}
if(s4r1v.filters.formats.size){
for(const f of Array.from(s4r1v.filters.formats)){
chips.push([`Formato: ${f}`, ()=>{ s4r1v.filters.formats.delete(f); w2d9l(true); }]);
}
}
if(s4r1v.filters.onlySeats) chips.push(['Con plazas', ()=>{ s4r1v.filters.onlySeats=false; w2d9l(true); }]);
const mp = s4r1v.filters.minPrice, xp = s4r1v.filters.maxPrice;
if(mp != null || xp != null) chips.push([`Precio: ${mp!=null?mp:'0'}–${xp!=null?xp:'∞'}€`, ()=>{ s4r1v.filters.minPrice=null; s4r1v.filters.maxPrice=null; c6p9d.minPrice.value=''; c6p9d.maxPrice.value=''; w2d9l(true); }]);
const mh = s4r1v.filters.minHours, xh = s4r1v.filters.maxHours;
if(mh != null || xh != null) chips.push([`Duración: ${mh!=null?mh:'0'}–${xh!=null?xh:'∞'}h`, ()=>{ s4r1v.filters.minHours=null; s4r1v.filters.maxHours=null; c6p9d.minHours.value=''; c6p9d.maxHours.value=''; w2d9l(true); }]);
if(s4r1v.filters.tags.length) chips.push([`Tags: ${s4r1v.filters.tags.join(', ')}`, ()=>{ s4r1v.filters.tags=[]; c6p9d.tagsInput.value=''; w2d9l(true); }]);
for(const [lab, fn] of chips) addChip(lab, fn);
if(!chips.length){
const p = document.createElement('p');
p.className = "text-xs text-slate-500 dark:text-slate-400";
p.textContent = "Sin filtros activos. Abre “Filtros” para refinar.";
c6p9d.activeChips.appendChild(p);
}
}
function v1p8o(){
const total = s4r1v.dataFiltered.length;
const pageSize = s4r1v.pageSize;
const pages = Math.max(1, Math.ceil(total / pageSize));
s4r1v.page = Math.min(s4r1v.page, pages);
const start = (s4r1v.page - 1) * pageSize;
const end = start + pageSize;
const sorted = y6l3p();
const slice = sorted.slice(start, end);
c6p9d.pageLabel.textContent = `${s4r1v.page} / ${pages}`;
c6p9d.btnPrev.disabled = s4r1v.page <= 1;
c6p9d.btnNext.disabled = s4r1v.page >= pages;
c6p9d.catalogList.className = (s4r1v.viewMode === 'list')
? "grid grid-cols-1 gap-3"
: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5";
c6p9d.catalogList.innerHTML = '';
for(const obj of slice){
const it = obj.it;
const li = document.createElement('li');
li.className = "p1k3d";
li.appendChild(k1m3s(it, s4r1v.viewMode));
c6p9d.catalogList.appendChild(li);
}
if(slice.length === 0){
const li = document.createElement('li');
li.className = "col-span-full";
li.innerHTML = `
Prueba a limpiar filtros, cambiar orden o buscar por otra palabra clave.
`;
c6p9d.catalogList.appendChild(li);
const r1 = g1a2q('#btnEmptyReset', li);
const r2 = g1a2q('#btnEmptyOpenFilters', li);
if(r1) r1.addEventListener('click', ()=>{ d9x2c(); });
if(r2) r2.addEventListener('click', ()=>{ c6p9d.filtersDlg.showModal(); k0c9q(); });
}
const vis = slice.map(x=>x.it);
c6p9d.statResults.textContent = `${total}`;
c6p9d.statShown.textContent = `${slice.length}`;
if(vis.length){
const avg = vis.reduce((a,x)=>a+u5b1c(x.rating),0)/vis.length;
c6p9d.statAvgRating.textContent = avg.toFixed(2);
c6p9d.statStars.textContent = s1a9t(avg);
const prices = vis.map(x=>u5b1c(x.priceEUR)).sort((a,b)=>a-b);
c6p9d.statFrom.textContent = z2m6a.format(prices[0]||0);
c6p9d.statTo.textContent = z2m6a.format(prices[prices.length-1]||0);
let nextT = Infinity;
let nextIt = null;
for(const x of vis){
const t = h2c7d(x);
if(t < nextT){
nextT = t;
nextIt = x;
}
}
if(nextIt && Number.isFinite(nextT) && nextT !== Infinity){
c6p9d.statNextDate.textContent = r9q3e.format(new Date(nextT));
c6p9d.statNextTitle.textContent = nextIt.title;
}else{
c6p9d.statNextDate.textContent = '—';
c6p9d.statNextTitle.textContent = '—';
}
}else{
c6p9d.statAvgRating.textContent = '—';
c6p9d.statStars.textContent = '—';
c6p9d.statFrom.textContent = '—';
c6p9d.statTo.textContent = '—';
c6p9d.statNextDate.textContent = '—';
c6p9d.statNextTitle.textContent = '—';
}
b8k2z();
y8o2p();
}
function k1m3s(it, mode){
const wrap = document.createElement('article');
const isFav = s4r1v.fav.has(it.id);
const inCart = s4r1v.cart.has(it.id);
const full = it.seatsAvailable <= 0;
const badge = (txt, cls) => ``;
const star = s1a9t(it.rating);
const nextT = h2c7d(it);
const nextLabel = (nextT !== Infinity) ? r9q3e.format(new Date(nextT)) : 'Sin fechas';
const baseCard = (mode === 'list')
? "rounded-3xl bg-white dark:bg-slate-900 ring-1 ring-slate-900/10 dark:ring-white/10 shadow-vpSoft p-4 sm:p-5 flex flex-col sm:flex-row gap-4 sm:items-center"
: "rounded-3xl bg-white dark:bg-slate-900 ring-1 ring-slate-900/10 dark:ring-white/10 shadow-vpSoft p-4 sm:p-5 flex flex-col";
wrap.className = baseCard;
const left = document.createElement('div');
left.className = (mode === 'list') ? "flex-1 min-w-0" : "min-w-0";
const topBadges = `
${badge(it.category, "bg-emerald-50 text-emerald-700 ring-1 ring-emerald-200/70 dark:bg-emerald-950/40 dark:text-emerald-200 dark:ring-emerald-800/50")}
${badge(it.level, "bg-sky-50 text-sky-700 ring-1 ring-sky-200/70 dark:bg-sky-950/40 dark:text-sky-200 dark:ring-sky-800/50")}
${badge(it.format, "bg-fuchsia-50 text-fuchsia-700 ring-1 ring-fuchsia-200/70 dark:bg-fuchsia-950/40 dark:text-fuchsia-200 dark:ring-fuchsia-800/50")}
${badge(full ? "Completo" : (it.seatsAvailable + " plazas"), full ? "bg-rose-50 text-rose-700 ring-1 ring-rose-200/70 dark:bg-rose-950/40 dark:text-rose-200 dark:ring-rose-800/50" : "bg-amber-50 text-amber-800 ring-1 ring-amber-200/70 dark:bg-amber-950/40 dark:text-amber-200 dark:ring-amber-800/50")}
`;
left.innerHTML = `
${topBadges}
${it.shortDescription || ''}
`;
const right = document.createElement('div');
right.className = (mode === 'list') ? "w-full sm:w-[280px] grid grid-cols-2 gap-2" : "mt-4 grid grid-cols-2 gap-2";
const cartBtnCls = inCart
? "bg-slate-900 text-white hover:bg-slate-800 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
: "bg-emerald-600 text-white hover:bg-emerald-700";
right.innerHTML = `
`;
wrap.appendChild(left);
wrap.appendChild(right);
wrap.addEventListener('click', (e)=>{
const t = e.target.closest('[data-vp-open],[data-vp-fav],[data-vp-cart]');
if(!t) return;
if(t.dataset.vpOpen){
const id = t.dataset.vpOpen;
d2s0a(id);
}
if(t.dataset.vpFav){
const id = t.dataset.vpFav;
f7y2u(id);
w2d9l(false);
}
if(t.dataset.vpCart){
const id = t.dataset.vpCart;
c9j2a(id, 1, true);
w2d9l(false);
}
});
return wrap;
}
function f7y2u(id){
if(!id) return;
if(s4r1v.fav.has(id)){
s4r1v.fav.delete(id);
t8c2y('Eliminado de favoritos', 'info', 2000);
}else{
s4r1v.fav.add(id);
t8c2y('Guardado en favoritos', 'ok', 2000);
}
u8q2n();
y8o2p();
a3k1h();
if(s4r1v.detailsId === id) n7m2x();
}
function c9j2a(id, qty=1, toast=false){
if(!id) return;
const it = s4r1v.dataAll.find(x=>x.id===id);
if(!it) return;
if(it.seatsAvailable <= 0){
if(toast) t8c2y('Este curso está completo', 'warn', 2600);
return;
}
const cur = s4r1v.cart.get(id) || 0;
const next = Math.max(1, cur + Math.max(1, Math.floor(qty)));
s4r1v.cart.set(id, next);
u8q2n();
y8o2p();
o8u2z();
if(toast) t8c2y('Añadido al carrito', 'ok', 2000);
if(s4r1v.detailsId === id) n7m2x();
}
function r8p5v(id){
if(!id) return;
if(s4r1v.cart.has(id)){
s4r1v.cart.delete(id);
u8q2n();
y8o2p();
o8u2z();
t8c2y('Eliminado del carrito', 'info', 2000);
if(s4r1v.detailsId === id) n7m2x();
}
}
function y8o2p(){
c6p9d.favCount.textContent = String(s4r1v.fav.size);
let n = 0;
for(const q of s4r1v.cart.values()) n += q;
c6p9d.cartCount.textContent = String(n);
}
function d2s0a(id){
const it = s4r1v.dataAll.find(x=>x.id===id);
if(!it) return;
s4r1v.detailsId = id;
c6p9d.dTitle.textContent = it.title;
c6p9d.dCategory.textContent = it.category;
c6p9d.dLevel.textContent = it.level;
c6p9d.dFormat.textContent = it.format;
c6p9d.dSeats.textContent = it.seatsAvailable <= 0 ? 'Completo' : `${it.seatsAvailable} plazas`;
c6p9d.dShort.textContent = it.shortDescription || '';
c6p9d.dDuration.textContent = `${u5b1c(it.durationHours)} h`;
c6p9d.dPrice.textContent = z2m6a.format(it.priceEUR);
c6p9d.dRating.textContent = `${it.rating.toFixed(1)} / 5 · ${s1a9t(it.rating)}`;
c6p9d.dInstructor.textContent = it.instructor || '—';
c6p9d.dLocale.textContent = it.locale || 'es-ES';
c6p9d.dId.textContent = `ID: ${it.id}`;
c6p9d.dSyllabus.innerHTML = '';
const syl = (it.syllabus && it.syllabus.length) ? it.syllabus : ['Programa en revisión: contacta para detalles.'];
for(const s of syl){
const li = document.createElement('li');
li.className = "rounded-2xl bg-white/70 dark:bg-slate-950/40 ring-1 ring-slate-900/10 dark:ring-white/10 px-4 py-3 text-sm";
li.innerHTML = ` ${s} `;
c6p9d.dSyllabus.appendChild(li);
}
c6p9d.dTags.innerHTML = '';
const tags = (it.tags && it.tags.length) ? it.tags : ['Sin etiquetas'];
for(const tg of tags){
const sp = document.createElement('span');
sp.className = "inline-flex items-center rounded-full px-3 py-1 text-xs font-extrabold bg-slate-900 text-white dark:bg-white dark:text-slate-900";
sp.textContent = tg;
c6p9d.dTags.appendChild(sp);
}
const dates = (it.startDates || []).map(l5z8s).filter(Number.isFinite).sort((a,b)=>a-b);
const now = Date.now();
const next = dates.find(t=>t>=now) ?? dates[0];
if(Number.isFinite(next)){
c6p9d.dNextLabel.textContent = 'Próxima: ' + r9q3e.format(new Date(next));
}else{
c6p9d.dNextLabel.textContent = '—';
}
c6p9d.dDates.innerHTML = '';
if(dates.length){
for(const t of dates.slice(0,8)){
const li = document.createElement('li');
li.className = "rounded-2xl bg-white/70 dark:bg-slate-950/40 ring-1 ring-slate-900/10 dark:ring-white/10 px-4 py-3 flex items-center justify-between gap-3";
const d = new Date(t);
const isNext = t === next;
li.innerHTML = `
${isNext ? 'Próxima fecha recomendada' : 'Disponible'}
`;
c6p9d.dDates.appendChild(li);
}
}else{
const li = document.createElement('li');
li.className = "rounded-2xl bg-white/70 dark:bg-slate-950/40 ring-1 ring-slate-900/10 dark:ring-white/10 px-4 py-3 text-sm text-slate-600 dark:text-slate-300";
li.textContent = 'No hay fechas publicadas.';
c6p9d.dDates.appendChild(li);
}
if(s4r1v.detailsTimer) window.clearInterval(s4r1v.detailsTimer);
const upd = ()=>{
if(!Number.isFinite(next)){
c6p9d.dCountdown.textContent = '—';
return;
}
const diff = next - Date.now();
c6p9d.dCountdown.textContent = diff>0 ? q0w1e(diff) : 'En curso / ya iniciado';
};
upd();
s4r1v.detailsTimer = window.setInterval(upd, 1000);
n7m2x();
if(!c6p9d.detailsDlg.open) c6p9d.detailsDlg.showModal();
}
function n7m2x(){
const id = s4r1v.detailsId;
const it = s4r1v.dataAll.find(x=>x.id===id);
if(!it) return;
const isFav = s4r1v.fav.has(id);
const inCart = s4r1v.cart.has(id);
const full = it.seatsAvailable <= 0;
c6p9d.dBtnFav.textContent = isFav ? 'Quitar de favoritos' : 'Guardar en favoritos';
c6p9d.dBtnFav.className = isFav
? "rounded-xl px-4 py-2.5 text-sm font-extrabold bg-rose-600 text-white hover:bg-rose-700"
: "rounded-xl px-4 py-2.5 text-sm font-extrabold bg-white text-slate-900 hover:bg-slate-50 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800/70 ring-1 ring-slate-900/10 dark:ring-white/10";
c6p9d.dBtnCart.disabled = full;
c6p9d.dBtnCart.textContent = full ? 'Sin plazas' : (inCart ? 'Añadido (sumar 1)' : 'Añadir al carrito');
}
function o8u2z(){
const items = Array.from(s4r1v.cart.entries()).map(([id,qty])=>{
const it = s4r1v.dataAll.find(x=>x.id===id);
if(!it) return null;
return { it, qty };
}).filter(Boolean);
c6p9d.cartList.innerHTML = '';
if(!items.length){
c6p9d.cartEmpty.classList.remove('hidden');
}else{
c6p9d.cartEmpty.classList.add('hidden');
}
let total = 0;
let count = 0;
for(const {it, qty} of items){
const line = (it.priceEUR || 0) * qty;
total += line;
count += qty;
const li = document.createElement('li');
li.className = "rounded-2xl bg-white/70 dark:bg-slate-950/40 ring-1 ring-slate-900/10 dark:ring-white/10 p-4";
li.innerHTML = `
${it.category} · ${it.level} · ${it.format}
${z2m6a.format(it.priceEUR)} / unidad
`;
c6p9d.cartList.appendChild(li);
}
c6p9d.chTotal.textContent = z2m6a.format(total);
c6p9d.chCount.textContent = `${count} ${count===1?'curso':'cursos'}`;
c6p9d.btnCheckout.disabled = !(items.length>0);
}
function a3k1h(){
const favItems = Array.from(s4r1v.fav.values()).map(id=>s4r1v.dataAll.find(x=>x.id===id)).filter(Boolean);
c6p9d.favList.innerHTML = '';
if(!favItems.length){
c6p9d.favEmpty.classList.remove('hidden');
}else{
c6p9d.favEmpty.classList.add('hidden');
}
for(const it of favItems){
const li = document.createElement('li');
li.className = "rounded-2xl bg-slate-50 dark:bg-slate-900/60 ring-1 ring-slate-900/10 dark:ring-white/10 p-4";
li.innerHTML = `
${it.category} · ${it.level} · ${it.format}
Precio: ${z2m6a.format(it.priceEUR)} · Rating: ${it.rating.toFixed(1)}
`;
c6p9d.favList.appendChild(li);
}
}
function k0c9q(){
v8k2p('[data-vp-cat]', c6p9d.filtersDlg).forEach(el=>{ el.checked = s4r1v.filters.categories.has(el.dataset.vpCat); });
v8k2p('[data-vp-lvl]', c6p9d.filtersDlg).forEach(el=>{ el.checked = s4r1v.filters.levels.has(el.dataset.vpLvl); });
v8k2p('[data-vp-fmt]', c6p9d.filtersDlg).forEach(el=>{ el.checked = s4r1v.filters.formats.has(el.dataset.vpFmt); });
c6p9d.onlySeats.checked = !!s4r1v.filters.onlySeats;
c6p9d.minPrice.value = s4r1v.filters.minPrice != null ? String(s4r1v.filters.minPrice) : '';
c6p9d.maxPrice.value = s4r1v.filters.maxPrice != null ? String(s4r1v.filters.maxPrice) : '';
c6p9d.minHours.value = s4r1v.filters.minHours != null ? String(s4r1v.filters.minHours) : '';
c6p9d.maxHours.value = s4r1v.filters.maxHours != null ? String(s4r1v.filters.maxHours) : '';
c6p9d.tagsInput.value = (s4r1v.filters.tags || []).join(', ');
z7n2v();
}
function z7n2v(){
const temp = {
categories: new Set(v8k2p('[data-vp-cat]', c6p9d.filtersDlg).filter(x=>x.checked).map(x=>x.dataset.vpCat)),
levels: new Set(v8k2p('[data-vp-lvl]', c6p9d.filtersDlg).filter(x=>x.checked).map(x=>x.dataset.vpLvl)),
formats: new Set(v8k2p('[data-vp-fmt]', c6p9d.filtersDlg).filter(x=>x.checked).map(x=>x.dataset.vpFmt)),
onlySeats: c6p9d.onlySeats.checked,
minPrice: i3r0k(c6p9d.minPrice.value),
maxPrice: i3r0k(c6p9d.maxPrice.value),
minHours: i3r0k(c6p9d.minHours.value),
maxHours: i3r0k(c6p9d.maxHours.value),
tags: (c6p9d.tagsInput.value||'').split(',').map(s=>s.trim()).filter(Boolean)
};
const old = s4r1v.filters;
const saved = s4r1v.filters;
s4r1v.filters = temp;
f6u1q();
const count = s4r1v.dataFiltered.length;
c6p9d.filtersPreview.textContent = String(count);
s4r1v.filters = saved;
s4r1v.dataFiltered = s4r1v.dataFiltered;
s4r1v.filters = old;
}
function t4v2a(){
const f = s4r1v.filters;
const validateRange = (minV, maxV) => (minV != null && maxV != null) ? (minV <= maxV) : true;
const mp = i3r0k(c6p9d.minPrice.value);
const xp = i3r0k(c6p9d.maxPrice.value);
const mh = i3r0k(c6p9d.minHours.value);
const xh = i3r0k(c6p9d.maxHours.value);
if(!validateRange(mp, xp)){
t8c2y('Rango de precio inválido', 'warn', 2600);
return false;
}
if(!validateRange(mh, xh)){
t8c2y('Rango de duración inválido', 'warn', 2600);
return false;
}
f.categories = new Set(v8k2p('[data-vp-cat]', c6p9d.filtersDlg).filter(x=>x.checked).map(x=>x.dataset.vpCat));
f.levels = new Set(v8k2p('[data-vp-lvl]', c6p9d.filtersDlg).filter(x=>x.checked).map(x=>x.dataset.vpLvl));
f.formats = new Set(v8k2p('[data-vp-fmt]', c6p9d.filtersDlg).filter(x=>x.checked).map(x=>x.dataset.vpFmt));
f.onlySeats = c6p9d.onlySeats.checked;
f.minPrice = mp;
f.maxPrice = xp;
f.minHours = mh;
f.maxHours = xh;
f.tags = (c6p9d.tagsInput.value||'').split(',').map(s=>s.trim()).filter(Boolean);
return true;
}
function d9x2c(){
s4r1v.page = 1;
s4r1v.pageSize = Number(c6p9d.pageSizeSelect.value) || 9;
s4r1v.sort = c6p9d.sortSelect.value || 'relevance';
s4r1v.query = '';
c6p9d.qInput.value = '';
c6p9d.minPrice.value = '';
c6p9d.maxPrice.value = '';
c6p9d.minHours.value = '';
c6p9d.maxHours.value = '';
c6p9d.tagsInput.value = '';
c6p9d.onlySeats.checked = false;
s4r1v.filters = { categories:new Set(), levels:new Set(), formats:new Set(), onlySeats:false, minPrice:null, maxPrice:null, minHours:null, maxHours:null, tags:[] };
w2d9l(true);
t8c2y('Filtros restablecidos', 'ok', 2000);
}
function w2d9l(resetPage){
if(resetPage) s4r1v.page = 1;
s4r1v.pageSize = Number(c6p9d.pageSizeSelect.value) || s4r1v.pageSize;
s4r1v.sort = c6p9d.sortSelect.value || s4r1v.sort;
s4r1v.query = c6p9d.qInput.value || '';
f6u1q();
v1p8o();
}
function l0q7s(){
c6p9d.qForm.addEventListener('submit', (e)=>{ e.preventDefault(); w2d9l(true); });
c6p9d.qInput.addEventListener('input', ()=>{ w2d9l(true); });
c6p9d.qClear.addEventListener('click', ()=>{ c6p9d.qInput.value=''; w2d9l(true); });
c6p9d.sortSelect.addEventListener('change', ()=>w2d9l(true));
c6p9d.pageSizeSelect.addEventListener('change', ()=>w2d9l(true));
c6p9d.btnPrev.addEventListener('click', ()=>{ s4r1v.page = Math.max(1, s4r1v.page-1); v1p8o(); window.scrollTo({top:0, behavior:'smooth'}); });
c6p9d.btnNext.addEventListener('click', ()=>{
const total = s4r1v.dataFiltered.length;
const pages = Math.max(1, Math.ceil(total / s4r1v.pageSize));
s4r1v.page = Math.min(pages, s4r1v.page+1);
v1p8o();
window.scrollTo({top:0, behavior:'smooth'});
});
c6p9d.btnViewGrid.addEventListener('click', ()=>{
s4r1v.viewMode = 'grid';
c6p9d.btnViewGrid.className = "rounded-xl px-3 py-1.5 text-xs font-extrabold bg-slate-900 text-white dark:bg-white dark:text-slate-900";
c6p9d.btnViewList.className = "rounded-xl px-3 py-1.5 text-xs font-extrabold text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800/60";
v1p8o();
});
c6p9d.btnViewList.addEventListener('click', ()=>{
s4r1v.viewMode = 'list';
c6p9d.btnViewList.className = "rounded-xl px-3 py-1.5 text-xs font-extrabold bg-slate-900 text-white dark:bg-white dark:text-slate-900";
c6p9d.btnViewGrid.className = "rounded-xl px-3 py-1.5 text-xs font-extrabold text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800/60";
v1p8o();
});
c6p9d.btnOnlyFav.addEventListener('click', ()=>{
const q = c6p9d.qInput.value || '';
const token = 'fav:';
if(!q.includes(token)){
c6p9d.qInput.value = (q ? (q + ' ') : '') + token;
}else{
c6p9d.qInput.value = q.replace(token,'').replace(/\s+/g,' ').trim();
}
w2d9l(true);
t8c2y('Atajo de favoritos aplicado a la búsqueda', 'info', 2200);
});
c6p9d.btnOnlySeats.addEventListener('click', ()=>{
s4r1v.filters.onlySeats = !s4r1v.filters.onlySeats;
t8c2y(s4r1v.filters.onlySeats ? 'Mostrando solo con plazas' : 'Mostrando todos', 'info', 2000);
w2d9l(true);
});
c6p9d.btnTop.addEventListener('click', ()=>window.scrollTo({top:0, behavior:'smooth'}));
c6p9d.btnOpenFilters.addEventListener('click', ()=>{
c6p9d.filtersDlg.showModal();
k0c9q();
});
const updPrev = ()=>z7n2v();
['input','change'].forEach(evt=>{
c6p9d.filtersDlg.addEventListener(evt, (e)=>{
const t = e.target;
if(!t) return;
if(t.matches('[data-vp-cat],[data-vp-lvl],[data-vp-fmt],#minPrice,#maxPrice,#minHours,#maxHours,#tagsInput,#onlySeats')) updPrev();
});
});
c6p9d.btnApplyFilters.addEventListener('click', ()=>{
if(!t4v2a()) return;
w2d9l(true);
t8c2y('Filtros aplicados', 'ok', 2000);
});
c6p9d.btnClearFilters.addEventListener('click', ()=>{
v8k2p('[data-vp-cat],[data-vp-lvl],[data-vp-fmt]', c6p9d.filtersDlg).forEach(x=>x.checked=false);
c6p9d.onlySeats.checked=false;
c6p9d.minPrice.value=''; c6p9d.maxPrice.value='';
c6p9d.minHours.value=''; c6p9d.maxHours.value='';
c6p9d.tagsInput.value='';
z7n2v();
t8c2y('Filtros limpiados (vista previa)', 'info', 2000);
});
c6p9d.btnResetAll.addEventListener('click', ()=>d9x2c());
c6p9d.dBtnFav.addEventListener('click', ()=>{ if(s4r1v.detailsId) { f7y2u(s4r1v.detailsId); } });
c6p9d.dBtnCart.addEventListener('click', ()=>{ if(s4r1v.detailsId) { c9j2a(s4r1v.detailsId, 1, true); w2d9l(false); } });
c6p9d.detailsDlg.addEventListener('close', ()=>{
if(s4r1v.detailsTimer){ window.clearInterval(s4r1v.detailsTimer); s4r1v.detailsTimer=null; }
s4r1v.detailsId = null;
});
c6p9d.btnOpenCart.addEventListener('click', ()=>{ o8u2z(); c6p9d.cartDlg.showModal(); });
c6p9d.btnOpenFav.addEventListener('click', ()=>{ a3k1h(); c6p9d.favDlg.showModal(); });
c6p9d.cartList.addEventListener('click', (e)=>{
const del = e.target.closest('[data-vp-cart-del]');
const inc = e.target.closest('[data-vp-qty-inc]');
const dec = e.target.closest('[data-vp-qty-dec]');
if(del){
r8p5v(del.dataset.vpCartDel);
w2d9l(false);
}
if(inc){
c9j2a(inc.dataset.vpQtyInc, 1, false);
o8u2z();
w2d9l(false);
}
if(dec){
const id = dec.dataset.vpQtyDec;
const cur = s4r1v.cart.get(id) || 0;
if(cur<=1){ r8p5v(id); }
else { s4r1v.cart.set(id, cur-1); u8q2n(); y8o2p(); o8u2z(); }
w2d9l(false);
}
});
c6p9d.btnClearCart.addEventListener('click', ()=>{
s4r1v.cart = new Map();
u8q2n();
y8o2p();
o8u2z();
w2d9l(false);
t8c2y('Carrito vaciado', 'info', 2200);
});
c6p9d.favList.addEventListener('click', (e)=>{
const op = e.target.closest('[data-vp-fav-open]');
const del = e.target.closest('[data-vp-fav-del]');
if(op){
const id = op.dataset.vpFavOpen;
c6p9d.favDlg.close();
d2s0a(id);
}
if(del){
const id = del.dataset.vpFavDel;
if(s4r1v.fav.has(id)) s4r1v.fav.delete(id);
u8q2n();
y8o2p();
a3k1h();
w2d9l(false);
t8c2y('Eliminado de favoritos', 'info', 2000);
}
});
c6p9d.btnClearFav.addEventListener('click', ()=>{
s4r1v.fav = new Set();
u8q2n();
y8o2p();
a3k1h();
w2d9l(false);
t8c2y('Favoritos vaciados', 'info', 2200);
});
c6p9d.btnCheckout.addEventListener('click', ()=>{
const name = (c6p9d.chName.value||'').trim();
const email = (c6p9d.chEmail.value||'').trim();
const phone = (c6p9d.chPhone.value||'').trim();
const items = Array.from(s4r1v.cart.keys());
const show = (text, type)=>{
c6p9d.checkoutAlert.classList.remove('hidden');
const base = "rounded-2xl p-3 text-sm font-semibold ring-1";
const map = {
ok: "bg-emerald-50 text-emerald-800 ring-emerald-200/80 dark:bg-emerald-950/40 dark:text-emerald-200 dark:ring-emerald-800/50",
err: "bg-rose-50 text-rose-800 ring-rose-200/80 dark:bg-rose-950/40 dark:text-rose-200 dark:ring-rose-800/50",
warn: "bg-amber-50 text-amber-900 ring-amber-200/80 dark:bg-amber-950/40 dark:text-amber-200 dark:ring-amber-800/50"
};
c6p9d.checkoutAlert.className = base + " " + (map[type] || map.warn);
c6p9d.checkoutAlert.textContent = text;
};
if(!items.length){ show('Añade al menos un curso al carrito.', 'warn'); return; }
if(name.length < 3){ show('Introduce tu nombre completo.', 'err'); return; }
if(!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email)){ show('Email inválido.', 'err'); return; }
if(!/^\+34\s?\d{3}\s?\d{3}\s?\d{3}$/.test(phone.replace(/-/g,' '))){ show('Teléfono inválido. Formato: +34 612 345 678', 'err'); return; }
const ref = 'VP-' + Math.random().toString(10).slice(2, 8);
show('Reserva confirmada. Referencia: ' + ref, 'ok');
t8c2y('Reserva confirmada: ' + ref, 'ok', 3600);
window.setTimeout(()=>{
s4r1v.cart = new Map();
u8q2n();
y8o2p();
o8u2z();
w2d9l(false);
}, 600);
window.setTimeout(()=>{
c6p9d.cartDlg.close();
c6p9d.checkoutAlert.classList.add('hidden');
c6p9d.chNotes.value = '';
}, 1200);
});
document.addEventListener('keydown', (e)=>{
if(e.key === 'Escape'){
if(!c6p9d.detailsDlg.open && !c6p9d.cartDlg.open && !c6p9d.favDlg.open && !c6p9d.filtersDlg.open) return;
}
if((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k'){
e.preventDefault();
c6p9d.qInput.focus();
}
});
}
function r2y7p(){
const q = b7t4m(s4r1v.query);
if(!q.includes('fav:')) return;
const filtered = s4r1v.dataFiltered.filter(x=>s4r1v.fav.has(x.it.id));
s4r1v.dataFiltered = filtered;
}
function w9s2q(){
const original = f6u1q;
f6u1q = function(){
original();
r2y7p();
};
}
function x2h5m(){
const clean = (inp)=>{
const val = inp.value.replace(/[^\d.,]/g,'');
inp.value = val;
};
[c6p9d.minPrice,c6p9d.maxPrice,c6p9d.minHours,c6p9d.maxHours].forEach(inp=>{
inp.addEventListener('input', ()=>clean(inp));
});
}
(async function(){
o4p1a();
await p3v9b();
g2h8f();
a9n7z();
j4d2s();
w9s2q();
x2h5m();
l0q7s();
await e1k2r();
w2d9l(true);
o8u2z();
a3k1h();
c6p9d.catalogList.addEventListener('click', (e)=>{
const t = e.target.closest('[data-vp-open],[data-vp-fav],[data-vp-cart]');
if(!t) return;
});
c6p9d.filtersDlg.addEventListener('close', ()=>{});
})();
})();