Files
Lizandro GuarnizoandClaude Opus 5 018fb13332 Módulo Soporte: visor de documentación con renderizado Markdown y buscador
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>
2026-08-04 10:07:06 -05:00

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