El problema que KMP viene a resolver

Una app con versión Android e iOS tiene dos bases de código distintas. La lógica de validación de un formulario, las reglas de negocio para calcular un precio, el mapeo de una respuesta de API a modelos del dominio — todo eso se escribe dos veces, en dos lenguajes, por dos equipos. Si cambia una regla, hay que cambiarla en dos lugares y confiar en que los dos cambios son equivalentes.

KMP propone una solución específica: compartir la lógica de negocio, pero dejar que cada plataforma tenga su propia UI nativa. No es un framework de UI multiplataforma — es un mecanismo para que Kotlin compile a múltiples targets.

KMP vs Flutter vs React Native — la diferencia que importa

# Flutter
# → Comparte TODO: lógica + UI
# → La UI es dibujada por Flutter (no nativa del SO)
# → Un solo equipo puede hacer Android e iOS
# → La UI puede verse ligeramente diferente a la nativa
# → Lenguaje: Dart

# React Native
# → Comparte lógica + estructura de UI (con componentes nativos bajo el capó)
# → Bridge JavaScript ↔ nativo tiene overhead
# → La UI intenta usar componentes nativos pero con limitaciones
# → Lenguaje: JavaScript/TypeScript

# KMP (Kotlin Multiplatform)
# → Comparte SOLO la lógica: dominio, datos, networking, caché
# → La UI es 100% nativa: Jetpack Compose en Android, SwiftUI en iOS
# → Requiere equipo de Android + equipo de iOS (o full-stack mobile)
# → La experiencia de usuario es indistinguible de una app nativa
# → Lenguaje: Kotlin (compartido) + Swift/SwiftUI (iOS)

# Cuándo elegir KMP:
# → Ya tenés equipos nativos en Android e iOS
# → La fidelidad de la UI nativa es prioritaria
# → Querés compartir lógica sin cambiar el stack de UI de ninguna plataforma
# → Tenés lógica de negocio compleja que vale la pena escribir una sola vez

Qué compartir y qué no

La clave del éxito con KMP es no intentar compartir todo. Hay código que se comparte bien y código que genera más fricción que valor.

# IDEAL para compartir — beneficio alto, fricción baja:
# ✓ Modelos de dominio (data classes Kotlin puras)
# ✓ Interfaces de repositorio
# ✓ Use cases / interactors (lógica de negocio pura)
# ✓ Llamadas de red con Ktor (cliente HTTP multiplataforma)
# ✓ Serialización/deserialización con kotlinx.serialization
# ✓ Caché local con SQLDelight (base de datos multiplataforma)
# ✓ Lógica de validación
# ✓ Formateo de fechas y monedas (con kotlinx-datetime)
# ✓ Flows como contrato entre shared y UI

# POSIBLE pero más complejo:
# ~ ViewModels compartidos (con KMM ViewModel de JetBrains)
# ~ Navegación compartida (con Decompose u otras librerías)
# ~ Inyección de dependencias (Koin multiplatform)

# MEJOR dejar a cada plataforma:
# ✗ UI (Compose en Android, SwiftUI en iOS)
# ✗ Permisos del sistema
# ✗ Notificaciones push (la implementación es radicalmente distinta)
# ✗ Deep linking nativo
# ✗ Biometría
# ✗ Acceso a hardware específico

El mecanismo expect/actual — la pieza central

Cuando el código compartido necesita hacer algo que es diferente en cada plataforma (obtener la hora, acceder al sistema de archivos, generar un UUID), KMP usa el mecanismo expect/actual: el código compartido declara qué necesita (expect) y cada plataforma provee su implementación (actual).

// En commonMain — declarar la expectativa
expect fun generarUUID(): String
// El shared module puede llamar a generarUUID() sin saber cómo se implementa

// En androidMain — implementación Android
actual fun generarUUID(): String = java.util.UUID.randomUUID().toString()

// En iosMain — implementación iOS
actual fun generarUUID(): String = platform.Foundation.NSUUID().UUIDString()

// El compilador verifica que TODAS las plataformas target tienen un actual
// para cada expect — falla en compilación si falta alguno
// expect/actual para clases completas
// commonMain:
expect class PlatformContext

// androidMain:
actual typealias PlatformContext = android.content.Context

// iosMain:
actual class PlatformContext  // iOS no necesita contexto de aplicación

// expect/actual para objetos:
expect object Logger {
    fun log(mensaje: String)
}

// androidMain:
actual object Logger {
    actual fun log(mensaje: String) = android.util.Log.d("KMP", mensaje)
}

// iosMain:
actual object Logger {
    actual fun log(mensaje: String) = println("[KMP] $mensaje")
}

expect/actual es para los bordes del shared module, no para la lógicaLa lógica de negocio central no debería necesitar expect/actual — si usa solo Kotlin puro y librerías multiplataforma, compila igual en todas las plataformas. El expect/actual aparece en los puntos donde el shared module necesita interactuar con el sistema operativo.

KMP hoy — madurez y adopción real

KMP salió de beta y alcanzó la versión estable en noviembre de 2023. Compose Multiplatform (la UI compartida opcional) es un proyecto separado de JetBrains, actualmente en beta para iOS.

# Estado actual (2025):
# ✓ KMP estable para Android, iOS, desktop (JVM), web (JS/Wasm)
# ✓ Ktor 3.x — cliente HTTP multiplataforma estable
# ✓ SQLDelight 2.x — base de datos multiplataforma estable
# ✓ kotlinx.serialization — estable y muy usado
# ✓ kotlinx-coroutines — multiplataforma estable
# ✓ Koin — DI multiplataforma estable
# ~ Compose Multiplatform — beta para iOS, estable para desktop

# Empresas que usan KMP en producción:
# Netflix, VMware, Philips, Cash App, Touchlab (consultora KMP)

# El riesgo real hoy no es la estabilidad del framework
# sino la madurez del ecosistema de librerías:
# Algunas librerías de Android no tienen versión KMP
# (Room → reemplazar con SQLDelight, Retrofit → reemplazar con Ktor)