Control Vehicular: de Flask a Node.js con el mismo esquema
Un sistema web que lee las facturas electrónicas de combustible de la DIAN y las concilia con los tanqueos de la flota de una distribuidora. Mi parte fue reescribir el backend de Python a Node.js, manteniendo el esquema y las rutas.
- Cliente
- Distribuciones Rosimar S.A.S.
- Mi parte
- La migración del backend y varias funciones portadas después. 7 de los 24 commits1 son míos; el sistema original y su lógica de conciliación son de otro desarrollador del equipo.
- Periodo
- Septiembre y octubre de 2026
- Stack
- Node.js 22, Express 5, MySQL con mysql2 (SQL directo), Nunjucks, node:test y supertest
- Código
- Privado. controlvehicular.rosimar.com tiene acceso restringido.
Por qué migrar
La aplicación original estaba en Flask. El hosting compartido de Hostinger que usa el cliente no soporta Python con frameworks, solo en un VPS. Se reescribió, con una condición: no cambiar el comportamiento. Mismo esquema MySQL, mismas rutas, mismas pantallas. La única excepción son los errores del original que se corrigieron por el camino, más abajo.
Qué se portó
- Autenticación con bloqueo tras 5 intentos fallidos durante 15 minutos, por usuario y por IP.
- CSRF en todo POST, también en formularios multipart, verificado antes de procesar cualquier dato.
- El parser de las facturas electrónicas de la DIAN: ZIP con XML y PDF, con XML que usa espacios de nombres.
- Conciliación de consumos contra facturas por período, auditoría, análisis e importación de Excel y CSV.
- SQL directo y parametrizado, sin ORM. Las sesiones y los archivos de facturas viven en la base de datos, no en disco.
- Un módulo que reproduce el redondeo de Python, para que los totales coincidan con los del sistema anterior.
- 1
Petición
Cabeceras de seguridad y sesión guardada en la base de datos.
- 2
CSRF y permisos
El token CSRF se verifica antes de procesar cualquier dato; el rol limita qué rutas ve cada usuario.
- 3
Router
Un router por módulo. Los roles admin, auxiliar y jefe ven cada uno su parte.
- 4
SQL
mysql2 con consultas parametrizadas, sin ORM.
- 5
Plantilla
Nunjucks, con una sintaxis casi igual a la de Jinja2 que usaba el original.
Errores del original que aparecieron al reescribir
Reescribir con atención obliga a leer cada ruta. Salieron estos problemas, que se corrigieron en la migración2:
- Un open redirect en el login.
- La ruta de vinculación masiva aceptaba GET sin token CSRF.
- La importación de recibos en xlsx guardaba una lista en vez de un entero.
- Una celda mal ubicada en el export de facturas.
- El conteo de hallazgos nuevos era falso en el análisis inicial.
Cómo se probó
Al cerrar la migración había 48 pruebas3, unitarias y de integración, con node:test y supertest contra una base MySQL real, sin mocks. Cada archivo de prueba crea y borra su propia base *_test, y el helper se niega a correr contra cualquier servidor que no sea localhost.
El código Python se conserva en legacy/, como referencia y como vía de regreso.
Límites
- No hay integración continua. Lo que llega a
mainse despliega directo en Hostinger, así que las pruebas se corren a mano antes de cada push. - Las pruebas necesitan un contenedor de MySQL local.
- No diseñé el producto. Lo mío fue el cambio de plataforma y el portado de funciones nuevas.
Fuentes de las cifras
- 1
Autoría de los commits de fleet_control
git shortlog de rosimar1/fleet_control (privado) · 9 de octubre de 2026
Otro desarrollador del equipo 17 y Fabian Gonzalez (mi usuario de git) 7: 24 en total.
- 2
Fallas del original corregidas en la migración
mensaje del commit d1cc1c2 y test/integration/auth.test.js de rosimar1/fleet_control (privado) · 23 de septiembre de 2026
Un open redirect en el login (hay una prueba: next=//evil.com se ignora) y una ruta de vinculación masiva que aceptaba GET sin token CSRF.
- 3
Pruebas al cerrar la migración
rosimar1/fleet_control, commit d1cc1c2 (privado) · 23 de septiembre de 2026
35 llamadas a test() más 14 casos que genera el bucle de permisos de test/integration/permissions.test.js. El mensaje del commit también dice 48.