Saltar al contenido

Catálogo interactivo

Cursos de Florística con filtros avanzados

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.

Resultados

Mostrando en la página actual

Valoración media

En base a cursos visibles

Precio desde

Rango visible

Próxima fecha

Entre los visibles

Vista
Página

    Sugerencia

    Usa Filtros para combinar categoría + nivel + formato. Añade a favoritos para comparar después.

    Filtros avanzados

    Refina tu catálogo

    Categoría

    Nivel y formato

    Nivel

    Formato

    Precio y duración

    Disponibilidad y etiquetas

    coincidencias estimadas (según filtros actuales)

    Detalles del curso

    Duración

    Precio

    Valoración

    Temario

      Etiquetas

      Carrito

      Revisión y checkout

      Cursos seleccionados

        Tu carrito está vacío

        Añade cursos desde el catálogo para ver el total y completar la reserva.

        Datos del alumno/a

        Total

        €0,00

        IVA incl.

        0 cursos

        Al confirmar, se simula una reserva local (no se realiza cobro). Puedes vaciar el carrito en cualquier momento.

        Pago seguro (demo)

        Favoritos

        Lista guardada

        Cursos

          Aún no has guardado cursos

          Marca cursos con el corazón en el catálogo o desde el modal de detalles.

          Se guarda en tu navegador

          '; 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 = `${c}`; 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 = `${l}`; 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 = `${f}`; 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 = `

          No se pudo cargar el catálogo

          Verifica que ./catalog.json 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 = `

          No hay resultados

          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) => `${txt}`; 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 || ''}

          Precio

          ${z2m6a.format(it.priceEUR)}

          Duración

          ${u5b1c(it.durationHours)} h

          Rating

          ${it.rating.toFixed(1)} ${star}

          Inicio

          ${nextLabel}

          `; 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 = `

          ${r9q3e.format(d)}

          ${isNext ? 'Próxima fecha recomendada' : 'Disponible'}

          ${isNext ? 'Siguiente' : ''} `; 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.title}

          ${it.category} · ${it.level} · ${it.format}

          ${z2m6a.format(it.priceEUR)} / unidad

          ${qty}

          ${z2m6a.format(line)}

          `; 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.title}

          ${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', ()=>{}); })(); })();