Atención rápida y clara

Contacto

Cuéntanos qué necesitas: dudas de cursos, fechas, niveles, certificación o asesoría para montar tu proyecto floral.

Teléfono

+34 657 192 048

Email

[email protected]

Respondemos en 1–2 días laborables.

Horario

Lunes a Viernes · 10:00–18:00 (CET)

Ahora mismo

Calculando…

Dirección (España)

Calle de Alcalá 124, 28009 Madrid

Visitas solo con cita previa.

Preferencia guardada

Aún no has elegido una franja horaria.

Recordatorio local

Te avisaremos aquí (en este navegador).

Sin recordatorios activos.

Formulario de contacto

Validación en el cliente. Confirmación y errores en modal. Sin envío real (demo).

Seguro Rápido

Guarda tu preferencia y úsala para programar un recordatorio local.

Tu zona: —
00153059

Vista previa

Selecciona una franja para ver sugerencia.

0/800

Validación activa

SLA orientativo

Calculando estimación de respuesta…

Preguntas rápidas

Si tu consulta encaja en alguna de estas, respondemos aún más rápido.

Programar recordatorio local

Te mostraremos un aviso en esta página cuando llegue la hora.

Resumen

Selecciona una opción.

Hora local

FAQs

Respuestas rápidas antes de enviar el formulario.

¿Ofrecéis cursos para principiantes?

Sí. Te recomendaremos un itinerario según tu experiencia y objetivos. Envíanos tu disponibilidad en la franja horaria preferida.

¿Dónde estáis?

Estamos en Madrid. Para visitas, pide cita previa. El soporte online funciona para toda España.

¿Cuánto tardáis en responder?

Normalmente 1–2 días laborables. El contador SLA de esta página muestra una estimación basada en horario laboral (CET).

