Saltar al contenido
Lección 5 de 12

Prompting para Refactorizacion

9 min read

El Desafio de la Refactorizacion

La refactorizacion es una de las tareas de mayor riesgo que puedes darle a una herramienta de IA. La generacion de codigo parte de nada -- si la salida esta mal, simplemente la borras. La refactorizacion modifica codigo existente que funciona. Si la salida esta mal, podrías no notarlo hasta que algo falle en produccion. Esto hace que la precision de tus prompts de refactorizacion sea critica.

El principio central de un buen prompt de refactorizacion es: estado actual + estado objetivo + restricciones. La IA necesita saber exactamente que existe, exactamente en que quieres que se convierta, y exactamente que NO deberia cambiar en el proceso.

La Estructura del Prompt de Refactorizacion

Cada prompt de refactorizacion debe responder tres preguntas:

  1. Cual es el estado actual? -- Que codigo existe y como funciona ahora?
  2. Cual es el estado objetivo? -- Como deberia verse despues de la refactorizacion?
  3. Cuales son las restricciones? -- Que debe permanecer sin cambios?
Estado actual: La clase UserService en src/services/UserService.ts tiene un
metodo createUser() de 200 lineas que maneja validacion, hash de password,
creacion en base de datos, envio de email y registro de auditoria todo en
una sola funcion.

Estado objetivo: Dividir createUser() en metodos mas pequenos de
responsabilidad unica: validateUserInput(), hashPassword(), persistUser(),
sendWelcomeEmail() y logUserCreation(). El metodo createUser() debe
orquestar estos metodos mas pequenos.

Restricciones:
- La API publica no debe cambiar (createUser sigue recibiendo los mismos
  parametros y devolviendo el mismo tipo)
- Todos los tests existentes en UserService.test.ts deben seguir pasando
- No cambiar el esquema de base de datos ni las consultas Prisma
- Mantener todos los metodos en la misma clase por ahora

Extraccion de Funciones

Extraer funciones grandes en funciones mas pequenas es una de las tareas de refactorizacion mas comunes. La clave es especificar los limites de extraccion.

Malo:

Divide esta funcion grande en funciones mas pequenas

Esto no le da a la IA orientacion sobre donde hacer los cortes. Podria dividir en limites arbitrarios que no corresponden a unidades logicas.

Bueno:

Extrae la siguiente logica de processOrder() en src/services/OrderService.ts
en metodos privados separados:

1. Lineas 15-30: validacion de inventario -> validateInventory(items: OrderItem[]): Promise<void>
2. Lineas 32-55: calculo de precio incluyendo descuentos -> calculateTotal(items: OrderItem[], coupon?: Coupon): number
3. Lineas 57-78: procesamiento de pago -> processPayment(total: number, paymentMethod: PaymentMethod): Promise<string>
4. Lineas 80-95: creacion del registro de orden -> createOrderRecord(customerId: string, items: OrderItem[], transactionId: string): Promise<Order>

processOrder() debe llamar estos metodos en secuencia. Si algun paso falla,
el error debe propagarse sin envolver adicional.

Al especificar rangos exactos de lineas, firmas de funciones y nombres, eliminas la ambiguedad. La IA producira exactamente la refactorizacion que quieres.

Conversion de Patrones

La conversion de patrones es otra tarea comun de refactorizacion: convertir componentes de clase a funcionales, callbacks a promises, CommonJS a ES modules, o SQL directo a consultas ORM.

De Clase a Componente Funcional

Convierte el componente de clase UserProfile en src/components/UserProfile.tsx
a un componente funcional con hooks.

Conversiones:
- this.state -> hooks useState (hook separado para cada campo de estado)
- componentDidMount -> useEffect con array de dependencias vacio
- componentDidUpdate para userData -> useEffect con dependencia [userId]
- this.handleSubmit -> const handleSubmit con useCallback
- Variable de instancia this.abortController -> useRef

Mantener todo el comportamiento existente:
- Carga de datos al montar y cuando userId cambia
- Limpieza del abort controller al desmontar
- Envio de formulario con actualizacion optimista de UI
- Integracion con error boundary (mantener el wrapper ErrorBoundary)

NO cambiar la interfaz de props ni la estructura del JSX renderizado.

De Callbacks a Async/Await

Refactoriza el pipeline de subida de archivos en src/utils/fileUpload.ts de
callbacks anidados a async/await.

Patron actual:
validateFile(file, (err, validFile) => {
  compressImage(validFile, (err, compressed) => {
    uploadToS3(compressed, (err, url) => {
      updateDatabase(url, (err, record) => { ... })
    })
  })
})

Patron objetivo:
async function uploadFile(file: File): Promise<UploadRecord> {
  const validFile = await validateFile(file);
  const compressed = await compressImage(validFile);
  const url = await uploadToS3(compressed);
  const record = await updateDatabase(url);
  return record;
}

Cada funcion basada en callback debe envolverse en una version promisificada.
Mantener las funciones originales con callback intactas y crear nuevos
wrappers async. Agregar manejo de errores apropiado con try/catch que preserve
los tipos de error de cada paso.

Especificando Lo Que NO Debe Cambiar

Las restricciones son la parte mas importante de los prompts de refactorizacion. Sin ellas, la IA podria "mejorar" cosas que no pediste que tocara, introduciendo bugs sutiles.

Refactoriza la capa de obtencion de datos en src/api/client.ts para usar
axios en lugar de la API nativa fetch.

Cambiar:
- Todas las llamadas fetch() -> equivalentes con axios
- Parseo de respuesta (no mas llamadas .json())
- Manejo de errores para usar tipos de error de axios

