# Datos de prueba (sandbox `sk_test`)

> Referencia de **datos de prueba** para integrar contra el ambiente **sandbox** de la
> QuickLink Pay API. Aquí encuentras qué cédulas, teléfonos, bancos y montos usar, qué
> resultado producen, y cómo reproducir cada estado y cada `motivo_rechazo`.
>
> Reemplaza `{{base_url}}` por la URL base de tu ambiente (p. ej. la de producción
> publicada en la documentación, o la que te haya indicado tu contacto de QuickLink).
> Todos los ejemplos usan esa variable: nunca la incrustes en tu código de integración.

---

## 0. TL;DR — lo esencial

- El **modo (test/live) lo fija la API key**, no el cliente. Una llave `sk_test_…`
  enruta a sandbox; una `sk_live_…` mueve dinero real. Tú nunca envías un campo `mode`.
- En **sandbox no se mueve dinero real**: los rieles van a **QA / simulado**
  (Banco Plaza QA, Bancaribe QA, Banco Activo QA; **BNC simulado**, sin red).
- La **respuesta tiene la misma forma** (`Resultado`) que en producción: el resultado
  vive en el campo `estado`, **no** en el código HTTP.
- Lee el estado final con `POST /switch/v1/consultar` (la liquidación es asíncrona) o
  configura un webhook.

> **Importante sobre el alcance de este documento.** Una parte de lo que sigue describe
> el comportamiento **real, ya implementado** del sandbox. Otra parte —en concreto, la
> tabla de “valores mágicos” que fuerzan cada `motivo_rechazo`— es una **convención
> propuesta**, aún no implementada en el código. Cada sección lo indica de forma
> explícita con una de estas etiquetas:
>
> - ✅ **Comportamiento actual** — así responde el sandbox hoy.
> - 🧭 **Propuesta (no implementada aún)** — convención sugerida; verifica con QuickLink
>   antes de depender de ella en tus pruebas automatizadas.

---

## 1. Autenticación y modo

Todas las llamadas usan la cabecera `x-switch-key` con el formato `<comercio>:<api_key>`:

```
x-switch-key: mi_comercio:sk_test_xxxxxxxxxxxxxxxxxxxx
```

- ✅ **El modo lo decide el prefijo de la llave**, resuelto en el servidor:
  - `sk_test_…` → **modo `test`** (sandbox: QA / simulado, sin dinero real).
  - `sk_live_…` → **modo `live`** (producción: rieles bancarios reales).
- ✅ El cliente **no** puede forzar el modo: el campo `mode` lo fija el servidor a partir
  de la llave y por eso **ni siquiera aparece** en el request documentado. Si lo envías,
  se ignora.
- ✅ Para probar, **usa siempre tu llave `sk_test_…`**. Genera una desde el panel
  (sección de API keys del comercio). Ese mismo `x-switch-key` sirve para la API REST y
  para el servidor MCP.

> Una llave inválida o mal formada (`<comercio>:<secreto>` incompleto) responde
> `401 { "error": "unauthorized" }`.

---

## 2. Catálogo de bancos de prueba

El campo `banco` de cada `Instrumento` es el **código BCV de 4 dígitos** del banco de esa
persona. En sandbox puedes usar cualquier banco válido; estos son los más usados en los
ejemplos:

| `banco` | Banco                | Notas para sandbox |
|---------|----------------------|--------------------|
| `0138`  | Banco Plaza          | Riel adquirente/tesorería por defecto. En test → **Plaza QA**. |
| `0191`  | BNC                  | En test → **simulado** (siempre éxito, no sale a la red). |
| `0105`  | Banco Mercantil      | Banco del pagador/beneficiario en ejemplos. |
| `0134`  | Banesco              | Banco del beneficiario en ejemplos. |
| `0102`  | Banco de Venezuela   | Genérico de prueba. |

Consulta el catálogo vigente de bancos habilitados con sus capacidades:

