Intermedio 11 minFinanzas

Contabilidad: extracción de factures

Respuesta directa

Para extraer facturas en local, ejecuta un modelo de visión (Qwen 3.5 9B, 6,6 GB, o Gemma 4) como servicio con Ollama, impón un esquema JSON a su respuesta y luego valida mediante código: importe sin impuestos (HT) más IVA (TVA) igual al total con impuestos (TTC), SIRET y fechas. Las facturas que no superan los controles pasan a revisión humana. Desde septiembre de 2026, las facturas electrónicas estructuradas llegan sin IA: este pipeline sirve para PDF simples y documentos escaneados.

Introducir facturas a mano es lento y propenso a errores, pero confiar en servicios en línea plantea problemas de confidencialidad. Un modelo de visión local lee la página, un esquema impone la forma del resultado y controles deterministas filtran los errores. Este guía monta el pipeline completo, desde el orden de PDF hasta la importación contable, y recuerda lo que cambia la factura electrónica a partir de septiembre 2026.

Por Mohamed Meguedmi·Actualización 2026-09-30·Probado en Windows, macOS y Linux

#Qué se automatiza y con qué nivel de fiabilidad

A partir de una factura de proveedor en PDF, se busca obtener un JSON estructurado (proveedor, número, fecha, importes sin IVA, IVA y con IVA, líneas) que se pueda importar por lotes al software contable. Un modelo de visión servido mediante Ollama lee directamente la imagen de la página, sin pasar por un OCR separado. La regla que hace fiable esta solución se resume en una frase: el modelo lee, el código comprueba. Los datos extraídos nunca se importan sin superar comprobaciones aritméticas y de identificadores, y todo lo que no las supera pasa a una cola de revisión humana.

Esta guía se centra en un caso concreto: un despacho profesional o una pyme que recibe unos cientos de facturas al mes y las procesa en un ordenador o un pequeño servidor local. Aquí no se promete ninguna cifra de precisión: la precisión depende de tus proveedores, de la calidad de los documentos escaneados y del modelo. El método de medición se explica más abajo, con una muestra de tus propias facturas.

#Factura electrónica: lo que cambia en 2026 y 2027

El kit de IA Local

Tu ChatGPT privado y gratuito en tu máquina en 1 hora — LM Studio, Ollama, Open WebUI, tus documentos, sin nube.

  • Espacio en línea de por vida
  • PDF + archivos
  • Reembolsado 30 j

Antes de construir un pipeline de lectura, hay que ver cómo afecta la reforma a las facturas entrantes. Desde el 1 de septiembre de 2026, según impots.gouv.fr, las grandes empresas y las empresas de tamaño intermedio deben emitir sus facturas a través de una plataforma acreditada, y todas las empresas deben poder recibir facturas electrónicas. Para las pymes, las empresas muy pequeñas y las microempresas, la obligación de emisión se aplica a partir del 1 de septiembre de 2027, según la guía práctica de la administración fiscal.

La consecuencia práctica es doble. Por un lado, una parte creciente de tus facturas entrantes llegará ya en forma estructurada, sin que sea necesaria ninguna lectura mediante IA. Por otro lado, el formato Factur-X, estándar francoalemán de factura híbrida, incluye en un mismo archivo un PDF legible y datos XML para el procesamiento automatizado, con varios perfiles de datos. Cuando un PDF contiene este XML, leerlo directamente es más seguro que pedirle a un modelo que lo deduzca. Por tanto, el flujo de procesamiento que aparece a continuación aplica un orden de prioridad: datos estructurados si existen, después el texto nativo del PDF y, como último recurso, la visión.

i
Lo que esta guía no cubre
La recepción a través de una plataforma acreditada, el e-reporting y las obligaciones de emisión dependen de la plataforma que elijas y del proveedor de tu software contable. Este pipeline se aplica a las facturas que aún llegan como PDF simple o escaneadas: proveedores extranjeros, pequeños prestadores de servicios, tickets de caja y documentos en papel digitalizados.

