Refactorizar código legacy con IA sin romper producción

Cómo refactorizar código legacy con agentes de IA sin riesgos: tests de caracterización primero, pasos pequeños, migraciones a TypeScript, revisión y rollback.

Solviera Teknoloji 12 min de lectura

Refactorizar código legacy con IA es poner a un trabajador muy rápido y muy seguro de sí mismo delante de la parte del código que nadie entiende del todo. Puede salir bien. Los agentes tienen paciencia con archivos de 3.000 líneas, no les importa renombrar una función en doscientos sitios y leen código viejo sin resoplar. También puede salir mal: una «limpieza» que cambia una regla de redondeo, se lleva por delante un efecto secundario que nadie documentó o acaba convertida en una reescritura imposible de revisar.

Esta guía va del primer escenario. Explica cómo montar una red de seguridad antes del primer cambio, cómo mantener cada paso pequeño y reversible, cómo abordar migraciones como pasar de JavaScript a TypeScript o actualizar un framework, y cómo revisar y deshacer lo que ha hecho el agente.

Por qué el código legacy también es difícil para los agentes

El código legacy es el código que te da miedo tocar, normalmente porque tiene pocos tests y muchas reglas no escritas. Lo que lo complica para las personas también lo complica para un agente:

  • El comportamiento vive en sitios raros. Un formato de fecha en un helper, un reintento en un decorador, una variable global que lee otra parte.
  • Algunos bugs son funcionalidades. Puede que un sistema externo dependa de un valor incorrecto. «Arreglarlo» rompe una integración que nunca has visto.
  • El código muerto parece vivo y el vivo parece muerto. Reflexión, despacho por cadenas, cron jobs y plantillas llaman a funciones que ninguna búsqueda encuentra.
  • La ventana de contexto es finita. Un agente ve los archivos que lee. No sabrá nada del proceso por lotes de otro repo que importa este módulo.

Además, un agente tiene una costumbre peligrosa aquí: mejora cosas. Pídele que extraiga una función y puede que de paso arregle el caso límite que vio, renombre variables y modernice la sintaxis. Cada cambio puede estar bien. Juntos dan un diff en el que no sabes qué se quería cambiar.

Paso 1: primero, la red de seguridad

No dejes que el agente cambie una sola línea de código de producción hasta que el comportamiento que te importa esté fijado con tests. En código legacy eso significa tests de caracterización: tests que registran lo que el código hace ahora, no lo que debería hacer.

Un prompt que funciona:

No cambies nada de código en src/. Escribe tests de caracterización para src/orders/pricing.js que registren su comportamiento actual, incluido todo lo que parezca incorrecto. Cubre cada rama que encuentres, también las de error. Donde el comportamiento parezca un bug, deja el test afirmando la salida actual y añade un comentario que empiece por SUSPICIOUS:. Ejecuta los tests y enséñame que pasan.

Algunos consejos:

  • Prueba a través de la interfaz pública. Los tests que llaman al módulo como lo hace el resto de la aplicación sobreviven al refactor. Los que se meten en helpers privados se rompen cuando los mueves.
  • Usa entradas reales. Payloads de ejemplo sacados de los logs (limpios de datos personales), archivos de fixtures o una copia de la base de datos de staging cubren los casos raros mucho mejor que entradas inventadas.
  • Aquí los archivos golden valen. Para un generador de informes o una exportación, grabar la salida completa y compararla es un test de caracterización rápido y honesto, siempre que la salida sea determinista.
  • Comprueba que los tests pueden fallar. Rompe el código a mano en un par de sitios o pasa una herramienta de mutation testing por el módulo. Si los tests siguen en verde, la red tiene agujeros.

La guía para escribir tests unitarios con IA trata con más detalle los tests de caracterización, la cobertura y el mutation testing.

Las notas SUSPICIOUS: valen oro. Son los bugs que arreglarás más adelante, a propósito, en sus propios commits, con alguien decidiendo que el nuevo comportamiento es el correcto.

Paso 2: refactoriza en pasos pequeños y reversibles

