From add9b2818823113ac9f08f2274f9633272986646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Consultor=C3=ADa=20Alcaraz=20Salazar?= Date: Sun, 11 Oct 2026 08:28:33 +0000 Subject: [PATCH] fase 2.3: portal publico de reservas en linea (5 endpoints anonimos con rate limit y token HMAC, wizard movil, gestion/cancelacion por enlace, distintivo en agenda, editor y enlace publico en Configuracion); apagado por defecto --- frontend/src/App.tsx | 5 + .../src/components/ReservasOnlinePanel.tsx | 165 +++++ frontend/src/lib/publicBooking.ts | 130 ++++ frontend/src/pages/Agenda.tsx | 14 +- frontend/src/pages/Configuracion.tsx | 13 +- frontend/src/pages/Login.tsx | 16 + frontend/src/pages/Reservar.tsx | 446 ++++++++++++ frontend/src/pages/ReservarCita.tsx | 154 ++++ frontend/src/services/odoo.ts | 129 ++++ odoo-addons/skeen_citas/clinic_settings.py | 110 +++ odoo-addons/skeen_citas/models/cita.py | 9 + .../skeen_whatsapp/controllers/__init__.py | 1 + .../skeen_whatsapp/controllers/frontend.py | 1 + .../controllers/public_booking.py | 670 ++++++++++++++++++ 14 files changed, 1859 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/ReservasOnlinePanel.tsx create mode 100644 frontend/src/lib/publicBooking.ts create mode 100644 frontend/src/pages/Reservar.tsx create mode 100644 frontend/src/pages/ReservarCita.tsx create mode 100644 odoo-addons/skeen_whatsapp/controllers/public_booking.py diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0cc9083..6beb7d9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -33,6 +33,8 @@ import WacrmLeads from './pages/WacrmLeads'; import Chat from './pages/Chat'; import MisVentas from './pages/MisVentas'; import Login from './pages/Login'; +import Reservar from './pages/Reservar'; +import ReservarCita from './pages/ReservarCita'; import './App.css'; const FullScreenLoader: FC = () => ( @@ -135,6 +137,9 @@ const App: FC = () => { } /> + {/* Portal público de reservas: FUERA del gate de autenticación. */} + } /> + } /> } /> } /> } /> diff --git a/frontend/src/components/ReservasOnlinePanel.tsx b/frontend/src/components/ReservasOnlinePanel.tsx new file mode 100644 index 0000000..0ad89a2 --- /dev/null +++ b/frontend/src/components/ReservasOnlinePanel.tsx @@ -0,0 +1,165 @@ +import type { FC, ReactNode } from 'react'; +import { useEffect, useState } from 'react'; +import { CalendarClock, Copy, ExternalLink, Save } from 'lucide-react'; +import { Button, Input, TextArea, toast } from './ui'; +import { + odooApi, RESERVAS_ONLINE_DEFAULT, + type ClinicSettings, type ReservasOnlineSettings, +} from '../services/odoo'; +import { enlaceReserva } from '../lib/publicBooking'; + +// Editor del bloque `reservas_online` (portal público de auto-agendado) y +// botón para copiar/abrir el enlace público de la clínica. + +interface Props { + settings: ClinicSettings; + onSaved: (settings: ClinicSettings) => void; +} + +const ReservasOnlinePanel: FC = ({ settings, onSaved }) => { + const [form, setForm] = useState( + settings.reservas_online || RESERVAS_ONLINE_DEFAULT); + const [saving, setSaving] = useState(false); + const [copiado, setCopiado] = useState(false); + + useEffect(() => { + setForm(settings.reservas_online || RESERVAS_ONLINE_DEFAULT); + }, [settings.reservas_online]); + + const set = (k: K, v: ReservasOnlineSettings[K]) => + setForm((prev) => ({ ...prev, [k]: v })); + + const enlace = enlaceReserva(settings.url_publica); + + const guardar = async () => { + setSaving(true); + try { + const res = await odooApi.putClinicSettings({ + reservas_online: form, + motivo: 'Reservas en línea desde Configuración', + }); + if (res.status === 'success') { + onSaved(res.settings); + toast.success('Reservas en línea guardadas'); + } + } catch (err) { + const msg = (err as { response?: { data?: { message?: string } } })?.response?.data?.message; + toast.error(msg || 'No se pudieron guardar las reservas en línea'); + } finally { + setSaving(false); + } + }; + + const copiar = async () => { + try { + await navigator.clipboard.writeText(enlace); + setCopiado(true); + setTimeout(() => setCopiado(false), 2000); + } catch { + toast.error('No se pudo copiar el enlace'); + } + }; + + return ( +
+
+ +
+

Reservas en línea

+

+ Portal público donde tus clientes agendan solos, sin usuario ni contraseña. + Mientras esté desactivado, nada cambia en el sistema ni se muestra ningún enlace. +

+
+
+ + set('activo', v)} + ayuda="Al desactivarlo, el portal responde deshabilitado y desaparecen los enlaces." + /> + +
+ set('titulo', e.target.value)} /> + set('telefono_contacto', e.target.value)} + placeholder="Opcional, para dudas de los clientes" /> +
+ +