Módulo 3: Entender Arquitectura Existente

Dependency Maps y Grafos de Dependencias

Dependency Maps y Grafos de Dependencias

Descripción de la capsula

Vas a aprender a generar dependency maps — representaciones visuales y textuales de qué módulo depende de qué en un codebase. Este es el primer artefacto de tu architecture map y el más fundamental: sin saber las dependencias, no puedes trazar flujos, no puedes identificar patterns, y no puedes predecir el impacto de cambios.

Un dependency map responde la pregunta más importante antes de cualquier refactoring: "Si cambio este módulo, ¿qué otros módulos se afectan?" La respuesta a esa pregunta determina el riesgo, el esfuerzo, y la prioridad de cualquier cambio. Sin dependency map, estimas a ojo. Con dependency map, decides con datos.

En esta cápsula aprenderás a usar Claude Code para generar dependency maps a tres niveles de zoom — desde la vista de pájaro de 3-5 componentes principales hasta el detalle de funciones individuales. Aprenderás a leer e interpretar esos mapas, detectar patterns problemáticos como circular dependencies y high fan-out, y producir artefactos que otro developer puede usar para entender la estructura del sistema.


Que Es un Dependency Map

La idea fundamental

Un dependency map es un grafo donde:

  • Nodos = módulos, archivos, clases, o funciones
  • Aristas = relaciones de dependencia (imports, llamadas, herencia)

Si user_service.py importa user_repository.py, hay una arista de user_service hacia user_repository. Eso significa que user_service depende de user_repository — si cambias user_repository, user_service podría romperse.

Que muestra un dependency map

Lo que VES:                       Lo que SIGNIFICA:

user_service -> user_repository   "user_service usa user_repository"
                                  "Si cambias user_repository,
                                   revisa user_service"

auth_middleware -> user_service    "auth necesita user_service"
                                  "No puedes eliminar user_service
                                   sin romper auth"

payment_service -> user_service   "Payments consulta users"
payment_service -> email_service  "Payments envía emails"
                                  "payment_service tiene fan-out de 2"

Por que importa

Sin dependency map:

Developer: "Voy a refactorizar user_repository"
Resultado: 💥 Se rompen auth, payments, notifications, y admin
Developer: "No sabía que 4 módulos dependían de esto"

Con dependency map:

Developer: "Voy a refactorizar user_repository"
Mapa: user_repository es importado por: user_service,
      auth_service, payment_service, admin_service
Developer: "OK, necesito actualizar 4 módulos. Empiezo
           por el que tiene menos dependencias propias."

Tres Niveles de Zoom

El error más común al crear dependency maps es intentar mostrar todo a la vez. Un mapa con 50 módulos y 200 flechas no es un mapa — es ruido. La solución es trabajar con tres niveles de zoom.

Nivel 1: High-Level (3-5 componentes principales)

Este nivel responde: "¿Cuáles son los grandes bloques del sistema y cómo se conectan?"

Tú a Claude Code:
> "Analiza la estructura de este proyecto. Identifica los 3-5
> componentes principales (no módulos individuales, sino grupos
> de módulos que cumplen una función). Muestra las dependencias
> entre esos componentes como un diagrama Mermaid."

Claude Code responde:
graph TD
    API["API Layer<br/>routes/, middleware/"]
    SERVICES["Business Logic<br/>services/"]
    DATA["Data Access<br/>repositories/, models/"]
    EXTERNAL["External Services<br/>clients/"]
    CONFIG["Configuration<br/>config/, utils/"]

    API --> SERVICES
    API --> CONFIG
    SERVICES --> DATA
    SERVICES --> EXTERNAL
    SERVICES --> CONFIG
    DATA --> CONFIG
    EXTERNAL --> CONFIG

Cómo leer este diagrama:

  • Las flechas van de "depende de" hacia "es usado por"
  • API depende de Services y Config
  • Services depende de Data, External, y Config
  • Config no depende de nadie — es la base

Cuándo usar Nivel 1:

  • Para explicar el sistema a alguien nuevo
  • Para entender la big picture antes de profundizar
  • Para verificar que la arquitectura tiene layers claros

