Conciliar justificantes de transferencia
Comprueba que cada justificante de transferencia trae el importe, el IBAN de tu cuenta y la referencia, uno a uno o en lotes con export a Excel.
Si cobras inscripciones, cuotas o licencias por transferencia, te llegan justificantes en PDF o capturas del móvil y
alguien tiene que cuadrarlos con cada pago pendiente. Con el tipo payment_receipt Constaia extrae los datos del
justificante y los compara con lo que esperas.
Las opciones
{
"expect": "payment_receipt",
"checks": {
"expected_amount": 45,
"expected_iban": "ES7921000813610123456789",
"expected_reference": "INSCRIPCION 123"
},
"metadata": { "registration_id": "123" }
}| Check | Qué compara | Si no coincide |
|---|---|---|
expected_amount | El campo amount con el importe esperado (tolerancia de medio céntimo). | expected_amount con error. |
expected_iban | Tu IBAN con los IBAN del justificante. Se ignoran mayúsculas y espacios. | expected_iban con error. |
expected_reference | Tu código dentro de reference o de concept. Ignora mayúsculas y tildes; basta con que esté contenido. | expected_reference con error. |
Además, cada IBAN que aparece pasa la validación determinista iban_checksum (dígitos de control). Si uno falla, el
veredicto es invalid con un motivo iban_checksum.
expected_iban no distingue ordenante y beneficiario
expected_iban da por bueno el justificante si tu IBAN aparece como beneficiary_iban o como payer_iban. Para
asegurarte de que el dinero va a tu cuenta, compara también fields.beneficiary_iban.value en tu código, como en el
ejemplo de abajo.
Otras opciones útiles:
checks.max_age_days: sobre la fecha de la transferencia (date). Por ejemplo,30para rechazar justificantes antiguos reutilizados.checks.holder.full_name: compara conpayer_name, si quieres que pague la propia persona inscrita.
Campos que obtienes
| Campo | Ejemplo |
|---|---|
amount, currency | 45, EUR |
date | 2026-09-20 |
payer_name, payer_iban | MARÍA GARCÍA LÓPEZ, ES9121000418450200051332 |
beneficiary_name, beneficiary_iban | CLUB DEPORTIVO ARCO MADRID, ES7921000813610123456789 |
concept, reference | INSCRIPCION 123 |
Un justificante
import { Constaia, ConstaiaError } from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node";
const constaia = new Constaia(); // lee CONSTAIA_API_KEY
const OUR_IBAN = process.env.OUR_IBAN; // ES7921000813610123456789
export async function reconcile(path, payment) {
const analysis = await constaia.analyze(await fromPath(path), {
expect: "payment_receipt",
checks: {
expectedAmount: payment.amount,
expectedIban: OUR_IBAN,
expectedReference: payment.reference,
},
metadata: { registration_id: String(payment.registrationId) },
});
const beneficiary = analysis.fields.beneficiary_iban?.value?.replace(/\s+/g, "").toUpperCase();
const failed = (analysis.verdict?.reasons ?? []).filter((r) => r.severity !== "info");
if (beneficiary && beneficiary !== OUR_IBAN) {
failed.push({ code: "beneficiary_iban", severity: "error", message: "El beneficiario no es nuestra cuenta." });
}
return {
analysisId: analysis.id,
status: failed.some((r) => r.severity === "error") ? "invalid" : (analysis.verdict?.status ?? "review"),
failedCodes: failed.map((r) => r.code),
messages: failed.map((r) => r.message),
paidAt: analysis.fields.date?.value ?? null,
payerIban: analysis.fields.payer_iban?.value ?? null,
};
}
try {
console.log(
await reconcile("./payment_receipt.pdf", { registrationId: 123, amount: 45, reference: "INSCRIPCION 123" }),
);
} catch (err) {
if (err instanceof ConstaiaError) console.error(err.code, err.message, err.requestId);
else throw err;
}Qué falló: leer el motivo
Con invalid, el code de cada motivo con severity: "error" te dice qué no cuadra, y el message trae el valor
encontrado y el esperado. Por ejemplo, si esperabas 50 € y el justificante dice 45:
{ "code": "expected_amount", "severity": "error", "message": "amount es «45» y se esperaba «50»." }code | Acción habitual |
|---|---|
expected_amount | Pago parcial o importe equivocado: pide la diferencia o revisa la tarifa aplicada. |
expected_iban | La transferencia no es a tu cuenta: rechaza. |
expected_reference | Falta el código de inscripción: busca el pago por importe y ordenante o pregunta. |
iban_checksum | Un IBAN no pasa los dígitos de control: justificante mal leído o manipulado; revisión humana. |
type_mismatch | No es un justificante (por ejemplo, una factura). |
Con review (foto mala, confianza baja) no concilies automáticamente: mira Revisión humana.
Y recuerda que un justificante no es el ingreso: el dinero hay que verlo en tu banco.
Muchos justificantes: lotes
Para conciliar al final del día todos los justificantes pendientes, usa un lote de hasta 100
documentos. Cada documento lleva sus propios checks en items[].options, y options.export en la raíz genera un
fichero combinado con una fila por justificante.
Las opciones de cada elemento se fusionan con las comunes clave a clave, también dentro de checks: podrías poner
expected_iban una sola vez en los checks comunes y dejar en cada elemento solo lo que cambia. Aquí cada elemento lleva
todos sus checks para que se lean de un vistazo.
import { Constaia } from "@constaia/sdk";
const constaia = new Constaia();
const OUR_IBAN = process.env.OUR_IBAN;
const pending = [
{ registrationId: 123, amount: 45, reference: "INSCRIPCION 123", url: "https://files.example.com/r/123.pdf" },
{ registrationId: 124, amount: 60, reference: "INSCRIPCION 124", url: "https://files.example.com/r/124.pdf" },
];
const batch = await constaia.batches.create(
{
items: pending.map((p) => ({
fileUrl: p.url,
options: {
checks: { expectedAmount: p.amount, expectedIban: OUR_IBAN, expectedReference: p.reference },
metadata: { registration_id: String(p.registrationId) },
},
})),
options: { expect: "payment_receipt", export: ["csv", "xlsx"], metadata: { run: "2026-09-29" } },
},
{ idempotencyKey: "reconcile-2026-09-29" },
);
console.log(batch.id, batch.status); // bat_…, processingCuando termina recibes el webhook batch.completed con counts (valid, invalid, review…), la lista de
analyses y las URL firmadas de exports.csv y exports.xlsx, válidas 24 horas. Lee cada análisis con
GET /v1/analyses/{id} y usa metadata.registration_id para marcar cada inscripción como pagada. El flujo completo,
con el manejador del webhook, está en Procesamiento masivo con lotes.
Probarlo
Con una clave ck_test_…, un PDF llamado payment_receipt.pdf (o que contenga receipt, justificante o transfer)
devuelve un justificante de 45 EUR a ES7921000813610123456789 con referencia INSCRIPCION 123: con las opciones de
esta página sale valid y dos validaciones iban_checksum superadas. Cambia expected_amount a 50 para ver el
motivo expected_amount con error. Más en Modo test.
Siguientes pasos
Certificado de delitos sexuales (LOPIVI)
Valida el certificado negativo de delitos sexuales de entrenadores y voluntarios (reciente, del titular, sin antecedentes) sin guardarlo.
Facturas a Excel
Extrae número, emisor, receptor, líneas e IVA de facturas en PDF o foto, valida totales y NIF y descarga un Excel por factura o por lote.