# 08 · Licencias, límites y costes

## La licencia

Cada tenant tiene (o no) una licencia del módulo `whatsapp` que decide **si puede usar WhatsApp y hasta dónde**. La gestiona el equipo de administración ([11](11-administracion.md)); vuestra app solo la **lee**:

```bash
curl $BASE_URL/v1/license -H "Authorization: Bearer $API_KEY"
```

```json
{ "module": "whatsapp", "status": "active", "valid_from": "2026-10-12T00:00:00Z", "valid_until": "2027-10-12T00:00:00Z",
  "limits": { "max_numbers": 1, "max_agents": 5, "max_templates": 50, "max_outbound_per_month": 20000, "features": ["chat","templates","api","embed","costs"] },
  "usage": { "period": "2026-10-01", "outbound_messages": 1312, "inbound_messages": 884, "numbers": 1, "agents": 3, "templates": 12 } }
```

### Estados

| `status` | Enviar | Recibir y guardar | Pantallas embebidas | Leer por API |
| --- | --- | --- | --- | --- |
| `trial` / `active` | sí | sí | sí | sí |
| `suspended` | **no** (`403 license_inactive`) | sí (no se pierden las respuestas de los clientes) | **no** | **sí** |
| `expired` | no | no (se descartan) | no | no |

Pasar a `suspended` o `expired` hace **fallar** los envíos que estuvieran en cola (`error.code: license_inactive`, `retryable: true`: se pueden reintentar al reactivar). Un cambio de licencia surte efecto en segundos y emite `license.updated`.

### Límites

| Límite | Qué cuenta | Error al superarlo |
| --- | --- | --- |
| `max_outbound_per_month` | Mensajes **aceptados** en el mes natural (UTC). Una repetición idempotente o un reintento no cuentan | `403 license_limit_reached` (`details.kind`) |
| `max_numbers` | Números de WhatsApp conectados | ídem (el alta falla) |
| `max_agents` | Agentes activos | ídem |
| `max_templates` | Plantillas no borradas | ídem |
| `features` | `chat`, `templates`, `api`, `embed`, `costs`, `bot`, `ai` | `403 feature_not_licensed` (`bot` e `ai` no cierran ninguna ruta del gateway: las lee el servicio del bot) |
| `max_ai_conversations_per_month` | Conversaciones atendidas con IA al mes. **Lo hace cumplir el servicio del bot**, que lo lee de `GET /v1/license`; el gateway solo lo guarda | — |

Los límites de cantidad se respetan aunque dos peticiones lleguen **a la vez**.

### Cómo usarla en vuestra app

- Al pintar un menú, llamad a `GET /v1/license` (una vez por sesión) y ocultad lo que `features` no incluya.
- Mostrad un aviso al llegar al 90 % de `max_outbound_per_month`.
- Tratad `403 license_inactive` como «módulo no disponible» y no como un error genérico.

## Costes: qué paga quién

Cada cliente paga **directamente a Meta, con su propia tarjeta** (la configura en WhatsApp Manager al conectar su número). Vosotros **no refacturáis** la mensajería. Por eso los importes del gateway son **informativos**: sirven para que el cliente controle su gasto.

Modelo de precios de Meta (verificad siempre la tarifa vigente de vuestro país, cambia con frecuencia): desde el 1 de julio de 2025 se cobra **por mensaje de plantilla entregado**, según su categoría y el país del destinatario:

- **Marketing**: siempre se cobra.
- **Utilidad** y **autenticación**: se cobran fuera de la ventana de atención al cliente (24 h); las de utilidad enviadas dentro de ella son gratuitas. Tienen tramos de volumen.
- **Servicio** (respuestas a un cliente que escribió): gratuito.
- Si el cliente llega desde un anuncio o botón «Click to WhatsApp», hay 72 h gratuitas.

### Consultar

```bash
GET /v1/usage/costs?from=2026-10-01&to=2026-10-31&group_by=category,country   # rol admin, función costs
GET /v1/usage/messages?from=2026-10-01&to=2026-10-31&group_by=day,status        # rol supervisor
```

```json
{ "currency": "EUR", "synced_at": "2026-10-12T03:00:11Z", "from": "2026-10-01", "to": "2026-10-31",
  "totals": { "volume": 170, "cost": 10.2 },
  "data": [ { "category": "UTILITY", "volume": 120, "cost": 5.4 }, { "category": "SERVICE", "volume": 40, "cost": null } ] }
```

- `group_by` (`costs`): `day`, `category`, `pricing_type`, `country`, `number` (combinables, separados por comas).
- `currency` es la de la cuenta de WhatsApp del cliente; **no se convierte**.
- **`cost: null`** = Meta no informa importe (mensajes gratuitos).
- Los datos de Meta se leen **cada noche** (03:00 UTC, repitiendo los últimos 3 días porque Meta ajusta cifras con retraso). Los de hoy pueden no estar aún: mirad `synced_at`.
- `usage/messages` sale de los datos propios del gateway, sin retraso, pero cuenta mensajes, no importes.
- Por mensaje: `billable`, `pricing_category` y `pricing_type` (cuando Meta los informa con la entrega).

### Alerta de gasto

El tenant fija un importe mensual (`PATCH /v1/settings` → `cost_alert_monthly`). Al alcanzar el **80 %** y el **100 %** se emite **una vez por umbral y mes** `whatsapp.usage.alert` con `threshold_pct`, `spent`, `limit` y `currency`.

## Estado de pago del cliente

`GET /v1/whatsapp/account` → `payment_status`:

| Valor | Significado |
| --- | --- |
| `awaiting_payment` | Número conectado, pero el cliente aún no añadió su tarjeta en WhatsApp Manager. Fuera de pruebas no podrá enviar plantillas |
| `active` | Meta aceptó un envío de plantilla: el pago está operativo (evento `whatsapp.account.payment_active`) |
| `blocked` | Meta rechazó por problema de pago (`131042`). Los envíos devuelven `402 payment_required` y llega `whatsapp.account.payment_blocked` |

Es un problema **entre el cliente y Meta**: avisadle (el evento os da `tenant_ref`) para que revise su tarjeta.

**Recuperación automática:** tras cada rechazo el gateway rechaza los envíos nuevos con `402` durante **10 minutos**. Pasado ese tiempo deja pasar el siguiente envío para que Meta decida: si lo acepta, la cuenta vuelve a `active` (evento `whatsapp.account.payment_active`); si lo rechaza de nuevo, se renueva el enfriamiento (sin repetir el evento `payment_blocked`).