#Elegir el modelo: lo que la visión requiere en memoria

Para la lectura de facturas, dos familias son adecuadas en la biblioteca Ollama. Qwen 3.5 con 9 mil millones de parámetros pesa 6,6 GB, acepta texto e imagen y anuncia una ventana de 256 000 tokens; su versión 27B pesa 17 GB. Gemma 4, con su variante e4b, ocupa entre 6,6 y 9,5 GB según la ficha Ollama y anuncia 128 000 tokens de contexto, texto e imagen. Una tarjeta de 12 GB es suficiente para la variante 9B o e4b, con margen para imágenes de páginas.

¿Qué herramienta para qué tipo de PDF?
Tipo de archivoHerramientaPor qué
PDF Factur-X o XML integradoLectura directa de XMLDatos exactos, ningún riesgo de error de lectura
PDF nativo con texto seleccionableExtracción de texto, luego LLM de textoRápido, sin procesar imágenes
PDF escaneado o foto claraModelo de visión (Qwen 3.5 9B, Gemma 4)Lee la disposición de la página y las tablas
Escaneo de baja calidadOCR Tesseract, luego revisión humanaLa visión se pierde en el ruido; un humano tomará la decisión

Referencia de memoria del sitio: un modelo de 9 mil millones de parámetros en Q4 pesa aproximadamente entre 5 y 6 GB, a los que se suman la caché de contexto y las imágenes de la página. Las páginas de facturas de varias páginas suponen un coste mayor que las demás: un lote de diez páginas en una sola solicitud puede superar la ventana de contexto predeterminada de Ollama, que sigue siendo muy inferior a los 256 000 tokens anunciados por el modelo mientras no la amplíes.

#Clasificar los PDF antes de leerlos: nativos, escaneados, estructurados

Detectar el tipo de archivo evita enviar innecesariamente una imagen a un modelo de visión. Con la biblioteca PyMuPDF, un PDF cuyo texto extraído supera unos cientos de caracteres es nativo; un PDF casi sin texto es un documento escaneado. Los archivos Factur-X contienen un archivo XML adjunto que se puede listar antes de cualquier otro procesamiento.

Enrutar según el tipo de PDF
import fitz  # PyMuPDF

def type_pdf(chemin):
    doc = fitz.open(chemin)
    pieces = doc.embfile_names()  # pièces jointes intégrées
    if any(n.lower().endswith('.xml') for n in pieces):
        return 'structure'
    texte = ''.join(page.get_text() for page in doc)
    return 'natif' if len(texte.strip()) > 300 else 'scan'

Para los escaneos de baja calidad, Tesseract sigue siendo una alternativa de respaldo gratuita y sin conexión: en ese caso hay que instalar el archivo de idioma francés y escanear a 300 puntos por pulgada. La guía sobre Tesseract detalla los ajustes. Un texto obtenido de un mal OCR nunca debe pasar sin revisión: la cola de revisión humana toma el relevo.

Instalar Tesseract con soporte para el francés
# macOS
brew install tesseract tesseract-lang

# Ubuntu / Debian
sudo apt install tesseract-ocr tesseract-ocr-fra

#Extraer con un modelo de visión y un esquema impuesto

Ollama puede restringir la respuesta del modelo a un esquema JSON: según su documentación, se proporciona un esquema en el campo format, y se recomienda repetirlo también en el prompt para orientar la respuesta. Es mucho más robusto que pedir «un JSON» en texto libre, porque las claves y los tipos quedan impuestos. Para las imágenes, la API REST espera imágenes codificadas en base64 en el campo images del mensaje.

Extracción estructurada de una factura (Ollama, visión)
import base64, json, requests
from io import BytesIO
from pdf2image import convert_from_path

