17 sept 2025·8 min de lectura

Refactorizar una estructura de código desordenada sin afectar la producción

Refactoriza una estructura de código desordenada con un plan práctico para límites de carpetas, nombres, extracción de módulos y PRs pequeños que mantienen el comportamiento estable.

Refactorizar una estructura de código desordenada sin afectar la producción

Lo que realmente significa una estructura de código desordenada

Una estructura de código desordenada no es “muchos archivos”. Es cuando el diseño deja de coincidir con cómo funciona el producto. No puedes decir dónde pertenece un cambio, así que cada cambio se siente arriesgado.

En repos reales, suele aparecer como mezclas de responsabilidades: código de UI hablando directamente con la base de datos, comprobaciones de auth dispersas por páginas y carpetas de "utils" que silenciosamente contienen la mitad de la lógica de negocio. Otro olor común son las importaciones circulares (A importa B, B importa A). Pueden crear comportamientos extraños en tiempo de ejecución y empujar a la gente a soluciones parche que solo hacen que la app arranque.

El nombrado es el problema silencioso. Verás tres maneras de nombrar lo mismo (userService, users.service, user_manager), o carpetas que no significan nada (misc, temp, old2). La gente duplica código porque no encuentra el lugar correcto, y el desorden se compone.

Sientes los problemas de estructura en el día a día más que en los diagramas de arquitectura:

  • Cambios pequeños toman horas porque persigues dependencias por todo el repo.
  • Los bugs se repiten porque las correcciones llegan a un sitio mientras el comportamiento real vive en otro.
  • Los releases dan miedo porque una edición "pequeña" puede romper algo no relacionado.

Una limpieza vale la pena cuando la estructura es lo que más te frena o causa bugs evitables. Si el producto es estable, los cambios son raros y el equipo entiende el layout actual, una refactorización grande puede esperar. A menudo la mejor jugada es "tócalo y ordénalo": mejorar la estructura solo en el área que ya estás cambiando por una función o bug.

"No romperlo todo" significa que el comportamiento se mantiene igual mientras la estructura cambia. Los usuarios no deberían notarlo. El beneficio son cambios más seguros: diffs más pequeños, límites claros y menos sorpresas durante pruebas y despliegue.

Define objetivos y guardrails antes de tocar el código

Los refactors van más rápido cuando puedes responder una pregunta: ¿qué intentamos mejorar? Elige un objetivo principal, como cambios más rápidos, menos bugs o onboarding más sencillo. Si intentas arreglar todo a la vez, pasarás una semana moviendo archivos sin hacer el sistema más fácil de trabajar.

Luego, escribe qué debe permanecer estable. La estabilidad no es solo "que pasen las pruebas". Es el contrato real que tu app tiene con usuarios y otros sistemas.

Acordad guardrails antes del primer commit:

  • No negociables: APIs públicas, esquema de la base de datos, rutas y flujos clave de UI que no deben cambiar.
  • Alcance: qué está dentro y qué está fuera de límites (por ahora).
  • Límite de tiempo: un tope claro (por ejemplo, 2–3 días) antes de reevaluar.
  • Prueba: qué comprobaciones deben estar verdes (tests, lint, build, más una prueba rápida de humo).
  • Hecho significa: "estas carpetas están limpiadas y documentadas", no "todo el repo es perfecto".

Ejemplo: heredas una app generada por IA donde auth funciona "la mayoría de las veces", las rutas están duplicadas y los archivos viven en lugares aleatorios. Tus guardrails podrían ser: no cambiar el flujo de login, las cookies de sesión ni las tablas de la base de datos esta semana. El objetivo es solo poner auth detrás de un módulo y mover archivos relacionados a una carpeta con nombres consistentes. Eso te da una victoria que puedes desplegar sin crear un nuevo bug misterioso.

Si trabajas con otros, pon los guardrails por escrito y consigue un sí rápido. Evita debates a mitad del refactor y mantiene las revisiones enfocadas en resultados, no en opiniones.

Mapa rápido de la estructura actual (30 a 60 minutos)

