Saltar al contenido principal

Prompts para Migrar Código Legacy con IA sin Romper Nada

10 prompts para migrar legacy con IA:Strategy de migración por capas, strangler fig, refactor incremental y tests que garantem que nada se rompe.

Fran Cobos 20 min de lectura 3805 palabras

Tabla de contenidos

El lunes por la mañana le dices a la IA “migra este módulo de PHP 7 a Node 22”. El viernes tienes un servicio nuevo que funciona, que tiene los tests en verde y que parece listo. El lunes siguiente el cliente reporta que los usuarios con el proveedor de pago español ven el IVA mal calculado. En el código viejo había un if ($pais == 'ES') { $iva = 0.21; } y un elseif que nadie se había fijado en tres años. En el nuevo no está.

Ese es el ciclo. Y lo importante es que la IA no falló haciendo algo descuidado: hizo exactamente lo que le pediste, que era una migración completa sin red de seguridad. Sin tests que congelen el comportamiento viejo, no hay forma de saber qué se perdió. Solo se descubre cuando un cliente se queja, y para entonces el arreglo ya está en producción.

Hay una segunda capa de por qué. Cuando le pides a un modelo que migre cien archivos, produce cien archivos coherentes entre sí y probablemente correctos en lo obvio. Pero la revisión humana de cien archivos es un problema distinto: nadie la hace a fondo, y el sesgo de aprobación es enorme porque todo parece razonable. El resultado es un sistema nuevo con una base de código que parece buena y que tiene bugs en los bordes que nadie miró.

La lección que se repite en todos los proyectos de migración es incómoda pero simple: la IA acelera la escritura, no la verificación. Todo el trabajo que hagas para no depender de la verificación humana (tests de caracterización antes de empezar, comparador de salidas, migraciones pequeñas con rollback) es lo que convierte la migración en algo seguro.

Si vas a migrar el frontend a una estructura moderna, la referencia de estructura de carpetas en React y Next.js te da el destino antes de empezar. Y si lo que quieres es Express más estructura, está el por qué usar NestJS en un SaaS.

Las cuatro reglas que evitan la hecatombe

  1. Nunca migres código que no puedas probar. Si no hay forma de comparar el comportamiento viejo y el nuevo, no empieces. Punto.
  2. Migraciones de menos de cinco archivos por PR. Pequeño es lo que hace que la revisión sea real y el rollback sea un revert.
  3. El código viejo no se borra hasta semanas después. No cuando “ya funciona”, sino cuando llevar semanas en producción sin incidentes.
  4. Una migración, un eje. No cambies framework, base de datos y despliegue a la vez. Aunque la IA te lo ofrezca todo junto.

Fase 0: entender antes de tocar

1. Radiografía del módulo antes de tocarlo

Voy a migrar este módulo: [pegar el código entero].
Contexto: [qué hace el sistema, quién lo consume, qué contratos tiene].

Antes de proponer nada, hazme una radiografía:
1. Qué hace este módulo, en cinco líneas, en lenguaje de negocio
2. Su API pública: funciones, clases y rutas que otros módulos usan
3. Las dependencias externas (base de datos, APIs, ficheros, colas)
4. Los efectos secundarios (escribe en BD, manda correos, toca el disco)
5. Los cinco puntos más frágiles: rarezas, workarounds, código que parece absurdo
6. Los casos límite que debería tener tests y probablemente no tiene
7. Qué haría falta saber de alguien que lo mantiene y ya no está

No propongas migración todavía. Solo entiende.

Por qué funciona: es el patrón del patito de goma aplicado a un módulo entero. Pedir la radiografía antes del plan expone los supuestos que el código esconde, y esos supuestos son exactamente los que se pierden al reescribir. Los puntos 5 y 6 son los que evitan el bug del IVA.

2. Tests de caracterización antes de cambiar nada

Código a congelar: [pegar].
Framework de tests del proyecto: [Jest / PHPUnit / pytest / ninguno].

