fase 3.5: licenciamiento por plan (clave firmada, planes basico/profesional/enterprise/custom, modulos incluidos y limites de usuarios/sucursales/citas/almacenamiento, modo aviso o bloqueo, panel de licencia) + API keys gestionables con alcances y revocacion (arregla la API publica que estaba rota) + webhooks salientes firmados con outbox, reintentos y bitacora; herramientas de vendedor y docs
This commit is contained in:
112
docs/producto/webhooks.md
Normal file
112
docs/producto/webhooks.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Webhooks salientes
|
||||
|
||||
Notificación de eventos de la clínica hacia sistemas externos, con patrón
|
||||
**outbox + cron + reintentos + bitácora**. Sin webhooks activos suscritos no se
|
||||
crea ninguna fila ni se envía nada (cero impacto).
|
||||
|
||||
## Modelos
|
||||
|
||||
### `skeen.webhook`
|
||||
|
||||
| Campo | Descripción |
|
||||
| --- | --- |
|
||||
| `name`, `url` | Nombre y URL destino |
|
||||
| `eventos` | Claves separadas por coma; vacío = todos |
|
||||
| `activo` | Habilitado |
|
||||
| `secreto` | Con este secreto se firma cada entrega |
|
||||
| `headers` | Cabeceras extra (JSON, opcional) |
|
||||
| `notas`, `creado_por_login` | Metadatos |
|
||||
| `ultima_entrega_at` | Última entrega correcta |
|
||||
| `fallos_consecutivos` | Se desactiva tras N fallos (default 20, configurable en `skeen.webhooks.fallos_max`) |
|
||||
|
||||
### `skeen.webhook.evento` (outbox)
|
||||
|
||||
`webhook_id`, `evento`, `payload` (JSON), `estado`
|
||||
(`pendiente|enviado|fallido|descartado`), `intentos`, `proximo_intento`,
|
||||
`respuesta_codigo`, `respuesta_texto`, `enviado_at`, `clave_unica`
|
||||
(idempotencia por evento+objeto).
|
||||
|
||||
## Catálogo de eventos
|
||||
|
||||
| Evento | Cuándo se emite |
|
||||
| --- | --- |
|
||||
| `cita.creada` | Al crear una cita |
|
||||
| `cita.confirmada` | `state` → `confirmed` |
|
||||
| `cita.cancelada` | `state` → `cancelled` |
|
||||
| `cita.no_show` | `state` → `no_show` |
|
||||
| `cita.completada` | `state` → `done` |
|
||||
| `venta.pagada` | `state` → `paid` |
|
||||
| `venta.cancelada` | `state` → `cancelled` o `refunded` |
|
||||
| `pago.registrado` | Al crear un `skeen.pago` |
|
||||
| `paciente.creado` | Al crear un `res.partner` con `is_patient` |
|
||||
| `membresia.renovada` | `action_renovar` |
|
||||
| `formulario.llenado` | `estado` → `llenado` |
|
||||
| `compra.recibida` | `estado` → `recibida`/`recibida_parcial` |
|
||||
|
||||
### Ejemplo de payload (`cita.creada`)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 123,
|
||||
"reference": "CITA-0123",
|
||||
"state": "pending",
|
||||
"date": "2026-10-20",
|
||||
"patient_id": 55,
|
||||
"patient": "María López",
|
||||
"service_id": 7,
|
||||
"servicio": "Limpieza facial",
|
||||
"doctor": "Dra. Pérez"
|
||||
}
|
||||
```
|
||||
|
||||
## Entrega
|
||||
|
||||
Cron cada 5 minutos (`skeen.webhook.service._cron_tick`) que toma los pendientes
|
||||
vencidos y hace `POST` JSON con:
|
||||
|
||||
| Cabecera | Valor |
|
||||
| --- | --- |
|
||||
| `X-Skeen-Event` | Clave del evento |
|
||||
| `X-Skeen-Delivery` | UUID de la entrega |
|
||||
| `X-Skeen-Signature` | `sha256=<HMAC-SHA256 del cuerpo con el secreto>` |
|
||||
|
||||
* Timeouts cortos (5 s conexión / 15 s lectura).
|
||||
* **Reintentos con backoff**: 1, 5, 30 min, 2 h, 6 h (tope 5 intentos); al
|
||||
agotarlos el evento queda `fallido`.
|
||||
* Tras N fallos consecutivos (default 20) el webhook se desactiva.
|
||||
|
||||
### Verificar la firma (Python)
|
||||
|
||||
```python
|
||||
import hashlib, hmac
|
||||
firma = request.headers['X-Skeen-Signature'] # 'sha256=...'
|
||||
esperada = 'sha256=' + hmac.new(
|
||||
SECRETO.encode(), request.body, hashlib.sha256).hexdigest()
|
||||
assert hmac.compare_digest(firma, esperada)
|
||||
```
|
||||
|
||||
### Verificar la firma (Node.js)
|
||||
|
||||
```js
|
||||
const crypto = require('crypto');
|
||||
const esperada = 'sha256=' + crypto
|
||||
.createHmac('sha256', SECRETO)
|
||||
.update(req.rawBody)
|
||||
.digest('hex');
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Método | Ruta | Acceso |
|
||||
| --- | --- | --- |
|
||||
| GET/POST | `/skeen/frontend/v1/webhooks` | permiso `webhooks.editar` (admin/gerencia) |
|
||||
| PUT/DELETE | `/skeen/frontend/v1/webhooks/<id>` | idem |
|
||||
| GET | `/skeen/frontend/v1/webhooks/<id>/eventos` | idem (filtra por `estado`, `evento`, `limit`) |
|
||||
| POST | `/skeen/frontend/v1/webhooks/<id>/probar` | idem (evento de prueba) |
|
||||
| POST | `/skeen/frontend/v1/webhook_eventos/<id>/reintentar` | idem |
|
||||
|
||||
## UI
|
||||
|
||||
Configuración → pestaña **Webhooks** (admin/gerencia): CRUD, selector de
|
||||
eventos, secreto (generado o propio), visor del payload de ejemplo, log de
|
||||
entregas con estado/código/fecha, reintento manual y "probar".
|
||||
Reference in New Issue
Block a user