Caso de estudio
Del Excel de 7.000 líneas a un sistema de trazabilidad fiscal
Un grupo de 17 empresas del sector construcción en México operaba toda su documentación comercial
desde un solo archivo .xlsm. Este es el sistema que lo reemplazó, y la auditoría de datos
que casi nadie habría hecho.
El problema
Diecisiete empresas relacionadas gestionaban cotizaciones, órdenes de compra, remisiones y facturas dentro de un único libro de Excel con macros: 24 módulos VBA, cerca de 7.000 líneas de código y 23 hojas de cálculo, en uso diario.
El síntoma que más dolía era el de los folios. Se generaban con MAX + 1 sobre la hoja
correspondiente, así que bastaba con que dos personas capturaran al mismo tiempo para que dos
documentos distintos nacieran con el mismo número. En un archivo compartido esto no es un caso raro:
es el día a día.
Debajo había un problema mayor. Nadie podía responder con certeza de qué cotización venía una factura determinada. La relación existía en la cabeza de quien había capturado el documento, no en los datos.
Antes de escribir una línea del sistema nuevo hice ingeniería inversa del archivo completo. Los catálogos formales estaban incompletos o desactualizados; las abreviaturas reales con las que el equipo identificaba a cada empresa terminé recuperándolas de una función VBA enterrada en un módulo, que era el único lugar donde estaban correctas.
Las restricciones
- La fuente de verdad es fiscal. Un CFDI timbrado no se corrige ni se normaliza: se conserva íntegro. Cualquier cosa que el sistema quiera decir sobre un comprobante tiene que vivir aparte del comprobante.
- Diecisiete empresas sobre un mismo modelo. Cada razón social tiene su propia numeración de folios, su branding en los documentos y sus propias contrapartes. El sistema tenía que mantenerlas separadas sin duplicar la lógica en diecisiete variantes.
- Operación en marcha. El Excel seguía en uso mientras se construía el reemplazo, así que la migración tenía que ser repetible y idempotente: volver a correrla no podía duplicar nada.
Las dos cadenas
El cliente entregó un diagrama de su flujo. Al modelarlo contra los datos reales apareció que describía solo la mitad: existían dos cadenas de compraventa encadenadas, no una. El mismo grupo actuaba como comprador en una y como vendedor en la otra, y los documentos de una alimentaban a la otra.
Qué construí
Un sistema web con backend en Python y FastAPI, base de datos PostgreSQL y frontend en Next.js 16 con App Router. Son 35 endpoints, 22 tablas, 12 migraciones versionadas y 15 pantallas de trabajo.
El ingestor de CFDI lee el XML del comprobante, extrae el Timbre Fiscal Digital y
usa el UUID como llave natural, de modo que reprocesar el mismo archivo no crea un duplicado. Cada
concepto se clasifica automáticamente como material o servicio a partir de los catálogos oficiales
del SAT (c_ClaveProdServ y c_ClaveUnidad), con una bandeja de clasificación
donde el equipo revisa y confirma los casos que lo requieren.
La pantalla de trazabilidad es la que justifica el proyecto: dada una factura, recorre hacia atrás la cadena hasta la cotización de origen y muestra cada eslabón con su documento, su folio y su fecha.
El generador documental replica celda por celda las cinco plantillas del Excel original, con el branding de cada una de las empresas y salida en PDF sin el límite de ítems que tenía la macro.
Está diseñado además el conector de Descarga Masiva del SAT con e.firma, para que los comprobantes entren solos en vez de cargarse a mano.
El modelo de datos
Los folios dejaron de calcularse con MAX + 1 y pasaron a funciones respaldadas por
secuencias reales de PostgreSQL: dos capturas simultáneas ya no pueden colisionar. La trazabilidad se
resuelve con una consulta recursiva que recorre la cadena documental completa, y hay vistas de saldo
y triggers de indexado para las búsquedas.
Probar el esquema contra la base real —no contra datos de prueba— hizo aparecer tres errores que en
un entorno limpio no se habrían visto: una columna demasiado angosta para los valores reales, una
duplicación de filas causada por un JOIN mal cardinalizado, y una recursión que no
terminaba cuando la cadena documental contenía un ciclo. Los tres se corrigieron antes de la carga
definitiva.
La decisión difícil
La hoja BDRemisiones del Excel contenía 35.365 registros con lo que parecía un histórico
completo de remisiones, cada una con su UUID. Era, con diferencia, la fuente más rica del archivo, y
el plan era migrarla entera.
Antes de cargarla crucé esos UUID contra los comprobantes fiscales reales. Ninguno existía. Cero de 35.365. No eran identificadores de CFDI: eran cadenas con el formato correcto y contenido inventado, generadas en algún momento para rellenar la hoja.
Migrarla habría metido 12.436 documentos falsos a un sistema con obligaciones fiscales, mezclados con los reales y prácticamente indistinguibles una vez dentro. El costo de descubrirlo después —en una auditoría, por ejemplo— no se compara con el de descubrirlo antes.
La hoja se descartó por completo y el histórico se reconstruyó únicamente desde comprobantes verificables. El sistema quedó con menos datos de los que prometía el Excel, y con todos ellos ciertos.
La lección que me llevo: cuando una fuente de datos parece demasiado completa, esa es exactamente la razón para validarla contra algo externo antes de confiar en ella.
Decisiones técnicas y por qué
- Validar archivos por números mágicos, no por extensión
- La extensión la controla quien sube el archivo; los primeros bytes, no. Renombrar un ejecutable
a
.xmldeja de servir de nada. - Derivar el nombre almacenado del RFC
- El nombre original del archivo nunca toca el sistema de archivos, lo que elimina de raíz el path traversal y los choques de nombres entre empresas.
- Sesión por cookie en lugar de autenticación básica
- Basic Auth reenvía la credencial en cada petición y no se puede cerrar sesión. La cookie de sesión es revocable.
- SQL explícito con SQLAlchemy Core, no ORM completo
- En un dominio fiscal quiero poder leer la consulta exacta que se ejecuta y auditarla. Un ORM que genera SQL por mí es más cómodo y menos verificable.
- Entornos reproducibles desde el primer día
docker-compose, devcontainer de Codespaces y scripts de arranque para Windows y Linux, para que levantar el sistema no dependa de la máquina de nadie.
Todo esto vive en 15 especificaciones por módulo y una bitácora de decisiones dentro del repositorio. Documentar por qué se eligió algo es lo que permite revisarlo meses después sin repetir el análisis.
Cómo trabajé: la IA como herramienta de desarrollo
Este sistema lo construí usando Claude Code como herramienta de desarrollo, y no tengo interés en disimularlo: es precisamente lo que me permitió ampliar mi alcance técnico. Un año antes mi terreno era HTML, CSS y JavaScript. Aquí levanté un backend en FastAPI, diseñé un esquema de 22 tablas en PostgreSQL con consultas recursivas y triggers, y monté 15 pantallas en Next.js 16. Trabajando solo.
El método importa más que la herramienta. Todo arranca de un archivo CLAUDE.md que fija
por escrito las reglas de negocio y las restricciones fiscales del dominio, porque el criterio legal
no puede quedarse en una conversación: tiene que estar en el repositorio, versionado y revisable como
cualquier otro artefacto. A partir de ahí dirijo, reviso cada decisión y verifico contra datos reales.
Ese último paso es el que marca la diferencia. Ninguna herramienta me habría dicho que
BDRemisiones era una hoja de datos fabricados; eso salió de desconfiar de una fuente
demasiado limpia y cruzarla contra los comprobantes reales. La IA acelera la ejecución, pero el
criterio sobre el dominio y la decisión de qué se migra y qué se descarta siguen siendo míos.
Uso el mismo patrón en mis otros proyectos. En la calculadora de nómina, el documento de contexto con el marco legal colombiano se escribió antes que el primer cálculo.
Resultados
- 9.535 CFDI migrados y validados contra su comprobante real
- 16.401 documentos generados con su cadena documental completa
- 0 duplicados, con el UUID como llave natural del modelo
- 12.436 documentos falsos detectados y excluidos antes de la carga
- 17 empresas operando sobre un solo sistema en lugar de un archivo compartido
- Folios sin colisiones, con secuencias de base de datos en vez de
MAX + 1
Estado actual: el sistema está construido y probado con los datos reales del periodo 2021–2025. Quedan pendientes el despliegue en producción, la suite de pruebas automatizadas y la autenticación por usuario individual.
¿Tienes un Excel que ya no aguanta?
Es el problema que más me gusta resolver.