Modernizar una aplicación legada sin frenar el negocio

Hay aplicaciones que no se pueden detener. Liquidan sueldos, gestionan compras, presupuestos o inventarios de toda una organización, y lo vienen haciendo desde hace diez o quince años. Se construyeron con los frameworks que tenían sentido en su momento, y cada año que pasa son más difíciles de mantener, más difíciles de asegurar y cuesta más encontrar gente que quiera trabajar en ellas.
La respuesta tentadora es reescribir todo de cero. En nuestra experiencia, rara vez es la correcta. Una reescritura completa congela el sistema viejo mientras se construye el nuevo, duplica el trabajo durante años y apuesta todo a un único día de cambio. Mientras tanto, el negocio sigue pidiendo cambios.
En Flowing Code modernizamos varias aplicaciones grandes sin detenerlas. En este artículo quiero compartir el enfoque que seguimos, a partir de dos migraciones de ERP que llevaron años y que hoy siguen generando valor todos los días.
Dos proyectos, un mismo enfoque
El primer caso es un ERP para entidades gubernamentales: una aplicación muy grande, construida a lo largo de los años sobre versiones antiguas de varios frameworks. El segundo es otro ERP importante, una migración que empezamos hace varios años y que todavía hoy sigue evolucionando.
Los dos proyectos tenían puntos de partida, equipos y restricciones distintas. Pero mirando hacia atrás, el camino que seguimos fue notablemente parecido. Se puede resumir en una idea: nunca romper lo que funciona, y hacer que cada paso sea lo suficientemente chico como para probarlo y volver atrás.

1. Empezar por un análisis global
Antes de tocar una línea de código, necesitamos entender el sistema completo: sus módulos, cómo dependen unos de otros, qué frameworks y versiones usa cada parte, cómo se construye y se despliega, y quién sabe qué.
El objetivo es encontrar los puntos críticos: las librerías obsoletas que bloquean las actualizaciones, las partes que concentran la mayor parte de la lógica de negocio, las zonas con problemas de seguridad o de rendimiento, y el conocimiento que vive solo en la cabeza de un par de personas. Ese mapa es lo que nos permite planificar la migración por etapas en lugar de adivinar.
2. Invertir primero en las personas
En el segundo proyecto hicimos algo antes de diseñar nada: capacitamos al equipo del cliente. Versionado de código, versiones actuales de Java, herramientas modernas de construcción y los frameworks que íbamos a usar.
Puede parecer un desvío, pero es lo que hace sostenible una migración. La tecnología nueva la van a mantener las personas que trabajan en ella todos los días. Si no la dominan, la modernización termina cuando se van los consultores. Un equipo que entiende las nuevas herramientas, además, pasa a ser parte activa del diseño, en lugar de un grupo que recibe cambios que no termina de entender.
3. Demostrar que lo viejo y lo nuevo pueden convivir
El riesgo central de una migración gradual es la convivencia: ¿pueden las tecnologías viejas y las nuevas funcionar dentro de la misma aplicación, compartir la sesión, navegar de una a otra y verse como un único producto para el usuario?
Por eso construimos una prueba de concepto y la probamos desde todos los ángulos: funcionalidad, rendimiento, seguridad, construcción y despliegue. En el primer proyecto, la prueba de concepto mostró cómo podían convivir las distintas tecnologías. En el segundo fue más allá: reescribimos el shell de la aplicación (el login, la navegación y la estructura general) con tecnologías modernas, y construimos un puente desde el shell nuevo hacia las pantallas existentes.

Ese puente es la pieza que hace posible todo lo demás. Desde ese momento, los usuarios trabajan en una única aplicación, y cada pantalla se puede migrar cuando esté lista.
4. Reestructurar el proyecto y encapsular el código legado
Las aplicaciones legadas grandes suelen ser un único proyecto enorme en el que todo puede ver todo. Uno de los primeros pasos técnicos es convertirlo en un proyecto Maven multimódulo, con límites claros entre las partes.
El código legado pasa a sus propios módulos, encapsulado: sigue funcionando exactamente igual que antes, pero el código nuevo depende de él solo a través de puntos bien definidos. Esto reduce el riesgo de cada cambio y permite actualizar o reemplazar cada pieza de manera independiente.
5. Migrar las pantallas testigo
Antes de migrar a escala, elegimos algunas pantallas testigo: pantallas representativas que cubren los patrones típicos de la aplicación, como un listado con filtros, un formulario complejo o un reporte.
Migrarlas primero valida la arquitectura con casos reales, define los patrones y componentes reutilizables que va a seguir el resto de la migración, y nos da una estimación realista del esfuerzo, basada en trabajo concreto y no en suposiciones.
6. Definir el camino y migrar de forma gradual
Con la convivencia resuelta y los patrones definidos, acordamos con el cliente un camino de migración y empezamos la migración gradual.
Arrancamos por las funcionalidades más sencillas y de base, las que muchas otras partes necesitan, y en cada iteración aumentamos la complejidad: la navegación, los procesos más elaborados, los circuitos críticos. Cada iteración sale a producción por sí sola, probada y con un camino de vuelta.
En el camino pasa algo interesante: las funcionalidades nuevas empiezan a construirse directamente en la tecnología nueva. En el segundo proyecto, la capa de presentación moderna nos permitió ofrecer cosas que la tecnología anterior directamente no podía, y la migración dejó de ser solo un costo para convertirse en una fuente de mejoras para los usuarios.
7. Ordenar lo que está por debajo
Modernizar no es solo cambiar código. En el primer proyecto, buena parte del valor vino de ordenar cómo se construía y se entregaba la aplicación:
- Todo versionado en Git, y cada entregable empaquetado con Docker.
- Un pipeline de CI/CD que construye, prueba y despliega siempre de la misma manera.
- Entornos separados para desarrollo, pruebas y producción.
- Sistemas operativos seguros y actualizables, para que aplicar un parche sea rutina y no un riesgo.
Sin esta base, cada despliegue de una pantalla migrada sería un evento manual y riesgoso. Con ella, las entregas chicas y frecuentes pasan a ser lo normal, y eso es exactamente lo que necesita una migración gradual.
Lo que hace que funcione
Estos procesos llevan meses, y a veces años. Mirando los dos proyectos, hubo tres cosas que marcaron la diferencia:
- Etapas controladas. Cada paso tiene un alcance claro, sale a producción por sí solo y se puede revertir.
- Pruebas desde distintos ángulos. Funcionales, de regresión, de rendimiento y de aceptación con los usuarios que conocen el negocio. En un sistema de este tamaño, lo que no se prueba, se rompe.
- Cuidar al equipo del cliente. Capacitar e involucrar a las personas que van a sostener el sistema es lo que les permite apropiarse del cambio tecnológico, en lugar de padecerlo.
Una modernización hecha así es menos espectacular que una reescritura total. No hay un día de lanzamiento. Pero el negocio nunca se detiene, el riesgo queda bajo control y cada mes la aplicación es un poco más moderna que el mes anterior.
Si tenés una aplicación demasiado importante para detenerla y demasiado antigua para dejarla como está, hablemos. Podemos ayudarte a encontrar el camino que se adapte a tu sistema y a tu equipo.
¡Sumate a la conversación!