Módulo 4: Refactoring Multi-File Coordinado

Move Module y Actualización de Imports

Move Module y Actualización de Imports

Descripción de la cápsula

Rename y extract cambian nombres y lógica, pero la estructura de directorios queda igual. Move module es el refactoring que reorganiza esa estructura: mover un archivo de un directorio a otro, dividir un módulo en varios archivos, o consolidar archivos dispersos en un solo módulo. Y cada movimiento requiere actualizar todos los imports que apuntan al viejo location.

En proyectos reales, la estructura de directorios se degrada con el tiempo. Un directorio utils/ crece hasta tener 30 archivos. Un services/ mezcla lógica de negocio con infraestructura. Un archivo que empezó en src/ debería estar en src/api/. Move module corrige esto — pero manualmente es uno de los refactoring más propensos a errores porque un solo import olvidado rompe todo en runtime.

Claude Code maneja esto porque puede leer todos los imports del proyecto, entender la estructura de dependencias, y actualizar cada reference coherentemente. En esta cápsula vas a reorganizar estructura de directorios con confianza.


Por Qué Mover Módulos es Difícil

El problema de los imports

# Estructura actual (desorganizada):
# src/
#   utils/
#     email_helper.py          # ← debería estar en services/
#     payment_calculator.py    # ← debería estar en services/
#     string_utils.py          # ← está bien aquí
#     auth_middleware.py       # ← debería estar en middleware/
#   services/
#     order_service.py
#     user_service.py

# payment_calculator.py es importado por:
# src/services/order_service.py:   from utils.payment_calculator import calculate_total
# src/api/routes/checkout.py:       from utils.payment_calculator import calculate_total
# src/api/routes/invoices.py:       from utils.payment_calculator import PaymentCalculator
# src/tasks/billing.py:             from utils.payment_calculator import calculate_total
# tests/test_payment.py:            from utils.payment_calculator import calculate_total
# tests/test_orders.py:             from utils.payment_calculator import PaymentCalculator

# Mover payment_calculator.py a services/ requiere actualizar
# 6 imports en 6 archivos diferentes

Un import olvidado no genera error de syntax — genera ModuleNotFoundError en runtime. Si ese import está en un path de código poco frecuente, el bug puede llegar a producción.


Move Simple: Un Archivo

El ciclo completo con Claude Code

Paso 1: Tests

> "Ejecuta todos los tests y confirma que pasan antes
   de mover payment_calculator.py"

Paso 2: Identificar impacto

> "Lista todos los archivos que importan payment_calculator.
   Para cada uno, muestra la línea de import exacta."

# Output esperado:
# 1. src/services/order_service.py:3 → from utils.payment_calculator import calculate_total
# 2. src/api/routes/checkout.py:5 → from utils.payment_calculator import calculate_total
# 3. src/api/routes/invoices.py:4 → from utils.payment_calculator import PaymentCalculator
# 4. src/tasks/billing.py:2 → from utils.payment_calculator import calculate_total
# 5. tests/test_payment.py:1 → from utils.payment_calculator import calculate_total
# 6. tests/test_orders.py:3 → from utils.payment_calculator import PaymentCalculator

Paso 3: Mover y actualizar

> "Mueve src/utils/payment_calculator.py a
   src/services/payment_calculator.py.
   Actualiza todos los imports en los 6 archivos
   que lo importan. Los imports deben cambiar de
   'from utils.payment_calculator' a
   'from services.payment_calculator'."

# Claude Code:
# 1. Mueve el archivo
# 2. Actualiza los 6 imports
# 3. Verifica que no hay más references al viejo path

Paso 4: Verificar

> "Ejecuta los tests y también haz un grep de
   'from utils.payment_calculator' para confirmar
   que no quedan references al viejo path"

# Output esperado:
# ✅ Tests: 47 passed
# ✅ grep: 0 results (no quedan references viejas)

Move Complejo: Reorganizar Directorio

Escenario: utils/ tiene 30 archivos

