# Códigos de error: Decisiones del webhook de pagos

> Cada aviso de la pasarela queda en `payment_webhook_events` con un `decision_code` (`src/payments/entities/payment-webhook-event.entity.ts`). El webhook nunca pierde un pago: lo que no puede aplicar lo guarda como `REVIEW_*` para revisión humana y lo que no entiende como `REJECTED_*`. `ACCEPTED_APPLIED` (el caso normal) no se lista.

**Cómo se detecta (toda la familia):** Base: `SELECT decision_code, COUNT(*) FROM payment_webhook_events GROUP BY 1`. Cada fila guarda el cuerpo recibido para revisarlo.

**Qué hacer (toda la familia):** `REVIEW_*`: alguien revisa el pago contra Zauru y la orden, y lo aplica a mano si corresponde. `REJECTED_*`: revisar la plantilla que manda la pasarela (Pabbly) si se repite.

Lo que un código no dice lo hereda de la familia.

## `ACCEPTED_ALREADY_PAID`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Llegó otro aviso de un link que ya estaba pagado: no se asienta dos veces. Informativo.

## `REJECTED_INVALID_UUID`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** El `uuid` del aviso no coincide con el del link (o llegó vacío). Es la única prueba de autenticidad del endpoint, así que el aviso se rechaza.
- **Causa típica:** La plantilla del proveedor (Pabbly) manda mal la variable del uuid, o alguien llamó al webhook sin ser la pasarela.
- **Qué hacer:** Si el uuid llegó vacío, revisar el paso que arma el cuerpo en la pasarela. Si no coincide, verificar con el proveedor que el cobro exista antes de hacer nada.

## `REJECTED_MALFORMED_PAYLOAD`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** El cuerpo del aviso no se pudo leer o el monto no es un cobro (0 o negativo). No hay dinero que registrar.
- **Causa típica:** La plantilla JSON del proveedor salió con huecos (variables sin llenar) o el proveedor mandó un aviso de prueba.
- **Cómo detectarlo:** Base: `payment_webhook_events` con `decision_code = 'REJECTED_MALFORMED_PAYLOAD'`; `decision_detail` dice qué faltó. Es el rechazo más común en producción (14 al 30/09/2026).
- **Qué hacer:** Revisar la plantilla del webhook en la pasarela (Pabbly) y que cada variable llegue llena.

## `REJECTED_STATUS_NOT_SUCCESS`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** El proveedor avisó un estado que no es cobro exitoso (rechazado, cancelado…). El link queda `failed`; no hay nada que conciliar.

## `REVIEW_AMOUNT_BELOW_EXPECTED`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Link de varias órdenes: entró menos de lo esperado y no hay a qué orden cargarle lo que falta. Se guarda para revisión humana.

## `REVIEW_AMOUNT_EXCEEDS_EXPECTED`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Entró más dinero del que esperaba el link. No se asienta solo: queda para revisión humana.

## `REVIEW_LINK_CANCELLED`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Llegó un pago para un link que ya estaba cancelado (p. ej. porque la cotización cambió). No se asienta: revisión humana.

## `REVIEW_LINK_NOT_FOUND`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Llegó un pago contra un link que no existe en esta base. No se asienta nada y el endpoint responde 404, pero la evidencia queda guardada.
- **Causa típica:** Link borrado, ambiente cruzado (aviso de pruebas contra producción o al revés) o un cobro real que hay que explicar.
- **Cómo detectarlo:** Render: `[webhook] uid=<n> link inexistente`. Base: `payment_webhook_events` con este `decision_code` y `needs_reconciliation = 1`.
- **Qué hacer:** Verificar con el proveedor si el cobro ocurrió de verdad y a qué link o entidad corresponde.

## `REVIEW_ORDERS_CHANGED`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Link de varias órdenes: desde que se emitió, alguna orden se pagó por otro lado, se anuló o bajó su saldo. Asentar lo pondría doble: revisión humana.

## `REVIEW_PAYMENT_WITHOUT_ID`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Zauru aceptó el pago pero no devolvió su id, así que no se puede confirmar. El link queda `failed` y el pago para revisión.

## `REVIEW_STATUS_MISSING`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Llegó un aviso con el estado vacío: no se sabe si el cobro fue exitoso. El link queda intacto y el aviso para revisión.
- **Causa típica:** La variable de estado no se llenó en el paso que arma el cuerpo del webhook en la pasarela.
- **Cómo detectarlo:** Render: `[webhook] uid=<n> llego con status vacio`.
- **Qué hacer:** Corregir la variable de estado en la pasarela y verificar con el proveedor si ese cobro ocurrió.

## `REVIEW_ZAURU_SYNC_FAILED`

- **Tipo:** pago · **HTTP:** — · **Estado:** vigente
- **Qué significa:** El cobro llegó bien pero no se pudo asentar en Zauru (Zauru caído o sesión muerta). Se guarda la evidencia y se responde error para que el proveedor reintente.
- **Causa típica:** Zauru no respondió o la sesión de servicio no estaba viva en ese momento.
- **Qué hacer:** Si el proveedor no reintentó con éxito, asentar el pago a mano en Zauru con los datos de `payment_webhook_events`.