Nivel 2: Medium (módulos dentro de cada componente)

Este nivel responde: "¿Qué módulos hay dentro de cada componente y cómo se conectan?"

Tú a Claude Code:
> "Ahora profundiza en el componente 'Services'. Lista todos
> los módulos en services/ y muestra las dependencias entre
> ellos. Incluye también qué módulos de otros componentes
> importan. Formato: diagrama Mermaid."

Claude Code responde:
graph TD
    subgraph Services
        US["user_service.py"]
        PS["payment_service.py"]
        NS["notification_service.py"]
        AS["auth_service.py"]
        OS["order_service.py"]
    end

    subgraph Repositories
        UR["user_repository.py"]
        PR["payment_repository.py"]
        OR["order_repository.py"]
    end

    subgraph External
        EC["email_client.py"]
        SC["stripe_client.py"]
    end

    US --> UR
    PS --> PR
    PS --> US
    PS --> SC
    NS --> EC
    NS --> US
    AS --> US
    AS --> UR
    OS --> OR
    OS --> US
    OS --> PS

Cómo leer este diagrama:

  • payment_service depende de user_service, payment_repository, y stripe_client — tiene fan-out de 3
  • user_service es importado por payment_service, notification_service, auth_service, y order_service — tiene fan-in de 4
  • user_service es el hub: el módulo más conectado del sistema

Cuándo usar Nivel 2:

  • Para planificar un refactoring específico
  • Para entender el impacto de cambiar un módulo
  • Para identificar módulos con demasiadas dependencias

Nivel 3: Low (funciones dentro de un módulo)

Este nivel responde: "¿Qué funciones tiene este módulo y cuáles son llamadas desde fuera?"

Tú a Claude Code:
> "Analiza user_service.py. Lista todas las funciones públicas
> y privadas. Para cada función pública, muestra qué otros
> módulos la llaman. Formato: tabla."

Claude Code responde:
FunciónTipoLlamada desdeDescripción
get_user(user_id)Públicaauth_service, order_service, admin_routesObtiene usuario por ID
create_user(data)Públicaauth_service, user_routesCrea nuevo usuario
update_user(user_id, data)Públicauser_routes, admin_routesActualiza datos de usuario
delete_user(user_id)Públicaadmin_routesElimina usuario (soft delete)
get_user_with_orders(user_id)Públicaorder_service, admin_routesUsuario con sus pedidos
_validate_email(email)Privada(interna)Valida formato de email
_hash_password(password)Privada(interna)Hash con bcrypt
_check_duplicates(email)Privada(interna)Verifica email único

Cuándo usar Nivel 3:

  • Para entender exactamente qué funciones son la "API pública" de un módulo
  • Para decidir si dividir un módulo grande
  • Para encontrar funciones que son privadas pero deberían ser públicas (o viceversa)

Generar Dependency Maps con Claude Code

Prompt básico: overview de dependencias

Tú a Claude Code:
> "Analiza las dependencias de este proyecto. Para cada archivo
> Python en src/, lista qué otros archivos del proyecto importa.
> No incluyas dependencias externas (pip packages), solo imports
> internos del proyecto. Formato: lista organizada por directorio."

Claude Code responde:

src/api/
  auth_routes.py -> services/auth_service.py, middleware/auth.py
  user_routes.py -> services/user_service.py, schemas/user.py
  order_routes.py -> services/order_service.py, schemas/order.py

src/services/
  auth_service.py -> repositories/user_repository.py, utils/jwt.py
  user_service.py -> repositories/user_repository.py, utils/validators.py
  order_service.py -> repositories/order_repository.py, services/user_service.py
  payment_service.py -> services/user_service.py, clients/stripe_client.py

src/repositories/
  user_repository.py -> models/user.py, config/database.py
  order_repository.py -> models/order.py, models/user.py, config/database.py

src/models/
  user.py -> config/database.py
  order.py -> config/database.py, models/user.py

Prompt para mermaid diagram

