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

# Link de seguimiento de transacciones

> Cada comprobante ahora tiene un link público compartible — línea de tiempo de estados estilo Wise, descarga del PDF y tres idiomas, sin iniciar sesión

export const EnvUrls = ({lang = "en"}) => {
  const T = {
    en: {
      test: "Test",
      live: "Live",
      hint: "Same API on both environments — build against test first, then go live by swapping the base URL and the key.",
      guide: "Environments and testing",
      href: "/en/environment-testing"
    },
    es: {
      test: "Test",
      live: "Live",
      hint: "La misma API en ambos ambientes — construye primero contra test y pasa a live cambiando la URL base y la key.",
      guide: "Entorno y pruebas",
      href: "/es/entorno-y-pruebas"
    },
    zh: {
      test: "Test",
      live: "Live",
      hint: "两个环境的 API 完全一致——先在 test 环境构建，再通过切换基础 URL 和密钥上线。",
      guide: "环境与测试",
      href: "/zh/environment-testing"
    }
  };
  const t = T[lang] || T.en;
  const row = (label, url, keyPattern, badgeCls) => <div className="flex flex-wrap items-center gap-2 px-3 py-2">
      <span className={"rounded px-1.5 py-0.5 text-xs font-semibold uppercase tracking-wide " + badgeCls}>
        {label}
      </span>
      <code className="text-xs">{url}</code>
      <span className="text-xs text-gray-400 dark:text-zinc-500">·</span>
      <code className="text-xs">{keyPattern}</code>
    </div>;
  return <div className="my-4 rounded-xl border border-gray-200 dark:border-zinc-700 divide-y divide-gray-200 dark:divide-zinc-700 text-sm not-prose">
      {row(t.test, "https://cryptobank.qbank.cl/platform", "pk_test_...", "bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300")}
      {row(t.live, "https://api.qbank.cl/platform", "pk_...", "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-300")}
      <p className="px-3 py-2 text-xs text-gray-500 dark:text-zinc-400 m-0">
        {t.hint} <a href={t.href}>{t.guide} →</a>
      </p>
    </div>;
};

<EnvUrls lang="es" />

## Qué es

Cada comprobante que emite la plataforma (payout, payin, devolución, transferencia interna, conversión, retiro o depósito crypto, operación banking, compra con tarjeta) tiene un **link público de seguimiento**, al estilo de Wise:

```
https://business.cbpayapp.com/t/{code}
```

Cualquiera que tenga el link puede abrir la página — **sin iniciar sesión, sin API key** — y ver el estado en vivo de esa transacción con una línea de tiempo paso a paso, el detalle completo del comprobante y la opción de descargar el PDF.

El `{code}` es el **mismo código firmado HMAC** que ya respalda la verificación de comprobantes (`GET /verify/receipts/{code}` y el QR impreso en cada PDF). El link *es* la capacidad: no se puede adivinar ni falsificar, y solo expone esa única transacción.

## De dónde sale el link

Nunca construyes la URL tú mismo — la plataforma te la entrega:

1. **`verify_url` en los payloads de comprobantes.** Cuando una transacción llega a un estado final, su comprobante incluye `verify_url`. Con el tracker habilitado, esa URL ahora apunta a `https://business.cbpayapp.com/t/{code}`.
2. **Emails de comprobante.** El correo brandeado que recibe tu cliente lleva el mismo link (botón "Verificar en línea" / seguimiento).
3. **El QR de cada comprobante PDF** codifica el mismo código — escanearlo abre el tracker.

Reenvía el link tal cual a tu cliente, a tu mesa de soporte o a tu equipo de finanzas. Todos los que tengan el link ven la misma página.

## Obtener el link de una transacción ya hecha (API)

Los comprobantes y los emails siempre llevan el link, pero no necesitas descargar nada para obtenerlo: llama `GET /v1/track-link` con el `kind` y el `id` de la transacción para alimentar un botón **"Compartir link"** en tu propia UI.

| Parámetro | Tipo   | Descripción                                                                                                                                                                                    |
| --------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind`    | string | Familia de la transacción: `payout`, `payin`, `payin_refund`, `transfer`, `crypto_withdrawal`, `crypto_deposit`, `swap`, `card_purchase`, `banking_operation`, `wallet_send`, `wallet_deposit` |
| `id`      | string | Identificador de la transacción — el mismo `id` que devuelven los endpoints del producto y los webhooks de ese `kind`                                                                          |

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -G "https://api.qbank.cl/platform/v1/track-link" \
  -H "Authorization: Bearer $CBPAY_TOKEN" \
  --data-urlencode "kind=payout" \
  --data-urlencode "id=9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
```

