Constaia
Guías por caso

Certificado de delitos sexuales (LOPIVI)

Valida el certificado negativo de delitos sexuales de entrenadores y voluntarios (reciente, del titular, sin antecedentes) sin guardarlo.

Si en tu organización hay personas que trabajan con menores (entrenadores, monitores, voluntarios), tienes que pedirles el certificado negativo del Registro Central de Delincuentes Sexuales. Con el tipo es_sexual_offences_certificate Constaia comprueba que es ese certificado, que es de esa persona, que no es antiguo y que no constan antecedentes.

Es un dato especialmente sensible, así que esta guía lo procesa sin guardar nada en Constaia.

Esta guía explica cómo automatizar la comprobación, no qué exige la ley en tu caso. La antigüedad máxima que aceptas y cada cuánto lo vuelves a pedir lo decide tu organización con su asesoría jurídica.

Las opciones

options.json
{
  "expect": "es_sexual_offences_certificate",
  "checks": {
    "max_age_days": 90,
    "holder": { "full_name": "María García López", "document_number": "12345678Z" }
  },
  "storage": "none",
  "keep_results": false,
  "metadata": { "volunteer_id": "311" }
}
OpciónPor qué
expectSi llega otro documento (por ejemplo, el certificado de antecedentes penales, que es es_criminal_record_certificate), el veredicto es invalid con type_mismatch.
checks.max_age_daysAntigüedad máxima desde la fecha de emisión. 90 es un ejemplo: pon la ventana que haya fijado tu organización.
checks.holderQue el titular del certificado es la persona que tienes registrada.
storage: "none"El fichero se procesa en memoria y no se escribe en ningún almacenamiento.
keep_results: falseTampoco se guardan los datos extraídos. Recibes la respuesta una vez y después GET /v1/analyses/{id} devuelve 404.

El motivo has_records se añade siempre, sin pedirlo:

has_recordsseverityMensaje
El certificado dice que no constan antecedentesinfo"El certificado indica que no constan antecedentes."
El certificado dice que constan antecedenteserror → invalid"El certificado indica que constan antecedentes."

El código de verificación (CSV)

Los certificados llevan un código seguro de verificación (CSV). Constaia lo extrae en csv_code y ejecuta la validación csv_format.

csv_format solo comprueba el formato

csv_format comprueba que el código tiene un formato válido. Constaia no consulta al Ministerio de Justicia si el código existe ni si corresponde a este certificado. Si tu procedimiento exige comprobar la autenticidad, introduce el CSV en el servicio de verificación de documentos de la sede electrónica del Ministerio de Justicia y compara el resultado con el documento que te han enviado.

Si el formato no es válido, checks[] trae csv_format con passed: false y el veredicto pasa a invalid con un motivo csv_format de severidad error.

La firma electrónica del PDF

El certificado que se descarga de la sede del Ministerio de Justicia es un PDF con firma electrónica (sello del Ministerio). Constaia la verifica sin coste adicional y sin consultar a nadie: que el contenido no se ha tocado desde que se firmó y que el certificado del firmante llega a una autoridad de confianza. El resultado va en signature y en verdict.reasons:

Qué llegaMotivoSin require_valid_signatureCon require_valid_signature: true
El PDF original, intactosignature_validinfoinfo
Un PDF modificado tras firmarsedocument_modified_after_signingwarning → reviewerror → invalid
Una firma rotasignature_invaliderror → invaliderror → invalid
Un PDF sin firma, una foto o un escaneosignature_missingwarning → review si es un PDF; en una foto no se añadeerror → invalid

Si solo aceptas el PDF original, añade "require_valid_signature": true a checks: una foto o un "imprimir a PDF" pasan a invalid y la persona sabe que tiene que subir el fichero descargado. Si aceptas fotos, déjalo sin activar y revisa a mano los review. Compara también signature.signer con el organismo esperado: una firma válida solo acredita quién firmó. Detalles y límites en Firmas digitales en PDF.

El código

check-lopivi.js
import { Constaia, ConstaiaError } from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node";

