Nuevo módulo `soporte` con la documentación del proyecto en cuatro secciones:
manual de usuario (visible para todos), y documentación técnica, arquitectura
y operación (solo administradores).
- Markdown.php: renderizador propio del subconjunto que usa la documentación
(encabezados, listas anidadas, tablas, código, citas). Escapa todo el texto
antes de aplicar formato, así que los .md no pueden inyectar HTML. Se
prefirió un archivo auditable a incorporar una dependencia externa.
- DocIndex.php: descubre los .md, arma el árbol, resuelve acceso por sección
y construye el índice del buscador.
- Generadores.php: expande marcadores {{modulos}}, {{endpoints}}, {{tablas}},
{{roles}} y {{servicios}} leyendo el código y la base en cada carga, para
que los inventarios no puedan quedar desactualizados.
Se registra en SYSTEM_MODULES y se concede a los 12 roles con permission=read.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.9 KiB
Integración con WhatsApp
Es la parte del sistema con más piezas fuera de nuestro control. Buena parte de la configuración vive en Meta, no en la base de datos, y eso explica varios comportamientos que de otro modo parecen inexplicables.
Dos números, una misma cuenta
El laboratorio opera con dos líneas sobre la misma cuenta de WhatsApp Business (WABA):
| Canal | Configuración | Para qué |
|---|---|---|
| Principal | whatsapp_phone_number_id |
Bot de atención general |
| Turnero | whatsapp_phone_number_id_turnero |
Consentimientos, encuestas y avisos de turno |
Se elige al construir el servicio:
$wa = new WhatsAppService(); // línea principal
$wa = new WhatsAppService('turnero'); // línea del turnero
Si un mensaje sale por el número equivocado, casi siempre es porque se instanció sin el canal. Es el mismo WABA y el mismo token: lo único que cambia es el
phone_number_id.
Configuración
Todo en system_config:
| Clave | Qué es |
|---|---|
whatsapp_token |
Token de acceso a la API |
whatsapp_api_url |
URL base de la Cloud API |
whatsapp_business_account_id |
Identificador del WABA |
whatsapp_phone_number_id |
Número principal |
whatsapp_phone_number_id_turnero |
Número del turnero |
webhook_verify_token |
Verificación del webhook |
Plantillas: la parte que no controlamos
Para escribir primero a alguien (fuera de la ventana de 24 horas) hay que usar una plantilla aprobada por Meta. message_templates guarda una copia local, pero la copia no manda: la versión real está en Meta.
Esto tiene una consecuencia importante y poco intuitiva:
Las URL de los botones viven en la plantilla, no en nuestro código.
La plantilla consentimiento_turno_v2 tiene un botón así:
https://erp.laboratorioximenacaicedo.com/form_cliente.php?t={{1}}
Nuestro código solo envía el token como {{1}}. Cambiar el código no cambia el enlace que recibe el paciente: hay que editar la plantilla en el WhatsApp Manager de Meta y esperar la reaprobación.
El único lugar donde sí armamos la URL completa es el respaldo en texto plano, que se usa cuando falla el envío por plantilla (modules/turnero/api/send_consentimiento.php).
Por qué el enlace pasa por dos páginas
El botón apunta a form_cliente.php?t=<UUID>, pero el consentimiento del turnero lo muestra ver_formulario_enviado.php?token=<UUID>.
form_cliente.php detecta que el token tiene formato UUID —o sea, que viene del turnero— y redirige. Convive así porque la plantilla ya estaba aprobada apuntando a la página de envíos, y cambiarla obliga a otra ronda de aprobación en Meta.
Términos y condiciones
Antes de conversar, el bot exige aceptar los términos. El usuario responde ACEPTO o NO ACEPTO.
La URL del documento está escrita en dos lugares y hay que cambiarlos juntos:
| Dónde | Rol |
|---|---|
terms_versions.documento_url |
El bot la adjunta al final del mensaje |
system_config.terms_message |
Va escrita dentro del texto de bienvenida |
Se vuelve a pedir la aceptación si: nunca aceptó, pasaron más de 6 meses, o hay una versión nueva con forzar_reenvio.
El webhook
Meta envía los mensajes entrantes al webhook, que los registra y se los pasa a BotService. Ahí se decide si responde el bot automático o queda para un operador humano, según el estado de la conversación y el horario de atención (BusinessHoursService).
Qué revisar cuando algo falla
| Síntoma | Dónde mirar primero |
|---|---|
| El mensaje sale por el número equivocado | Que se haya pasado 'turnero' al constructor |
| Un enlace llega roto o apunta mal | La plantilla en Meta, no el código |
| No llega ninguna plantilla | Estado de aprobación en el WhatsApp Manager |
| Falla el envío pero llega un texto plano | Es el respaldo actuando: la plantilla falló |
| El bot no responde | webhook_logs, y el horario de atención |