# Antes (desorganizado):
# src/utils/
#   string_utils.py
#   date_utils.py
#   email_helper.py          # → services/email/
#   sms_helper.py            # → services/notifications/
#   payment_calculator.py    # → services/billing/
#   invoice_generator.py     # → services/billing/
#   auth_middleware.py       # → middleware/
#   cors_middleware.py       # → middleware/
#   rate_limiter.py          # → middleware/
#   logger.py                # → infrastructure/
#   cache.py                 # → infrastructure/
#   db_connection.py         # → infrastructure/
#   ... (18 archivos más)

# Después (organizado):
# src/utils/          → Solo utilities puras (string, date, etc.)
# src/services/       → Lógica de negocio
# src/middleware/      → Middleware HTTP
# src/infrastructure/ → Logging, cache, DB

Plan de reorganización con Claude Code

# Paso 1: Analizar y planificar
> "Analiza todos los archivos en src/utils/ y clasifícalos
   en categorías: utils puras, services, middleware, e
   infrastructure. Para cada archivo, sugiere el directorio
   destino y lista cuántos archivos importan cada uno."

# Output esperado:
# Utils puras (quedan): string_utils.py, date_utils.py (4 importers)
# Services (mover a src/services/): email_helper.py (8), payment_calculator.py (6), ...
# Middleware (mover a src/middleware/): auth_middleware.py (3), cors.py (2), ...
# Infrastructure (mover a src/infrastructure/): logger.py (12), cache.py (7), ...

# Paso 2: Mover en orden de menor a mayor impacto
> "Empieza por los archivos con menos importers.
   Mueve cors_middleware.py a src/middleware/cors.py.
   Actualiza los 2 imports. Ejecuta tests."

# Paso 3: Repetir para cada archivo
> "Mueve auth_middleware.py a src/middleware/auth.py.
   Actualiza los 3 imports. Ejecuta tests."

# Paso 4: Los de mayor impacto al final
> "Mueve logger.py a src/infrastructure/logger.py.
   Actualiza los 12 imports. Ejecuta tests."

Regla: mueve un archivo a la vez, ejecuta tests después de cada movimiento. Nunca muevas 5 archivos de golpe.


Move con Re-export (Backwards Compatibility)

Cuándo usarlo

Si el módulo es público (otros proyectos lo importan) o si quieres hacer la migración gradual, puedes dejar un re-export en el viejo location:

# src/utils/payment_calculator.py (archivo viejo, ahora re-exporta)
"""
DEPRECATED: Movido a src/services/payment_calculator.py
Este archivo existe solo por backwards compatibility.
Eliminar cuando todos los consumidores hayan actualizado sus imports.
"""
from services.payment_calculator import *  # re-export todo

import warnings
warnings.warn(
    "Importar de utils.payment_calculator está deprecated. "
    "Usa services.payment_calculator en su lugar.",
    DeprecationWarning,
    stacklevel=2
)
# Prompt a Claude Code:
> "Mueve payment_calculator.py a src/services/.
   En el viejo location, deja un archivo que re-exporta
   todo desde el nuevo location con un DeprecationWarning.
   Actualiza los imports internos del proyecto al nuevo path.
   Los imports externos (si existen) seguirán funcionando
   con el re-export."

Move con Split: Un Archivo → Múltiples

Cuándo splitear

Cuando un archivo tiene 500+ líneas y contiene múltiples clases/funciones con responsabilidades diferentes:

# src/services/user_service.py (600 líneas, 3 responsabilidades)
class UserAuthService:          # Autenticación
    def login(self): ...
    def logout(self): ...
    def reset_password(self): ...

class UserProfileService:       # Perfil
    def get_profile(self): ...
    def update_profile(self): ...
    def upload_avatar(self): ...

class UserNotificationService:  # Notificaciones
    def send_welcome(self): ...
    def send_password_reset(self): ...

Ejecutar el split con Claude Code

> "Divide src/services/user_service.py en 3 archivos:
   1. src/services/user_auth_service.py — UserAuthService
   2. src/services/user_profile_service.py — UserProfileService
   3. src/services/user_notification_service.py — UserNotificationService
   
   Para cada clase:
   - Mueve la clase y sus imports al nuevo archivo
   - Actualiza todos los imports en el proyecto
   
   Si algún archivo importaba múltiples clases de user_service.py,
   actualiza a imports separados de cada nuevo archivo.
   
   Ejecuta los tests después de cada movimiento."

