# Plantillas de WhatsApp por API

Fuera de la ventana de 24 horas, lo único que WhatsApp deja entrar es una plantilla
aprobada por Meta. Esta guía es para crearlas desde tu código, no desde el panel.

## Antes de empezar: una plantilla NO es del número

Es el malentendido que más caro sale, así que va primero.

Una plantilla pertenece a la **cuenta de WhatsApp** —la WABA—, no a un número de
teléfono. Si tu cuenta tiene tres números, la plantilla que crees queda disponible
para los tres. No existe «una plantilla para este número».

Y si tienes **varias cuentas** conectadas al mismo canal, ahí sí importa en cuál la
creas: una plantilla de la cuenta A no se puede enviar desde un número de la cuenta B.

## El identificador del canal

Todo empieza por el canal:

```ts
const canales = await met.channels.list();
const whatsapp = canales.find((c) => c.type === 'whatsapp')!;
```

Y las plantillas que ya tienes, con la cuenta de la que salió cada una:

```ts
const { data, accounts } = await met.channels.templates(whatsapp.id);
```

> **Mira siempre `accounts`.** Si una de tus cuentas no respondió, la lista llega
> incompleta y con `200`, no con error. Ahí también salen los `waba_id` y sus alias,
> que es de donde sacas el valor del paso siguiente.

## Crear una plantilla

```ts
const creada = await met.channels.createTemplate(whatsapp.id, {
  name: 'recordatorio_cita',
  language: 'es',
  category: 'UTILITY',
  components: [
    { type: 'BODY', text: 'Hola {{1}}, te recordamos tu cita del {{2}}.' },
  ],
  waba_id: '102938475610293',   // opcional: si lo omites, va a tu cuenta primaria
});

creada.template_status;   // 'PENDING'
```

Pide el permiso `channels:manage`, y **una clave de prueba no alcanza**: esto manda
una solicitud real a revisión de Meta sobre la cuenta de tu cliente.

### Si el encabezado lleva imagen

Meta exige una muestra. Pásala como `sample_url` dentro del componente `HEADER`:

```ts
components: [
  {
    type: 'HEADER',
    format: 'IMAGE',
    sample_url: 'https://tu-cdn.com/muestras/factura.png',
  },
  { type: 'BODY', text: 'Tu factura de {{1}} ya está lista.' },
]
```

Tiene que ser **`https`** y accesible desde internet: nuestro servidor la descarga
para subírsela a Meta, y por seguridad rechaza direcciones internas y de red privada.
Pesa máximo 5 MB.

## Hay un tope, y conviene entender por qué

**25 creaciones por cuenta y por hora.** Pasado ese punto sale un `429` con
`rate_limited`.

El motivo no es proteger nuestros servidores: **una plantilla rechazada le baja la
calificación de calidad a la cuenta de WhatsApp**. Si estás integrando para un
cliente, esa calificación es de él. Un script que prueba veinte variantes de una
plantilla hasta que una pase le deja el daño a alguien que no participó de la
decisión.

## Enterarte cuando Meta resuelve

`createTemplate` vuelve al instante con `PENDING`. Meta decide después, y lo normal
es que tarde. Puedes volver a pedir la lista, o suscribirte y que te avisen:

```ts
await met.webhooks.subscriptions.create({
  url: 'https://tu-servidor.com/webhooks/met',
  enabled_events: [
    'channel.template.approved',
    'channel.template.rejected',
    'channel.template.status_updated',
  ],
});
```

| Evento | Cuándo | Qué hacer |
|---|---|---|
| `channel.template.approved` | Meta la aprobó | Ya puedes enviarla. |
| `channel.template.rejected` | Meta la rechazó | Trae `reason`. Corrige y vuelve a crearla con otro nombre. |
| `channel.template.status_updated` | Cambió de estado por otra razón | **Léelo.** Ver abajo. |

**El tercero es el que no conviene ignorar.** Meta manda catorce estados distintos
por ese canal, y varios —pausada, deshabilitada, marcada, límite excedido— le pasan a
plantillas que **ya estaban aprobadas y funcionando**. Operativamente eso es peor que
un rechazo: un rechazo lo descubres cuando vas a mirar, una pausa te corta envíos en
curso sin que nadie toque nada.

El `data` del evento trae `template_name`, `template_language`, `waba_id`, el `status`
crudo de Meta y, cuando existe, el `reason`.

## Enviarla

Una vez aprobada, el envío es el de siempre:

```ts
await met.contacts.sendTemplate(contactId, {
  name: 'recordatorio_cita',
  language: 'es',
  namespace: process.env.WA_TEMPLATE_NAMESPACE!,
  params: { '1': 'Ana', '2': 'martes a las 3' },
});
```

## Borrarla

```ts
await met.channels.deleteTemplate(whatsapp.id, { name: 'recordatorio_cita' });
```

Deja de existir para **todos los números** de esa cuenta.

## Y de paso: los atajos

Los atajos —las respuestas guardadas que tu equipo inserta en el chat— también están
en la API, y no tienen nada que ver con Meta: son texto tuyo, dentro del workspace.

```ts
const atajos = await met.conversations.quickReplies();
await met.conversations.createQuickReply({ title: 'Horario', content: 'Atendemos de 8 a 6.' });
```

Leer pide `conversations:read`; crear, editar y borrar piden `conversations:write`.

## Y después

- [Un agente que contesta WhatsApp](receta-whatsapp.html) — la ventana de 24 horas y el traspaso a una persona.
- [Eventos](eventos.html) — el registro guarda 30 días, los escuche alguien o no.
