Ir al contenido principal
Agentes IA con visión: PDFs e imágenes en producción

Agentes IA con visión: PDFs e imágenes en producción

AI Integration
7 min readPor Daily Miranda Pardo

Tu agente de texto lleva semanas en producción. Funciona. Entonces llega el mensaje del cliente: "Necesitamos que el agente procese nuestras facturas escaneadas. Son unas 200 al mes, algunas en PDF, otras fotos desde el móvil."

En ese momento, todo lo que construiste para texto necesita una revisión completa. No porque sea difícil, sino porque los fallos de los pipelines multimodales son distintos — y aparecen más tarde, más silenciosos y más caros.

Lo que los LLMs modernos pueden "ver" (y lo que no pueden)

Claude, GPT-4V y Gemini aceptan imágenes en sus APIs. Eso no significa que "lean" cualquier documento como lo haría un humano.

Funcionan bien en producción:

  • Extracción de campos estructurados en documentos con formato consistente (facturas, contratos, formularios)
  • Texto impreso con resolución adecuada (≥ 150 DPI)
  • Tablas simples y relaciones espaciales básicas
  • Clasificación por tipo de documento

Fallan o degradan en producción:

  • Documentos escaneados a baja resolución (fotocopia de fotocopia)
  • Texto manuscrito informal o cursiva densa
  • Tablas con más de 40 filas sin estructura clara
  • PDFs de varias páginas tratados como una sola imagen

Esto no es un límite temporal. Es una característica estructural del modelo. Tu pipeline de preprocesamiento vale tanto como tu prompt.

El primer error: enviar el PDF directamente

// ❌ El antipatrón que funciona en demo y rompe en producción
const fileBuffer = await fs.readFile('factura.pdf');
const base64 = fileBuffer.toString('base64');

const response = await anthropic.messages.create({
  model: 'claude-opus-5',
  messages: [{
    role: 'user',
    content: [{
      type: 'image',
      source: { type: 'base64', media_type: 'application/pdf', data: base64 }
    }, {
      type: 'text',
      text: 'Extrae número de factura, fecha, importe total e IVA.'
    }]
  }]
});

Esto rompe en producción por tres razones concretas:

  1. Tamaño: La API tiene límite de 5 MB por imagen. Un PDF de 10 páginas puede triplicarlo con scans de alta calidad.
  2. Multipágina: La API no renderiza páginas intermedias. Dependiendo del modelo, solo procesa la primera o devuelve un error.
  3. Coste invisible: Un PDF de 5 MB como base64 consume ~28.000 tokens de imagen. Con 200 facturas al mes, tu factura de API se dispara antes de que nadie lo note.

El pipeline correcto: PDF → páginas → imágenes optimizadas

import { PDFDocument } from 'pdf-lib';
import sharp from 'sharp';

async function preprocessInvoice(buffer: Buffer): Promise<string[]> {
  const pdf = await PDFDocument.load(buffer);
  const pageCount = pdf.getPageCount();
  const base64Pages: string[] = [];

  // Procesar solo las páginas relevantes (facturas suelen estar en las 2 primeras)
  const pagesToProcess = Math.min(pageCount, 2);

  for (let i = 0; i < pagesToProcess; i++) {
    const singlePage = await PDFDocument.create();
    const [page] = await singlePage.copyPages(pdf, [i]);
    singlePage.addPage(page);

    const pdfBytes = await singlePage.save();

    // density: 200 = legibilidad sin inflar tokens
    const imageBuffer = await sharp(pdfBytes, { density: 200 })
      .png()
      .resize({ width: 1200, withoutEnlargement: true })
      .toBuffer();

    base64Pages.push(imageBuffer.toString('base64'));
  }

  return base64Pages;
}

density: 200 es el punto de equilibrio para facturas: suficiente resolución para texto pequeño, sin disparar el tamaño del payload. Con resize({ width: 1200 }) mantienes legibilidad y reduces tokens en un 60% frente al PDF original sin procesar.

Cuántas páginas procesar

Define reglas por tipo de documento, no un valor genérico para todo:

  • Facturas: máximo 2 páginas
  • Contratos: página 1 + páginas con firma
  • Albaranes: solo página 1

Procesar las 20 páginas de un contrato para leer solo el NIF del proveedor en la primera es quemar tokens innecesariamente. El clasificador de tipo de documento (un modelo pequeño y barato) decide cuántas páginas necesitas antes de la llamada cara.

Prompt engineering para visión: diferente al texto

Con texto, un prompt vago puede dar resultados aceptables. Con imágenes, la precisión multiplica la calidad de extracción.