Tú a Claude Code:
> "Genera un diagrama Mermaid de dependencias para los archivos
> en src/services/. Muestra cada archivo como un nodo y cada
> import interno como una flecha. Agrupa los nodos por directorio
> usando subgraphs."

Output esperado:

graph TD
    subgraph API
        AR["auth_routes"]
        URR["user_routes"]
        ORR["order_routes"]
    end

    subgraph Services
        AS["auth_service"]
        US["user_service"]
        OS["order_service"]
        PS["payment_service"]
    end

    subgraph Repositories
        URep["user_repository"]
        ORep["order_repository"]
    end

    AR --> AS
    URR --> US
    ORR --> OS
    AS --> URep
    US --> URep
    OS --> ORep
    OS --> US
    PS --> US

Prompt para detectar metricas clave

Tú a Claude Code:
> "Calcula las siguientes métricas de dependencias para cada
> archivo Python en src/:
> 1. Fan-out: cuántos módulos internos importa
> 2. Fan-in: cuántos módulos internos lo importan
> 3. Ratio fan-in/fan-out
> Ordena por fan-in descendente. Formato: tabla."

Claude Code responde:

| Archivo | Fan-out | Fan-in | Ratio | Rol |
|---------|---------|--------|-------|-----|
| user_service.py | 2 | 4 | 2.0 | Hub |
| user_repository.py | 2 | 3 | 1.5 | Hub |
| config/database.py | 0 | 5 | ∞ | Fundación |
| order_service.py | 3 | 1 | 0.3 | Consumer |
| payment_service.py | 2 | 0 | 0.0 | Endpoint |

Cómo interpretar las métricas:

  • Fan-in alto = muchos módulos dependen de él. Cambios aquí tienen alto impacto. Manejar con cuidado.
  • Fan-out alto = depende de muchos módulos. Posible god object o responsabilidades mixtas.
  • Fan-in alto + Fan-out bajo = módulo fundacional. Estable, muchos lo usan, pero él es independiente.
  • Fan-out alto + Fan-in bajo = módulo coordinador o posible candidato para dividir.
  • Fan-in = 0 = nadie lo importa. Posible dead code o entry point.

Detectar Patterns Problematicos

Circular dependencies

Una dependencia circular ocurre cuando A depende de B y B depende de A (directa o indirectamente).

Tú a Claude Code:
> "Busca dependencias circulares en este proyecto. Una dependencia
> circular es cuando el módulo A importa B, y B importa A (directa
> o indirectamente a través de otros módulos). Lista todas las
> que encuentres."

Claude Code responde:

⚠️ Dependencia circular detectada:

Ciclo 1: auth_service.py -> user_service.py -> auth_service.py
  - auth_service importa user_service (para get_user en login)
  - user_service importa auth_service (para hash_password en create)
  
Ciclo 2: order_service.py -> payment_service.py -> order_service.py
  - order_service importa payment_service (para procesar pago)
  - payment_service importa order_service (para actualizar estado)

Por qué son problemáticas:

  • Hacen difícil entender qué depende de qué
  • Dificultan testing: no puedes testear A sin B, ni B sin A
  • Complican refactoring: no puedes mover A sin mover B
  • En Python, pueden causar ImportError si no se manejan con lazy imports

Cómo se resuelven (preview del Módulo 4):

  • Extract common interface: sacar lo compartido a un tercer módulo
  • Dependency inversion: crear una abstracción que ambos implementen
  • Event-based communication: A emite un evento, B lo escucha (sin import directo)

High fan-out (demasiadas dependencias)

Tú a Claude Code:
> "Identifica los archivos con mayor fan-out (que importan más
> módulos internos del proyecto). Si alguno importa más de 5
> módulos diferentes, márcalo como potencial problema."

Claude Code responde:

⚠️ High fan-out detectado:

order_service.py - Fan-out: 7
  Importa: user_service, payment_service, notification_service,
           order_repository, product_service, shipping_service,
           config
  
  Análisis: Este módulo coordina demasiadas responsabilidades.
  Cada cambio en cualquiera de los 7 módulos puede afectarlo.
  Candidato para dividir en módulos más pequeños.

