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>
91 lines
3.2 KiB
Markdown
91 lines
3.2 KiB
Markdown
# 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:
|
|
|
|
```php
|
|
$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:
|
|
|
|
```php
|
|
$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:
|
|
|
|
```php
|
|
$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:
|
|
|
|
1. ¿Aceptó los términos? Si no, se los pide y no avanza.
|
|
2. ¿Está en horario? (`BusinessHoursService`)
|
|
3. ¿La conversación la tomó un operador humano? El bot no interrumpe.
|
|
4. 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 |
|