const extractionPrompt = `Analiza esta factura y extrae exactamente los siguientes campos.
Responde ÚNICAMENTE con JSON válido, sin texto adicional, sin bloques de código markdown.

{
  "invoice_number": string,      // ej: "FAC-2026-00123"
  "issue_date": string,          // formato ISO: "2026-08-12"
  "supplier_name": string,
  "supplier_tax_id": string,     // NIF o CIF sin guiones
  "subtotal_eur": number,        // importe sin IVA
  "vat_percentage": number,      // ej: 21
  "vat_amount_eur": number,
  "total_eur": number
}

Reglas:
- Si un campo no es legible, usa null (nunca inventes datos)
- Si hay varias fechas, usa la fecha de emisión, no la de vencimiento
- Los importes son números, nunca strings`;

Dos técnicas que elevan la precisión en producción:

  1. Ejemplos de formato en el prompt (ej: "FAC-2026-00123") reducen alucinaciones de formato en ~40%
  2. null explícito en lugar de campo ausente evita que el modelo invente datos cuando el campo no es visible

Añade validación Zod post-extracción para detectar inconsistencias antes de persistir:

import { z } from 'zod';

const InvoiceSchema = z.object({
  invoice_number: z.string().nullable(),
  issue_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable(),
  supplier_name: z.string().nullable(),
  supplier_tax_id: z.string().nullable(),
  subtotal_eur: z.number().positive().nullable(),
  vat_percentage: z.number().min(0).max(100).nullable(),
  vat_amount_eur: z.number().positive().nullable(),
  total_eur: z.number().positive().nullable(),
}).refine(
  (data) => {
    if (data.subtotal_eur && data.vat_amount_eur && data.total_eur) {
      const expected = data.subtotal_eur + data.vat_amount_eur;
      return Math.abs(expected - data.total_eur) < 0.02; // margen de redondeo
    }
    return true;
  },
  { message: 'El total no cuadra con subtotal + IVA — marcar para revisión humana' }
);

Este refine detecta el caso más frecuente de extracción incorrecta: el total que no cuadra. En lugar de insertar un dato malo en el ERP, el registro se marca para revisión humana con el problema específico identificado.

Coste real y cómo controlarlo

Procesar una imagen de 1.200 × 1.600 px con Claude Opus cuesta aproximadamente 1.500–2.000 tokens de imagen. Con 200 facturas al mes:

  • Sin optimización: ~400.000 tokens × $0.015/1K = $6/mes solo en imágenes
  • Con preprocesamiento correcto (resize + PNG optimizado): $2–3/mes

El ahorro viene del tamaño del PNG, no de bajar resolución. Por debajo de 150 DPI el modelo empieza a fallar en caracteres pequeños, especialmente cifras con decimal.

Para clientes con volumen alto, añadimos una capa de semantic cache basado en embeddings: documentos con estructura idéntica (mismo proveedor, mismo template de factura) se sirven desde caché sin llamar a la vision API.

Lo que desplegamos en producción para un cliente de distribución

Un cliente con 400 facturas mensuales de proveedores distintos, formatos variables, algunas escaneadas desde el móvil. El pipeline que construimos en su integración de IA:

  1. Clasificador ligero — modelo pequeño (haiku-class) para identificar tipo de documento y número de páginas relevantes antes de la extracción cara
  2. Preprocesamiento por tipo — reglas distintas para PDFs nativos vs. imágenes escaneadas (para scans bajamos a 150 DPI con contrast boost en sharp)
  3. Validación Zod post-extracción — si total ≠ subtotal + IVA, el registro entra en cola de revisión humana con el error específico, no como un blob genérico
  4. Audit trail en Supabase — cada extracción guarda JSON resultante, página procesada, tokens consumidos y confidence score para trazabilidad y mejora continua

Resultado: 94% de facturas procesadas sin intervención humana. El 6% restante llega al equipo contable con el campo concreto marcado y el motivo del fallo — no como "algo falló, revísalo todo".


Si estás construyendo un pipeline de procesamiento documental y el tiempo de integración se está extendiendo más de lo esperado, el problema suele estar en el preprocesamiento, no en el prompt.

Cuéntame cómo está tu arquitectura actual →

Compartir artículo

LinkedInXWhatsApp

¿Procesos repetitivos en tu empresa?

Descarga gratis el Mapa de Automatización IA — los 5 procesos que más tiempo roban y cómo resolverlos.

Sin spam. Solo el PDF. Puedes darte de baja cuando quieras.

Escrito por Daily Miranda Pardo

Ayudo a empresas a automatizar procesos, crear agentes IA y conectar sistemas inteligentes.