Cómo interpretar:

  • Fan-out > 5 es una señal de alerta (no una regla rígida)
  • Pregunta clave: "¿Este módulo tiene una responsabilidad clara, o está haciendo demasiadas cosas?"
  • Si es un coordinador legítimo (como un orchestrator), el fan-out puede ser aceptable
  • Si mezcla lógica de negocio con coordinación, es candidato a split

High fan-in sin abstracción

Tú a Claude Code:
> "Identifica los archivos con mayor fan-in (que son importados
> por más módulos). Si alguno tiene fan-in > 5 y no es un módulo
> de configuración o utilidad, márcalo."

Claude Code responde:

📊 Fan-in alto:

user_service.py - Fan-in: 8
  Importado por: auth_routes, user_routes, admin_routes,
                 order_service, payment_service,
                 notification_service, auth_service,
                 reporting_service

  Análisis: Este es el hub del sistema. Cualquier cambio en
  su interfaz pública afecta 8 módulos. Si no tiene una
  interfaz estable y bien definida, cada cambio es riesgoso.

Cómo interpretar:

  • Fan-in alto no es inherentemente malo — los módulos fundacionales tienen fan-in alto legítimamente
  • El problema es cuando el módulo cambia frecuentemente Y tiene fan-in alto
  • Pregunta clave: "¿La interfaz pública de este módulo es estable?"

Dependency Maps con Código Python

A veces necesitas generar dependency maps programáticamente. Aquí hay un approach con Claude Code que combina análisis y código:

Generar dependency map automatizado

Tú a Claude Code:
> "Escribe un script Python que analice los imports internos
> de todos los archivos .py en src/. Para cada archivo, extrae
> las líneas 'from ... import' y 'import ...' que referencien
> otros archivos del proyecto (no pip packages). Output: JSON
> con las dependencias."

Claude Code genera:

# dependency_analyzer.py
import ast
import os
import json
from pathlib import Path


def get_internal_imports(file_path: str, project_root: str) -> list[str]:
    """
    Extrae imports internos de un archivo Python.
    Solo incluye imports que referencien otros archivos del proyecto.
    """
    try:
        with open(file_path, "r") as f:
            tree = ast.parse(f.read())
    except SyntaxError:
        return []
    
    imports = []
    
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom):
            if node.module and not _is_external(node.module, project_root):
                imports.append(node.module)
        elif isinstance(node, ast.Import):
            for alias in node.names:
                if not _is_external(alias.name, project_root):
                    imports.append(alias.name)
    
    return imports


def _is_external(module_name: str, project_root: str) -> bool:
    """Determina si un import es externo (pip package) o interno."""
    # Convierte dots a path: "services.user_service" -> "services/user_service"
    module_path = module_name.replace(".", "/")
    
    # Busca si existe como archivo o directorio en el proyecto
    for ext in [".py", "/__init__.py", ""]:
        candidate = os.path.join(project_root, "src", module_path + ext)
        if os.path.exists(candidate):
            return False
    
    return True


def build_dependency_map(project_root: str) -> dict:
    """
    Construye el mapa completo de dependencias internas.
    Returns: {archivo: [lista de archivos de los que depende]}
    """
    src_dir = os.path.join(project_root, "src")
    dependency_map = {}
    
    for py_file in Path(src_dir).rglob("*.py"):
        if "__pycache__" in str(py_file):
            continue
        
        relative_path = str(py_file.relative_to(src_dir))
        imports = get_internal_imports(str(py_file), project_root)
        
        if imports:
            dependency_map[relative_path] = imports
    
    return dependency_map