const constaia = new Constaia(); // lee CONSTAIA_API_KEY

export async function checkSexualOffencesCertificate(path, person) {
  const analysis = await constaia.analyze(await fromPath(path), {
    expect: "es_sexual_offences_certificate",
    checks: {
      maxAgeDays: 90,
      holder: { fullName: person.fullName, documentNumber: person.documentNumber },
    },
    storage: "none",
    keepResults: false,
    metadata: { volunteer_id: String(person.id) },
  });

  return {
    analysisId: analysis.id,
    checkedAt: analysis.completed_at,
    status: analysis.verdict?.status ?? "review",
    problems: (analysis.verdict?.reasons ?? [])
      .filter((r) => r.severity !== "info")
      .map((r) => r.message),
  };
}

try {
  const result = await checkSexualOffencesCertificate("./sexual_offences_certificate.pdf", {
    id: 311,
    fullName: "María García López",
    documentNumber: "12345678Z",
  });
  console.log(result);
} catch (err) {
  if (err instanceof ConstaiaError) console.error(err.code, err.message, err.requestId);
  else throw err;
}

Qué devuelve

Con el fichero de test sexual_offences_certificate.pdf y las opciones del ejemplo de curl, si el PDF es el original firmado (si no, verás además signature_missing con warning y el veredicto será review; ver Probarlo):

verdict
{
  "expected": ["es_sexual_offences_certificate"],
  "match": true,
  "status": "valid",
  "reasons": [
    { "code": "type_match", "severity": "info", "message": "El documento es Certificado de delitos de naturaleza sexual." },
    { "code": "max_age_days", "severity": "info", "message": "Emitido hace 14 días (máximo 90)." },
    { "code": "holder", "severity": "info", "message": "Los datos del titular coinciden (full_name)." },
    { "code": "has_records", "severity": "info", "message": "El certificado indica que no constan antecedentes." }
  ]
}

Y en checks[]: nif_check_digit (la letra del DNI del titular, 12345678Z) y csv_format con passed: true. Los campos son holder_name (MARÍA GARCÍA LÓPEZ), holder_id (12345678Z), issue_date (2026-09-15), has_records (false) y csv_code (MJU4-7K2P-9QXA-3ZTR). Los días de "Emitido hace…" dependen de la fecha en que lo ejecutes.

Qué guardar en tu sistema

Como Constaia no conserva nada (keep_results: false), tu registro es la única constancia. Guarda lo mínimo:

DatoPara qué
analysis.idReferencia del análisis ante una incidencia o una consulta a soporte. No podrás recuperar el resultado con él.
Fecha de la comprobación (completed_at)Saber cuándo caduca según tu ventana y cuándo volver a pedirlo.
Resultado (valid, invalid, review)La decisión tomada.

No guardes el PDF ni el resto de campos si no los necesitas. Si guardas el csv_code para verificarlo más tarde en la sede electrónica, trátalo como un dato sensible: sirve para localizar el certificado.

Decidir

VeredictoAcción
VálidoRegistra la fecha y habilita a la persona para actividades con menores.
No válidoSi el motivo es has_records, sigue tu protocolo interno. Si es antigüedad, titular o tipo, pide un certificado nuevo.
RevisarRevisión humana: foto mala, fecha de emisión ilegible o confianza baja.

Para review necesitas tu propia copia del documento, porque con storage: "none" Constaia no la conserva. Lo explica Revisión humana.

Probarlo

Con una clave ck_test_…, un PDF llamado sexual_offences_certificate.pdf (o cualquier nombre que contenga sexual, delitos o penales) devuelve el certificado sin antecedentes de MARÍA GARCÍA LÓPEZ emitido el 2026-09-15. Cambia holder.full_name para ver invalid o baja max_age_days a 7 para ver el motivo max_age_days con error. Más en Modo test.

La firma no se simula: con un PDF cualquiera sin firmar recibirás signature_missing (warning) y el veredicto review. Usa una imagen JPEG o PNG con ese nombre para ver el valid de la tabla, o tu propio certificado descargado de la sede para ver signature_valid.

Siguientes pasos

En esta página