React
Añade el widget de Constaia a una app React con el componente ConstaiaUpload y el hook useConstaiaUpload, subiendo los archivos a tu propio backend.
@constaia/widget/react es un wrapper fino sobre el web component <constaia-upload>: captura el documento (arrastrar, elegir archivo o cámara), revisa la calidad de la imagen y lo sube a tu backend. Funciona con React 18 y 19, y con renderizado en servidor.
Necesitas un backend
React se ejecuta en el navegador y la clave de API nunca puede estar ahí. El widget sube el archivo a un endpoint tuyo (/api/constaia) y es ese servidor el que llama a Constaia. Si usas Next.js o React Router, sigue Next.js o Remix y React Router; para una SPA con Vite, monta el backend con Express (o cualquier otro de las integraciones).
Instalación
npm i @constaia/widgetLo que debe hacer tu backend
El widget envía POST multipart/form-data al endpoint con dos campos:
| Campo | Contenido |
|---|---|
file | El documento. Para DNI, NIE y documento de identidad europeo, anverso y reverso unidos en un solo JPEG. |
options | JSON con expect y language tomados de los atributos. Es solo una pista. |
Tu backend debe:
- Autenticar al usuario y limitar los intentos (cada análisis gasta créditos).
- Llamar a Constaia con
expectychecksdecididos en el servidor, no con los que mande el navegador. - Devolver el análisis tal cual (JSON) con estado 200, o 202 si aún está en cola.
- En caso de error, devolver un estado no 2xx con
{ "error": { "message": "…" } }: el widget muestra ese mensaje.
Un ejemplo mínimo con Express:
import express from "express";
import multer from "multer";
import { Constaia, ConstaiaError, InvalidRequestError } from "@constaia/sdk";
const constaia = new Constaia({ apiKey: process.env.CONSTAIA_API_KEY });
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 20 * 1024 * 1024 } });
const app = express();
app.post("/api/constaia", upload.single("file"), async (req, res) => {
if (!req.file) return res.status(400).json({ error: { message: "Falta el archivo." } });
try {
const analysis = await constaia.analyze(req.file.buffer, {
filename: req.file.originalname,
expect: "es_dni",
checks: { notExpired: true, minAgeYears: 18 },
});
res.status(analysis.status === "completed" ? 200 : 202).json(analysis);
} catch (err) {
if (err instanceof InvalidRequestError) return res.status(err.status ?? 400).json({ error: { message: err.message } });
console.error(err instanceof ConstaiaError ? err.requestId : "", err);
res.status(502).json({ error: { message: "No se ha podido verificar el documento. Inténtalo de nuevo." } });
}
});
app.listen(3000);La versión completa (sesión, todos los errores del SDK, webhook con firma) está en Express.
En desarrollo, haz que Vite redirija /api al backend para compartir origen y cookies:
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [react()],
server: {
proxy: { "/api": "http://localhost:3000" },
},
});El componente ConstaiaUpload
import { ConstaiaUpload } from "@constaia/widget/react";
import { useState } from "react";
export function VerifyDocument() {
const [verdict, setVerdict] = useState<string | null>(null);
return (
<section>
<h1>Verifica tu DNI</h1>
<ConstaiaUpload
endpoint="/api/constaia"
document="es_dni"
lang="es"
onResult={(analysis) => setVerdict(analysis.verdict?.status ?? null)}
onError={(error) => console.warn(error.code, error.message)}
onStatus={(status) => status === "idle" && setVerdict(null)}
/>
{verdict === "valid" && <a href="/registro/datos">Continuar</a>}
</section>
);
}Las props son los atributos del widget en camelCase más los manejadores de eventos:
| Prop | Descripción |
|---|---|
endpoint | URL de tu backend. |
document | Tipo de documento para la interfaz (marco de tarjeta, dos caras). Si falta expect, también se envía como expect. |
expect | Tipo o tipos esperados (string o string[]), enviados en options. |
sides, frame | 1 o 2 caras; marco card o none. Automáticos para DNI, NIE y documento de identidad europeo. |
accept, maxSizeMb | Tipos aceptados y tamaño máximo (20 MB por defecto). |
autoSubmit | false espera a que el usuario (o submit()) envíe tras el control de calidad. |
withCredentials | Envía cookies a un endpoint de otro origen. |
lang, theme, camera | Idioma (es, en, pt, fr), tema (light, dark, auto) y botón de cámara (auto, always, never). |
headers | Cabeceras extra, por ejemplo un token CSRF. |
messages | Sustituye textos de la interfaz. |
onFile, onQuality, onProgress, onResult, onError, onStatus | Eventos del widget. onResult recibe el análisis tal y como lo devuelve tu backend. |
La referencia completa de atributos, eventos y estilos está en Widget.
El hook useConstaiaUpload
El hook expone el estado del widget para construir tu propia interfaz alrededor: ref, element, status, progress, result, error, reset() y submit(). Acepta los mismos atributos que el componente, más messages y headers.
import { ConstaiaUpload, useConstaiaUpload } from "@constaia/widget/react";
export function VerifyWithControls({ csrfToken }: { csrfToken: string }) {
const { ref, status, progress, result, error, reset, submit } = useConstaiaUpload({
endpoint: "/api/constaia",
document: "es_dni",
lang: "es",
autoSubmit: false,
headers: { "X-CSRF-Token": csrfToken },
});
const verdict = result?.verdict?.status;
return (
<section>
<ConstaiaUpload ref={ref} />
{status === "ready" && (
<button type="button" onClick={() => submit()}>
Enviar documento
</button>
)}
{status === "uploading" && <progress value={progress} max={100} />}
{status === "analyzing" && <p>Analizando…</p>}
{verdict === "valid" && <a href="/registro/datos">Continuar</a>}
{verdict === "review" && <p>No se lee bien. Haz otra foto con buena luz y sin reflejos.</p>}
{(verdict === "invalid" || error) && (
<button type="button" onClick={reset}>
Probar con otro documento
</button>
)}
</section>
);
}Los estados son idle, checking, quality, ready, uploading, analyzing, done y error. Si el análisis tarda más de 30 s, tu backend devuelve el análisis en queued o processing y el widget muestra un aviso de "en cola"; el resultado final te llega en el backend por webhook.
No te fíes del resultado en el navegador
result sirve para la interfaz. Cuando el usuario continúe, tu backend debe decidir con el resultado que guardó al llamar a Constaia (o con GET /v1/analyses/{id}), nunca con un veredicto que le reenvíe el navegador.
Probar en modo test
Con una clave ck_test_... en tu backend no se gastan créditos y el resultado depende del nombre del archivo, que debe ser una imagen o PDF real. El widget une las dos caras en un JPEG con el nombre del anverso.
| Archivo | verdict.status | Motivo principal |
|---|---|---|
dni_valid.jpg | Válido | not_expired (info): "Vigente hasta el 12/03/2031." |
dni_expired.jpg | No válido | not_expired (error): "Caducado el 15/06/2020." |
blurry.jpg | Revisar | low_quality (warning); warnings: blurry, low_quality |
foto.jpg (otro nombre) | No válido | type_mismatch: se detecta generic |
Más nombres en Modo test.
Producción
- La clave vive solo en el backend. Nunca en variables
VITE_*niREACT_APP_*, que acaban en el bundle. - El endpoint de subida exige sesión y limita intentos por usuario: cada llamada gasta créditos.
- Backend y proxy aceptan cuerpos de al menos 20 MB (o baja
maxSizeMbal límite de tu plataforma). - Timeouts del backend y del proxy de 60 s o más: el análisis síncrono puede tardar hasta 30 s.
- Si el backend está en otro dominio, configura CORS con credenciales y usa
withCredentials, o pasa un token conheaders.
Siguientes pasos
Angular
Integra Constaia en Angular 18+ con el custom element y CUSTOM_ELEMENTS_SCHEMA, o con HttpClient, subiendo siempre a tu backend (ejemplo con Express).
Vue
Añade el widget de Constaia a una app Vue 3 con el componente ConstaiaUpload, el plugin ConstaiaPlugin o la etiqueta nativa, subiendo a tu backend.