Respuesta `200 OK`:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "track_url": "https://business.cbpayapp.com/t/P9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d8f4a2c1e5b7",
  "code": "P9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d8f4a2c1e5b7"
}
```

El `code` es el mismo código firmado con HMAC que se imprime como QR en cada comprobante, así que el link es **determinista**: llamar el endpoint dos veces para la misma transacción siempre devuelve la misma URL.

**Quién puede llamarlo.** La cuenta dueña de la transacción (API key o sesión de miembro), los admins de la organización y los admins de plataforma — el mismo alcance de lectura que los comprobantes. Una transacción fuera de tu alcance (o un `kind` desconocido) responde `404 not_found`, nunca `403`: la existencia nunca se revela. Si falta `kind` o `id`, responde `400 invalid_payload`.

## Qué muestra la página

* **Badge de estado** — un estado público y legible (`completed`, `processing`, `failed`) con el detalle de la operación (destinatario, referencia, montos, tasa de cambio).
* **Línea de tiempo** — una secuencia de pasos fija por tipo de operación (por ejemplo, un payout: *Iniciado → Procesando → En tránsito → Completado*). Solo se muestran timestamps reales: el primer paso lleva la hora de creación y el paso terminal alcanzado lleva la última actualización; los pasos intermedios jamás muestran fechas fabricadas.
* **Comprobante PDF** — el mismo comprobante brandeado y verificable, generado al vuelo, descargable directamente desde la página.
* **Tu branding** — nombre, logo, sitio web y color de acento de tu organización (white-label por diseño).
* **Link al explorador blockchain** — para transacciones crypto con hash on-chain, un link al explorador público.
* **Bloque de soporte** — "¿Problemas con esta transferencia?" apuntando al sitio web de tu organización.

## API JSON pública (construye tu propio tracker)

Los mismos datos que renderiza la página están disponibles como JSON — útil si quieres embeber el seguimiento dentro de tu propio portal en vez de redirigir al nuestro:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://api.qbank.cl/platform/v1/public/track/P9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d8f4a2c1e5b7?lang=es"
```

Sin autenticación. Respuesta de un payout completado:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "kind": "payout",
  "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "status": "completed",
  "status_class": "ok",
  "subtitle": "Venezuela — Pago Móvil",
  "fields": [
    { "label": "ID DE TRANSACCIÓN", "value": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "mono": true },
    { "label": "FECHA Y HORA", "value": "8 de agosto de 2026 a las 15:29 UTC" },
    { "label": "BENEFICIARIO", "value": "María Pérez" },
    { "label": "BANCO", "value": "Banco de Venezuela" },
    { "label": "TELÉFONO", "value": "0414-1234567" },
    { "label": "MENSAJE", "value": "Factura 1042" }
  ],
  "amounts": [
    { "label": "MONTO RECIBIDO", "value": "1250000.00 VES" },
    { "label": "TASA DE CAMBIO", "value": "950.00" },
    { "label": "DÉBITO TOTAL", "value": "1315.79 USDT" }
  ],
  "created_at": "2026-08-08T15:29:00Z",
  "timeline": [
    { "key": "initiated",   "state": "complete", "at": "2026-08-08T15:29:00Z" },
    { "key": "processing",  "state": "complete", "at": null },
    { "key": "in_transit",  "state": "complete", "at": null },
    { "key": "completed",   "state": "complete", "at": "2026-08-08T15:31:12Z" }
  ],
  "branding": {
    "name": "CBPay",
    "logo_url": "https://cdn.cbpayapp.com/branding/cbpay/logo.svg",
    "website": "https://www.cbpayapp.com",
    "accent": "#FBC140"
  },
  "receipt_pdf_url": "https://api.qbank.cl/platform/v1/public/track/P9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d8f4a2c1e5b7/receipt.pdf",
  "whats_next": "payout.done",
  "support": { "website": "https://www.cbpayapp.com" }
}
```

| Campo                | Descripción                                                                                                                                                                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind`               | `payout` · `payin` · `payin_refund` · `transfer` · `swap` · `crypto_withdrawal` · `crypto_deposit` · `wallet_send` · `wallet_deposit` · `banking_operation` · `card_purchase`                                                                           |
| `status`             | Estado **público**: el estado de la plataforma en minúscula — ver la tabla de anti tipping-off abajo. Los estados de revisión nunca se exponen.                                                                                                         |
| `status_class`       | `ok` (completed, credited, confirmed, settled, captured, approved) · `failed` (failed, declined, canceled, rejected, reversed, expired) · `pending` (todo lo demás) — define el color del badge.                                                        |
| `fields` / `amounts` | Filas etiqueta/valor. **Las etiquetas llegan ya traducidas** según `?lang=`; tu front solo traduce el marco de la página y los pasos de la línea de tiempo. `mono: true` marca valores largos (IDs, hashes) para renderizar en monoespaciada más chica. |
| `timeline[].key`     | Clave i18n del paso (ej. `initiated`, `in_transit`, `credited`).                                                                                                                                                                                        |
| `timeline[].state`   | `complete` · `in_progress` · `upcoming` · `failed`.                                                                                                                                                                                                     |
| `timeline[].at`      | Timestamp RFC3339 real o `null`. Solo el primer paso (creación) y el paso terminal alcanzado llevan fecha — **los timestamps jamás se fabrican**.                                                                                                       |
| `whats_next`         | Clave i18n contextual que le dice al destinatario qué sigue: `<kind>.done` (ok), `<kind>.failed` (failed) o `<kind>.<paso activo>` mientras avanza (ej. `payout.in_transit`, `crypto_withdrawal.confirming`).                                           |
| `explorer_url`       | URL del explorador blockchain público — solo presente en transacciones crypto con hash on-chain real.                                                                                                                                                   |
| `receipt_pdf_url`    | URL directa del comprobante PDF (mismo código firmado).                                                                                                                                                                                                 |

