# Un agente que contesta WhatsApp

Con el autopiloto encendido, Meteor ya contesta los mensajes de WhatsApp solo. Esta receta
es para lo otro: cuando quieres **tu propia lógica en el medio** — consultar tu inventario
antes de responder, escalar según el cliente, escribir en tu CRM, decidir cuándo calla el
Met y contesta una persona.

## Antes de empezar

Conecta el canal de WhatsApp a tu workspace desde el panel (**Canales → WhatsApp**). La
API no conecta canales: eso pasa una vez y necesita la aprobación de Meta.

Tu key necesita cinco scopes, uno por cada cosa que hace la receta:

| scope | para qué |
|---|---|
| `webhooks:manage` | registrar el endpoint que recibe los eventos |
| `conversations:read` | recibir `contact.message.received` |
| `runs:execute` | ejecutar el Met |
| `channels:send` | responderle al contacto |
| `handoff:manage` | apagar y prender el autopiloto |

## 1. Suscríbete a los mensajes que entran

```ts
import Met from '@meteor.ia/sdk';

const met = new Met(process.env.MET_API_KEY!, {
  workspaceId: Number(process.env.MET_WORKSPACE_ID),
});

const sub = await met.webhooks.subscriptions.create({
  url: 'https://tu-servidor.com/webhooks/met',
  enabled_events: ['contact.message.received'],
});

console.log(sub.secret);   // whsec_… — se muestra UNA sola vez
```

Guarda el `secret` apenas lo recibes: lo necesitas para verificar cada entrega y no se
vuelve a mostrar.

## 2. Recibe el mensaje y contesta

El evento llega con este cuerpo:

```json
{
  "contact_id": 4821,
  "message_id": 99312,
  "channel_id": 12,
  "channel_type": "whatsapp",
  "content": "¿Todavía tienen la bici azul?",
  "attachments": []
}
```

```ts
import express from 'express';

const app = express();
// El cuerpo CRUDO es obligatorio: si tu framework re-serializa el JSON antes de
// verificar, la firma deja de coincidir aunque el contenido sea idéntico.
app.use('/webhooks/met', express.raw({ type: 'application/json' }));

app.post('/webhooks/met', async (req, res) => {
  let evento;
  try {
    evento = met.webhooks.constructEvent(
      req.body,
      req.headers['x-met-signature'] as string,
      process.env.MET_WEBHOOK_SECRET!,
    );
  } catch {
    return res.status(400).send('firma inválida');
  }

  // Responde YA. Meteor reintenta si no ve un 2xx, y un Met puede tardar varios
  // segundos: contestar después de pensar te duplica los mensajes.
  res.sendStatus(200);

  if (evento.type !== 'contact.message.received') return;

  const { contact_id, content } = evento.data;
  const run = await met.runs.create({ input: content });
  await met.contacts.sendMessage(contact_id, run.output);
});

app.listen(3000);
```

El run que creas por API no queda atado al contacto: recibe el texto que le pasas y nada
más. Si quieres que el Met responda sabiendo con quién habla, arma tú el input — por
ejemplo con `met.contacts.retrieve(contact_id)` y `met.contacts.messages(contact_id)` — o
usa `conversation_id` para que la conversación tenga memoria entre runs.

## 3. Pásasela a una persona cuando haga falta

Un Met que no sabe algo tiene que poder soltar la conversación. Con el autopiloto apagado
los mensajes siguen llegando a tu bandeja, pero el Met deja de responder a ese contacto
hasta que lo vuelvas a prender.

```ts
if (/hablar con (alguien|una persona|un asesor)/i.test(content)) {
  await met.contacts.setAutopilot(contact_id, false);
  await met.contacts.sendMessage(contact_id, 'Te comunico con alguien del equipo 👋');
  await met.contacts.createNote(contact_id, 'Pidió atención humana.');
  return;
}
```

Para devolverle el control al Met: `met.contacts.setAutopilot(contact_id, true)`.

## La ventana de 24 horas

Esto no es un límite de Meteor y no se puede saltar: **WhatsApp solo te deja escribir
libremente durante las 24 horas siguientes al último mensaje de la persona.** Pasada esa
ventana, lo único que entra es una plantilla aprobada por Meta:

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

Si tu flujo contesta a destiempo —una cola, un reintento nocturno— esa ventana es la
primera causa a mirar, antes que la key o los scopes.

## Probarlo sin desplegar nada

El CLI te reenvía los eventos reales del workspace a tu máquina, así que puedes escribir
el handler contra tráfico de verdad antes de tener servidor:

```bash
met listen --forward http://localhost:3000/webhooks/met --types contact.message.received
```

## Y después

- El esquema de cada evento, los reintentos y el log de entregas:
  [Webhooks](webhooks.html).
- Contestar en vivo, palabra por palabra, en vez de esperar el run completo:
  [Mostrar un run en vivo en tu UI](receta-streaming.html).
- Que el Met consulte tus propios datos al responder:
  [Datos: colecciones e ítems](colecciones-e-items.html).
