Cómo usar Claude Code para refactorizar una aplicación TypeScript
Refactorizar con un coding agent no es pedir 'mejora esto'. Es un proceso con pasos concretos, puntos de verificación y una lista de cosas que salen mal si te confías.
Pedirle a un coding agent que “refactorice este módulo” y aceptar el resultado sin más es la forma más rápida de introducir un bug que no vas a detectar hasta producción. No porque el modelo sea poco capaz —Claude Opus 4.8 resuelve el 88,6% de los problemas de SWE-bench Verified, un conjunto de tareas de ingeniería real— sino porque un refactor tiene una propiedad que generar código nuevo no tiene: el comportamiento ya existe, ya lo usan otras partes del sistema, y “parece que funciona” no es lo mismo que “sigue haciendo exactamente lo mismo que antes”.
Este artículo describe el workflow que de verdad funciona con Claude Code para refactorizar TypeScript en una base de código real, no un ejemplo de juguete: preparar el terreno, diagnosticar en modo lectura, dividir en pasos que se puedan verificar uno a uno, y una lista concreta de lo que sale mal cuando te saltas alguno de esos pasos.
Por qué un refactor es un tipo de tarea distinto
Generar una función nueva tiene un criterio de éxito relativamente simple: ¿hace lo que le pedí? Refactorizar tiene dos criterios simultáneos, y el segundo es el que se olvida: ¿sigue haciendo exactamente lo mismo que antes, para todos los casos que ya estaban cubiertos, incluidos los que nadie recuerda por qué existen? Un agente sin ese segundo criterio explícito optimiza solo por el primero: código que “se ve mejor”, aunque cambie silenciosamente un edge case.
La mitigación no es desconfiar del modelo en abstracto. Es estructurar la tarea para que el segundo criterio sea verificable en cada paso, no solo al final.
Paso 1: preparar el terreno antes de tocar código
Si el repositorio no tiene un CLAUDE.md con el stack, las convenciones del proyecto y los comandos habituales (npm test, npm run typecheck, cómo se levanta el entorno), es la primera inversión que hay que hacer, y es de las de mayor retorno: diez minutos que evitan que el agente reinvente convenciones ya decididas o use un comando de test equivocado en cada sesión.
Lo segundo, no negociable: tiene que haber tests que cubran el comportamiento actual del código que vas a tocar. Si no los hay, escribirlos (o pedirle a Claude Code que los escriba, revisándolos tú) es el paso cero del refactor, no un extra. Sin una red de seguridad automatizable, “verificar cada paso” se convierte en “leer el diff a ojo y confiar”, que es justo lo que este workflow intenta evitar.
<!-- CLAUDE.md (fragmento relevante para un refactor) -->
## Comandos
- Tests: `npm test`
- Typecheck: `npm run typecheck`
- Lint: `npm run lint`
## Convenciones de este repo
- Preferimos `type` sobre `interface` salvo en las props de componentes públicos.
- Nunca usar `any`; si el tipo es realmente desconocido, `unknown` + narrowing.
- Los módulos de `src/domain/` no importan nada de `src/infra/`.
Paso 2: diagnóstico en modo lectura, sin tocar nada
Antes de que el agente escriba una sola línea, pídele que lea el módulo objetivo y mapee sus dependencias: qué lo importa, qué efectos secundarios tiene, qué asunciones implícitas hace el código actual. Claude Code tiene un modo dedicado para esto —Plan Mode (/plan o Shift+Tab dos veces)— en el que el agente puede leer e investigar pero no puede editar archivos ni ejecutar comandos que cambien el proyecto. Es la fase en la que detectas si el agente ha entendido mal algo antes de que ese malentendido se convierta en un diff.
Paso 3: dividir en pasos pequeños y verificables
Este es el punto donde más se falla: pedir un refactor grande de una sola vez (“convierte todo este servicio a usar el nuevo cliente HTTP y separa la lógica de negocio”) genera un diff enorme, difícil de revisar con atención real, y difícil de revertir de forma parcial si algo falla.
Ejemplo concreto: un servicio de facturación con una función de 200 líneas que mezcla validación, cálculo y llamadas a una API externa, tipada con any en varios puntos.
// Antes — un fragmento representativo del problema
async function procesarFactura(datos: any) {
if (!datos.cliente || !datos.lineas) throw new Error('inválido');
let total = 0;
for (const linea of datos.lineas) {
total += linea.cantidad * linea.precio * (1 - (linea.descuento || 0));
}
const resultado = await fetch('https://api.facturacion.com/emitir', {
method: 'POST',
body: JSON.stringify({ cliente: datos.cliente, total }),
});
return resultado.json();
}
El refactor se divide en pasos independientes, cada uno con su propio checkpoint verificable:
- Tipar la entrada (
FacturaInputconzodo tipos explícitos) sin cambiar ninguna lógica. Correr tests. Commit. - Extraer el cálculo del total a una función pura
calcularTotal(lineas: LineaFactura[]): number, testeable de forma aislada. Correr tests. Commit. - Extraer la llamada a la API a un cliente inyectable (
FacturacionClient), para poder mockearlo en tests. Correr tests. Commit. - Componer las tres piezas en la función original, ahora mucho más corta. Correr tests. Commit.
// Después del paso 4
type LineaFactura = { cantidad: number; precio: number; descuento?: number };
type FacturaInput = { cliente: string; lineas: LineaFactura[] };
function calcularTotal(lineas: LineaFactura[]): number {
return lineas.reduce((acc, l) => acc + l.cantidad * l.precio * (1 - (l.descuento ?? 0)), 0);
}
async function procesarFactura(datos: FacturaInput, cliente: FacturacionClient) {
const total = calcularTotal(datos.lineas);
return cliente.emitir({ cliente: datos.cliente, total });
}
Cada uno de esos cuatro pasos es una sesión corta y acotada con Claude Code, con un criterio de éxito binario (tests en verde, typecheck limpio) antes de pasar al siguiente. Si el paso 2 rompe algo, lo sabes inmediatamente y el diff que hay que revisar es pequeño, no un cambio de 200 líneas donde el bug puede estar en cualquier parte.
Paso 4: verificar cada paso, no solo confiar en “parece que funciona”
Después de cada paso: git diff --stat para ver el alcance real del cambio, la suite de tests, y el typecheck. Claude Code registra automáticamente un checkpoint antes de cada turno de la conversación (hasta los cien más recientes de la sesión), accesible con /rewind o pulsando Esc dos veces con el prompt vacío. Es la red de seguridad para el propio proceso: si un paso sale mal, revierte los archivos a como estaban antes de ese cambio sin perder el resto de la conversación ni el contexto que ya habías construido.
Errores típicos de confiar ciegamente en la salida
- Aceptar un diff grande sin leerlo entero. Si el cambio es demasiado largo para revisarlo con atención en cinco minutos, es demasiado grande para un solo paso; había que haberlo dividido antes de pedirlo.
- No correr los tests después de cada paso, solo al final. Cuando algo falla al final de una cadena de cinco cambios, no sabes cuál de los cinco lo rompió sin hacer bisección manual, exactamente el trabajo que dividir en pasos pretendía evitar.
- Dar por hecho que “los tests pasan” significa “el comportamiento es idéntico”. Los tests solo verifican lo que cubren. Un refactor que toca una rama sin test asociado puede cambiar silenciosamente su comportamiento y nadie se entera hasta que un caso real la ejecuta en producción. Si el refactor toca una zona sin cobertura, escribir el test que falta es parte del trabajo, no un lujo.
- Dejar que el agente “arregle” un test que falla cambiando el test en vez de el código. Es el fallo más traicionero porque produce una suite en verde que ya no prueba nada. Revisa siempre el diff de los propios archivos de test, no solo el código de producción.
- Pedir el refactor y el cambio de comportamiento a la vez. “Refactoriza esto y de paso arregla el bug del descuento” mezcla dos cosas con criterios de verificación distintos. Sepáralos: primero el refactor puro (comportamiento idéntico, verificable con los tests existentes), después el cambio funcional (comportamiento nuevo, con un test nuevo que lo demuestre).
Cuándo NO delegar el refactor completo a un agente
Hay código donde el coste de un error silencioso es demasiado alto para este workflow tal cual: lógica de cálculo de dinero sin cobertura de test previa, código de autenticación o permisos, o cualquier módulo que toque datos de producción sin posibilidad de rollback rápido. En esos casos, el agente puede seguir siendo útil para el diagnóstico (Paso 2) y para proponer el plan, pero la escritura del cambio final —o al menos su revisión línea a línea antes de mergear— debería quedar en manos humanas con más fricción deliberada, no menos.
El resultado de hacerlo bien
Un refactor dividido en pasos pequeños con verificación en cada uno no es más lento que pedirlo todo de golpe: es más lento por paso, pero elimina casi por completo el tiempo que se pierde depurando qué rompió un cambio grande que “parecía funcionar”. La diferencia no está en la capacidad del modelo, está en el proceso alrededor: contexto explícito, diagnóstico antes de escribir, pasos verificables uno a uno y una revisión humana real del resultado, no una aceptación automática.
Artículos relacionados
Cómo construir un agente de IA con Next.js y MCP
Una aplicación Next.js completa que habla con un servidor MCP propio: arquitectura, código real y las decisiones que cambian entre desarrollo local y producción.
MCP explicado a fondo: qué es, cómo funciona y cómo construir tu propio servidor
El protocolo que estandariza cómo los agentes de IA acceden a herramientas y datos externos. Arquitectura, transportes, seguridad y un servidor MCP construido paso a paso.
Cómo proteger un agente frente a prompt injection
El riesgo número uno para agentes con acceso a herramientas no es que el modelo se equivoque: es que algo que procesa le diga qué hacer sin que tú lo autorices.