SCHEMA = {
  'type': 'object',
  'properties': {
    'fournisseur': {'type': 'object', 'properties': {
      'nom': {'type': 'string'},
      'siret': {'type': ['string', 'null']},
      'tva_intra': {'type': ['string', 'null']}}},
    'facture': {'type': 'object', 'properties': {
      'numero': {'type': 'string'},
      'date': {'type': 'string'},
      'echeance': {'type': ['string', 'null']}}},
    'montants': {'type': 'object', 'properties': {
      'ht': {'type': 'number'}, 'tva': {'type': 'number'},
      'ttc': {'type': 'number'}, 'devise': {'type': 'string'}}},
    'lignes': {'type': 'array', 'items': {'type': 'object', 'properties': {
      'description': {'type': 'string'}, 'quantite': {'type': 'number'},
      'pu_ht': {'type': 'number'}, 'total_ht': {'type': 'number'}}}}
  },
  'required': ['fournisseur', 'facture', 'montants']
}

PROMPT = ("Tu extrais les données d'une facture française. "
  "Réponds uniquement par un JSON conforme à ce schéma : " + json.dumps(SCHEMA) +
  ". Si une donnée est absente ou illisible, mets null. Les montants sont des nombres (1234.56), "
  "les dates au format AAAA-MM-JJ. N'invente rien.")

def pages_b64(pdf, dpi=200):
    sortie = []
    for img in convert_from_path(pdf, dpi=dpi):
        buf = BytesIO(); img.save(buf, format='PNG')
        sortie.append(base64.b64encode(buf.getvalue()).decode())
    return sortie

def extraire(pdf):
    r = requests.post('http://localhost:11434/api/chat', json={
      'model': 'qwen3.5:9b', 'stream': False, 'format': SCHEMA,
      'messages': [{'role': 'user', 'content': PROMPT, 'images': pages_b64(pdf)}],
      'options': {'temperature': 0, 'num_ctx': 16384}})
    return json.loads(r.json()['message']['content'])

Tres opciones merecen una explicación. La temperatura a cero hace que la salida sea reproducible. El parámetro num_ctx amplía la ventana, ya que las imágenes de varias páginas consumen rápidamente la reducida ventana predeterminada de Ollama; la guía sobre la ventana de contexto detalla este mecanismo. Por último, la instrucción «No inventes nada», junto con la posibilidad de usar el valor null, reduce el riesgo más grave: que un modelo rellene un campo ilegible con un valor plausible. Un esquema estricto impone la forma de la respuesta, no la exactitud de su contenido.

#Extender el esquema a tus casos reales

El esquema básico cubre la mayoría de las facturas de proveedores comunes. Las extensiones a considerar dependen de tu actividad. Añádelas una por una y mide el efecto de cada una sobre tu muestra: un esquema demasiado pesado degrada la lectura de los campos esenciales.

Anticipo y saldo pendiente
Añade un campo acompte_paye. Sin él, el total con impuestos incluidos no coincide con el importe pendiente de pago.
Referencias de pedidos
Un campo bon_commande_ref permite cotejar las facturas con tus pedidos.
Gastos de envío y descuentos
Una línea dedicada o el campo port_ht; de lo contrario, la suma de las líneas no coincide con el total sin IVA.
Desglose del IVA
Una lista con el tipo, la base y el importe. Indispensable cuando la factura combina varios tipos.
Imputación contable
No le pidas al modelo que adivine la cuenta contable: genera una propuesta, identificada como tal, que tu regla de negocio o una persona confirme.

#Validar antes de importar: las comprobaciones que detectan errores

Es el paso que determina la calidad del sistema. Cada comprobación es determinista y, por tanto, más fiable que el modelo. La primera es aritmética: el importe sin impuestos (HT) más el IVA (TVA) debe ser igual al importe con impuestos (TTC), con una tolerancia de unos céntimos por los redondeos. La segunda comprueba las líneas de la factura: su suma debe coincidir con el importe sin impuestos. La tercera comprueba las fechas: no deben ser anteriores a un límite razonable ni estar en el futuro. La cuarta comprueba el SIRET.

