# Errores

> Todas las respuestas tienen la misma forma; los errores traen un código estable y qué hacer.

Cada respuesta es un objeto con `version: 1`, `ok`, `progreso` y `requiereRevision`. Si `ok` es `true` trae `datos`; si es `false` trae `error`. Decidí siempre por `error.codigo` y `error.recuperacion`, nunca por el texto de `mensaje` (que está pensado para mostrarle a la clienta y puede cambiar).

```json
{
  "version": 1,
  "ok": false,
  "progreso": {
    "principal": "NO_GUARDADO"
  },
  "requiereRevision": false,
  "error": {
    "dominio": "compras",
    "codigo": "HORARIO_NO_DISPONIBLE",
    "mensaje": "Ese horario ya no está disponible.",
    "categoria": "ESTADO",
    "reintentable": false,
    "recuperacion": "RELEER_Y_CONFIRMAR"
  }
}
```

## Campos del error

| Campo | Qué es |
| --- | --- |
| `dominio` | `api` (la puerta: clave, origen, límites, formato) o `compras` (el motor de reservas). |
| `codigo` | Código estable. Es el que tenés que usar en tu código. |
| `mensaje` | Texto en español para mostrar. |
| `categoria` | `ENTRADA`, `ACCESO`, `ESTADO`, `IDENTIDAD`, `PROVEEDOR` o `TECNICO`. |
| `reintentable` | `true` solo si repetir el mismo pedido, con la misma `Idempotency-Key`, puede andar. |
| `recuperacion` | Qué hacer: ver la tabla de abajo. |
| `campos` | Con `ENTRADA_INVALIDA`: la lista de campos con problema (`campo`, `codigo`, `mensaje`). |
| `referenciaDiagnostico` | Con errores técnicos: un identificador para pedir soporte. No trae detalles internos. |

## Qué hacer según la recuperación

| Recuperación | Qué hacer |
| --- | --- |
| `CORREGIR_ENTRADA` | Corregí los datos (mirá `campos`) y mandá un pedido nuevo. |
| `RELEER_Y_CONFIRMAR` | El estado cambió (por ejemplo, alguien tomó el horario). Volvé a consultar la disponibilidad y pedile a la clienta que elija otra vez. |
| `REINTENTAR_MISMA_INTENCION` | Reintentá el mismo pedido con la misma `Idempotency-Key`, respetando `Retry-After` si viene. |
| `CONSULTAR_ESTADO` | No se sabe si se guardó. Reintentá con la misma clave de idempotencia o consultá la reserva. |
| `REVISION_HUMANA` | Necesita que alguien del negocio lo mire. No reintentes en bucle. |
| `NO_REINTENTAR` | Repetir da lo mismo. |

## Progreso

`progreso.principal` dice qué se sabe de la operación: `CONSULTADO` (lecturas), `NO_GUARDADO`, `GUARDADO` (la reserva existe pero falta algo, por ejemplo el pago), `CONFIRMADO` o `DESCONOCIDO`. En reservas puede venir `progreso.pago`: `PENDIENTE`, `CONFIRMADO`, `EN_REVISION` o `INCIERTO`. Un error nunca se presenta como algo mayor a lo que el servidor confirmó.

## Códigos de la API (dominio api)

| HTTP | Código | Reintentable | Mensaje |
| --- | --- | --- | --- |
| 404 | `API_NO_HABILITADA` | No | La API externa no está habilitada. |
| 403 | `PLAN_SIN_API` | No | La API está incluida en el plan Enterprise. El negocio puede cambiar de plan desde su panel. |
| 401 | `CREDENCIAL_FALTANTE` | No | Mandá la clave en Authorization: Bearer <clave>. |
| 401 | `CREDENCIAL_INVALIDA` | No | La clave no es válida. |
| 401 | `CREDENCIAL_REVOCADA` | No | La clave fue revocada por el negocio. |
| 403 | `CLAVE_SECRETA_EN_NAVEGADOR` | No | La clave secreta es solo para servidores: desde el navegador usá la clave pública. |
| 403 | `ORIGEN_NO_PERMITIDO` | No | Este sitio no está autorizado para usar la clave. |
| 403 | `SCOPE_FALTANTE` | No | La clave no tiene permiso para esta operación. |
| 429 | `LIMITE_USO` | Sí | Demasiados pedidos. Esperá unos segundos y reintentá con los mismos datos. |
| 400 | `IDEMPOTENCY_KEY_FALTANTE` | No | Mandá la cabecera Idempotency-Key: una clave estable por operación. |
| 400 | `IDEMPOTENCY_KEY_INVALIDA` | No | Idempotency-Key: entre 8 y 100 caracteres A-Z, a-z, 0-9, punto, guion, guion bajo o dos puntos. |
| 400 | `ENTRADA_INVALIDA` | No | Revisá los datos enviados. |
| 404 | `NO_ENCONTRADO` | No | No existe o no pertenece a este negocio. |
| 409 | `SERVICIO_DERIVADO_WHATSAPP` | No | Este servicio se agenda por WhatsApp con la profesional, no por la API. |
| 500 | `ERROR_TECNICO` | Sí | No pudimos completar el pedido. Reintentá con los mismos datos. |
| 400 o 404 | `CONSULTA_RECHAZADA` | No | Lecturas: el catálogo o la disponibilidad rechazaron el pedido (por ejemplo, un servicio que no existe). El mensaje explica por qué. |

## Códigos de la reserva (dominio compras)

| HTTP | Código | Recuperación | Reintentable |
| --- | --- | --- | --- |
| 400 | `ENTRADA_INVALIDA` | `CORREGIR_ENTRADA` | No |
| 400 | `SOLICITUD_NO_PERMITIDA` | `CORREGIR_ENTRADA` | No |
| 404 o 400 | `CATALOGO_NO_DISPONIBLE` | `CORREGIR_ENTRADA` | No |
| 400 | `DESCUENTO_NO_APLICABLE` | `CORREGIR_ENTRADA` | No |
| 409 | `HORARIO_NO_DISPONIBLE` | `RELEER_Y_CONFIRMAR` | No |
| 409 | `ESTADO_CAMBIO` | `RELEER_Y_CONFIRMAR` | No |
| 409 | `IMPORTE_NO_CORRESPONDE` | `RELEER_Y_CONFIRMAR` | No |
| 409 | `OPERACION_NO_PERMITIDA` | `NO_REINTENTAR` | No |
| 400 o 409 | `CONFIGURACION_INCOMPLETA` | `REVISION_HUMANA` | No |
| 409 | `DATOS_INCONSISTENTES` | `REVISION_HUMANA` | No |
| 409 | `COMPRA_GUARDADA_REVISAR` | `CONSULTAR_ESTADO` | No |
| 409 | `AGENDA_OCUPADA` | `REINTENTAR_MISMA_INTENCION` | Sí |
| 409 | `CONFLICTO_INTENCION` | `REVISION_HUMANA` | No |
| 409 | `PAGO_NO_VERIFICADO` | `REVISION_HUMANA` | No |
| 404 | `NO_ENCONTRADO` | `NO_REINTENTAR` | No |
| 403 | `SIN_ACCESO` | `NO_REINTENTAR` | No |