Move con Merge: Múltiples Archivos → Uno

Cuándo consolidar

Lo opuesto del split: archivos de 20-30 líneas que deberían ser un solo módulo:

# Antes (fragmentado):
# src/validators/
#   email_validator.py    (25 líneas, 1 función)
#   phone_validator.py    (30 líneas, 1 función)
#   age_validator.py      (20 líneas, 1 función)
#   name_validator.py     (25 líneas, 1 función)

# Después (consolidado):
# src/validators/
#   validators.py         (100 líneas, 4 funciones)
> "Consolida los 4 archivos de validators en
   src/validators/validators.py. Mueve todas las funciones
   al archivo consolidado. Actualiza todos los imports.
   Elimina los archivos viejos. Ejecuta tests."

Comparación: Move Manual vs Claude Code

CriterioManualClaude Code
Encontrar todos los importsgrep + revisión manualAutomático y completo
Actualizar importsFind-and-replace (propenso a errores)Semántico y contextual
Imports relativosFácil olvidar .. pathsCalcula automáticamente
Re-exportsEscribir manualmenteGenera con deprecation warning
VerificaciónEjecutar tests manualmenteEjecuta y reporta
Split/MergeTedioso, muchos pasosCoordinado en un prompt

Conexión con Proyecto

En el Proyecto del Módulo (cápsula 06), si el codebase tiene problemas de organización (archivos en directorios incorrectos, utils/ inflado, naming inconsistente), move module es una de las técnicas que vas a usar. Combina con rename (cápsula 02) y extract (cápsula 02) para una reorganización completa.


Troubleshooting

Problema 1: ModuleNotFoundError después del move

Causa: Quedó un import apuntando al viejo path.

Solución:

> "Busca cualquier import que todavía apunte a
   'utils.payment_calculator' (el viejo path).
   Actualiza al nuevo path 'services.payment_calculator'."

Problema 2: Import circular después de mover

Causa: El nuevo directorio crea una dependencia circular que no existía antes.

Solución: Analiza antes de mover:

> "Si muevo payment_calculator.py a services/,
   ¿se creará alguna dependencia circular? Analiza
   los imports de payment_calculator y los imports
   de los archivos en services/."

Problema 3: __init__.py no actualizado

Causa: El directorio destino tiene un __init__.py que no exporta el nuevo módulo.

Solución:

> "Después de mover el archivo, actualiza también
   el __init__.py del directorio destino para exportar
   las clases/funciones del nuevo módulo."

Problema 4: Paths relativos se rompen

Causa: El archivo movido usaba imports relativos que ya no son válidos.

Solución:

> "Actualiza los imports DENTRO del archivo movido.
   Los imports relativos (from . import X) pueden
   haber cambiado al cambiar de directorio."

Ejercicios

Ejercicio 1: Planificar un move (Fácil)

src/helpers/email_sender.py necesita moverse a src/services/email/sender.py. Escribe los 4 pasos del ciclo con prompts para Claude Code.

Ver solución
# Paso 1: Tests
> "Ejecuta tests y confirma que pasan."

# Paso 2: Impacto
> "Lista todos los archivos que importan email_sender
   desde src/helpers/. Muestra la línea de import exacta."

# Paso 3: Mover
> "Crea el directorio src/services/email/ con __init__.py.
   Mueve src/helpers/email_sender.py a
   src/services/email/sender.py. Actualiza todos los imports
   de 'from helpers.email_sender' a 'from services.email.sender'."

# Paso 4: Verificar
> "Ejecuta tests. También busca 'helpers.email_sender'
   en todo el proyecto para confirmar 0 references viejas."

Ejercicio 2: Reorganizar utils/ (Medio)

Tu src/utils/ tiene estos 8 archivos. Clasifícalos y propón la estructura destino:

