El análisis de completitud cruzando roles contra módulos y tablas contra documentos encontró dos huecos reales. Manual de domicilios: recepcionista, lab_recepcion y lab_readonly tienen acceso a la pantalla administrativa de domicilios, y la única página sobre el tema era la del portal del enfermero, restringida a enfermeros. Son pantallas distintas —una ve todo, la otra solo lo propio— y ahora cada una tiene su guía. Los enfermeros pasan también a ver el manual de formularios, que su rol habilita. Menús y respuestas automáticas del bot: había cuatro tablas en uso que ninguna página explicaba. El bot responde solo las preguntas frecuentes mediante autoresponses (7 configuradas) y ofrece un menú interactivo de 21 opciones en dos niveles, todo configurable desde la base sin desplegar código. También quedan documentados el estado de conversación, los envíos masivos y las encuestas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.5 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.
Contenido configurable
Buena parte de lo que responde el bot vive en la base, no en el código: respuestas automáticas por palabra clave y menús interactivos. Ver Menús y respuestas automáticas.
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 |