def calculate_metrics(dep_map: dict) -> dict:
    """Calcula fan-in y fan-out para cada módulo."""
    all_modules = set(dep_map.keys())
    
    # Agregar módulos que son importados pero no tienen imports propios
    for imports in dep_map.values():
        for imp in imports:
            module_file = imp.replace(".", "/") + ".py"
            all_modules.add(module_file)
    
    metrics = {}
    
    for module in all_modules:
        fan_out = len(dep_map.get(module, []))
        fan_in = sum(
            1 for imports in dep_map.values()
            if any(imp.replace(".", "/") + ".py" == module for imp in imports)
        )
        
        metrics[module] = {
            "fan_out": fan_out,
            "fan_in": fan_in,
            "ratio": round(fan_in / fan_out, 2) if fan_out > 0 else float("inf")
        }
    
    return dict(sorted(
        metrics.items(),
        key=lambda x: x[1]["fan_in"],
        reverse=True
    ))


if __name__ == "__main__":
    import sys
    
    project_root = sys.argv[1] if len(sys.argv) > 1 else "."
    
    dep_map = build_dependency_map(project_root)
    
    print("=== Dependency Map ===")
    print(json.dumps(dep_map, indent=2))
    
    print("\n=== Metrics ===")
    metrics = calculate_metrics(dep_map)
    
    print(f"\n{'Module':<40} {'Fan-out':>8} {'Fan-in':>7} {'Ratio':>7}")
    print("-" * 65)
    for module, m in metrics.items():
        ratio_str = f"{m['ratio']:.1f}" if m['ratio'] != float('inf') else "∞"
        print(f"{module:<40} {m['fan_out']:>8} {m['fan_in']:>7} {ratio_str:>7}")

# Output esperado:
# === Dependency Map ===
# {
#   "services/user_service.py": ["repositories.user_repository", "utils.validators"],
#   "services/order_service.py": ["repositories.order_repository", "services.user_service"],
#   ...
# }
#
# === Metrics ===
# Module                                   Fan-out  Fan-in   Ratio
# -----------------------------------------------------------------
# services/user_service.py                       2       4     2.0
# repositories/user_repository.py                2       3     1.5
# config/database.py                             0       5       ∞

Generar mermaid desde el analysis

Tú a Claude Code:
> "Toma el dependency map que generamos y conviértelo en un
> diagrama Mermaid. Agrupa los módulos por directorio usando
> subgraphs. Solo incluye dependencias con fan-in > 1 para
> mantener el diagrama legible."

Esto produce un diagrama filtrado que muestra solo las relaciones más importantes, no todo el ruido de dependencias menores.


Comparacion: Análisis Manual vs Claude Code

AspectoAnálisis ManualCon Claude Code
Tiempo para proyecto de 10K líneas4-8 horas15-30 minutos
PrecisiónHumana (se olvidan imports indirectos)Completa (analiza todos los archivos)
ActualizaciónRepetir todo el procesoRe-ejecutar el prompt
Formato de outputDepende del tool (pizarrón, draw.io)Texto + Mermaid (versionable)
Detección de circularesMuy difícil manualmenteAutomático
Métricas (fan-in/fan-out)Tedioso de calcularInstantáneo
Diferentes zoom levelsHay que redibujarNuevo prompt, nuevo nivel

El trade-off:

  • Claude Code es más rápido y completo para la generación
  • El humano es mejor para la interpretación y las decisiones
  • El workflow ideal: Claude Code genera -> tú interpretas -> Claude Code ajusta basándose en tu feedback

Conexión con Proyecto

En el Architecture Map de Proyecto Real (cápsula 05), el dependency map es el primer componente que generarás.

Usarás las técnicas de esta cápsula para:

  • Generar el dependency map a Nivel 1 (high-level) y Nivel 2 (medium) del proyecto elegido
  • Calcular métricas de fan-in y fan-out
  • Detectar dependencias circulares y high fan-out
  • Documentar los hallazgos con diagramas Mermaid

Todo lo que aprendes hoy se aplica directamente en la cápsula 05.

Y en el Módulo 4 (Refactoring), los dependency maps que generes aquí te dirán exactamente qué módulos tocar y en qué orden refactorizar.


Troubleshooting

Problema 1: Claude Code genera un diagrama demasiado complejo

Causa: El proyecto tiene muchos módulos y Claude Code intenta mostrar todos. Solución:

Tú a Claude Code:
> "El diagrama tiene demasiados nodos. Simplifica:
> 1. Agrupa archivos por directorio (un nodo por directorio)
> 2. Solo muestra dependencias entre directorios, no entre
>    archivos individuales
> 3. Máximo 8 nodos en el diagrama"

Problema 2: Claude Code incluye dependencias externas (pip packages)

Causa: Claude Code no distingue correctamente entre imports internos y externos. Solución:

Tú a Claude Code:
> "Solo incluye dependencias INTERNAS del proyecto. Excluye:
> - Cualquier import de la standard library (os, sys, json, etc.)
> - Cualquier import de pip packages (fastapi, sqlalchemy, etc.)
> - Solo muestra imports que referencien archivos dentro de src/"

Problema 3: No detecta dependencias indirectas

Causa: Solo miras imports directos, pero A depende de B que depende de C (A depende indirectamente de C). Solución:

Tú a Claude Code:
> "Genera el dependency map incluyendo dependencias transitivas.
> Si A importa B y B importa C, muestra que A depende
> indirectamente de C. Usa líneas punteadas para dependencias
> indirectas y sólidas para directas."

Problema 4: El diagrama Mermaid no renderiza correctamente

Causa: Caracteres especiales en nombres de archivos o syntax errors en Mermaid. Solución:

Tú a Claude Code:
> "El diagrama Mermaid no renderiza. Revisa:
> 1. Que los IDs de nodos no tengan puntos ni guiones
> 2. Que los labels estén entre comillas si tienen espacios
> 3. Que no haya nodos huérfanos
> Genera una versión corregida."

Problema 5: No se como interpretar las metricas

Causa: Los números de fan-in/fan-out no tienen significado sin contexto. Solución: Usa estas heurísticas como punto de partida:

MétricaNormalAtenciónProblema
Fan-out1-34-67+
Fan-in1-56-1011+
Circular deps012+

Recuerda: estas son heurísticas, no reglas. Un módulo config.py con fan-in de 15 puede ser perfectamente normal.


Ejercicios

Ejercicio 1: Dependency Map básico (Fácil)

Dado este código de ejemplo, identifica todas las dependencias internas y dibuja el dependency map:

# Archivo: routes/user_routes.py
from services.user_service import UserService
from schemas.user import UserCreate, UserResponse

# Archivo: services/user_service.py
from repositories.user_repository import UserRepository
from utils.validators import validate_email

# Archivo: repositories/user_repository.py
from models.user import User
from config.database import get_session

# Archivo: services/auth_service.py
from services.user_service import UserService
from utils.jwt import create_token

Dibuja el dependency map como texto (flechas ASCII) y calcula el fan-in y fan-out de cada módulo.

Ver solución

Dependency map:

routes/user_routes -> services/user_service
routes/user_routes -> schemas/user

services/user_service -> repositories/user_repository
services/user_service -> utils/validators

repositories/user_repository -> models/user
repositories/user_repository -> config/database

services/auth_service -> services/user_service
services/auth_service -> utils/jwt

Métricas:

MóduloFan-outFan-in
routes/user_routes20
services/user_service22
services/auth_service20
repositories/user_repository21
schemas/user01
utils/validators01
models/user01
config/database01
utils/jwt01

Observaciones:

  • services/user_service es el hub (fan-in = 2, fan-out = 2)
  • Los módulos con fan-in = 0 son entry points o módulos leaf
  • Los módulos con fan-out = 0 son fundacionales (no dependen de nada interno)

Explicación: El dependency map se construye siguiendo cada import. El fan-out es cuántos módulos importa, el fan-in es cuántos lo importan a él.

Ejercicio 2: Detectar dependencia circular (Fácil)

Observa estos imports y determina si hay dependencias circulares:

# Archivo: services/order_service.py
from services.payment_service import process_payment
from services.user_service import get_user

# Archivo: services/payment_service.py
from services.order_service import update_order_status
from clients.stripe import charge_card

# Archivo: services/user_service.py
from repositories.user_repository import UserRepository

# Archivo: services/notification_service.py
from services.user_service import get_user
from services.order_service import get_order
Ver solución