NO cambiar:
- Las firmas de funciones exportadas (getUser, createUser, etc.)
- La logica de reintentos en withRetry()
- La inyeccion de headers de autenticacion en getAuthHeaders()
- El interceptor de logging de request/response
- La configuracion de base URL desde variables de entorno
- Ningun archivo de test

La lista de "NO cambiar" es tan importante como la lista de "Cambiar". Protege las partes del codigo que funcionan correctamente y no deben ser modificadas.

La Tecnica de Refactorizar en Pasos

Para tareas de refactorizacion grandes, dividelas en pasos secuenciales y revisables. Esto es critico porque cada paso puede ser verificado antes de proceder al siguiente.

Necesito migrar nuestra gestion de estado de Redux a Zustand. Hagamoslo en
pasos. Para el Paso 1, enfocate solo en el store de usuario.

Paso 1: Convierte src/store/userSlice.ts de un slice de Redux a un store
de Zustand.

El slice actual de Redux exporta: selectUser, selectIsAuthenticated, setUser,
clearUser, updateProfile.

Crea src/store/useUserStore.ts como un store de Zustand con la misma forma
de estado y acciones. Exporta un hook useUserStore que proporcione los mismos
selectores y acciones.

No modifiques ningun componente aun. No toques otros slices de Redux. Solo
crea el nuevo archivo de store Zustand que refleje la funcionalidad existente
de Redux.

Actualizare los componentes en el Paso 2 despues de revisar este store.

Despues de revisar y aprobar el Paso 1:

Paso 2: Actualiza los componentes que usan el slice Redux de usuario para
que usen el nuevo store de Zustand.

Archivos a actualizar:
- src/components/Header.tsx (usa selectUser y clearUser)
- src/components/ProfileForm.tsx (usa selectUser y updateProfile)
- src/pages/Login.tsx (usa setUser)
- src/pages/Settings.tsx (usa selectUser, updateProfile)

Para cada archivo:
- Reemplazar useSelector(selectUser) con useUserStore(state => state.user)
- Reemplazar useDispatch() + dispatch(setUser(data)) con useUserStore.getState().setUser(data)
- Eliminar imports de Redux

No eliminar el slice viejo de Redux aun (otros slices pueden depender del store).

Este enfoque paso a paso te permite verificar cada cambio antes de construir sobre el. Si el Paso 1 tiene un problema, lo corriges antes de que el Paso 2 construya sobre una base rota.

Requisitos de Testing en Prompts de Refactorizacion

Siempre especifica expectativas de testing en los prompts de refactorizacion. El objetivo completo de la refactorizacion es cambiar la estructura interna sin cambiar el comportamiento externo -- y los tests verifican eso.

Refactoriza el OrderService para usar el patron Repository. Extrae todas las
consultas Prisma en una nueva clase OrderRepository en
src/repositories/OrderRepository.ts.

Requisitos de testing:
- Todos los tests existentes en src/services/OrderService.test.ts deben
  pasar sin modificacion
- Crear src/repositories/OrderRepository.test.ts con tests unitarios para
  cada metodo del repository
- El OrderService debe recibir el repository via inyeccion en el constructor
  para que pueda ser mockeado en tests

Prompts de Migracion

Las migraciones son tareas de refactorizacion a gran escala que cambian librerias, frameworks o patrones fundamentales. Requieren prompts especialmente cuidadosos.

Migracion de Libreria

Migra el manejo de fechas en src/utils/dates.ts de moment.js a date-fns.

El archivo exporta 8 funciones. Migralas una por una:
1. formatDate(date, format) - usar format() de date-fns
2. parseDate(dateString) - usar parseISO() de date-fns
3. addDays(date, days) - usar addDays() de date-fns
4. diffInDays(date1, date2) - usar differenceInCalendarDays() de date-fns
5. isAfter(date1, date2) - usar isAfter() de date-fns
6. startOfDay(date) - usar startOfDay() de date-fns
7. endOfDay(date) - usar endOfDay() de date-fns
8. formatRelative(date) - usar formatDistanceToNow() de date-fns

Importante: Los strings de formato de moment difieren de date-fns. Convertir
todos los strings de formato: 'YYYY' -> 'yyyy', 'DD' -> 'dd', 'MM' se
mantiene 'MM'.

Despues de la migracion, moment no debe ser importado en ninguna parte de
este archivo. Ejecutar los tests existentes para verificar que todas las
operaciones de fecha producen los mismos resultados.

Actualizacion de Framework

Actualiza nuestra pagina Next.js 13 src/app/products/page.tsx a patrones
de Next.js 14.

Cambios necesarios:
- Reemplazar el export getServerSideProps con un server component que obtenga
  datos directamente
- Mover la interactividad del lado del cliente (filtro de busqueda, botones
  de ordenamiento) a un componente 'use client' separado
- Reemplazar los imports de next/router con next/navigation
- Actualizar el export de metadata para usar la nueva funcion generateMetadata

Restricciones:
- Mantener la misma estructura de URL (/products con parametros ?search y ?sort)
- Mantener el mismo layout visual y clases Tailwind
- La funcionalidad de busqueda y ordenamiento debe funcionar identicamente

Revisando la Salida de Refactorizacion de la IA

Despues de que la IA produzca codigo refactorizado, verifica tres cosas:

  1. Preservacion de comportamiento: El codigo sigue haciendo lo mismo? Ejecuta tus tests.
  2. Adherencia al alcance: La IA solo cambio lo que pediste que cambiara? Revisa el diff cuidadosamente.
  3. Consistencia de patrones: El codigo refactorizado coincide con los patrones usados en el resto del codebase?

Si alguna de estas verificaciones falla, da feedback especifico en tu siguiente prompt en lugar de pedir que rehaga todo. "El calculateTotal() refactorizado no considera la bandera tax-exempt que se manejaba en la linea 45 de la funcion original" es mucho mas util que "esto esta mal, intenta de nuevo."