```bash
curl -s "{{base_url}}/switch/v1/bancos" \
  -H "x-switch-key: mi_comercio:sk_test_xxx"
```

> ✅ **Qué banco procesa tu operación lo decide el enrutamiento del switch**, no el
> `banco` del instrumento (ese es el banco de la *persona*). Para sugerir un riel,
> envía `banco_preferido` (p. ej. `"bnc"` para caer en el camino **simulado**
> determinista). Valores: `banco_plaza`, `bnc`, `bancaribe`, `banco_activo`.

---

## 3. Identidades de prueba (cédulas, teléfonos, cuentas)

✅ **Comportamiento actual:** el sandbox **no** valida estas identidades contra un
registro real ni cambia el resultado según la cédula. Cumple las reglas de formato del
contrato (validadas con zod en todos los bordes):

- `identificacion`: cédula/RIF **con prefijo de persona** — `V`/`E` (natural),
  `J`/`G` (jurídico), `P` (pasaporte). Ej.: `V13759368`, `J00378944781`.
- `valor` (teléfono): celular local de 11 dígitos con `0` inicial. Ej.: `04125551234`.
- `valor` (cuenta): número de cuenta de **20 dígitos**. Ej.: `01380000011234567890`.
- `nombre`: 1–40 caracteres.
- `banco`: 4 dígitos (ver §2).

Identidades sugeridas para tus pruebas (cualquiera válida sirve; estas son convenientes):

| Rol            | `tipo`     | `valor`               | `banco` | `identificacion` | `nombre`         |
|----------------|------------|-----------------------|---------|------------------|------------------|
| Comercio (tú)  | `cuenta`   | `01380000011234567890`| `0138`  | `J00378944781`   | `Mi Comercio CA` |
| Pagador        | `telefono` | `04125551234`         | `0105`  | `V13759368`      | `Ana Pérez`      |
| Beneficiario   | `telefono` | `04145556677`         | `0134`  | `V14589678`      | `María Gómez`    |
| Beneficiario   | `cuenta`   | `01340000019876543210`| `0134`  | `V14589678`      | `María Gómez`    |

> 🧭 **Propuesta (no implementada aún).** Si en el futuro quieres forzar resultados por
> identidad (p. ej. “esta cédula siempre está bloqueada”), la vía recomendada es
> registrarla en las **listas del motor de riesgo** del panel en modo test (deny/allow),
> en lugar de codificar cédulas mágicas. Ver §7.3.

---

## 4. Montos

- ✅ Montos en **VES** (`moneda` por defecto `"VES"`, ISO-4217). `valor` debe ser un
  número **positivo**.
- ✅ En sandbox **el límite diario por comercio y el motor de riesgo NO aplican**
  (corren solo sobre dinero real). Por eso, hoy, **el monto no cambia el resultado**: una
  operación de sandbox que llega al riel BNC simulado siempre tiene éxito sin importar el
  monto.
- 🧭 **Propuesta (no implementada aún):** ver §6, donde se propone usar el monto como
  “valor mágico” para disparar de forma **determinista** cada `motivo_rechazo` en un
  simulador de sandbox.

---

## 5. Flujos y cómo simular cada **estado**

Los estados normalizados (`EstadoTransaccion`) son:
`ACEPTADA`, `PENDIENTE`, `LIQUIDADA`, `RECHAZADA`, `ERROR`, `DESCONOCIDA`.

### 5.1 `LIQUIDADA` — éxito final (camino simulado determinista)

✅ **Comportamiento actual.** El camino más predecible en sandbox es **BNC simulado**:
no sale a la red y devuelve éxito siempre. Un **pago** (`/pagar`) por BNC simulado es
instantáneo → **`LIQUIDADA`** directo.