Sí hay una dependencia circular:

order_service -> payment_service -> order_service  ⚠️ CIRCULAR
  • order_service importa payment_service (para process_payment)
  • payment_service importa order_service (para update_order_status)

No hay circular en:

  • user_service no importa ningún otro service (es unidireccional)
  • notification_service importa user_service y order_service, pero ninguno lo importa a él

Cómo resolver la circular (preview):

  1. Extraer update_order_status a un módulo compartido
  2. Usar eventos: payment_service emite un evento "payment_completed" y order_service lo escucha
  3. Dependency inversion: crear una interfaz que ambos usen

Explicación: La circular se detecta trazando las flechas. Si puedes seguir una ruta que te lleva de vuelta al punto de partida, hay un ciclo.

Ejercicio 3: Análisis con Claude Code (Medio)

Usa Claude Code para analizar las dependencias de un proyecto real. Elige un proyecto Python que tengas disponible (puede ser el que usaste en los Módulos 1-2) y ejecuta estos prompts:

  1. Prompt para listar todos los imports internos
  2. Prompt para generar el diagrama Mermaid a Nivel 1
  3. Prompt para calcular fan-in y fan-out

Documenta los prompts exactos que usaste, las respuestas de Claude Code, y tu interpretación.

Ver solución

Ejemplo con un proyecto FastAPI:

Prompt 1:
> "Lista todos los archivos Python en src/ y para cada uno,
> muestra los imports internos (no pip packages, no stdlib).
> Formato: archivo -> [lista de imports]"

Prompt 2:
> "Genera un diagrama Mermaid de las dependencias a nivel de
> directorio. Cada directorio es un nodo. Las flechas muestran
> si archivos de un directorio importan archivos de otro."

Prompt 3:
> "Para cada archivo Python en src/, calcula:
> - Fan-out: cuántos archivos internos importa
> - Fan-in: cuántos archivos internos lo importan
> Ordena por fan-in descendente. Formato tabla."

Tu interpretación debería responder:

  • ¿Cuál es el módulo más conectado? (mayor fan-in)
  • ¿Hay algún módulo con fan-out sospechosamente alto?
  • ¿Hay dependencias circulares?
  • ¿La estructura de capas (layers) se respeta? (API -> Services -> Repositories)

Explicación: Este ejercicio te obliga a practicar el workflow completo: prompt -> output -> interpretación. No hay una única respuesta correcta — depende del proyecto que elijas.

Ejercicio 4: Interpretar metricas (Medio)

Dado este reporte de métricas, identifica los problemas potenciales y sugiere acciones:

Module                                   Fan-out  Fan-in   Ratio
-----------------------------------------------------------------
services/user_service.py                       2       8     4.0
services/order_orchestrator.py                 9       1     0.1
config/settings.py                             0      12       ∞
services/payment_service.py                    3       3     1.0
services/auth_service.py                       4       6     1.5
utils/helpers.py                               0       7       ∞
models/base.py                                 0      10       ∞
services/legacy_handler.py                     6       0     0.0
Ver solución

Problemas identificados:

  1. order_orchestrator.py — Fan-out de 9 ⚠️

    • Depende de 9 módulos diferentes
    • Posible god object o módulo con demasiadas responsabilidades
    • Acción: Investigar si se puede dividir en orquestadores más pequeños
  2. user_service.py — Fan-in de 8 ⚠️

    • 8 módulos dependen de él
    • Cualquier cambio en su interfaz pública afecta a 8 consumidores
    • Acción: Asegurar que su interfaz pública sea estable y bien definida
  3. legacy_handler.py — Fan-out de 6, Fan-in de 0 ⚠️

    • Depende de 6 módulos pero nadie lo importa
    • Posible dead code o entry point no documentado
    • Acción: Verificar si realmente se usa (puede ser llamado dinámicamente o desde tests)
  4. config/settings.py y models/base.py — Fan-in alto, fan-out 0 ✅

    • Esto es normal: son módulos fundacionales que muchos necesitan
    • No requieren acción
  5. utils/helpers.py — Fan-in de 7 ⚠️

    • Un módulo "helpers" con fan-in alto a menudo es un cajón de sastre
    • Acción: Investigar si las funciones deberían estar en módulos más específicos