Con la red puesta, mantén cada paso lo bastante pequeño como para revisarlo en unos minutos y deshacerlo sin tocar nada más.

Separa estructura y comportamiento

La regla más útil: un commit de refactor solo cambia la estructura, y un cambio de comportamiento va en su propio commit. Díselo al agente sin rodeos:

Esto es un refactor puro. No cambies el comportamiento, no arregles bugs, no cambies firmas públicas. Si ves algo que debería cambiar, apúntalo al final en lugar de hacerlo.

Si los tests de caracterización cambian en un commit de refactor, algo va mal. Eso te da una comprobación sencilla: git diff --stat main -- tests/ no debería mostrar ediciones en los tests existentes.

Un tipo de cambio por paso

Buenos tamaños de paso:

  • Extraer una función o una clase.
  • Mover un módulo y actualizar sus imports.
  • Sustituir una API obsoleta en todo el código.
  • Introducir una interfaz en una frontera para que el código viejo y el nuevo convivan.

Pide al agente que ejecute los tests después de cada paso y haga un commit con un mensaje claro antes de seguir. Un plan ayuda: «Propón el refactor como una lista numerada de pasos, cada uno dejando los tests en verde. Espera mi OK antes de empezar.» La mayoría de los agentes de código tienen un modo de planificación para esto; el plan mode de Claude Code, por ejemplo, explora y propone sin editar.

Estrangula, no reescribas

Para una parte que de verdad hay que sustituir, pon la nueva implementación junto a la vieja detrás de una interfaz o un flag, pasa a los llamadores uno a uno y borra el código viejo cuando ya nadie lo use. Es el patrón strangler fig. Convierte un cambio arriesgado en muchos cambios pequeños y aburridos, y a los agentes se les da bien lo aburrido.

Paso 3: migraciones con agentes

En las migraciones es donde los agentes más tiempo ahorran, porque el trabajo es amplio y repetitivo. También es donde los diffs se vuelven enormes. El truco es dejar la parte mecánica a las herramientas y las decisiones al agente.

De JavaScript a TypeScript

Un camino que mantiene la aplicación funcionando todo el tiempo:

  1. Añade TypeScript junto a JavaScript. En tsconfig.json, pon allowJs: true para que convivan archivos .js y .ts, y ejecuta npx tsc --noEmit en CI como comprobación de tipos.
  2. Empieza en modo permisivo. Arranca con strict: false. Si quieres, activa checkJs o añade // @ts-check a archivos JS sueltos para encontrar problemas antes de renombrarlos.
  3. Convierte primero los módulos hoja. Los archivos sin imports internos, como utilidades y constantes, son los más fáciles. Luego sube hacia los puntos de entrada.
  4. Una carpeta por paso. Pide al agente que convierta src/utils/ a .ts, añada tipos, mantenga los tests en verde y haga pasar tsc --noEmit. Luego la siguiente carpeta.
  5. Endurece poco a poco. Cuando todo sea .ts, activa noImplicitAny, luego strictNullChecks y después strict completo, arreglando errores entre medias.

Dos reglas para darle al agente: nada de any sin un comentario que explique por qué, y nada de @ts-ignore para hacer desaparecer errores. Si no, el camino más rápido hacia un compilador en verde es ir apagándolo línea a línea.

Actualizaciones de frameworks y librerías

En un salto de versión mayor, el orden importa:

  1. Lee primero la guía de migración. Haz que el agente resuma la guía oficial de actualización y liste los cambios incompatibles que afectan a tu código, con rutas de archivo. Es un paso de solo lectura.
  2. Ejecuta el codemod oficial si existe. Muchos proyectos publican codemods, por ejemplo npx @next/codemod para Next.js; en Java, las recetas de OpenRewrite cubren muchas actualizaciones. Un codemod es determinista; un agente no. Deja que el codemod haga todo lo que pueda.
  3. Deja al agente lo que sobra. Los casos que el codemod no supo resolver, los wrappers propios, los tests que dependían del comportamiento antiguo.
  4. Sube una versión mayor cada vez. Pasar de la 3 a la 6 de golpe apila todos los cambios incompatibles en un solo diff.