Antes de mover carpetas, dedica una hora enfocada a hacer un mapa aproximado. Esto reduce el riesgo porque dejas de adivinar dónde empieza el comportamiento y dónde se extiende.

Empieza con puntos de entrada: dónde el sistema despierta y empieza a hacer cosas. Muchas apps tienen más de uno: un servidor web, un worker programado, un consumidor de colas, un script CLI y a veces un runner de migraciones.

Luego encuentra el dolor: los archivos y carpetas que la gente toca constantemente. El alto churn a menudo significa que esos archivos hacen demasiado o todos dependen de ellos.

Mantén un mapa simple abierto mientras trabajas:

  • 3 a 6 puntos de entrada (qué se ejecuta primero y cómo arranca)
  • 5 a 10 archivos o carpetas de alto churn (dónde siguen llegando cambios)
  • 3 a 5 puntos calientes de dependencia (importados por muchos otros módulos)
  • Una frase para cada carpeta top-level describiendo su propósito (aunque sea fea)

Un punto caliente de dependencia es donde los refactors mueren. Una pista rápida es un único archivo "utils" que contiene helpers de auth, formateo de fechas, código de base de datos y llamadas a APIs. Si está importado en todas partes, cada cambio es arriesgado.

Mantén el mapa honesto, no bonito. "src/helpers: cosas al azar que no supimos dónde poner" es útil. Después puedes convertir esa frase en un plan.

Define límites de carpetas que reduzcan el acoplamiento

Si quieres reorganizar sin romper producción, empieza dibujando fronteras claras. El objetivo es simple: un cambio en un área no debería obligarte a editar todo el repo.

Elige un modelo de carpetas que puedas explicar en una frase

Escoge el modelo más simple que coincida con cómo piensa tu equipo:

  • Por feature: todo para "billing" vive junto (UI, lógica, datos).
  • Por capa: ui/, domain/, data/ están separados y las features se ubican dentro.
  • Híbrido: carpetas de feature en la raíz, con ui/, domain/, data/ dentro de cada feature.

Cualquiera de estos puede funcionar. Lo que importa es que "¿Dónde va este archivo?" tenga una respuesta obvia.

Escribe reglas claras y en lenguaje cotidiano sobre qué pertenece dónde

Define límites con lenguaje de todos los días. Por ejemplo:

  • UI: componentes, pantallas, formularios y lógica de presentación.
  • Domain: reglas y decisiones de negocio (cálculo de precios, comprobaciones de elegibilidad).
  • Acceso a datos: clientes de API, consultas a la DB y persistencia.

Luego añade una corta lista de "no cruzar" para evitar acoplamientos accidentales durante el refactor:

  • La UI no importa directamente desde acceso a datos.
  • Domain no importa UI.
  • No llamadas directas a la BD fuera de acceso a datos.
  • No leer secretos de entorno fuera de un único módulo de config.
  • Ninguna carpeta de feature accede a las internas de otra feature.

Un escenario rápido: si una pantalla de checkout necesita un total de pedido, debe llamar a una función de domain (como calculateTotal). Esa función puede llamar a acceso a datos a través de una pequeña interfaz, no con SQL crudo o un cliente de API directo.

Finalmente, decide ownership. Nombra un revisor (o un grupo pequeño) para cada área para que las roturas de límites se detecten temprano.

Convenciones de nombres y reglas pequeñas que mantienen el orden

Estabiliza la autenticación primero
Detén las comprobaciones de autenticación dispersas y las sesiones inestables con un módulo de auth limpio y fiable.

Los refactors suelen fallar por una razón aburrida: la gente no sabe dónde poner el siguiente archivo, así que el desorden vuelve a crecer. Las convenciones de nombres suenan puntillosas, pero quitan decisiones diarias y evitan excepciones "solo esta vez".

Elige convenciones que tu equipo realmente siga. Si una regla necesita debate cada vez, es demasiado estricta o demasiado ingeniosa. El objetivo es consistencia, no perfección.

