Agregar el shared module como dependencia

// androidApp/build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

dependencies {
    // El shared module — se referencia como proyecto Gradle
    implementation(project(":shared"))

    // Dependencias Android-only (UI, ViewModel, etc.)
    implementation(libs.androidx.lifecycle.viewmodel.ktx)
    implementation(libs.koin.android)
    implementation(libs.koin.androidx.compose)
    // Compose, Navigation, etc.
}

// settings.gradle.kts (raíz) — ambos módulos declarados
include(":androidApp", ":shared")

Koin multiplatform — DI compartido y por plataforma

// shared/commonMain — módulo de DI compartido
// Define los bindings que son iguales en todas las plataformas
val sharedModule = module {
    // Use cases — Kotlin puro, mismos en todas las plataformas
    factory { GetProductosUseCase(get()) }
    factory { BuscarProductosUseCase(get()) }
    factory { SincronizarUseCase(get()) }

    // Repositorios
    single<ProductoRepository> { ProductoRepositoryImpl(get(), get()) }

    // API client
    single { ProductoApi(get()) }
    single { crearHttpClient(get()) }  // usa el engine que provee cada plataforma
}

// shared/androidMain — módulo Android-specific
val androidModule = module {
    // Engine de Ktor para Android
    single<HttpClientEngine> { OkHttp.create() }

    // Implementación Android de Settings usando SharedPreferences
    single { Settings(androidContext()) }
}

// androidApp — Application class donde se inicializa Koin
class MiApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MiApp)
            modules(
                sharedModule,   // módulo compartido
                androidModule,  // módulo Android-specific
                androidAppModule // módulo de la app Android (ViewModels, etc.)
            )
        }
    }
}

// androidApp — módulo de DI de la app Android
val androidAppModule = module {
    // ViewModels de Android (no son parte del shared)
    viewModel { ProductosAndroidViewModel(get(), get(), get()) }
    viewModel { DetalleAndroidViewModel(get()) }
}

ViewModel de Android consumiendo el shared module

// androidApp — el ViewModel de Android es un wrapper liviano sobre el shared
// Convierte los tipos del shared en tipos observables de Android

class ProductosAndroidViewModel(
    private val getProductos: GetProductosUseCase,
    private val sincronizar: SincronizarUseCase,
    private val buscar: BuscarProductosUseCase
) : ViewModel() {

    // Reexponer el Flow del use case como StateFlow de Android
    val uiState: StateFlow<ProductosUiState> = getProductos()
        .map { productos -> ProductosUiState(productos = productos) }
        .catch { e -> emit(ProductosUiState(error = e.message)) }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = ProductosUiState(isLoading = true)
        )

    fun onPullRefresh() {
        viewModelScope.launch {
            try {
                sincronizar()
            } catch (e: Exception) {
                // manejar error
            }
        }
    }

    fun buscarProductos(query: String) {
        viewModelScope.launch {
            val resultado = buscar(query)
            when (resultado) {
                is Resultado.Exito -> { /* actualizar state */ }
                is Resultado.Error -> { /* mostrar error */ }
                Resultado.Cargando -> { /* mostrar loader */ }
            }
        }
    }
}

// ProductosUiState vive en el shared module — se reutiliza en Android e iOS

Compose en Android con el shared module

// Compose en Android consume los datos del shared module exactamente igual
// que con cualquier otro ViewModel — no hay diferencia visible

@Composable
fun ProductosPantalla(
    viewModel: ProductosAndroidViewModel = koinViewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when {
        uiState.isLoading -> CircularProgressIndicator()
        uiState.error != null -> ErrorView(uiState.error!!)
        else -> LazyColumn {
            items(uiState.productos, key = { it.id }) { producto ->
                // Producto es el data class del shared module — Kotlin puro
                ProductoCard(producto = producto)
            }
        }
    }
}

@Composable
fun ProductoCard(producto: Producto) {  // Producto del shared module
    Card {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(producto.nombre, style = MaterialTheme.typography.titleMedium)
            Text("$${producto.precio}", style = MaterialTheme.typography.bodyLarge)
            producto.descripcion?.let { Text(it) }
        }
    }
}

Pasar el Context de Android al shared module

El shared module no puede tener el Context de Android como dependencia directa — eso rompería la compilación en iOS. El patrón correcto es una variable global inicializada una vez al arrancar la app:

// androidMain — variable global de contexto
// (el único lugar donde esto es aceptable en Android)
lateinit var appContext: android.content.Context

// androidApp — inicializar en Application.onCreate()
class MiApp : Application() {
    override fun onCreate() {
        super.onCreate()
        appContext = this  // antes de startKoin
        startKoin { /* ... */ }
    }
}

// androidMain — usar en las implementaciones actual
actual class Settings actual constructor(name: String) {
    private val prefs = appContext.getSharedPreferences(name, Context.MODE_PRIVATE)
    // ...
}

// iosMain — no hay equivalente, la clase Settings usa NSUserDefaults directamente
// y no necesita ningún contexto de aplicación