Cómo automatizar refactorizaciones seguras con Claude Code
Workflow práctico para refactorizar con Claude Code: ejemplos antes/después, pruebas, git diff, revisión, riesgos y prompts listos.
Empieza definiendo el límite de seguridad
La forma más segura de automatizar refactoring con Claude Code no es pedir “limpia todo este proyecto”. Esa frase parece eficiente, pero suele producir un diff enorme, difícil de revisar y con cambios de comportamiento escondidos. Refactorizar significa mejorar la estructura interna sin cambiar lo que ven usuarios, APIs, pruebas y sistemas externos. Si ese límite no está claro, la automatización deja de ser refactoring y se convierte en desarrollo de funcionalidades.
Este artículo propone un workflow práctico: Claude Code investiga, prepara un plan pequeño, cambia una sola parte, ejecuta pruebas y explica el git diff. La documentación oficial de common workflows ayuda a entender esta forma de trabajar. Para permisos de comandos y configuración del proyecto, revisa también la documentación de settings.
Si todavía estás creando reglas de uso para el equipo, conviene leer las guías de permisos de Claude Code y gestión de contexto en Claude Code. Aquí bajamos eso a una rutina diaria: qué pedir primero, qué cambios son seguros para principiantes, cómo probarlos y cómo revisar el resultado.
Nota práctica de Masa: en pruebas pequeñas, Claude Code funcionó muy bien para renombrar variables, extraer funciones puras, aclarar tipos TypeScript y añadir tests de regresión. En cambio, cuando el prompt era amplio, como “moderniza este servicio”, el diff crecía demasiado. El método aburrido es el que mejor paga: alcance pequeño, tests visibles y revisión por diff.
Workflow seguro: investigar, planear, cambiar una cosa y verificar
Usa este orden hasta que el equipo confíe en el proceso.
| Paso | Qué hace Claude Code | Qué revisa la persona |
|---|---|---|
| 1. Investigar | Lee archivos, dependencias y cobertura de tests | Que el alcance no sea demasiado grande |
| 2. Planear | Propone un plan de tres pasos o menos | Que no haya cambios funcionales ocultos |
| 3. Editar | Cambia un solo tema | Que el diff sea revisable |
| 4. Verificar | Ejecuta test, typecheck y lint | Que los fallos estén explicados |
| 5. Revisar | Resume el git diff y los riesgos | Que el comportamiento antes/después sea igual |
Empieza con un prompt sin edición.
Inspecciona este repositorio y busca candidatos seguros para refactoring.
No edites archivos todavía.
Condiciones:
- No cambiar comportamiento externo
- Mantener cada diff en tres archivos o menos
- Priorizar zonas con tests existentes
- Devolver una tabla con candidato, motivo, comando de verificación y riesgo
La frase “No edites archivos todavía” es importante. Claude Code puede pasar de leer a modificar muy rápido si la petición suena ejecutable. Separar investigación e implementación reduce mucho los accidentes.
Antes de editar, crea una rama y mide el estado base.
git status --short
git checkout -b refactor/safe-extract-order-total
npm test
npm run typecheck
npm run lint
Adapta los comandos a tu package.json. Si los tests ya fallan antes del refactor, anótalo. Si no lo haces, después no sabrás si Claude Code rompió algo o si el fallo ya existía.
Use case 1: Renombrar y extraer una función pura pequeña
El primer ejercicio seguro es mejorar nombres y extraer una función pura. Una función pura devuelve el mismo resultado para la misma entrada y no toca base de datos, email, APIs ni estado global. Es ideal para Claude Code porque el éxito se puede probar con facilidad.
// before: src/domain/order.ts
export function calc(o: { items: { p: number; q: number }[]; d?: number }) {
let t = 0;
for (const i of o.items) {
t += i.p * i.q;
}
if (o.d) {
t = t - o.d;
}
return Math.max(t, 0);
}
El código es corto, pero p, q y d no comunican el dominio. Pide a Claude Code que fije primero el comportamiento con pruebas y luego mejore los nombres.
Refactoriza de forma segura la función calc en src/domain/order.ts.
Requisitos:
- Añadir tests unitarios que fijen el comportamiento actual antes de cambiar la implementación
- Mantener el nombre exportado calc en este diff
- Mejorar nombres de variables y tipos
- Conservar la regla de que el total nunca sea negativo
- Ejecutar npm test -- order después del cambio
Un buen resultado es pequeño.
// after: src/domain/order.ts
type OrderLine = {
price: number;
quantity: number;
};
type OrderInput = {
items: OrderLine[];
discount?: number;
};
export function calc(order: OrderInput): number {
const subtotal = order.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
return Math.max(subtotal - (order.discount ?? 0), 0);
}
Tests copiables:
// src/domain/order.test.ts
import { describe, expect, it } from "vitest";
import { calc } from "./order";
describe("calc", () => {
it("multiplies price and quantity", () => {
expect(calc({ items: [{ price: 1200, quantity: 2 }] })).toBe(2400);
});
it("applies discount without returning a negative total", () => {
expect(calc({ items: [{ price: 500, quantity: 1 }], discount: 800 })).toBe(0);
});
});
Revisa solo los archivos tocados.
git diff -- src/domain/order.ts src/domain/order.test.ts
npm test -- order
npm run typecheck
La pregunta de revisión no es “¿se ve elegante?”. Es “¿las mismas entradas siguen significando lo mismo para el negocio?”. Comprueba cálculo, nombre exportado y descripción de los tests.
Use case 2: Quitar any empezando por la frontera
Eliminar any mejora la seguridad, pero hacerlo en todo el proyecto de golpe es un error frecuente. Empieza por fronteras: respuestas de API, formularios, configuración, webhooks o filas importadas. Ahí es donde entran datos desconocidos.
// before: src/lib/user-api.ts
export async function fetchUser(id: string): Promise<any> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
export function getDisplayName(user: any): string {
return user.profile.displayName || user.name;
}
Pide un objetivo estrecho e incluye el caso de datos incompletos.
Reduce el uso de any en src/lib/user-api.ts.
Requisitos:
- Añadir un tipo para la respuesta de API
- Mantener igual la URL y el significado del retorno
- Hacer seguro getDisplayName cuando falta profile
- Añadir tests para el comportamiento actual del nombre visible
- Ejecutar npm test -- user-api y npm run typecheck
Un primer diff aceptable:
// after: src/lib/user-api.ts
export type UserResponse = {
id: string;
name: string;
profile?: {
displayName?: string;
};
};
export async function fetchUser(id: string): Promise<UserResponse> {
const response = await fetch(`/api/users/${id}`);
return response.json() as Promise<UserResponse>;
}
export function getDisplayName(user: UserResponse): string {
return user.profile?.displayName ?? user.name;
}
Este cast no valida datos en runtime. Si necesitas seguridad real de entrada, haz un segundo diff con un validador como zod o con el parser que ya use el proyecto. No mezcles “quitar any” y “añadir una librería de validación” en el primer cambio si el equipo no está listo para revisar ambas cosas.
// src/lib/user-api.test.ts
import { describe, expect, it } from "vitest";
import { getDisplayName, type UserResponse } from "./user-api";
describe("getDisplayName", () => {
it("uses profile displayName when present", () => {
const user: UserResponse = {
id: "u1",
name: "Masa",
profile: { displayName: "Masa I." },
};
expect(getDisplayName(user)).toBe("Masa I.");
});
it("falls back to name when profile is missing", () => {
expect(getDisplayName({ id: "u2", name: "Guest" })).toBe("Guest");
});
});
En revisión busca atajos peligrosos: as any, errores silenciados, cadenas vacías como fallback o cambios en campos opcionales. Un diff más tipado todavía puede romper comportamiento.
Use case 3: Dividir una función grande solo con tests alrededor
Las funciones grandes son tentadoras, pero también esconden comportamiento. Pedidos, facturación, permisos, notificaciones e importaciones mezclan validación, cálculo, persistencia y efectos secundarios. Pide a Claude Code extraer solo una pieza pura.
// before: src/services/order-service.ts
export async function createOrder(input: CreateOrderInput) {
if (input.items.length === 0) {
throw new Error("items required");
}
const subtotal = input.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const shippingFee = subtotal >= 10000 ? 0 : 800;
const total = subtotal + shippingFee;
const order = await db.order.create({
data: { userId: input.userId, subtotal, shippingFee, total },
});
await mailer.sendOrderCreated(order.id);
return order;
}
Deja claro qué queda fuera.
Haz más pequeña createOrder en src/services/order-service.ts.
En este diff:
- Extrae solo el cálculo de envío y total a una función pura
- Llámala calculateOrderTotals
- Añade tests unitarios para calculateOrderTotals
- Mantén igual el orden de escritura en DB y envío de email
No hagas en este diff:
- Cambiar esquema de base de datos
- Cambiar mensajes de error
- Cambiar forma de respuesta API
- Mover funciones no relacionadas
- Formatear todo el archivo
Después:
// after: src/services/order-service.ts
export function calculateOrderTotals(items: OrderItem[]) {
const subtotal = items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
const shippingFee = subtotal >= 10000 ? 0 : 800;
return {
subtotal,
shippingFee,
total: subtotal + shippingFee,
};
}
Comandos de revisión:
git diff --stat
git diff -- src/services/order-service.ts
git diff -- src/services/order-service.test.ts
npm test -- order-service
Si Claude Code formatea partes no relacionadas, reduce el alcance:
El diff es demasiado grande.
Revierte cambios solo de formato y deja únicamente la extracción de calculateOrderTotals y sus tests.
No cambies comportamiento externo, textos de error, escrituras en DB ni orden de emails.
Revisa con git diff, no con intuición
La explicación de Claude Code ayuda, pero el diff manda.
git diff --check
git diff --stat
git diff --name-only
git diff --word-diff -- src/domain/order.ts
| Área | Qué revisar |
|---|---|
| Comportamiento | Entradas, salidas, excepciones, HTTP status y orden de persistencia |
| Tamaño | Que el cambio quepa en una revisión humana |
| Tests | Que el comportamiento anterior quede protegido |
| Tipos | Que no aparezcan as any, casts inseguros ni errores ignorados |
| Efectos | Que API, email, cobros, borrados y permisos mantengan el orden |
| Resumen | Que la explicación coincida con el diff real |
Prompt de revisión:
Revisa este git diff.
Comprueba:
- Si el cambio supera el alcance de refactoring
- Qué comportamiento no está protegido por tests
- Si hay casts inseguros o errores silenciados
- Qué archivos debe mirar una persona con cuidado
Devuelve:
- Seguro
- Requiere confirmación humana
- Debe corregirse
con nombres de archivo y motivos.
Pitfall: fallos y riesgos habituales
El primer failure es un prompt demasiado amplio.
Haz más limpia esta capa de servicios.
Eso puede mezclar extracción, nombres, errores, movimiento de archivos y formato. Mejor:
Extrae solo el cálculo de envío de createOrder a una función pura.
No cambies orden de proceso, mensajes de error ni valores de retorno.
El segundo risk es aceptar un diff bonito sin tests. La legibilidad puede mejorar mientras cambian límites: descuentos, envío gratis, permisos, reintentos o valores nulos. El tercero es mezclar formateo con refactoring estructural. Si Prettier cambia cientos de líneas, el cambio real se esconde. El cuarto es abrir permisos de comandos demasiado pronto. Empieza por lectura, test, typecheck y lint; amplía permisos solo cuando el workflow sea estable.
Checklist reutilizable y CTA
## Refactoring checklist
- [ ] El cambio tiene un solo propósito
- [ ] Se ejecutaron tests base antes de editar
- [ ] El comportamiento before/after es equivalente
- [ ] Hay tests nuevos o existentes que protegen la conducta
- [ ] git diff --stat es revisable
- [ ] git diff --check pasa
- [ ] No se añadieron any, casts inseguros ni errores silenciados
- [ ] DB, email, cobros, borrados y permisos mantienen su orden
Prompt final:
Ejecuta un único diff de refactoring seguro.
Objetivo:
- src/services/order-service.ts
- src/services/order-service.test.ts
Criterios de éxito:
- No cambia comportamiento externo
- Se extrae calculateOrderTotals
- Pasan tests existentes y nuevos
- Reporta git diff --stat y comandos ejecutados
Prohibido:
- Cambios de esquema DB
- Cambios de respuesta API
- Cambios de mensajes de error
- Edición de archivos no relacionados
En mi verificación, lo que más mejoró la calidad fue pedir primero un plan sin edición y terminar siempre con resumen de git diff. Si quieres aplicar esto en equipo, combínalo con la checklist de revisión de Claude Code y las buenas prácticas de CLAUDE.md.
Para equipos que quieren reglas seguras de uso, la formación de Claude Code puede cubrir permisos, prompts, revisión y diseño del workflow. La automatización de refactoring da dinero cuando reduce riesgo de mantenimiento, no cuando crea diffs imposibles de revisar.
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.