```bash
curl -s -X POST "{{base_url}}/switch/v1/pagar" \
  -H "x-switch-key: mi_comercio:sk_test_xxx" \
  -H "content-type: application/json" \
  -d '{
    "idempotency_key": "11111111-1111-4111-8111-111111111111",
    "monto": { "valor": 250.00, "moneda": "VES" },
    "banco_preferido": "bnc",
    "via": "pago_movil",
    "ordenante":    { "tipo": "cuenta",   "valor": "01380000011234567890", "banco": "0138", "identificacion": "J00378944781", "nombre": "Mi Comercio CA" },
    "beneficiario": { "tipo": "telefono", "valor": "04145556677",          "banco": "0134", "identificacion": "V14589678",    "nombre": "Maria Gomez" },
    "concepto": "Reintegro de prueba",
    "proposito": "transferencia"
  }'
```

Respuesta (forma `Resultado`):

```jsonc
{
  "id": "...",
  "estado": "LIQUIDADA",
  "banco_usado": "bnc",
  "referencia_banco": "TEST-11111111",   // prefijo TEST- + 8 primeros del idempotency_key
  "endtoend": null,
  "motivo_rechazo": null,
  "intentos": 1,
  "raw": { "simulated": true }           // marca inequívoca de sandbox
}
```

> Reconoces una respuesta simulada porque `raw.simulated === true` y la
> `referencia_banco` empieza por `TEST-`.

### 5.2 `ACEPTADA` → `PENDIENTE` → `LIQUIDADA` — flujo asíncrono

✅ **Comportamiento actual.** Los **cobros** por BNC simulado devuelven **`ACEPTADA`**
(aceptado por el banco, aún no liquidado):

```bash
curl -s -X POST "{{base_url}}/switch/v1/cobrar" \
  -H "x-switch-key: mi_comercio:sk_test_xxx" \
  -H "content-type: application/json" \
  -d '{
    "idempotency_key": "22222222-2222-4222-8222-222222222222",
    "monto": { "valor": 320.00, "moneda": "VES" },
    "banco_preferido": "bnc",
    "cobrador": { "tipo": "cuenta",   "valor": "01380000011234567890", "banco": "0138", "identificacion": "J00378944781", "nombre": "Mi Comercio CA" },
    "pagador":  { "tipo": "telefono", "valor": "04125551234",          "banco": "0105", "identificacion": "V13759368",     "nombre": "Ana Perez" },
    "concepto": "Matricula 2026-1",
    "otp": "12345678"
  }'
```

→ `{ "estado": "ACEPTADA", "banco_usado": "bnc", "referencia_banco": "TEST-22222222", "raw": { "simulated": true }, ... }`

Para conocer el estado final, **consulta** con el `id` devuelto:

```bash
curl -s -X POST "{{base_url}}/switch/v1/consultar" \
  -H "x-switch-key: mi_comercio:sk_test_xxx" \
  -H "content-type: application/json" \
  -d '{ "id": "<id-de-la-respuesta-anterior>" }'
```

> En rieles que liquidan por BCV (Crédito Inmediato vía Plaza/Bancaribe QA), una
> operación puede quedar **`ACEPTADA`/`PENDIENTE`** y el poller la confirma luego como
> **`LIQUIDADA`** o **`RECHAZADA`**. Repite `/consultar` (o usa webhook) hasta ver un
> estado **final** (`LIQUIDADA` / `RECHAZADA`).

### 5.3 Cobro con OTP en dos pasos (token + cobrar)

✅ **Comportamiento actual.** Un cobro por débito inmediato puede requerir un **OTP** que
el banco del pagador emite en un paso previo:

1. **Solicitar token** (`/cobrar/token`): el banco del pagador genera el OTP. En sandbox
   BNC esto devuelve `ACEPTADA` simulado (no se envía SMS real).
   ```bash
   curl -s -X POST "{{base_url}}/switch/v1/cobrar/token" \
     -H "x-switch-key: mi_comercio:sk_test_xxx" \
     -H "content-type: application/json" \
     -d '{
       "idempotency_key": "33333333-3333-4333-8333-333333333333",
       "monto": { "valor": 320.00, "moneda": "VES" },
       "banco_preferido": "bnc",
       "cobrador": { "tipo": "cuenta",   "valor": "01380000011234567890", "banco": "0138", "identificacion": "J00378944781", "nombre": "Mi Comercio CA" },
       "pagador":  { "tipo": "telefono", "valor": "04125551234",          "banco": "0105", "identificacion": "V13759368",     "nombre": "Ana Perez" },
       "concepto": "Matricula 2026-1"
     }'
   ```
2. **Cobrar** (`/cobrar`) con el `otp` capturado (ver §6 para el OTP de prueba).

### 5.4 `DESCONOCIDA` — respuesta ambigua

✅ **Comportamiento actual.** `DESCONOCIDA` aparece cuando una operación de dinero sufre
un **timeout/corte post-envío** (el banco pudo haber movido el dinero). Es **ambigua por
diseño**: el switch **no reintegra ni reintenta a ciegas**; lo resuelve `/consultar` o el
poller. En el camino BNC simulado no ocurre de forma natural.

> 🧭 **Propuesta (no implementada aún):** ver §6 para un monto mágico que fuerce
> `DESCONOCIDA` en un simulador determinista.

### 5.5 `RECHAZADA` y `ERROR`

- `RECHAZADA` = rechazo de **negocio**; el detalle está en `motivo_rechazo` (ver §6).
- `ERROR` = fallo técnico interno (reintentable). En sandbox no deberías verlo salvo
  problemas reales de infraestructura del propio QA.

---

## 6. `motivo_rechazo` — catálogo y cómo disparar cada uno

### 6.1 Estado actual

✅ **Comportamiento actual.** El conjunto de motivos está **normalizado** (mismo para
todos los bancos); el código crudo del banco se preserva siempre en `Resultado.raw`.
Valores posibles de `motivo_rechazo`:

```
SALDO_INSUFICIENTE · OTP_INVALIDO · CUENTA_NO_EXISTE · CUENTA_BLOQUEADA ·
CUENTA_NO_DEBITO · CUENTA_NO_CREDITO · MONTO_INVALIDO · EXCEDE_LIMITE ·
BENEFICIARIO_NO_COINCIDE · NO_AFILIADO_SERVICIO · BANCO_NO_DISPONIBLE · DUPLICADA ·
FUERA_DE_HORARIO · CANCELADA_POR_PAGADOR · PENDIENTE_BCV · RECHAZO_TECNICO ·
FIRMA_AUTH · NO_SOPORTADO · BLOQUEO_RIESGO · DESCONOCIDO
```

> Hoy, el sandbox **no** tiene cédulas/teléfonos/montos “mágicos” que fuercen un rechazo
> concreto:
> - El camino **BNC simulado** siempre devuelve éxito.
> - Los caminos **Plaza/Bancaribe/Activo QA** producen rechazos **reales del QA del
>   banco** según los datos que aceptes/configures allí; **no son deterministas** desde
>   esta API y dependen del estado del QA.
>
> Por eso, para pruebas **reproducibles** de cada `motivo_rechazo`, se propone la
> convención de la sección siguiente.

### 6.2 🧭 Convención propuesta de **valores mágicos** (no implementada aún)

> **Propuesta para discutir con QuickLink.** Define un **simulador de sandbox**
> determinista que enrute por el camino simulado y elija el resultado según **el monto**
> (céntimos) y según un **OTP especial**. Es la forma habitual en pasarelas tipo Stripe y
> no requiere cédulas mágicas. **Mientras no esté implementada, estos valores se comportan
> como un cobro/pago de prueba normal (éxito).**

**Por monto** (los **céntimos** seleccionan el resultado; la parte entera es libre):

