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.7 KiB
3.7 KiB
Webhook de WhatsApp
Migrado de WEBHOOK_ENDPOINTS.md (raíz del repositorio), donde vivía suelto.
Endpoint principal
URL: /api/webhook.php
GET — Verificación de webhook
GET /api/webhook.php?hub.mode=subscribe&hub.verify_token=TOKEN&hub.challenge=CHALLENGE
Parámetros que envía Meta
| Parámetro | Valor esperado |
|---|---|
hub.mode |
subscribe |
hub.verify_token |
El token configurado en system_config.webhook_verify_token |
hub.challenge |
Número aleatorio que debe devolverse tal cual |
⚠️ Bug conocido
El código lee $_GET['hub_verify_token'] (con guión bajo), pero PHP convierte los puntos a guiones bajos automáticamente al parsear $_GET, por lo que funciona correctamente.
Respuesta exitosa
HTTP 200
Body: {challenge}
Respuesta fallida
HTTP 403
Body: {"error":"Token de verificación inválido"}
POST — Recepción de eventos
POST /api/webhook.php
Content-Type: application/json
Estructura del payload esperado (Meta Cloud API)
{
"object": "whatsapp_business_account",
"entry": [{
"id": "WABA_ID",
"changes": [{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"phone_number_id": "PHONE_NUMBER_ID"
},
"contacts": [{
"wa_id": "573001234567",
"profile": { "name": "Nombre Contacto" }
}],
"messages": [{
"from": "573001234567",
"id": "wamid.XXX",
"timestamp": "1234567890",
"type": "text",
"text": { "body": "Hola" }
}]
}
}]
}]
}
Tipos de mensaje soportados
type |
Descripción |
|---|---|
text |
Texto plano |
image |
Imagen (con caption opcional) |
audio |
Audio / nota de voz |
video |
Video |
document |
Documento / PDF |
sticker |
Sticker |
reaction |
Reacción emoji a otro mensaje |
interactive |
Respuesta de lista o botón |
El campo field del change puede ser
messages→ mensajes entrantes y estadosconversations→ alias aceptado también
Eventos de estado (statuses)
"statuses": [{
"id": "wamid.XXX",
"status": "sent|delivered|read|failed",
"recipient_id": "573001234567"
}]
Respuesta exitosa
HTTP 200
Body: {"status":"success"}
Configuración necesaria en system_config (BD)
| config_key | Descripción |
|---|---|
whatsapp_token |
Access Token de Meta |
whatsapp_phone_number_id |
Phone Number ID de la línea |
webhook_verify_token |
Token de verificación del webhook |
whatsapp_api_url |
https://graph.facebook.com/v22.0/ |
Variables de entorno equivalentes (.env)
WHATSAPP_TOKEN=
WHATSAPP_PHONE_NUMBER_ID=
WEBHOOK_VERIFY_TOKEN=
WHATSAPP_API_URL=https://graph.facebook.com/v22.0/
DB_HOST=
DB_PORT=3306
DB_NAME=
DB_USER=
DB_PASS=
Tablas BD que usa el webhook
| Tabla | Uso |
|---|---|
users |
Crea o busca usuario por phone_number |
conversations |
Guarda cada mensaje (deduplicado por message_id) |
webhook_logs |
Registra el payload crudo de cada POST |
notifications |
Crea aviso de nuevo mensaje entrante |
media_queue |
Encola media que no pudo descargarse en el momento |
system_config |
Lee tokens y configuración |
Seguridad — pendiente de implementar
- No valida la firma
X-Hub-Signature-256en el POST. - Se recomienda agregar antes de procesar:
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $input, APP_SECRET);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}