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

# Escenarios de prueba

> Fuerza comportamientos específicos vía el header X-Sandbox-Scenario.

## Cómo usar

Agrega el header `X-Sandbox-Scenario: <escenario>` a cualquier llamada de cash-in o cash-out. Sin el header, el sandbox usa el escenario `success` por defecto.

```bash theme={"system"}
curl -X POST https://sandbox.mx.ntxpay.com/api/spei/cash-out \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Sandbox-Scenario: error:insufficient-funds" \
  -H "Content-Type: application/json" \
  -d '{
    "amountCentavos": 15000,
    "destinationClabe": "012180001234567890",
    "beneficiaryName": "Maria Lopez",
    "externalId": "test-error-001"
  }'
```

<Warning>
  El header solo controla el **webhook asíncrono**. La respuesta HTTP siempre es `201 Created` con `status: PENDING`, sin importar el escenario. El resultado final (`CONFIRMED`, `FAILED` o `EXPIRED`) llega en el webhook \~1 segundo después.
</Warning>

## Escenarios disponibles

### Escenarios de éxito

| Header Value        | Comportamiento síncrono | Webhook             |
| ------------------- | ----------------------- | ------------------- |
| `success` (default) | `201 PENDING`           | `CONFIRMED` en \~1s |
| *(sin header)*      | `201 PENDING`           | `CONFIRMED` en \~1s |

### Escenarios de error

| Header Value                  | Comportamiento síncrono | Webhook                                         |
| ----------------------------- | ----------------------- | ----------------------------------------------- |
| `error:insufficient-funds`    | `201 PENDING`           | `FAILED` con `errorCode: INSUFFICIENT_FUNDS`    |
| `error:invalid-clabe`         | `201 PENDING`           | `FAILED` con `errorCode: INVALID_CLABE`         |
| `error:account-not-found`     | `201 PENDING`           | `FAILED` con `errorCode: ACCOUNT_NOT_FOUND`     |
| `error:account-blocked`       | `201 PENDING`           | `FAILED` con `errorCode: ACCOUNT_BLOCKED`       |
| `error:duplicate-external-id` | `201 PENDING`           | `FAILED` con `errorCode: DUPLICATE_EXTERNAL_ID` |
| `error:bank-rejected`         | `201 PENDING`           | `FAILED` con `errorCode: BANK_REJECTED`         |

### Escenarios de atraso

| Header Value  | Comportamiento síncrono | Webhook               |
| ------------- | ----------------------- | --------------------- |
| `delayed:5s`  | `201 PENDING`           | `CONFIRMED` tras +5s  |
| `delayed:30s` | `201 PENDING`           | `CONFIRMED` tras +30s |
| `delayed:60s` | `201 PENDING`           | `CONFIRMED` tras +60s |

<Info>
  El delay máximo permitido es de **120 segundos** — valores mayores se truncan automáticamente.
</Info>

## Ejemplo: webhook de éxito

```json theme={"system"}
{
  "event": "cash_out",
  "deliveryId": "8e2c5b6f-3a12-4b9c-9a18-77a2b3c4d5e6",
  "createdAt": "2026-03-26T10:00:00.000Z",
  "transaction": {
    "id": 12345,
    "externalId": "test-success-001",
    "paymentMethod": "SPEI",
    "direction": "out",
    "type": "cash_out",
    "status": "CONFIRMED",
    "provider": "sandbox",
    "amountCentavos": 15000,
    "clabe": "012180001234567890",
    "referenceNumerical": "9876543",
    "createdAt": "2026-03-26T09:59:59.000Z",
    "confirmedAt": "2026-03-26T10:00:00.000Z",
    "counterpart": {
      "name": "Maria Lopez",
      "taxId": null,
      "bank": {}
    }
  },
  "errorCode": null,
  "errorMessage": null,
  "metadata": {}
}
```

## Ejemplo: webhook de falla

```json theme={"system"}
{
  "event": "cash_out",
  "deliveryId": "1a3f9e8d-2c47-4b9c-aa18-77a2b3c4d5e6",
  "createdAt": "2026-03-26T10:01:00.000Z",
  "transaction": {
    "id": 12346,
    "externalId": "test-error-001",
    "paymentMethod": "SPEI",
    "direction": "out",
    "type": "cash_out",
    "status": "FAILED",
    "provider": "sandbox",
    "amountCentavos": 15000,
    "clabe": "012180001234567890",
    "referenceNumerical": null,
    "confirmedAt": null
  },
  "errorCode": "INSUFFICIENT_FUNDS",
  "errorMessage": "Cuenta sin saldo suficiente",
  "metadata": {}
}
```

Notas:

* En `status: FAILED`, `referenceNumerical` y `confirmedAt` son `null` — la red SPEI nunca confirmó la transacción.
* `errorCode` y `errorMessage` describen el motivo de la falla.

## Restricciones

* El header `X-Sandbox-Scenario` funciona **exclusivamente** en cuentas sandbox.
* Las cuentas de producción que envíen el header reciben:

```json theme={"system"}
{
  "statusCode": 400,
  "message": "X-Sandbox-Scenario header is only supported in sandbox mode."
}
```

## Buenas prácticas

1. **Prueba todos los escenarios** antes de ir a producción — implementa el manejo de `CONFIRMED`, `PENDING`, `FAILED` y `EXPIRED`.
2. **Valida los campos de error** — usa `errorCode` para decisiones automáticas; reserva `errorMessage` para logs/usuarios.
3. **Prueba con delay** — verifica que tu sistema maneja bien la entrega lenta del webhook.
4. **Idempotencia** — usa `transaction.id` como clave de idempotencia; el mismo webhook puede ser re-entregado.
