const { Card, Button, Badge, Divider, Field, Input, Checkbox, Textarea, Tabs, ScheduleRow, Radio, SectionLabel } = window.PilatesLifeStudioDesignSystem_f49721; const MODES = [ ['grupal', 'Clase grupal', 'Máximo seis personas, una camilla por persona.', 'reformer'], ['privada', 'Clase privada', 'Camilla individual, horario a convenir.', 'private'], ['valoracion', 'Valoración (primera vez)', 'Sesión individual de evaluación para quienes empiezan.', 'private'], ]; const FORM_INICIAL = { notas: '', acepta: true }; function BookingPage({ onSubmit, onGoToPrecios, onNavigate, sesion, refreshSignal, resetSignal }) { const dias = React.useMemo(() => proximosDias(7), []); const [mode, setMode] = React.useState('grupal'); const [fecha, setFecha] = React.useState(dias[0].id); const [horarios, setHorarios] = React.useState([]); const [cerradoNocturno, setCerradoNocturno] = React.useState(false); const [esDomingo, setEsDomingo] = React.useState(false); const [motivoBloqueo, setMotivoBloqueo] = React.useState(null); const [cargando, setCargando] = React.useState(false); const [errorCarga, setErrorCarga] = React.useState(''); const [selected, setSelected] = React.useState(null); const [form, setForm] = React.useState(FORM_INICIAL); const [errorForm, setErrorForm] = React.useState(''); const [esperaBusy, setEsperaBusy] = React.useState(null); const [esperaMsg, setEsperaMsg] = React.useState(null); React.useEffect(() => { setSelected(null); setEsperaMsg(null); }, [mode, fecha]); React.useEffect(() => { let vigente = true; setCargando(true); setErrorCarga(''); fetchDisponibilidad(fecha, mode) .then(datos => { if (vigente) { setHorarios(datos.horarios); setCerradoNocturno(Boolean(datos.cerrado_nocturno)); setEsDomingo(Boolean(datos.es_domingo)); setMotivoBloqueo(datos.motivo_bloqueo ?? null); } }) .catch(err => { if (vigente) setErrorCarga(err.message); }) .finally(() => { if (vigente) setCargando(false); }); return () => { vigente = false; }; }, [fecha, mode, refreshSignal]); React.useEffect(() => { if (resetSignal) { setSelected(null); setForm(FORM_INICIAL); } }, [resetSignal]); const campo = (clave) => (e) => setForm({ ...form, [clave]: e.target.value }); // Anotarse a la lista de espera de un horario lleno (ver ScheduleRow: cuando spots=0 y // hay onAction, el botón ya se llama "Esperar" por diseño). No hace falta refetch: se // marca en_lista_espera=true localmente para que el botón deje de ofrecerse dos veces. const anotarseListaEspera = (hora) => { if (esperaBusy) return; setEsperaBusy(hora); setEsperaMsg(null); unirseListaEspera({ fecha, hora, tipo: mode }) .then(() => { setHorarios(prev => prev.map(x => x.hora === hora ? { ...x, en_lista_espera: true } : x)); setEsperaMsg({ tono: 'ok', texto: 'Listo, te avisaremos por WhatsApp si se libera un cupo.' }); }) .catch(err => setEsperaMsg({ tono: 'error', texto: err.message })) .finally(() => setEsperaBusy(null)); }; const confirmar = () => { if (!selected) return setErrorForm('Elige un horario disponible.'); if (!form.acepta) return setErrorForm('Acepta la política de cancelación para continuar.'); setErrorForm(''); onSubmit({ fecha: selected.fecha, hora: selected.hora, etiqueta: selected.etiqueta, tipo: mode, notas: form.notas.trim(), }); }; // Reservar ya no es anónimo: hace falta cuenta y al menos un crédito disponible. // `sesion` llega desde Site.jsx: null mientras se consulta, false si no hay sesión, // o el objeto de mi_cuenta.php si el cliente está logueado. if (sesion === null) { return (

Cargando…

); } if (sesion === false) { return (
Reservar

Inicia sesión para reservar

Solo clientes registrados con créditos en su tiquetera pueden reservar. Si ya tienes cuenta, inicia sesión; si no, créala en un minuto.

); } if ((sesion.cliente?.saldo_tiquetera ?? 0) < 1) { return (
Reservar

No te quedan créditos

Tu tiquetera está en 0. Escríbenos para recargarla y poder reservar tu próxima clase.

); } return (
Reservar

Compra tu clase y reserva en línea

1 · Modalidad
{MODES.map(([id, t, d]) => ( setMode(id)} padding="var(--space-5)">
setMode(id)} /> {t}

{d}

))}

¿Solo quieres ver precios? { e.preventDefault(); onGoToPrecios(); }}>Consulta los planes.

2 · Horario
{cargando &&

Cargando horarios…

} {!cargando && errorCarga &&

{errorCarga}

} {!cargando && !errorCarga && cerradoNocturno && (

Las reservas para este día están cerradas de 9:00 pm a 8:00 am. Vuelve a intentarlo después de las 8:00 am.

)} {!cargando && !errorCarga && !cerradoNocturno && horarios.length === 0 && (

{esDomingo ? 'El estudio no abre los domingos. Elige otro día.' : motivoBloqueo ? `Este día no está disponible: ${motivoBloqueo}` : 'Este día no está disponible para reservas. Elige otro día.'}

)} {!cargando && !errorCarga && !cerradoNocturno && esperaMsg && (

{esperaMsg.texto}

)} {!cargando && !errorCarga && !cerradoNocturno && horarios.map(h => { const lleno = !h.pasado && !h.cierra_pronto && !h.bloqueado && !h.disponible; const titulo = h.pasado ? 'Ya pasó' : (h.cierra_pronto || h.bloqueado) ? 'Cerrado' : h.disponible ? 'Disponible' : (h.en_lista_espera ? 'Sin cupo · ya estás en la lista de espera' : 'Sin cupo'); return ( { const yaEstaSeleccionado = selected && selected.hora === h.hora; setSelected(yaEstaSeleccionado ? null : { fecha, hora: h.hora, etiqueta: h.etiqueta }); } : (lleno && !h.en_lista_espera) ? () => anotarseListaEspera(h.hora) : undefined } style={selected && selected.hora === h.hora ? { background: 'var(--surface-page-alt)', boxShadow: 'inset 3px 0 0 var(--blue)' } : undefined} /> ); })}
3 · Detalles adicionales