# 07 · Conversaciones, contactos y consentimiento

## Contactos

Un contacto es una persona de WhatsApp **de un tenant**. Se crea solo la primera vez que se le escribe o que escribe, o a mano:

```bash
curl -X POST $BASE_URL/v1/contacts -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"phone": "+34 600 111 222", "display_name": "Ana Pérez", "external_ref": "cli-9917"}'
```

Si el teléfono ya existe devuelve el contacto (`200`) en vez de crear otro (`201`).

| Campo | Notas |
| --- | --- |
| `wa_id` | El teléfono normalizado, clave única dentro del tenant |
| `external_ref` | **Vuestro** id del cliente. Es el enlace entre WhatsApp y vuestra base de datos: fijadlo al enviar (`contact.external_ref`) o aquí. Se puede buscar con `GET /v1/contacts?external_ref=` y `GET /v1/conversations?external_ref=` |
| `profile_name` | El nombre que la persona tiene en WhatsApp (informativo) |
| `display_name` | El nombre que le da el tenant |
| `attributes` | Objeto libre para vuestros datos |
| `opt_in_status` | `unknown` · `opted_in` · `opted_out` |

## Consentimiento (opt-in) y bajas

WhatsApp exige que el cliente final haya aceptado recibir vuestros mensajes. El gateway **guarda la prueba** y hace cumplir las reglas:

```bash
# registrar el consentimiento, con su origen y fecha (vuestra prueba de RGPD)
curl -X POST $BASE_URL/v1/contacts/ctc_…/opt-in -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"source": "formulario_web", "at": "2026-10-12T09:30:00Z"}'
```

| Situación | Efecto |
| --- | --- |
| `opted_out` | Las plantillas de **marketing** devuelven `403 contact_opted_out`. Utilidad y respuestas siguen permitidas |
| Contacto `blocked` | Cualquier plantilla devuelve `403 contact_opted_out` |
| Ajuste del tenant `enforce_opt_in = true` | **Toda** plantilla exige `opted_in`: `403 opt_in_required` si falta. Recomendado si enviáis marketing |
| El contacto escribe una **palabra de baja** (por defecto `BAJA` o `STOP`, configurables en `optout_keywords`) | Pasa a `opted_out` automáticamente |
| Meta indica `131050` (el usuario rechazó marketing) | Pasa a `opted_out` |

Dar de baja a mano: `POST /v1/contacts/{id}/opt-out`. Volver a consentir: `…/opt-in`.

## Borrado a petición del interesado (RGPD)

`DELETE /v1/contacts/{id}` (rol `admin`) elimina el contacto, sus conversaciones, mensajes, notas y archivos, y **anonimiza** su rastro en la auditoría. No se puede deshacer. Además cada tenant tiene `data_retention_days` (365 por defecto): cada noche se borra lo más antiguo.

Para borrar solo el contenido de **un mensaje** (un dato sensible que el cliente escribió) sin borrar la conversación, usad `POST /v1/messages/{id}/redact` ([03](03-enviar-mensajes.md#datos-sensibles-que-escribe-el-cliente)).

## Conversaciones

Una conversación es el hilo entre **un número del tenant y un contacto**. Se crea sola.

| Campo | Significado |
| --- | --- |
| `status` | `open`, `pending`, `closed`. Un mensaje entrante reabre una cerrada |
| `window_open`, `window_expires_at` | Si se puede escribir libremente (24 h desde el último mensaje del contacto). Ver [03](03-enviar-mensajes.md) |
| `unread_count`, `last_message_preview`, `last_message_at` | Para pintar la bandeja |
| `assigned_agent_id` | Agente responsable |
| `tags` | Etiquetas libres (hasta 20) |

```bash
GET  /v1/conversations?status=open&assigned_agent_id=none&unread=true&q=ana&limit=50
GET  /v1/conversations/{id}/messages          # más recientes primero, con cursor
PATCH /v1/conversations/{id}                  # { "status": "closed" } · { "assigned_agent_id": "agt_…" } · { "tags": [...] }
POST /v1/conversations/{id}/read              # pone a 0 los no leídos y avisa a Meta
```

- `assigned_agent_id=none` lista las **sin asignar**.
- `updated_since=<fecha>` devuelve solo lo cambiado: ideal para sincronizar.
- Asignar es **atómico**: si dos agentes lo intentan a la vez, el segundo recibe `409 already_assigned`. Un `agent` solo puede asignarse a sí mismo; un `supervisor`/`admin` (y las claves de API) reasignan.
- Una conversación con un agente desactivado queda sin asignar.

### Asignación automática

Ajuste `auto_assign_mode` del tenant (`PATCH /v1/settings`): `manual` (por defecto), `round_robin` (al que más tiempo lleva sin recibir) o `least_busy` (al que menos abiertas tiene). Se aplica a los mensajes entrantes de conversaciones sin agente.

### Notas internas

`GET/POST /v1/conversations/{id}/notes`: comentarios entre agentes que el contacto **nunca** ve.

## Ajustes del tenant

`GET/PATCH /v1/settings` (escritura: rol `admin`):

| Ajuste | Valores | Por defecto |
| --- | --- | --- |
| `auto_assign_mode` | `manual`, `round_robin`, `least_busy` | `manual` |
| `reopen_on_inbound` | `previous_agent`, `unassigned` | `previous_agent` |
| `optout_keywords` | lista de palabras | `["BAJA","STOP"]` |
| `enforce_opt_in` | `true`/`false` | `false` |
| `cost_alert_monthly` | importe o `null` | sin alerta |
| `notify_email` | correo o `null` | — |