Escribe tests de caracterización: pruebas que documenten lo que el código
HACE AHORA, no lo que debería hacer. Si un comportamiento parece un bug,
lo reproduces igualmente y lo marcas con `// BUG CONOCIDO: [por qué]`.

Cubrir, en este orden:
1. El camino feliz con valores típicos
2. Casos límite del dominio (nulo, cero, negativo, vacío)
3. Los workarounds que detectaste en la radiografía
4. El comportamiento con datos raros (texto con acentos, fechas, importes)

Cada test con un nombre que describa el comportamiento, no la función.
Ejecuta los tests contra el código viejo y pásamelos todos en verde.

Por qué funciona: es la pieza que convierte una migración en algo verificable. Los tests de caracterización no dicen lo que el código debería hacer, dicen lo que hace. Cuando el nuevo sistema falle uno, sabes que has perdido un comportamiento, no solo un test.

Ejemplo real de la diferencia:

// ❌ Test que expresa lo que el código DEBERÍA hacer
test('aplica el IVA correcto', () => {
  expect(calcularTotal(100, 'ES')).toBe(121); // falla: el legacy devuelve 121.00 string
});

// ✅ Test de caracterización: congela lo que hace, rareza incluida
test('ES devuelve el total como string con 2 decimales', () => {
  expect(calcularTotal(100, 'ES')).toBe('121.00');
});

test('proveedor desconocido NO aplica IVA (raría histórica, sin tocar)', () => {
  expect(calcularTotal(100, 'XX')).toBe(100);
});

3. Comparador de salidas viejo/nuevo

Tengo dos implementaciones de la misma función:
- VIEJA (legacy): [pegar]
- NUEVA (ya escrita): [pegar]

Quiero un script que compare las salidas y me diga si divergen.
Casos: [generar combinaciones de inputs, incluidos los casos límite].
Formato de salida: JSON.

Quiero que detecte: diferencias de valor, de tipo, de orden y de redondeo.
Un array reordenado es una diferencia, aunque tenga los mismos elementos.

Devuélveme el script listo para ejecutar y cómo interpretar un fallo.

Por qué funciona: los tests de caracterización cubren los casos que pensaste; el comparador cubre los que no pensaste. Es la diferencia entre probar cinco escenarios y probar mil combinaciones. La regla del array reordenado es la que más falsos negativos evita.

Fase 1: estrategia de migración

4. Elegir estrategia antes de migrar

Sistema: [descripción, stack, tamaño, equipo].
Objetivo de la migración: [por qué se hace: rendimiento, mantenibilidad, coste].
Plazo: [semanas].
Equipo: [N devs].

Compara estas estrategias para MI caso y recomienda una:
A) Reescritura completa (big bang)
B) Migración por capas, módulo a módulo, con strangler fig
C) Refactor incremental sin cambiar de stack
D) Mover solo la base de datos y dejarlo todo

Para cada una: coste en semanas, riesgo, qué se rompe, reversibilidad,
y qué pasa con el equipo durante la migración.
Elige una y justifica con mis números, no con la teoría.

Por qué funciona: sin este paso, cada migración acaba siendo A (reescritura) porque es la más fácil de empezar y la más cara de terminar. Con los números delante, la respuesta sale sola.

Comparativa de estrategias:

EstrategiaTiempoRiesgoReversibleCuándo tiene sentido
Big bangAltoMuy altoNo, casi nuncaSistema pequeño y aislado
Por capas (strangler fig)MedioBajoSí, módulo a móduloLegacy en producción que no para
Refactor incrementalBajoMuy bajoSí, commit a commitEl stack no es el problema real
Solo datosMedioMedioSí, con Trigger/CDCEl esquema es el cuello de botella
Congelar y reescribir detrásMuy altoMedioSí, con flagLa lógica sigue igual, cambia la infra