string_utils.py, date_utils.py, db_connection.py,
redis_cache.py, auth_check.py, rate_limiter.py,
csv_exporter.py, pdf_generator.py
Ver solución
# Clasificación:
# Utils puras (quedan en utils/):
#   string_utils.py, date_utils.py

# Infrastructure (mover a infrastructure/):
#   db_connection.py, redis_cache.py

# Middleware (mover a middleware/):
#   auth_check.py, rate_limiter.py

# Services (mover a services/):
#   csv_exporter.py, pdf_generator.py

# Estructura final:
# src/
#   utils/           → string_utils.py, date_utils.py
#   infrastructure/  → db_connection.py, redis_cache.py
#   middleware/       → auth_check.py, rate_limiter.py
#   services/        → csv_exporter.py, pdf_generator.py

Criterio: utils puras = sin side effects, sin I/O. Infrastructure = conexiones externas. Middleware = intercepta requests. Services = lógica de negocio.

Ejercicio 3: Diseñar un split (Medio)

src/services/app_service.py tiene 800 líneas con 4 clases: AuthService, UserService, OrderService, NotificationService. Escribe el prompt para Claude Code que haga el split.

Ver solución
> "Divide src/services/app_service.py en 4 archivos:
   1. src/services/auth_service.py — AuthService + sus imports
   2. src/services/user_service.py — UserService + sus imports
   3. src/services/order_service.py — OrderService + sus imports
   4. src/services/notification_service.py — NotificationService + sus imports
   
   Para cada clase:
   - Incluye solo los imports que esa clase necesita
   - Actualiza todos los imports en el proyecto que apuntaban
     a app_service.AuthService → auth_service.AuthService (etc.)
   - Si alguna clase depende de otra del mismo archivo,
     agrega el import al nuevo archivo

   Después de mover las 4 clases, elimina app_service.py.
   Ejecuta tests después de cada movimiento individual."

Ejercicio 4: Move con backwards compatibility (Difícil)

Tu módulo src/utils/logger.py es importado por 15 archivos internos y 3 proyectos externos. Diseña un plan de migración que no rompa los proyectos externos.

Ver solución
# Fase 1: Mover con re-export
> "Mueve src/utils/logger.py a src/infrastructure/logger.py.
   En src/utils/logger.py, deja un archivo que:
   1. Re-exporta todo desde infrastructure.logger
   2. Emite DeprecationWarning al importar
   3. Tiene un comentario 'Eliminar después de v2.0'

   Actualiza los 15 imports internos al nuevo path.
   Los 3 proyectos externos seguirán funcionando con el re-export."

# Fase 2: Comunicar a proyectos externos
# (fuera de Claude Code: enviar PR/notificación a los 3 proyectos)

# Fase 3: Eliminar re-export (después de que los externos migraron)
> "Elimina el archivo de re-export src/utils/logger.py.
   Verifica que ningún import interno lo usa."

Clave: re-export + DeprecationWarning permite migración gradual sin breaking changes.


Resumen

En esta cápsula aprendiste:

  • Move module reorganiza la estructura de directorios actualizando todos los imports
  • El ciclo es: tests → identificar impacto → mover → verificar — un archivo a la vez
  • Move simple: mover un archivo y actualizar N imports
  • Move con re-export: backwards compatibility para consumidores externos
  • Split: dividir un archivo grande en múltiples especializados
  • Merge: consolidar archivos pequeños en un módulo coherente
  • Claude Code encuentra todos los imports incluyendo relativos, __init__.py, y paths dinámicos
  • Mover de menor a mayor impacto reduce riesgo

Próxima cápsula: Interface Changes y Propagación. Vas a aprender el refactoring más complejo: cambiar la firma de una función y propagar a todos los consumers.


Recursos Adicionales

  1. Python Import System - Documentación oficial del sistema de imports de Python
  2. PEP 328 - Imports Multi-Line and Absolute/Relative - La PEP que define imports absolutos y relativos
  3. Refactoring Guru - Move Method/Class - Explicación visual del move refactoring
  4. importlib - Python Docs - Para entender cómo Python resuelve imports
  5. isort - Python Import Sorter - Herramienta para organizar imports automáticamente
  6. absolufy-imports - Convertir imports relativos a absolutos