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.
@constaia/widget/vue 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, que es quien llama a Constaia.
Necesitas un backend
Vue 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). Si usas Nuxt, sigue Nuxt; para una SPA con Vite, monta el backend 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.
Configuración de Vite
El proxy comparte origen y cookies con el backend en desarrollo. isCustomElement solo hace falta si usas la etiqueta <constaia-upload> directamente en plantillas (con el wrapper no es necesario, pero no molesta).
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: { isCustomElement: (tag) => tag.startsWith("constaia-") },
},
}),
],
server: {
proxy: { "/api": "http://localhost:3000" },
},
});El componente ConstaiaUpload
<script setup lang="ts">
import { ConstaiaUpload } from "@constaia/widget/vue";
import type { Analysis, WidgetErrorDetail, WidgetStatus } from "@constaia/widget/vue";
import { ref } from "vue";
const upload = ref<{ reset: () => void } | null>(null);
const verdict = ref<string | null>(null);
const status = ref<WidgetStatus>("idle");
function onResult(analysis: Analysis) {
verdict.value = analysis.verdict?.status ?? null;
}
function onError(error: WidgetErrorDetail) {
console.warn(error.code, error.message);
}
function retry() {
upload.value?.reset();
verdict.value = null;
}
</script>
<template>
<h1>Verifica tu DNI</h1>
<ConstaiaUpload
ref="upload"
endpoint="/api/constaia"
document="es_dni"
lang="es"
@result="onResult"
@error="onError"
@status="(s: WidgetStatus) => (status = s)"
/>
<p v-if="status === 'analyzing'">Analizando…</p>
<a v-if="verdict === 'valid'" href="/registro/datos">Continuar</a>
<p v-else-if="verdict === 'review'">No se lee bien. Haz otra foto con buena luz y sin reflejos.</p>
<button v-else-if="verdict === 'invalid'" type="button" @click="retry">Probar con otro documento</button>
</template>Props del wrapper: endpoint, expect (string o string[]), document, sides, frame, accept, maxSizeMb, autoSubmit (por defecto true), withCredentials, lang, theme, camera, messages y headers. Eventos: @file, @quality, @progress, @result, @error y @status (este último recibe directamente el estado: idle, checking, quality, ready, uploading, analyzing, done o error). Por ref expone reset(), submit() y element.
Para enviar un token CSRF o de sesión, usa la prop headers, por ejemplo :headers="{ 'X-CSRF-Token': csrfToken }".
El plugin y la etiqueta nativa
Si prefieres usar <constaia-upload> en tus plantillas sin el wrapper, instala ConstaiaPlugin (registra el elemento) y mantén isCustomElement en la configuración de Vite.
import { ConstaiaPlugin } from "@constaia/widget/vue";
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).use(ConstaiaPlugin).mount("#app");<script setup lang="ts">
import type { Analysis } from "@constaia/widget/vue";
import { ref } from "vue";
const verdict = ref<string | null>(null);
function onResult(event: Event) {
verdict.value = (event as CustomEvent<Analysis>).detail.verdict?.status ?? null;
}
</script>
<template>
<constaia-upload endpoint="/api/constaia" document="es_dni" lang="es" @constaia:result="onResult" />
<p v-if="verdict">Resultado: {{ verdict }}</p>
</template>Con la etiqueta nativa los eventos son los del web component (constaia:result, constaia:error…) y los datos llegan en event.detail.
No te fíes del resultado en el navegador
El veredicto que recibe Vue 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
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
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.
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.