La validación del SIRET merece especial atención. El número tiene 14 dígitos y el último es un dígito de control calculado con la fórmula de Luhn, según Wikipedia. Existe una excepción: los establecimientos de La Poste, cuyo SIREN es 356000000, siguen otra regla en la que la suma de los 14 dígitos debe ser un múltiplo de 5. Una validación ingenua con Luhn rechazaría por error facturas de La Poste. El código siguiente gestiona los dos casos.

Controles de coherencia de una factura extraída
from datetime import date

def luhn_ok(s):
    total = 0
    for i, c in enumerate(reversed(s)):
        d = int(c)
        if i % 2 == 1:
            d *= 2
            if d > 9:
                d -= 9
        total += d
    return total % 10 == 0

def siret_valide(s):
    if not (s.isdigit() and len(s) == 14):
        return False
    if s.startswith('356000000'):  # La Poste : somme multiple de 5
        return sum(int(c) for c in s) % 5 == 0
    return luhn_ok(s)

def valider(f):
    erreurs = []
    m = f['montants']
    if abs(m['ht'] + m['tva'] - m['ttc']) > 0.02:
        erreurs.append('HT + TVA différent du TTC')
    lignes = f.get('lignes') or []
    if lignes and abs(sum(l['total_ht'] for l in lignes) - m['ht']) > 0.05:
        erreurs.append('somme des lignes différente du HT')
    siret = (f['fournisseur'].get('siret') or '').replace(' ', '')
    if siret and not siret_valide(siret):
        erreurs.append('SIRET invalide : ' + siret)
    try:
        d = date.fromisoformat(f['facture']['date'])
        if d.year < 2000 or d > date.today():
            erreurs.append('date suspecte : ' + str(d))
    except ValueError:
        erreurs.append('date illisible')
    return erreurs
!
Nunca importar una incoherencia
Una factura cuyo total no coincida debe ir a la cola «a revisar». Una extracción que supera todos los controles no tiene por qué ser correcta, pero las que no los superan deben revisarse sin duda: la revisión humana se centra en ellas.

#Medir la precisión en tus propias facturas

Ninguna cifra de precisión publicada sustituye a una medición sobre tu corpus, porque un despacho que gestiona facturas de mayoristas no tiene los mismos documentos que una asociación. Reúne una muestra de unas cincuenta facturas representativas, introduce manualmente los campos esenciales y luego compara campo por campo.

  1. 01
    Preparar la muestra
    Toma facturas reales variadas: documentos escaneados, PDF nativos, facturas de varias páginas y de distintos proveedores. Anonimízalas si compartes los resultados.
  2. 02
    Introducir los datos de referencia
    Anota a mano los campos que se deben automatizar: número, fecha, importe sin impuestos (HT), IVA (TVA), importe con impuestos (TTC), SIRET.
  3. 03
    Comparar campo por campo
    Calcula el porcentaje de exactitud por campo, no globalmente: un modelo puede leer las fechas perfectamente y equivocarse en los datos del IVA.
  4. 04
    Establecer un umbral de automatización
    Decide qué campos pueden importarse sin revisar nuevamente. Los montos, si pasan el control aritmético, son buenos candidatos; la imputación contable, nunca.
  5. 05
    Volver a ejecutar con cada cambio
    Cambia de modelo, de resolución o de prompt: vuelve a ejecutar la muestra antes de pasar a producción.

#Procesar una carpeta de facturas por lotes

El procesamiento por lotes encadena la clasificación, la extracción y la validación, y luego guarda cada factura según el resultado. Conserva siempre los dos archivos juntos: el PDF original y el JSON extraído.

Procesamiento por lotes con cola de revisión
from pathlib import Path
import json