Explicación: Las métricas por sí solas no dicen "hay un problema." Pero combinadas con el contexto (¿qué hace el módulo?) permiten identificar patterns sospechosos y priorizar investigación.

Ejercicio 5: Generar recomendaciones (Difícil)

Dado el siguiente dependency map, genera un reporte de recomendaciones que incluya: (a) problemas detectados, (b) nivel de riesgo de cada uno, (c) acción sugerida para cada problema.

graph TD
    A["auth_service"] --> B["user_service"]
    B --> A
    C["order_service"] --> B
    C --> D["payment_service"]
    D --> C
    E["notification_service"] --> B
    E --> C
    F["admin_service"] --> B
    F --> C
    F --> D
    F --> E
    F --> A
    G["reporting_service"] --> B
    G --> C
    G --> D
Ver solución

Reporte de recomendaciones:

#ProblemaRiesgoAcción sugerida
1Circular: auth_service <-> user_serviceAltoExtraer funcionalidad compartida a un módulo auth_utils o usar dependency inversion
2Circular: order_service <-> payment_serviceAltoUsar eventos: payment emite "payment_completed", order lo escucha. Rompe la dependencia directa
3admin_service tiene fan-out de 5MedioRevisar si es un god object. Posiblemente dividir en admin_user_service, admin_order_service, etc.
4user_service tiene fan-in de 5MedioAsegurar interfaz pública estable. Considerar crear una abstracción/interfaz si cambia frecuentemente
5reporting_service depende de 3 services directamenteBajoEvaluar si debería depender de repositories directamente en vez de services, o usar un read model

Orden de prioridad para refactoring:

  1. Resolver circulares (auth <-> user, order <-> payment) — bloquean otros cambios
  2. Evaluar admin_service — fan-out de 5 es señal de demasiadas responsabilidades
  3. Estabilizar interfaz de user_service — afecta a 5 módulos
  4. Evaluar reporting_service — optimización, no urgente

Explicación: Las dependencias circulares son siempre la prioridad más alta porque complican todos los demás cambios. El orden de refactoring se basa en: (1) desbloquear, (2) estabilizar hubs, (3) optimizar.


Resumen

En esta cápsula aprendiste:

  • ✅ Qué es un dependency map y por qué es el primer artefacto del architecture map
  • ✅ Los tres niveles de zoom: high-level (componentes), medium (módulos), low (funciones)
  • ✅ Cómo usar Claude Code para generar dependency maps en texto y Mermaid
  • ✅ Métricas clave: fan-in (cuántos te usan), fan-out (de cuántos dependes)
  • ✅ Patterns problemáticos: circular dependencies, high fan-out, god objects
  • ✅ La diferencia entre análisis manual (horas) y con Claude Code (minutos)
  • ✅ Cómo interpretar métricas: los números sin contexto no dicen nada, pero con contexto informan decisiones

Próxima cápsula: Flow Analysis — Request->Response y Data Pipelines — cómo trazar el camino que siguen los datos y requests a través de las dependencias que acabas de mapear.


Recursos Adicionales

  1. Mermaid Flowchart Syntax — Referencia completa para crear diagramas de flujo y grafos de dependencias en Mermaid
  2. Python AST Module — Official Docs — Para entender cómo funciona el análisis estático de imports en Python
  3. Dependency Management in Software Architecture — Martin Fowler sobre dependency inversion y cómo resolver dependencias problemáticas
  4. Software Design X-Rays — Adam Tornhill — Análisis de dependencias usando datos de versión control como complemento al análisis estático
  5. Circular Dependencies in Python — Real Python — Guía práctica sobre imports y cómo manejar circulares en Python
  6. C4 Model — Component Diagram — Inspiración para diferentes niveles de zoom en diagramas de arquitectura

Módulo 3, Cápsula 02 — Refactoring & Legacy Code with Claude Code Guide