Modernizar código legacy con Claude Code de forma segura
Flujo práctico para modernizar código legacy con Claude Code: pruebas, TypeScript, riesgos, ejemplos ejecutables y verificación.
La modernización falla cuando empieza demasiado grande
El código legacy no es solo código antiguo. Es código cuyo comportamiento cuesta demostrar, que tiene poca cobertura de pruebas, que nadie quiere tocar y que suele romper procesos sensibles como pedidos, facturación, soporte o publicación de contenido. Claude Code ayuda mucho en este contexto, pero no debería usarse como un botón de reescritura masiva.
El patrón seguro es investigar primero, fijar el comportamiento actual con pruebas después y refactorizar en cortes pequeños al final. En este artículo, una prueba de caracterización significa una prueba que documenta cómo se comporta hoy el sistema, incluso si ese comportamiento no es perfecto. El harness es el andamiaje que permite trabajar al agente: pruebas, permisos, comandos, reglas del proyecto y checklist de revisión.
La documentación oficial de Claude Code common workflows organiza tareas como explorar un codebase, refactorizar, trabajar con pruebas, crear PR y usar worktrees. Esa es la mentalidad correcta para modernizar código legacy: Claude Code acelera el trabajo, pero el equipo sigue controlando el riesgo y la aceptación.
Tres casos de uso reales
No priorices por estética. Empieza por las zonas donde una mejora reduce riesgo de negocio.
| Caso de uso | Objetivo | Qué puede hacer Claude Code | Qué debe revisar una persona |
|---|---|---|---|
| Pedidos, facturación o pagos | Evitar errores de dinero y estado del cliente | Mapear comportamiento, añadir pruebas, encontrar bordes | Impuestos, descuentos, redondeo y reglas legales |
| Migración de JavaScript a TypeScript | Hacer más seguros los cambios futuros | Añadir tipos, reducir any, corregir errores por lotes | Compatibilidad de API pública y build |
| Callbacks o funciones gigantes | Mejorar mantenimiento sin cambiar conducta | Separar responsabilidades, proponer nombres, explicar diffs | Errores, reintentos, efectos secundarios y logs |
En ClaudeCodeLab uso este flujo para scripts de publicación, integración de checkout y transformaciones antiguas de contenido. Lo importante no es que Claude Code escriba rápido, sino que cada cambio sea revisable y tenga una prueba o una nota de verificación manual.
flowchart LR
A[Explorar] --> B[Fijar conducta con pruebas]
B --> C[Refactorizar en pasos pequeños]
C --> D[Añadir tipos y ordenar dependencias]
D --> E[Revisión humana del riesgo]
E --> B
Empieza con una auditoría de solo lectura
La primera instrucción debe prohibir ediciones. En ese momento necesitas un mapa del sistema, no un parche producido con contexto incompleto.
Lee @src/legacy y @test.
Todavía no modifiques archivos.
Devuelve esta auditoría:
1. Archivos principales y responsabilidades
2. I/O externo, base de datos, API, escrituras de archivos y efectos secundarios
3. Comportamientos que deben seguir siendo compatibles
4. Pruebas faltantes y ramas de alto riesgo
5. Orden más seguro para cambios pequeños
Si una regla no está clara, escribe "requiere confirmación humana" en vez de adivinar.
La página oficial How Claude Code works explica que Claude Code puede leer archivos, ejecutar comandos y editar código. Esa capacidad es útil, pero en sistemas antiguos conviene que la primera fase esté limitada a comprensión y plan. Un cambio que parece idiomático puede romper un contrato que otro servicio ya consume.
Ejemplo mínimo ejecutable
El ejemplo es pequeño a propósito, pero sigue la misma secuencia que aplico en proyectos reales. Primero crea el proyecto.
mkdir legacy-modernization-demo
cd legacy-modernization-demo
npm init -y
npm install -D vitest typescript @types/node
npm pkg set type="module"
npm pkg set scripts.test="vitest run"
npm pkg set scripts.typecheck="tsc --noEmit"
mkdir -p src/legacy test
El procesador antiguo mezcla validación, cálculo y construcción de respuesta. No es un ejemplo artificialmente terrible; es el tipo de código que funciona y por eso se ha quedado años en producción.
// src/legacy/orderProcessor.js
export function processOrder(order) {
if (!order || !Array.isArray(order.items) || order.items.length === 0) {
return { status: "error", message: "items is required" };
}
const subtotal = order.items.reduce((sum, item) => {
return sum + item.price * item.qty;
}, 0);
const discount = order.customer?.type === "vip" ? subtotal * 0.1 : 0;
return {
status: "confirmed",
total: subtotal - discount,
items: order.items,
discount
};
}
Ahora fija el comportamiento actual con Vitest. Estas pruebas no intentan rediseñar el dominio; solo protegen lo que no puede cambiar sin una decisión explícita.
// test/orderProcessor.test.ts
import { describe, expect, it } from "vitest";
import { processOrder } from "../src/legacy/orderProcessor.js";
describe("processOrder legacy behavior", () => {
it("calculates total for a regular customer", () => {
const result = processOrder({
items: [
{ id: "A1", qty: 2, price: 1000 },
{ id: "B2", qty: 1, price: 500 }
],
customer: { id: "C1", type: "regular" }
});
expect(result).toMatchObject({
status: "confirmed",
total: 2500,
discount: 0
});
});
it("applies a 10 percent VIP discount", () => {
const result = processOrder({
items: [{ id: "A1", qty: 1, price: 10000 }],
customer: { id: "C2", type: "vip" }
});
expect(result.status).toBe("confirmed");
expect(result.total).toBe(9000);
expect(result.discount).toBe(1000);
});
it("returns an error when items are empty", () => {
const result = processOrder({
items: [],
customer: { id: "C3", type: "regular" }
});
expect(result.status).toBe("error");
expect(result.message).toContain("items");
});
});
Ejecuta npm test. Solo cuando pase, pide a Claude Code que edite.
Lee @src/legacy/orderProcessor.js y @test/orderProcessor.test.ts.
Migra este código a TypeScript manteniendo las pruebas en verde.
Reglas:
- Mantén el nombre público processOrder
- Conserva status, total, discount y message
- Añade tipos primero y separa responsabilidades después
- Ejecuta npm test y npm run typecheck
- Explica qué compatibilidad preservó cada diff
Una estructura TypeScript más revisable
Después de la modernización, separa tipos, validación, cálculo y orquestación. La meta no es crear abstracciones elegantes, sino hacer visible la lógica que afecta dinero o estado de clientes.
// src/orderTypes.ts
export type CustomerType = "regular" | "vip";
export type OrderItem = {
id: string;
qty: number;
price: number;
};
export type OrderInput = {
items: OrderItem[];
customer: {
id: string;
type: CustomerType;
};
};
export type OrderResult =
| {
status: "confirmed";
total: number;
items: OrderItem[];
discount: number;
}
| {
status: "error";
message: string;
};
// src/validators.ts
import type { OrderInput } from "./orderTypes";
export function validateOrder(order: OrderInput | null | undefined): string | null {
if (!order || !Array.isArray(order.items) || order.items.length === 0) {
return "items is required";
}
return null;
}
// src/calculators.ts
import type { CustomerType, OrderItem } from "./orderTypes";
export function calculateSubtotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
export function calculateDiscount(subtotal: number, customerType: CustomerType): number {
return customerType === "vip" ? subtotal * 0.1 : 0;
}
// src/orderProcessor.ts
import { calculateDiscount, calculateSubtotal } from "./calculators";
import type { OrderInput, OrderResult } from "./orderTypes";
import { validateOrder } from "./validators";
export function processOrder(order: OrderInput): OrderResult {
const validationMessage = validateOrder(order);
if (validationMessage) {
return { status: "error", message: validationMessage };
}
const subtotal = calculateSubtotal(order.items);
const discount = calculateDiscount(subtotal, order.customer.type);
return {
status: "confirmed",
total: subtotal - discount,
items: order.items,
discount
};
}
Cambia el import del test a ../src/orderProcessor, luego ejecuta npm test y npm run typecheck. Un diff de este tamaño aún se puede revisar con seriedad. Si el mismo PR también mueve carpetas, sube dependencias mayores, cambia formato y renombra conceptos del dominio, la revisión se vuelve débil.
Separa las actualizaciones de dependencias
Otro error frecuente es mezclar refactorización con upgrades mayores. Cuando algo falla, ya no sabes si la causa es TypeScript, una API rota, el bundler o la lógica que acabas de tocar.
Primero pide inventario.
Lee package.json y el lockfile.
No actualices nada todavía.
Devuelve una tabla con:
- paquete
- versión actual
- versión objetivo recomendada
- si es major upgrade
- URL de la guía oficial de migración
- archivos probablemente afectados
- pruebas que deberíamos añadir antes de actualizar
Para operaciones destructivas o amplias, mantén permisos conservadores. La documentación oficial de Claude Code permissions conviene revisarla antes de permitir migraciones, borrados o despliegues. La velocidad del agente no sirve si elimina el punto de aprobación que protegía al sistema.
Errores concretos que veo a menudo
El primer error es refactorizar antes de tener pruebas. Un código más limpio sigue siendo una regresión si cambia un descuento, un redondeo o un mensaje de error.
El segundo es aceptar una sugerencia de Claude Code como regla de negocio. Que una respuesta sea más idiomática no significa que los clientes existentes puedan procesarla.
El tercero es crear un PR enorme. Migración de tipos, separación de lógica, actualizaciones de dependencias, movimientos de archivos y formateo deberían separarse.
El cuarto es “mejorar” demasiado rápido el manejo de errores. En sistemas antiguos, un null, una cadena concreta o un estado HTTP raro puede ser parte del contrato.
El quinto es dejar la documentación para el final. Pide a Claude Code que incluya notas de compatibilidad, pasos de verificación manual y plan de rollback en la descripción del PR.
Revisión, enlaces y CTA
Combina este flujo con la guía de automatización de refactorización, TDD con Claude Code y la generación automática de documentación. Para equipos, documenta zonas prohibidas, comandos de prueba, términos de dominio y reglas de revisión en buenas prácticas de CLAUDE.md.
ClaudeCodeLab ayuda a equipos que quieren introducir Claude Code en productos existentes mediante formación, plantillas de CLAUDE.md y consultoría de modernización. El objetivo no es una reescritura con IA, sino un flujo repetible donde pruebas, permisos y revisión humana trabajan juntos.
Resultado verificado
Probé el flujo creando legacy-modernization-demo: primero el procesador JavaScript antiguo, luego tres pruebas con Vitest, después la migración a TypeScript y finalmente npm test junto con npm run typecheck. La mayor reducción de riesgo vino de separar la auditoría de solo lectura del prompt de edición. Los diffs fueron más pequeños y fue más fácil confirmar que total, descuento VIP y error de pedidos vacíos seguían siendo compatibles.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.