Antes de empezar — dos precondiciones

Migrar una app existente a multimodulo sin estas dos cosas en su lugar es garantía de caos:

  1. Convention plugins básicos: aunque sea solo uno que configure las opciones de compilación comunes. Sin esto, cada módulo nuevo requiere copiar y pegar 30 líneas de Gradle manualmente.
  2. La app ya usa inyección de dependencias: Hilt o Koin. Extraer un módulo de una app donde todo se instancia directamente es muchísimo más difícil que si ya tenés DI. Si no tenés DI, migrá primero.

Qué extraer primero — el orden importa

El error más común es intentar extraer una feature completa como primer módulo. Las features tienen muchas dependencias. Lo correcto es empezar por lo que menos dependencias tiene y es más estable:

# Orden recomendado de extracción:

# 1. :core:domain (o similar)
#    → Modelos de datos puros, interfaces de repositorio
#    → No depende de nada de Android
#    → Es el módulo más consumido — extraerlo primero desbloquea todo lo demás
#    → Riesgo: bajo. Es solo clases Kotlin.

# 2. :core:ui
#    → Componentes visuales compartidos, el Theme, colores, tipografía
#    → Depende de Android pero no de la lógica de la app
#    → Riesgo: bajo. Si algo se rompe, es visible inmediatamente.

# 3. :core:network
#    → OkHttpClient, Retrofit base, interceptors
#    → Depende de librerías externas, no de features
#    → Riesgo: medio. Requiere ajustar algunos imports.

# 4. Una feature estable que no cambia seguido
#    → El módulo de "Ajustes" o "Perfil" suele ser buen candidato inicial
#    → Riesgo: medio-alto. Primer contacto real con dependencias circulares.

# 5. Las features más activas (checkout, productos, etc.)
#    → Dejás para el final lo que más cambia y más dependencias tiene
#    → Ya tenés experiencia de los módulos anteriores

El proceso para extraer un módulo

# Antes de mover una sola línea de código, mapear las dependencias:

# 1. Identificar qué clases van en el nuevo módulo
#    Ej: todo en el paquete ar.pensa.app.domain.*

# 2. Listar qué importa ese código (qué necesita)
#    → ¿Importa android.*? → necesita módulo Android library
#    → ¿Solo Kotlin/coroutines? → puede ser java-library (más liviano)
#    → ¿Importa Room, Retrofit? → necesita esas dependencias en el nuevo módulo

# 3. Listar qué usa ese código (quién lo consume)
#    → ¿Solo el :app? → la extracción es simple
#    → ¿Todo el proyecto? → el nuevo módulo lo van a usar todos → hacelo :core

# 4. Crear el módulo con la estructura correcta
# 5. Mover el código
# 6. Corregir errores de compilación
# 7. Verificar que los tests siguen pasando

Crear el módulo — la mecánica

# Android Studio: File → New → New Module → Android Library (o Java/Kotlin Library)
# Nombre: core-domain, core-ui, feature-checkout, etc.
# Android Studio crea la estructura y agrega a settings.gradle automáticamente
// settings.gradle — verificar que el módulo quedó declarado
include(":app")
include(":core:domain")   // nuevo
include(":core:ui")       // nuevo

// :core:domain/build.gradle — módulo Kotlin puro
plugins {
    alias(libs.plugins.android.miapp.kotlin.library)  // convention plugin
    // O si no tenés convention plugins todavía:
    id("java-library")
    alias(libs.plugins.kotlin.jvm)
}

dependencies {
    implementation(libs.kotlinx.coroutines.core)
}

// :app/build.gradle — agregar la dependencia al nuevo módulo
dependencies {
    implementation(project(":core:domain"))  // agregar esto
    // ... resto de las dependencias
}

Verificar que el módulo es realmente independiente

# La prueba más importante: compilar SOLO el módulo sin el resto del proyecto
./gradlew :core:domain:compileKotlin

# Si compila solo → el módulo es verdaderamente independiente
# Si falla → hay dependencias que no declaraste en su build.gradle

# Ver el grafo de dependencias del módulo:
./gradlew :core:domain:dependencies

# Ver qué módulos dependen de este módulo (dependientes reversos):
./gradlew :app:dependencies | grep "core:domain"

# Correr solo los tests del módulo extraído:
./gradlew :core:domain:test

El módulo que no compila solo no está listoSi el módulo solo compila cuando está el proyecto completo, tiene dependencias implícitas que no declaraste. Esas dependencias van a causar problemas cuando alguien intente reusar el módulo o cuando cambies la estructura del proyecto. Resolvé esto antes de seguir extrayendo.

Iterar gradualmente — no en un sprint

La migración de una app grande a multimodulo no se hace en una semana. El proceso correcto:

# Sprint 1: infraestructura
# → Convention plugins básicos
# → Módulo :core:domain extraído y compilando solo
# → CI/CD actualizado para correr tests por módulo

# Sprint 2-3: core modules
# → :core:ui, :core:network, :core:database
# → Cada extracción se integra, se testea, se hace merge antes de la siguiente

# Sprint 4+: feature modules
# → Una feature por sprint, empezando por la más estable
# → No apresurarse — cada feature trae sus propios desafíos de DI y navegación

Errores comunes en la migración

# Error 1: mover código sin ajustar los imports
# Los package names cambian cuando el código va a un módulo nuevo
# Solución: usar "Move to module" de Android Studio que actualiza los imports

# Error 2: olvidar agregar dependencias al nuevo build.gradle
# El código compilaba en :app porque :app tenía todo en el classpath
# Al moverlo a un módulo propio, las dependencias no están
# Solución: agregar explícitamente cada dependencia que usa el módulo

# Error 3: dependencias circulares no detectadas antes
# :feature:checkout usa algo de :feature:productos
# Al intentar hacer módulos separados, Gradle falla
# Solución: mover el código compartido a :core:domain primero

# Error 4: la app deja de compilar por semanas
# Migrar demasiadas cosas a la vez sin hacer merge frecuente
# Solución: extraer un módulo, hacer merge, verificar que todo funciona, repetir

# Error 5: duplicar configuración de Gradle por no tener convention plugins
# Cada módulo tiene su propio build.gradle con las mismas 40 líneas
# Solución: crear convention plugins ANTES de crear más módulos