| `monto.valor` (céntimos `.NN`) | Estado resultante | `motivo_rechazo`          |
|--------------------------------|-------------------|---------------------------|
| cualquiera **no** listado      | `ACEPTADA`/`LIQUIDADA` | `null`               |
| `…​.51`                        | `RECHAZADA`       | `SALDO_INSUFICIENTE`      |
| `…​.65`                        | `RECHAZADA`       | `OTP_INVALIDO`            |
| `…​.02`                        | `RECHAZADA`       | `CUENTA_NO_EXISTE`        |
| `…​.06`                        | `RECHAZADA`       | `CUENTA_BLOQUEADA`        |
| `…​.45`                        | `RECHAZADA`       | `CUENTA_NO_DEBITO`        |
| `…​.48`                        | `RECHAZADA`       | `CUENTA_NO_CREDITO`       |
| `…​.13`                        | `RECHAZADA`       | `MONTO_INVALIDO`          |
| `…​.32`                        | `RECHAZADA`       | `EXCEDE_LIMITE`           |
| `…​.30`                        | `RECHAZADA`       | `BENEFICIARIO_NO_COINCIDE`|
| `…​.10`                        | `RECHAZADA`       | `NO_AFILIADO_SERVICIO`    |
| `…​.91`                        | `RECHAZADA`       | `BANCO_NO_DISPONIBLE`     |
| `…​.11`                        | `RECHAZADA`       | `DUPLICADA`               |
| `…​.01`                        | `RECHAZADA`       | `FUERA_DE_HORARIO`        |
| `…​.99`                        | `RECHAZADA`       | `CANCELADA_POR_PAGADOR`   |
| `…​.31`                        | `PENDIENTE`       | `PENDIENTE_BCV` (en `raw`)|
| `…​.90`                        | `RECHAZADA`       | `RECHAZO_TECNICO`         |
| `…​.00` con OTP especial (ver) | `RECHAZADA`       | `FIRMA_AUTH`              |
| `…​.77`                        | `RECHAZADA`       | `BLOQUEO_RIESGO`          |
| `…​.79`                        | `DESCONOCIDA`     | `null` (no reintegrar)    |

Ejemplo (propuesto): `"monto": { "valor": 320.51 }` → `RECHAZADA` /
`SALDO_INSUFICIENTE`.

**Por OTP** (solo aplica al **paso `/cobrar`**, donde se envía `otp`):

| `otp`        | Resultado del cobro                  |
|--------------|--------------------------------------|
| `00000000`   | `RECHAZADA` / `OTP_INVALIDO`         |
| cualquiera (8 díg.) | OTP **válido** (sigue el monto) |

> Con esta convención, `OTP_INVALIDO` se puede disparar de dos formas equivalentes:
> con el monto `…​.65` o con el OTP `00000000`.

> **Por qué es propuesta y no realidad:** el código actual no inspecciona céntimos ni
> OTPs especiales en modo test; el motor de riesgo (que produciría `BLOQUEO_RIESGO`) y el
> límite diario (`EXCEDE_LIMITE`) **se saltan explícitamente en sandbox**. Implementar
> esto requiere un “banco simulador” dedicado en el switch. Trátalo como **diseño
> sugerido**, no como contrato.

---

## 7. Otras formas (reales) de provocar resultados específicos en sandbox

Aunque no haya montos mágicos, sí puedes provocar ciertos resultados **de verdad** en
sandbox usando la configuración del panel, porque algunas validaciones corren **antes**
de tocar el banco:

### 7.1 `NO_SOPORTADO`
✅ Operaciones que un banco no soporta (p. ej. `/reverso` en rieles sin anulación)
responden `RECHAZADA` / `NO_SOPORTADO`. Útil para probar tu manejo de “no se puede
revertir, reintegra manual”.

### 7.2 `BANCO_NO_DISPONIBLE`
✅ Si deshabilitas todos los bancos capaces de una operación (desde el panel, en modo
test), el enrutamiento no encuentra riel y la operación se rechaza limpio.