Algunos básicos para anotar:

  • Nombres de archivos y carpetas: elige kebab-case (user-profile.ts) o camelCase (userProfile.ts) y mantenlo.
  • Singular vs plural: por ejemplo, usa singular para carpetas de módulo (invoice/) y plural solo para colecciones (invoices/).
  • Exports: prefiere exports nombrados para código compartido; evita default exports a menos que haya una razón clara.
  • Archivos index: o prohíbelos o limítalos a reexportar la API pública, para que las importaciones sean predecibles.
  • Un concepto por archivo: si un archivo crece más allá de una pantalla o dos, sepáralo por responsabilidad.

Un pequeño ejemplo “dorado” ayuda más que un documento largo. Manténlo pequeño y cercano a lo que construyes más a menudo:

features/
  auth/
    api.ts
    routes.ts
    components/
      LoginForm.tsx
    index.ts

Cuando alguien agrega una nueva pantalla de auth, puede copiar el patrón sin adivinar.

Una regla ligera para código nuevo ayuda a que el refactor perdure aunque la limpieza no esté terminada:

  • Los archivos nuevos siguen las nuevas reglas de nombres y viven en las nuevas carpetas.
  • "Tócalo, ordénalo": si modificas un archivo, muévelo al lugar correcto o arregla el nombre.
  • No crear nuevos cajones de basura como misc.

Si esto se siente difícil de imponer, suele ser señal de que la estructura aún no coincide con cómo funciona la app.

Paso a paso: extraer un módulo de forma segura

Elige un objetivo pequeño y de bajo riesgo primero. Un buen inicio es una utilidad compartida que muchos archivos importan (formateo de fechas, feature flags, validación de inputs) o una feature que esté mayormente autocontenida. Si intentas sacar un área de dominio central el primer día, pasarás tiempo persiguiendo sorpresas.

Un refactor seguro cambia la forma del código sin cambiar lo que hace. El truco es crear un límite y mover el código detrás de él poco a poco.

Secuencia segura de extracción

Empieza escribiendo la promesa del nuevo módulo en una frase: qué hace y qué no hará. Luego:

  • Congela el comportamiento detrás de una interfaz. Una función o clase exportada basta. Mantén los internos feos por ahora; la fachada debe ser simple.
  • Mueve en commits pequeños. Actualiza importaciones a medida que avanzas. Si debes renombrar cosas, hazlo después del movimiento para que los diffs sean legibles.
  • Comprueba después de cada movimiento. Ejecuta la app y las pruebas. Si no tienes pruebas, haz una comprobación manual rápida del flujo que lo usa.
  • Borra el código viejo al final. Solo después de poder probar que nadie depende de él (buscar importaciones, revisar logs de ejecución, confirmar que no quedan copias duplicadas).
  • Añade tests focalizados en el límite. Un happy path, un caso límite y un caso de fallo suelen ser suficientes.

Ejemplo: notas tres archivos diferentes respondiendo "¿el usuario está logueado?" de formas ligeramente distintas. Crea un módulo authSession con getSession() y requireUser(). Primero haz que esas funciones llamen al código antiguo. Luego mueve la lógica dentro del módulo, actualiza un llamador a la vez y añade 2–3 tests que aseguren los resultados esperados.

La extracción también saca a la luz acoplamientos ocultos: globals, mezclas de responsabilidades, valores secretos en archivos al azar y helpers "temporales" que se volvieron permanentes.

Cómo desplegar el refactor usando PRs incrementales

Este trabajo es más seguro si lo tratas como una serie de entregas pequeñas, no un gran reescrito. Los PRs incrementales mantienen el radio de impacto pequeño. Cuando algo falla, lo detectas rápido y puedes revertir sin pánico.

Mantén los PRs pequeños y aburridos

Apunta a un tipo de cambio por PR: mover una carpeta, renombrar un grupo de archivos o extraer un módulo. Si sientes la tentación de "arreglar un par de cosas mientras estás aquí", apúntalas y hazlas después.

