21 documentos en cuatro secciones, escritos sobre el comportamiento real del sistema —incluidos los casos que costaron diagnosticar esta semana. Manual de usuario (visible para todos): primeros pasos, recepción, toma de muestras, portal del enfermero y administración. Orientado a tareas concretas, no a describir pantallas. Documentación técnica: índice de módulos, turnero, formularios y firma digital, WhatsApp y bot, domicilios, webhook (migrado de WEBHOOK_ENDPOINTS.md) e inventario de endpoints. Arquitectura: visión general, enrutamiento y registro de módulos, roles y permisos, modelo de datos, integración con WhatsApp, y decisiones tomadas con su deuda técnica asociada. Operación: runbook de incidentes ordenado por síntoma, configuraciones críticas —incluido qué vive en Meta y no en la base— y despliegue. Se documentan explícitamente las trampas conocidas: role/role_id que hay que mantener sincronizados, las columnas can_* que el control de acceso no lee, las URL de plantilla que no se cambian desde el código, y las columnas históricas que quedaron en NULL sin forma de recuperarlas. README_DOCS.md apunta al módulo y explica cómo agregar páginas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.2 KiB
WhatsApp y bot
El sistema nació como bot de WhatsApp y esa integración sigue siendo central.
Servicios
{{servicios}}
WhatsAppService
Envuelve la Cloud API de Meta. Se elige la línea al construirlo:
$wa = new WhatsAppService(); // principal
$wa = new WhatsAppService('turnero'); // turnero
Ambas líneas comparten cuenta (WABA) y token; lo único que cambia es el phone_number_id. Si un mensaje sale por la línea equivocada, casi siempre falta el argumento.
Métodos principales:
$wa->sendTextMessage($telefono, $texto, $meta);
$wa->sendTemplateMessage($telefono, $plantilla, $idioma, [], [], $componentes, $meta);
$meta acompaña el registro del mensaje: ['canal' => 'turnero', 'operator_id' => adminId()].
Plantillas
Fuera de la ventana de 24 horas hay que usar plantilla aprobada. message_templates guarda una copia local con sus components, pero la copia no manda: la versión real vive en Meta.
Consecuencia importante:
Las URL de los botones están en la plantilla, no en el código. Nuestro código solo envía los parámetros (
{{1}}). Cambiar el código no altera el enlace que recibe el paciente.
Ejemplo — botón de consentimiento_turno_v2:
https://erp.laboratorioximenacaicedo.com/form_cliente.php?t={{1}}
Y así se arman los componentes al enviar:
$rawComps = [
['type' => 'body', 'parameters' => [['type' => 'text', 'text' => $codigo]]],
['type' => 'button', 'sub_type' => 'url', 'index' => '0',
'parameters' => [['type' => 'text', 'text' => $token]]],
];
Respaldo
Si el envío por plantilla falla, se manda un texto plano con el enlace armado desde el dominio del servidor. Ese texto no pasa por Meta, así que su URL puede diferir de la del botón.
BotService
Decide qué hacer con cada mensaje entrante:
- ¿Aceptó los términos? Si no, se los pide y no avanza.
- ¿Está en horario? (
BusinessHoursService) - ¿La conversación la tomó un operador humano? El bot no interrumpe.
- Si no, responde según el estado de la conversación (
ConversationStateService) y el menú (MenuService).
Términos y condiciones
| Tabla | Contenido |
|---|---|
terms_versions |
Versión activa, URL del documento, mensajes |
terms_acceptance |
Historial de aceptaciones |
system_config.terms_message |
Texto de bienvenida, repite la URL dentro |
Se vuelve a pedir la aceptación si nunca aceptó, si pasaron más de 6 meses, o si hay versión nueva con forzar_reenvio.
La URL del documento está en dos lugares. Cambiar solo uno deja al otro sirviendo un enlace viejo.
Configuración
Todo en system_config: whatsapp_token, whatsapp_api_url, whatsapp_business_account_id, whatsapp_phone_number_id, whatsapp_phone_number_id_turnero, webhook_verify_token.
Diagnóstico
| Síntoma | Dónde mirar |
|---|---|
| Sale por el número equivocado | Falta 'turnero' en el constructor |
| Enlace roto en un botón | La plantilla en Meta |
| No llega ninguna plantilla | Estado de aprobación en WhatsApp Manager |
| Llega texto plano en vez de plantilla | El respaldo actuó: la plantilla falló |
| El bot no responde | webhook_logs, horario, estado de la conversación |