import type { FC, ReactNode } from 'react'; import { useEffect, useMemo, useState } from 'react'; import { BellRing, FlaskConical, PlugZap, Save } from 'lucide-react'; import { Button, Input, Select, TextArea, toast } from './ui'; import { odooApi, NOTIFICACIONES_DEFAULT, NOTIF_VARIABLES, NOTIF_VENTANAS, type ClinicSettings, type NotificacionCanal, type NotificacionesSettings, } from '../services/odoo'; import { useGlosario } from '../lib/glosario'; // Editor del bloque `notificaciones` (recordatorios y avisos automáticos de // citas). Todo apagado por defecto: mientras `activo` esté apagado nada se // envía ni se encola. Con `modo_prueba` encendido (default) los mensajes se // registran como simulados y no se llama a ningún proveedor externo. interface Props { settings: ClinicSettings; onSaved: (settings: ClinicSettings) => void; } const TIPOS_PLANTILLA: { key: keyof NotificacionesSettings; label: string }[] = [ { key: 'plantilla_recordatorio', label: 'Recordatorio' }, { key: 'plantilla_confirmacion', label: 'Confirmación' }, { key: 'plantilla_cumpleanos', label: 'Cumpleaños' }, { key: 'plantilla_post_visita', label: 'Post-visita' }, { key: 'plantilla_formulario', label: 'Formulario digital' }, ]; const NotificacionesPanel: FC = ({ settings, onSaved }) => { const { term } = useGlosario(); const [form, setForm] = useState( settings.notificaciones || NOTIFICACIONES_DEFAULT); const [saving, setSaving] = useState(false); const [probando, setProbando] = useState(false); const [pruebaCanal, setPruebaCanal] = useState('whatsapp'); const [pruebaDestino, setPruebaDestino] = useState(''); useEffect(() => { setForm(settings.notificaciones || NOTIFICACIONES_DEFAULT); }, [settings.notificaciones]); const set = (k: K, v: NotificacionesSettings[K]) => setForm((prev) => ({ ...prev, [k]: v })); // Ejemplo con datos ficticios para la vista previa (respeta el glosario). const ejemplo = useMemo(() => ({ nombre: 'Ana López', cita: term('appointment_singular'), servicio: 'Limpieza facial', fecha: '15/03/2026', hora: '10:30', doctor: 'Dra. García', sucursal: settings.nombre || 'Sucursal Centro', telefono: settings.telefono || '+52 661 000 0000', enlace: 'https://clinica.mx/formulario/abc123', formulario: 'Consentimiento informado', }), [term, settings.nombre, settings.telefono]); const previsualizar = (plantilla: string) => plantilla.replace(/\{([a-zA-Z_]+)\}/g, (_m, key: string) => (ejemplo as Record)[key] ?? `{${key}}`); const toggleVentana = (horas: number) => { const actuales = form.recordatorio_horas_antes || []; const siguientes = actuales.includes(horas) ? actuales.filter((h) => h !== horas) : [...actuales, horas]; set('recordatorio_horas_antes', siguientes.sort((a, b) => a - b)); }; const guardar = async () => { setSaving(true); try { const res = await odooApi.putClinicSettings({ notificaciones: form, motivo: 'Notificaciones desde Configuración', }); if (res.status === 'success') { onSaved(res.settings); toast.success('Notificaciones guardadas'); } } catch (err) { const msg = (err as { response?: { data?: { message?: string } } })?.response?.data?.message; toast.error(msg || 'No se pudieron guardar las notificaciones'); } finally { setSaving(false); } }; const probar = async () => { setProbando(true); try { const res = await odooApi.probarNotificacion({ canal: pruebaCanal, destino: pruebaDestino }); if (res.ok) { toast.success(res.message || 'Canal configurado'); } else { toast.error(res.message || 'El canal no está configurado'); } } catch { toast.error('No se pudo probar el canal'); } finally { setProbando(false); } }; return (

Recordatorios y notificaciones

Avisos automáticos de {term('appointment_plural').toLowerCase()} por WhatsApp o correo: recordatorios antes de la {term('appointment_singular').toLowerCase()}, confirmación al reservar en línea, cumpleaños y recuperación de inasistencias. Apagado = nada cambia.

set('activo', v)} ayuda="Mientras esté apagado no se envía ni se registra ninguna notificación." />
set('modo_prueba', v)} ayuda={form.modo_prueba ? 'Modo prueba activo: los mensajes se registran como «simulados» y NO se envía nada real a ningún paciente.' : 'Modo prueba apagado: los mensajes se intentarán enviar de verdad por el canal elegido.'} /> {!form.modo_prueba && (

Atención: los pacientes recibirán mensajes reales.

)}
set('whatsapp_numero', e.target.value)} placeholder="+52 661 000 0000 (opcional)" />
set('email_remitente', e.target.value)} placeholder="citas@clinica.mx (opcional)" />
{/* Ventanas de recordatorio */}

Avisar antes de la {term('appointment_singular').toLowerCase()}

Puedes elegir varias ventanas (por ejemplo, 24 h y 2 h). Se envía una sola vez por ventana.

{NOTIF_VENTANAS.map((v) => { const activa = (form.recordatorio_horas_antes || []).includes(v.horas); return ( ); })}
{(form.recordatorio_horas_antes || []).length === 0 && (

Sin ventanas seleccionadas no se enviarán recordatorios.

)}
set('confirmacion_al_crear', v)} ayuda="Avisa al paciente cuando agenda solo, desde el portal de reservas en línea." /> set('cumpleanos', v)} /> set('recuperacion_no_show', v)} ayuda="Mensaje a quien no se presentó, para reagendar." /> set('hora_envio_cumpleanos', e.target.value)} />
set('post_visita_horas', Math.max(0, Number(e.target.value) || 0))} />

0 = apagado.

{/* Plantillas con variables y vista previa */}

Plantillas

Variables disponibles:{' '} {NOTIF_VARIABLES.map((v) => ( {`{${v}}`} ))}

{TIPOS_PLANTILLA.map(({ key, label }) => (