Constaia
Integraciones

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/widget

Lo 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:

server/server.mjs
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).

vite.config.ts
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

src/components/VerifyDocument.vue
<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.

src/main.ts
import { ConstaiaPlugin } from "@constaia/widget/vue";
import { createApp } from "vue";
import App from "./App.vue";

createApp(App).use(ConstaiaPlugin).mount("#app");
src/components/VerifyRaw.vue
<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.

Archivoverdict.statusMotivo principal
dni_valid.jpgVálidonot_expired (info): "Vigente hasta el 12/03/2031."
dni_expired.jpgNo válidonot_expired (error): "Caducado el 15/06/2020."
blurry.jpgRevisarlow_quality (warning); warnings: blurry, low_quality
foto.jpg (otro nombre)No válidotype_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 maxSizeMb al 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 con headers.

Siguientes pasos

En esta página