# 06 · Errores

Cómo debe fallar su herramienta para que el agente reaccione bien.

Esta página no es una convención genérica: los seis valores de `error_type` son el **contrato
canónico que el runtime de Asixto ya consume hoy** para decidir qué hace el agente (reintentar,
quedarse en el paso, cambiar de flujo o escalar a un humano). Un valor fuera de esta lista se trata
como `UNKNOWN`.

## Forma del error

Un fallo de negocio **no es** un error de transporte: devuelva la respuesta de la herramienta con el
error dentro, no un 500.

```json
{
  "error": {
    "error_type": "NOT_FOUND",
    "user_message": "No encontré una cita con esos datos."
  }
}
```

| Campo | Exigencia | Uso |
|---|---|---|
| `error_type` | **DEBE** | Uno de los seis valores de la tabla siguiente, exactamente con esa grafía. Determina la reacción del agente. |
| `user_message` | **DEBE** | Frase corta, en español neutro, sin jerga ni identificadores. El agente la parafrasea o la lee en voz alta. |
| `details` | PUEDE | Detalle técnico para su propia traza. **No lo consumimos** y no llega al modelo ni al usuario. |

No añada otros campos esperando que el agente los interprete: no hay nada más en el contrato. En
particular, **no existe una marca de "reintentable"**: la decisión de reintentar la deriva el agente
del `error_type`, según la tabla.

## Los seis valores de `error_type`

| `error_type` | Cuándo usarlo | Qué hace el agente |
|---|---|---|
| `NOT_FOUND` | La entidad no existe con los datos dados. | Vuelve al paso de identificación y pide verificar los datos. |
| `VALIDATION` | El dato viene mal formado, incumple una regla que el esquema no puede expresar, el estado no permite la operación o hay ambigüedad. | Pide la corrección **sin avanzar de paso**. Es el más útil y el que más va a usar. |
| `PERMISSION` | La operación no le corresponde a ese cliente, o la entidad no le pertenece. | Escala a un humano. **Salvo en llamada telefónica**: ahí el escalado no existe como acción y el agente solo puede ofrecer transferir. |
| `SYSTEM` | Fallo técnico transitorio: su dependencia no responde, timeout, 5xx. | Se disculpa y permite reintentar. **Nunca** dice que el dato no existe. |
| `BLOCKER` | El cliente tiene un impedimento que le prohíbe la operación: mora, suspensión, cuenta bloqueada. | Lo explica con tacto y **cambia de flujo** hacia facturación. Lea el aviso de abajo antes de usarlo. |
| `UNKNOWN` | Cualquier cosa no clasificada. | **Ninguna reacción definida.** Úselo como último recurso: en la práctica el agente improvisa con el `user_message`. |

`PERMISSION` cubre dos cosas que en su sistema pueden ser distintas ("no tiene permiso" y "esa cita
no es suya"): las dos deben terminar en escalado, así que van con el mismo valor.

> **Cuidado con `BLOCKER`.** Es el único valor que **cambia el flujo de la conversación** hacia el
> escenario de facturación. Si ese escenario no está habilitado para su tenant, el agente pierde el
> resto del catálogo en esa conversación y se queda solo con lo básico. Úselo únicamente para
> impedimentos reales de cartera, y coordine con nosotros que el escenario de facturación esté
> activo. Para «este cliente no puede hacer esto por su estado», use `VALIDATION`.

## Lo que NO es un error

| Situación | Respuesta correcta |
|---|---|
| Búsqueda válida sin resultados | **Éxito** con lista vacía y `total: 0`. Ver el aviso de abajo. |
| El cliente pide una franja ocupada | `VALIDATION`, con las alternativas disponibles en la respuesta. |
| Dato ambiguo (varios candidatos) | `VALIDATION` y un `user_message` que pida precisar. Nunca elija por él. |
| Entidad en estado terminal (cita cancelada, oportunidad cerrada) | `VALIDATION`, y diga en el `user_message` cuál es el estado actual. |
| Su servidor está limitando por cuota | `SYSTEM`. Para el agente es un fallo transitorio, que es lo correcto. |
| Su herramienta tarda más de 30 s | No hay nada que devolver: el runtime ya cortó. Diseñe para no llegar ahí ([página 07](./07-limits.md)). |

> **Catálogo vacío ≠ error.** Es el error de integración más frecuente y el más caro. Si devuelve
> `error` cuando simplemente no hay resultados, el agente entra en la rama de fallo: reintenta,
> retrocede de paso o se disculpa por un problema técnico que no existe. Devuelva
> `{"resultados": [], "total": 0}` y déjelo hablar con naturalidad.

## La distinción que más importa

```
❌  catálogo vacío  →  UNKNOWN / SYSTEM  →  "tengo un problema técnico" (falso)
❌  su BD caída     →  NOT_FOUND         →  "no tenemos ese producto"   (falso y grave)
✅  catálogo vacío  →  éxito, total 0    →  "no encontré ese producto"
✅  su BD caída     →  SYSTEM            →  "no puedo consultarlo ahora, ¿lo intentamos de nuevo?"
```

Colapsar los dos casos en un solo valor tiene una consecuencia concreta: el agente le dice a un
cliente real que usted no vende algo que sí vende, y lo dice con total naturalidad.

## Errores de protocolo

Reserve los fallos de transporte para lo que realmente lo es:

| Situación | Respuesta correcta |
|---|---|
| Falta la credencial o es inválida | **401** con `WWW-Authenticate` |
| Entrada que no cumple el `inputSchema` | Error de contrato del SDK (lo genera el propio SDK al validar) |
| Versión de protocolo no soportada | El error de versión previsto por el estándar, no un 500 |
| Herramienta inexistente | Error de método del protocolo |
| Excepción no controlada | Capture y devuelva `error_type: "SYSTEM"`. Un 500 sin cuerpo deja al agente sin nada que decir |

## Reglas de redacción de `user_message`

- Máximo una o dos frases. Se puede escuchar por teléfono.
- Sin identificadores, códigos internos, nombres de tabla ni de sistema.
- Sin culpar al usuario y sin disculpas largas: el agente pone el tono.
- Sin instrucciones al modelo (`"pide el documento otra vez"`): eso lo decide el `error_type`.
- Sin datos personales de terceros, aunque sea para desambiguar.