Fija el resultado esperado en tu prompt: «el build pasa, la suite de tests pasa, no hay advertencias nuevas en la consola». Un vago «actualiza a la última versión» invita al agente a subir de paso otros cinco paquetes.

Paso 4: aísla el trabajo con worktrees

Un refactor largo no debería vivir en tu carpeta de trabajo principal, y los pasos en paralelo no deberían compartir una. Un git worktree da a cada paso su propia rama y su propia carpeta:

git worktree add ../app-ts-utils -b refactor/ts-utils
git worktree add ../app-ts-api -b refactor/ts-api

Ahora un agente convierte utils, otro convierte api y tú sigues trabajando en la carpeta principal. Cuando un paso está terminado y revisado, lo fusionas; cuando no lleva a ninguna parte, borras el worktree y la rama. Más en git worktrees para agentes en paralelo.

Cuidado con los pasos en paralelo que tocan los mismos archivos. Dos agentes renombrando cosas en el mismo módulo producirán conflictos, y resolver conflictos en un refactor es justo por donde se cuela el comportamiento. Reparte el trabajo por carpeta o por módulo, no por tipo de cambio.

Paso 5: revisa cada paso

Un diff de refactor es fácil de aprobar porque parece que no hace nada. Precisamente por eso hay que leerlo. Qué mirar:

  • ¿Han cambiado los tests? En un refactor puro, los tests existentes no deberían cambiar.
  • ¿Han cambiado las firmas públicas? Funciones exportadas, respuestas de la API, columnas de la base de datos, nombres de eventos.
  • ¿Se ha borrado algo? Pregunta al agente por qué cada función eliminada no se usaba. «No se encontraron referencias» no es una prueba cuando hay reflexión o despacho por cadenas.
  • ¿Ha cambiado el manejo de errores? Una excepción que antes se tragaba y ahora se propaga, o al revés, es un cambio de comportamiento.
  • ¿El diff ha crecido más allá del paso? Un paso de «mover este módulo» no debería reformatear de paso otros veinte archivos. Pide el formateo en su propio commit, o que no lo haga.

Un segundo modelo revisando cada turno detecta bastante de esto. Consulta revisión de código con IA para ver cómo funciona.

Paso 6: planifica el rollback antes de necesitarlo

Deshacer sale barato si lo preparas:

  • Commits pequeños. git revert <sha> quita un paso limpiamente. Un commit de 4.000 líneas solo se puede revertir entero.
  • Revierte, no reescribas. Cuando algo ya está en main, revierte el commit o el merge en lugar de hacer force push sobre un historial compartido.
  • Bisect cuando algo se cuela. Con commits pequeños y en verde, git bisect encuentra en unas pocas ejecuciones el paso que rompió el comportamiento.
  • Flags para los cambios arriesgados. Cuando el código viejo y el nuevo conviven, un flag apaga el camino nuevo sin desplegar.
  • Deshacer dentro de la sesión. Antes de hacer commit, puedes deshacer el último turno de un agente; consulta cómo deshacer los cambios de Claude Code.

Errores habituales

  • «Refactoriza este módulo» sin tests. Obtienes una reescritura y ninguna forma de comprobarla.
  • Mezclar refactor y arreglos de bugs. Cuando el comportamiento cambia, no sabes si era intencionado.
  • Una rama de migración gigante. Se aleja de main, se acumulan los conflictos y nadie puede revisarla.
  • Fiarse de «no se usa». La búsqueda no ve llamadas dinámicas, otros repos ni tareas programadas.
  • Dejar que el agente silencie al compilador. any, @ts-ignore y reglas de lint desactivadas esconden el trabajo en lugar de hacerlo.
  • Fusionar pasos paralelos sin probarlos juntos. Dos ramas en verde pueden ponerse en rojo al fusionarse, y para eso está una cola de fusión.

Cómo hacerlo en AgentVera