Por qué funciona: la tabla convierte “migrar legacy” en una decisión con criterios. El error clásico es medir el tiempo de la migración en lugar del riesgo, y el riesgo es lo que acaba costando.

5. Plan de migración por capas

Estrategia elegida: [B, strangler fig].
Sistema: [módulos que tiene].
Resultado final deseado: [arquitectura objetivo].

Diseña el plan por capas:
1. Qué módulo se migra primero y por qué ese (el más aislado o el más crítico)
2. La fachada: cómo el código viejo sigue sirviendo mientras entra el nuevo
3. Los puntos de entrada exactos (rutas, eventos, jobs) donde se cambia el enrutado
4. Qué se migra en cada iteración, con tamaño máximo de 5 archivos
5. Qué feature flag controla cada iteración y cómo se revierte
6. El criterio de "esta capa está terminada" para pasar a la siguiente
7. Qué pasa con los datos: compartidos, duplicados o migrados

Timeline realista por iteración y qué se entrega en cada una.

Por qué funciona: la fachada es lo que convierte una migración en un cambio continuo. Sin ella estás haciendo un big bang por etapas. Y el criterio de terminación por capa evita el limbo de “está casi, pero falta una cosa más”.

6. Feature flags para migrar sin downtime

Migración en curso: [qué módulo se está moviendo al nuevo sistema].
Sistema de flags: [LaunchDarkly / Unleash / variables de entorno / propio].

Dame:
- La flag, su nombre y su alcance (por usuario, por porcentaje, por entorno)
- Cómo se enciende para el equipo interno primero
- Cómo se sube al 10%, 50%, 100% con criterios de parada automáticos
- El kill switch: cómo volver al 100% viejo en menos de 10 segundos
- Qué se mide en cada etapa y qué umbral dispara el rollback automático
- Qué pasa con las sesiones abiertas cuando cambias el backend

El objetivo es poder volver atrás sin desplgar nada nuevo.

Por qué funciona: la feature flag es lo que convierte el rollback de “restaurar una base de datos” en “cambiar un booleano”. Sin ella, cada migración es un despliegue irreversible, y ahí empieza el miedo que hace que la gente posponga las migraciones indefinidamente.

Fase 2: traducir entre stacks

7. Traducción de un módulo entre stacks

Traduce este módulo de [jQuery a React 19 / PHP 8 a Node 22 con Fastify /
Express a NestJS 11 / Angular a React] al stack destino: [pegar].

REGLAS NO NEGOCIABLES:
1. Comportamiento idéntico, incluidas las rarezas de la versión vieja
2. No uses el código del legacy como guía de estilo del nuevo: usa el estilo
   del proyecto de destino, leyendo un módulo ya migrado
3. Si el legacy hace algo mal, migra el comportamiento y deja un TODO con
   el motivo, NO lo "mejores" por tu cuenta
4. Toda la lógica de negocio va testeada; el código generado va en un commit aparte

Primero dame el mapa de equivalencias: qué hace cada pieza en el destino.
Después el código. Después los tests de caracterización portados.
Si alguna equivalencia no existe en el stack destino, para y pregúntame.

Por qué funciona: la regla 3 es la que evita el bug del IVA. Es tentadísimo “mejorar” el código viejo mientras lo traduces, y ese es exactamente el punto donde el comportamiento se pierde. Y la instrucción de parar y preguntar convierte una suposición peligrosa en una decisión consciente.

Ejemplo real del patrón de la regla 3:

// ❌ La IA "mejora" el legacy y cambia el comportamiento
// Legacy: si el proveedor es desconocido, IVA 0
const IVA: Record<string, number> = { ES: 0.21, FR: 0.20, DE: 0.19 };
const tipo = IVA[proveedor] ?? 0.21; // "mejorado": ahora los desconocidos pagan

