Tender un puente a un sistema de producción COBOL sin tocarlo
Un sistema de planta en COBOL exportaba cada noche archivos planos fragmentados y nadie podía sacar un informe de ellos. Este artículo recorre la plataforma de integración que convirtió esas exportaciones en un modelo relacional normalizado —ingesta idempotente, fronteras transaccionales y un módulo de planificación encima— y lo que me enseñó sobre modernizar sin reescribir.
Related project: Legacy Production System Integration and Data Normalization Platform
Este artículo se basa en el case study Legacy Production System Integration, una publicación solo documental de una plataforma industrial propietaria. Describe arquitectura, patrones y decisiones; no se divulga código fuente, datos del cliente ni reglas de negocio.
El sistema que no se podía reemplazar
La planta funcionaba sobre una aplicación COBOL que llevaba décadas haciendo su trabajo: archivos indexados, procesos batch programados y una exportación nocturna de archivos planos a un directorio de red compartido. Nadie iba a apagarla. Contenía la verdad operativa —productos, componentes, fichas técnicas, órdenes de trabajo— y cada turno dependía de ella.
Lo que no podía hacer era responder preguntas. Las exportaciones estaban fragmentadas: un archivo para productos, otro para operaciones, un tercero para especificaciones, con identificadores que se referían entre sí solo por convención. No había informes centralizados, la sincronización era manual y la trazabilidad terminaba en el borde del archivo. Cada aplicación nueva que necesitaba datos de producción tenía que reinventar el parsing.
El encargo, por tanto, no fue "reemplazar el sistema heredado", sino algo más interesante: construir un puente cuya existencia el sistema heredado no conoce. Este sigue exportando archivos exactamente igual que antes; la plataforma del otro lado los convierte en un modelo relacional que las herramientas modernas pueden usar.
Un monolito modular, a propósito
La plataforma es un único backend Django sobre PostgreSQL, organizado en módulos con responsabilidades claras: ingesta, transformación y sincronización, una capa de API, informes y —aguas abajo de todo eso— planificación de la producción. Elegir un monolito modular frente a microservicios fue deliberado: un despliegue, una base de datos, una frontera transaccional y una estructura interna que deja sitio para separar piezas más adelante si la carga lo justifica. Para una integración que vive o muere por la consistencia de los datos, tenerlo todo dentro de una transacción atómica valía más que la escalabilidad independiente.
El trabajo largo —importaciones por lotes, cálculos diarios de métricas, sincronizaciones grandes— corre fuera del ciclo de la petición, en workers de Celery con un broker de mensajes y lógica de reintentos para los fallos transitorios típicos de una integración por archivos: un recurso de red que no responde un momento, un archivo que aún se está escribiendo, un bloqueo temporal en la base.
La cadena de ingesta: detectar, interpretar, validar, transformar, persistir
Cada importación sigue las mismas cinco etapas, y las fronteras entre ellas son donde se fue la mayor parte de la ingeniería.
- Detección. Los archivos nuevos se encuentran por escaneo programado, por disparo manual o por una llamada a la API. Las tres entradas comparten el mismo código, así que un operador que relanza una importación a mano obtiene exactamente el comportamiento del proceso nocturno.
- Interpretación. Las exportaciones heredadas no traen esquema. La codificación se detecta en vez de suponerse, los registros se separan por delimitador y los formatos de campo se normalizan antes de que nada más los mire.
- Validación. Campos obligatorios, tipos, restricciones de unicidad y —lo crítico— si las entidades a las que un registro se refiere existen de verdad. Un registro que apunta a un producto desconocido es una pregunta para una persona, no algo que persistir y esperar.
- Transformación. Los identificadores heredados se mapean a entidades relacionales. Aquí los archivos fragmentados se convierten en un dominio: productos con fichas técnicas, fichas con componentes, órdenes de trabajo ligadas a ambos.
- Persistencia. Todo se escribe dentro de
transaction.atomic, conget_or_createyupdate_or_create, de modo que procesar el mismo archivo dos veces produce la misma base de datos, no filas duplicadas.
Esa última propiedad —la idempotencia— es la que más importa en la práctica. Las exportaciones se reenvían. Los trabajos se reintentan. Alguien restaura un respaldo y vuelve a pasar una semana de archivos. Si la ingesta no es idempotente, cada uno de esos sucesos corrientes corrompe los datos; si lo es, no son sucesos.
Planificar sobre datos confiables
Una vez que el modelo relacional fue fiable, se hizo posible una segunda capacidad: la planificación de la producción. Un módulo de programación asigna órdenes de trabajo a máquinas y operadores, calcula duraciones, gestiona la disponibilidad por franjas horarias y respeta las restricciones de secuencia del proceso de la planta —el mismo tipo de restricciones de precedencia que hacen difícil el job shop en la teoría, ahora con ventanas de disponibilidad reales que se parten y se juntan a medida que se asigna.
El punto arquitectónico importante es la dirección de la dependencia. La planificación consume datos sincronizados; es una capacidad aguas abajo, no la función principal del sistema. Tener claro ese orden mantuvo simple la capa de integración y dejó que la planificación evolucionara sin tocar el contrato de ingesta.
Lo que costó, con honestidad
El case study enumera los riesgos con la misma claridad que las fortalezas, y son los que cabe esperar de un sistema con esta forma: lógica de integración algo acoplada a la capa de persistencia, carga pesada sobre la base durante importaciones grandes, riesgo de concurrencia cuando varios procesos tocan los mismos registros, y una dependencia dura de las exportaciones de un sistema externo: si el batch heredado falla a las dos de la mañana, la plataforma no tiene nada que ingerir. El camino de evolución es igual de previsible: ingesta dirigida por eventos, sincronización incremental, mejor observabilidad y, en su momento, un servicio de planificación dedicado.
Tres lecciones
- Moderniza los datos, no el sistema. La aplicación heredada nunca cambió. El valor vino de dar a su salida un lugar normalizado y consultable.
- La idempotencia es la funcionalidad. En una integración por archivos, "qué pasa si esto corre dos veces" es la primera pregunta, no la última.
- Pon la frontera transaccional donde está el invariante de negocio. Un producto, su ficha y sus componentes son un solo hecho; se persisten como una unidad o no se persisten.
La arquitectura, el flujo de datos, el modelo de dominio y los riesgos conocidos están documentados en el repositorio público del case study.
Comments
Comments live on GitHub Discussions: sign in with GitHub in the box below to reply, or open the thread on GitHub.
No GitHub account? Leave a comment here
Comments are reviewed before they appear. Your email is optional and is never published.