---
url: 'https://www.corbado.com/es/blog/como-construir-un-verificador-de-credenciales-verificables'
title: 'Cómo construir un verificador de credenciales digitales (Guía para desarrolladores)'
description: 'Aprende a construir un verificador de credenciales digitales desde cero con Next.js, OpenID4VP e ISO mDoc. Esta guía para desarrolladores te muestra paso a paso cómo crear un verificador que puede solicitar, recibir y validar licencias de conducir móviles'
lang: 'es'
author: 'Amine'
date: '2025-08-20T15:39:29.519Z'
lastModified: '2026-03-27T07:04:23.775Z'
keywords: 'verificador de credenciales digitales, tutorial verificador, construir verificador'
category: 'Digital Credentials'
---

# Cómo construir un verificador de credenciales digitales (Guía para desarrolladores)

## 1. Introducción

Probar identidades en línea es un desafío constante que nos ha llevado a depender de
contraseñas y a compartir documentos sensibles por canales inseguros. Esto ha hecho que la
[verificación](https://www.corbado.com/es/blog/verificacion-de-identidad-digital) de identidad para las empresas
sea un proceso lento, costoso y propenso al fraude. Las
[credenciales digitales](https://www.corbado.com/es/blog/digital-credentials-api) ofrecen un nuevo enfoque que
devuelve a los usuarios el control de sus datos. Son el equivalente digital de una
[wallet](https://www.corbado.com/blog/digital-wallet-assurance) física, que contiene desde una licencia de
conducir hasta un título universitario, pero con los beneficios añadidos de ser
criptográficamente seguras, preservar la privacidad y poder verificarse al instante.

Esta guía ofrece a los desarrolladores un
[tutorial](https://www.corbado.com/es/blog/aplicacion-crud-react-express-mysql) práctico y paso a paso para
construir un verificador de [credenciales digitales](https://www.corbado.com/es/blog/digital-credentials-api).
Aunque los estándares existen, hay poca orientación sobre cómo implementarlos. Este
[tutorial](https://www.corbado.com/es/blog/aplicacion-crud-react-express-mysql) llena ese vacío, mostrando cómo
construir un verificador utilizando la API nativa de
[credenciales digitales](https://www.corbado.com/es/blog/digital-credentials-api) del navegador,
[OpenID4VP](https://www.corbado.com/glossary/open-id-4-vp) para el protocolo de presentación e ISO
[mDoc](https://www.corbado.com/glossary/mdoc) (por ejemplo, una
[licencia de conducir móvil](https://www.corbado.com/es/blog/licencia-de-conducir-movil)) como formato de
credencial.

El resultado final será una aplicación [Next.js](https://www.corbado.com/blog/nextjs-passkeys) simple pero
funcional que puede solicitar, recibir y verificar una credencial digital desde una
[wallet](https://www.corbado.com/blog/digital-wallet-assurance) móvil compatible.

Aquí tenemos un vistazo rápido a la aplicación final en acción. El proceso consta de
cuatro pasos principales:

**Paso 1: Página inicial** El usuario llega a la página inicial y hace clic en "Verificar
con [Identidad Digital](https://www.corbado.com/es/blog/digital-credentials-api)" para iniciar el proceso.
![Página inicial para la solicitud de verificación](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Screenshot_2025_07_25_at_11_00_33_5217b35c96.png)

**Paso 2: Solicitud de confianza** El navegador solicita al usuario que confíe en el
sitio. El usuario hace clic en "Continuar" para proceder.
![Solicitud de confianza del navegador](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Screenshot_2025_07_25_at_11_00_39_ba390a8097.png)

**Paso 3: Escaneo de código QR** Se muestra un
[código QR](https://www.corbado.com/es/blog/inicio-sesion-codigo-qr-autenticacion) que el usuario escanea con su
aplicación de [wallet](https://www.corbado.com/blog/digital-wallet-assurance) compatible.
![Código QR para escanear](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Screenshot_2025_07_25_at_11_00_45_3b30669a10.png)

**Paso 4: Credencial decodificada** Tras una
[verificación](https://www.corbado.com/es/blog/verificacion-de-identidad-digital) exitosa, la aplicación muestra
los datos de la credencial decodificada.
![Resultado de la credencial decodificada](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Screenshot_2025_07_25_at_11_01_36_684f7489cd.png)

### 1.1 Cómo funciona

La magia detrás de las credenciales digitales reside en un modelo simple pero potente de
"**triángulo de confianza**" que involucra a tres actores clave:

- **Emisor:** Una autoridad de confianza (por ejemplo, una agencia
  [gubernamental](https://www.corbado.com/passkeys-for-public-sector), una universidad o un banco) que firma
  criptográficamente y emite una credencial a un usuario.
- **Titular:** El usuario, que recibe la credencial y la almacena de forma segura en una
  [billetera digital](https://www.corbado.com/es/blog/garantia-billetera-digital-marcos-ue-eeuu-australia)
  personal en su dispositivo.
- **Verificador:** Una aplicación o servicio que necesita comprobar la credencial del
  usuario.

![Ecosistema de Credenciales Verificables del W3C](https://www.w3.org/TR/vc-data-model/diagrams/ecosystem.svg)

Cuando un usuario quiere acceder a un servicio, presenta la credencial desde su wallet. El
verificador puede entonces comprobar instantáneamente su autenticidad sin necesidad de
contactar directamente al emisor original.

### 1.2 Por qué los verificadores son esenciales (y por qué estás aquí)

Para que este **ecosistema de identidad descentralizada** prospere, el papel del
**verificador** es absolutamente crucial. Son los guardianes de esta nueva
[infraestructura](https://www.corbado.com/passkeys-for-critical-infrastructure) de confianza, los que consumen
las credenciales y las hacen útiles en el mundo real. Como ilustra el siguiente diagrama,
un verificador completa el triángulo de confianza solicitando, recibiendo y validando una
credencial del titular.

Si eres desarrollador, construir un servicio para realizar esta
[verificación](https://www.corbado.com/es/blog/verificacion-de-identidad-digital) es una habilidad fundamental
para la próxima generación de aplicaciones seguras y centradas en el usuario. Esta guía
está diseñada para guiarte exactamente a través de ese proceso. Cubriremos todo lo que
necesitas saber para **construir tu propio verificador de credenciales verificables**,
desde los conceptos y estándares básicos hasta los detalles de implementación paso a paso
para validar firmas y comprobar el estado de las credenciales.

> **¿Quieres ir directamente al final?** Puedes encontrar el proyecto completo y terminado
> de este [tutorial](https://www.corbado.com/es/blog/aplicacion-crud-react-express-mysql) en GitHub. Siéntete
> libre de clonarlo y probarlo tú mismo:
> [https://github.com/corbado/digital-credentials-example](https://github.com/corbado/digital-credentials-example)

Empecemos.

## 2. Prerrequisitos para construir un verificador

Antes de empezar, asegúrate de tener:

1. **Conocimientos básicos de credenciales digitales y mdoc**
    - Este tutorial se centra en el formato **ISO mDoc** (por ejemplo, para licencias de
      conducir móviles) y no cubre otros formatos como las credenciales verificables (VC)
      del W3C. Estar familiarizado con los conceptos básicos de [mdoc](https://www.corbado.com/glossary/mdoc)
      será útil.
2. **Docker y Docker Compose**
    - Nuestro proyecto utiliza una base de datos
      [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) en un contenedor de Docker para
      gestionar el estado de la sesión OIDC. Asegúrate de tener ambos instalados y en
      funcionamiento.
3. **Protocolo elegido: OpenID4VP**
    - Usaremos el protocolo **OpenID4VP** (OpenID for Verifiable Presentations) para el
      flujo de intercambio de credenciales.
4. **Pila tecnológica lista**
    - Usaremos **TypeScript** (Node.js) para la lógica del backend.
    - Usaremos **Next.js** tanto para el backend (rutas de API) como para el frontend
      (UI).
    - Bibliotecas clave: bibliotecas de decodificación [CBOR](https://www.corbado.com/glossary/cbor) para el
      análisis de [mdoc](https://www.corbado.com/glossary/mdoc) y un cliente de
      [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide).
5. **Credenciales de prueba y wallet**
    - Usaremos la
      **[CMWallet](https://github.com/digitalcredentialsdev/CMWallet/actions/runs/16407676816/artifacts/3574255220)**
      para [Android](https://www.corbado.com/blog/how-to-enable-passkeys-android), que entiende las solicitudes
      [OpenID4VP](https://www.corbado.com/glossary/open-id-4-vp) y puede presentar credenciales mdoc.
6. **Conocimientos básicos de criptografía**
    - Entender las firmas digitales y los conceptos de clave pública/privada en relación
      con los flujos de mdoc y OIDC.

---

Ahora repasaremos cada uno de estos prerrequisitos en detalle, empezando por los
estándares y protocolos que sustentan este verificador basado en mdoc.

### 2.1 Elección de protocolos

Nuestro verificador está construido para lo siguiente:

| Estándar / Protocolo                                                                      | Descripción                                                                                                                                                                                                          |
| :---------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **[W3C VC](https://www.w3.org/TR/vc-data-model/)**                                        | El modelo de datos de credenciales verificables del W3C. Define la estructura estándar para las credenciales digitales, incluyendo declaraciones (claims), metadatos y pruebas.                                      |
| **[SD-JWT](https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/)** | Divulgación selectiva para JWT. Un formato para VCs basado en JSON Web Tokens que permite a los titulares divulgar selectivamente solo declaraciones específicas de una credencial, mejorando la privacidad.         |
| **[ISO mDoc](https://www.iso.org/standard/69084.html)**                                   | ISO/IEC 18013-5. El estándar internacional para licencias de conducir móviles (mDL) y otras identificaciones móviles, que define estructuras de datos y protocolos de comunicación para uso en línea y sin conexión. |
| **OpenID4VP**                                                                             | OpenID para presentaciones verificables. Un protocolo de presentación interoperable construido sobre OAuth 2.0. Define cómo un verificador solicita credenciales y cómo la wallet del titular las presenta.          |

Para este tutorial, usamos específicamente:

- **OpenID4VP** como el protocolo para solicitar y recibir credenciales.
- **ISO mDoc** como el formato de credencial (por ejemplo, para licencias de conducir
  móviles).

> **Nota sobre el alcance:** Aunque introducimos brevemente W3C VC y
> SD-[JWT](https://www.corbado.com/es/glossary/jwks) para proporcionar un contexto más amplio, este tutorial
> implementa exclusivamente credenciales ISO mDoc a través de
> [OpenID4VP](https://www.corbado.com/glossary/open-id-4-vp). Las VCs basadas en W3C están fuera del alcance de
> este ejemplo.

#### 2.1.1 ISO mDoc (Mobile Document)

El estándar ISO/IEC 18013-5 mDoc define la estructura y codificación para documentos
móviles como las licencias de conducir móviles (mDL). Las credenciales mDoc están
codificadas en [CBOR](https://www.corbado.com/glossary/cbor), firmadas criptográficamente y pueden presentarse
digitalmente para su verificación. Nuestro verificador se centrará en decodificar y
validar estas credenciales mdoc.

#### 2.1.2 OpenID4VP (OpenID for Verifiable Presentations)

OpenID4VP es un protocolo interoperable para solicitar y presentar credenciales digitales,
construido sobre [OAuth 2.0](https://www.corbado.com/glossary/oauth2) y OpenID Connect. En esta implementación,
OpenID4VP se utiliza para:

- Iniciar el flujo de presentación de credenciales (a través de un
  [código QR](https://www.corbado.com/es/blog/inicio-sesion-codigo-qr-autenticacion) o API del navegador)
- Recibir la credencial mdoc de la wallet del usuario
- Garantizar un intercambio de credenciales seguro, con estado y que preserva la
  privacidad

### 2.2 Elección de la pila tecnológica

Ahora que tenemos una comprensión clara de los estándares y protocolos, necesitamos elegir
la pila tecnológica adecuada para construir nuestro verificador. Nuestras elecciones están
diseñadas para la robustez, la experiencia del desarrollador y la compatibilidad con el
ecosistema web moderno.

#### 2.2.1 Lenguaje: TypeScript

Usaremos **TypeScript** tanto para nuestro código de frontend como de backend. Como
superconjunto de JavaScript, añade tipado estático, lo que ayuda a detectar errores
temprano, mejora la calidad del código y facilita la gestión de aplicaciones complejas. En
un contexto sensible a la [seguridad](https://www.corbado.com/es/blog/como-eliminar-contrasenas-por-completo)
como la verificación de credenciales, la
[seguridad](https://www.corbado.com/es/blog/como-eliminar-contrasenas-por-completo) de tipos es una gran ventaja.

#### 2.2.2 Framework: Next.js

**Next.js** es nuestro framework de elección porque proporciona una experiencia fluida e
integrada para construir aplicaciones
[full-stack](https://www.corbado.com/es/blog/aplicacion-crud-react-express-mysql).

- **Para el frontend:** Usaremos [Next.js](https://www.corbado.com/blog/nextjs-passkeys) con
  [React](https://www.corbado.com/blog/react-passkeys) para construir la interfaz de usuario donde se inicia el
  proceso de verificación (por ejemplo, mostrando un
  [código QR](https://www.corbado.com/es/blog/inicio-sesion-codigo-qr-autenticacion)).
- **Para el backend:** Aprovecharemos las **rutas de API de Next.js** para crear los
  endpoints del lado del servidor. Estos endpoints son responsables de crear solicitudes
  OpenID4VP válidas y de actuar como `redirect_uri` para recibir y verificar de forma
  segura la respuesta final de la CMWallet.

#### 2.2.3 Bibliotecas clave

Nuestra implementación se basa en un conjunto específico de bibliotecas para el frontend y
el backend:

- **next**: El framework [Next.js](https://www.corbado.com/blog/nextjs-passkeys), utilizado tanto para las rutas
  de API del backend como para la UI del frontend.
- **react** y **react-dom**: Impulsan la interfaz de usuario del frontend.
- **cbor-web**: Se utiliza para decodificar credenciales mdoc codificadas en
  [CBOR](https://www.corbado.com/glossary/cbor) en objetos JavaScript utilizables.
- **mysql2**: Proporciona conectividad a la base de datos
  [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) para almacenar desafíos y sesiones de
  verificación.
- **uuid**: Una biblioteca para generar cadenas de desafío (nonces) únicas.
- **@types/uuid**: Tipos de TypeScript para la generación de UUID.

> **Nota sobre `openid-client`:** Los verificadores más avanzados y de grado de producción
> podrían usar la biblioteca `openid-client` para manejar el protocolo OpenID4VP
> directamente en el backend, habilitando características como una `redirect_uri`
> dinámica. En un flujo OpenID4VP impulsado por el servidor con una `redirect_uri`,
> `openid-client` se usaría para analizar y validar las respuestas `vp_token`
> directamente. Para este tutorial, estamos utilizando un flujo más simple, mediado por el
> navegador, que no lo requiere, lo que facilita la comprensión del proceso.

Esta pila tecnológica garantiza una implementación de verificador robusta, con
[seguridad](https://www.corbado.com/es/blog/como-eliminar-contrasenas-por-completo) de tipos y escalable,
centrada en la [API de credenciales digitales](https://www.corbado.com/es/blog/digital-credentials-api) del
navegador y el formato de credencial ISO mDoc.

### 2.3 Obtener una wallet y credenciales de prueba

Para probar tu verificador, necesitas una wallet móvil que pueda interactuar con la API de
credenciales digitales del navegador.

Usaremos la
**[CMWallet](https://github.com/digitalcredentialsdev/CMWallet/actions/runs/16407676816/artifacts/3574255220)**,
una robusta wallet de prueba compatible con OpenID4VP para
[Android](https://www.corbado.com/blog/how-to-enable-passkeys-android).

**Cómo instalar CMWallet (Android):**

1. **Descarga el archivo APK** usando el enlace de arriba directamente en tu dispositivo
   [Android](https://www.corbado.com/blog/how-to-enable-passkeys-android).
2. Abre **Ajustes > Seguridad** en tu dispositivo.
3. Habilita **"Instalar aplicaciones desconocidas"** para el navegador que usaste para
   descargar el archivo.
4. Localiza el APK descargado en tu carpeta "Descargas" y tócalo para comenzar la
   instalación.
5. Sigue las instrucciones en pantalla para completar la instalación.
6. Abre CMWallet y la encontrarás precargada con credenciales de prueba, lista para el
   flujo de verificación.

> **Nota:** Solo instala archivos APK de fuentes en las que confíes. El enlace
> proporcionado es del repositorio oficial del proyecto.

### 2.4 Conocimientos de criptografía

Antes de sumergirnos en la implementación, es esencial comprender los conceptos
criptográficos que sustentan las credenciales verificables. Esto es lo que las hace
"verificables" y confiables.

#### 2.4.1 Firmas digitales: la base de la confianza

En esencia, una credencial verificable es un conjunto de declaraciones (como nombre, fecha
de nacimiento, etc.) que ha sido firmada digitalmente por un emisor. Una firma digital
proporciona dos garantías cruciales:

- **Autenticidad:** Prueba que la credencial fue creada por el emisor y no por un
  impostor.
- **Integridad:** Prueba que la credencial no ha sido alterada o manipulada desde que fue
  firmada.

#### 2.4.2 Criptografía de clave pública/privada

Las firmas digitales se crean utilizando
[criptografía de clave pública](https://www.corbado.com/es/blog/webauthn-pubkeycredparams-credentialpublickey)/privada
(también llamada criptografía asimétrica). Así es como funciona en nuestro contexto:

1. **El emisor tiene un par de claves:** una clave privada, que se mantiene en secreto, y
   una clave pública, que está disponible para todos (generalmente a través de su
   Documento DID).
2. **Firma:** Cuando un emisor crea una credencial, utiliza su **clave privada** para
   generar una firma digital única para esos datos de credencial específicos.
3. **Verificación:** Cuando nuestro verificador recibe la credencial, utiliza la **clave
   pública** del emisor para comprobar la firma. Si la comprobación es exitosa, el
   verificador sabe que la credencial es auténtica y no ha sido manipulada. Cualquier
   cambio en los datos de la credencial invalidaría la firma.

> **Nota sobre los DID:** En este tutorial, no resolvemos las claves del emisor a través
> de DID. En producción, los emisores normalmente expondrían sus claves públicas a través
> de DID u otros endpoints autorizados, que el verificador usaría para la validación
> criptográfica.

#### 2.4.3 Credenciales verificables como JWT

Las credenciales verificables a menudo se formatean como **JSON Web Tokens (JWT)**. Un
[JWT](https://www.corbado.com/es/glossary/jwks) es una forma compacta y segura para URL de representar
declaraciones que se transferirán entre dos partes. Un [JWT](https://www.corbado.com/es/glossary/jwks) firmado
(también conocido como JWS) tiene tres partes separadas por puntos (`.`):

- **Encabezado:** Contiene metadatos sobre el token, como el algoritmo de firma utilizado
  (`alg`).
- **Carga útil (Payload):** Contiene las declaraciones reales de la credencial verificable
  (declaración `vc`), incluyendo el `issuer`, `credentialSubject`, etc.
- **Firma:** La firma digital generada por el emisor, que cubre el encabezado y la carga
  útil.

```
// Ejemplo de una estructura JWT
[Encabezado].[Carga útil].[Firma]
```

> **Nota:** Las credenciales verificables basadas en JWT están fuera del alcance de esta
> publicación de blog. Esta implementación se centra en las credenciales ISO mDoc y
> OpenID4VP, no en las credenciales verificables W3C o las credenciales basadas en JWT.

#### 2.4.4 La presentación verificable: probar la posesión

No es suficiente que un verificador sepa que una credencial es válida; también necesita
saber que la persona que _presenta_ la credencial es el titular legítimo. Esto evita que
alguien use una credencial robada.

Esto se soluciona usando una **Presentación Verificable (VP)**. Una VP es un envoltorio
alrededor de una o más VCs que está **firmado por el propio titular**.

El flujo es el siguiente:

1. El verificador le pide al usuario que presente una credencial.
2. La wallet del usuario crea una Presentación Verificable, empaqueta las credenciales
   requeridas dentro de ella y firma la presentación completa usando la **clave privada
   del titular**.
3. La wallet envía esta VP firmada al verificador.

Nuestro verificador debe entonces realizar **dos** comprobaciones de firma separadas:

1. **Verificar la(s) credencial(es):** Comprobar la firma en cada VC dentro de la
   presentación usando la **clave pública del emisor**. (Prueba que la credencial es
   real).
2. **Verificar la presentación:** Comprobar la firma en la propia VP usando la **clave
   pública del titular**. (Prueba que la persona que la presenta es el propietario).

Esta comprobación de dos niveles garantiza tanto la autenticidad de la credencial como la
identidad de la persona que la presenta, creando un modelo de confianza robusto y seguro.

> **Nota:** El concepto de presentaciones verificables tal como se define en el ecosistema
> W3C VC está fuera del alcance de esta publicación de blog. El término Presentación
> Verificable aquí se refiere a la respuesta `vp_token` de OpenID4VP, que se comporta de
> manera similar a una VP de W3C pero se basa en la semántica de ISO mDoc en lugar del
> modelo de firma [JSON-LD](https://www.corbado.com/glossary/json-ld) de W3C. Esta guía se centra en las
> credenciales ISO mDoc y OpenID4VP, no en las presentaciones verificables W3C ni en su
> validación de firma.

## 3. Descripción general de la arquitectura

Nuestra arquitectura de verificador utiliza la **API de credenciales digitales** integrada
del navegador como un intermediario seguro para conectar nuestra aplicación web con la
**CMWallet** móvil del usuario. Este enfoque simplifica el flujo al permitir que el
navegador maneje la visualización nativa del código QR y la comunicación con la wallet.

- **Frontend (Next.js y React):** Un sitio web ligero de cara al usuario. Su trabajo es
  obtener un objeto de solicitud de nuestro backend, pasarlo a la API
  `navigator.credentials.get()` del navegador, recibir el resultado y reenviarlo a nuestro
  backend para su verificación.
- **Backend (rutas de API de Next.js):** El motor del verificador. Genera un objeto de
  solicitud válido para la API del navegador y expone un endpoint para recibir la
  presentación de la credencial desde el frontend para la validación final.
- **Navegador (API de credenciales):** El facilitador. Recibe el objeto de solicitud de
  nuestro frontend, entiende el protocolo `openid4vp` y genera un código QR de forma
  nativa. Luego espera a que la wallet devuelva una respuesta.
- **CMWallet (aplicación móvil):** La wallet del usuario. Escanea el código QR, procesa la
  solicitud, obtiene el consentimiento del usuario y envía la respuesta firmada de vuelta
  al navegador.

Aquí hay un diagrama de secuencia que ilustra el flujo completo y preciso:

![Flujo de verificación usando la API de credenciales digitales del navegador](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Mermaid_Chart_Create_complex_visual_diagrams_with_text_A_smarter_way_of_creating_diagrams_2025_07_24_233623_1b6ed9b957.svg)

**Explicación del flujo:**

1. **Iniciación:** El usuario hace clic en el botón "Verificar" en nuestro **Frontend**.
2. **Objeto de solicitud:** El frontend llama a nuestro **Backend** (`/api/verify/start`),
   que genera un objeto de solicitud que contiene la consulta y un nonce, y luego lo
   devuelve.
3. **Llamada a la API del navegador:** El frontend llama a `navigator.credentials.get()`
   con el objeto de solicitud.
4. **Código QR nativo:** El **Navegador** ve la solicitud del protocolo `openid4vp` y
   muestra un código QR de forma nativa. La promesa `.get()` ahora está pendiente.

> **Nota:** Este flujo de código QR ocurre en navegadores de escritorio. En navegadores
> móviles (Android Chrome con la bandera experimental habilitada), el navegador puede
> comunicarse directamente con wallets compatibles en el mismo dispositivo, eliminando la
> necesidad de escanear un código QR. Para habilitar esta función en Android Chrome,
> navega a `chrome://flags#web-identity-digital-credentials` y establece la bandera en
> "Enabled".

5. **Escanear y presentar:** El usuario escanea el código QR con **CMWallet**. La wallet
   obtiene la aprobación del usuario y envía la Presentación Verificable de vuelta al
   navegador.
6. **Resolución de la promesa:** El navegador recibe la respuesta, y la promesa original
   `.get()` en el frontend finalmente se resuelve, entregando la carga útil de la
   presentación.
7. **Verificación del backend:** El frontend envía la carga útil de la presentación
   mediante **POST** al endpoint `/api/verify/finish` de nuestro backend. El backend
   valida el nonce y la credencial.
8. **Resultado:** El backend devuelve un mensaje final de éxito o fracaso al frontend, que
   actualiza la interfaz de usuario.

## 4. Construyendo el verificador

Ahora que tenemos una sólida comprensión de los estándares, protocolos y el flujo
arquitectónico, podemos empezar a construir nuestro verificador.

> **Sigue el paso a paso o usa el código final**
>
> Ahora repasaremos la configuración y la implementación del código paso a paso. Si
> prefieres saltar directamente al producto terminado, puedes clonar el proyecto completo
> desde nuestro repositorio de GitHub y ejecutarlo localmente.
>
> ```bash
> git clone https://github.com/corbado/digital-credentials-example.git
> ```

### 4.1 Configurando el proyecto

Primero, inicializaremos un nuevo proyecto de Next.js, instalaremos las dependencias
necesarias y pondremos en marcha nuestra base de datos.

#### 4.1.1 Inicializando la aplicación Next.js

Abre tu terminal, navega al directorio donde quieres crear tu proyecto y ejecuta el
siguiente comando. Estamos usando el App Router, TypeScript y Tailwind CSS para este
proyecto.

```bash
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
```

Este comando crea una nueva aplicación Next.js en tu directorio actual.

#### 4.1.2 Instalando dependencias

A continuación, necesitamos instalar las bibliotecas que se encargarán de la
decodificación CBOR, las conexiones a la base de datos y la generación de UUID.

```bash
npm install cbor-web mysql2 uuid @types/uuid
```

Este comando instala:

- `cbor-web`: Para decodificar la carga útil de la credencial mdoc.
- `mysql2`: El cliente de MySQL para nuestra base de datos.
- `uuid`: Para generar cadenas de desafío únicas.
- `@types/uuid`: Tipos de TypeScript para la biblioteca `uuid`.

#### 4.1.3 Poniendo en marcha la base de datos

Nuestro backend requiere una base de datos MySQL para almacenar los datos de la sesión
OIDC, asegurando que cada flujo de verificación sea seguro y con estado. Hemos incluido un
archivo `docker-compose.yml` para facilitar esto.

Si has clonado el repositorio, puedes simplemente ejecutar `docker-compose up -d`. Si
estás construyendo desde cero, crea un archivo llamado `docker-compose.yml` con el
siguiente contenido:

```yaml
services:
    mysql:
        image: mysql:8.0
        restart: always
        environment:
            MYSQL_ROOT_PASSWORD: rootpassword
            MYSQL_DATABASE: digital_credentials
            MYSQL_USER: app_user
            MYSQL_PASSWORD: app_password
        ports:
            - "3306:3306"
        volumes:
            - mysql_data:/var/lib/mysql
            - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
        healthcheck:
            test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
            timeout: 20s
            retries: 10

volumes:
    mysql_data:
```

Esta configuración de Docker Compose también requiere un script de inicialización SQL.
Crea un directorio llamado `sql` y dentro de él, un archivo llamado `init.sql` con el
siguiente contenido para configurar las tablas necesarias:

```sql
-- Create database if not exists
CREATE DATABASE IF NOT EXISTS digital_credentials;
USE digital_credentials;

-- Table for storing challenges
CREATE TABLE IF NOT EXISTS challenges (
    id VARCHAR(36) PRIMARY KEY,
    challenge VARCHAR(255) NOT NULL UNIQUE,
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    used BOOLEAN DEFAULT FALSE,
    INDEX idx_challenge (challenge),
    INDEX idx_expires_at (expires_at)
);

-- Table for storing verification sessions
CREATE TABLE IF NOT EXISTS verification_sessions (
    id VARCHAR(36) PRIMARY KEY,
    challenge_id VARCHAR(36),
    status ENUM('pending', 'verified', 'failed', 'expired') DEFAULT 'pending',
    presentation_data JSON,
    verified_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (challenge_id) REFERENCES challenges(id) ON DELETE CASCADE,
    INDEX idx_challenge_id (challenge_id),
    INDEX idx_status (status)
);

-- Table for storing verified credentials data (optional)
CREATE TABLE IF NOT EXISTS verified_credentials (
    id VARCHAR(36) PRIMARY KEY,
    session_id VARCHAR(36),
    credential_type VARCHAR(255),
    issuer VARCHAR(255),
    subject VARCHAR(255),
    claims JSON,
    verified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (session_id) REFERENCES verification_sessions(id) ON DELETE CASCADE,
    INDEX idx_session_id (session_id),
    INDEX idx_credential_type (credential_type)
);
```

Una vez que ambos archivos estén en su lugar, abre tu terminal en la raíz del proyecto y
ejecuta:

```bash
docker-compose up -d
```

Este comando iniciará un contenedor de MySQL en segundo plano.

### 4.2 Descripción general de la arquitectura de la aplicación Next.js

Nuestra aplicación Next.js está estructurada para separar las responsabilidades entre el
frontend y el backend, aunque forman parte del mismo proyecto.

- **Frontend (`src/app/page.tsx`):** Una única página de [React](https://www.corbado.com/blog/react-passkeys) que
  inicia el flujo de verificación y muestra el resultado. Interactúa con la
  [API de credenciales digitales](https://www.corbado.com/es/blog/digital-credentials-api) del navegador.
- **Rutas de API del backend (`src/app/api/verify/...`):**
    - `start/route.ts`: Genera la solicitud OpenID4VP y un nonce de seguridad.
    - `finish/route.ts`: Recibe la presentación de la wallet (a través del navegador),
      valida el nonce y decodifica la credencial.
- **Biblioteca (`src/lib/`):**
    - `database.ts`: Gestiona todas las interacciones con la base de datos (creación de
      desafíos, verificación de sesiones).
    - `crypto.ts`: Se encarga de la decodificación de la credencial mDoc basada en CBOR.

Aquí hay un diagrama que ilustra la arquitectura interna:

![Arquitectura interna de NextJS](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Mermaid_Chart_Create_complex_visual_diagrams_with_text_A_smarter_way_of_creating_diagrams_2025_07_25_091202_f96ccb049f.svg)

### 4.3 Construyendo el frontend

Nuestro frontend es intencionadamente ligero. Su responsabilidad principal es actuar como
el disparador de cara al usuario para el flujo de verificación y comunicarse tanto con
nuestro backend como con las capacidades nativas de manejo de credenciales del navegador.
No contiene ninguna lógica de protocolo compleja; todo eso se delega.

Específicamente, el frontend se encargará de lo siguiente:

- **Interacción del usuario:** Proporciona una interfaz simple, como un botón "Verificar",
  para que el usuario inicie el proceso.
- **Gestión de estado:** Gestiona el estado de la UI, mostrando indicadores de carga
  mientras la verificación está en progreso y mostrando el mensaje final de éxito o error.
- **Comunicación con el backend (Solicitud):** Llama a `/api/verify/start` y recibe una
  carga útil JSON estructurada (`protocol`, `request`, `state`) que describe exactamente
  lo que la wallet debe presentar.
- **Invocación de la API del navegador:** Pasa ese objeto JSON a
  `navigator.credentials.get()`, que renderiza un código QR nativo y espera la respuesta
  de la wallet.
- **Comunicación con el backend (Respuesta):** Una vez que la API del navegador devuelve
  la presentación verificable, envía estos datos a nuestro endpoint `/api/verify/finish`
  en una solicitud POST para la validación final del lado del servidor.
- **Mostrar resultados:** Actualiza la UI para informar al usuario si la verificación fue
  exitosa o falló, basándose en la respuesta del backend.

La lógica principal está en la función `startVerification`:

```typescript
// src/app/page.tsx

const startVerification = async () => {
    setLoading(true);
    setVerificationResult(null);

    try {
        // 1. Comprobar si el navegador soporta la API
        if (!navigator.credentials?.get) {
            throw new Error("El navegador no soporta la API de credenciales.");
        }

        // 2. Pedir a nuestro backend un objeto de solicitud
        const res = await fetch("/api/verify/start");
        const { protocol, request } = await res.json();

        // 3. Pasar ese objeto al navegador – esto activa el código QR nativo
        const credential = await (navigator.credentials as any).get({
            mediation: "required",
            digital: {
                requests: [
                    {
                        protocol, // "openid4vp"
                        data: request, // contiene dcql_query, nonce, etc.
                    },
                ],
            },
        });

        // 4. Reenviar la respuesta de la wallet (desde el navegador) a nuestro endpoint de finalización para las comprobaciones del lado del servidor
        const verifyRes = await fetch("/api/verify/finish", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(credential),
        });

        const result = await verifyRes.json();

        if (verifyRes.ok && result.verified) {
            setVerificationResult(`Éxito: ${result.message}`);
        } else {
            throw new Error(result.message || "La verificación falló.");
        }
    } catch (err) {
        setVerificationResult(`Error: ${(err as Error).message}`);
    } finally {
        setLoading(false);
    }
};
```

Esta función muestra los cuatro pasos clave de la lógica del frontend: comprobar el
soporte de la API, obtener la solicitud del backend, llamar a la API del navegador y
enviar el resultado de vuelta para su verificación. El resto del archivo es código
estándar de [React](https://www.corbado.com/blog/react-passkeys) para el estado y la renderización de la UI, que
puedes ver en el
[repositorio de GitHub](https://github.com/corbado/digital-credentials-example).

#### ¿Por qué `digital` y `mediation: 'required'`?

Puede que notes que nuestra llamada a `navigator.credentials.get()` se ve diferente de
ejemplos más simples. Esto se debe a que nos adherimos estrictamente a la
[especificación oficial de la API de credenciales digitales del W3C](https://www.w3.org/TR/digital-credentials/#the-digital-credentials-api).

- **Miembro `digital`:** La especificación requiere que todas las solicitudes de
  credenciales digitales estén anidadas dentro de un objeto `digital`. Esto proporciona un
  espacio de nombres claro y estandarizado para esta API, distinguiéndola de otros tipos
  de credenciales (como `password` o `federated`) y permitiendo futuras extensiones sin
  conflictos.

- **`mediation: 'required'`:** Esta opción es una característica crucial de seguridad y
  experiencia de usuario. Obliga a que el usuario interactúe activamente con una
  indicación (por ejemplo, un escaneo biométrico, la entrada de un PIN o una pantalla de
  consentimiento) para aprobar la solicitud de credencial. Sin ella, un sitio web podría
  intentar acceder a las credenciales en segundo plano de forma silenciosa, lo que supone
  un riesgo significativo para la privacidad. Al requerir la mediación, nos aseguramos de
  que el usuario siempre tenga el control y dé su consentimiento explícito para cada
  transacción.

### 4.4 Construyendo los endpoints del backend

Con la interfaz de usuario de React en su lugar, ahora necesitamos dos rutas de API que
hagan el trabajo pesado en el servidor:

1. **`/api/verify/start`** – construye una solicitud OpenID4VP, persiste un desafío de un
   solo uso en MySQL y devuelve todo al navegador.
2. **`/api/verify/finish`** – recibe la respuesta de la wallet, valida el desafío,
   verifica y decodifica la credencial, y finalmente devuelve un resultado JSON conciso a
   la UI.

#### 4.4.1 `/api/verify/start`: Generar la solicitud OpenID4VP

```typescript
// src/app/api/verify/start/route.ts
import { NextResponse } from "next/server";
import { v4 as uuidv4 } from "uuid";
import { createChallenge, cleanupExpiredChallenges } from "@/lib/database";

export async function GET() {
    // 1️⃣  Crear un nonce (desafío) aleatorio y de corta duración
    const challenge = uuidv4();
    const challengeId = uuidv4();
    const expiresAt = new Date(Date.now() + 5 * 60 * 1000);

    await createChallenge(challengeId, challenge, expiresAt);
    cleanupExpiredChallenges().catch(console.error);

    // 2️⃣  Construir una consulta DCQL que describa *qué* queremos
    const dcqlQuery = {
        credentials: [
            {
                id: "cred1",
                format: "mso_mdoc",
                meta: { doctype_value: "eu.europa.ec.eudi.pid.1" },
                claims: [
                    { path: ["eu.europa.ec.eudi.pid.1", "family_name"] },
                    { path: ["eu.europa.ec.eudi.pid.1", "given_name"] },
                    { path: ["eu.europa.ec.eudi.pid.1", "birth_date"] },
                ],
            },
        ],
    };

    // 3️⃣  Devolver un objeto que el navegador pueda pasar a navigator.credentials.get()
    return NextResponse.json({
        protocol: "openid4vp", // le dice al navegador qué protocolo de wallet usar
        request: {
            dcql_query: dcqlQuery, // QUÉ presentar
            nonce: challenge, // anti-repetición
            response_type: "vp_token",
            response_mode: "dc_api", // la wallet hará un POST directamente a /finish
        },
        state: {
            credential_type: "mso_mdoc", // se guarda para comprobaciones posteriores
            nonce: challenge,
            challenge_id: challengeId,
        },
    });
}
```

> **Parámetros clave**
>
> • **`nonce`** – desafío criptográfico que vincula la solicitud y la respuesta (previene
> la repetición). • **`dcql_query`** – Un objeto que describe las declaraciones exactas
> que necesitamos. Para esta guía, usamos una estructura `dcql_query` inspirada en
> borradores recientes del Digital Credential Query Language, aunque este aún no es un
> estándar finalizado. • **`state`** – JSON arbitrario que la wallet devuelve para que
> podamos buscar el registro en la base de datos.

#### 4.4.2 Ayudantes de base de datos

El archivo `src/lib/database.ts` envuelve las operaciones básicas de MySQL para los
desafíos y las sesiones de verificación (insertar, leer, marcar como usado). Mantener esta
lógica en un único módulo facilita el cambio del almacén de datos más adelante.

---

### 4.5 `/api/verify/finish`: Validar y decodificar la presentación

```typescript
// src/app/api/verify/finish/route.ts
import { NextResponse, NextRequest } from "next/server";
import { v4 as uuidv4 } from "uuid";
import {
    getChallenge,
    markChallengeAsUsed,
    createVerificationSession,
    updateVerificationSession,
} from "@/lib/database";
import { decodeDigitalCredential, decodeAllNamespaces } from "@/lib/crypto";

export async function POST(request: NextRequest) {
    const body = await request.json();

    // 1️⃣  Extraer las piezas de la presentación verificable
    const vpTokenMap = body.vp_token ?? body.data?.vp_token;
    const state = body.state;
    const mdocToken = vpTokenMap?.cred1; // pedimos este ID en dcqlQuery

    if (!vpTokenMap || !state || !mdocToken) {
        return NextResponse.json(
            { verified: false, message: "Respuesta mal formada" },
            { status: 400 },
        );
    }

    // 2️⃣  Validación del desafío de un solo uso
    const stored = await getChallenge(state.nonce);
    if (!stored) {
        return NextResponse.json(
            { verified: false, message: "Desafío inválido o caducado" },
            { status: 400 },
        );
    }

    const sessionId = uuidv4();
    await createVerificationSession(sessionId, stored.id);

    // 3️⃣  Comprobaciones (pseudo)criptográficas – reemplazar con validación mDL real en producción
    // En una aplicación real, usarías una biblioteca dedicada para realizar la validación
    // criptográfica completa de la firma mdoc contra la clave pública del emisor.
    const isValid = mdocToken.length > 0;
    if (!isValid) {
        await updateVerificationSession(sessionId, "failed", {
            reason: "la validación de mdoc falló",
        });
        return NextResponse.json(
            { verified: false, message: "La validación de la credencial falló" },
            { status: 400 },
        );
    }

    // 4️⃣  Decodificar la carga útil de la licencia de conducir móvil (mdoc) a JSON legible
    const decoded = await decodeDigitalCredential(mdocToken);
    const readable = decodeAllNamespaces(decoded)["eu.europa.ec.eudi.pid.1"];

    await markChallengeAsUsed(state.nonce);
    await updateVerificationSession(sessionId, "verified", { readable });

    return NextResponse.json({
        verified: true,
        message: "¡Credencial mdoc verificada con éxito!",
        credentialData: readable,
        sessionId,
    });
}
```

> **Campos importantes en la respuesta de la wallet**
>
> • **`vp_token`** – mapa que contiene _cada_ credencial que la wallet devuelve. Para
> nuestra demo extraemos `vp_token.cred1`. • **`state`** – eco del blob que proporcionamos
> en `/start`; contiene el `nonce` para que podamos buscar el registro en la base de
> datos. • **`mdocToken`** – una estructura CBOR codificada en Base64URL que representa el
> ISO mDoc.

### 4.6 Decodificando la credencial mdoc

Cuando el verificador recibe una credencial mdoc del navegador, es una cadena Base64URL
que contiene datos binarios codificados en CBOR. Para extraer las declaraciones reales, el
endpoint `finish` realiza un proceso de decodificación de varios pasos utilizando
funciones de ayuda de `src/lib/crypto.ts`.

#### 4.6.1 Paso 1: Decodificación de Base64URL y CBOR

La función `decodeDigitalCredential` se encarga de la conversión de la cadena codificada a
un objeto utilizable:

```typescript
// src/lib/crypto.ts
export async function decodeDigitalCredential(encodedCredential: string) {
    // 1. Convertir Base64URL a Base64 estándar
    const base64UrlToBase64 = (input: string) => {
        let base64 = input.replace(/-/g, "+").replace(/_/g, "/");
        const pad = base64.length % 4;
        if (pad) base64 += "=".repeat(4 - pad);
        return base64;
    };

    const base64 = base64UrlToBase64(encodedCredential);

    // 2. Decodificar Base64 a binario
    const binaryString = atob(base64);
    const byteArray = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));

    // 3. Decodificar CBOR
    const decoded = await cbor.decodeFirst(byteArray);
    return decoded;
}
```

- **Base64URL a Base64:** Convierte la credencial de codificación Base64URL a Base64
  estándar.
- **Base64 a binario:** Decodifica la cadena Base64 en un array de bytes binario.
- **Decodificación CBOR:** Utiliza la biblioteca `cbor-web` para decodificar los datos
  binarios en un objeto JavaScript estructurado.

#### 4.6.2 Paso 2: Extrayendo declaraciones con espacios de nombres

La función `decodeAllNamespaces` procesa aún más el objeto CBOR decodificado para extraer
las declaraciones reales de los espacios de nombres relevantes:

```typescript
// src/lib/crypto.ts
export function decodeAllNamespaces(jsonObj) {
    const decoded = {};

    try {
        jsonObj.documents.forEach((doc, idx) => {
            // 1) issuerSigned.nameSpaces:
            const issuerNS = doc.issuerSigned?.nameSpaces || {};
            Object.entries(issuerNS).forEach(([nsName, entries]) => {
                if (!decoded[nsName]) decoded[nsName] = {};
                (entries as any[]).forEach((entry) => {
                    const bytes = Uint8Array.from(entry.value);
                    const decodedEntry = cbor.decodeFirstSync(bytes);
                    Object.assign(decoded[nsName], decodedEntry);
                });
            });

            // 2) deviceSigned.nameSpaces (si está presente):
            const deviceNS = doc.deviceSigned?.nameSpaces;
            if (deviceNS?.value?.data) {
                const bytes = Uint8Array.from(deviceNS.value);
                decoded[`deviceSigned_ns_${idx}`] = cbor.decodeFirstSync(bytes);
            }
        });
    } catch (e) {
        console.error(e);
    }

    return decoded;
}
```

- **Itera sobre todos los documentos** en la credencial decodificada.
- **Decodifica cada espacio de nombres** (por ejemplo, `eu.europa.ec.eudi.pid.1`) para
  extraer los valores reales de las declaraciones (como nombre, fecha de nacimiento,
  etc.).
- **Maneja tanto los espacios de nombres firmados por el emisor como los firmados por el
  dispositivo**, si están presentes.

#### Salida de ejemplo

Después de ejecutar estos pasos, el endpoint de finalización obtiene un objeto legible por
humanos que contiene las declaraciones del mdoc, por ejemplo:

```json
{
    "family_name": "Doe",
    "given_name": "John",
    "birth_date": "1990-01-01"
}
```

Este proceso garantiza que el verificador pueda extraer de forma segura y fiable la
información necesaria de la credencial mdoc para su visualización y posterior
procesamiento.

### 4.7 Mostrando el resultado en la UI

El endpoint de finalización devuelve un objeto JSON mínimo al frontend:

```json
{
    "verified": true,
    "message": "¡Credencial mdoc verificada con éxito!",
    "credentialData": {
        "family_name": "Doe",
        "given_name": "John",
        "birth_date": "1990-01-01"
    }
}
```

El frontend recibe esta respuesta en `startVerification()` y simplemente la persiste en el
estado de React para que podamos renderizar una bonita tarjeta de confirmación o mostrar
declaraciones individuales, por ejemplo, _“¡Bienvenido, John Doe (nacido el
1990-01-01)!”_.

## 5. Ejecutando el verificador y próximos pasos

Ahora tienes un verificador completo y funcional que utiliza las capacidades nativas de
manejo de credenciales del navegador. Aquí te explicamos cómo ejecutarlo localmente y qué
puedes hacer para llevarlo de una prueba de concepto a una aplicación lista para
producción.

### 5.1 Cómo ejecutar el ejemplo

1. **Clona el repositorio:**

    ```bash
    git clone https://github.com/corbado/digital-credentials-example.git
    cd digital-credentials-example
    ```

2. **Instala las dependencias:**

    ```bash
    npm install
    ```

3. **Inicia la base de datos:** Asegúrate de que Docker se está ejecutando en tu máquina y
   luego inicia el contenedor de MySQL:

    ```bash
    docker-compose up -d
    ```

4. **Ejecuta la aplicación:**

    ```bash
    npm run dev
    ```

    Abre tu navegador en `http://localhost:3000` y deberías ver la interfaz de usuario del
    verificador. Ahora puedes usar tu CMWallet para escanear el código QR y completar el
    flujo de verificación.

### 5.2 Próximos pasos: de la demo a la producción

Este tutorial proporciona los componentes básicos para un verificador. Para que esté listo
para producción, necesitarías implementar varias características adicionales:

- **Validación criptográfica completa:** La implementación actual utiliza una comprobación
  de marcador de posición (`mdocToken.length > 0`). En un escenario real, debes realizar
  una validación criptográfica completa de la firma mdoc contra la clave pública del
  emisor (por ejemplo, resolviendo su DID o recuperando su certificado de clave pública).
  Para los estándares de resolución de DID, consulta la
  [especificación de resolución de DID del W3C](https://www.w3.org/TR/did-resolution/).

- **Comprobación de revocación del emisor:** Las credenciales pueden ser revocadas por el
  emisor antes de su fecha de caducidad. Un verificador de producción debe comprobar el
  estado de la credencial consultando una lista de revocación o un endpoint de estado
  proporcionado por el emisor. La
  [Lista de estado de credenciales verificables del W3C](https://www.w3.org/TR/vc-bitstring-status-list/)
  proporciona el estándar para las listas de revocación de credenciales.

- **Manejo de errores robusto y seguridad:** Añade un manejo de errores completo,
  validación de entradas, limitación de velocidad en los endpoints de la API y asegúrate
  de que toda la comunicación sea a través de HTTPS (TLS) para proteger los datos en
  tránsito. Las
  [directrices de seguridad de API de OWASP](https://owasp.org/www-project-api-security/)
  proporcionan las mejores prácticas de seguridad para API.

- **Soporte para múltiples tipos de credenciales:** Amplía la lógica para manejar
  diferentes valores de `doctype` y formatos de credenciales si esperas recibir más que
  solo la credencial PID de la [Identidad Digital](https://www.corbado.com/es/blog/digital-credentials-api)
  Europea (EUDI). El
  [Modelo de Datos de Credenciales Verificables del W3C](https://www.w3.org/TR/vc-data-model/)
  proporciona especificaciones completas de formato de VC.

### 5.3 Qué está fuera del alcance de este tutorial

Este ejemplo está intencionadamente enfocado en el flujo central mediado por el navegador
para que sea fácil de entender. Los siguientes temas se consideran fuera del alcance:

- **Seguridad lista para producción:** El verificador es para fines educativos y carece
  del endurecimiento requerido para un entorno en vivo.
- **Credenciales verificables del W3C:** Este tutorial se centra exclusivamente en el
  formato ISO mDoc para licencias de conducir móviles. No cubre otros formatos populares
  como JWT-VCs o VCs con pruebas de datos vinculados (LD-Proofs).
- **Flujos avanzados de OpenID4VP:** No implementamos características más complejas de
  OpenID4VP, como la comunicación directa de la wallet al backend usando una
  `redirect_uri` o el registro dinámico de clientes.

Al construir sobre esta base e incorporar estos próximos pasos, puedes desarrollar un
verificador robusto y seguro capaz de confiar y validar credenciales digitales en tus
propias aplicaciones.

## Conclusión

¡Eso es todo! Con menos de 250 líneas de TypeScript, ahora tenemos un verificador de
extremo a extremo que:

1. Publica una solicitud para la API de credenciales del navegador.
2. Permite que cualquier wallet compatible proporcione una presentación verificable.
3. Valida la presentación en el servidor.
4. Actualiza la interfaz de usuario en tiempo real.

En producción, reemplazarías la validación de marcador de posición con comprobaciones
completas de [ISO 18013-5](https://www.corbado.com/glossary/iso-18013-5), añadirías búsquedas de revocación del
emisor, limitación de velocidad, registro de auditoría y, por supuesto, TLS de extremo a
extremo, pero los componentes básicos siguen siendo exactamente los mismos.

## Recursos

Aquí tienes algunos de los recursos, especificaciones y herramientas clave utilizados o
referenciados en este tutorial:

- **Repositorio del proyecto:**
    - [Código fuente completo en GitHub](https://github.com/corbado/digital-credentials-example)

- **Especificaciones clave:**
    - [Modelo de Datos de Credenciales Verificables del W3C](https://www.w3.org/TR/vc-data-model/):
      El estándar fundamental para las VCs.
    - [OpenID for Verifiable Presentations (OpenID4VP)](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html):
      El protocolo de presentación utilizado para el intercambio de credenciales.
    - [ISO/IEC 18013-5 (mDoc)](https://www.iso.org/standard/69084.html): El estándar
      internacional para licencias de conducir móviles (mDL).
    - [API de credenciales digitales del W3C](https://www.w3.org/TR/digital-credentials/#the-digital-credentials-api):
      La API del navegador utilizada para solicitar credenciales a una wallet.

- **Herramientas:**
    - [CMWallet para Android](https://github.com/digitalcredentialsdev/CMWallet/actions/runs/16407676816/artifacts/3574255220):
      La wallet de prueba utilizada en esta guía.

- **Bibliotecas:**
    - Next.js: El framework de React para construir el frontend y el backend.
    - [cbor-web](https://github.com/hildjj/cbor-web): Para decodificar credenciales mdoc
      codificadas en CBOR.
    - [mysql2](https://github.com/sidorares/node-mysql2): El cliente de MySQL para
      [Node.js](https://www.corbado.com/blog/nodejs-passkeys).