### Estado público (anti tipping-off)

Los estados internos sensibles se generalizan antes de llegar a la página pública — una operación en revisión de compliance no debe ser distinguible de una que simplemente se está procesando:

| Estado interno                                                              | Estado                | Clase     |
| --------------------------------------------------------------------------- | --------------------- | --------- |
| `in_review`, `held`, `review`, `on_hold`, `compliance_hold`                 | `processing`          | `pending` |
| `completed`, `credited`, `confirmed`, `settled`, `captured`, `approved`     | (estado en minúscula) | `ok`      |
| `failed`, `declined`, `canceled`, `rejected`, `reversed`, `expired`         | (estado en minúscula) | `failed`  |
| Cualquier otro estado transitorio (`pending`, `matched`, `broadcasting`, …) | (estado en minúscula) | `pending` |

### Secuencias de la línea de tiempo

La secuencia de pasos es **fija por familia de operación** — el destinatario siempre ve los mismos pasos para el mismo producto:

| Familia                                          | Pasos                                                     |
| ------------------------------------------------ | --------------------------------------------------------- |
| Payout                                           | `initiated` → `processing` → `in_transit` → `completed`   |
| Payin — transferencia anunciada                  | `initiated` → `matched` → `processing` → `completed`      |
| Payin — QR / collect / tarjeta / cuenta dedicada | `initiated` → `processing` → `completed`                  |
| Devolución de cobro                              | `initiated` → `processing` → `completed`                  |
| Transferencia interna · conversión               | `initiated` → `completed`                                 |
| Retiro crypto / envío desde wallet               | `initiated` → `broadcasting` → `confirming` → `completed` |
| Depósito crypto                                  | `detected` → `confirming` → `credited`                    |
| Operación banking                                | `initiated` → `processing` → `completed`                  |
| Compra con tarjeta                               | `authorized` → `settled`                                  |

Mientras la operación avanza, los pasos anteriores al actual quedan `complete`, el actual `in_progress` y el resto `upcoming`. Cuando una operación **falla**, el paso donde se detuvo queda `failed`, los anteriores quedan `complete` y los posteriores `upcoming`. Cuando **se completa**, todos los pasos quedan `complete`.

