Un PDF de proveedor nunca fue diseñado para ser parseado. Es una página — columnas, cajas de texto flotantes, celdas combinadas, notas al pie. Escribir un parser determinístico para PDFs arbitrarios es un hoyo de diez años.
El formato PDF en sí mismo es el enemigo. Un PDF no almacena 'una tabla con 5 columnas y 12 filas'. Almacena instrucciones para dibujar glifos en coordenadas específicas de una página. Dos celdas que se ven adyacentes en el renderizado pueden estar a páginas de distancia en el stream de texto interno. Un precio que aparece en la tercera columna visualmente puede ser el primer fragmento de texto en el archivo. PDF fue diseñado para impresoras, no para parsers — y el pipeline de exportación de cada proveedor produce un sabor diferente de caos.
Texto primero, estructura después
Usamos pypdf para extraer texto crudo de cada página, concatenamos con double newline, y dejamos que el LLM razone sobre la estructura. Grilla, grupo, unidad, columna de cantidad — el modelo los capta por contexto.
Esta es la decisión arquitectural clave: no intentamos parsear el layout interno del PDF. Extraemos texto, lo aplanamos, y se lo entregamos a un modelo que entiende estructura de lenguaje natural. El modelo ve algo como 'SKU: AB-001 Pieza A 12 pzas $14.50' y sabe — por contexto y entrenamiento — que esto es un ítem de línea con código, descripción, cantidad, unidad y precio. Sin plantillas por proveedor, sin configuración de mapeo de columnas, sin regex frágil que se rompe cuando el proveedor cambia sus ajustes de exportación.
La red de seguridad del JSON schema
Al modelo nunca se le pide texto libre. El structured output de IA impone un schema estricto: grupo, item, código, cantidad, unidad, precio. Si el PDF es solo imagen (scan sin capa de texto), retornamos `unreadable_file` antes de gastar tokens.
El schema es el contrato. El modelo no puede devolver 'creo que esto es un ítem de línea pero no estoy seguro'. Debe devolver un array de objetos QuoteSection, cada uno conteniendo ítems QuoteLine con campos tipados. Si el texto está distorsionado o es ambiguo, el modelo devuelve su mejor interpretación — pero siempre en el schema. El código del importador nunca se ramifica sobre el contenido de la respuesta; solo valida la forma. Esta separación de responsabilidades significa que el pipeline de importación es testeable con JSON mockeado, y el prompt de la IA puede iterarse de forma independiente.
Modos de falla La mayoría de las fallas son aguas arriba — un PDF que en realidad es una foto de papel. La solución es out-of-band: le pedimos al usuario re-exportar del portal del proveedor. Ninguna IA arregla un documento sin texto.
Los PDFs solo de imagen son la falla más común, pero no la única. PDFs protegidos con contraseña, archivos corruptos que abren en el visor pero fallan al parsearse, y archivos con fuentes embebidas que mapean glifos a code points Unicode inesperados — todos aparecen en producción. Tratamos cada uno con un código de error específico — unreadable_file, encrypted_file, corrupted_file — para que el frontend renderice un mensaje accionable. 'Este archivo está protegido con contraseña. Por favor quítale la contraseña e intenta de nuevo' es más útil que 'Falló la subida'.
