Estructura del proyecto

Un proyecto KMP típico con Android e iOS tiene esta forma:

miapp-kmp/
├── androidApp/               # App Android — solo UI y Android-specific
│   ├── src/main/
│   └── build.gradle.kts
├── iosApp/                   # App iOS — solo UI SwiftUI/UIKit
│   ├── iosApp.xcodeproj/
│   └── iosApp/
│       └── ContentView.swift
├── shared/                   # Módulo compartido — el corazón del proyecto
│   ├── src/
│   │   ├── commonMain/       # Código Kotlin que compila en todas las plataformas
│   │   │   └── kotlin/
│   │   ├── androidMain/      # Código específico de Android (actual declarations)
│   │   │   └── kotlin/
│   │   ├── iosMain/          # Código específico de iOS
│   │   │   └── kotlin/
│   │   ├── commonTest/       # Tests que corren en todas las plataformas
│   │   ├── androidUnitTest/
│   │   └── iosTest/
│   └── build.gradle.kts
├── build.gradle.kts          # raíz
├── gradle/libs.versions.toml
└── settings.gradle.kts

Gradle con el plugin de KMP

// shared/build.gradle.kts
plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidLibrary)
    alias(libs.plugins.kotlinxSerialization)
}

kotlin {
    // Targets — plataformas para las que compilamos
    androidTarget {
        compilations.all {
            kotlinOptions { jvmTarget = "17" }
        }
    }

    // iOS targets — hay que declarar todas las arquitecturas
    iosX64()      // simulador en Mac Intel
    iosArm64()    // dispositivo físico iOS
    iosSimulatorArm64()  // simulador en Mac Apple Silicon

    // XCFramework agrupa los tres targets iOS en un solo framework
    val xcframeworkName = "Shared"
    val xcf = XCFramework(xcframeworkName)
    listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach {
        it.binaries.framework {
            baseName = xcframeworkName
            xcf.add(this)
        }
    }

    sourceSets {
        // Código común — disponible en TODAS las plataformas
        commonMain.dependencies {
            implementation(libs.kotlinx.coroutines.core)
            implementation(libs.kotlinx.serialization.json)
            implementation(libs.ktor.client.core)
            implementation(libs.ktor.client.content.negotiation)
            implementation(libs.ktor.serialization.kotlinx.json)
        }

        // Dependencias específicas de Android
        androidMain.dependencies {
            implementation(libs.ktor.client.okhttp)  // engine OkHttp para Android
        }

        // Dependencias específicas de iOS
        iosMain.dependencies {
            implementation(libs.ktor.client.darwin)  // engine Darwin para iOS
        }

        // Tests comunes
        commonTest.dependencies {
            implementation(libs.kotlin.test)
        }
    }
}

android {
    namespace = "ar.pensa.miapp.shared"
    compileSdk = 35
    defaultConfig { minSdk = 24 }
}
# gradle/libs.versions.toml
[versions]
kotlin = "2.0.0"
ktor = "3.0.0"
kotlinxCoroutines = "1.9.0"
kotlinxSerialization = "1.7.0"

[libraries]
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }

[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
androidLibrary = { id = "com.android.library", version = "8.5.0" }
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

Source sets — dónde va cada cosa

# commonMain/kotlin/ar/pensa/app/shared/
# → Toda la lógica que no depende de ninguna plataforma
# → Puede importar: kotlin.*, kotlinx.*, librerías declaradas en commonMain.dependencies
# → NO puede importar: android.*, java.*, Foundation.* (iOS)

# androidMain/kotlin/ar/pensa/app/shared/
# → Implementaciones "actual" para Android
# → Puede importar todo lo que commonMain puede + android.*

# iosMain/kotlin/ar/pensa/app/shared/
# → Implementaciones "actual" para iOS
# → Puede importar todo lo de commonMain + platform.Foundation.*, platform.UIKit.*

# Regla práctica:
# Si el archivo no tiene ningún import de plataforma → va en commonMain
# Si necesita android.* o java.* → va en androidMain
# Si necesita platform.* (objetos de Objective-C/Swift) → va en iosMain

Targets disponibles en KMP

kotlin {
    // JVM / Android
    androidTarget()    // para apps Android
    jvm()              // para desktop JVM, backend

    // iOS / macOS / tvOS / watchOS (todos basados en LLVM)
    iosX64()
    iosArm64()
    iosSimulatorArm64()
    macosX64()
    macosArm64()
    tvosX64()
    tvosArm64()
    watchosArm64()

    // JavaScript / WebAssembly
    js(IR) { browser(); nodejs() }
    wasmJs { browser() }

    // Linux / Windows (para CLIs, herramientas)
    linuxX64()
    mingwX64()  // Windows

    // Para Android + iOS (el caso más común), solo necesitás:
    // androidTarget(), iosX64(), iosArm64(), iosSimulatorArm64()
}

Crear el proyecto con el wizard de Android Studio

# Opción 1: Android Studio con el plugin KMP
# File → New → New Project → Kotlin Multiplatform App
# (requiere el plugin "Kotlin Multiplatform" instalado en Android Studio)

# Opción 2: KMP Wizard online — la más recomendada para empezar
# https://kmp.jetbrains.com
# → Elegir Android + iOS + shared module
# → Descargar el proyecto generado
# → Abrirlo en Android Studio

# Opción 3: agregar KMP a un proyecto Android existente
# → Crear un nuevo módulo shared en el proyecto existente
# → Mover gradualmente código del módulo app al shared

El primer build — lo que puede fallar

# Buildear el módulo compartido:
./gradlew :shared:build

# Buildear solo el framework para iOS (genera el XCFramework):
./gradlew :shared:assembleXCFramework

# El framework generado aparece en:
# shared/build/XCFrameworks/debug/Shared.xcframework

# Errores comunes en el primer build:

# Error 1: Kotlin version mismatch
# "The applied plugin 'kotlin-multiplatform' requires the 'org.jetbrains.kotlin:kotlin-gradle-plugin' version to be exactly 2.0.0"
# → Asegurarse de que todas las versiones de Kotlin sean consistentes en libs.versions.toml

# Error 2: Xcode Command Line Tools no instalados (para iOS)
# → xcode-select --install

# Error 3: El módulo shared no puede importar librerías de Android
# → Una librería en commonMain.dependencies no puede ser android-only
# → Librerías como Room, Retrofit, Glide no tienen versión KMP
# → Reemplazar por SQLDelight, Ktor, Coil KMP respectivamente