AgentVera ejecuta CLIs de agentes de código lado a lado por proyecto, con git integrado, lo que encaja con el estilo paso a paso de un refactor de código legacy.

  • Un worktree por paso. Desde el panel de worktrees, abre una rama y una carpeta para cada paso e inicia un agente directamente en ella: Claude Code en uno, Codex o Gemini CLI en otro. Tu carpeta principal se queda como está.
  • Checkpoints en cada turno. Al empezar cada turno, la carpeta de trabajo se guarda en una referencia de git oculta, y se conservan los últimos 60 turnos por agente. Mira qué archivos cambió un turno y deshaz solo ese turno si el agente se pasó de la raya.
  • Revisión al final de cada turno. La revisión de código automática lee el código que escribió el agente con el modelo de Claude que elijas y lista los hallazgos con su gravedad y su file:line; si quieres, los hallazgos vuelven al agente, hasta dos rondas seguidas.
  • Verify contra tus criterios. Escribe en tu mensaje «refactor puro, todos los tests pasan, no ha cambiado ningún archivo de tests existente». Verify lo convierte en criterios y, al terminar el turno, un verificador independiente que no puede editar archivos ejecuta los tests y marca cada criterio con una evidencia.
  • Cola de fusión para los pasos en paralelo. Las ramas se fusionan de una en una sobre la última versión del destino y el comando de comprobación se ejecuta sobre el resultado fusionado. Por defecto, una rama en verde espera tu aprobación.
  • Equipo en paralelo para migraciones grandes. Para una migración que se divide en partes independientes, un equipo en paralelo ejecuta cada parte en su propio worktree, fusiona las partes terminadas en una rama de integración y lanza la comprobación final, así que revisas un único pull request.
  • Git sin salir de la app. El gestor de commits, los diffs y el historial en grafo están en el panel derecho, así que revisar cada paso y revertir uno es rápido.

Para terminar

La IA no hace que el código legacy sea seguro de tocar; eso lo hacen los tests y los pasos pequeños. Lo que cambia la IA es el coste de esos pasos. Escribir tests de caracterización, convertir una carpeta a TypeScript o lanzar un codemod y limpiar después era el trabajo para el que nunca había tiempo. Con agentes sale barato, así que puedes permitirte hacerlo bien: primero la red, un cambio cada vez, cada paso revisado y cada paso reversible.

Sigue leyendo:

Para probar AgentVera, empieza en la página de inicio.

Preguntas frecuentes

¿Puede la IA refactorizar código legacy de forma segura?

Puede, si le das una red de seguridad y pasos pequeños. Haz que el agente escriba primero tests de caracterización, luego que haga un cambio estructural cada vez, ejecute los tests después de cada paso y lo confirme con un commit. Las peticiones grandes del tipo «limpia este módulo» son donde los agentes cambian el comportamiento sin avisar.

¿Qué son los tests de caracterización?

Tests que registran lo que el código hace hoy, incluido el comportamiento raro o con bugs, en lugar de lo que debería hacer. Te permiten cambiar la estructura y darte cuenta al momento si el comportamiento se ha movido.

¿Dejo que un agente migre todo el código a TypeScript de una vez?

No. Activa allowJs, convierte un módulo o una carpeta cada vez, mantén el build y los tests en verde entre pasos y endurece las opciones del compilador poco a poco. Un único cambio gigante es muy difícil de revisar.

¿Cómo deshago un refactor hecho con IA que ha roto algo?

Si cada paso es su propio commit, git revert quita solo ese paso. Antes de fusionar, los checkpoints de AgentVera también pueden deshacer un único turno del agente. Después de fusionar, revierte el commit o el merge en main en lugar de reescribir el historial.

¿Qué agente es mejor para refactorizar?

Claude Code, Codex y Gemini CLI pueden hacerlo. Importa más el proceso que el modelo: tests primero, pasos pequeños y revisar cada diff. En AgentVera puedes ejecutarlos lado a lado en pasos distintos.

Reúne a tus agentes en un mismo escritorio.

Descarga AgentVera gratis; tus CLI instaladas ya están listas.

Más artículos