def traiter(dossier_in, dossier_ok, dossier_revue):
    for pdf in Path(dossier_in).glob('*.pdf'):
        try:
            f = extraire(str(pdf))
            erreurs = valider(f)
        except Exception as e:
            f, erreurs = {}, ['échec extraction : ' + str(e)]
        dest = Path(dossier_ok if not erreurs else dossier_revue)
        (dest / (pdf.stem + '.json')).write_text(
            json.dumps({'donnees': f, 'erreurs': erreurs}, ensure_ascii=False, indent=2))
        pdf.rename(dest / pdf.name)
        print(('OK ' if not erreurs else 'A VERIFIER ') + pdf.name)

#Introducir los datos en el software contable

Cada editor tiene su formato de importación y sus especificaciones evolucionan: empieza por la documentación de tu herramienta, no por un modelo genérico. Los softwares de contabilidad ofrecen normalmente importación por archivo estructurado o una interfaz de programación. Lo más seguro es generar un archivo de importación conforme, cargarlo en un directorio de prueba, luego comparar las escrituras generadas con las que habrías introducido tú mismo.

Conserva la pista de auditoría: el PDF original, el JSON extraído, la versión del modelo y la fecha de procesamiento. En caso de inspección, debes poder remontarte desde el asiento contable hasta el documento justificativo. Las facturas contienen datos personales y comerciales: trabajar en local evita confiárselos a un tercero, pero el almacenamiento de los archivos sigue sujeto a tus normas de conservación y seguridad.

FAQ
¿Un LLM local puede leer una factura escaneada?+
Sí, un modelo de visión como Qwen 3.5 9B o Gemma 4 lee la imagen de una página y extrae los campos. La fiabilidad depende de la calidad del escaneo y de la disposición del contenido en la página. Por tanto, hay que validar el resultado mediante comprobaciones aritméticas y enviar a una persona cualquier factura que no supere esas comprobaciones.
¿Se necesita un OCR además del modelo de visión?+
No siempre. Un modelo de visión lee directamente la imagen, lo que evita la pérdida de información de un OCR separado. Tesseract sigue siendo útil como recurso de respaldo para documentos escaneados muy deteriorados y para PDF nativos de los que se puede extraer directamente el texto. Prueba ambos con tu muestra en lugar de darlo por supuesto.
¿Cómo garantizar que la salida sea un JSON válido?+
Ollama permite pasar un esquema JSON en el campo format de la API: la respuesta queda entonces restringida a esa estructura. Esto garantiza la forma, no la exactitud de los valores. Añade, por tanto, siempre comprobaciones de coherencia en el código: importe sin impuestos más IVA, SIRET, fechas y total de las líneas.
¿Qué cambia con la factura electrónica obligatoria para este tipo de herramienta?+
Desde el 1 de septiembre de 2026, todas las empresas deben poder recibir facturas electrónicas, y la emisión se vuelve obligatoria para las PYME en septiembre de 2027. Las facturas estructuradas, como Factur-X, se leen sin IA. El pipeline sirve principalmente para los proveedores que aún envían archivos PDF simples o documentos en papel.
¿Qué tarjeta gráfica se necesita para extraer facturas?+
Un modelo de 9 mil millones de parámetros en Q4 pesa aproximadamente 6 GB, a lo que hay que sumar las imágenes de las páginas y el contexto. Una tarjeta de 12 GB es adecuada; una de 8 GB basta para facturas de una página con un contexto reducido. Sin GPU, el procesamiento funciona, pero se vuelve lento: prevé procesarlas durante la noche.
¿Se puede confiar en la imputación contable propuesta por el modelo?+
No, no sin validación. El modelo puede sugerir una cuenta a partir del nombre del proveedor, pero es una propuesta que debe confirmarse mediante una regla de negocio o por el contador. El error de imputación es silencioso: pasa todos los controles aritméticos. Trata este aspecto como el único que siempre exige una revisión humana.
¿Esta guía te ha ayudado?

¿Un comentario, un error, una precisión? Avísanos, eso mejora la guía para todos.