// ✅ Mismo comportamiento, con el motivo documentado
const IVA: Record<string, number> = { ES: 0.21, FR: 0.20, DE: 0.19 };
// TODO BUG CONOCIDO: el legacy aplicaba 21% por defecto a proveedores
// desconocidos. Hay ticket #412. Cuando se arregle, cambia a ?? 0.
const tipo = IVA[proveedor] ?? 0.21;

8. Migrar la base de datos sin downtime

Base de datos actual: [PostgreSQL 12 / MySQL 5.7].
Esquema implicado: [pegar DDL].
Volumen: [N filas, tamaño].
Aplicación: [cómo accede hoy a esos datos].

Diseña la migración de esquema sin downtime:
1. Patrón expandir-migrar-contrenir, paso a paso
2. Qué se añade en cada fase y por qué no rompe la app vieja
3. Cómo se dual-write o se hace backfill con los [N] millones de filas
4. Cómo se verifica que ambos caminos devuelven lo mismo
5. Cuándo se puede eliminar la columna vieja y qué pasa si no
6. Rollback de cada fase

Dale el SQL de cada fase. Nada de "añade un índice aquí" sin el SQL.

Por qué funciona: en una migración con tráfico real, añadir una columna no es un problema. Renombrarla sí lo es, porque la app vieja sigue consultando el nombre viejo. El patrón expandir-migrar-contrenir existe para que ninguna fase rompa la versión anterior, y eso solo se garantiza si cada fase tiene su SQL.

9. Refactor de un módulo vivo, sin feature flag

Quiero refactorizar este módulo que está en producción y no puede tener
parada: [pegar código].
No puedo añadir feature flags porque [motivo real: monolito sin infraestructura].

Dame un plan de refactor seguro sin flags:
1. Qué extracciones son puras (no cambian comportamiento) y cuáles no
2. En qué orden hacerlas para que cada commit sea desplegable
3. Qué test necesita cada extracción antes de hacerla
4. Qué señales observas entre commit y commit para detectar que algo se rompió
5. Cómo hacer el cutover final sin downtime

Un commit, un cambio, un rollback posible. Nada de refactorizar en un PR de
300 líneas.

Por qué funciona: mucha gente cree que sin feature flags no hay forma de migrar sin riesgo, y eso lleva a no hacer nada. La alternativa real es hacer muchos cambios pequeños, cada uno desplegable y verificable, en vez de uno grande e irreversible. La clave es elegir extracciones puras, que son más frecuentes de lo que parece.

Fase 3: verificación y rollback

10. Verificar que la migración no rompió nada

Migración: [qué se movió].
Sistema viejo: [cómo se ejecuta todavía, o cómo se ejecutaba].
Sistema nuevo: [cómo se ejecuta ahora].

Comparación: [qué parte se compara].
Mismo dataset: [cómo se garantiza].
Tolerance: [cero diferencias, o diferencias toleradas y por qué].

Devuélveme:
1. Un script que ejecute las dos versiones sobre los mismos datos
2. Una comparación de salidas que normalice formato, orden y redondeo
3. Una lista explícita de diferencias aceptables y por qué
4. Los casos que el script NO cubre, con el riesgo de cada uno

Y dame el criterio de aceptación: qué resultado significa "puedo avanzar".

Por qué funciona: la migración no se da por buena cuando el código nuevo pasa sus propios tests. Se da por buena cuando produce lo mismo que el viejo sobre datos reales. Este prompt produce el script que lo demuestra, no la afirmación.

11. Plan de rollback

Migración desplegada: [qué].
Señales de alarma: [qué métrica o error indica que está fallando].
Ventana de decisión: [cuánto tiempo tienes antes de que el rollback sea caro].

Dame el plan de rollback:
1. Qué se revierte y en qué orden (código, datos, flags)
2. Cuánto tarda cada paso y quién lo ejecuta
3. Si hay migración de datos irreversible, cómo se compensa
4. Qué pasa con los datos escritos por la versión nueva durante la ventana
5. El punto sin retorno: el momento en el que ya no se puede volver atrás
6. Cómo decidir en menos de 5 minutos si se hace rollback