Haz primero cambios mecánicos: movimientos, renombres, formato, actualización de rutas de importación. Son fáciles de revisar porque el comportamiento no cambia.

Cuando la estructura esté estable, haz cambios de comportamiento en PRs separados (correcciones de lógica, cambios de forma de datos, manejo de errores). Mezclar estructura y comportamiento es lo que convierte refactors en bugs misteriosos.

Escribe descripciones de PR que ayuden a verificar

Facilita la revisión incluyendo:

  • Qué cambió (una frase)
  • El riesgo (qué podría romper y dónde)
  • Cómo verificar (una prueba manual corta más comprobaciones automáticas clave)
  • Plan de rollback (usualmente "revertir este PR")

Ejemplo: "Moví el código de auth a modules/auth y actualicé imports. Riesgo: wiring de ruta de login. Verificar: registrarse, iniciar sesión, cerrar sesión, refrescar sesión.".

Si más de una persona trabaja, acordad un orden que evite conflictos. Mergea PRs de límites primero y luego extracciones que dependan del nuevo layout. Asigna ownership para que dos personas no editen los mismos archivos de alto churn al mismo tiempo.

Mantén el comportamiento estable mientras la estructura cambia

Cierra las brechas de seguridad
Arreglamos secretos expuestos, consultas inseguras y riesgos comunes de inyección en código escrito por IA.

El objetivo es aburrido: la app debe comportarse igual para los usuarios. La mayoría de refactors fallan porque pequeños cambios de comportamiento se deslizan sin notarse.

Empieza con cheques de humo que coincidan con el uso real del producto. Mantén la lista corta para que realmente la ejecutes cada vez:

  • Registro, login, logout (y restablecimiento de contraseña)
  • Crear el objeto principal de tu app (orden, post, proyecto, ticket)
  • Actualizar y borrar ese objeto
  • Una acción de dinero (checkout, factura, cambio de suscripción), si aplica
  • Una acción de mensaje (email, notificación, webhook), si aplica

Las pruebas son tripwires, no un proyecto de perfección. Si la cobertura es débil, escribe 2–5 tests de alto valor alrededor de los flujos anteriores y para de allí. Un par de tests end-to-end o de integración suelen atrapar más errores de refactor que docenas de unit tests pequeños.

Atento a fallos silenciosos. Auth puede romper sin errores obvios (cookies, almacenamiento de sesión, rutas de redirect). Pagos y emails pueden “fallar con éxito” si callbacks no están conectados o jobs en background no corren. Las colas pueden perder tareas si cambian nombres de jobs o rutas de importación durante una extracción.

Haz que las fallas sean visibles rápido. Confirma que puedes ver logs y errores en un solo lugar, y que los errores incluyen suficiente contexto (user ID, request ID, job name). Durante el refactor, añade unas líneas de log específicas alrededor de límites críticos (auth, facturación, emails salientes) y elimínalas una vez que la situación se estabilice.

Trampas comunes que hacen que los refactors salgan mal

La mayoría de refactors fallan cuando pequeños cambios se acumulan hasta que nadie está seguro de qué es seguro. Vigila estas trampas desde temprano.

La trampa de “aún compila”

Mover archivos puede parecer inofensivo, especialmente cuando las pruebas están verdes. Pero si mueves cosas sin decidir cuál es la interfaz pública, terminas rompiendo imports por toda la app o forzando a todos a "actualizar la ruta" en docenas de lugares.

Un patrón más seguro es mantener puntos de entrada estables (por ejemplo, un archivo index por módulo que defina la API pública) y cambiar rutas internas detrás de esa interfaz.

También evita crear un nuevo cajón de basura como arreglo rápido. Un misc o utils nuevo se convierte en el mismo problema en una semana. Si algo no tiene hogar claro, considéralo una señal de que tus límites no están claros.

Puertas traseras ocultas entre módulos

La extracción no termina cuando los archivos se mueven. Termina cuando el módulo puede sostenerse por sí mismo.

