Un despacho de importación empieza con un correo y varios documentos adjuntos (PDFs, docx, xslx, etc.). Dentro vienen facturas, packing list, conocimientos de embarque (B/L), certificados sanitarios y, si la mercancía es pesca, certificados de capturas con sus apéndices de transporte. A veces llegan en ficheros separados, y otras todos pegados en el mismo PDF.
Con esos papeles hay que validar el expediente frente a la normativa, preparar la declaración aduanera (DUA) y presentar los certificados de captura en TRACES, el sistema de la Comisión Europea. Con una agencia de aduanas de referencia construimos la plataforma que hace ese trabajo, un robot que automatiza la ingesta de los documentos, extracción y estructuración de datos, validación de los mismos contra normativas legales y generación y emisión a los sistemas de la administración aduanera española y europea. Este artículo cuenta la parte más difícil: leer los datos. Explicamos la arquitectura que tenemos hoy, los modelos que hemos usado y descartado, las medidas que nos hicieron cambiar de idea y los errores que encontramos por el camino. Todo sale del historial del proyecto.
01El problema: un dígito cuesta caro
En un documento aduanero no hay errores pequeños:
- Un peso neto con un dígito cambiado descuadra la declaración y los controles sanitarios.
- Un número de contenedor con una letra mal leída rompe el cruce con la naviera.
- Una fecha leída como mes/día en vez de día/mes coloca una firma después de la salida del buque, y eso invalida un certificado.
Y los documentos reales no ayudan. Estos son los casos que nos hemos encontrado en expedientes reales:
- Escaneos a 150 ppp, torcidos, con grano, con un sello encima de la cifra que importa.
- Páginas de lado o del revés dentro de un PDF por lo demás correcto.
- Documentos mezclados: un PDF de 41 páginas con 20 documentos dentro. Un certificado zoosanitario aparece dos veces, una como borrador sin firmar y otra firmada.
- Documentos mixtos: PDFs con capa de texto donde un fragmento, a veces menos del 10 % de la página, está rasterizado y es justo el dato clave.
- Tablas que continúan en otra página sin cabecera, y packing lists de 59 filas repartidas en cinco hojas.
- Convenciones numéricas cruzadas en la misma página:
21995.050sin separador,39,643.12con coma de miles y50,000CBM con la coma como decimal. - Ruido impreso: certificados que imprimen
3321.3900000000003porque el sistema que los generó no redondea la coma flotante.
El riesgo que más nos preocupa no es fallar. Lo que más nos preocupa es fallar sin que nadie se entere.
Lo peligroso es una extracción que parece completa, con todos los campos rellenos, y que lleva dentro un número equivocado.
02Primera arquitectura: un modelo multimodal lo hace todo
El diseño inicial era el que hoy parece obvio. Un LLM con visión recibe la página y devuelve directamente el JSON con los campos del documento, y otro LLM valida el expediente contra las reglas del trámite. El documento de decisiones del proyecto lo justificaba así frente a Tesseract y frente a Google Vision o AWS Textract:
| Criterio | OCR clásico + reglas | OCR en la nube | LLM multimodal |
|---|---|---|---|
| Salida estructurada | Expresiones regulares y parser propio | Texto plano | JSON directo desde el esquema |
| Sellos, manuscrito, tablas irregulares | Débil | Solo texto | Entiende estructura y contexto |
| Excepciones («importe = base + IVA ± 0,01») | Hay que programarlas | Hay que programarlas | El modelo razona |
El stack que se eligió inicialmente y se mantiene: Python 3.11, Apache Airflow para los pipelines, FastAPI, PostgreSQL con JSONB, React en el frontal y los modelos servidos por Ollama. Pronto aprendimos tres cosas.
Los modelos cambian por debajo de ti
Ninguno de los modelos con los que arrancamos sigue en la extracción. Una arquitectura que confía en «el modelo» queda a merced del siguiente cambio. La conclusión fue que el modelo tenía que ser una pieza sustituible y medible, no el centro del sistema.
El modelo no avisa cuando se equivoca
Con los primeros expedientes reales aparecieron las primeras divergencias entre documentos del mismo expediente. Casi nunca eran discrepancias reales: eran malas lecturas a 150 ppp, como un lote 31690-1 leído 31590-1 o dos dígitos trasladados. El modelo devolvía cada valor con la misma seguridad. Respondimos con módulos deterministas:
- la comprobación del dígito de control ISO 6346 de los contenedores, primero;
- la caracterización de divergencias de un solo carácter, por confusión documentada o por transposición de adyacentes, después.
Al validador hay que darle hechos, no pedirle criterio
Después auditamos validaciones reales:
- La pregunta «¿aparece este documento obligatorio entre los aportados?» es aritmética, y el validador fallaba en la mitad de los casos aunque el documento faltaba de verdad.
- Una firma de inspector posterior a la fecha de salida, que es un hecho objetivo de los datos, solo se detectaba en 1 de cada 4 validaciones.
Desde entonces, todo lo que se puede comprobar con código se comprueba con código y entra en el prompt del validador como hecho ya verificado. Al modelo le queda el criterio, no la aritmética.
03El giro: digitalizar por fases y registrar cada decisión
Arreglar caso a caso sobre la extracción de una sola llamada tenía un límite: no sabíamos por qué salía cada valor. Por eso empezamos un pipeline nuevo, primero en modo de solo lectura sobre expedientes reales, con tres principios:
- Cada decisión queda registrada. Toda decisión, sea del modelo o determinista, se escribe en un registro con su fase, el resultado, el motivo, la evidencia, las alternativas descartadas y la fuente:
regla,medida,modeloocomparacion. No hay descartes silenciosos. - Nunca adivinar en silencio. Si no hay un único valor defendible, el campo queda vacío con los candidatos y el motivo, y pasa a revisión humana.
- Lo verificable manda sobre lo probable. Un dígito de control, la capa digital del PDF o una suma que cuadra pesan más que el consenso de un modelo.
Y una regla de método, escrita en la propia documentación del pipeline: coste y tiempo no son la restricción; el objetivo es el mínimo de errores de lectura.
Cada fase es reanudable: se puede volver a ejecutar la coherencia sobre un registro ya escrito sin repetir OCR ni llamadas al modelo.
Entró en producción después de un tiempo de prueba en preproducción, con migraciones de base de datos ensayadas antes sobre una copia de producción.
04Formato e imagen: medir antes de tocar
El tipo real sale de la firma del fichero, no de la extensión. Hay PDFs que son JPEG renombrados y «Excel» que son HTML. Los ficheros de oficina se leen directamente de su XML o de sus celdas, sin OCR.
De cada página PDF se mide si tiene capa de texto, qué imágenes trae y con qué resolución nativa, el /Rotate declarado y la orientación del texto. Después se renderiza y se aplican mejoras condicionadas a lo medido, con registro del antes y el después:
- Brillo: el blanco del papel está por debajo de 200.
- Contraste: el rango entre los percentiles 2 y 98 es menor de 80.
- Nitidez: el índice de nitidez baja de 0,16.
Una página de solo texto vectorial no se toca.
El giro: por qué preguntamos cuatro veces
La primera idea fue preguntar al modelo, con una imagen, cuántos grados había que girarla. Lo medimos con unas 700 llamadas sobre páginas reales y sintéticas:
| Método | Acierto en páginas reales |
|---|---|
| Una imagen y «¿cuántos grados hay que girarla?» | 67 % |
| Bucle cerrado: aplicar el giro y volver a preguntar | 85 % |
| Cuatro versiones barajadas (0°, 90°, 180°, 270°) y «¿cuál está derecha?» | 98,2 % · 100 % por mayoría |
Con una sola imagen, en las páginas de lado el modelo acertaba el sentido solo el 29 % de las veces, y el bucle cerrado no convergía en un 13 % de los casos. Además, la confianza que devolvía el modelo era siempre 1,0, acertara o fallara.
Un PDF real completo de 24 páginas añadió un matiz: la votación fallaba en 3 de 17 páginas digitales de texto denso con marca de agua. La regla final quedó así:
- Las páginas con texto horizontal legible en su capa digital no se votan.
- El resto se vota tres veces y solo se gira con unanimidad. Sin ella, la página se queda como está y pasa a revisión.
05OCR y fusión: transcribir fiel y no fiarse del orden
El OCR es una llamada por página al modelo de visión. Se le pide una transcripción literal línea a línea, sin normalizar números ni fechas, y que marque [ilegible] en la línea concreta en lugar de dar por perdida la página.
Los bucles de repetición
En un expediente real de 55 páginas, 3 páginas devolvieron JSON roto. No eran respuestas largas: eran bucles, el modelo repitiendo líneas de una tabla de casillas vacías hasta agotar el presupuesto de tokens. La investigación dio tres lecciones:
temperature=0empeora los bucles. Un estudio que consultamos mide 48 veces más bucles que con temperatura 1,0. Usamos 0,1 con penalización de repetición y una ventana amplia.- Un prompt de texto idéntico en cada llamada puede colisionar con la caché del servidor. Cada llamada lleva ahora una referencia aleatoria.
- El arreglo de fondo era de prompt, no de parámetros: pedir explícitamente que no enumere casillas vacías y que nunca repita una línea ya escrita.
Con los modelos de razonamiento apareció la versión moderna del problema: deepseek y minimax piensan por defecto y se comían el presupuesto de salida antes de transcribir nada. Hay que llamarlos con think: false para el OCR.
Fusionar con la capa digital sin duplicar
Cuando la página tiene capa de texto, cada línea del OCR se ancla a la línea digital con la que más «tokens fuertes» comparte: palabras de cuatro letras o más, o números comparados sin separadores. Así no dependemos del orden de lectura, que en un formulario de dos columnas casi nunca coincide. Cada línea queda cubierta, en conflicto_cifra (se conservan las dos versiones) o huerfana, que es contenido que la capa digital no tiene, como un sello.
Una página es mixta si tiene 40 caracteres huérfanos o más, o 2 cifras huérfanas o más. El umbral es absoluto a propósito: un sello de cuatro palabras con el dato clave se diluye en cualquier porcentaje de página.
06Clasificar y dividir: la numeración manda
Las páginas se clasifican por texto en ventanas de 6 páginas con paso 3, de forma que casi cada página se juzga dos veces. El desacuerdo entre esas dos lecturas es una señal de calidad gratuita. El corte entre documentos se decide con campos literales, en este orden de autoridad:
- Numeración: dos páginas con numeración coherente nunca se separan aunque el modelo les asigne tipos distintos. Se asume que se equivocó la etiqueta.
- Identificador distinto, o un inicio de documento declarado.
- Que el modelo diga «no es continuación», como último recurso.
Cada documento sale con un estado documental (provisional, copia, definitivo, anulado o sin marca), sacado de las marcas estampadas. Una marca solo cuenta si ocupa ella sola su línea, para no confundir la palabra «Sello» de una casilla vacía con un sello real. En un expediente de 41 páginas y 20 documentos detectó las 6 páginas marcadas sin ningún falso positivo.
La división genera un PDF por documento copiando las páginas originales sin volver a renderizarlas, así que la capa de texto llega intacta a las fases siguientes.
07Extracción: tres lecturas ciegas y una decisión por campo
Este es el núcleo, y el diseño cambió tres veces: una lectura; dos lecturas con una tercera «dirigida» al campo en discusión; y, por último, tres lecturas independientes y ciegas, en las que ninguna ve lo que respondieron las otras y cada campo se decide por mayoría. Las tres se guardan siempre. Si las tres difieren, el campo va a revisión con las tres a la vista.
Dos lecturas que coinciden no son dos lecturas que aciertan.
Por eso descartamos leer los campos de lista página a página, que hacía converger lecturas en el mismo valor incorrecto, y por eso la mayoría no es la última palabra.
Los números se deciden en código, no en el modelo
Para los campos numéricos al modelo se le pide el literal tal como está impreso. Si le pides el número ya «decidido», normaliza, y lo hace también donde la convención real era otra. La decisión la toma una cascada determinista que se detiene en el primer criterio que deja un solo candidato:
| Regla | Criterio |
|---|---|
| R1 | Literal inequívoco |
| R2 | Identidades aritméticas del propio documento (cantidad × precio = importe, Σ = total) |
| R3 | Mayoría por fuentes distintas, no por apariciones |
| R4 | Rango plausible del campo |
| R5 | Convención del documento, solo como desempate |
| R6 | Vacío con motivo y candidatos, nunca un valor adivinado |
La convención de miles y decimales no es del documento, es de cada campo. Sobre esa base hay comprobaciones que ya han cazado errores reales:
- Cuadre por testigo: la suma de una columna se confirma contra el total impreso.
- Fechas por columna: DD/MM o MM/DD se decide para toda la columna, nunca celda a celda.
- Bultos leídos como kilos: un packing list cuyo desglose eran 1.998 cartones se había leído como kilos. Si la suma de pesos por línea coincide con el total de bultos y diverge de los pesos conocidos del expediente, se anulan esos pesos.
Un caso que nos enseñó a medir con el pipeline completo
Un certificado escaneado de 7 páginas salió con 15 partidas cuando eran 9, y las tres lecturas traían las 15. La causa estaba en nuestro troceo:
- Las imágenes se mandan en lotes de como mucho 4 imágenes o 4 MB, y el texto OCR de todas las páginas viajaba pegado solo al primero.
- El primer lote ya veía las 9 filas a través del texto, y el segundo volvía a leer las de sus páginas desde la imagen.
- La marca delatora: las filas que venían del OCR tenían coma decimal y las que venían de la imagen, punto.
El primer arreglo corregía ese certificado, pero hacía que un packing list de 59 filas perdiera las 11 de su última página, una continuación sin cabecera, en torno a un 30 % de las veces. Con un aviso en el texto del lote («extrae también las filas de las páginas que no se muestran como imagen») falló 0 de 28 veces (prueba exacta de Fisher, p ≈ 0,002). Añadimos un árbitro: si las lecturas difieren en número de filas, gana la que cuadra con el total impreso. «Gana la más larga» se descartó porque en el histórico, sobre 1.192 tablas, la más larga no acertó ni una vez en los desacuerdos.
La capa digital prevalece
Un valor que aparece literalmente en la capa de texto del PDF, o en las celdas de un Excel, no ha pasado por un reconocimiento óptico y no puede tener un carácter cambiado. Cada valor lleva su procedencia (digital, OCR o conflicto) y, cuando dos documentos se contradicen, manda el digital sin votación. La procedencia se comprueba por valor, no por página, porque una página mixta tiene parte digital y parte escaneada.
Después lo extendimos por un caso real: una guarda no reconoció un packing list titulado «Certificado de empaque» y las tres lecturas salieron vacías, mientras la capa digital traía todos los datos. Ahora la capa digital prevalece también cuando el modelo no devuelve nada.
08Zonas: saber de qué casilla sale cada cifra
Un OCR por página devuelve una lista plana de líneas, y en un formulario de dos columnas las etiquetas contiguas se funden:
I.24. Número total de bultos I.25. Cantidad total I.26. Peso neto total I.26. Peso bruto total 1320 Caja de cartón 22440 kg 23760 kg
El extractor reconstruía por contexto cuál era el neto y cuál el bruto. Acertaba, pero no era auditable. La fase de zonas divide la página antes de leer, con tres detectores en orden:
- Bloques de la capa de texto, exactos y gratuitos donde la hay.
- Morfología con OpenCV sobre la imagen girada. En un certificado real saca sus 39 casillas, y en un packing list sin líneas no inventa ninguna.
- El modelo señalando la tabla sobre una rejilla de referencia, aceptado solo si tres rejillas distintas coinciden.
09Coherencia: el expediente como testigo
Cuando las tres lecturas no dan mayoría, el resto del expediente suele tener la respuesta. A partir de un catálogo de 41 cruces candidatos, la coherencia aplica decisores deterministas sobre el conjunto de candidatos. Nunca inventa un valor que ninguna lectura dio:
- Dígito de control ISO 6346 del contenedor.
- Código de nomenclatura contra la especie declarada.
- Identificadores con formato rígido que se validan solos, como los códigos REX o el del puesto de inspección fronterizo.
- Mayoría entre documentos distintos, no entre lecturas del mismo. Con dos declarantes un desacuerdo nunca tiene mayoría, así que buscamos declarantes nuevos, como el B/L escondido en una casilla del certificado sanitario.
En la demo que grabamos con documentos ficticios, el apéndice escaneado leyó el contenedor como CCVU4182735. No cumple el dígito de control, y otros cuatro documentos declaran OCVU4182735, que difiere en una sola letra. La plataforma lo corrige y deja el campo marcado para revisión.
Aplicar correcciones sin perder información
- Catálogo explícito de decisiones que pueden corregir. Lo demás no toca nada.
- Correcciones aditivas: la lectura original nunca se sobrescribe, así que siempre se puede decir qué leyó la extracción y qué aplicó la coherencia.
- Compare-and-swap: solo se corrige donde el valor sigue siendo el que examinó el cruce. Protege celdas con dos códigos, como un precinto legítimo junto a una mala lectura.
- Aplicar no es cerrar la revisión. La revisión solo se cierra cuando el código verifica el valor. Una corrección por corroboración o por juicio de un modelo se aplica con reservas y el campo sigue en la cola.
10Un árbitro externo como fallback: un servicio de terceros especialista
Los campos que siguen pendientes de revisión al terminar la coherencia se mandan a la API de un servicio especialista en extracción y estructuración de datos con el esquema del propio tipo de documento: una sola llamada parsea y extrae. Ese contraste destapó un fallo en nuestro esquema. Decía «8 dígitos» para el código de nomenclatura, y ante un certificado que lo imprime a 6 (030743), cualquier extractor lo inflaba a 03074300 para cumplir la instrucción.
El árbitro hace una llamada por página, cacheada por hash, y solo sustituye los campos que estaban en revisión. En la misma página y con el mismo esquema, devolvió una descripción correcta en 3 de 5 llamadas y confundió la columna en las otras 2. Sobrescribirlo todo habría importado ese ruido.
Que las cuentas cuadren, sin tolerancia
Facturas y packing lists se comprueban por su propia aritmética. Si las lecturas divergen o no cuadran, se contrasta con el fallback: si lo suyo cuadra se graba, y si no queda en revisión. La tolerancia es la del redondeo de lo impreso y nada más. Con el 0,5 % que usábamos antes pasaba un error de 5.000 sobre 5,8 millones.
El caso que nos hizo cambiar fue un escaneo en el que las tres lecturas eran idénticas y llevaban un dígito de más en el total.
Ni la mayoría ni la unanimidad garantizan nada; la aritmética, sí.
11Qué modelo, y cómo lo elegimos
Más tarde comparamos tres candidatos con el pipeline completo sobre un expediente de 7 documentos y 79 páginas, usando el fallback como árbitro (había acertado 24 de 24 campos verificados a mano):
| gemma4 | deepseek-v4.1-flash | minimax-m3.1-flash | |
|---|---|---|---|
| Campos de certificados escaneados (de 60) | 24–25 | 59 | 56 |
| Giro con ángulo conocido (de 24) | 17 | 22 | 24 |
| Certificados mal partidos (de 11) | 4–5 | 1 | 10 |
| Tiempo total | 56–57 min | 47 min | 74 min |
| Reintentos de OCR | 1–2 | 0 | 17 |
gemma4 leía mal el año de las fechas en 18 o 19 de 20 casos, y entre dos pasadas suyas solo coincidía en el 86 % de los campos. Un segundo contraste, con 40 ficheros de 5 emisores, nos obligó a matizar:
- En precisión hubo empate, en torno al 88–90 %.
- Donde hubo desacuerdo estable,
deepseekacertó 53 veces ygemma4, 20. deepseekdeja sin rellenar un 18 % más de valores, lo que para nosotros es mejor que rellenarlos mal.
Hoy la extracción usa deepseek-v4.1-flash con think: false. Antes de adoptarlo hubo que corregir nuestro parser, que descartaba sus respuestas cuando devolvía un objeto suelto en vez de una lista. Antes de culpar al modelo, conviene leer lo que contesta.
También probamos un modelo decisor externo, pplx-decider: acertó 24 de 24 orientaciones y 22 de 24 clasificaciones, con un error dado con confianza 0,93. No le dimos autoridad. Un decisor seguro de sí mismo y equivocado es justo lo que intentamos evitar.
12El método
- Siempre con expedientes reales, procesados de punta a punta en preproducción. Las cifras sintéticas solo orientan.
- Diagnosticar contra el papel: antes de corregir se relee el literal crudo del registro y, si hace falta, se vuelve a renderizar la página al mismo DPI que ve el modelo.
- Medir con el pipeline completo y con varios tipos de documento. Un arreglo correcto para un documento puede romper otro.
- Revisión de diseño independiente: ante una duda real consultamos a un segundo modelo, siempre un Frontier MoE, y verificamos su propuesta contra el código y los datos antes de aplicarla.
- Cada commit cuenta el caso real que lo motivó. Así, más de mil commits se leen como un cuaderno de laboratorio.
13Lo que aún no está resuelto
- La cobertura de los documentos mixtos por regiones: corroborar por la posición real de cada imagen en la página, no solo por línea.
- Más casos con verdad conocida: expedientes con cada campo verificado a mano, que son los que permiten medir de verdad cada cambio.
- Las zonas como fuente de cada valor en todos los tipos de documento, no solo en los formularios de casillas.
Conclusiones
Los modelos de visión son excelentes leyendo y malos avisando de que se han equivocado. El sistema que los rodea tiene que hacer esa segunda parte: medir antes de transformar, leer más de una vez sin contaminar las lecturas, preferir lo verificable a lo probable, usar el expediente entero como testigo y dejar escrita cada decisión. Lo que no puede resolver con certeza, lo pone delante de una persona con la evidencia al lado.
Hay una distancia tecnológica importante entre la lectura de un documento cualquiera que te hace una aplicación de IA (como un ChatGPT, Claude, u otras) y un robot Agente de Extracción de Datos que automatiza el proceso industrial de miles de documentos heterogéneos, y garantiza fiabilidad, trazabilidad, seguridad y escalabilidad.