### 7.3 `BLOQUEO_RIESGO` (vía listas del panel, en test)
🧭 **Propuesta/condicional.** El motor de riesgo, por diseño, **solo evalúa dinero real**;
en sandbox no bloquea. Si necesitas ejercitar el manejo de `BLOQUEO_RIESGO` en test,
coordínalo con QuickLink (requeriría habilitar la evaluación de listas en modo test). El
**sujeto** evaluado es quien recibe el efecto del dinero: el *pagador* en
`cobrar`/`token`, el *beneficiario* en `pagar`.

---

## 8. Idempotencia (vale para sandbox y producción)

✅ **Comportamiento actual.** Envía siempre un `idempotency_key` **UUID** único por
operación.

- Reintentar `cobrar`/`pagar`/`token` con el **mismo** `idempotency_key` (mismo comercio)
  **no duplica** la operación: devuelve el **resultado original**.
- Cambia el `idempotency_key` para ejecutar una operación **nueva**.
- En el camino BNC simulado, el `idempotency_key` también determina la
  `referencia_banco`: `TEST-` + sus **8 primeros caracteres**.

Genera un UUID v4 por operación (`uuidgen`, `crypto.randomUUID()`, etc.).

---

## 9. `pagar`: elegir el riel con `via`

✅ **Comportamiento actual.** El campo `via` del request de `/pagar` elige el riel:

| `via`                 | Riel                              | Instrumento     | Liquidación |
|-----------------------|-----------------------------------|-----------------|-------------|
| `credito_inmediato` (default) | Crédito Inmediato (CCE/BCV)| teléfono o cuenta | Asíncrona → `ACEPTADA` y luego `LIQUIDADA` por consulta |
| `pago_movil`          | Pago Móvil P2P                    | solo teléfono   | Instantánea → `LIQUIDADA` |

> Regla: **a cuenta siempre es crédito inmediato**; a teléfono es crédito inmediato salvo
> que indiques `via: "pago_movil"`. Para una `LIQUIDADA` inmediata y determinista en
> sandbox, usa `banco_preferido: "bnc"` + `via: "pago_movil"` (§5.1).

---

## 10. Consultas que no mueven dinero

✅ Disponibles también en sandbox (read-only, no las filtran las reglas de pago):

```bash
# Saldo de una cuenta cobradora
curl -s "{{base_url}}/switch/v1/saldo?cuenta=01380000011234567890&moneda=VES" \
  -H "x-switch-key: mi_comercio:sk_test_xxx"

# Movimientos (conciliación) — 'desde' es obligatorio (YYYY-MM-DD)
curl -s "{{base_url}}/switch/v1/movimientos?cuenta=01380000011234567890&desde=2026-06-01" \
  -H "x-switch-key: mi_comercio:sk_test_xxx"

# Catálogo de bancos
curl -s "{{base_url}}/switch/v1/bancos" \
  -H "x-switch-key: mi_comercio:sk_test_xxx"
```

> En sandbox, `saldo`/`movimientos` se sirven desde el QA del banco correspondiente; sus
> cifras son de prueba y **no reflejan dinero real**.

---

## 11. Recordatorio de seguridad

> ✅ **`sk_test` no mueve dinero real.** El sandbox enruta a ambientes de ensayo:
> **Banco Plaza QA**, **Bancaribe QA**, **Banco Activo QA** y **BNC simulado** (BNC no
> tiene sandbox alcanzable, así que el switch lo simula sin salir a la red). Ninguna
> operación de prueba debita ni acredita cuentas productivas.
>
> Cuando integres en producción, **cambia únicamente la API key** (`sk_test_…` →
> `sk_live_…`): el contrato, los endpoints y la forma de las respuestas son idénticos.
> Reserva las llaves `sk_live_…` para tráfico real y nunca las publiques en código cliente
> ni repositorios.