Las puertas traseras habituales son cross-imports (el módulo A importa silenciosamente el módulo B) y globals compartidos (objetos de config, singletons, caches mutables) que permiten a código atravesar límites.

Trampas que suelen arruinar la calidad de la revisión:

  • Renombrar muchos archivos y símbolos al mismo tiempo que mover carpetas
  • Mezclar trabajo de refactor con nuevas features
  • Dejar la API vieja y la nueva temporalmente y nunca eliminar la antigua
  • Hacer un PR gigante que los revisores solo pueden ojear
  • Cambiar comportamiento accidentalmente (por ejemplo, mover auth y alterar el orden del middleware)

Ejemplo: un equipo extrae un módulo auth, pero algunas pantallas aún importan un global currentUser desde la carpeta antigua. Todo parece bien hasta producción, donde un cold start carga módulos en distinto orden.

Checklist rápido para cada PR de refactor

Refactoriza con guardrails claros
Sal con guardrails, alcance y un plan incremental de PRs que tu equipo pueda seguir.

Los PRs pequeños son cómo reorganizas estructura sin convertir producción en un juego de adivinanzas.

Antes de abrir el PR

Si no puedes explicar el cambio en dos frases, probablemente el PR es muy grande.

  • Limita el alcance a una corrección de límites, un conjunto de renombres o un movimiento de módulo.
  • Lista los puntos de entrada que podrías afectar (rutas, comandos CLI, jobs background, imports desde otros paquetes).
  • Nota un plan de rollback (usualmente "revertir este PR"; a veces una exportación de compatibilidad por una release).
  • Ejecuta lint y tests localmente y escribe 2–3 smoke checks.
  • Confirma que no hay cambio de comportamiento a menos que el título del PR lo diga.

Ejemplo concreto: si mueves helpers de auth a un nuevo módulo, destaca la ruta de login, el refresh de tokens y los imports de middleware como puntos de entrada. Smoke checks: registrarse, iniciar sesión, cerrar sesión, refrescar token y cargar una página protegida.

Después de abrir y mergear el PR

Haz una pasada de sanity antes del siguiente trozo:

  • Comprueba que no introdujiste importaciones circulares (un build más una comprobación de dependencias suele revelarlo).
  • Elimina carpetas temporales creadas durante el movimiento o renómbralas al hogar final.
  • Asegúrate de que el equipo acuerda dónde van los archivos nuevos (un comentario corto en el PR suele bastar).
  • Actualiza notas simples: un README corto en la carpeta o un comentario breve explicando el propósito.
  • Confirma que el PR no cambió silenciosamente APIs públicas (exports, rutas de archivos que otros importan).

Próximos pasos: mantener el impulso (y saber cuándo pedir ayuda)

Elige una feature pequeña y real y úsala como ruta de prueba. Si trabajas a partir de un prototipo generado por IA, un slice común es: extraer comprobaciones de auth fuera de componentes UI, reunir acceso a datos en un módulo y reducir la UI a solo render.

Refactoriza una vertical primero y aplaza el resto. Por ejemplo: toma "Sign in -> cargar cuenta -> mostrar dashboard" y hazlo aburrido y predecible. Mueve comprobaciones de auth a un lugar, junta acceso a datos en un módulo y mantén la UI enfocada en presentación. Guarda debates mayores (renombres de carpetas completos, cambios de framework, modelado del dominio perfecto) para luego, cuando tengas impulso y una red de seguridad.

Cuando expliques el plan a stakeholders no técnicos, empieza por lo que se mantiene estable: pantallas, lógica de precios, datos de clientes. Luego explica lo que cambia internamente: menos sorpresas en producción, propiedad más clara y manejo de secretos más seguro.

Suele ser más rápido pedir ayuda cuando el código está muy enredado (todo importa todo), ves banderas rojas de seguridad (keys expuestos, queries inseguras) o la autenticación ya es poco fiable.

Si tu código vino de herramientas como Lovable, Bolt, v0, Cursor o Replit y pasas más tiempo desenredando que construyendo, FixMyMess (fixmymess.ai) puede hacer una auditoría gratuita de código para identificar las áreas más riesgosas y el orden más seguro para arreglarlas antes de que empieces a mover todo.

