Svelte
Usa el web component constaia-upload en Svelte 5, con eventos escuchados vía bind:this y addEventListener y los archivos subidos a tu backend.
En Svelte no hace falta wrapper: <constaia-upload> es un web component estándar. Lo importas una vez, lo usas como cualquier etiqueta y escuchas sus eventos. Esta guía es para una app de Svelte 5 con Vite; si usas SvelteKit, sigue SvelteKit, que incluye también el backend.
Necesitas un backend
Svelte 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 ese servidor llama a Constaia. Móntalo con Express o con cualquiera 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 file (para DNI, NIE y documento de identidad europeo, las dos caras unidas en un JPEG) y options (JSON con expect y language, solo como pista). Tu backend debe autenticar al usuario y limitar intentos, llamar a Constaia con expect y checks decididos en el servidor, devolver el análisis tal cual (200, o 202 si sigue en cola) y, si falla, responder con un estado no 2xx y { "error": { "message": "…" } }, que es lo que el widget muestra.
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, redirige /api al backend:
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [svelte()],
server: {
proxy: { "/api": "http://localhost:3000" },
},
});El componente
Los eventos del widget llevan dos puntos en el nombre (constaia:result, constaia:error, constaia:status). Los atributos de evento de Svelte 5 (onclick) y la directiva on: no están pensados para nombres así, de modo que lo seguro es obtener el elemento con bind:this y registrar los listeners con addEventListener dentro de un $effect, que además los retira al desmontar.
<script lang="ts">
import "@constaia/widget";
import type { Analysis, ConstaiaUploadElement, WidgetErrorDetail, WidgetStatus } from "@constaia/widget";
let { csrfToken }: { csrfToken?: string } = $props();
let uploader: ConstaiaUploadElement | undefined = $state();
let verdict = $state<string | null>(null);
let status = $state<WidgetStatus>("idle");
$effect(() => {
const el = uploader;
if (!el) return;
if (csrfToken) el.headers = { "X-CSRF-Token": csrfToken };
const onResult = (e: Event) => {
verdict = (e as CustomEvent<Analysis>).detail.verdict?.status ?? null;
};
const onError = (e: Event) => {
const { code, message } = (e as CustomEvent<WidgetErrorDetail>).detail;
console.warn(code, message);
};
const onStatus = (e: Event) => {
status = (e as CustomEvent<{ status: WidgetStatus }>).detail.status;
};
el.addEventListener("constaia:result", onResult);
el.addEventListener("constaia:error", onError);
el.addEventListener("constaia:status", onStatus);
return () => {
el.removeEventListener("constaia:result", onResult);
el.removeEventListener("constaia:error", onError);
el.removeEventListener("constaia:status", onStatus);
};
});
function retry() {
uploader?.reset();
verdict = null;
}
</script>
<h1>Verifica tu DNI</h1>
<constaia-upload bind:this={uploader} endpoint="/api/constaia" document="es_dni" lang="es"></constaia-upload>
{#if status === "analyzing"}
<p>Analizando…</p>
{/if}
{#if verdict === "valid"}
<a href="/registro/datos">Continuar</a>
{:else if verdict === "review"}
<p>No se lee bien. Haz otra foto con buena luz y sin reflejos.</p>
{:else if verdict === "invalid"}
<button type="button" onclick={retry}>Probar con otro documento</button>
{/if}import "@constaia/widget"registra el elemento una sola vez para toda la app.- Los atributos (
endpoint,document,expect,lang,theme,camera,max-size-mb…) se escriben como en HTML. La referencia completa está en Widget. - Propiedades como
headers(objeto) omessagesse asignan sobre el elemento, como en el$effect. - Con
document="es_dni"el widget pide las dos caras, revisa la calidad de la imagen y las une en un JPEG antes de subirlo.
No te fíes del resultado en el navegador
El veredicto que recibe el componente 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}). Si el análisis tarda más de 30 s, el widget muestra un aviso de "en cola" y el resultado llega a tu backend por webhook.
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_*, 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
max-size-mbal 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 añade el atributo
with-credentials, o pasa un token conheaders.
Siguientes pasos
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.
Expo y React Native
Fotografía documentos en una app Expo o React Native con expo-image-picker, súbelos a tu backend con FormData y deja que el servidor llame a Constaia.