> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waspytech.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Handoff humano (origin)

> Cómo distinguir mensajes de tu bot, del panel humano, de la AI nativa o de campañas — y pausar el bot cuando un agente toma la conversación.

Cada `MessagePublic` incluye un campo `origin` que te dice exactamente quién generó ese mensaje. Es la señal canónica para implementar handoff humano sin que tu bot pise a un agente real.

## Valores de `origin`

| Valor          | Significado                                                                      |
| -------------- | -------------------------------------------------------------------------------- |
| `inbound`      | Mensaje recibido del contacto (no aplica handoff — vos respondés a estos)        |
| `panel`        | Lo mandó un agente humano desde el panel web de Waspy                            |
| `external_api` | Lo mandó tu integración (esta API v2 con tu API key)                             |
| `ai_agent`     | Lo mandó la AI nativa de Waspy (chatbot flows, AI agents)                        |
| `campaign`     | Es parte de una campaña / template scheduled                                     |
| `history`      | Backfill de history sync de Meta (no es reactivo, ignoralo en lógica de negocio) |

## Patrón de handoff

```ts theme={null}
// Tu webhook handler
function shouldBotRespond(msg: MessagePublic): boolean {
  // Solo respondemos a mensajes entrantes del contacto
  if (msg.direction !== 'inbound') return false;
  // Si origin no es 'inbound', es algo que generamos nosotros — no entrar en loop
  if (msg.origin !== 'inbound') return false;
  return true;
}

function shouldPauseBot(conversation, recentMessages: MessagePublic[]): boolean {
  // Si el último mensaje saliente fue del panel, un humano está atendiendo
  const lastOutbound = recentMessages.findLast((m) => m.direction === 'outbound');
  return lastOutbound?.origin === 'panel';
}
```

## Cómo lo aplicás en webhooks

Los eventos `message.status_changed` y `message.received` traen `origin` en el payload. Tu bot debería:

1. **`message.received`** con `origin: 'inbound'` → procesar normalmente.
2. **`message.status_changed`** con `origin: 'panel'` → marcar la conversación como "humano-controlada", no responder.
3. **`message.status_changed`** con `origin: 'external_api'` → confirmación de que tu propio mensaje fue entregado.

## Estado por conversación

Si tu CRM mantiene estado por conversación (ej. "bot vs humano"), una buena heurística:

* Cuando llega cualquier mensaje saliente con `origin: 'panel'`, marcá la conversación como `botPaused: true`.
* Cuando llega un mensaje entrante después de N minutos sin actividad humana, podés re-activar el bot (`botPaused: false`).

## Origin para mensajes pre-abril 2026 (legacy fallback)

Los mensajes creados antes de que existiera `metadata.origin` tienen el campo derivado en orden de prioridad:

| Si...                                           | Entonces `origin` = |
| ----------------------------------------------- | ------------------- |
| `direction === 'inbound'`                       | `inbound`           |
| `source === 'history'`                          | `history`           |
| `campaignId` no es null                         | `campaign`          |
| `sentByAi === true`                             | `ai_agent`          |
| `sentBy` no es null (era un user UUID en panel) | `panel`             |
| ninguna de las anteriores                       | `external_api`      |

La precisión es alta para los casos comunes. Casos borde:

* Mensaje viejo enviado por API con `sentBy` poblado por error → se clasifica como `panel`. Workaround: si tu lógica depende del orden de eventos, considerá también `metadata.apiKeyId` cuando esté presente (mensajes nuevos sí lo setean).
* Mensaje de history sync que ALSO tenía `sentByAi: true` → clasifica como `history` (ganaba precedencia). Esos mensajes no son reactivos, ignoralos.

Para mensajes nuevos (post-sprint integration-ready, abril 2026 en adelante) `metadata.origin` siempre se setea explícitamente y este fallback no aplica.

## Notas adicionales

* `metadata.origin` es la fuente de verdad en DB; el campo público `origin` es la versión canónica que vas a usar.