Sé honesto con el punto sin retorno. Si existe, dilo aunque no me guste.

Por qué funciona: casi todas las migraciones tienen un punto sin retorno en cuanto empiezan a escribirse datos con el esquema nuevo. Reconocerlo por escrito evita la conversación incómoda de “¿podemos volver?” cuando ya es demasiado tarde, y fija la decisión antes de que haya presión.

12. Auditoría posterior a la migración

Migración terminada: [qué, cuándo, cuánto duró].
Incidencias posteriores: [ninguna / descripción].

Quiero una revisión post-mortem técnica:
1. ¿El código viejo y el nuevo presentan divergencias que nadie comprobó?
2. ¿Qué tests de caracterización han dejado de servir y cuáles sobran ya?
3. ¿El código viejo que dejamos sigue siendo un riesgo (bug fixing, acceso)?
4. ¿Hay deuda nueva: TODOs, flags activos, código muerto?
5. ¿Qué aprendimos que aplicaría en la siguiente migración?

Devuélveme un informe corto con acciones concretas y responsable.

Por qué funciona: cerrar el ciclo es lo que convierte una migración en aprendizaje. Los tests de caracterización que ya no atrapan nada son ruido que hay que borrar; los que siguen fallando cuando cambia el legacy son la señal de que aún queda acoplamiento. Y sin esa revisión, cada migración se hace desde cero otra vez.

Anti-patrones que destruyen migraciones

❌ La migración big bang con IA

# ❌ "Migra todo el proyecto" en un solo intento
# Resultado: 180 archivos nuevos, cero revisión real, tres bugs en producción

❌ El prompt sin contexto del destino

❌ "Convierte este código de jQuery a React"

Sin leer un módulo ya migrado del proyecto, la IA produce React genérico de blog, no tu arquitectura.

✅ El prompt acotado con contexto

Lee primero estos dos ficheros del proyecto destino:
- [componente ya migrado que se parece a este]
- [hook de datos que usaremos]
Y luego traduce siguiendo esos patrones, no los tuyos.

❌ “Mejorar” el legacy mientras se traduce

// ❌ La IA "limpia" y cambia el comportamiento
$total = $subtotal * (1 + $tax);
if ($rounding == 'up') $total = ceil($total); // el legacy solo redondeaba en EUR

// ✅ Se porta tal cual y se documenta la rareza
// TODO: el legacy solo redondeaba en EUR. Ticket #388.
$total = $subtotal * (1 + $tax);
if ($rounding == 'up' && $currency === 'EUR') $total = ceil($total);

❌ Borrar el legacy el día que la migración funciona

❌ rm -rf legacy/          # en el mismo commit que "ya funciona"

✅ El legacy se va 4-8 semanas después, con métricas de incidentes en cero

❌ Confundir más tests con más seguridad

// ❌ 40 tests nuevos que solo comprueban que el código hace lo que hace
expect(fn(1, 'ES')).toBe('121.00'); // mismo valor, mismo motivo, misma razón

// ✅ Tests que comparan contra el legacy y detectan divergencia real
expect(nuevo(1, 'ES')).toEqual(viejo(1, 'ES')); // el valor cambia si el legacy cambia

Errores frecuentes y cómo salir de ellos

  • “La IA se inventa que el legacy hace otra cosa”: casi siempre es que no le has dado los datos ni el contexto. Pídele que cite las líneas exactas donde comprueba cada afirmación sobre el comportamiento.
  • “Migré el módulo y ahora el servicio no arranca”: el error más común al migrar es el contrato: rutas, códigos de estado, headers, formatos de fecha y zona horaria. Pide un inventario de contrato antes de empezar.
  • “La ventana de contexto no me cabe el módulo entero”: eso pasa con archivos grandes. Usa los límites naturales del módulo y valida cada parte por separado, como explico en cómo aprovechar la context window.
  • “El código generado se ve perfecto y tiene un bug en el borde”: el código limpio esconde los casos raros, y los casos raros son los que no se ven. Por eso los tests de caracterización van primero, no después.
  • “Me pido la migración en 5 PRs de 400 líneas cada uno”: eso no son cinco PRs, es un big bang por etapas. Baja a cinco archivos por PR aunque tarde más; el tiempo total va a ser menor porque no vas a perder un día por bug.
  • “El equipo no sabe qué se está moviendo”: escribe un ADR por capa migrada. Sin el ADR con las decisiones, en tres meses nadie sabe por qué el módulo X está duplicado.

