Documentación completa del proyecto en el módulo Soporte
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
018fb13332
commit
32c209710c
@@ -0,0 +1,166 @@
|
||||
# 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)
|
||||
|
||||
```json
|
||||
{
|
||||
"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 estados
|
||||
- `conversations` → alias aceptado también
|
||||
|
||||
### Eventos de estado (statuses)
|
||||
```json
|
||||
"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`)
|
||||
|
||||
```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-256` en el POST.
|
||||
- Se recomienda agregar antes de procesar:
|
||||
```php
|
||||
$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;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user