Etiqueta: Kotlin Multiplatform

  • Unidad 2 — Tema 1: Interoperabilidad Nativa: Cómo usar expect/actual en KMP

    Unidad 2 — Tema 1: Interoperabilidad Nativa: Cómo usar expect/actual en KMP

    En el artículo anterior de la serie (Unidad 1 — Tema 3), desglosamos el esqueleto físico del monorepo fullstack y organizamos la estructura modular de directorios. Con las carpetas en su sitio, el gran reto es lograr que el código compartido dialogue fluidamente con el hardware e infraestructura de cada sistema operativo sin pagar penalizaciones de rendimiento.

    En el desarrollo multiplataforma, el Santo Grial es maximizar la reutilización de código sin perder el acceso a las capacidades nativas de cada sistema operativo. Kotlin Multiplatform (KMP) aborda este desafío de una manera radicalmente distinta a otros frameworks que introducen capas de abstracción pesadas o puentes de serialización en tiempo de ejecución. KMP prefiere resolver la interoperabilidad en tiempo de compilación.

    Cuando el compilador de Kotlin genera los binarios para Android (JVM) e iOS (Apple Silicon/Intel), necesita un mecanismo seguro para enlazar el código común con las APIs nativas de cada plataforma. Aquí es donde entra el mecanismo de expect/actual. No se trata de un simple reflejo o una inyección de dependencias tradicional en tiempo de ejecución; es un contrato estricto a nivel de compilador que nos permite declarar «expectativas» en nuestro código compartido y exigir «realidades» en el código de la plataforma. En este artículo, analizaremos a fondo cómo funciona este ciclo, sus reglas a nivel de arquitectura y cómo implementarlo de forma eficiente.

    1. El Mecanismo Expect/Actual a Nivel de Compilador

    A diferencia de los patrones de diseño tradicionales donde las abstracciones se resuelven dinámicamente, el par expect/actual funciona como un enlace en tiempo de compilación. Cuando declaras un elemento con la palabra clave expect en el source set commonMain, estás definiendo un contrato topológico. El compilador de Kotlin no generará el artefacto final a menos que cada una de las plataformas destino (como androidMain e iosMain) provea una implementación idéntica marcada con actual.

    Es importante entender que expect no es una interfaz. Mientras que una interfaz define un comportamiento que una clase debe implementar en tiempo de ejecución, expect puede aplicarse a clases, funciones, propiedades, enumerados o funciones de extensión. El compilador valida que las firmas (nombre, parámetros, tipos de retorno y modificadores de visibilidad) sean compatibles entre la declaración expect y las declaraciones actual. Si hay una discrepancia no permitida, la compilación fallará antes de generar cualquier binario.

    2. Reglas Estrictas y Restricciones de Visibilidad

    Para dominar este mecanismo, es crucial comprender las restricciones que impone el compilador de Kotlin para garantizar la seguridad de tipos:

    • Coincidencia de Firmas: Los nombres de los parámetros y sus tipos deben ser exactamente los mismos. No se permite cambiar el nombre de un argumento en la implementación actual, ya que esto rompería la resolución de argumentos con nombre (named arguments) en el código común.
    • Modificadores de Visibilidad: La declaración actual debe tener una visibilidad compatible con la expect. En Kotlin actual, eso significa que puede mantener la misma visibilidad o incluso ser más permisiva que el contrato esperado; lo que no puede hacer es ser más restrictiva y dejar inaccesible algo que el código común esperaba usar.
    • Restricción de Paquetes: Tanto la declaración expect como la actual deben residir exactamente en el mismo paquete (package) dentro de sus respectivos source sets. El compilador utiliza el nombre completamente cualificado (FQCN) para emparejarlos durante la fase de enlace.

    3. Ejemplo Práctico: Un Identificador Único de Dispositivo

    Veamos un ejemplo genérico pero de arquitectura sénior: obtener un identificador único de plataforma o el nombre del sistema operativo. Esto requiere acceder a las APIs nativas de Android e iOS, pero queremos exponer una propiedad limpia en nuestro dominio compartido.

    En commonMain (El Contrato):

    package dev.asiles.blog.core
    
    /**
     * Define el contrato para obtener información del entorno nativo.
     * El compilador garantizará que cada plataforma resuelva esta propiedad.
     */
    public expect class PlatformInfo() {
        public val operatingSystemName: String
        
        public fun logDebugInfo(): Unit
    }

    En androidMain (La Realidad en Android):

    package dev.asiles.blog.core
    
    import android.os.Build
    
    public actual class PlatformInfo {
        // Usamos la API nativa de Android (SDK de Java/Android)
        public actual val operatingSystemName: String = "Android ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})"
    
        public actual fun logDebugInfo() {
            android.util.Log.d("PlatformInfo", "Running on $operatingSystemName")
        }
    }

    En iosMain (La Realidad en iOS):

    package dev.asiles.blog.core
    
    import platform.UIKit.UIDevice
    
    public actual class PlatformInfo {
        // Acceso directo a las APIs de Apple a través del interop nativo de Kotlin/Native
        public actual val operatingSystemName: String = "${UIDevice.currentDevice.systemName} ${UIDevice.currentDevice.systemVersion}"
    
        public actual fun logDebugInfo() {
            // En iOS usamos la función de impresión estándar de Kotlin/Native que mapea a la consola nativa
            println("PlatformInfo: Running on $operatingSystemName")
        }
    }

    4. Enfoque Arquitectónico: Cuándo Usarlo y Cuándo Evitarlo

    El mecanismo expect/actual es extremadamente potente, pero en la arquitectura de un sistema de nivel empresarial, debe ser tu última opción, no la primera.

    Cuándo usarlo:

    • Para construir wrappers muy delgados sobre tipos primitivos o firmas de funciones que dependen directamente de tipos de la plataforma (por ejemplo, dar formato a una fecha, interactuar con tipos atómicos o inicializar una base de datos local como SQLDelight).
    • Cuando necesitas instanciar una clase nativa cuyo constructor requiere un contexto específico de la plataforma.

    Cuándo evitarlo (y qué usar en su lugar):

    • No lo uses para lógica de negocio pesada. Si creas grandes clases expect, estarás duplicando lógica y rompiendo el propósito de KMP.
    • Alternativa superior (Inyección de Dependencias / Inversión de Control): Para componentes arquitectónicos complejos (como clientes HTTP personalizados, gestores de Bluetooth o sistemas de almacenamiento), es mucho más limpio definir una interfaz estándar de Kotlin (interface) en commonMain e inyectar las implementaciones nativas mediante un framework de DI como Koin o mediante inyección manual en el punto de entrada de la aplicación. Esto facilita los tests unitarios en commonMain mediante el uso de dobles de prueba (mocks/fakes) sin depender del mecanismo de compilación.

    5. Type Aliasing: El Truco Avanzado de Rendimiento

    Existe una característica avanzada dentro del ecosistema actual que los desarrolladores senior deben aprovechar: los actual typealias. Si una plataforma ya tiene un objeto que cumple perfectamente con el contrato que necesitas, no es necesario crear una clase envoltorio (wrapper) que añada sobrecarga en tiempo de ejecución. Puedes simplemente mapearlo.

    Por ejemplo, si necesitas un tipo de dato para manejar UUIDs de forma nativa, puedes declarar un expect class UUID en el código común. En androidMain, en lugar de reimplementar la clase, puedes hacer:

    // En androidMain
    public actual typealias UUID = java.util.UUID

    Esto le dice al compilador que, cuando compile para Android, reemplace directamente cualquier uso de tu UUID compartido por la clase nativa optimizada de Java, eliminando cualquier capa de abstracción intermitente y manteniendo el rendimiento nativo al 100%.

    Para profundizar

    Para contrastar estos conceptos arquitectónicos con las especificaciones técnicas vigentes, recomiendo encarecially la lectura de los siguientes recursos de la documentación oficial de JetBrains:

    Conclusión

    El mecanismo expect/actual es uno de los pilares que hacen de Kotlin Multiplatform una tecnología tan robusta y respetuosa con el rendimiento nativo. Al delegar la resolución de contratos al tiempo de compilación en lugar de pagar el costo de rendimiento en tiempo de ejecución, KMP nos permite estructurar aplicaciones altamente optimizadas.

    Como buena práctica senior, mantén tus declaraciones expect lo más minimalistas posible. Trátalas como los puntos de anclaje de tu infraestructura y construye el resto de tu arquitectura (interfaces, casos de uso y ViewModels) sobre abstracciones puras en commonMain. Esto garantizará que tu base de código sea escalable, fácil de testear y preparada para añadir nuevas plataformas en el futuro con un esfuerzo mínimo.

    Una vez consolidado el plano teórico y las reglas de visibilidad del compilador, es hora de poner en práctica este mecanismo en un escenario real de producción. En el próximo artículo, Unidad 2 — Tema 2: Escribiendo código específico de plataforma: Obteniendo información del dispositivo en iOS y Android, nos arremangaremos para implementar un lector de telemetría de hardware conectando con las APIs nativas de Android SDK y CocoaTouch.

  • Unidad 1 — Tema 3: Estructura de un proyecto KMP: ¿Dónde vive mi código compartido?

    Unidad 1 — Tema 3: Estructura de un proyecto KMP: ¿Dónde vive mi código compartido?

    En el artículo anterior de la serie (Unidad 1 — Tema 2), pusimos a punto nuestro entorno de desarrollo y logramos tener listos Xcode, Android Studio y KDoctor para compilar en cualquier plataforma. Con la máquina ya configurada, es hora de dar el primer paso práctico: crear nuestro proyecto. Al abrir el repositorio multiplataforma por primera vez, es normal sentir cierto desconcierto.

    Si vienes del desarrollo web fullstack o de entornos backend distribuidos, estás acostumbrado a mitigar un problema histórico: la sincronización de contratos. Mantener los modelos de datos de tu API en TypeScript mapeados idénticamente con tus entidades del backend en Java, C# o Go suele requerir generadores de código o un mantenimiento manual propenso a fallos. Al aterrizar en un proyecto Kotlin Multiplatform (KMP) fullstack, la primera fricción mental desaparece al abrir el repositorio: no estamos ante una simple app móvil, sino ante un monorepo Gradle nativo que orquesta el backend, el navegador y los clientes nativos bajo una única fuente de verdad.

    El error más común al empezar con KMP es heredar los vicios de las arquitecturas móviles simples. Cuando el Kotlin Multiplatform Wizard oficial genera un proyecto con soporte para Servidor, Web, Desktop y Móvil, estructura el árbol de directorios de forma modular para escalar limpiamente. En este artículo vamos a destripar un ejemplo representativo de proyecto fullstack, entendiendo la separación de responsabilidades entre submódulos y cómo las entrañas de Gradle configuran el grafo de dependencias.

    1. El Grafo de SourceSets: La jerarquía del código multiplataforma

    A diferencia de un proyecto de software tradicional con su clásico src/main/kotlin, KMP introduce el concepto de SourceSets. Un SourceSet es una agrupación de código fuente, recursos y dependencias que apunta a un objetivo de compilación específico o a una abstracción intermedia en el árbol de compilación.

    El motor que orquesta esta magia es la configuración de estructura jerárquica (Hierarchical Structure Support) introducida por el plugin multiplataforma de Kotlin. En la cúspide de esta pirámide se encuentra el denominador común: commonMain.

    • commonMain: Es el núcleo agnóstico al entorno de ejecución. El código aquí escrito se compila usando el compilador común de Kotlin y solo puede acceder a la librería estándar y a dependencias multiplataforma puras (Ktor, kotlinx.serialization). No hay acceso a java.*, javax.*, ni a las APIs nativas del navegador o de iOS.
    • Plataformización dirigida: A medida que descendemos en el grafo, el compilador especializa el entorno. En los targets basados en la JVM (como el servidor o el escritorio), tienes acceso total al ecosistema Java. En los targets de Apple, el compilador Kotlin/Native expone los frameworks del sistema (Foundation, UIKit). Y en el target Web, el compilador genera JavaScript o WebAssembly (Wasm), dándote acceso directo al DOM de la página.

    2. Anatomía de la raíz: Desglosando el Monorepo Fullstack

    Al configurar un proyecto KMP que abarca todo el stack tecnológico, el Wizard organiza el código aislando los entornos de ejecución clientes dentro de un directorio paraguas y segregando la lógica pura. La estructura exacta puede variar según la versión del wizard y las opciones marcadas, así que el siguiente árbol debe leerse como un ejemplo posible y realista, no como una salida inmutable o literal de todas las versiones:

    ├─ app/                  # Capa de Presentación de los Clientes (Frontend)
    │   ├─ androidApp/       # Módulo Gradle: Punto de entrada para Android
    │   ├─ desktopApp/       # Módulo Gradle: Punto de entrada para Escritorio (JVM)
    │   ├─ iosApp/           # Proyecto Xcode nativo (Swift / SwiftUI)
    │   ├─ webApp/           # Módulo Gradle: Configuración para Kotlin/Wasm o Kotlin/JS
    │   └─ shared/           # UI Compartida (Compose Multiplatform) para todos los clientes
    ├─ core/                 # Lógica de negocio pura compartida entre CLIENTE y SERVIDOR
    ├─ server/               # Backend de la aplicación (Ktor / Entorno JVM)
    └─ gradle/
        └─ libs.versions.toml # Catálogo centralizado de dependencias (Version Catalog)

    El contenedor app/ y la UI compartida

    Las aplicaciones androidApp, desktopApp, iosApp y webApp suelen actuar como cascarones de inicialización. En una variante basada en Compose Multiplatform, la lógica de UI real (pantallas, componentes visuales, estados de vista) puede residir en app/shared/src/commonMain. Pero esa no es la única opción: otros proyectos mantienen toda la UI nativa en cada cliente y comparten solo datos y dominio. Lo importante es entender la responsabilidad de cada módulo, no memorizar un árbol cerrado.

    El módulo core/ (La frontera Fullstack)

    Este es el componente más crítico del sistema. Es un módulo KMP puro (sin dependencias de interfaz de usuario). Su único objetivo es albergar los modelos de datos (DTOs), reglas de validación comunes y configuraciones de red que consumirá tanto la UI en el cliente como el Backend en el servidor.

    3. Configuración del sistema de módulos en Gradle

    Para que este monorepo funcione sin acoplamientos cíclicos, la orquestación en los archivos de configuración de Gradle debe ser limpia y rigurosa. Todo comienza en la raíz del proyecto, donde le indicamos al motor de construcción la estructura modular.

    A nivel de código práctico, la vinculación jerárquica se declara en el archivo settings.gradle.kts:

    // settings.gradle.kts (Raíz del proyecto)
    rootProject.name = "Demo"
    
    // Módulos base independientes
    include(":core")
    include(":server")
    
    // Módulos anidados bajo el contenedor de clientes (app)
    include(":app:shared")
    include(":app:androidApp")
    include(":app:desktopApp")
    include(":app:webApp")
    

    La verdadera ventaja competitiva de este diseño se aprecia en cómo el módulo server consume al módulo core como una dependencia local interna. Al ser un entorno backend (por ejemplo, usando Ktor), su archivo build.gradle.kts se simplifica drásticamente:

    // server/build.gradle.kts
    plugins {
        alias(libs.plugins.kotlinJvm)
        alias(libs.plugins.ktor) // Plugin oficial de Ktor Server
    }
    
    dependencies {
        // Inyección directa de la lógica de negocio y contratos comunes
        api(projects.core)
        
        // Infraestructura del servidor
        implementation(libs.logback)
        implementation(libs.ktor.serverCore)
        implementation(libs.ktor.serverNetty) // Motor de ejecución embebido
    }

    4. Enfoque Arquitectónico Sénior: El Flujo de Datos Unificado

    Diseñar un monorepo fullstack con KMP introduce ventajas operativas enormes, pero exige disciplina arquitectónica para no convertir el proyecto en un monolito acoplado indescifrable.

    El flujo ideal del dato Compartido

    1. Una entidad de negocio UserSession se define en el módulo :core.
    2. El módulo :server procesa la autenticación, serializa UserSession a JSON y la expone en un endpoint.
    3. El módulo :app:shared (Frontend) realiza la petición HTTP a través de Ktor Client, recibe el JSON y, al tener :core mapeado como dependencia, deserializa automáticamente el flujo a la clase exacta UserSession. No hay mapeos intermedios ni archivos autogenerados en TypeScript.

    Cuándo evitar la compartición absoluta y usar aislamiento

    • Arquitecturas Backend complejas: Si tu servidor necesita conectarse a bases de datos relacionales vía Hibernate/Exposed o interactuar con colas de mensajería (Kafka, RabbitMQ), ese código jamás debe rozar el módulo :core. El módulo :core debe permanecer estéril, limitado a estructuras de datos puras y lógica computacional agnóstica.
    • Alternativas en Frontend: Si el rendimiento visual en la web es crítico y necesitas SEO indexable por motores de búsqueda tradicionales, forzar Compose Multiplatform vía WebAssembly en app/webApp puede ser contraproducente, ya que renderiza la UI en un <canvas>. Una alternativa senior es usar KMP en la web solo para la capa de datos (:core) y acoplarla a un framework JS nativo mediante Kotlin/JS.

    5. El proceso de entrega: Compilación multiplataforma en tiempo de ejecución

    Tener todos los módulos bajo el mismo paraguas de Gradle no significa que se compilen en un único ejecutable. El flujo de compilación segmenta los artefactos según sus naturalezas nativas:

    • Backend (:server): El compilador ejecuta kotlinJvm. Compila el código del servidor y el de :core en archivos .class estándar de Java, listos para ser empaquetados en un Fat JAR ejecutable sobre cualquier JRE 17 o superior.
    • Navegador (:app:webApp): El compilador traduce el código Kotlin a binarios de WebAssembly (.wasm) y genera un pegamento mínimo en JavaScript (.js) junto al archivo HTML index, optimizando el tamaño del bundle mediante el DCE (Dead Code Elimination).
    • Móviles (:app:androidApp y :app:iosApp): Android genera su bytecode intermedio optimizado por R8/D8, mientras que el pipeline de iOS genera el framework binario de Kotlin/Native que después enlaza Xcode. La herramienta cinterop no se usa para «encapsular» tu framework exportado, sino para generar bindings de Kotlin a librerías nativas externas cuando necesitas consumir APIs de C u Objective-C desde el código Kotlin.

    Para profundizar

    Para estudiar a fondo el comportamiento de los entornos de ejecución web, la integración de redes y la estructura modular oficial del ecosistema, consulta los portales de referencia:

    Conclusión

    El Wizard fullstack de Kotlin Multiplatform nos demuestra que el lenguaje ha madurado hacia una solución de ingeniería de software global. Al separar radicalmente la lógica de presentación de los clientes de las reglas e intercambio de datos del negocio, se consigue un desacoplamiento que respira las mejores prácticas de la arquitectura limpia. Mantener el módulo core libre de dependencias de plataforma es la mayor garantía de rendimiento y mantenibilidad que un arquitecto de software puede asegurar en este ecosistema.

    En el próximo artículo, Unidad 2 — Tema 1: Interoperabilidad Nativa: Cómo usar expect/actual en KMP, daremos el salto a la acción escribiendo nuestro primer código multiplataforma y aprendiendo cómo invocar de manera segura APIs del sistema nativo de Android e iOS utilizando las palabras clave expect y actual.

  • Unidad 1 — Tema 2: Configura tu entorno KMP sin morir en el intento

    Unidad 1 — Tema 2: Configura tu entorno KMP sin morir en el intento

    En el artículo anterior de la serie (Unidad 1 — Tema 1), exploramos por qué Kotlin Multiplatform no es otro clon de Flutter o React Native, sino un enfoque enfocado en compartir lógica de negocio pura conservando interfaces nativas. Sin embargo, antes de poder saborear los beneficios de esta arquitectura, hay un peaje inevitable que pagar: poner en marcha nuestras herramientas de trabajo.

    Todos hemos estado ahí. Encuentras una nueva tecnología espectacular, te entusiasmas con la documentación, decides crear tu primer proyecto y… ¡pum! Cientos de errores de dependencias, variables de entorno que faltan y problemas de compatibilidad. Es el famoso «funciona en mi máquina», pero al revés.

    Configurar un entorno de desarrollo para Kotlin Multiplatform (KMP) solía dar algo de respeto porque obliga a convivir a dos gigantes que históricamente se han llevado mal: el ecosistema de Google (Android) y el de Apple (iOS).

    Afortunadamente, el entorno ha madurado al máximo y hoy tenemos asistentes oficiales, plantillas modernas y herramientas de diagnóstico que ayudan mucho cuando algo falla. Vamos a configurar tu entorno The Right Way (de la forma correcta).

    1. Prerrequisito físico: El dilema del hardware

    Para desarrollar código compartido de KMP y probar la parte de Android, puedes usar Windows, Linux o macOS. Sin embargo, si quieres compilar y ejecutar la versión de iOS, la regla de Apple sigue siendo estricta: necesitas una Mac. El compilador de iOS (Xcode) solo corre sobre macOS.

    2. Las herramientas base (La interfaz visual)

    Para tener el entorno definitivo, necesitamos instalar tres piezas de software esenciales. Asegúrate de tenerlas actualizadas:

    • Android Studio: Sigue siendo el centro de control visual absoluto para los proyectos KMP. Es donde escribirás la inmensa mayoría de tu código compartido en Kotlin.
    • Xcode: Es el entorno oficial de Apple. KMP lo necesita obligatoriamente bajo el capó para usar sus compiladores y lanzar los simuladores de iPhone. Una vez instalado, ábrelo al menos una vez para aceptar los términos de la licencia e instalar sus componentes internos.
    • El Plugin de Kotlin Multiplatform: Dentro de Android Studio, ve a Settings -> Plugins, busca e instala Kotlin Multiplatform. Este plugin añade las plantillas de proyectos y te permitirá algo maravilloso: presionar el botón de «Play» en Android Studio y ejecutar la app tanto en el emulador de Android como en el simulador de iOS de forma directa.

    3. El entorno moderno: CLI y automatización

    Hoy en día no podemos configurar un entorno pensando solo en que un humano va a hacer «clic» en una pantalla. El desarrollo asistido por Inteligencia Artificial y agentes autónomos exige herramientas de línea de comandos robustas.

    Disponer de las utilidades nativas de comandos conectadas en tu sistema te permitirá ejecutar tareas Gradle de forma directa, arrancar emuladores de Android de forma autónoma desde tu terminal, instalar la app compilada desde tu módulo KMP y ejecutar pruebas automatizadas de extremo a extremo sin necesidad de mover el ratón. Tener tus rutas y herramientas CLI enlazadas en la consola es el pase de oro para la eficiencia y automatización del desarrollo moderno.

    4. Diagnóstico asistido: kdoctor

    Para evitar que pases horas adivinando por qué tu configuración falla, KDoctor sigue siendo una herramienta útil de consola que escanea tu sistema de arriba a abajo, encuentra qué le falta a tu configuración y te da pistas muy concretas para solucionarlo. Hoy no suele ser el flujo principal de entrada a KMP, porque el plugin y la documentación oficial cubren gran parte del camino, pero sí es un excelente apoyo cuando algo no cuadra.

    Cómo instalarlo y ejecutarlo (en tu terminal de Mac):

    brew install kdoctor
    kdoctor

    KDoctor analizará tu Sistema Operativo, tu versión de Java (JDK), Android Studio y Xcode. Te devolverá un reporte donde verás un check verde [✓] si todo está bien, o un error [✗] con la solución exacta en texto plano que debes copiar y pegar en tu terminal para arreglarlo. Si el asistente del plugin no te deja claro qué está fallando, aquí suele aparecer la pista que faltaba.

    5. El Checklist definitivo de variables de entorno

    Abre el archivo de configuración de tu terminal (.zshrc o .zprofile en Mac) y asegúrate de exportar correctamente las rutas del SDK, las herramientas de Java y los binarios de comandos para que tanto tú como los procesos de automatización de tu sistema puedan encontrarlos:

    export ANDROID_HOME=$HOME/Library/Android/sdk
    export JAVA_HOME=/Applications/Android Studio.app/Contents/jbr/Contents/Home
    export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin

    Para asegurar que Xcode sea totalmente accesible desde la consola por los procesos automatizados de compilación, ejecuta también este comando una vez:

    sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

    6. Enfoque Sénior: El dilema de la Mac y alternativas de compilación remota

    Tener que comprar una Mac física solo para compilar e iniciar un simulador de iOS suele ser un obstáculo para muchos desarrolladores individuales y equipos pequeños orientados a Android. A nivel profesional, existen tres alternativas viables si necesitas compilar el binario nativo de iOS (.framework) sin contar con hardware Apple local:

    • Servidores de compilación en la nube (CI/CD): Herramientas como Codemagic, GitHub Actions (utilizando runners de macOS) o Bitrise permiten compilar el módulo de iOS en cada push de Git de manera desatendida.
    • Alquiler de Macs en la nube: Servicios como MacinCloud o MacStadium proporcionan acceso vía Escritorio Remoto a equipos Mac dedicados por una tarifa mensual muy competitiva.
    • Desacoplamiento total: Si trabajas en un equipo donde solo algunos programadores desarrollan la UI de iOS, puedes distribuir el módulo KMP como un paquete precompilado de Swift Package Manager (SPM) o integrarlo con otros mecanismos soportados por el proyecto. De este modo, los desarrolladores de Swift reducen la necesidad de compilar Kotlin continuamente en sus equipos.

    Para profundizar

    Si quieres entender a fondo la configuración oficial y las herramientas recomendadas, revisa estos enlaces:

    Conclusión

    Configurar el entorno de KMP ya no es una odisea de dependencias rotas. El ecosistema actual combina asistentes en IDE, Gradle, Xcode, integración mediante Swift Package Manager cuando encaja, y herramientas de apoyo como kdoctor para diagnosticar incidencias concretas. CocoaPods sigue siendo válido en proyectos que ya lo usan o en integraciones específicas; simplemente ya no es la única ruta que suele aparecer primero en un proyecto nuevo. El resultado es un entorno moderno, más limpio y mucho más predecible.

    Ahora que tu máquina tiene el entorno idóneo y los prerrequisitos de compilación están claros, el siguiente paso lógico es crear tu primer proyecto multiplataforma. En el próximo artículo, Unidad 1 — Tema 3: Estructura de un proyecto KMP: ¿Dónde vive mi código compartido?, abriremos el capó del monorepo generado por el Gradle Wizard para entender la jerarquía de SourceSets y cómo interactúan las dependencias comunes y de plataforma.

  • Unidad 1 — Tema 1: Por qué Kotlin Multiplatform no es otro Flutter/React Native y por qué deberías usarlo

    Unidad 1 — Tema 1: Por qué Kotlin Multiplatform no es otro Flutter/React Native y por qué deberías usarlo

    En el artículo anterior de la serie (Unidad 0 — Tema 4: Programación Reactiva: Domina StateFlow y SharedFlow), consolidamos los pilares fundamentales del lenguaje explorando el flujo asíncrono caliente. Con los cimientos de Kotlin asentados, hoy nos adentramos formalmente en la Unidad 1.

    Cuando escuchas las palabras «desarrollo multiplataforma», es normal que sientas un ligero escepticismo. La mente viaja automáticamente a las soluciones clásicas: React Native o Flutter. Historias de interfaces que a veces no se sienten del todo «nativas», peleas para acceder a las últimas API del sistema o la necesidad de adoptar un ecosistema gigantesco solo para lanzar una aplicación.

    Tanto Flutter como React Native son herramientas espectaculares que han madurado muchísimo, resolviendo gran parte de sus problemas históricos de rendimiento. Sin embargo, Kotlin Multiplatform (KMP) juega en una liga completamente diferente. No es un intento de unificar cómo se dibuja una pantalla; es una estrategia quirúrgica para compartir lo que realmente importa: la lógica de negocio.

    Vamos a abrir el capó para entender por qué el enfoque de KMP es tan distinto y revolucionario.

    1. La Gran Diferencia: Compartir código vs. Compartir UI

    Para entender el valor de KMP, primero debemos entender la filosofía de las alternativas actuales:

    • React Native: Ha dejado atrás el viejo y lento «puente» asíncrono basado en JSON. Sus arquitecturas modernas permiten que el código JavaScript interactúe directamente con las APIs del sistema operativo a través de enlaces ultra-rápidos en C++. Sigue enfocado en construir toda tu aplicación y su interfaz bajo su propio ecosistema.
    • Flutter: Apuesta por el control total de los píxeles. No utiliza los componentes visuales del sistema operativo; en su lugar, incluye su propio motor gráfico de alto rendimiento que dibuja la interfaz directamente en la pantalla. Es perfecto si buscas una consistencia absoluta (píxel por píxel) en cualquier dispositivo.
    • Kotlin Multiplatform: Da un paso al lado en la batalla por la UI. KMP te dice: «Puedes conservar tu UI nativa con Jetpack Compose o Views en Android y SwiftUI o UIKit en iOS; o incluso compartir parte de la UI con Compose Multiplatform si ese trade-off encaja en tu producto. Yo me encargaré de las bases de datos, las peticiones de red, los algoritmos y la encriptación, y te los entregaré de forma nativa en cada plataforma».

    2. ¿Cómo funciona bajo el capó? La ventaja de la compilación directa

    KMP no añade un motor de renderizado multiplataforma al estilo de Flutter ni una capa puente tipo JavaScript runtime para comunicar la lógica compartida con iOS y Android. Aun así, sí depende de los runtimes nativos de cada destino, como la JVM en Android o el runtime de Kotlin/Native en Apple. Su ventaja es que el código compartido se compila al formato que cada sistema operativo espera en lugar de ejecutarse dentro de un contenedor UI adicional.

    Cuando compilas tu módulo compartido, el compilador toma dos caminos separados:

                      ┌───► Compilador Kotlin/JVM ───► Bytecode (.class) ───► Android Nativo
                      │
    Módulo Compartido ┤
    (Código Kotlin)   │
                      └───► Compilador Kotlin/Native ───► LLVM ───► Framework Nativo (.framework) ───► iOS Nativo

    El camino hacia Android: Kotlin/JVM

    Para Android, el proceso es el de toda la vida. Tu código Kotlin se compila a Bytecode de la JVM. Se integra como cualquier otra librería Kotlin del ecosistema Android y aprovecha el runtime habitual de la plataforma.

    El camino hacia iOS: Kotlin/Native

    Aquí ocurre la verdadera magia. Como iOS no entiende de máquinas virtuales de Java, el compilador utiliza la infraestructura LLVM (la misma tecnología que usa Apple para compilar Swift). Tu código se transforma en un archivo binario .framework nativo. Xcode lo lee como una librería local más, permitiendo que el desarrollador de iOS invoque tus clases y funciones desde Swift con total fluidez.

    3. Cara a cara: Tabla comparativa real

    CaracterísticaReact NativeFlutterKotlin Multiplatform (KMP)
    LenguajeJavaScript / TypeScriptDartKotlin
    Interfaz de UIComponentes del sistema modificadosMotor gráfico propio (Canvas)Normalmente nativa (Compose/Views en Android, SwiftUI/UIKit en iOS), con opción de compartir UI en algunos escenarios mediante Compose Multiplatform
    RendimientoExcelente (comunicación directa C++)Excelente (renderizado directo)Muy cercano al nativo en la lógica compartida, al compilar para cada plataforma
    Adopción GradualRequiere una arquitectura completaRequiere una arquitectura completaExtremadamente fácil (puedes compartir desde una sola función)

    4. ¿Por qué el enfoque de KMP convence a los equipos de ingeniería?

    1. Cero compromisos en la Experiencia de Usuario (UX): Un usuario de iPhone nota al instante cuando las animaciones de rebote, los menús contextuales o el lector de pantalla para accesibilidad no son los nativos de Apple. Si decides mantener la interfaz en SwiftUI y Jetpack Compose, tu app seguirá el comportamiento esperado del sistema. Y si más adelante apuestas por Compose Multiplatform, esa decisión será explícita y localizada en la capa de presentación, no impuesta por KMP como requisito.
    2. Adopción incremental única: No tienes que tirar tu aplicación actual a la basura ni convencer a tu empresa de reescribirla por completo. Puedes crear un módulo KMP hoy mismo para gestionar, por ejemplo, el sistema de caché, inyectarlo en tus apps nativas actuales y seguir expandiéndolo poco a poco.
    3. Adiós a duplicar la lógica aburrida: Las validaciones de formularios, las reglas de negocio, los cálculos de precios o la sincronización con tu API se escriben una sola vez, se prueban con tests unitarios una sola vez y se comparten en todas partes.

    5. Enfoque Sénior: Cuándo elegir KMP y cuándo evitarlo

    Como arquitecto de software, la elección de KMP frente a frameworks híbridos UI-centric (como Flutter o React Native) o un enfoque nativo clásico debe basarse en métricas de negocio e infraestructura técnica, y no en preferencias de lenguaje:

    Cuándo elegir Kotlin Multiplatform:

    • Equipos con bases de código Android ya existentes: Si ya cuentas con un desarrollo nativo en Android maduro en Kotlin, adoptar KMP requiere un esfuerzo de reescritura bajísimo. Solo debes mover tu lógica de datos y negocio a un módulo común, manteniendo el 100% de tu UI en Android sin alterar.
    • Aplicaciones con alta dependencia del hardware y el diseño del sistema: Si tu app requiere integrar APIs de bajo nivel (sensores, Bluetooth, criptografía nativa) o requiere que la UI luzca 100% nativa en Android e iOS para pasar los estándares de accesibilidad o diseño de Apple.

    Cuándo evitar Kotlin Multiplatform (Alternativas):

    • Equipos sin experiencia en desarrollo móvil nativo: Si tu equipo está compuesto puramente por desarrolladores web que no conocen Xcode, Gradle ni los ciclos de vida móviles, frameworks como React Native o Flutter tienen una curva de aprendizaje menor para lanzar una app sencilla rápidamente.
    • Consistencia absoluta pixel-perfect: Si tu cliente exige que la aplicación se renderice idénticamente al pixel en cualquier versión y plataforma sin invertir tiempo adaptando componentes nativos, el motor gráfico de Flutter (Impeller/Skia) sigue siendo superior en este escenario.

    Para profundizar

    Si quieres ver las entrañas del proceso de compilación y la filosofía detrás del proyecto, dale un vistazo a estos enlaces:

    Conclusión

    Kotlin Multiplatform no viene a sustituir el desarrollo nativo; viene a potenciarlo. Te permite enfocarte en escribir interfaces hermosas y fluidas usando las herramientas oficiales de cada casa, o decidir conscientemente cuándo compartir también la UI, mientras elimina la pesadilla de tener dos equipos intentando replicar exactamente la misma lógica en lenguajes distintos. Es, probablemente, el enfoque más pragmático y eficiente de la ingeniería de software móvil actual.

    En la próxima entrega, Unidad 1 — Tema 2: Configura tu entorno KMP sin morir en el intento, arremangaremos las camisas para instalar y enlazar las herramientas base del sistema, variables de entorno y utilizaremos KDoctor como herramienta de diagnóstico complementaria para certificar que tu entorno está listo para el desarrollo multiplataforma profesional.

  • Unidad 0 — Tema 2: Modelando datos sin errores: El poder de las Sealed Interfaces en Kotlin

    Unidad 0 — Tema 2: Modelando datos sin errores: El poder de las Sealed Interfaces en Kotlin

    En el artículo anterior de la serie (Unidad 0 — Tema 1), exploramos el escudo protector de Kotlin Null Safety y cómo sus garantías de nulidad estricta cruzan las fronteras de Android e iOS. Sin embargo, proteger variables individuales no es suficiente cuando la aplicación escala.

    Imagina diseñar una aplicación donde sea físicamente imposible representar un estado inválido. Olvídate de esos bugs extraños donde la pantalla muestra el icono de carga (loader) y, al mismo tiempo, el mensaje de error y los datos antiguos mezclados.

    En el desarrollo moderno, la arquitectura se basa en predecir estados precisos. Y en el ecosistema Kotlin, la herramienta definitiva para lograr esto son las jerarquías selladas: las Sealed Classes y Sealed Interfaces.

    Cuando trabajamos en Kotlin Multiplatform (KMP), estas estructuras se convierten en el contrato sagrado que define cómo fluyen los datos desde el código compartido hasta la interfaz de usuario en Android y iOS.

    1. ¿Qué significa que algo esté «sellado»?

    En Kotlin, anteponer la palabra clave sealed a una clase o interfaz significa restringir su árbol de herencia. Le estás diciendo al compilador: «Escucha bien, nadie fuera de este paquete/módulo puede crear subclases de esta interfaz».

    Esto le da al compilador el control total y absoluto de cuántas variantes existen de un tipo.

    ¿Sealed Class o Sealed Interface?

    Aunque nacieron en momentos distintos de la evolución de Kotlin, hoy conviven en armonía:

    • sealed class: Ideal si tus subtipos necesitan heredar comportamiento común o propiedades compartidas definidas en la propia clase base. Puede tener constructor primario, pero eso no implica constructores «mágicos» en las subclases: cada subtipo sigue declarando explícitamente cómo inicializa su propio estado.
    • sealed interface: Más ligera desde el punto de vista del modelo. Al ser una interfaz, una misma implementación puede cumplir con varios contratos a la vez. Es la opción favorita por defecto para modelar estados puros si no necesitas heredar lógica compleja.

    2. El combo perfecto: Estados de UI y Respuestas de Red

    El caso de uso estrella para las estructuras selladas es el manejo de peticiones asíncronas. En lugar de tener tres variables booleanas independientes (isLoading, hasError, data), unificamos todo en un único flujo de verdad:

    sealed interface UIState<out T> {
        object Loading : UIState<Nothing>
        data class Success<out T>(val data: T) : UIState<T>
        data class Error(val message: String) : UIState<Nothing>
    }

    Con este modelo, tu pantalla solo puede estar en uno de estos tres estados a la vez. No hay espacio para la ambigüedad.

    3. El when exhaustivo: Tu compilador es tu mejor QA

    La verdadera magia ocurre cuando combinas una interfaz sellada con la expresión when. Como el compilador conoce exactamente todas las opciones posibles, no te exige escribir una rama else.

    fun renderUI(state: UIState<String>) {
        when (state) {
            is UIState.Loading -> showLoader()
            is UIState.Success -> showContent(state.data)
            is UIState.Error -> showError(state.message)
            // ¡No hace falta poner un 'else'! El compilador sabe que no hay más opciones.
        }
    }

    💡 La ventaja competitiva: Si mañana decides añadir un nuevo estado llamado UIState.Empty, el proyecto dejará de compilar de inmediato en todos los lugares donde uses este when. El compilador te obligará a controlar el nuevo caso antes de lanzar la app, eliminando por completo los olvidos humanos.

    4. Cruzando el puente hacia iOS: ¿Qué pasa en Swift?

    Históricamente, este ha sido uno de los mayores dolores de cabeza en KMP. Como Objective-C (el puente intermedio clásico) no entiende el concepto de clases selladas, el compilador las traducía a clases normales herederas de una base. En Swift, los desarrolladores de iOS se veían obligados a escribir sentencias switch con un caso default obligatorio, perdiendo la verificación estática del compilador.

    Afortunadamente, las herramientas modernas han solucionado esto:

    El héroe actual: SKIE

    Herramientas de la comunidad altamente adoptadas como SKIE (desarrollada por Touchlab) analizan tu código Kotlin y generan automáticamente Enums de Swift con valores asociados (Enums with associated values).

    Para el desarrollador de iOS, consumir tu estado de Kotlin se siente 100% nativo:

    switch viewState {
        case .loading:
            showLoader()
        case .success(let content):
            showContent(content.data)
        case .error(let error):
            showError(error.message)
    } // ¡Exhaustivo y sin necesidad de un 'default'!

    El futuro: Swift Export

    JetBrains está desarrollando de forma nativa Swift Export, un mecanismo directo en el compilador de Kotlin que elimina el puente intermedio de Objective-C para que características potentes como los enums y las jerarquías de tipos se comuniquen de forma directa y transparente entre ambos lenguajes en los próximos años.

    5. Enfoque Sénior: Sealed Classes vs. Sealed Interfaces bajo el capó de la JVM

    Desde la perspectiva de diseño, la decisión entre sealed class y sealed interface debería apoyarse más en expresividad y restricciones del modelo que en supuestas ganancias de rendimiento difíciles de medir en la mayoría de aplicaciones.

    • Constructor y estado compartido: Una sealed class puede centralizar propiedades y lógica base en su constructor o en miembros concretos. Eso resulta útil cuando todos los subtipos comparten invariantes reales.
    • Composición de contratos: Las sealed interfaces permiten modelar estados o eventos sin imponer jerarquías de estado común, y además pueden combinarse con otras interfaces del dominio.
    • Cuándo elegir cada una: Por defecto, prefiere las Sealed Interfaces para modelar estados o eventos de la UI si solo necesitas variantes exhaustivas. Elige Sealed Classes cuando de verdad necesites compartir estado o comportamiento implementado entre subtipos.

    Para profundizar

    Si quieres ver las reglas exactas de compilación y cómo exprimir estas estructuras en tus proyectos, dale un vistazo a la documentación oficial:

    Conclusión

    Modelar tus datos usando sealed interface en un proyecto KMP eleva la calidad de todo el sistema. Estás exportando una arquitectura libre de estados imposibles que tanto el desarrollador de Android como el de iOS agradecerán profundamente. Menos condicionales defensivos, cero estados duplicados y toda la potencia del chequeo en tiempo de compilación.

    Pero modelar el estado no sirve de nada si no somos capaces de resolver de dónde proceden las peticiones asíncronas de red y bases de datos que lo mutan. En el próximo artículo, Unidad 0 — Tema 3: Hilos y Asincronía: Tu primera guía de Corrutinas en Kotlin, aprenderemos a dominar la suspensión y la concurrencia estructurada multiplataforma de forma legible y elegante.

  • Unidad 0 — Tema 1: Kotlin Null Safety: El escudo protector que hereda tu código multiplataforma

    Unidad 0 — Tema 1: Kotlin Null Safety: El escudo protector que hereda tu código multiplataforma

    Antes de sumergirte de lleno en los pipelines de compilación y la creación de tu primer monorepo (como detallamos en nuestra Guía Completa de Kotlin Multiplatform), es fundamental dominar los cimientos estables del lenguaje.

    Si vienes del desarrollo nativo en Android, probablemente ya des por sentado el Null Safety (la seguridad contra nulos) de Kotlin. Es como el cinturón de seguridad: te olvidas de que está ahí hasta que te salva de un choque con el famoso NullPointerException.

    Pero cuando entramos en el terreno de Kotlin Multiplatform (KMP), ocurre la verdadera magia: este escudo no solo te protege dentro de Kotlin, sino que también se proyecta hacia las plataformas de destino. En Swift el mapeo es muy sólido; en Java, en cambio, se traduce sobre todo en anotaciones de nulabilidad y no en una garantía absoluta de compilador.

    ¿Cómo interactúan estos mundos tan diferentes? Vamos a verlo.

    1. El breve recordatorio: La navaja suiza de Kotlin

    En Kotlin, la distinción entre un tipo que puede ser nulo y uno que no, se define desde el sistema de tipos en tiempo de compilación. No es un parche, es parte del ADN del lenguaje.

    var nombre: String = "Kotlin"  // No puede ser nulo jamás
    // nombre = null // Esto ni siquiera compila ❌
    
    var apellido: String? = null  // Puede ser nulo

    Hasta aquí, todo bajo control. Pero, ¿qué pasa cuando compilamos este código y el equipo de iOS o de Backend lo consume en sus respectivos lenguajes?

    2. El puente de interoperabilidad: De Kotlin al resto del mundo

    Cuando compilas un proyecto KMP, el compilador traduce tu código Kotlin a un framework de Objective-C (para iOS) y a Bytecode (para Java/Android). Es aquí donde el sistema de tipos de Kotlin tiene que «negociar» con las reglas de los demás.

    El mapa de traducción de tipos:

    Tipo en KotlinTraducción en Swift (iOS)Traducción en Java (Android/Backend)
    String (No nulo)String (Non-optional)@NonNull String
    String? (Nullable)String? (Optional)@Nullable String
    Int (No nulo)Int32 / Int64int (Primitivo)
    Int? (Nullable)KotlinInt? (Wrapper)Integer (Objeto)

    El caso de Swift: Un matrimonio casi perfecto

    Swift y Kotlin comparten una filosofía muy similar respecto a los nulos (en Swift se llaman Optionals). Gracias a esto, la traducción es limpísima. Si defines un String? en tu código compartido de KMP, Swift lo recibirá como un String? (u Optional<String>). El desarrollador de iOS estará obligado por su propio compilador a desenvolver (unwrap) esa variable.

    El caso de Java: El peso de la historia

    Java nació en una época donde todo objeto podía ser null. Para comunicar la intención de Kotlin, KMP inyecta anotaciones de JetBrains (@NonNull y @Nullable). Las herramientas modernas como Android Studio o IntelliJ leerán estas anotaciones y avisarán al desarrollador con un warning en el IDE si intenta pasar un nulo donde no debe, aunque técnicamente la JVM lo siga permitiendo en tiempo de ejecución. Además, en cuanto Kotlin consume APIs escritas en Java, aparecen los platform types, una zona gris donde el compilador ya no puede garantizar la nulidad con la misma fuerza.

    3. El «choque cultural»: Detalles que debes cuidar

    Aunque la traducción es automática, existen un par de situaciones donde el escudo puede agrietarse si no prestas atención:

    ⚠️ Los tipos primitivos nulos en iOS: Los tipos como Int, Boolean o Double en Kotlin son primitivos eficientes. Pero si los marcas como opcionales (Int?), Swift no puede convertirlos directamente en sus opcionales nativos de la misma manera porque los primitivos en Objective-C no manejan la nulidad igual. Kotlin los envuelve en clases especiales como KotlinInt. En el lado de Swift, te tocará lidiar con contenedores en lugar del tipo limpio.

    Ejemplo práctico en Swift:

    Si en Kotlin compartes esto:

    data class Usuario(val id: Int, val edad: Int?)

    En Swift se consumirá de una forma similar a esta:

    let usuario = ObtenerUsuario()
    print(usuario.id) // Un Int nativo normal y corriente
    print(usuario.edad) // Ojo: Aquí es un objeto de tipo KotlinInt?, requiere un trato especial

    4. Enfoque Sénior: Diseño de APIs y la mitigación de wrappers opcionales en iOS

    En proyectos de gran envergadura, forzar a los desarrolladores de iOS a desenvolver wrappers artificiales como KotlinInt? o KotlinBoolean? genera frustración y un código Swift poco natural. Un arquitecto de software sénior en KMP debe aplicar una de las siguientes dos estrategias para garantizar una API limpia en la capa común:

    • Exposición de nulos a través de tipos de referencia nativos: En lugar de declarar tipos primitivos anulables en el data class común, se pueden utilizar fachadas o modelos dedicados para la UI que expongan tipos no primitivos o encapsulados directamente en una estructura de datos nativa que Objective-C y Swift interpreten con mayor fluidez.
    • Implementación anticipada de SKIE o Swift Export: Integrar herramientas de compilación como SKIE permite que el compilador intercepte estos wrappers y los transforme automáticamente en opcionales puros de Swift durante la generación del framework, eliminando la fricción de raíz.

    Para profundizar

    Si quieres ver los detalles técnicos de cómo el compilador de Kotlin gestiona la nulidad y exporta estos componentes bajo el capó, te recomiendo revisar la documentación oficial de JetBrains:

    Conclusión

    El sistema de tipos de Kotlin es el pilar invisible de KMP. Al escribir tu lógica de negocio una sola vez, no solo compartes algoritmos; estás exportando contratos de estabilidad. En Swift esos contratos se traducen con enorme fidelidad; en Java reducen ambigüedad gracias a las anotaciones, aunque la frontera con APIs heredadas y platform types siga requiriendo criterio adicional.

    ¡Tu código, tus reglas, en cualquier plataforma!

    Una vez asentados los tipos y la seguridad contra nulos, el siguiente reto es aprender a representar estructuras complejas y flujos de estados finitos en KMP. En el próximo artículo, Unidad 0 — Tema 2: Modelando datos sin errores: El poder de las Sealed Interfaces en Kotlin, descubriremos cómo blindar nuestra UI contra estados contradictorios o imposibles de forma estricta y nativa.

  • Guía Completa de Kotlin Multiplatform (KMP)

    Guía Completa de Kotlin Multiplatform (KMP)

    ¿Estás cansado de duplicar la lógica de negocio de tus aplicaciones móviles? ¿Escribir el mismo cliente HTTP, las mismas reglas de validación y la misma arquitectura una vez en Swift para iOS y otra vez en Kotlin para Android?

    El desarrollo multiplataforma ha madurado definitivamente. Ya no estamos obligados a elegir entre el rendimiento de un binario nativo y la reutilización de código mediante frameworks que renderizan interfaces web lentas o imitan burdamente los componentes visuales nativos. Kotlin Multiplatform (KMP) ha llegado para redefinir el paradigma del desarrollo móvil: comparte tu lógica central, tus bases de datos y tus clientes de red, pero conserva el control total de la interfaz de usuario nativa si así lo deseas.

    En esta serie de guías y tutoriales avanzados, te llevaré de la mano desde los fundamentos más básicos de Kotlin y la concurrencia reactiva, pasando por la arquitectura limpia y Compose Multiplatform, hasta la puesta en producción comercial, automatización de pipelines (CI/CD) y monitoreo de crashes en dispositivos reales. ¡Bienvenido al curso definitivo «Aprendiendo KMP»!

    El Mapa de Ruta Definitivo: ¿Qué vamos a aprender?

    A continuación, tienes a tu disposición el índice interactivo del curso. Este mapa de ruta se mantendrá permanentemente actualizado. Los temas ya publicados cuentan con un enlace directo a su tutorial detallado, mientras que los restantes se irán enlazando a medida que se publiquen de forma comercial:

    Unidad 0: Fundamentos del Lenguaje y Concurrencia Reactiva

    Antes de cruzar plataformas, debemos dominar el motor de ejecución común. Estudiaremos las características exclusivas de Kotlin que sustentan toda la magia multiplataforma:

    Unidad 1: Configuración de Entorno e Inicialización de Proyectos

    Prepararemos nuestras herramientas de trabajo locales (Android Studio, Xcode y dependencias) y analizaremos cómo se distribuye y compila el software multiplataforma:

    Unidad 2: Interoperabilidad Nativa con expect/actual

    Aprenderemos a consumir APIs nativas y sensores específicos de cada sistema operativo desde nuestra base de código compartida común:

    • Tema 2.1: Interoperabilidad Nativa: Cómo usar expect/actual en KMP
    • Tema 2.2: Escribiendo código específico de plataforma: Obteniendo información del dispositivo en iOS y Android

    Unidad 3: Arquitectura Clean, Red con Ktor y Persistencia Offline-First

    El núcleo fuerte de las aplicaciones comerciales. Diseñaremos una base de datos local y llamadas de red concurrentes estructurando un patrón de repositorio limpio e indestructible:

    • Tema 3.1: Consumiendo una API real con Ktor en KMP
    • Tema 3.2: Offline-first con Room: caché local y Repository pattern
    • Tema 3.3: ViewModel + Koin + Compose: la app funciona de principio a fin

    Unidad 4: UI Unificada con Compose Multiplatform e Interoperabilidad Nativa

    Llevaremos la reutilización de código al máximo nivel maquetando pantallas visuales compartidas e incrustando componentes UIKit/SwiftUI nativos de Apple de forma transparente:

    • Tema 4.1: Compose Multiplatform: Diseña tu interfaz una vez, ejecútala en todas partes
    • Tema 4.2: Integrando SwiftUI y componentes nativos en Compose Multiplatform

    Unidad 5: Inyección de Dependencias y Estrategias de Testing de Producción

    Dotaremos a nuestra aplicación de robustez industrial, desacoplando servicios mediante inyección de dependencias y validando reglas de negocio con pruebas automatizadas veloces:

    • Tema 5.1: Inyección de dependencias limpia en KMP usando Koin
    • Tema 5.2: Testing en KMP: Asegurando tu lógica común

    Unidad 6: Concurrencia y Gestión Avanzada de Memoria en iOS

    Dominaremos el runtime de Kotlin/Native y su recolector de basura de última generación, y utilizaremos SKIE para consumir Flows reactivos como AsyncSequence en Swift nativo:

    • Tema 6.1: Entendiendo la gestión de memoria en Kotlin/Native
    • Tema 6.2: Consumiendo Kotlin Flows en Swift de forma nativa y elegante

    Unidad 7: Migración e Integración en Proyectos Corporativos Existentes

    Aprenderemos a empaquetar código KMP en formato Swift Package Manager (SPM) y diseñaremos una hoja de ruta segura para migrar apps nativas heredadas sin reescrituras de riesgo:

    • Tema 7.1: Integra KMP en tu app iOS existente como un Swift Package
    • Tema 7.2: Migración paso a paso: Convierte tu app nativa en Multiplataforma

    Unidad 8: Automatización de Compilación (CI/CD) y Monitoreo Unificado

    Estructuraremos workflows duales en GitHub Actions optimizando costes de ejecución y configuraremos CrashKiOS para registrar excepciones de Kotlin legibles en la consola de Firebase:

    • Tema 8.1: Automatizando KMP: Configura tu CI/CD dual en GitHub Actions
    • Tema 8.2: Monitoreo Multiplataforma: Centralizando analíticas y crashes

    Unidad 9: Publicación Comercial y Cumplimiento de Políticas en Tiendas

    Generaremos archivos de producción óptimos, configuraremos el obligatorio Privacy Manifest de iOS y controlaremos el inicio asíncrono seguro para superar las auditorías de Apple y Google:

    • Tema 9.1: Publicación en Android: De Gradle a la Google Play Console
    • Tema 9.2: Publicación en iOS: Distribuye tu app KMP usando TestFlight
    • Tema 9.3: Lanzamiento Exitoso: Cómo superar la revisión de las App Stores

    ¿A quién va dirigida esta serie formativa?

    • Desarrolladores Android que desean expandir su alcance hacia el desarrollo de Apple reutilizando su dominio del lenguaje Kotlin.
    • Desarrolladores iOS que buscan acelerar la entrega de lógica empresarial compartida sin renunciar a la excelencia de las interfaces nativas escritas en SwiftUI o UIKit.
    • Arquitectos de Software, Tech Leads y Managers que demandan una tecnología móvil unificada, sostenible y de bajísimo riesgo a largo plazo respaldada oficialmente por líderes tecnológicos como Google y JetBrains.

    ¿Estás preparado para dar un salto cualitativo en tu trayectoria y aprender a diseñar sistemas multiplataforma indestructibles? ¡Comencemos la andadura con la Unidad 0!