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

# Revelado de tarjeta

> Monta un visor alojado por Fluz en tu página para mostrar al usuario su PAN, vencimiento y CVV, sin que los datos toquen tus servidores ni el JavaScript de tu página.

<Note>
  Esta página asume que ya leíste el [Resumen de Secure Elements](/build-a-platform/secure-elements-overview): cubre la emisión de un token de cliente, la carga del SDK y el estilo de los campos, todo lo cual aplica aquí.
</Note>

## Verlo en vivo

<iframe
  src="https://demo.secure.fluz.app/"
  title="Fluz Secure Elements — Demo de revelado de tarjeta"
  loading="lazy"
  style={{
width: "100%",
height: "720px",
border: "1px solid #e5e5e5",
borderRadius: "8px",
}}
/>

La demo emite su propio token y monta el visor automáticamente. Usa **Remint token & remount** si los botones de revelado dejan de responder, y **Reveal**, **Reveal CVV only** o **Mask** para probar los controles a nivel de campo descritos abajo. [Ábrela en su propia pestaña →](https://demo.secure.fluz.app/)

## Emitir un token de revelado

Llama a [`POST /v1/client-token`](/build-a-platform/secure-elements-overview#mint-a-client-token) con `"purpose": "reveal"` y el `virtualCardId` que quieres mostrar:

```json theme={null}
{
  "purpose": "reveal",
  "virtualCardId": "c107e50b-10f3-449c-92c0-609d9a8cfa2a"
}
```

Pasa el `clientToken` y `loadToken` que devuelve directamente a `createCardViewer` abajo.

## Crear el visor

```js theme={null}
const viewer = createCardViewer({
  clientToken,
  loadToken,
  frameHostOrigin: "https://staging.secure.fluz.app",
  fields: ["pan", "expiry", { field: "cvv", individualReveal: false }],
});
```

`fields` controla qué partes de la tarjeta se renderizan y en qué orden; si lo omites obtendrás `["pan", "expiry", "cvv"]`. Cada entrada es un nombre de campo simple o un objeto `{ field, individualReveal }`; `"pan"` es una forma abreviada de `{ field: "pan", individualReveal: true }`. `pan`, `expiry` y `cvv` son los únicos nombres de campo válidos: cualquier otro lanza un `FluzElementsError` (`error.code === "INVALID_FIELD"`) de forma síncrona, desde `createCardViewer`, antes de que llames a `mount()`.

| Campo    | Marcador enmascarado, antes de cualquier revelado | Notas                                                                                 |
| :------- | :------------------------------------------------ | :------------------------------------------------------------------------------------ |
| `pan`    | `•••• •••• •••• {last4}`                          | Los últimos 4 dígitos provienen de los metadatos de la tarjeta en tu token de cliente |
| `expiry` | El `MM/YYYY` real — no enmascarado                | No se trata como sensible                                                             |
| `cvv`    | `•••`                                             | Soporta `individualReveal: false` (abajo)                                             |

<Note>
  `individualReveal` es `true` por defecto en cada campo. Configurarlo en `false` impide que ese campo se revele por sí solo; consulta [Revelar un solo campo](#revelar-un-solo-campo). Es independiente de `reveal()`, que siempre revela todos los campos sin importar este ajuste.
</Note>

## Montarlo

```js theme={null}
await viewer.mount(document.getElementById("card-viewer"));
```

`mount()` agrega un iframe sandbox por cada campo configurado dentro del elemento contenedor que le pases: tres iframes separados para los `fields` por defecto, no un solo iframe combinado, y devuelve una promesa que se resuelve cuando cada iframe completa su handshake. Rechaza con un `FluzElementsError` si:

* el `style` que pasaste a `createCardViewer` falla la validación (`error.code === "INVALID_STYLE"`) — ver [Estilo de campos](/build-a-platform/secure-elements-overview#styling-fields)
* un iframe no completa su handshake dentro de `mountTimeoutMs` (`error.code === "MOUNT_TIMEOUT"`; por defecto 10 segundos, configurable vía `createCardViewer({ ..., mountTimeoutMs })`)
* un iframe no carga en absoluto, o este visor ya está montado (`error.code === "MOUNT_FAILED"`) — cada instancia de `CardViewer` solo puede montarse una vez; crea una nueva con `createCardViewer` si necesitas montar de nuevo

## Revelar y enmascarar campos

```js theme={null}
await viewer.reveal(); // fetch and show every configured field at once
await viewer.reveal("cvv"); // fetch and show one field, if that field allows it
viewer.setMask("cvv", true); // re-mask a field that's already been revealed
viewer.setMask("cvv", false); // un-mask it again -- see below
```

* **`reveal(field?)`** — asíncrona. Obtiene el valor real desde Fluz y lo muestra. Sin argumento, obtiene y muestra todos los campos configurados sin importar `individualReveal`. Con un nombre de campo, obtiene y muestra solo ese — a menos que ese campo haya sido configurado con `individualReveal: false`, en cuyo caso rechaza con `error.code === "INDIVIDUAL_REVEAL_DISABLED"`. Un visor no montado, o un nombre de campo que no esté en `fields`, rechaza con `MOUNT_FAILED`.
* **`setMask(field, masked, options?)`** — síncrona, no asíncrona. Nunca obtiene nada; solo alterna lo que se muestra actualmente:
  * `setMask(field, true)` vuelve a enmascarar el campo a su marcador, se haya revelado o no.
  * `setMask(field, false)` lo desenmascara, pero solo muestra el valor real si `reveal()` ya obtuvo uno para ese campo. Llámalo antes de cualquier `reveal()` y el campo se quedará en su marcador, ya que aún no hay un valor obtenido que mostrar.
  * `setMask(field, true, { hidden: true })` deja el campo en blanco por completo (vacío, ni siquiera un marcador) en lugar de mostrar puntos/last4/expiry. `hidden` solo tiene efecto mientras `masked` es `true`.
  * No hay una llamada masiva para "enmascarar todo"; llama a `setMask` una vez por cada campo en `fields` si necesitas reiniciar todo el visor.
* **`destroy()`** — desmonta cada iframe y separa el visor. Llama a esto al desmontar para no dejar iframes montados cuando tu componente desaparezca.

### Revelar un solo campo

Para revelar solo un campo por sí mismo (por ejemplo, un botón "Mostrar CVV" junto a ese campo), llama a `reveal(field)`; cada campo permite esto de forma predeterminada, así que no se necesita configuración para el caso común.

Si un campo no debe revelarse nunca por sí solo, y solo debe aparecer como parte de la llamada `reveal()` de toda la tarjeta, exclúyelo con `{ field, individualReveal: false }` en `fields`:

```js theme={null}
const viewer = createCardViewer({
  clientToken,
  loadToken,
  frameHostOrigin: "https://staging.secure.fluz.app",
  fields: ["pan", "expiry", { field: "cvv", individualReveal: false }],
});

await viewer.reveal("cvv"); // rejects — error.code === "INDIVIDUAL_REVEAL_DISABLED"
await viewer.reveal(); // succeeds — reveals pan, expiry, and cvv together
```

`reveal(field)` contra un campo excluido rechaza con `FluzElementsError` (`code: "INDIVIDUAL_REVEAL_DISABLED"`) sin contactar a `frame-host`. En cualquier caso, `reveal()` sin argumento siempre revela todos los campos montados; `individualReveal` no tiene efecto sobre ella.

<Note>
  Esta es una elección de integración del lado del cliente, no una capacidad aplicada por el servidor: controla lo que tu propia UI puede activar, no qué datos puede devolver el grant. No confíes en `individualReveal: false` como un límite de seguridad.
</Note>

## Manejar eventos

```js theme={null}
const unsubscribeMount = viewer.onMount(() => {
  // all configured fields have finished rendering
});

const unsubscribeError = viewer.onError((error) => {
  // error.code, error.message
});
```

`onMount` se dispara una vez, después de que cada campo configurado haya renderizado dentro del iframe. `onError` se dispara por problemas que ocurren dentro de un iframe ya montado — un `reveal()` fallido o un límite de rate — en lugar de problemas con `mount()` o `createCardViewer()` en sí, que en su lugar rechazan o lanzan directamente (ver abajo). Tanto `onMount` como `onError` devuelven una función para desuscribirse.

Cada `FluzElementsError` que esta capacidad puede producir, y dónde aparece:

| Código                       | Dónde aparece                                                   | Significado                                                                                                               |
| :--------------------------- | :-------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
| `INVALID_FIELD`              | Lanzado por `createCardViewer`                                  | Una entrada de `fields` no es `pan`, `expiry` o `cvv`                                                                     |
| `INVALID_FRAME_HOST_ORIGIN`  | Lanzado por `createCardViewer`                                  | `frameHostOrigin` no es un host de iframes de Fluz reconocido                                                             |
| `INVALID_STYLE`              | Rechazado por `mount()`                                         | Un valor de `style` falló la validación                                                                                   |
| `MOUNT_TIMEOUT`              | Rechazado por `mount()`                                         | El iframe de un campo no completó su handshake dentro de `mountTimeoutMs`                                                 |
| `MOUNT_FAILED`               | Rechazado por `mount()`, o lanzado por `reveal()` / `setMask()` | Un iframe no cargó; el visor ya estaba montado; o `reveal()` / `setMask()` se llamó con un campo no montado o desconocido |
| `INDIVIDUAL_REVEAL_DISABLED` | Rechazado por `reveal(field)`                                   | Ese campo fue configurado con `individualReveal: false`                                                                   |
| `FIELD_ERROR`                | Entregado a `onError`                                           | Un `reveal()` falló dentro del iframe después del montaje (error de red, o revelado aún no disponible)                    |
| `RATE_LIMITED`               | Entregado a `onError`                                           | Demasiados intentos de revelado para este grant — `error.message` incluye el tiempo de reintento                          |

## Ejemplo completo

```html theme={null}
<div id="card-viewer"></div>

<script src="https://secure-cdn-staging.fluz.app/secure-elements/v0.1.0/index.global.js"></script>
<script>
  (async () => {
    const { createCardViewer } = FluzSecureElements;

    const res = await fetch("/mint-reveal-token", { method: "POST" });
    const { clientToken, loadToken } = await res.json();

    const viewer = createCardViewer({
      clientToken,
      loadToken,
      frameHostOrigin: "https://staging.secure.fluz.app",
      fields: ["pan", "expiry", { field: "cvv", individualReveal: false }],
      style: {
        fontFamily: "IBM Plex Mono",
        fontSize: "16px",
        color: "#1a1a1a",
      },
    });

    viewer.onError((error) => console.error(error.code, error.message));
    viewer.onMount(() => console.log("card viewer ready"));

    await viewer.mount(document.getElementById("card-viewer"));
  })();
</script>
```

`/mint-reveal-token` es tu propia ruta de backend: la que llama a `POST /v1/client-token` con tu token de acceso OAuth de Fluz, como se describe en [Emitir un token de cliente](/build-a-platform/secure-elements-overview#mint-a-client-token).

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Resumen de Secure Elements" icon="book-open" href="/build-a-platform/secure-elements-overview">
    Emisión de tokens, carga del SDK, estilos y CSP.
  </Card>

  {" "}

  <Card title="Demo en vivo" icon="play" href="https://demo.secure.fluz.app/">
    Revelar, revelar solo CVV y enmascarar, ejecutándose contra staging.
  </Card>

  <Card title="Integraciones de ejemplo" icon="github" href="https://github.com/fluz-app/secure-elements-examples">
    Ejemplos ejecutables en HTML plano y React para revelado con un servidor que emite tokens.
  </Card>
</CardGroup>