`; const footerFallback = ` `; async function safeInject(url, mountEl, fallbackHtml){ try{ const res = await fetch(url, {cache:'no-store'}); if(!res.ok) throw new Error('HTTP '+res.status); const txt = await res.text(); mountEl.innerHTML = txt; }catch(e){ mountEl.innerHTML = fallbackHtml; } } safeInject('./header.html', mountHeader, headerFallback).then(()=>wireHeaderBits()); safeInject('./footer.html', mountFooter, footerFallback).then(()=>wireFooterBits()); function wireHeaderBits(){ const btn = document.getElementById('themeToggleBtn'); const label = document.getElementById('themeToggleLabel'); const icon = document.getElementById('themeIcon'); if(btn){ const setUI = ()=>{ const isDark = document.documentElement.classList.contains('dark'); if(label) label.textContent = isDark ? 'Oscuro' : 'Claro'; if(icon){ icon.innerHTML = isDark ? '' : ''; } }; setUI(); btn.addEventListener('click', ()=>{ const isDark = document.documentElement.classList.contains('dark'); const next = isDark ? 'light' : 'dark'; localStorage.setItem('theme', next); document.documentElement.classList.toggle('dark', next==='dark'); setUI(); announceModal('Tema actualizado', `Has cambiado a tema ${next==='dark'?'oscuro':'claro'}.`, 'info'); }); } } function wireFooterBits(){ const y = document.getElementById('yearNow'); if(y) y.textContent = String(new Date().getFullYear()); const openPrivacyBtn = document.getElementById('openPrivacyBtn'); const openCookiesBtn = document.getElementById('openCookiesBtn'); if(openPrivacyBtn){ openPrivacyBtn.addEventListener('click', ()=>{ showModal({ type:'info', title:'Privacidad', subtitle:'Resumen breve (demo)', bodyNodes: [ nodeP('Usamos tus datos solo para responder a tu consulta. No vendemos ni cedemos información a terceros.'), nodeP('Este formulario es una demo de validación en el cliente; el envío no se realiza a un servidor.') ], primaryText:'Entendido' }); }); } if(openCookiesBtn){ openCookiesBtn.addEventListener('click', ()=>{ showModal({ type:'info', title:'Cookies', subtitle:'Preferencias de navegador', bodyNodes: [ nodeP('Guardamos: tema (localStorage), preferencia de franja horaria y recordatorios locales.'), nodeP('Puedes cambiar tu decisión desde el banner cuando aparezca (si no has aceptado/rechazado).') ], primaryText:'Entendido' }); }); } } const cookieBanner = document.getElementById('cookieBanner'); const cookieAccept = document.getElementById('cookieAccept'); const cookieDecline = document.getElementById('cookieDecline'); const cookieClose = document.getElementById('cookieClose'); const consentKey = 'vp_cookie_consent_v1'; function getConsent(){ try{return localStorage.getItem(consentKey);}catch(e){return null;} } function setConsent(v){ try{localStorage.setItem(consentKey, v);}catch(e){} } function showCookieBannerIfNeeded(){ const c = getConsent(); if(c==='accepted' || c==='declined') return; cookieBanner.classList.remove('hidden'); } function hideCookieBanner(){ cookieBanner.classList.add('hidden'); } if(cookieAccept) cookieAccept.addEventListener('click', ()=>{ setConsent('accepted'); hideCookieBanner(); announceModal('Preferencias guardadas', 'Has aceptado el almacenamiento local para mejorar tu experiencia.', 'success'); }); if(cookieDecline) cookieDecline.addEventListener('click', ()=>{ setConsent('declined'); hideCookieBanner(); announceModal('Preferencias actualizadas', 'Has rechazado el almacenamiento local. Algunas funciones (recordatorios/preferencias) se desactivan.', 'warning'); wipeLocalFeatures(); }); if(cookieClose) cookieClose.addEventListener('click', ()=>{ hideCookieBanner(); }); function storageAllowed(){ const c = getConsent(); return c==='accepted'; } function wipeLocalFeatures(){ try{ localStorage.removeItem('vp_contact_pref_v1'); localStorage.removeItem('vp_contact_reminder_v1'); }catch(e){} syncSavedPrefUI(); syncReminderUI(); } setTimeout(showCookieBannerIfNeeded, 650); const resultModal = document.getElementById('resultModal'); const modalCloseX = document.getElementById('modalCloseX'); const modalPrimary = document.getElementById('modalPrimary'); const modalSecondary = document.getElementById('modalSecondary'); const modalTitle = document.getElementById('modalTitle'); const modalSubtitle = document.getElementById('modalSubtitle'); const modalBody = document.getElementById('modalBody'); const modalIcon = document.getElementById('modalIcon'); function nodeP(t){ const p = document.createElement('p'); p.className = 'leading-relaxed'; p.textContent = t; return p; } function nodeList(items){ const ul = document.createElement('ul'); ul.className = 'grid gap-2'; items.forEach(it=>{ const li = document.createElement('li'); li.className = 'rounded-xl border border-slate-200/70 bg-white/60 px-3 py-2 text-sm text-slate-800 dark:border-slate-800/60 dark:bg-slate-950/25 dark:text-slate-100'; li.textContent = it; ul.appendChild(li); }); return ul; } function iconFor(type){ if(type==='success') return ''; if(type==='error') return ''; if(type==='warning') return ''; return ''; } function showModal({type='info', title='—', subtitle='—', bodyNodes=[], primaryText='Entendido', secondaryText='Cerrar', onPrimary=null, onSecondary=null}){ if(!resultModal) return; modalTitle.textContent = title; modalSubtitle.textContent = subtitle; modalBody.innerHTML = ''; bodyNodes.forEach(n=>modalBody.appendChild(n)); modalPrimary.textContent = primaryText; modalSecondary.textContent = secondaryText; modalIcon.className = 'mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-2xl'; if(type==='success') modalIcon.classList.add('bg-emerald-500/12','text-emerald-800','dark:text-emerald-200'); else if(type==='error') modalIcon.classList.add('bg-rose-500/12','text-rose-800','dark:text-rose-200'); else if(type==='warning') modalIcon.classList.add('bg-amber-500/14','text-amber-800','dark:text-amber-200'); else modalIcon.classList.add('bg-sky-500/12','text-sky-800','dark:text-sky-200'); modalIcon.innerHTML = iconFor(type); const cleanup = ()=>{ modalPrimary.onclick = null; modalSecondary.onclick = null; }; modalPrimary.onclick = ()=>{ try{ if(onPrimary) onPrimary(); } finally { cleanup(); resultModal.close(); } }; modalSecondary.onclick = ()=>{ try{ if(onSecondary) onSecondary(); } finally { cleanup(); resultModal.close(); } }; resultModal.showModal(); setTimeout(()=>modalPrimary.focus(), 0); } function announceModal(t, s, type){ showModal({type, title:t, subtitle:'', bodyNodes:[nodeP(s)], primaryText:'OK', secondaryText:'Cerrar'}); } if(modalCloseX) modalCloseX.addEventListener('click', ()=>resultModal.close()); if(resultModal) resultModal.addEventListener('click', (e)=>{ const r = resultModal.getBoundingClientRect(); const inside = e.clientX>=r.left && e.clientX<=r.right && e.clientY>=r.top && e.clientY<=r.bottom; if(!inside) resultModal.close(); }); const contactForm = document.getElementById('contactForm'); const fullName = document.getElementById('fullName'); const email = document.getElementById('email'); const phone = document.getElementById('phone'); const topic = document.getElementById('topic'); const timeBand = document.getElementById('timeBand'); const timeMinute = document.getElementById('timeMinute'); const minutePreview = document.getElementById('minutePreview'); const bandPreview = document.getElementById('bandPreview'); const savePrefBtn = document.getElementById('savePrefBtn'); const message = document.getElementById('message'); const msgCount = document.getElementById('msgCount'); const consent = document.getElementById('consent'); const submitBtn = document.getElementById('submitBtn'); const sendSpinner = document.getElementById('sendSpinner'); const resetBtn = document.getElementById('resetBtn'); const tzChip = document.getElementById('tzChip'); function setErr(id, text){ const el = document.getElementById('err_'+id); if(!el) return; if(text){ el.textContent = text; el.classList.remove('hidden'); }else{ el.textContent = ''; el.classList.add('hidden'); } } function normalizeSpaces(s){ return (s||'').replace(/\s+/g,' ').trim(); } function validEmail(v){ const s = String(v||'').trim(); if(s.length<6 || s.length>120) return false; return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i.test(s); } function validPhoneOptional(v){ const s = String(v||'').trim(); if(!s) return true; if(s.length<7 || s.length>22) return false; return /^\+?[0-9][0-9\s().-]{6,21}$/.test(s); } function bandWindow(b){ if(b==='mañana') return {startH:10,startM:0,endH:12,endM:30,label:'Mañana (10:00–12:30)'}; if(b==='mediodia') return {startH:12,startM:30,endH:15,endM:0,label:'Mediodía (12:30–15:00)'}; if(b==='tarde') return {startH:15,startM:0,endH:18,endM:0,label:'Tarde (15:00–18:00)'}; return null; } function pad2(n){ return String(n).padStart(2,'0'); } function updateMinutePreview(){ if(!minutePreview) return; minutePreview.textContent = pad2(Number(timeMinute.value||0))+' min'; } function updateBandPreview(){ const b = timeBand.value; const w = bandWindow(b); if(!w){ bandPreview.textContent = 'Selecciona una franja para ver sugerencia.'; return; } const m = Number(timeMinute.value||0); const suggested = {h:w.startH, min:m}; const s = `Sugerencia: ${pad2(suggested.h)}:${pad2(suggested.min)} (hora local). Franja: ${w.label}.`; bandPreview.textContent = s; } if(timeMinute){ timeMinute.addEventListener('input', ()=>{ updateMinutePreview(); updateBandPreview(); }); } if(timeBand){ timeBand.addEventListener('change', updateBandPreview); } updateMinutePreview(); updateBandPreview(); function updateMsgCount(){ const len = (message.value||'').length; msgCount.textContent = `${len}/800`; msgCount.classList.toggle('text-rose-600', len>800); msgCount.classList.toggle('dark:text-rose-300', len>800); } if(message){ message.addEventListener('input', ()=>{ if(message.value.length>1200) message.value = message.value.slice(0,1200); updateMsgCount(); }); } updateMsgCount(); function validateAll(){ const errs = []; const nm = normalizeSpaces(fullName.value); if(nm.length<2 || nm.length>80){ errs.push('Nombre: introduce entre 2 y 80 caracteres.'); setErr('fullName','Introduce entre 2 y 80 caracteres.'); } else setErr('fullName',''); const em = String(email.value||'').trim(); if(!validEmail(em)){ errs.push('Email: formato inválido.'); setErr('email','Revisa el formato del email.'); } else setErr('email',''); const ph = String(phone.value||'').trim(); if(!validPhoneOptional(ph)){ errs.push('Teléfono: usa dígitos y signos comunes (+, espacios, guiones).'); setErr('phone','Formato no válido. Ej.: +34 612 345 678'); } else setErr('phone',''); if(!topic.value){ errs.push('Tema: selecciona un tema.'); setErr('topic','Selecciona un tema.'); } else setErr('topic',''); if(!timeBand.value){ errs.push('Franja: selecciona una franja horaria.'); setErr('timeBand','Selecciona una franja.'); } else setErr('timeBand',''); const msg = String(message.value||'').trim(); if(msg.length<12){ errs.push('Mensaje: escribe al menos 12 caracteres.'); setErr('message','Escribe al menos 12 caracteres.'); } else if(msg.length>800){ errs.push('Mensaje: máximo 800 caracteres.'); setErr('message','Máximo 800 caracteres.'); } else setErr('message',''); if(!consent.checked){ errs.push('Consentimiento: debes aceptar para continuar.'); setErr('consent','Marca la casilla para poder enviar.'); } else setErr('consent',''); return errs; } function focusFirstError(){ const order = [fullName,email,phone,topic,timeBand,message,consent]; for(const el of order){ const id = el && el.id; if(!id) continue; const errEl = document.getElementById('err_'+id); if(errEl && !errEl.classList.contains('hidden')){ el.focus({preventScroll:false}); return; } } } function simulateSend(payload){ return new Promise((resolve, reject)=>{ const t = 800 + Math.round(Math.random()*750); setTimeout(()=>{ const r = Math.random(); if(r < 0.86) resolve({ok:true, ticket:'VP-'+String(Math.floor(100000 + Math.random()*900000))}); else reject(new Error('Temporalmente no disponible. Intenta de nuevo en unos minutos.')); }, t); }); } function setSending(s){ submitBtn.disabled = s; resetBtn.disabled = s; submitBtn.classList.toggle('opacity-80', s); sendSpinner.classList.toggle('hidden', !s); } function prefKey(){ return 'vp_contact_pref_v1'; } function savePref(){ if(!storageAllowed()){ showModal({ type:'warning', title:'Preferencias desactivadas', subtitle:'Necesitas aceptar almacenamiento local', bodyNodes:[nodeP('Para guardar la franja horaria y habilitar recordatorios locales, acepta cookies/almacenamiento en el banner inferior.')], primaryText:'Entendido' }); return; } const b = timeBand.value; if(!b){ setErr('timeBand','Selecciona una franja para poder guardarla.'); focusFirstError(); return; } const m = Number(timeMinute.value||0); const w = bandWindow(b); const obj = {band:b, minute:m, label:w ? w.label : b, savedAt: Date.now()}; try{ localStorage.setItem(prefKey(), JSON.stringify(obj)); }catch(e){} syncSavedPrefUI(); announceModal('Preferencia guardada', `Franja: ${obj.label}. Minuto: ${pad2(obj.minute)}.`, 'success'); } if(savePrefBtn) savePrefBtn.addEventListener('click', savePref); function loadPref(){ try{ const raw = localStorage.getItem(prefKey()); if(!raw) return null; const obj = JSON.parse(raw); if(!obj || !obj.band) return null; return obj; }catch(e){ return null; } } const savedPrefText = document.getElementById('savedPrefText'); const clearPrefBtn = document.getElementById('clearPrefBtn'); function syncSavedPrefUI(){ const can = storageAllowed(); const pref = can ? loadPref() : null; if(savedPrefText){ if(!can){ savedPrefText.textContent = 'Almacenamiento local rechazado: preferencias desactivadas.'; }else if(pref){ const dt = new Date(pref.savedAt||Date.now()); const when = `${pad2(dt.getDate())}/${pad2(dt.getMonth()+1)}/${dt.getFullYear()} ${pad2(dt.getHours())}:${pad2(dt.getMinutes())}`; savedPrefText.textContent = `Franja: ${pref.label} · Minuto: ${pad2(pref.minute)} · Guardado: ${when}`; }else{ savedPrefText.textContent = 'Aún no has elegido una franja horaria.'; } } if(clearPrefBtn){ clearPrefBtn.disabled = !can || !pref; } } if(clearPrefBtn){ clearPrefBtn.addEventListener('click', ()=>{ if(!storageAllowed()) return; try{ localStorage.removeItem(prefKey()); }catch(e){} syncSavedPrefUI(); announceModal('Preferencia eliminada', 'Se borró la franja horaria guardada.', 'info'); }); } function applyPrefToInputs(){ if(!storageAllowed()) return; const pref = loadPref(); if(!pref) return; if(timeBand && pref.band) timeBand.value = pref.band; if(timeMinute && typeof pref.minute==='number') timeMinute.value = String(Math.max(0, Math.min(59, pref.minute))); updateMinutePreview(); updateBandPreview(); } const copyPhoneBtn = document.getElementById('copyPhoneBtn'); const copyPhoneLabel = document.getElementById('copyPhoneLabel'); if(copyPhoneBtn){ copyPhoneBtn.addEventListener('click', async ()=>{ const txt = '+34 657 192 048'; try{ await navigator.clipboard.writeText(txt); if(copyPhoneLabel) copyPhoneLabel.textContent = 'Copiado'; setTimeout(()=>{ if(copyPhoneLabel) copyPhoneLabel.textContent = 'Copiar'; }, 1100); }catch(e){ announceModal('No se pudo copiar', 'Tu navegador no permite copiar automáticamente. Selecciona y copia el número manualmente: +34 657 192 048', 'warning'); } }); } function localTZLabel(){ try{ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || 'local'; const offMin = new Date().getTimezoneOffset(); const sign = offMin<=0 ? '+' : '-'; const abs = Math.abs(offMin); const hh = pad2(Math.floor(abs/60)); const mm = pad2(abs%60); return `${tz} (UTC${sign}${hh}:${mm})`; }catch(e){ return 'local'; } } if(tzChip) tzChip.textContent = 'Tu zona: '+localTZLabel(); function computeOpenNowCET(){ const now = new Date(); const cetNow = new Date(now.toLocaleString('en-US', { timeZone: 'Europe/Madrid' })); const day = cetNow.getDay(); const h = cetNow.getHours(); const m = cetNow.getMinutes(); const mins = h*60+m; const open = (day>=1 && day<=5) && (mins>=10*60) && (mins<=18*60); return {open, cetNow}; } const openDot = document.getElementById('openDot'); const openLabel = document.getElementById('openLabel'); function updateOpenUI(){ const st = computeOpenNowCET(); if(openDot){ openDot.className = 'h-2.5 w-2.5 rounded-full ' + (st.open ? 'bg-emerald-500' : 'bg-rose-500'); } if(openLabel){ const hh = pad2(st.cetNow.getHours()); const mm = pad2(st.cetNow.getMinutes()); openLabel.textContent = st.open ? `Abierto (CET ${hh}:${mm})` : `Cerrado (CET ${hh}:${mm})`; } } updateOpenUI(); setInterval(updateOpenUI, 30000); const slaText = document.getElementById('slaText'); const slaCountdown = document.getElementById('slaCountdown'); const slaBar = document.getElementById('slaBar'); function nextBusinessSlotCET(){ const now = new Date(); const cetNow = new Date(now.toLocaleString('en-US', { timeZone: 'Europe/Madrid' })); const d = new Date(cetNow.getTime()); const day = d.getDay(); const mins = d.getHours()*60 + d.getMinutes(); function setToDayAt(dayDelta, hh, mm){ const x = new Date(d.getTime()); x.setDate(x.getDate()+dayDelta); x.setHours(hh, mm, 0, 0); return x; } const isWorkday = (wd)=>wd>=1 && wd<=5; if(isWorkday(day)){ if(mins < 10*60) return {start: setToDayAt(0,10,0), reason:'Antes del horario'}; if(mins <= 18*60) return {start: d, reason:'Dentro del horario'}; let delta = 1; while(!isWorkday((day+delta)%7)) delta++; return {start: setToDayAt(delta,10,0), reason:'Fuera del horario'}; }else{ let delta = 1; while(!isWorkday((day+delta)%7)) delta++; return {start: setToDayAt(delta,10,0), reason:'Fin de semana'}; } } function addBusinessHoursCET(startCET, hours){ const d = new Date(startCET.getTime()); let remaining = hours*60; while(remaining>0){ const day = d.getDay(); const isWorkday = day>=1 && day<=5; if(!isWorkday){ d.setDate(d.getDate()+1); d.setHours(10,0,0,0); continue; } const curMin = d.getHours()*60 + d.getMinutes(); if(curMin < 10*60){ d.setHours(10,0,0,0); continue; } if(curMin >= 18*60){ d.setDate(d.getDate()+1); d.setHours(10,0,0,0); continue; } const endMin = 18*60; const avail = endMin - curMin; const take = Math.min(avail, remaining); d.setMinutes(d.getMinutes()+take); remaining -= take; if(remaining>0 && (d.getHours()*60+d.getMinutes())>=endMin){ d.setDate(d.getDate()+1); d.setHours(10,0,0,0); } } return d; } function toLocalFromCET(cetDate){ const now = new Date(); const inv = new Date(cetDate.toLocaleString('en-US', { timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone || undefined })); const offset = now.getTime() - new Date(now.toLocaleString('en-US', { timeZone: 'Europe/Madrid' })).getTime(); return new Date(cetDate.getTime() + offset); } const slaTargetHours = 24; let slaStartCET = null; let slaDeadlineCET = null; function initSLA(){ const slot = nextBusinessSlotCET(); slaStartCET = slot.start; slaDeadlineCET = addBusinessHoursCET(slot.start, slaTargetHours); if(slaText){ const startLocal = new Date(slaStartCET.getTime() + (new Date().getTime() - new Date(new Date().toLocaleString('en-US', { timeZone: 'Europe/Madrid' })).getTime())); const dlLocal = new Date(slaDeadlineCET.getTime() + (new Date().getTime() - new Date(new Date().toLocaleString('en-US', { timeZone: 'Europe/Madrid' })).getTime())); const fmt = (d)=>`${pad2(d.getDate())}/${pad2(d.getMonth()+1)} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`; slaText.textContent = `Estimación: respuesta en ≤ ${slaTargetHours}h laborables (CET). Ventana: ${fmt(startLocal)} → ${fmt(dlLocal)} (tu hora local).`; } } function updateSLACountdown(){ if(!slaStartCET || !slaDeadlineCET) return; const now = new Date(); const dlLocal = new Date(slaDeadlineCET.getTime() + (now.getTime() - new Date(now.toLocaleString('en-US', { timeZone: 'Europe/Madrid' })).getTime())); const ms = dlLocal.getTime() - now.getTime(); const clamped = Math.max(0, ms); const s = Math.floor(clamped/1000); const hh = Math.floor(s/3600); const mm = Math.floor((s%3600)/60); const ss = s%60; if(slaCountdown) slaCountdown.textContent = `Quedan ${pad2(hh)}:${pad2(mm)}:${pad2(ss)}`; const startLocal = new Date(slaStartCET.getTime() + (now.getTime() - new Date(now.toLocaleString('en-US', { timeZone: 'Europe/Madrid' })).getTime())); const total = Math.max(1, dlLocal.getTime() - startLocal.getTime()); const elapsed = Math.min(total, Math.max(0, now.getTime() - startLocal.getTime())); const pct = Math.max(0, Math.min(100, Math.round((elapsed/total)*100))); if(slaBar) slaBar.style.width = pct+'%'; } initSLA(); updateSLACountdown(); setInterval(updateSLACountdown, 1000); if(resetBtn){ resetBtn.addEventListener('click', ()=>{ contactForm.reset(); setErr('fullName',''); setErr('email',''); setErr('phone',''); setErr('topic',''); setErr('timeBand',''); setErr('message',''); setErr('consent',''); updateMsgCount(); updateMinutePreview(); updateBandPreview(); fullName.focus(); }); window.addEventListener('keydown', (e)=>{ if(e.key==='Escape'){ if(document.activeElement && (document.activeElement.tagName==='INPUT' || document.activeElement.tagName==='TEXTAREA' || document.activeElement.tagName==='SELECT')){ resetBtn.click(); } } }, {passive:true}); } const quickBtns = document.querySelectorAll('button[data-prefill]'); quickBtns.forEach(b=>{ b.addEventListener('click', ()=>{ const t = b.getAttribute('data-prefill') || ''; if(message){ message.value = (message.value? (message.value.trim()+'\n\n') : '') + t; updateMsgCount(); message.focus(); } }); }); if(contactForm){ contactForm.addEventListener('submit', async (e)=>{ e.preventDefault(); const errs = validateAll(); if(errs.length){ showModal({ type:'error', title:'Revisa el formulario', subtitle:'Hay campos que necesitan corrección', bodyNodes:[nodeP('Corrige lo siguiente y vuelve a enviar:'), nodeList(errs)], primaryText:'Ir al primer error', secondaryText:'Cerrar', onPrimary: ()=>focusFirstError() }); return; } const payload = { fullName: normalizeSpaces(fullName.value), email: String(email.value||'').trim(), phone: String(phone.value||'').trim(), topic: topic.value, timeBand: timeBand.value, minute: Number(timeMinute.value||0), message: String(message.value||'').trim(), ts: Date.now() }; setSending(true); try{ const res = await simulateSend(payload); setSending(false); if(storageAllowed()){ const pref = loadPref(); if(pref && pref.band){ timeBand.value = pref.band; timeMinute.value = String(pref.minute||0); updateMinutePreview(); updateBandPreview(); } } showModal({ type:'success', title:'Mensaje enviado', subtitle:`Ticket: ${res.ticket}`, bodyNodes:[ nodeP('Gracias. Hemos recibido tu consulta.'), nodeP(`Preferencia horaria: ${bandWindow(payload.timeBand)?.label || payload.timeBand}, minuto ${pad2(payload.minute)}.`), nodeP('Si has programado recordatorio local, te avisaremos en este navegador.') ], primaryText:'Perfecto', secondaryText:'Cerrar', onPrimary: ()=>{ contactForm.reset(); updateMsgCount(); updateMinutePreview(); updateBandPreview(); } }); }catch(err){ setSending(false); showModal({ type:'warning', title:'No se pudo enviar', subtitle:'Error temporal', bodyNodes:[nodeP(String(err && err.message ? err.message : 'Error desconocido.'))], primaryText:'Reintentar', secondaryText:'Cerrar', onPrimary: ()=>contactForm.requestSubmit() }); } }); } const reminderBtn = document.getElementById('reminderBtn'); const reminderModal = document.getElementById('reminderModal'); const reminderCloseX = document.getElementById('reminderCloseX'); const reminderCancelBtn = document.getElementById('reminderCancelBtn'); const reminderForm = document.getElementById('reminderForm'); const reminderDelay = document.getElementById('reminderDelay'); const reminderSummary = document.getElementById('reminderSummary'); const reminderLocalTime = document.getElementById('reminderLocalTime'); const reminderStatus = document.getElementById('reminderStatus'); const cancelReminderBtn = document.getElementById('cancelReminderBtn'); function reminderKey(){ return 'vp_contact_reminder_v1'; } function setReminderErr(t){ const el = document.getElementById('err_reminderDelay'); if(!el) return; if(t){ el.textContent = t; el.classList.remove('hidden'); }else{ el.textContent = ''; el.classList.add('hidden'); } } function loadReminder(){ try{ const raw = localStorage.getItem(reminderKey()); if(!raw) return null; const obj = JSON.parse(raw); if(!obj || !obj.fireAt) return null; return obj; }catch(e){ return null; } } function saveReminder(obj){ try{ localStorage.setItem(reminderKey(), JSON.stringify(obj)); }catch(e){} } function clearReminder(){ try{ localStorage.removeItem(reminderKey()); }catch(e){} } let reminderTimer = null; function fmtLocal(dt){ return `${pad2(dt.getDate())}/${pad2(dt.getMonth()+1)}/${dt.getFullYear()} ${pad2(dt.getHours())}:${pad2(dt.getMinutes())}`; } function syncReminderUI(){ const can = storageAllowed(); const r = can ? loadReminder() : null; if(!reminderStatus) return; if(!can){ reminderStatus.textContent = 'Almacenamiento local rechazado: recordatorios desactivados.'; if(cancelReminderBtn) cancelReminderBtn.disabled = true; if(reminderBtn) reminderBtn.disabled = true; stopReminderLoop(); return; } if(reminderBtn) reminderBtn.disabled = false; if(r){ const fireAt = new Date(r.fireAt); const leftMs = fireAt.getTime() - Date.now(); const left = Math.max(0, Math.floor(leftMs/1000)); const hh = Math.floor(left/3600); const mm = Math.floor((left%3600)/60); const ss = left%60; reminderStatus.textContent = `Activo: ${fmtLocal(fireAt)} (en ${pad2(hh)}:${pad2(mm)}:${pad2(ss)})`; if(cancelReminderBtn) cancelReminderBtn.disabled = false; startReminderLoop(); }else{ reminderStatus.textContent = 'Sin recordatorios activos.'; if(cancelReminderBtn) cancelReminderBtn.disabled = true; stopReminderLoop(); } } function startReminderLoop(){ if(reminderTimer) return; reminderTimer = setInterval(()=>{ const r = loadReminder(); if(!r) { syncReminderUI(); return; } const fireAt = new Date(r.fireAt).getTime(); const now = Date.now(); if(now >= fireAt){ clearReminder(); syncReminderUI(); showModal({ type:'success', title:'Recordatorio', subtitle:'Tu preferencia horaria está lista', bodyNodes:[ nodeP('Este es tu recordatorio local para retomar el contacto o enviar tu consulta.'), nodeP('Si lo deseas, ajusta la franja horaria y vuelve a programar otro recordatorio.') ], primaryText:'Abrir formulario', secondaryText:'Cerrar', onPrimary: ()=>{ document.getElementById('contactForm').scrollIntoView({behavior:'smooth', block:'start'}); fullName.focus(); } }); }else{ syncReminderUI(); } }, 1000); } function stopReminderLoop(){ if(reminderTimer){ clearInterval(reminderTimer); reminderTimer = null; } } function updateReminderSummary(){ if(!reminderDelay || !reminderSummary || !reminderLocalTime) return; const v = reminderDelay.value; if(!v){ reminderSummary.textContent = 'Selecciona una opción.'; reminderLocalTime.textContent = '—'; return; } const sec = Number(v); const fireAt = new Date(Date.now() + sec*1000); reminderSummary.textContent = `Se mostrará un aviso dentro de ${Math.round(sec/60)} min (aprox.).`; reminderLocalTime.textContent = fmtLocal(fireAt); } if(reminderDelay) reminderDelay.addEventListener('change', ()=>{ setReminderErr(''); updateReminderSummary(); }); if(reminderBtn){ reminderBtn.addEventListener('click', ()=>{ if(!storageAllowed()){ showModal({ type:'warning', title:'Recordatorios desactivados', subtitle:'Necesitas aceptar almacenamiento local', bodyNodes:[nodeP('Para programar recordatorios locales debes aceptar cookies/almacenamiento en el banner inferior.')], primaryText:'Entendido' }); return; } const pref = loadPref(); if(!pref){ showModal({ type:'info', title:'Antes guarda una preferencia', subtitle:'Franja horaria', bodyNodes:[nodeP('Selecciona una franja horaria en el formulario y pulsa “Guardar preferencia”. Luego podrás programar el recordatorio.')], primaryText:'Ir a franja', secondaryText:'Cerrar', onPrimary: ()=>{ document.getElementById('timeBand').scrollIntoView({behavior:'smooth', block:'center'}); timeBand.focus(); } }); return; } reminderDelay.value = ''; setReminderErr(''); updateReminderSummary(); reminderModal.showModal(); setTimeout(()=>reminderDelay.focus(), 0); }); } if(reminderCloseX) reminderCloseX.addEventListener('click', ()=>reminderModal.close()); if(reminderCancelBtn) reminderCancelBtn.addEventListener('click', ()=>reminderModal.close()); if(reminderModal) reminderModal.addEventListener('click', (e)=>{ const r = reminderModal.getBoundingClientRect(); const inside = e.clientX>=r.left && e.clientX<=r.right && e.clientY>=r.top && e.clientY<=r.bottom; if(!inside) reminderModal.close(); }); if(reminderForm){ reminderForm.addEventListener('submit', (e)=>{ e.preventDefault(); if(!storageAllowed()){ reminderModal.close(); return; } const v = reminderDelay.value; if(!v){ setReminderErr('Selecciona cuándo quieres el recordatorio.'); reminderDelay.focus(); return; } setReminderErr(''); const sec = Number(v); const pref = loadPref(); const obj = { fireAt: Date.now() + sec*1000, createdAt: Date.now(), prefBand: pref ? pref.band : null, prefMinute: pref ? pref.minute : null }; saveReminder(obj); reminderModal.close(); syncReminderUI(); announceModal('Recordatorio programado', `Te avisaremos el ${fmtLocal(new Date(obj.fireAt))} (hora local).`, 'success'); }); } if(cancelReminderBtn){ cancelReminderBtn.addEventListener('click', ()=>{ if(!storageAllowed()) return; const r = loadReminder(); if(!r) return; showModal({ type:'warning', title:'Cancelar recordatorio', subtitle:'Confirmación', bodyNodes:[nodeP('¿Quieres cancelar el recordatorio local programado?')], primaryText:'Sí, cancelar', secondaryText:'No', onPrimary: ()=>{ clearReminder(); syncReminderUI(); } }); }); } const faqBtn = document.getElementById('faqBtn'); const faqModal = document.getElementById('faqModal'); const faqCloseX = document.getElementById('faqCloseX'); const faqCloseBtn = document.getElementById('faqCloseBtn'); const faqToFormBtn = document.getElementById('faqToFormBtn'); if(faqBtn) faqBtn.addEventListener('click', ()=>{ faqModal.showModal(); }); if(faqCloseX) faqCloseX.addEventListener('click', ()=>faqModal.close()); if(faqCloseBtn) faqCloseBtn.addEventListener('click', ()=>faqModal.close()); if(faqToFormBtn) faqToFormBtn.addEventListener('click', ()=>{ faqModal.close(); document.getElementById('contactForm').scrollIntoView({behavior:'smooth', block:'start'}); }); if(faqModal) faqModal.addEventListener('click', (e)=>{ const r = faqModal.getBoundingClientRect(); const inside = e.clientX>=r.left && e.clientX<=r.right && e.clientY>=r.top && e.clientY<=r.bottom; if(!inside) faqModal.close(); }); function hotkeys(e){ if((e.altKey || e.metaKey) && e.key==='?'){ e.preventDefault(); faqModal.showModal(); } } window.addEventListener('keydown', hotkeys); syncSavedPrefUI(); applyPrefToInputs(); syncReminderUI(); })();