/* BookingModal — agendar visita desde el portal del comprador */ const bookingStyles = ` .bk-overlay { position: fixed; inset: 0; background: oklch(0.2 0.03 290 / .5); backdrop-filter: blur(4px); z-index: 90; display: flex; align-items: center; justify-content: center; padding: 18px; } .bk { background: #fff; border-radius: 20px; width: min(560px, 100%); max-height: 90vh; overflow-y: auto; padding: 24px 24px 20px; box-shadow: 0 30px 80px oklch(0.3 0.08 295 / .4); } .bk h2 { font-size: 19px; font-weight: 800; display: flex; align-items: center; gap: 9px; } .bk .bk-sub { font-size: 13px; color: var(--ink-soft); font-weight: 600; margin-top: 5px; line-height: 1.45; } .bk-days { display: flex; gap: 7px; margin-top: 16px; overflow-x: auto; padding-bottom: 4px; } .bk-day { flex: 0 0 auto; border: 1.5px solid var(--line); background: #fff; border-radius: 12px; padding: 9px 13px; text-align: center; cursor: pointer; } .bk-day.on { border-color: var(--purple); background: var(--purple); color: #fff; } .bk-day .dw { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; opacity: .75; } .bk-day .dn { font-family: "Sora"; font-weight: 800; font-size: 16px; margin-top: 2px; } .bk-hours { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 14px; } @media (max-width: 480px) { .bk-hours { grid-template-columns: repeat(3, 1fr); } } .bk-hour { border: 1.5px solid var(--line); background: #fff; border-radius: 10px; padding: 10px 4px; font-weight: 700; font-size: 13.5px; cursor: pointer; text-align: center; font-family: "Sora"; } .bk-hour.on { border-color: var(--purple); background: var(--purple-soft); color: var(--purple-deep); } .bk-hour:disabled { opacity: .32; cursor: not-allowed; text-decoration: line-through; } .bk-form { display: flex; flex-direction: column; gap: 9px; margin-top: 16px; } .bk-form input { border: 1.5px solid var(--line); border-radius: 11px; padding: 12px 14px; font-family: inherit; font-size: 14px; outline: none; } .bk-form input:focus { border-color: var(--purple); } .bk-form input.err { border-color: oklch(0.62 0.19 25); } .bk-cta { margin-top: 14px; width: 100%; border: none; background: var(--purple); color: #fff; font-family: "Sora"; font-weight: 800; font-size: 15px; padding: 14px; border-radius: 12px; cursor: pointer; } .bk-cta:disabled { opacity: .5; cursor: not-allowed; } .bk-cancel { width: 100%; background: none; border: none; color: var(--ink-soft); font-weight: 700; font-size: 13px; padding: 12px 0 2px; cursor: pointer; } .bk-ok { text-align: center; padding: 18px 6px 6px; } .bk-ok .ic { width: 56px; height: 56px; border-radius: 50%; background: var(--green-soft); color: oklch(0.45 0.13 152); font-size: 26px; display: grid; place-items: center; margin: 0 auto; } .bk-ok h3 { font-size: 18px; font-weight: 800; margin-top: 12px; } .bk-ok p { font-size: 13.5px; color: var(--ink-soft); font-weight: 600; margin-top: 8px; line-height: 1.5; } .bk-resumen { text-align: left; background: var(--bg); border: 1px solid var(--line); border-radius: 13px; padding: 13px 15px; margin-top: 14px; display: flex; flex-direction: column; gap: 7px; } .bk-resumen .r { display: flex; justify-content: space-between; font-size: 13px; } .bk-resumen .r span { color: var(--ink-soft); font-weight: 600; } .bk-resumen .r b { font-weight: 800; text-align: right; } .bk-estado { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 800; padding: 4px 11px; border-radius: 999px; background: oklch(0.95 0.05 70); color: oklch(0.5 0.12 70); margin-top: 10px; } .mc-overlay { position: fixed; inset: 0; background: oklch(0.2 0.03 290 / .5); backdrop-filter: blur(4px); z-index: 90; display: flex; align-items: center; justify-content: center; padding: 18px; } .mc { background: #fff; border-radius: 20px; width: min(500px, 100%); max-height: 85vh; overflow-y: auto; padding: 22px 22px 18px; box-shadow: 0 30px 80px oklch(0.3 0.08 295 / .4); } .mc h2 { font-size: 18px; font-weight: 800; display: flex; align-items: center; gap: 9px; } .mc-close { position: absolute; top: 18px; right: 18px; border: none; background: oklch(0.94 0.006 290); width: 32px; height: 32px; border-radius: 50%; font-size: 14px; cursor: pointer; } .mc-row { border: 1px solid var(--line); border-radius: 14px; padding: 13px 15px; margin-top: 12px; display: flex; flex-direction: column; gap: 5px; position: relative; } .mc-row .mc-t { font-family: "Sora"; font-weight: 800; font-size: 13.5px; padding-right: 90px; } .mc-row .mc-when { font-size: 12.5px; color: var(--ink-soft); font-weight: 700; } .mc-row .mc-badge { position: absolute; top: 13px; right: 15px; font-size: 10.5px; font-weight: 800; padding: 4px 10px; border-radius: 999px; } .mc-row .mc-badge.pend { background: oklch(0.95 0.05 70); color: oklch(0.5 0.12 70); } .mc-row .mc-badge.conf { background: oklch(0.95 0.04 152); color: oklch(0.4 0.13 152); } .mc-row .mc-badge.canc { background: oklch(0.94 0.006 290); color: var(--ink-soft); } .mc-empty { text-align: center; padding: 30px 10px; color: var(--ink-soft); font-weight: 600; font-size: 13.5px; } `; function BookingModal({ prop, onClose }) { const { useState, useMemo } = React; const HORAS = [9, 10, 11, 12, 13, 16, 17, 18]; const days = useMemo(() => { const out = []; const t = new Date(); t.setHours(0, 0, 0, 0); for (let i = 1; i <= 7; i++) { const d = new Date(t); d.setDate(t.getDate() + i); out.push(d); } return out; }, []); const [dayIdx, setDayIdx] = useState(0); const [hora, setHora] = useState(null); const [form, setForm] = useState(() => { const c = window.CCVRank ? window.CCVRank.getContacto() : null; return { nombre: (c && c.nombre) || "", whatsapp: (c && c.celular) || "" }; }); const [touched, setTouched] = useState(false); const [ok, setOk] = useState(null); const dk = (d) => { const p = (n) => String(n).padStart(2, "0"); return d.getFullYear() + "-" + p(d.getMonth() + 1) + "-" + p(d.getDate()); }; const busy = useMemo(() => { try { return JSON.parse(localStorage.getItem("ccv_busy") || "[]"); } catch (e) { return []; } }, []); const citas = useMemo(() => { try { return JSON.parse(localStorage.getItem("ccv_citas") || "[]"); } catch (e) { return []; } }, []); function ocupado(fecha, h) { const slot = fecha + " " + String(h).padStart(2, "0") + ":00"; if (busy.includes(slot)) return true; return citas.some((c) => c.fecha === fecha && parseInt(c.hora) === h && c.estado !== "cancelada" && (!c.inmobiliaria || !prop.inmobiliaria || c.inmobiliaria === prop.inmobiliaria)); } const waOK = /^\d{10}$/.test(form.whatsapp.replace(/\D/g, "")); const valid = form.nombre.trim().length > 2 && waOK && hora != null; const fecha = dk(days[dayIdx]); const fmtDia = (d) => d.toLocaleDateString("es-MX", { weekday: "long", day: "numeric", month: "long" }); function reservar() { setTouched(true); if (!valid) return; const cita = { id: "CT" + Date.now(), cliente: form.nombre.trim(), whatsapp: form.whatsapp.replace(/\D/g, ""), fecha, hora: String(hora).padStart(2, "0") + ":00", propiedad: prop.titulo, propId: prop.id, agente: prop.agente || "", inmobiliaria: prop.inmobiliaria || "", origen: "portal", estado: "solicitada", ts: Date.now(), }; try { const arr = JSON.parse(localStorage.getItem("ccv_citas") || "[]"); arr.push(cita); localStorage.setItem("ccv_citas", JSON.stringify(arr)); localStorage.setItem("ccv_last_wa", cita.whatsapp); } catch (e) {} if (window.CCVRank) window.CCVRank.saveContacto({ nombre: cita.cliente, celular: cita.whatsapp }); if (window.CCVRank) window.CCVRank.trackEvent(prop.id, "cita"); if (window.CCVDB && window.CCVDB.ONLINE) window.CCVDB.saveAppointment(cita).catch((e) => console.error(e)); setOk(cita); } return (
{ if (e.target === e.currentTarget) onClose(); }}>
{!ok ? (

📅 Agenda tu visita

{prop.titulo} · {prop.ciudad}. Elige el día y la hora que te acomoden; el asesor confirmará tu cita.
{days.map((d, i) => ( ))}
{HORAS.map((h) => { const occ = ocupado(fecha, h); return ; })}
setForm({ ...form, nombre: e.target.value })} /> setForm({ ...form, whatsapp: e.target.value })} />
) : (

¡Visita solicitada, {ok.cliente.split(" ")[0]}!

El asesor {ok.agente || "responsable"} confirmará tu cita por WhatsApp al {ok.whatsapp}.

Propiedad{ok.propiedad}
Fecha{fmtDia(days[dayIdx])}
Hora{ok.hora}
Asesor{ok.agente || ok.inmobiliaria || "—"}
⏳ Pendiente de confirmación
)}
); } Object.assign(window, { BookingModal, bookingStyles }); (function () { const s = document.createElement("style"); s.textContent = bookingStyles; document.head.appendChild(s); })(); function MyBookingsModal({ whatsapp, onClose }) { const citas = React.useMemo(() => { try { const all = JSON.parse(localStorage.getItem("ccv_citas") || "[]"); const mine = whatsapp ? all.filter((c) => c.whatsapp === whatsapp) : all.filter((c) => c.origen === "portal"); return mine.sort((a, b) => (a.fecha + a.hora).localeCompare(b.fecha + b.hora)); } catch (e) { return []; } }, [whatsapp]); const EST = { solicitada: ["pend", "⏳ Por confirmar"], agendada: ["pend", "⏳ Por confirmar"], confirmada: ["conf", "✓ Confirmada"], cancelada: ["canc", "✕ Cancelada"] }; const fmtDia = (f, h) => { try { return new Date(f + "T" + (h || "12:00")).toLocaleDateString("es-MX", { weekday: "long", day: "numeric", month: "long" }); } catch (e) { return f; } }; return (
{ if (e.target === e.currentTarget) onClose(); }}>

📅 Mis citas

{citas.length === 0 ? (
Aún no has agendado ninguna visita. Cuando reserves una, aparecerá aquí con su estado.
) : citas.map((c) => { const e = EST[c.estado] || EST.solicitada; return (
{e[1]}
{c.propiedad}
{fmtDia(c.fecha, c.hora)} · {c.hora}
{c.agente &&
Asesor: {c.agente}
}
); })}
); } window.MyBookingsModal = MyBookingsModal;