Sistemas vivos
Escribir en tablas FoxPro que siguen abiertas
Sacar más de 500.000 registros de un sistema de los noventa sin cerrar la operación un solo día.
- Periodo
- 2024 —
- Rol
- Diseño e implementación
- Stack
- Python · FastAPI · PostgreSQL · FoxPro · DBF · Alembic · Docker · Linux
- Prueba
- py-foxpro-engine ↗
01 Contexto
FoxPro sostiene la operación diaria de una clínica privada, y la aplicación está abierta en cada escritorio desde las ocho de la mañana. Nunca había habido una migración: los datos vivían en la red local, atados a la aplicación que los escribía, y lo único que salía de ahí era un reporte en Excel exportado a mano.
02 Problema
Todas las vías normales —ODBC, exportar a mano, un ejecutable de Windows de terceros— exigen que nadie esté usando la tabla, y algunas escriben de forma que FoxPro deja de reconocerla después. Y una exportación de fin de semana tampoco resolvería la necesidad real: admisión tiene que resolver un paciente por su documento mientras cada caja está escribiendo en ese mismo fichero.
03 Restricciones
- La operación no se puede detener. Ni un turno.
- La aplicación de los noventa no se toca: sin fuentes, sin proveedor y sin contrato de soporte.
- Los datos de salud son categoría sensible en la ley peruana. Nada sale de la red de la clínica sin control.
- Tiene que correr sin depender de un runtime de Windows concreto ni de un driver de 32 bits.
04 Decisiones
01
Escribir el motor DBF en vez de adoptar uno
Un parser que entiende la cabecera de la tabla, los flags de nulidad y el marcador de fin de fichero 0x1A, y que lee y escribe registros en su desplazamiento de bytes. Sin ninguna dependencia.
DescartadoEl driver ODBC de VFP exige un runtime de Microsoft de 32 bits y bloquea la tabla entera. Las librerías de Python que hay resuelven la mitad del problema: leen, y aquí hay que escribir.
02
Bloquear por rangos de bytes, no el fichero
El motor retiene solo los bytes del registro que está tocando. La aplicación de siempre sigue leyendo y escribiendo el mismo fichero a la vez, y no se entera. Es la decisión de la que cuelga todo lo demás.
DescartadoEl bloqueo exclusivo del fichero es lo que toman las herramientas al uso, y es justo lo que obliga a la ventana de parada que este proyecto existe para evitar.
03
Reservar los autoincrementales nativos
FoxPro lleva sus propios contadores. Insertar sin reservarlos rompe la integridad referencial con el sistema viejo, en silencio y días después.
04
Escribir el registro entero de una pasada
Un registro se serializa entero en memoria y se escribe en una sola operación, nunca campo por campo. No existe un instante en el que un lector concurrente vea una fila mitad vieja y mitad nueva. Borrar marca la fila, como hace FoxPro, y no compacta nunca: desplazar registros invalidaría todo índice y todo número de registro que otro proceso tenga en memoria.
05
No fiarse de la cabecera
Una tabla que FoxPro no cerró bien declara un número de registros y contiene otro. El motor informa de los dos —el declarado y el medido por el tamaño del fichero— y avisa de si coinciden. Ese aviso es lo que hace posible la decisión de migración de aquí abajo.
06
Migrar por calidad del dato, no por volumen
Solo la tabla de resultados de laboratorio guarda más de dos millones de registros desde 2019. Se migraron únicamente 2025 y 2026: lo anterior es inconsistente, y ahora se trae bajo demanda y por identificador, cuando alguien lo necesita de verdad. Migrarlo todo habría quedado bien de anunciar y habría envenenado los reportes.
DescartadoLa carga histórica completa, que es la respuesta por defecto y aquí la equivocada.
07
Entregarlo como servicio, no como script
Un servicio FastAPI donde una migración se dispara por rango de fechas y se sigue por su identificador, junto a un planificador que se arranca y se para con el sistema en marcha, y endpoints que sirven los agregados cuando el dato ha aterrizado.
DescartadoUn cron que alguien se acuerda de mirar es un cron que se deja de mirar.
08
Extraer el motor y publicarlo
El valor técnico está en el motor; el valor comercial y los datos son del cliente. Así que el motor salió despojado de toda regla de negocio y se publicó con licencia MIT, y el pipeline que sabe de la clínica se quedó privado.
- Windows ServerFoxPro · .dbf abiertos todo el día
- Carpeta compartidamisma red, dos sistemas
- Motor DBFel protocolo de bloqueo de FoxPro
- FastAPIconsulta en vivo o migra
- PostgreSQLadmisión · recepción · reportes
↩ Y de vuelta: el sistema heredado llama a estos endpoints para construir reportes que no podía producir solo.
05 Resultado
- Más de 500.000 registros históricos en PostgreSQL, con cero días de parada.
- Alrededor de 2.000 registros diarios por el pipeline automatizado.
- Lo importante nunca fue la parada que se evitó: es que el dato dejó de ser local. En cuanto se pudo alcanzar desde cualquier sitio, cosas que antes no se podían ni plantear pasaron a ser normales — tableros que se actualizan solos, un dispositivo reaccionando a lo que ocurrió en otro, y análisis sobre el histórico en vez de sobre la hoja de cálculo del mes pasado.
- El motor publicado como paquete independiente y sin dependencias.
- El tráfico va en los dos sentidos, y esa es la parte que no esperaba. Mi servicio lee del sistema heredado — y el sistema heredado llama ahora a mis endpoints, porque un compañero los usa para construir reportes que su propio stack no podía producir. Un ingreso registrado de un lado aparece del otro sin que nadie lo vuelva a teclear.
- La prueba está en el día a día: admisión consulta la agenda del día desde una tableta, recepción resuelve el historial completo de un paciente por su documento en tiempo real —si es de seguro, particular u hospitalizado, y si tiene turno con algún médico— y gerencia lee reportes en vivo desde el móvil. Todo mientras cada caja sigue trabajando dentro de la aplicación de los noventa.
- El motor publicado lleva tests del escritor y del introspector, que es la parte que dolería si estuviera mal. El pipeline privado que lo orquesta no: tiene comprobaciones de salud, no una batería. Decir las dos cosas es la versión honesta.
- Extraerlo sacó dos defectos de corrección que llevaban escondidos dentro del pipeline: abrir el fichero sin modo binario, que corrompe el marcador EOF y descuadra el conteo de registros, y un bloqueo de cabecera que no se soltaba en toda la sesión. Sacar una pieza a la luz es, en sí mismo, una revisión.
06 Prueba
Todo lo anterior se puede ver aquí: py-foxpro-engine ↗