// ===== RIVAMAR Group — Formulario de Reservas Inteligente (v1.1) ===== const { useState, useEffect } = React; function Field({ label, children, hint, required }) { return ( ); } const inputCls = "w-full min-h-[44px] px-3.5 rounded-md bg-carbon border border-white/10 text-gray-100 text-sm placeholder-gray-600 focus:border-gold focus:ring-1 focus:ring-gold outline-none transition-colors"; function ReservaForm({ prefill, onSubmit }) { const [local, setLocal] = useState(prefill.local || ''); const [form, setForm] = useState({ nombre: '', empresa: '', correo: '', telefono: '', fecha: '', hora: '', comentarios: '' }); const [pax, setPax] = useState(2); const [exponorChecked, setExponorChecked] = useState(!!prefill.exponor); const [credencial, setCredencial] = useState(''); const [submitting, setSubmitting] = useState(false); const set = (k) => (e) => setForm({ ...form, [k]: e.target.value }); const localObj = local ? LOCAL_MAP[local] : null; const maxPax = local ? CAPACIDAD[local] : 20; const minPax = local ? MINIMO[local] : 1; const overCapacity = local && pax > maxPax; const underMin = local && pax < minPax; const credValida = credencial.trim().length >= 5; const exponorAplicado = exponorChecked && credValida; useEffect(() => { if (!local) return; setPax((p) => Math.min(Math.max(p, MINIMO[local]), CAPACIDAD[local])); }, [local]); const valid = local && form.nombre && form.correo && form.telefono && form.fecha && form.hora && !overCapacity && !underMin && (!exponorChecked || credValida); const handleSubmit = (e) => { e.preventDefault(); if (!valid || submitting) return; setSubmitting(true); // El folio se pide al servidor (correlativo central y atómico). Si no hay // servidor, nextReservaIdAsync entrega un folio provisional único. const pedirId = window.nextReservaIdAsync ? window.nextReservaIdAsync() : Promise.resolve({ id: nextReservaId(), source: 'local' }); pedirId.then((res) => { onSubmit({ ...form, local, pax, exponor: exponorAplicado, credencial: exponorAplicado ? credencial.trim() : '', id: res.id, _idSource: res.source, estado: 'pendiente', }); setSubmitting(false); }); }; return (
{/* Paso 1 — contacto */}

1 Datos de contacto

{/* Paso 2 — detalle */}

2 Detalle de la reserva

{LOCALES.map((l) => ( ))}
{pax}
{overCapacity && (
{localObj.nombre} admite un máximo de {maxPax} pax por mesa.{local === 'rivamar' ? '' : ' Para grupos mayores, use Rivamar (10+ pax) o solicite un convenio B2B.'}
)} {underMin && (
{localObj.nombre} habilita reservas grupales desde {minPax} pax. Activa flujos especiales de atención.
)}