El flujo completo

┌────────────────────────────────────────────────────────────┐
│  FASE 0 - ENTENDER (sin escribir código nuevo)             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ Prompt 1: radiografía del módulo                     │  │
│  │ Prompt 2: tests de caracterización (LO MÁS IMPORTANTE)│  │
│  │ Prompt 3: comparador de salidas viejo/nuevo          │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────────────┬───────────────────────────────┘
                             ▼
┌────────────────────────────────────────────────────────────┐
│  FASE 1 - DECIDIR                                           │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ Prompt 4: elegir estrategia (big bang vs strangler)  │  │
│  │ Prompt 5: plan por capas con criterios de fin         │  │
│  │ Prompt 6: feature flags y kill switch                 │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────────────┬───────────────────────────────┘
                             ▼
┌────────────────────────────────────────────────────────────┐
│  FASE 2 - MIGRAR (iteraciones de menos de 5 archivos)      │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ Prompt 7: traducir módulo con reglas de fidelidad    │  │
│  │ Prompt 8: esquema de BD sin downtime                 │  │
│  │ Prompt 9: refactor sin flags, cambios puros          │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────────────┬───────────────────────────────┘
                             ▼
┌────────────────────────────────────────────────────────────┐
│  FASE 3 - CERRAR                                            │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ Prompt 10: verificación con datos reales             │  │
│  │ Prompt 11: rollback y punto sin retorno              │  │
│  │ Prompt 12: post-mortem y limpieza                    │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────┘

Conclusión

La IA es excelente migrando código y mala verificando que el código migrado hace lo mismo. Esa asimetría define la estrategia entera: usa todo el automatismo posible para escribir y todo el esfuerzo posible para no depender de la revisión humana.

Si te llevas una sola cosa de este artículo, que sea esta: los tests de caracterización antes del primer cambio. Son la única parte del trabajo que la IA no puede hacer por ti y la única que convierte “confío en que no se rompió nada” en “lo he comprobado”. Sin ellos, todo lo demás es una apuesta.

Lo segundo que te llevas es el tamaño. Migraciones pequeñas, frecuentes y reversibles fallan mucho menos que migraciones grandes y definitivas, y el tiempo total acaba siendo menor porque no pagas los días que se pierden depurando en producción. El strangler fig existe por una razón: deja el sistema viejo ahí, sirviendo, hasta que el nuevo esté demostrado.

Y si en algún momento te cansas de la recomendación automática y el código sale incompleto a medias, eso suele ser el síntoma de que falta contexto, no de que falte un prompt mejor. Está el detalle en código incompleto por lazy coding, y el resto de errores frecuentes con asistentes en errores comunes con Copilot y Cline.

Para lo que no es legacy pero se parece, también te puede servir testear el código generado por IA: los tests de caracterización son, en el fondo, la misma disciplina aplicada a algo que no controlas del todo. Y si el cuello de botella mientras migras son las queries, está prompts para optimizar SQL.


Sigue aprendiendo:

Fran Cobos

Fran Cobos

Desarrollador Full Stack especializado en IA aplicada, automatización y desarrollo web. Escribo sobre herramientas, tutoriales y casos reales para programadores.

¿Necesitas desarrollo a medida?

Apps web, IA, módulos ERP — cuéntame tu proyecto.