## Endpoint del comprobante PDF

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -OJ "https://api.qbank.cl/platform/v1/public/track/P9b1deb4d.../receipt.pdf?lang=zh"
```

Devuelve el comprobante PDF brandeado (`Content-Type: application/pdf`, `Content-Disposition: attachment`), generado al vuelo con el mismo renderer de los endpoints autenticados — incluido el render completo en chino (fuente Noto Sans SC). El endpoint del PDF comparte el mismo rate limit por IP que el JSON.

## Privacidad y seguridad

| Medida                     | Detalle                                                                                                                                                                                                  |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Link = capacidad**       | El `code` está firmado HMAC (80 bits de entropía por transacción). No se puede adivinar ni enumerar, y desbloquea exactamente una transacción.                                                           |
| **Anti-enumeración**       | Un código inválido, malformado o inexistente devuelve siempre el mismo `404` uniforme — un atacante no puede saber si un código existe.                                                                  |
| **Rate limiting**          | Ambos endpoints comparten un throttle por IP; el abuso recibe `429`.                                                                                                                                     |
| **Sin indexación**         | Toda respuesta lleva `X-Robots-Tag: noindex` y `Cache-Control: no-store`; la página misma declara meta tags `noindex`/`nofollow`. Las páginas de seguimiento jamás aparecen en buscadores ni se cachean. |
| **Vista previa neutra**    | Al pegar el link en un chat, la tarjeta de vista previa solo muestra el nombre de tu marca — nunca montos, contrapartes ni estados.                                                                      |
| **Anti tipping-off**       | Los estados de revisión de compliance jamás se muestran públicamente (tabla de arriba).                                                                                                                  |
| **Agnóstico de proveedor** | La página nunca revela qué rieles o proveedores procesan el pago.                                                                                                                                        |

<Warning>
  El link es una capacidad: **quien tenga el link puede ver la transacción**. Compártelo solo con el destinatario previsto, de la misma forma que compartirías el PDF del comprobante.
</Warning>

## Idiomas

La página y el PDF son completamente trilingües — **inglés, español y chino simplificado**:

* Resolución (cadena pública): `?lang=` / `?locale=` (un valor inválido se ignora, nunca 400), luego cookie del pagador `cbpay_pay_locale` (HttpOnly=false, Secure, SameSite=Lax, 30 días), luego la cuenta del comercio, luego el `default_locale` de la org, luego Accept-Language, luego inglés. La cookie SSR del portal `cbpay_lang` es otra cookie del front: la API no la lee.
* La API JSON traduce las etiquetas de `fields`/`amounts` en el servidor con el mismo parámetro.
* El PDF se renderiza completamente en el idioma pedido, chino incluido.

## Endpoint legacy de verificación (sin cambios)

`GET /platform/v1/verify/receipts/{code}` **sigue vivo** — no hay nada que migrar:

* Un **navegador** que lo abre (request con `Accept: text/html`) recibe un redirect `302` a la página del tracker.
* Un **cliente API** recibe el mismo payload JSON de siempre.

Los comprobantes nuevos y los emails de comprobante generan URLs del tracker directamente; los comprobantes emitidos antes de este cambio siguen funcionando para siempre a través del redirect.

## Errores

| HTTP | `error`             | Cuándo                                                                   | Qué hacer                                                                                                   |
| ---- | ------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| 404  | `not_found`         | El código es inválido, está malformado o no existe (uniforme por diseño) | Vuelve a copiar el link completo desde el comprobante o el email — un código truncado es la causa habitual. |
| 429  | `too_many_attempts` | Se alcanzó el rate limit por IP                                          | Espera un momento y reintenta; no consultes el endpoint en un loop.                                         |

Revisa la [referencia de errores](/es/errores) para el shape global de errores.

## FAQ

<AccordionGroup>
  <Accordion title="¿Necesito una API key o iniciar sesión para abrir un link de seguimiento?">
    No. El código firmado en la URL es la credencial. Eso es lo que hace seguro compartir el link con tu cliente por email, chat o SMS.
  </Accordion>

  <Accordion title="¿Puede alguien enumerar transacciones adivinando códigos?">
    No. Los códigos están firmados con HMAC con 80 bits de entropía por transacción, los códigos inválidos son indistinguibles de los válidos-inexistentes (404 uniforme) y el rate limiting por IP bloquea la fuerza bruta.
  </Accordion>

  <Accordion title="¿Por qué una transacción muestra 'processing' más tiempo del habitual?">
    `processing` cubre todos los estados transitorios, incluida la revisión interna de compliance. La página pública intencionalmente no distingue estados de revisión — la operación pasará a `completed` o `failed` cuando se resuelva.
  </Accordion>

  <Accordion title="¿La línea de tiempo muestra alguna vez fechas estimadas?">
    Nunca. Solo se renderizan timestamps reales: cuándo se creó la transacción y cuándo alcanzó su paso terminal actual. Los pasos intermedios no muestran fecha.
  </Accordion>

  <Accordion title="¿El link legacy /verify/receipts que ya integré se va a romper?">
    No. Sigue devolviendo JSON para clientes API y ahora redirige los navegadores a la página del tracker. Ambos comportamientos son permanentes.
  </Accordion>

  <Accordion title="¿Puedo ocultar el tracker y quedarme solo con la verificación JSON?">
    El tracker es la cara pública del mismo código firmado y está habilitado para toda la plataforma. Si prefieres no exponer la página hospedada, simplemente no compartas la URL — el endpoint JSON sigue funcionando igual.
  </Accordion>
</AccordionGroup>

<CardGroup cols={2}>
  <Card title="Comprobantes" icon="receipt" href="/es/guias/comprobantes">
    Cómo se generan los comprobantes, su layout PDF y el QR de verificación.
  </Card>

  <Card title="Errores" icon="triangle-exclamation" href="/es/errores">
    Catálogo global de errores y shape de respuesta.
  </Card>
</CardGroup>