Preguntas Frecuentes

¿Cómo sé si la estructura de mi código está realmente “desordenada” o simplemente es grande?

Una estructura desordenada se nota cuando no puedes decir dónde pertenece un cambio, así que cada edición parece arriesgada. Señales comunes: preocupaciones mezcladas (la UI toca la base de datos), lógica de negocio escondida en “utils”, nombres inconsistentes para el mismo concepto e importaciones circulares que generan comportamientos extraños en tiempo de ejecución.

¿Cuándo vale la pena una refactorización estructural y cuándo debería esperar?

Házlo cuando la estructura esté ralentizando claramente la entrega o causando errores repetidos, no solo porque se vea feo. Si el producto es estable, los cambios son raros y el equipo puede trabajar con seguridad, una limpieza grande puede esperar mientras aplicas la estrategia “tócalo y ordénalo” en las áreas que ya modificas.

¿Qué guardrails debo establecer antes de empezar a mover archivos?

Elige un objetivo principal, por ejemplo “los cambios en auth deben ser predecibles”, y escribe lo que no se puede tocar: rutas, esquema de la base de datos y flujos críticos. Añade un límite de tiempo y una definición clara de “hecho” para que entregues mejoras en lugar de mover archivos sin fin.

¿Cuál es la forma más rápida de entender un repo antes de refactorizarlo?

Dedica 30–60 minutos a mapear puntos de entrada y puntos calientes antes de tocar la estructura. Encuentra dónde arranca la app, qué archivos cambian con más frecuencia y qué módulos importa todo el mundo, y escribe una frase honesta sobre el propósito de cada carpeta de primer nivel.

¿Cómo elijo límites de carpetas que no terminen en discusiones?

Empieza con una regla de límites sencilla que puedas aplicar (por ejemplo, “la UI no llama a la base de datos” y “los secretos solo vienen de un módulo de config”). Luego elige un modelo de carpetas (por feature, por capa o híbrido) basado en cómo habla el equipo, no en lo que esté de moda.

¿Qué convenciones de nombres previenen realmente que el desorden vuelva?

Hazlo aburrido y consistente para que la gente no tenga que pensar cada vez que crea un archivo. Elige un estilo de nombres, decide cómo usar los archivos index y mantén un pequeño ejemplo “dorado” que muestre el patrón que quieres repetir.

¿Cuál es una forma segura de extraer un módulo sin romper producción?

Extrae primero un módulo pequeño y colócale una interfaz limpia. Mueve los llamadores uno por uno y verifica después de cada cambio. Mantén el comportamiento igual, añade 2–3 tests focalizados en el límite y borra el código viejo solo cuando pruebes que nadie depende de él.

¿Qué tamaño deben tener los PRs de refactor y qué debo evitar mezclar?

Limita cada PR a un tipo de cambio y mantén refactors separados de cambios de comportamiento. Si un PR mezcla movimientos de archivos con ediciones lógicas, los revisores no podrán detectar el riesgo y es más probable que aparezca un “bug misterioso”.

Si no tengo buenas pruebas, ¿cómo mantengo el comportamiento estable?

Empieza con una breve prueba de humo que reproduzca flujos reales de usuario y ejecútala cada vez que muevas estructura. Si las pruebas son débiles, añade unas pocas comprobaciones de alto valor en los límites de los módulos (auth, facturación, correo) para que los errores de refactor se detecten rápido.

¿Cuándo debería pedir ayuda en lugar de intentar refactorizar un código generado por IA yo solo?

Pide ayuda cuando todo se importe entre sí, la autenticación sea inestable, los secretos estén expuestos o veas patrones de consultas inseguros. FixMyMess (fixmymess.ai) puede realizar una auditoría gratuita en código generado por IA y señalar las áreas más riesgosas y el orden más seguro para arreglarlas, a menudo dejándote una estructura lista para producción en 48–72 horas.