CASO DE ESTUDIO · PROYECTO PROPIO · IA Y AUTOMATIZACIÓN
Automatización de facturas con IA
Pipeline de extremo a extremo que convierte facturas en PDF y texto en datos estructurados, guardados en PostgreSQL y expuestos en una API REST desplegada en Azure.
Video de presentación del proyecto.
Contexto
Las facturas en PDF y texto son documentos no estructurados. Este sistema automatiza de extremo a extremo la extracción, validación, persistencia y exposición de sus datos contables.
El resultado se expone como una API REST documentada con Swagger, lista para integrarse con sistemas externos y con flujos de automatización en n8n.
Lo que construí
- Extracción con IA: usa la API de Groq con el modelo
gpt-oss-120bpara estructurar proveedor, fecha, CUIT, número y total en JSON validado, a partir de PDF y TXT. - API REST: FastAPI con documentación OpenAPI (Swagger) pública. Endpoints para consultar y filtrar facturas e integrarlas con otros sistemas.
- Persistencia: PostgreSQL en Neon con SQLAlchemy y psycopg2 (con SSL), con inserción transaccional y consultas parametrizadas.
- Procesamiento por lotes: scripts para ingestar directorios completos de PDF y TXT (pypdf, pathlib), con manejo de excepciones y transacciones por archivo.
- Automatización: un flujo de n8n consume la API (HTTP Request), separa la respuesta en facturas individuales (Split Out) y aplica una regla condicional sobre los montos (If). El JSON del flujo está en el repositorio.
- Despliegue: Azure App Service (Linux) con integración continua en GitHub Actions.
Cómo lo trabajé
Un problema real: conexiones que se caían
Neon es PostgreSQL serverless y suspende las conexiones inactivas. Durante el procesamiento de lotes de PDFs y en consultas esporádicas a la API, eso provocaba errores psycopg2.OperationalError: SSL connection has been closed unexpectedly y respuestas HTTP 500 en producción.
Cómo lo resolví
- En la API: configuré el motor de SQLAlchemy con
pool_pre_ping=True, que valida la conexión antes de cada sentencia y reconecta de forma transparente, ypool_recycle=300. - En el script de lotes: dejé de mantener una conexión abierta durante la lectura de archivos. Ahora abre una conexión nueva por cada archivo y la cierra en un bloque
finally, así un archivo con problemas no frena el resto del lote.
Cómo verifiqué que funciona
- Pruebas de integración locales con lotes mixtos de PDFs (válidos, vacíos y con anomalías) para comprobar que los errores quedan aislados por archivo (
try/exceptcon rollback). - Validé los endpoints en Swagger UI y confirmé cada despliegue en Azure en los logs de GitHub Actions.