<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>CursosDroid · Artículos</title>
    <link>https://cursosdroid.com/articles</link>
    <atom:link href="https://cursosdroid.com/rss.xml" rel="self" type="application/rss+xml" />
    <description>Artículos sobre desarrollo Android y Kotlin Multiplatform en CursosDroid: guías, trucos y proyectos prácticos.</description>
    <language>es-ES</language>
    <lastBuildDate>Thu, 02 Jul 2026 11:56:02 GMT</lastBuildDate>
  <item>
    <title>Flutter, React Native vs. Kotlin Multiplatform: cómo elegir sin caer en la guerra de frameworks</title>
    <link>https://cursosdroid.com/articles/flutter-react-native-vs-kotlin-multiplatform-como-elegir-sin-caer-en-la-guerra-de-frameworks</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/flutter-react-native-vs-kotlin-multiplatform-como-elegir-sin-caer-en-la-guerra-de-frameworks</guid>
    <pubDate>Thu, 02 Jul 2026 11:56:02 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>Flutter, React Native y Kotlin Multiplatform resuelven el desarrollo multiplataforma desde enfoques muy distintos. La mejor elección depende de cuánto código quieras compartir, qué experiencia nativa necesitas y las habilidades de tu equipo.</description>
    <category>Android</category>
    <category>Kotlin Multiplatform</category>
    <category>Flutter</category>
    <category>React Native</category>
    <content:encoded><![CDATA[<p>El debate entre Flutter, React Native y Kotlin Multiplatform suele plantearse como una competición con un ganador absoluto. Pero la pregunta útil no es cuál es “mejor”, sino cuál encaja con la arquitectura, el producto y el equipo que tienes delante.</p>
<p>Las tres tecnologías permiten reducir trabajo duplicado entre Android e iOS, pero lo hacen con filosofías diferentes. Flutter propone controlar casi toda la interfaz desde su propio motor. React Native combina JavaScript o TypeScript con componentes nativos. Kotlin Multiplatform comparte código Kotlin sin obligarte a renunciar a una interfaz nativa por plataforma.</p>
<h2>Tres enfoques para un mismo problema</h2>
<p>Flutter permite construir aplicaciones multiplataforma desde una única base de código en Dart. Su interfaz se compone de widgets y se renderiza mediante su propio framework gráfico, lo que favorece una apariencia consistente entre plataformas. (<a href="https://docs.flutter.dev/resources/architectural-overview?utm_source=chatgpt.com" title="Flutter architectural overview">Documentación de Flutter</a>)</p>
<p>React Native parte del ecosistema React. La interfaz se declara con JavaScript o TypeScript, pero se apoya en vistas nativas para representar los componentes. Su arquitectura moderna incorpora Fabric, TurboModules y Codegen para mejorar la comunicación entre la capa JavaScript y el código nativo. (<a href="https://reactnative.dev/architecture/landing-page?utm_source=chatgpt.com" title="About the New Architecture">reactnative.dev</a>)</p>
<p>Kotlin Multiplatform, en cambio, no es exclusivamente un framework de interfaz. Su propuesta principal es compartir lógica de negocio, modelos, red, persistencia y casos de uso en Kotlin, manteniendo interfaces nativas con Jetpack Compose en Android y SwiftUI o UIKit en iOS cuando sea necesario. Compose Multiplatform permite compartir también la UI, pero es una decisión opcional. (<a href="https://kotlinlang.org/multiplatform/?utm_source=chatgpt.com" title="Kotlin Multiplatform – Build Cross-Platform Apps">Kotlin</a>)</p>
<h2>Flutter: una experiencia visual uniforme</h2>
<p>Flutter destaca cuando el producto necesita una interfaz muy personalizada y homogénea en Android e iOS. Al no depender de que cada plataforma represente cada componente de la misma forma, resulta más sencillo mantener animaciones, layouts y estilos coherentes.</p>
<p>También es una opción atractiva para equipos pequeños que quieren lanzar un producto multiplataforma rápido, especialmente si no tienen una base Android o iOS existente que deban conservar. Flutter soporta además objetivos como web y escritorio desde el mismo ecosistema, aunque cada plataforma debe evaluarse según las necesidades reales del producto. (<a href="https://docs.flutter.dev/reference/supported-platforms?utm_source=chatgpt.com" title="Supported deployment platforms">Documentación de Flutter</a>)</p>
<p>Su principal coste aparece cuando necesitas integrarte profundamente con APIs específicas del sistema o con SDKs nativos poco habituales. Hay paquetes para muchas integraciones, pero cualquier carencia puede obligar a escribir código en Kotlin, Java, Swift u Objective-C mediante canales de plataforma.</p>
<h2>React Native: aprovechar el talento web</h2>
<p>React Native tiene una ventaja clara: acerca el desarrollo móvil a equipos que ya dominan React, TypeScript y las prácticas del frontend web. Para una organización con una base sólida de desarrolladores JavaScript, la curva de aprendizaje puede ser menor que adoptar Dart o Kotlin Multiplatform.</p>
<p>La propuesta no significa que Android e iOS desaparezcan. Las aplicaciones reales siguen necesitando configuración nativa, compilación con Gradle y Xcode, permisos, notificaciones, analítica y, en algunos casos, módulos propios. La nueva arquitectura reduce limitaciones históricas del puente tradicional, pero también introduce requisitos de compatibilidad que las librerías del ecosistema deben adoptar. (<a href="https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here?utm_source=chatgpt.com" title="New Architecture is here">reactnative.dev</a>)</p>
<p>React Native es una buena elección cuando la velocidad de desarrollo depende de reutilizar conocimiento web y el producto no exige un control extremo sobre cada detalle visual o cada API nativa desde el primer día.</p>
<h2>Kotlin Multiplatform: compartir sin sustituir lo nativo</h2>
<p>Kotlin Multiplatform es especialmente interesante para equipos Android que ya usan Kotlin, arquitectura por capas, coroutines y bibliotecas del ecosistema JetBrains. Permite compartir el núcleo de una aplicación sin obligar a que Android e iOS se comporten como la misma plataforma.</p>
<p>Un caso típico es mantener una UI con Jetpack Compose en Android y SwiftUI en iOS, mientras se comparten modelos, reglas de negocio, autenticación, clientes HTTP y repositorios. Así, cada plataforma puede respetar sus patrones y convenciones de interacción sin duplicar la lógica crítica.</p>
<pre><code class="language-kotlin">// commonMain
data class UserProfile(
    val id: String,
    val name: String,
    val isPremium: Boolean
)

class ProfileUseCase(
    private val repository: ProfileRepository
) {
    suspend fun loadProfile(userId: String): UserProfile {
        return repository.getProfile(userId)
    }

    fun canAccessDownloads(profile: UserProfile): Boolean {
        return profile.isPremium
    }
}

</code></pre>
<p>Este enfoque es útil cuando una app Android madura necesita llegar a iOS de forma progresiva. No es necesario reescribir todo: puedes empezar compartiendo una capa concreta y ampliar la adopción cuando el equipo y la arquitectura estén preparados. La documentación oficial recomienda separar la lógica compartida de la UI compartida cuando alguna plataforma mantendrá una interfaz nativa. (<a href="https://kotlinlang.org/docs/multiplatform/multiplatform-project-recommended-structure.html?utm_source=chatgpt.com" title="Recommended Kotlin Multiplatform project structure">Kotlin</a>)</p>
<p>Compose Multiplatform amplía la reutilización hacia la interfaz. Actualmente es estable para Android, iOS y escritorio, mientras que el objetivo web continúa en beta. (<a href="https://kotlinlang.org/multiplatform/?utm_source=chatgpt.com" title="Kotlin Multiplatform – Build Cross-Platform Apps">Kotlin</a>)</p>
<h2>Rendimiento y experiencia nativa</h2>
<p>No conviene resumir el rendimiento en una tabla de “rápido” o “lento”. En las tres opciones, una aplicación puede ofrecer una experiencia excelente o presentar problemas si la arquitectura, las listas, las imágenes y el trabajo en segundo plano se diseñan mal.</p>
<p>Flutter controla su pipeline de renderizado y su motor Impeller busca evitar bloqueos asociados a la compilación de shaders durante animaciones e interacciones. (<a href="https://docs.flutter.dev/perf/impeller?utm_source=chatgpt.com" title="Impeller rendering engine">Documentación de Flutter</a>) React Native representa componentes nativos y ha evolucionado su arquitectura para mejorar la coordinación entre JavaScript y las capas de plataforma. Kotlin Multiplatform compila código para cada destino y permite conservar UI nativa o compartirla con Compose Multiplatform. (<a href="https://reactnative.dev/architecture/landing-page?utm_source=chatgpt.com" title="About the New Architecture">reactnative.dev</a>)</p>
<p>La experiencia más “nativa” no depende solo de la tecnología. Depende de respetar patrones de navegación, accesibilidad, tipografía, gestos y expectativas propias de Android e iOS.</p>
<h2>Una decisión práctica</h2>
<p>La siguiente guía puede servir como punto de partida:</p>
<table>
<thead>
<tr>
<th>Situación</th>
<th>Opción más natural</th>
</tr>
</thead>
<tbody>
<tr>
<td>Producto nuevo con UI muy personalizada y un único diseño visual</td>
<td>Flutter</td>
</tr>
<tr>
<td>Equipo con experiencia fuerte en React y TypeScript</td>
<td>React Native</td>
</tr>
<tr>
<td>App Android existente que necesita compartir lógica con iOS</td>
<td>Kotlin Multiplatform</td>
</tr>
<tr>
<td>Requisitos de UI realmente nativa en ambas plataformas</td>
<td>Kotlin Multiplatform</td>
</tr>
<tr>
<td>Necesidad de máxima reutilización de UI móvil y escritorio</td>
<td>Flutter o Compose Multiplatform</td>
</tr>
<tr>
<td>Integración frecuente con SDKs y APIs específicas de Android e iOS</td>
<td>Kotlin Multiplatform</td>
</tr>
</tbody>
</table>
<h2>Conclusión</h2>
<p>Flutter, React Native y Kotlin Multiplatform son soluciones válidas, pero no intercambiables. Flutter prioriza consistencia visual y control del rendering. React Native aprovecha el ecosistema web y React. Kotlin Multiplatform prioriza compartir código estratégico sin perder la libertad de construir experiencias nativas.</p>
<p>Para un equipo Android, Kotlin Multiplatform suele ser la opción más alineada cuando ya existe una app, una arquitectura Kotlin consolidada o una necesidad real de preservar la identidad de cada plataforma. Para un producto nuevo, sin legado y con una interfaz altamente personalizada, Flutter puede acelerar la salida al mercado. Y cuando la empresa vive en React, React Native mantiene una propuesta difícil de ignorar.</p>
<p>La elección correcta no es la que comparte más código: es la que reduce complejidad sin comprometer el producto.</p>]]></content:encoded>
  </item>
  <item>
    <title>Agent Development Kit for Android: cuándo usar ADK y cómo integrar agentes de IA en tu app</title>
    <link>https://cursosdroid.com/articles/agent-development-kit-for-android-cuando-usar-adk-y-como-integrar-agentes-de-ia-en-tu-app</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/agent-development-kit-for-android-cuando-usar-adk-y-como-integrar-agentes-de-ia-en-tu-app</guid>
    <pubDate>Thu, 02 Jul 2026 09:32:22 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>ADK for Android permite crear agentes de IA directamente en aplicaciones Android con Kotlin o Java. Aprende cuándo aporta valor, cómo elegir entre ejecución local, cloud o híbrida, y cómo montar una primera integración.</description>
    <category>Android</category>
    <category>Kotlin</category>
    <category>Inteligencia Artificial</category>
    <category>ADK</category>
    <content:encoded><![CDATA[<p>El Agent Development Kit for Android, o ADK, es un framework para construir agentes de IA dentro de una aplicación Android. No se limita a enviar un prompt y mostrar una respuesta: permite definir instrucciones, gestionar sesiones, coordinar varios agentes y conectar capacidades de la app como herramientas invocables por el modelo.</p>
<p>La biblioteca está diseñada para Kotlin y Java, incorpora dependencias específicas para Android y permite ejecutar modelos en el dispositivo mediante Gemini Nano y las APIs de ML Kit GenAI. También puede trabajar con modelos cloud o combinar ambos enfoques en una arquitectura híbrida. (<a href="https://developer.android.com/ai/adk" title="Build ADK agents for Android  |  AI  |  Android Developers">Android Developers</a>)</p>
<h2>Qué es exactamente un agente con ADK</h2>
<p>Un agente es una unidad de software que recibe una intención del usuario, interpreta el contexto, decide qué pasos necesita ejecutar y genera una respuesta. Para hacerlo, puede apoyarse en un modelo de lenguaje, herramientas de tu aplicación y otros agentes especializados.</p>
<p>Por ejemplo, una app de viajes podría tener:</p>
<ul>
<li>Un agente principal que entiende la petición del usuario.</li>
<li>Un agente local que resume reservas ya almacenadas en el dispositivo.</li>
<li>Un agente cloud que propone un itinerario.</li>
<li>Herramientas para consultar vuelos, guardar favoritos o crear recordatorios.</li>
</ul>
<p>ADK aporta la estructura para organizar esa colaboración. En lugar de repartir lógica de prompts, estados y llamadas a servicios por distintos <code>ViewModel</code>, puedes concentrar la responsabilidad en una capa de agentes.</p>
<h2>Cuándo conviene integrarlo en una app Android</h2>
<p>ADK resulta útil cuando la IA necesita <strong>razonar sobre una petición</strong>, consultar información contextual o ejecutar varias acciones coordinadas.</p>
<p>Algunos casos claros son:</p>
<table>
<thead>
<tr>
<th>Caso de uso</th>
<th>Por qué ADK encaja</th>
</tr>
</thead>
<tbody>
<tr>
<td>Asistente de productividad</td>
<td>Puede interpretar órdenes, consultar tareas y generar planes de acción.</td>
</tr>
<tr>
<td>Búsqueda semántica en contenido propio</td>
<td>Combina contexto de la app, herramientas de búsqueda y respuestas generadas.</td>
</tr>
<tr>
<td>Soporte al cliente dentro de la app</td>
<td>Mantiene una sesión conversacional y deriva consultas a herramientas o agentes especializados.</td>
</tr>
<tr>
<td>Apps con datos sensibles</td>
<td>Permite usar modelos en el dispositivo para procesar contenido sin enviarlo a un servidor.</td>
</tr>
<tr>
<td>Flujos complejos</td>
<td>Coordina pasos como clasificar, resumir, validar y presentar una respuesta final.</td>
</tr>
</tbody>
</table>
<p>No es la mejor opción para cada funcionalidad de IA. Si solo necesitas resumir un texto, corregir una frase o clasificar una imagen, una API específica de ML Kit puede ser más simple y predecible. ADK merece la pena cuando hay decisiones, contexto, herramientas o varios pasos.</p>
<h2>Ejecución local, cloud o híbrida</h2>
<p>La decisión más importante no es el prompt, sino dónde se ejecuta la inferencia.</p>
<h3>Modelo en el dispositivo</h3>
<p>Con Gemini Nano a través de ML Kit GenAI, la inferencia puede realizarse sin conexión y sin enviar el contenido a la nube. Esto reduce la latencia de red, mejora la privacidad y evita costes por petición, aunque el rendimiento depende del hardware y de la disponibilidad del modelo en cada dispositivo. (<a href="https://developer.android.com/ai/gemini-nano" title="Gemini Nano  |  AI  |  Android Developers">Android Developers</a>)</p>
<p>Encaja especialmente bien en funcionalidades como:</p>
<ul>
<li>Resumen de notas privadas.</li>
<li>Reescritura de mensajes.</li>
<li>Asistentes que trabajan con contenido local.</li>
<li>Funciones disponibles sin conexión.</li>
</ul>
<h3>Modelo en la nube</h3>
<p>Un modelo cloud suele ofrecer mayor capacidad para tareas complejas, acceso a información remota y respuestas más ricas. A cambio, añade latencia, coste, conectividad y requisitos de seguridad.</p>
<h3>Arquitectura híbrida</h3>
<p>El enfoque híbrido combina ambas opciones. Un agente principal puede usar un modelo cloud para coordinar el flujo, mientras subagentes locales procesan información sensible, como notas, historial o documentos descargados.</p>
<p>Esta arquitectura permite equilibrar privacidad y capacidad. Por ejemplo, una app de salud podría resumir localmente síntomas registrados por el usuario y enviar al backend solo una representación mínima y consentida para obtener contenido educativo.</p>
<h2>Configura ADK en el proyecto</h2>
<p>Actualmente, ADK para Android requiere <code>minSdk</code> 24 o superior, <code>compileSdk</code> 34 o superior, Java 17 y KSP para procesar anotaciones como <code>@Tool</code>. La dependencia Android reemplaza a la variante JVM: no debes añadir ambas. (<a href="https://developer.android.com/ai/adk" title="Build ADK agents for Android  |  AI  |  Android Developers">Android Developers</a>)</p>
<pre><code class="language-gradle">plugins {
    id("com.android.application")
    kotlin("android")
    id("com.google.devtools.ksp") version "2.1.20-2.0.1"
}

android {
    compileSdk = 34

    defaultConfig {
        minSdk = 24
        targetSdk = 34
    }
}

dependencies {
    implementation("com.google.adk:google-adk-kotlin-core-android:0.1.0")
    ksp("com.google.adk:google-adk-kotlin-processor:0.1.0")
}

kotlin {
    jvmToolchain(17)
}

</code></pre>
<p>Revisa siempre la documentación antes de copiar versiones: ADK para Android sigue evolucionando y sus artefactos pueden cambiar.</p>
<h2>Crea un agente local sencillo</h2>
<p>El siguiente ejemplo crea un agente que trabaja con un modelo local ya inicializado mediante ML Kit. En una app real, conviene exponerlo desde una capa de dominio o un repositorio, no crearlo directamente en un <code>Composable</code>.</p>
<pre><code class="language-kotlin">import com.google.adk.kt.agents.Instruction
import com.google.adk.kt.agents.LlmAgent
import com.google.adk.kt.models.mlkit.GenaiPrompt
import com.google.adk.kt.runners.InMemoryRunner
import com.google.adk.kt.sessions.InMemorySessionService
import com.google.adk.kt.types.Content
import com.google.adk.kt.types.Part
import com.google.adk.kt.types.Role
import com.google.mlkit.genai.prompt.GenerativeModel
import kotlinx.coroutines.flow.Flow

class NotesAssistant(
    generativeModel: GenerativeModel
) {
    private val model = GenaiPrompt.create(
        generativeModel = generativeModel,
        name = "gemini-nano"
    )

    private val agent = LlmAgent(
        name = "notes_assistant",
        description = "Resume y organiza notas del usuario.",
        model = model,
        instruction = Instruction(
            "Responde en español. Resume el contenido con claridad y " +
                "no inventes información que no aparezca en la nota."
        )
    )

    private val runner = InMemoryRunner(
        agent = agent,
        sessionService = InMemorySessionService()
    )

    fun ask(note: String): Flow&#x3C;Any> {
        return runner.runAsync(
            userId = "current-user",
            sessionId = "notes-session",
            newMessage = Content(
                role = Role.USER,
                parts = listOf(
                    Part(text = "Resume esta nota:\n$note")
                )
            )
        )
    }
}

</code></pre>
<p>El <code>InMemoryRunner</code> ejecuta el agente y expone los eventos de respuesta como un flujo. En Android, recógelo desde un <code>ViewModel</code> con <code>viewModelScope</code> y publica el texto final o los fragmentos parciales en el estado de UI. La documentación oficial muestra este patrón basado en corrutinas y <code>collect</code>. (<a href="https://developer.android.com/ai/adk" title="Build ADK agents for Android  |  AI  |  Android Developers">Android Developers</a>)</p>
<h2>Añade herramientas cuando el agente debe actuar</h2>
<p>El valor diferencial de ADK aparece cuando el agente puede usar funciones de tu app. Una herramienta puede consultar una base de datos local, recuperar una reserva, crear una tarea o buscar un producto.</p>
<p>Define funciones pequeñas, deterministas y con permisos claros. Evita exponer operaciones destructivas sin confirmación explícita del usuario.</p>
<pre><code class="language-kotlin">import com.google.adk.kt.annotations.Param
import com.google.adk.kt.annotations.Tool

class TaskTools(
    private val repository: TasksRepository
) {
    @Tool
    suspend fun getPendingTasks(
        @Param("Categoría opcional para filtrar tareas")
        category: String?
    ): List&#x3C;String> {
        return repository.pendingTasks(category)
            .map { task -> task.title }
    }
}

</code></pre>
<p>Después puedes registrar las herramientas generadas en el agente mediante <code>tools = TaskTools(repository).generatedTools()</code>. Mantén estas operaciones dentro de tu capa de datos o dominio: el agente decide <em>cuándo</em> pedir una acción, pero tu aplicación conserva las reglas de negocio, autorización y validación.</p>
<h2>Recomendaciones para producción</h2>
<p>Empieza con un único agente y una única capacidad útil. Un flujo pequeño es más fácil de probar, medir y proteger que una red de subagentes creada demasiado pronto.</p>
<p>También conviene aplicar estas reglas:</p>
<ul>
<li>Conserva las decisiones críticas fuera del modelo.</li>
<li>Pide confirmación antes de compras, borrados, envíos o cambios de cuenta.</li>
<li>Registra métricas de latencia, errores, cancelaciones y coste por interacción.</li>
<li>Diseña estados de carga y recuperación ante falta de red.</li>
<li>Trata la salida del modelo como contenido no confiable hasta validarla.</li>
<li>Prueba en dispositivos reales si dependes de inferencia local.</li>
</ul>
<p>ADK no sustituye la arquitectura Android habitual. Sigue necesitando <code>ViewModel</code>, repositorios, control de permisos, pruebas y una UI clara. Su función es aportar una capa de orquestación para experiencias de IA que ya no caben en una sola llamada a un modelo.</p>]]></content:encoded>
  </item>
  <item>
    <title>Styles en Jetpack Compose: la mejora para estilizar componentes sin abusar de Modifier</title>
    <link>https://cursosdroid.com/articles/styles-en-jetpack-compose-la-mejora-para-estilizar-componentes-sin-abusar-de-modifier</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/styles-en-jetpack-compose-la-mejora-para-estilizar-componentes-sin-abusar-de-modifier</guid>
    <pubDate>Thu, 02 Jul 2026 08:19:52 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>La nueva API Style de Jetpack Compose separa la apariencia visual de los comportamientos y layouts puntuales. No sustituye a Modifier, pero facilita el theming, los estados interactivos y las animaciones con menos código repetido.</description>
    <category>Android</category>
    <category>Jetpack Compose</category>
    <category>Kotlin</category>
    <category>Design System</category>
    <content:encoded><![CDATA[<p>La nueva API <code>Style</code> de Jetpack Compose propone una forma distinta de definir la apariencia de componentes. No se trata de crear una <code>data class</code> propia con colores y tamaños: es una API experimental integrada en Compose para agrupar propiedades visuales, aplicar variantes reutilizables y reaccionar a estados como pulsado, foco o hover. Su objetivo no es eliminar <code>Modifier</code>, sino reservarlo para aquello que hace mejor: comportamiento, semántica, gestos y layouts específicos. (<a href="https://developer.android.com/develop/ui/compose/styles" title="Styles in Compose  |  Jetpack Compose  |  Android Developers">Android Developers</a>)</p>
<h2>Qué son realmente los <code>Style</code></h2>
<p>Un <code>Style</code> define propiedades visuales como fondo, padding, bordes, forma, sombras, tamaño, transformaciones o atributos tipográficos. Puedes pasarlo a un componente que exponga un parámetro <code>style</code> o aplicarlo sobre layouts mediante <code>Modifier.styleable</code>. (<a href="https://developer.android.com/develop/ui/compose/styles/fundamentals" title="Fundamentals of Styles  |  Jetpack Compose  |  Android Developers">Android Developers</a>)</p>
<p>La diferencia clave es que un <code>Style</code> funciona como una capa de propiedades <strong>sobrescribibles</strong>. Si defines dos veces un fondo, se aplicará el último. Con los modifiers ocurre lo contrario: normalmente se encadenan de forma aditiva. Añadir dos bordes con <code>Modifier.border()</code> puede dibujar dos bordes; aplicar dos fondos en un <code>Style</code> reemplaza el anterior. (<a href="https://developer.android.com/develop/ui/compose/styles/fundamentals" title="Fundamentals of Styles  |  Jetpack Compose  |  Android Developers">Android Developers</a>)</p>
<p>Esto cambia cómo modelamos un design system. En lugar de construir una larga cadena de <code>Modifier</code> en cada pantalla, puedes definir un estilo base y reemplazar solamente las propiedades que cambian.</p>
<h2><code>Style</code> no reemplaza a <code>Modifier</code></h2>
<p>La forma correcta de entender la API es esta: <code>Style</code> es un tipo especializado de <code>Modifier</code> orientado a configuración visual. Todo lo que logra un <code>Style</code> puede conseguirse con modifiers, pero <code>Style</code> no cubre todas las capacidades de <code>Modifier</code>. (<a href="https://developer.android.com/develop/ui/compose/styles/styles-vs-modifiers" title="Styles versus modifiers  |  Jetpack Compose  |  Android Developers">Android Developers</a>)</p>
<table>
<thead>
<tr>
<th>Necesidad</th>
<th>Opción recomendada</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fondo, borde, forma, padding, tamaño y tipografía</td>
<td>Style</td>
</tr>
<tr>
<td>Apariencia compartida por un tema o design system</td>
<td>Style</td>
</tr>
<tr>
<td>Animaciones visuales entre estados</td>
<td>Style</td>
</tr>
<tr>
<td>Clicks, gestos y comportamiento</td>
<td>Modifier</td>
</tr>
<tr>
<td>Semántica y accesibilidad</td>
<td>Modifier</td>
</tr>
<tr>
<td>Layouts puntuales o ajustes únicos</td>
<td>Modifier</td>
</tr>
<tr>
<td>Propiedades que deben acumularse</td>
<td>Modifier</td>
</tr>
</tbody>
</table>
<p>Un componente puede usar ambos sin problema: <code>Modifier</code> para hacerlo pulsable o colocarlo en pantalla, y <code>Style</code> para decidir cómo se ve.</p>
<h2>Un ejemplo con estados y animación</h2>
<p>El siguiente ejemplo aplica un <code>Style</code> a un <code>Box</code>. El <code>Modifier.clickable</code> conserva la responsabilidad de la interacción, mientras que <code>Style</code> define fondo, padding, forma y respuesta visual al estado <code>pressed</code>.</p>
<pre><code class="language-kotlin">@Composable
fun SaveSurface(
    onSave: () -> Unit,
    modifier: Modifier = Modifier
) {
    val interactionSource = remember { MutableInteractionSource() }
    val styleState = remember(interactionSource) {
        MutableStyleState(interactionSource)
    }

    val saveStyle = Style {
        externalPadding(horizontal = 16.dp, vertical = 8.dp)
        contentPadding(20.dp)
        shape(RoundedCornerShape(20.dp))
        background(Color(0xFF6750A4))

        pressed {
            animate {
                background(Color(0xFF4F378B))
            }

            animate(
                spring(
                    dampingRatio = Spring.DampingRatioMediumBouncy
                )
            ) {
                scale(0.98f)
            }
        }
    }

    Box(
        modifier = modifier
            .clickable(
                interactionSource = interactionSource,
                indication = null,
                onClick = onSave
            )
            .styleable(styleState, saveStyle)
    ) {
        Text("Guardar cambios")
    }
}

</code></pre>
<p>Con modifiers tradicionales, este comportamiento suele implicar observar el estado de interacción, calcular valores con <code>animateColorAsState</code> o APIs similares y aplicar las animaciones manualmente. Los <code>Style</code> permiten declarar esas variaciones dentro del propio estilo mediante bloques como <code>pressed</code>, <code>hovered</code> o <code>focused</code>, además de incluir <code>animate</code> para interpolar propiedades visuales. (<a href="https://developer.android.com/develop/ui/compose/styles/state-animations" title="State and animations in Styles  |  Jetpack Compose  |  Android Developers">Android Developers</a>)</p>
<h2>Por qué supone una mejora</h2>
<h3>Estados visuales más declarativos</h3>
<p>Un botón puede tener estilos distintos cuando está deshabilitado, seleccionado, enfocado o pulsado. Con <code>StyleState</code>, estas reglas viven en el mismo lugar que el resto de la apariencia, en lugar de dispersarse entre condiciones, animaciones y cadenas de modifiers. La API también admite estados personalizados, útiles para componentes de reproducción, carga, error o selección. (<a href="https://developer.android.com/develop/ui/compose/styles/state-animations" title="State and animations in Styles  |  Jetpack Compose  |  Android Developers">Android Developers</a>)</p>
<h3>Mejor encaje con el theming</h3>
<p>Los styles pueden definirse como valores reutilizables y combinarse con <code>then</code>. Esto permite partir de una apariencia base y crear variantes sin duplicar toda la configuración. Además, pueden leer valores de <code>CompositionLocal</code>, una pieza importante para conectarlos con tokens de diseño globales. (<a href="https://developer.android.com/develop/ui/compose/styles/fundamentals" title="Fundamentals of Styles  |  Jetpack Compose  |  Android Developers">Android Developers</a>)</p>
<h3>Animaciones visuales con menos recomposición</h3>
<p>Según la documentación de Compose, los cambios de <code>Style</code> se resuelven en las fases de layout y dibujo, evitando la fase de composición en muchas actualizaciones visuales. Esto resulta especialmente interesante para animar color, escala, bordes o padding durante interacciones frecuentes. (<a href="https://developer.android.com/develop/ui/compose/styles" title="Styles in Compose  |  Jetpack Compose  |  Android Developers">Android Developers</a>)</p>
<h2>Cuándo usarlo en un proyecto real</h2>
<p><code>Style</code> encaja especialmente bien en componentes propios de un design system: tarjetas, botones internos, chips, superficies interactivas o controles que necesiten muchas variantes visuales. También es una buena opción cuando quieres reemplazar valores por defecto sin que las propiedades se acumulen de forma inesperada.</p>
<p>No conviene usarlo por inercia para cada <code>Box</code> o <code>Text</code>. Resolver un estilo completo puede tener más coste que aplicar un único modifier, y los modifiers siguen siendo imprescindibles para comportamiento y accesibilidad. (<a href="https://developer.android.com/develop/ui/compose/styles/styles-vs-modifiers" title="Styles versus modifiers  |  Jetpack Compose  |  Android Developers">Android Developers</a>)</p>
<p>La mejora no consiste en dejar de usar <code>Modifier</code>, sino en evitar que tenga que resolver todos los problemas de estilo. Usa <code>Style</code> para expresar identidad visual, variantes, estados y transiciones; usa <code>Modifier</code> para definir comportamiento y contexto. Esa separación hace que los componentes sean más coherentes, las APIs más pequeñas y el design system más fácil de mantener.</p>]]></content:encoded>
  </item>
  <item>
    <title>Android XR: cómo diseñar aplicaciones espaciales que complementen al smartphone</title>
    <link>https://cursosdroid.com/articles/android-xr-como-disenar-aplicaciones-espaciales-que-complementen-al-smartphone</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/android-xr-como-disenar-aplicaciones-espaciales-que-complementen-al-smartphone</guid>
    <pubDate>Wed, 01 Jul 2026 11:29:22 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>Android XR abre una nueva superficie para las aplicaciones Android, pero no exige abandonar el móvil. Descubre cómo crear experiencias espaciales útiles, cómodas y conectadas con el smartphone.</description>
    <category>Android XR</category>
    <category>Android</category>
    <category>Jetpack Compose</category>
    <category>Kotlin Multiplatform</category>
    <content:encoded><![CDATA[<p>Android XR no debe entenderse como una pantalla más grande ni como un sustituto inmediato del smartphone. Su valor aparece cuando una aplicación aprovecha el espacio, el contexto y la atención del usuario sin obligarle a abandonar por completo el móvil. El objetivo no es convertir cada interfaz en una escena 3D, sino elegir qué tareas mejoran al distribuirse alrededor de la persona.</p>
<p>Android permite que aplicaciones móviles y optimizadas para pantallas grandes funcionen en XR, pero una aplicación realmente diferenciada añade capacidades específicas como paneles espaciales, modelos 3D, audio espacial o entornos inmersivos. (<a href="https://developer.android.com/docs/quality-guidelines/android-xr" title="Android XR app quality guidelines  |  Android Developers">Android Developers</a>)</p>
<h2>Diseñar para un espacio, no para una pantalla</h2>
<p>Una aplicación XR puede funcionar en dos contextos principales. En <code>Home Space</code>, el usuario mantiene varias aplicaciones abiertas y trabaja con paneles acotados. En <code>Full Space</code>, la aplicación toma el protagonismo y puede acceder a experiencias inmersivas, contenido 3D y una interfaz espacial más amplia. (<a href="https://developer.android.com/design/ui/xr/guides/foundations" title="Foundations  |  XR Headsets &#x26; wired XR Glasses  |  Android Developers">Android Developers</a>)</p>
<p>Esta diferencia debe guiar la arquitectura de producto:</p>
<ul>
<li>Usa <code>Home Space</code> para productividad, mensajería, listas, reproducción de contenido o consultas rápidas.</li>
<li>Reserva <code>Full Space</code> para tareas que realmente se beneficien de la inmersión: formación práctica, visualización de datos, recorridos 3D, edición audiovisual o experiencias de bienestar.</li>
<li>Permite que el usuario cambie de contexto sin perder su progreso.</li>
</ul>
<p>Una buena aplicación XR no fuerza la inmersión. La propone cuando aporta una ventaja clara.</p>
<h2>Principios para crear buenas aplicaciones Android XR</h2>
<h3>Prioriza la utilidad frente al efecto visual</h3>
<p>Un panel flotando en el espacio no mejora automáticamente una aplicación. Antes de añadir profundidad, pregúntate qué problema resuelve.</p>
<p>Por ejemplo, una aplicación de cursos puede mostrar el vídeo principal delante del usuario, una guía de ejercicios a un lado y una lista de recursos al otro. En cambio, una pantalla de inicio de sesión, una configuración de cuenta o un formulario de pago suelen funcionar mejor como interfaces convencionales.</p>
<p>Los paneles espaciales son contenedores para interfaz, contenido interactivo y experiencias inmersivas. Jetpack Compose para XR permite construirlos con conceptos familiares de Compose, como filas, columnas y composables reutilizables. (<a href="https://developer.android.com/develop/xr/jetpack-xr-sdk/ui-compose" title="Develop spatial UI with Jetpack Compose for XR  |  Android XR for Jetpack XR SDK  |  Android Developers">Android Developers</a>)</p>
<h3>Diseña para manos, mirada y otros métodos de entrada</h3>
<p>En XR, el usuario puede interactuar con mirada, gestos, manos, controladores, teclado o trackpad. No asumas que tendrá un mando ni que podrá realizar movimientos muy precisos durante varios minutos.</p>
<p>Android recomienda controles táctiles de al menos <code>48dp</code>, con <code>56dp</code> como tamaño recomendado para mejorar la comodidad y la precisión. También es importante evitar movimientos bruscos de cámara y mantener referencias visuales estables para reducir el riesgo de mareo. (<a href="https://developer.android.com/docs/quality-guidelines/android-xr" title="Android XR app quality guidelines  |  Android Developers">Android Developers</a>)</p>
<p>Esto se traduce en decisiones concretas:</p>
<ul>
<li>Botones grandes y separados.</li>
<li>Pocos controles visibles al mismo tiempo.</li>
<li>Texto legible desde diferentes distancias.</li>
<li>Animaciones suaves y breves.</li>
<li>Acciones importantes disponibles sin gestos complejos.</li>
</ul>
<h3>Mantén la navegación cerca del contenido</h3>
<p>En una interfaz espacial, la navegación no debería obligar al usuario a recorrer visualmente toda la escena. Los <code>Orbiters</code>permiten asociar controles contextuales a un panel, por ejemplo para pausar un vídeo, cambiar de lección o abrir acciones relacionadas con el contenido actual. (<a href="https://developer.android.com/develop/xr/jetpack-xr-sdk/ui-compose" title="Develop spatial UI with Jetpack Compose for XR  |  Android XR for Jetpack XR SDK  |  Android Developers">Android Developers</a>)</p>
<p>Esto ayuda a que la interfaz siga siendo comprensible sin llenar el espacio de menús flotantes.</p>
<h2>El smartphone como compañero, no como duplicado</h2>
<p>La mejor relación entre Android XR y el móvil es complementaria. El smartphone sigue siendo excelente para tareas privadas, rápidas y de alta densidad de información. XR destaca cuando la información necesita espacio, contexto o atención sostenida.</p>
<table>
<thead>
<tr>
<th>Tarea</th>
<th>Smartphone</th>
<th>Android XR</th>
</tr>
</thead>
<tbody>
<tr>
<td>Configuración inicial</td>
<td>Registro, permisos, pagos y recuperación de cuenta</td>
<td>Confirmación visual o guía puntual</td>
</tr>
<tr>
<td>Consulta rápida</td>
<td>Notificaciones, mensajes, listas breves</td>
<td>Alertas discretas y contenido de un vistazo</td>
</tr>
<tr>
<td>Trabajo profundo</td>
<td>Edición puntual y control táctil preciso</td>
<td>Multitarea, visualización y concentración</td>
</tr>
<tr>
<td>Formación</td>
<td>Descarga de cursos y planificación</td>
<td>Lecciones inmersivas, práctica y contenido espacial</td>
</tr>
<tr>
<td>Creación</td>
<td>Captura, escritura y gestión de archivos</td>
<td>Revisión de escenas, vídeo o modelos 3D</td>
</tr>
</tbody>
</table>
<p>Para gafas de audio o de pantalla, Android XR contempla un modelo en el que la aplicación se ejecuta en un dispositivo anfitrión, como un teléfono Android, y proyecta la experiencia hacia las gafas mediante Jetpack Projected. (<a href="https://developer.android.com/develop/xr/jetpack-xr-sdk" title="Develop with the Jetpack XR SDK  |  Android XR for Jetpack XR SDK  |  Android Developers">Android Developers</a>)</p>
<p>Esto permite plantear un flujo continuo: el usuario prepara una sesión en el móvil, consume o explora contenido en XR y vuelve al teléfono para compartir resultados, revisar detalles o continuar una tarea fuera del visor.</p>
<h2>Un ejemplo de adaptación progresiva con Compose</h2>
<p>No es necesario reescribir toda la aplicación para probar una interfaz espacial. Puedes conservar la misma pantalla principal y mostrarla dentro de un panel espacial cuando el dispositivo lo permita.</p>
<pre><code class="language-kotlin">@Composable
fun CourseWorkspace() {
    if (LocalSpatialCapabilities.current.isSpatialUiEnabled) {
        Subspace {
            SpatialPanel(
                modifier = SubspaceModifier
                    .transformingMovable()
                    .transformingResizable()
            ) {
                CourseContent()
            }
        }
    } else {
        CourseContent()
    }
}

</code></pre>
<p>Este patrón mantiene una única implementación de <code>CourseContent()</code> y añade comportamiento XR solo cuando la interfaz espacial está disponible. Android recomienda comprobar las capacidades espaciales en tiempo de ejecución, ya que pueden variar según el dispositivo y el contexto de uso. (<a href="https://developer.android.com/design/ui/xr/guides/foundations" title="Foundations  |  XR Headsets &#x26; wired XR Glasses  |  Android Developers">Android Developers</a>)</p>
<h2>Arquitectura recomendada para experiencias multidispositivo</h2>
<p>La continuidad entre móvil y XR es más importante que la apariencia de los paneles. Para conseguirla:</p>
<ol>
<li>Centraliza el estado de usuario, progreso y contenido en una capa compartida.</li>
<li>Mantén la lógica de dominio separada de la interfaz.</li>
<li>Diseña pantallas específicas para móvil y XR, aunque compartan casos de uso.</li>
<li>Sincroniza el punto exacto donde el usuario dejó una tarea.</li>
<li>Permite iniciar una acción en un dispositivo y terminarla en otro.</li>
</ol>
<p>Kotlin Multiplatform puede ser especialmente útil aquí. Puedes compartir modelos, reglas de negocio, repositorios y casos de uso entre plataformas, mientras cada interfaz adopta los patrones adecuados para su contexto. El móvil puede priorizar velocidad y densidad; XR, espacio, foco y contexto.</p>
<h2>Una estrategia realista para empezar</h2>
<p>Empieza identificando un único flujo donde XR aporte valor medible. Puede ser una lección práctica, un visor de planos, una experiencia de compra con objetos 3D o una pantalla de trabajo con varios documentos.</p>
<p>Después, optimiza primero tu aplicación para pantallas grandes y entrada externa. Cuando esa base sea sólida, adapta componentes Material 3 y añade uno o dos elementos espaciales, como un <code>SpatialPanel</code> o un <code>Orbiter</code>. Material Design para XR permite que componentes y layouts existentes adopten comportamientos espaciales en dispositivos compatibles. (<a href="https://developer.android.com/develop/xr/jetpack-xr-sdk/material-design" title="Implement Material Design for your spatial UI  |  Android XR for Jetpack XR SDK  |  Android Developers">Android Developers</a>)</p>
<p>Android XR tendrá más impacto en las aplicaciones que entiendan una idea simple: el smartphone sigue siendo el centro personal y portátil; XR puede convertirse en el espacio donde ese contenido gana contexto, escala y presencia.</p>]]></content:encoded>
  </item>
  <item>
    <title>ViewModels con alcance composable: la nueva API Scoped ViewModels en Jetpack Compose</title>
    <link>https://cursosdroid.com/articles/viewmodels-con-alcance-composable-la-nueva-api-scoped-viewmodels-en-jetpack-compose</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/viewmodels-con-alcance-composable-la-nueva-api-scoped-viewmodels-en-jetpack-compose</guid>
    <pubDate>Tue, 30 Jun 2026 08:44:35 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>Lifecycle 2.11.0 permite crear ViewModel vinculados a una zona concreta de la composición, no solo a una Activity o destino de navegación. Descubre cómo usar rememberViewModelStoreOwner() y rememberViewModelStoreProvider() en pantallas dinámicas, paginadores y flujos complejos.</description>
    <category>Android</category>
    <category>Jetpack Compose</category>
    <category>ViewModel</category>
    <category>Kotlin</category>
    <content:encoded><![CDATA[<p>Jetpack Compose facilita crear interfaces dinámicas, pero hasta ahora los límites de vida de un <code>ViewModel</code> no siempre coincidían con los de la UI. Un <code>ViewModel</code> creado desde un composable hijo normalmente quedaba asociado a la <code>Activity</code> o al destino de navegación más cercano, aunque ese bloque visual desapareciera antes de la pantalla.</p>
<p>Lifecycle 2.11.0 introduce una solución nativa para este caso: <strong>Scoped ViewModels</strong>. Con ella puedes asociar un <code>ViewModel</code> a una parte concreta de la composición, conservarlo ante cambios de configuración y liberarlo automáticamente cuando esa parte abandona el árbol de UI. (<a href="https://developer.android.com/jetpack/androidx/releases/lifecycle" title="Lifecycle  |  Jetpack  |  Android Developers">Android Developers</a>)</p>
<h2>El problema: el alcance no siempre coincide con la UI</h2>
<p>La función <code>viewModel()</code> obtiene la instancia desde el <code>ViewModelStoreOwner</code> más cercano disponible en <code>LocalViewModelStoreOwner</code>. En una app Compose con Navigation, ese propietario suele ser el <code>NavBackStackEntry</code> actual; sin Navigation, puede ser la <code>Activity</code>. (<a href="https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-apis" title="ViewModel Scoping APIs  |  App architecture  |  Android Developers">Android Developers</a>)</p>
<p>Eso funciona muy bien para una pantalla completa: por ejemplo, <code>DetailViewModel</code> asociado a la ruta de detalle. Sin embargo, hay componentes que tienen vida propia dentro de una pantalla:</p>
<ul>
<li>Cada página de un <code>HorizontalPager</code>.</li>
<li>Un editor temporal que aparece y desaparece.</li>
<li>Un panel expandible con operaciones asíncronas.</li>
<li>Una zona de UI dinámica con estado complejo e independiente.</li>
</ul>
<p>Antes, para darles un ciclo de vida específico, era habitual crear y limpiar <code>ViewModelStore</code> manualmente o aceptar que el estado viviera más tiempo del necesario. La nueva API elimina ese trabajo y hace explícito el alcance desde Compose.</p>
<h2>Las tres piezas de Scoped ViewModels</h2>
<p>Lifecycle 2.11.0 incorpora tres APIs relacionadas:</p>
<ul>
<li><code>rememberViewModelStoreOwner()</code>: crea un <code>ViewModelStoreOwner</code> asociado al punto donde se invoca el composable.</li>
<li><code>rememberViewModelStoreProvider()</code>: conserva un proveedor de stores para crear varios alcances independientes.</li>
<li><code>ViewModelStoreProvider</code>: es la pieza de más bajo nivel que gestiona los stores hijos y su relación con el propietario padre. (<a href="https://developer.android.com/jetpack/androidx/releases/lifecycle" title="Lifecycle  |  Jetpack  |  Android Developers">Android Developers</a>)</li>
</ul>
<p>La API principal para la mayoría de casos es <code>rememberViewModelStoreOwner()</code>. El <code>ViewModel</code> asociado a ese owner sobrevive a una rotación, pero se limpia cuando el composable que lo contiene deja la composición. (<a href="https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-apis" title="ViewModel Scoping APIs  |  App architecture  |  Android Developers">Android Developers</a>)</p>
<h2>Añade la dependencia</h2>
<p>Incluye el artefacto de Compose para <code>ViewModel</code>. La versión 2.11.0 fue publicada el 17 de junio de 2026. (<a href="https://developer.android.com/jetpack/androidx/releases/lifecycle" title="Lifecycle  |  Jetpack  |  Android Developers">Android Developers</a>)</p>
<pre><code class="language-gradle">dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0")
}

</code></pre>
<h2>Ejemplo: un ViewModel por página en un Pager</h2>
<p>Imagina un catálogo donde cada página permite escribir una nota temporal sobre un producto. Cada producto necesita conservar su propia nota mientras su página está en composición, sin compartirla con el resto.</p>
<pre><code class="language-kotlin">data class ProductPageUiState(
    val note: String = ""
)

class ProductPageViewModel(
    private val productId: String
) : ViewModel() {

    var uiState by mutableStateOf(ProductPageUiState())
        private set

    fun onNoteChanged(note: String) {
        uiState = uiState.copy(note = note)
    }
}

@Composable
fun ProductPager(productIds: List&#x3C;String>) {
    val pagerState = rememberPagerState(
        pageCount = { productIds.size }
    )

    val storeProvider = rememberViewModelStoreProvider()

    HorizontalPager(state = pagerState) { page ->
        val productId = productIds[page]

        val pageOwner = rememberViewModelStoreOwner(
            provider = storeProvider,
            key = productId
        )

        CompositionLocalProvider(
            LocalViewModelStoreOwner provides pageOwner
        ) {
            val pageViewModel: ProductPageViewModel = viewModel {
                ProductPageViewModel(productId)
            }

            ProductPage(
                note = pageViewModel.uiState.note,
                onNoteChanged = pageViewModel::onNoteChanged
            )
        }
    }
}

</code></pre>
<p><code>rememberViewModelStoreProvider()</code> mantiene el proveedor común, mientras que <code>rememberViewModelStoreOwner()</code> crea un owner distinto por cada clave. En este caso, <code>productId</code> identifica de forma estable la página y evita mezclar el estado entre productos.</p>
<p>Al proporcionar <code>pageOwner</code> mediante <code>CompositionLocalProvider</code>, cualquier llamada a <code>viewModel()</code> dentro de ese bloque utiliza el nuevo alcance. La documentación oficial muestra este patrón para páginas de <code>HorizontalPager</code> y otros escenarios con varios scopes independientes. (<a href="https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-apis" title="ViewModel Scoping APIs  |  App architecture  |  Android Developers">Android Developers</a>)</p>
<h2>Cuándo merece la pena usarlo</h2>
<p>Scoped ViewModels no sustituye a <code>remember</code> ni a <code>rememberSaveable</code>. Para un campo visual sencillo, una animación o una selección efímera, el estado local suele ser más claro y ligero.</p>
<p>Esta API encaja cuando el bloque de UI necesita lógica, operaciones asíncronas, una fuente de estado propia o independencia real frente a otros bloques hermanos. También es una buena opción cuando un componente dinámico es complejo, pero no representa una pantalla completa.</p>
<p>Las recomendaciones de arquitectura de Android siguen priorizando <code>ViewModel</code> a nivel de pantalla, destino o grafo de navegación. Para piezas de UI reutilizables, suele ser preferible un state holder simple; reserva el scope composable para secciones dinámicas con una responsabilidad de estado clara. (<a href="https://developer.android.com/topic/architecture/recommendations?utm_source=chatgpt.com" title="Recommendations for Android architecture">Android Developers</a>)</p>
<h2>Matices importantes</h2>
<p>Que una página deje de estar visible no implica necesariamente que su <code>ViewModel</code> se destruya de inmediato. Se limpia cuando el composable sale de la composición, y componentes como un pager pueden mantener páginas cercanas preparadas durante un tiempo.</p>
<p>Tampoco debes confundir supervivencia ante rotación con restauración tras muerte de proceso. El nuevo owner protege el <code>ViewModel</code> durante cambios de configuración, pero para recuperar datos tras una finalización del proceso iniciada por el sistema necesitas persistir el estado necesario, por ejemplo mediante <code>SavedStateHandle</code>. (<a href="https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-savedstate" title="Saved State module for ViewModel  |  App architecture  |  Android Developers">Android Developers</a>)</p>
<p>La gran ventaja de Scoped ViewModels es que el ciclo de vida deja de ser un detalle implícito. Puedes modelar la duración de cada estado según la parte de UI que realmente lo necesita, sin forzar toda la lógica a vivir al nivel de la pantalla.</p>]]></content:encoded>
  </item>
  <item>
    <title>DAM y desarrollo mobile: dónde encajan los perfiles junior y qué salario pueden esperar</title>
    <link>https://cursosdroid.com/articles/dam-y-desarrollo-mobile-donde-encajan-los-perfiles-junior-y-que-salario-pueden-esperar</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/dam-y-desarrollo-mobile-donde-encajan-los-perfiles-junior-y-que-salario-pueden-esperar</guid>
    <pubDate>Thu, 25 Jun 2026 15:06:36 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>Terminar DAM sigue abriendo puertas en mobile, aunque el primer empleo no siempre se llama “Android Developer Junior”. Repasamos las rutas de entrada, las bandas salariales orientativas en España y las habilidades que más ayudan a destacar.</description>
    <category>DAM</category>
    <category>Android</category>
    <category>Kotlin</category>
    <category>Empleo tecnológico</category>
    <content:encoded><![CDATA[<p>Terminar un ciclo de Desarrollo de Aplicaciones Multiplataforma no garantiza una incorporación inmediata a un equipo Android, pero sí aporta una base válida para entrar en el sector mobile. El reto está en que el mercado junior es más competitivo y las empresas buscan señales prácticas de que puedes trabajar sobre una aplicación real: entender código existente, corregir errores, consumir APIs, escribir tests y colaborar con Git.</p>
<p>El espacio para un perfil recién titulado existe, pero suele aparecer bajo nombres distintos: desarrollador Android trainee, programador Kotlin, QA mobile, desarrollador backend Java/Kotlin, soporte de aplicaciones o desarrollador multiplataforma. La clave es no limitar la búsqueda a una única etiqueta.</p>
<h2>El mercado mobile para perfiles junior</h2>
<p>El desarrollo mobile sigue siendo una especialidad relevante, pero en 2025 muchas empresas redujeron la prioridad de nuevas aplicaciones y concentraron inversión en IA, datos e infraestructura. La guía salarial de Manfred para 2026 describe un mercado mobile con menos movimiento en ofertas y una caída de los salarios en los tramos altos, especialmente frente a otras áreas técnicas. (<a href="https://www.getmanfred.com/en/blog/guia-salarial-2026-salarios-en-tecnologia-espana-manfred" title="Salarios tecnología España 2026">Get Manfred</a>)</p>
<p>Eso no significa que Android o iOS hayan dejado de ser opciones profesionales. Significa que las empresas valoran más a los candidatos que llegan con proyectos terminados, criterio técnico y capacidad para integrarse en productos ya existentes.</p>
<p>Para un recién titulado de DAM, el primer empleo puede estar más cerca de estas rutas:</p>
<table>
<thead>
<tr>
<th>Puesto inicial</th>
<th>Relación con mobile</th>
<th>Qué conviene demostrar</th>
</tr>
</thead>
<tbody>
<tr>
<td>Android Junior o Trainee</td>
<td>Desarrollo nativo con Kotlin</td>
<td>Jetpack Compose, navegación, APIs, Git</td>
</tr>
<tr>
<td>QA Mobile</td>
<td>Pruebas manuales y automatizadas</td>
<td>Casos de prueba, logs, testing y dispositivos</td>
</tr>
<tr>
<td>Backend Java/Kotlin</td>
<td>Servicios que consumen las apps</td>
<td>REST, autenticación, bases de datos</td>
</tr>
<tr>
<td>Desarrollo multiplataforma</td>
<td>Flutter o Kotlin Multiplatform</td>
<td>Arquitectura compartida y consumo de APIs</td>
</tr>
<tr>
<td>Mantenimiento de apps</td>
<td>Evolución de productos publicados</td>
<td>Corrección de bugs, releases y lectura de código</td>
</tr>
</tbody>
</table>
<p>Un puesto de mantenimiento puede parecer menos atractivo que crear una aplicación desde cero, pero ofrece una experiencia muy útil. Aprendes a trabajar con incidencias reales, versiones antiguas de Android, dependencias, analítica, errores de producción y ciclos de publicación.</p>
<h2>Bandas salariales orientativas en España</h2>
<p>Las cifras dependen de la ciudad, el tipo de empresa, el nivel de inglés, la modalidad de trabajo y las tecnologías que domines. Aun así, estas bandas ayudan a tener una referencia para negociar y detectar ofertas claramente por debajo del mercado.</p>
<p>Todos los importes de esta tabla son salarios brutos anuales aproximados.</p>
<table>
<thead>
<tr>
<th>Perfil</th>
<th>Experiencia</th>
<th>Banda orientativa</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mobile Developer junior</td>
<td>Menos de 2 años</td>
<td>20.000 € – 30.000 €</td>
</tr>
<tr>
<td>QA Mobile junior</td>
<td>Menos de 2 años</td>
<td>20.000 € – 25.000 €</td>
</tr>
<tr>
<td>Backend Java/Kotlin junior</td>
<td>Menos de 2 años</td>
<td>20.000 € – 30.000 €</td>
</tr>
<tr>
<td>Android Junior en Madrid</td>
<td>0 a 2 años</td>
<td>23.000 € – 30.000 € base</td>
</tr>
<tr>
<td>Mobile Developer intermedio</td>
<td>2 a 5 años</td>
<td>31.000 € – 40.000 €</td>
</tr>
<tr>
<td>Mobile Developer senior</td>
<td>5 a 10 años</td>
<td>41.000 € – 45.000 €</td>
</tr>
</tbody>
</table>
<p>La guía de Manfred sitúa los perfiles mobile con menos de dos años de experiencia entre 20.000 € y 30.000 € brutos anuales. Para perfiles con dos a cinco años, el rango se mueve entre 31.000 € y 40.000 €. (<a href="https://www.getmanfred.com/en/blog/guia-salarial-2026-salarios-en-tecnologia-espana-manfred" title="Salarios tecnología España 2026">Get Manfred</a>)</p>
<p>Como referencia más específica, Glassdoor estima para un <code>Junior Android Developer</code> en Madrid una horquilla de salario base entre 23.000 € y 30.000 €, con una compensación total habitual de entre 25.000 € y 32.000 €. Debe tomarse como una señal de mercado, no como una cifra garantizada, porque se basa en salarios compartidos por usuarios. (<a href="https://www.glassdoor.com/Salaries/madrid-spain-junior-android-developer-salary-SRCH_IL.0%2C12_IM1030_KO13%2C37.htm" title="Salary: Junior Android Developer in Madrid, Spain 2026 | Glassdoor">Glassdoor</a>)</p>
<p>También conviene recordar que Hays sitúa el salario de un desarrollador junior generalista en España entre 25.000 € y 35.000 € brutos anuales. Un perfil mobile con portfolio sólido, inglés funcional y conocimientos de arquitectura puede acercarse antes a la parte alta de ese rango. (<a href="https://www.hays.es/empleos/it/software-developer" title="Software Developer">Hays Spain</a>)</p>
<h2>Qué hace subir o bajar el salario</h2>
<p>La ciudad sigue influyendo. Madrid y Barcelona suelen concentrar más empresas de producto, fintech, consultoras tecnológicas y equipos internacionales. Eso puede elevar el salario, aunque también aumenta el coste de vida.</p>
<p>El tipo de empresa también importa. Una consultora puede ofrecer una entrada más accesible, formación y proyectos variados, pero con una banda inicial más ajustada. Una empresa de producto puede pedir más nivel técnico desde el inicio, aunque suele valorar mejor el conocimiento profundo de Android, arquitectura, testing y publicación en Google Play.</p>
<p>Las tecnologías complementarias marcan diferencias. Kotlin es la base para Android moderno, pero saber trabajar con <code>Jetpack Compose</code>, <code>Room</code>, <code>Retrofit</code>, <code>WorkManager</code>, Firebase, CI/CD y testing puede convertir un perfil junior generalista en un candidato más específico.</p>
<p>En perfiles senior, React Native y Flutter mantienen salarios más elevados cuando existe escasez de talento especializado, pero esa prima no suele aplicarse al primer empleo. (<a href="https://www.getmanfred.com/en/blog/guia-salarial-2026-salarios-en-tecnologia-espana-manfred" title="Salarios tecnología España 2026">Get Manfred</a>)</p>
<h2>El portfolio sustituye a la experiencia que todavía no tienes</h2>
<p>El título de DAM acredita formación. El portfolio demuestra capacidad para aplicar esa formación. No necesitas publicar diez clones de una red social: necesitas uno o dos proyectos terminados, con una README clara y decisiones técnicas justificadas.</p>
<p>Una aplicación de hábitos, gastos personales o reservas puede demostrar mucho más que una interfaz bonita. Incluye carga de datos, estados de error, persistencia local, navegación, tests y una arquitectura sencilla.</p>
<pre><code>data class HabitUiState(
    val isLoading: Boolean = false,
    val habits: List&#x3C;Habit> = emptyList(),
    val error: String? = null
)

class HabitViewModel(
    private val repository: HabitRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(HabitUiState())
    val uiState: StateFlow&#x3C;HabitUiState> = _uiState.asStateFlow()

    fun loadHabits() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }

            runCatching { repository.getHabits() }
                .onSuccess { habits ->
                    _uiState.update {
                        it.copy(isLoading = false, habits = habits)
                    }
                }
                .onFailure { exception ->
                    _uiState.update {
                        it.copy(
                            isLoading = false,
                            error = exception.message ?: "Error inesperado"
                        )
                    }
                }
        }
    }
}

</code></pre>
<p>Este ejemplo refleja elementos habituales en un proyecto Android: <code>ViewModel</code>, corrutinas, <code>StateFlow</code>, estados de interfaz y tratamiento de errores. No necesitas una arquitectura excesivamente compleja; necesitas demostrar que entiendes por qué usas cada pieza.</p>
<h2>Una estrategia realista para entrar en mobile</h2>
<p>Especialízate en una ruta principal, como Android con Kotlin y Jetpack Compose, pero mantén una segunda competencia cercana. Backend Kotlin, testing, Firebase, Flutter o Kotlin Multiplatform amplían las oportunidades sin alejarte del ecosistema mobile.</p>
<p>Busca vacantes con términos como <code>Android trainee</code>, <code>Kotlin junior</code>, <code>QA mobile</code>, <code>programador Java</code>, <code>desarrollo multiplataforma</code> o <code>mantenimiento de aplicaciones</code>. Muchas empresas no publican el primer puesto bajo la etiqueta exacta que esperas.</p>
<p>Tu valor como junior no depende de tener años de experiencia. Depende de demostrar que puedes aprender rápido, comunicarte bien, revisar tu propio código y aportar en un equipo desde el primer sprint.</p>]]></content:encoded>
  </item>
  <item>
    <title>Guía paso a paso para usar Android Skills con Claude y Codex, y crear skills para proyectos Android y Kotlin Multiplatform</title>
    <link>https://cursosdroid.com/articles/guia-paso-a-paso-para-usar-android-skills-con-claude-y-codex-y-crear-skills-para-proyectos-android-y-kotlin-multiplatform</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/guia-paso-a-paso-para-usar-android-skills-con-claude-y-codex-y-crear-skills-para-proyectos-android-y-kotlin-multiplatform</guid>
    <pubDate>Wed, 24 Jun 2026 11:01:22 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>Aprende a instalar y aprovechar las Android Skills oficiales en agentes como Claude Code y Codex. También veremos cómo diseñar skills propias para automatizar convenciones, validaciones y flujos recurrentes en proyectos Android y Kotlin Multiplatform.</description>
    <category>Android</category>
    <category>Kotlin Multiplatform</category>
    <category>Claude Code</category>
    <category>Codex</category>
    <content:encoded><![CDATA[<p>Los agentes de código son más útiles cuando conocen las reglas reales de tu proyecto. No basta con pedirles “crea una pantalla Compose” o “migra este módulo a Kotlin Multiplatform”: necesitan contexto sobre arquitectura, comandos de compilación, convenciones, dependencias y criterios de validación.</p>
<p>Las <em>skills</em> resuelven ese problema. Son paquetes reutilizables de instrucciones, referencias, scripts y recursos que un agente carga cuando una tarea lo requiere. El repositorio <a href="https://github.com/android/skills">android/skills</a> publica skills mantenidas por Android para tareas específicas, como migraciones de AGP, CameraX, edge-to-edge, Navigation 3, R8 o Android CLI. El formato sigue el estándar abierto Agent Skills y gira alrededor de un archivo <code>SKILL.md</code>. (<a href="https://github.com/android/skills" title="GitHub - android/skills · GitHub">GitHub</a>)</p>
<h2>Qué diferencia hay entre una skill y las instrucciones globales</h2>
<p>Antes de instalar nada, conviene separar dos conceptos:</p>
<table>
<thead>
<tr>
<th>Recurso</th>
<th>Úsalo para</th>
</tr>
</thead>
<tbody>
<tr>
<td>AGENTS.md o CLAUDE.md</td>
<td>Reglas permanentes del repositorio: arquitectura, módulos, comandos, convenciones y definición de terminado.</td>
</tr>
<tr>
<td>SKILL.md</td>
<td>Procesos concretos y reutilizables: migrar una API, revisar accesibilidad, validar un cambio KMP o preparar una release.</td>
</tr>
</tbody>
</table>
<p>Una skill no debería repetir todo el contexto del proyecto. Debe explicar un flujo concreto, cuándo activarlo y cómo comprobar el resultado. Así el agente no carga instrucciones largas en cada conversación y obtiene detalles solo cuando el trabajo lo necesita.</p>
<h2>Instalar Android Skills oficiales</h2>
<p>Android distribuye sus skills mediante Android CLI. Puedes instalar una skill concreta en el proyecto actual:</p>
<pre><code class="language-bash">android skills add --skill=r8-analyzer --project=.

</code></pre>
<p>Para instalar todas las skills disponibles para los agentes detectados:</p>
<pre><code class="language-bash">android skills add --all

</code></pre>
<p>También puedes limitar la instalación a uno o varios agentes usando <code>--agent</code>, o elegir una skill concreta con <code>--skill</code>. Si no indicas una skill ni <code>--all</code>, Android CLI instala únicamente la skill <code>android-cli</code>. (<a href="https://github.com/android/skills" title="GitHub - android/skills · GitHub">GitHub</a>)</p>
<p>Un flujo razonable para un repositorio Android sería este:</p>
<pre><code class="language-bash">cd mi-app-android
android skills add --all --project=.
git status

</code></pre>
<p>Después, revisa qué directorios se han creado y decide cuáles deben versionarse. Para un equipo, lo habitual es mantener las skills de proyecto dentro del repositorio para que todos los desarrolladores y agentes trabajen con las mismas reglas.</p>
<h2>Usar Android Skills con Claude Code</h2>
<p>Claude Code reconoce skills mediante directorios que contienen un archivo <code>SKILL.md</code>. Puedes guardarlas a nivel personal en <code>~/.claude/skills/</code> o solo para un repositorio en <code>.claude/skills/</code>. La ubicación del directorio define el comando disponible: por ejemplo, <code>.claude/skills/android-release/SKILL.md</code> crea la skill <code>/android-release</code>. (<a href="https://code.claude.com/docs/fr/skills" title="Étendre Claude avec des skills - Claude Code Docs">code.claude.com</a>)</p>
<h3>Paso 1: instala las skills en el proyecto</h3>
<p>Desde la raíz del repositorio:</p>
<pre><code class="language-bash">android skills add --all --project=.

</code></pre>
<p>Comprueba si el instalador ha creado o actualizado directorios para Claude. Después inicia Claude Code desde la raíz:</p>
<pre><code class="language-bash">claude

</code></pre>
<h3>Paso 2: consulta las skills detectadas</h3>
<p>En Claude Code, abre el selector de skills o escribe <code>/</code> para ver las disponibles. Una skill puede activarse de dos formas:</p>
<ul>
<li>De manera explícita, escribiendo su comando.</li>
<li>De forma automática, cuando Claude interpreta que tu petición coincide con la descripción de la skill.</li>
</ul>
<p>Por ejemplo, si tienes una skill orientada a analizar R8, puedes pedir:</p>
<pre><code>Usa la skill de análisis R8 para investigar por qué esta build release falla tras activar minifyEnabled.

</code></pre>
<p>La descripción de una skill es clave: Claude la usa para decidir cuándo cargarla automáticamente. El contenido completo se incorpora solo al activarla, lo que evita llenar el contexto con instrucciones que no aplican a la tarea actual. (<a href="https://code.claude.com/docs/fr/skills" title="Étendre Claude avec des skills - Claude Code Docs">code.claude.com</a>)</p>
<h3>Paso 3: combina skills con <code>CLAUDE.md</code></h3>
<p>Mantén en <code>CLAUDE.md</code> las reglas que siempre deben cumplirse. Por ejemplo:</p>
<pre><code># Proyecto Android

- Usa Kotlin y Jetpack Compose para interfaces nuevas.
- La capa `domain` no depende de Android.
- Ejecuta `./gradlew test lint` antes de finalizar cambios relevantes.
- No modifiques versiones del catálogo sin justificarlo.
- Los módulos KMP comparten lógica en `commonMain`.

</code></pre>
<p>Después deja los flujos especializados en skills: migraciones, auditorías, generación de changelogs o revisión de módulos.</p>
<h2>Usar Android Skills con Codex</h2>
<p>Codex también trabaja con el estándar <code>SKILL.md</code>. Una skill contiene un directorio con el archivo principal y, opcionalmente, carpetas como <code>scripts/</code>, <code>references/</code>, <code>assets/</code> o <code>agents/</code>. El frontmatter debe incluir al menos <code>name</code> y <code>description</code>. (<a href="https://developers.openai.com/codex/skills?utm_source=chatgpt.com" title="Agent Skills – Codex">Desarrolladores de OpenAI</a>)</p>
<p>Para un proyecto, Codex busca skills en <code>.agents/skills</code> desde el directorio actual hasta la raíz del repositorio. (<a href="https://developers.openai.com/codex/skills?utm_source=chatgpt.com" title="Agent Skills – Codex">Desarrolladores de OpenAI</a>)</p>
<h3>Paso 1: instala o copia las skills al repositorio</h3>
<p>Ejecuta la instalación de Android CLI desde la raíz del proyecto:</p>
<pre><code class="language-bash">android skills add --all --project=.

</code></pre>
<p>Comprueba dónde ha dejado los archivos el instalador. Para Codex, la estructura recomendada del repositorio es:</p>
<pre><code>mi-proyecto/
├── AGENTS.md
├── .agents/
│   └── skills/
│       ├── android-release/
│       │   └── SKILL.md
│       └── kmp-module-review/
│           └── SKILL.md
└── shared/

</code></pre>
<h3>Paso 2: define reglas persistentes en <code>AGENTS.md</code></h3>
<p>Codex lee <code>AGENTS.md</code> antes de empezar a trabajar. Es el lugar adecuado para documentar cómo se construye, prueba y organiza el repositorio. Las instrucciones más cercanas al directorio actual tienen prioridad sobre las más generales. (<a href="https://developers.openai.com/codex/guides/agents-md?utm_source=chatgpt.com" title="Custom instructions with AGENTS.md – Codex">Desarrolladores de OpenAI</a>)</p>
<p>Un ejemplo útil para Android y KMP:</p>
<pre><code># AGENTS.md

## Arquitectura
- `androidApp` contiene la aplicación Android.
- `shared` contiene código Kotlin Multiplatform.
- Las reglas de negocio viven en `shared/commonMain`.
- Las implementaciones de plataforma usan `expect` y `actual` solo cuando son necesarias.

## Validación
- Para cambios en código Kotlin: `./gradlew check`.
- Para cambios Android: `./gradlew :androidApp:assembleDebug`.
- Para cambios en `shared`: `./gradlew :shared:allTests`.

## Restricciones
- No añadas dependencias sin comprobar el catálogo de versiones.
- No cambies APIs públicas sin añadir o actualizar pruebas.

</code></pre>
<h3>Paso 3: invoca una skill</h3>
<p>Codex puede activar una skill por coincidencia con su descripción o de forma explícita desde el selector de skills. En CLI e IDE puedes usar <code>/skills</code> o mencionar una skill con <code>$</code>. (<a href="https://developers.openai.com/codex/skills?utm_source=chatgpt.com" title="Agent Skills – Codex">Desarrolladores de OpenAI</a>)</p>
<p>Un prompt práctico sería:</p>
<pre><code>Usa $kmp-module-review para revisar el módulo shared. Detecta dependencias Android en commonMain, APIs no portables y pruebas ausentes. No modifiques archivos todavía: entrega primero un plan priorizado.

</code></pre>
<h2>Crear una skill propia para Android o Kotlin Multiplatform</h2>
<p>Empieza por un problema repetitivo. Una buena skill captura decisiones estables de tu equipo, no preferencias temporales ni instrucciones vagas.</p>
<p>Ejemplos apropiados:</p>
<ul>
<li>Revisar que un módulo KMP no filtre dependencias Android a <code>commonMain</code>.</li>
<li>Preparar una migración de XML a Compose.</li>
<li>Validar cambios que afectan a R8 y reglas ProGuard.</li>
<li>Crear una pantalla siguiendo la arquitectura y el design system del proyecto.</li>
<li>Revisar que una nueva API incluya pruebas unitarias y de UI.</li>
</ul>
<h3>Estructura mínima</h3>
<p>Para Codex, crea la skill dentro de <code>.agents/skills</code>. Para Claude Code, usa <code>.claude/skills</code>. Puedes mantener el mismo contenido y duplicarlo, o crear enlaces simbólicos si el entorno del equipo lo permite.</p>
<pre><code>.agents/
└── skills/
    └── kmp-module-review/
        ├── SKILL.md
        └── references/
            └── architecture.md

</code></pre>
<h3>Ejemplo: skill para revisar módulos KMP</h3>
<pre><code>---
name: kmp-module-review
description: Revisa módulos Kotlin Multiplatform cuando se modifique código compartido, dependencias o source sets. Detecta APIs Android en commonMain, dependencias no multiplataforma, acoplamiento de plataforma y pruebas faltantes.
---

## Objetivo

Revisa los cambios del módulo Kotlin Multiplatform sin modificar archivos salvo que el usuario lo pida expresamente.

## Proceso

1. Identifica los archivos modificados en el módulo objetivo.
2. Revisa `build.gradle.kts` y clasifica las dependencias por source set.
3. Comprueba que `commonMain` no dependa de APIs `android.*`, `Context`, `Parcelable`, `ViewModel` de AndroidX ni recursos Android.
4. Verifica que el código específico de Android esté en `androidMain`.
5. Comprueba que los contratos compartidos tengan pruebas en `commonTest` cuando sea viable.
6. Ejecuta las verificaciones disponibles indicadas en `AGENTS.md`.
7. Entrega un informe con severidad, archivo afectado, motivo y propuesta de corrección.

## Criterios de salida

- No afirmes que una verificación se ha ejecutado si no has visto su resultado.
- Diferencia entre errores confirmados, riesgos y recomendaciones.
- Prioriza cambios que rompan compilación, portabilidad o aislamiento entre capas.

## Recursos

Consulta [architecture.md](references/architecture.md) para conocer las excepciones aprobadas por el proyecto.

</code></pre>
<p>La descripción no es solo documentación: es el mecanismo de descubrimiento. Debe indicar qué hace la skill, cuándo usarla y, cuando sea útil, cuándo no debe activarse. Codex carga primero el nombre y la descripción; el cuerpo completo de <code>SKILL.md</code> llega cuando selecciona la skill. (<a href="https://developers.openai.com/blog/skills-agents-sdk?utm_source=chatgpt.com" title="Using skills to accelerate OSS maintenance">Desarrolladores de OpenAI</a>)</p>
<h2>Añadir referencias y scripts sin inflar el contexto</h2>
<p>Una skill madura no debería convertirse en un documento enorme. Mantén <code>SKILL.md</code> como guía de navegación y mueve el detalle a archivos auxiliares.</p>
<pre><code>kmp-module-review/
├── SKILL.md
├── references/
│   ├── architecture.md
│   ├── approved-libraries.md
│   └── testing-rules.md
└── scripts/
    └── verify-kmp.sh

</code></pre>
<p>En Claude Code se recomienda mantener el cuerpo de <code>SKILL.md</code> por debajo de unas 500 líneas y enlazar referencias para que el agente las abra solo cuando sean necesarias. (<a href="https://code.claude.com/docs/fr/skills" title="Étendre Claude avec des skills - Claude Code Docs">code.claude.com</a>)</p>
<p>Un script es útil para pasos deterministas. Por ejemplo:</p>
<pre><code class="language-bash">#!/usr/bin/env bash
set -euo pipefail

./gradlew :shared:allTests
./gradlew :androidApp:assembleDebug

</code></pre>
<p>No uses scripts para sustituir el juicio del agente. Úsalos para repetir comandos fiables y dejar que la skill explique cuándo deben ejecutarse, cómo interpretar fallos y qué evidencia incluir en el resultado.</p>
<h2>Crear skills con asistentes integrados</h2>
<p>Claude Code permite crear una skill manualmente añadiendo un directorio y su <code>SKILL.md</code>. También admite archivos de soporte, argumentos, control de herramientas y activación por rutas. (<a href="https://code.claude.com/docs/fr/skills" title="Étendre Claude avec des skills - Claude Code Docs">code.claude.com</a>)</p>
<p>Codex incluye <code>$skill-creator</code>, una skill integrada que te guía para definir el objetivo, condiciones de activación y necesidad de scripts. Es una buena opción cuando ya conoces el flujo, pero quieres convertirlo rápidamente en una estructura consistente. (<a href="https://developers.openai.com/codex/skills?utm_source=chatgpt.com" title="Agent Skills – Codex">Desarrolladores de OpenAI</a>)</p>
<p>Un prompt de partida para Codex sería:</p>
<pre><code>$skill-creator

Crea una skill para revisar Pull Requests de Android. Debe activarse cuando cambien archivos Kotlin, Gradle o Compose. Tiene que verificar arquitectura, estado en Compose, accesibilidad básica, pruebas y comandos Gradle relevantes. Debe ser de solo lectura y devolver un informe priorizado.

</code></pre>
<h2>Cómo evaluar si una skill funciona</h2>
<p>No des por buena una skill porque el agente la detecta. Evalúa dos cosas por separado:</p>
<ol>
<li><strong>Activación:</strong> ¿se selecciona para las peticiones adecuadas?</li>
<li><strong>Resultado:</strong> ¿el informe, código o validación cumple los criterios definidos?</li>
</ol>
<p>Crea prompts de prueba con casos positivos y negativos:</p>
<table>
<thead>
<tr>
<th>Caso</th>
<th>Resultado esperado</th>
</tr>
</thead>
<tbody>
<tr>
<td>“Revisa este cambio en shared/commonMain”</td>
<td>Activa kmp-module-review.</td>
</tr>
<tr>
<td>“Añade un icono a la pantalla principal”</td>
<td>No activa la revisión KMP.</td>
</tr>
<tr>
<td>“Prepara la release Android 2.4.0”</td>
<td>Activa la skill de release, pero no publica sin confirmación.</td>
</tr>
<tr>
<td>“El build release se cierra tras minificación”</td>
<td>Activa una skill relacionada con R8 si está instalada.</td>
</tr>
</tbody>
</table>
<p>Cuando el agente cometa el mismo error varias veces, decide dónde debe vivir la corrección: en <code>AGENTS.md</code> si es una regla permanente del repositorio, o en una skill si pertenece a un flujo concreto.</p>
<h2>Recomendaciones finales</h2>
<p>Empieza con pocas skills y problemas muy claros. Una colección pequeña, bien activada y versionada junto al código será más útil que decenas de instrucciones genéricas.</p>
<p>Para un proyecto Android/Kotlin Multiplatform, una base sólida suele incluir:</p>
<ul>
<li>Un <code>AGENTS.md</code> o <code>CLAUDE.md</code> breve con arquitectura, comandos y restricciones.</li>
<li>Una skill de revisión KMP.</li>
<li>Una skill de verificación de builds y pruebas.</li>
<li>Una skill de revisión de UI Compose y accesibilidad.</li>
<li>Una skill de release manual, protegida contra activación automática.</li>
</ul>
<p>Con esta separación, Claude y Codex pueden usar las mismas prácticas de ingeniería sin obligarte a repetirlas en cada conversación.</p>]]></content:encoded>
  </item>
  <item>
    <title>Cómo integrar Gemini en una app Android sin convertirla en un chatbot</title>
    <link>https://cursosdroid.com/articles/como-integrar-gemini-en-una-app-android-sin-convertirla-en-un-chatbot</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/como-integrar-gemini-en-una-app-android-sin-convertirla-en-un-chatbot</guid>
    <pubDate>Wed, 24 Jun 2026 10:45:29 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>Gemini puede mejorar una app Android sin añadir una conversación infinita. Úsalo para resumir, clasificar, extraer información y proponer acciones dentro de flujos ya existentes.</description>
    <category>Android</category>
    <category>Gemini</category>
    <category>Inteligencia artificial</category>
    <category>Kotlin</category>
    <content:encoded><![CDATA[<p>Gemini no tiene por qué convertirse en una pestaña llamada “Chat” ni en una interfaz de mensajes. En una app Android, su mejor papel suele ser mucho más discreto: transformar texto, comprender contenido, sugerir una acción o estructurar datos para que la interfaz existente sea más útil.</p>
<p>La clave es diseñar la IA como una capacidad concreta del producto, no como un destino dentro de la navegación. En lugar de preguntar “¿qué quieres preguntarle a Gemini?”, plantea necesidades reales: “resume esta nota”, “extrae los gastos”, “clasifica esta incidencia” o “convierte esta descripción en una tarea”.</p>
<h2>Piensa en acciones, no en conversaciones</h2>
<p>Un chatbot es una interfaz abierta: el usuario decide qué preguntar y la aplicación debe estar preparada para responder a casi cualquier cosa. Eso añade complejidad, resultados imprevisibles y una carga de diseño importante.</p>
<p>Una integración orientada a tareas limita el problema. La app controla el contexto, el formato esperado y el momento en el que se invoca el modelo.</p>
<p>Algunos casos de uso habituales son:</p>
<ul>
<li>Resumir una nota larga antes de guardarla.</li>
<li>Generar un título para una publicación o documento.</li>
<li>Extraer fechas, importes o participantes desde texto libre.</li>
<li>Clasificar una incidencia según prioridad y categoría.</li>
<li>Reformular una descripción para hacerla más clara.</li>
<li>Crear etiquetas para contenido generado por el usuario.</li>
<li>Proponer una lista breve de próximos pasos.</li>
</ul>
<p>La diferencia parece pequeña, pero cambia toda la experiencia. El usuario no “habla con una IA”: pulsa una acción que resuelve una tarea concreta.</p>
<h2>Elige entre inferencia en la nube y en el dispositivo</h2>
<p>En Android puedes integrar modelos Gemini en la nube o aprovechar Gemini Nano para ejecutar determinadas tareas localmente. Google recomienda las APIs GenAI de ML Kit como punto de partida para casos de uso integrados en el dispositivo, ya que ofrecen interfaces de alto nivel sobre AICore. (<a href="https://developer.android.com/ai/gemini-nano?utm_source=chatgpt.com" title="Gemini Nano | AI">Android Developers</a>)</p>
<p>La elección depende de la experiencia que quieras construir:</p>
<table>
<thead>
<tr>
<th>Necesidad</th>
<th>Enfoque recomendado</th>
</tr>
</thead>
<tbody>
<tr>
<td>Resumen o reescritura de texto local</td>
<td>Gemini Nano con ML Kit GenAI</td>
</tr>
<tr>
<td>Datos sensibles que no deberían salir del dispositivo</td>
<td>Inferencia en dispositivo</td>
</tr>
<tr>
<td>Comprensión compleja, multimodal o gran contexto</td>
<td>Gemini en la nube</td>
</tr>
<tr>
<td>Misma experiencia en todos los dispositivos</td>
<td>Servicio en la nube</td>
</tr>
<tr>
<td>Respuesta rápida sin conexión</td>
<td>Inferencia en dispositivo</td>
</tr>
</tbody>
</table>
<p>No todos los dispositivos tendrán las mismas capacidades locales. Por eso conviene tratar Gemini Nano como una mejora progresiva: úsalo cuando esté disponible y prepara una alternativa cuando no lo esté.</p>
<h2>Diseña una salida estructurada</h2>
<p>El error más común es pedir texto libre y luego intentar interpretarlo con expresiones regulares. Para una función integrada en producto, Gemini debería devolver datos que tu app pueda consumir con seguridad.</p>
<p>Por ejemplo, una app de gastos puede recibir este texto:</p>
<blockquote>
<p>Ayer pagué 24,50 € en una cena con el equipo.</p>
</blockquote>
<p>La aplicación no necesita una respuesta conversacional. Necesita algo parecido a esto:</p>
<pre><code class="language-kotlin">{
  "amount": 24.5,
  "currency": "EUR",
  "category": "restauracion",
  "date": "2026-06-23",
  "description": "Cena con el equipo"
}

</code></pre>
<p>Los modelos Gemini permiten solicitar respuestas ajustadas a un esquema JSON, lo que reduce ambigüedad y facilita trabajar con resultados tipados. Esta opción está pensada, entre otros casos, para extracción de datos y clasificación estructurada. (<a href="https://ai.google.dev/gemini-api/docs/structured-output?utm_source=chatgpt.com" title="Structured outputs - generateContent API">Google AI for Developers</a>)</p>
<p>En Kotlin, el resultado puede representarse con una clase de dominio:</p>
<pre><code class="language-kotlin">@Serializable
data class ExpenseDraft(
    val amount: Double,
    val currency: String,
    val category: String,
    val date: String?,
    val description: String
)

</code></pre>
<p>Después, tu <code>ViewModel</code> puede validar el objeto antes de mostrarlo en pantalla. El usuario sigue teniendo el control: revisa los campos detectados y confirma antes de guardar.</p>
<h2>Integra Gemini detrás de una interfaz de dominio</h2>
<p>Evita que la UI conozca detalles del SDK, prompts o modelos. La pantalla debería depender de una abstracción orientada al caso de uso.</p>
<pre><code class="language-kotlin">interface ExpenseExtractor {
    suspend fun extract(text: String): Result&#x3C;ExpenseDraft>
}

</code></pre>
<p>Una implementación concreta puede construir el prompt, invocar el modelo y convertir la respuesta estructurada a <code>ExpenseDraft</code>. La pantalla solo se preocupa por estados como carga, éxito o error.</p>
<pre><code class="language-kotlin">class CreateExpenseViewModel(
    private val expenseExtractor: ExpenseExtractor
) : ViewModel() {

    var uiState by mutableStateOf(CreateExpenseUiState())
        private set

    fun analyzeDescription(text: String) {
        viewModelScope.launch {
            uiState = uiState.copy(isAnalyzing = true)

            expenseExtractor.extract(text)
                .onSuccess { draft ->
                    uiState = uiState.copy(
                        isAnalyzing = false,
                        amount = draft.amount.toString(),
                        category = draft.category,
                        description = draft.description
                    )
                }
                .onFailure {
                    uiState = uiState.copy(
                        isAnalyzing = false,
                        error = "No se pudo analizar la descripción"
                    )
                }
        }
    }
}

</code></pre>
<p>Esta separación facilita pruebas, permite cambiar de proveedor y evita que el modelo se convierta en una dependencia transversal de toda la app.</p>
<h2>Haz que la IA sea opcional y reversible</h2>
<p>Una buena función de IA no bloquea el flujo principal. El usuario debe poder completar la tarea manualmente aunque Gemini falle, no tenga conexión o devuelva una respuesta poco útil.</p>
<p>En la práctica, esto implica:</p>
<ul>
<li>Mostrar resultados como sugerencias editables.</li>
<li>No guardar cambios automáticamente.</li>
<li>Mantener formularios y acciones manuales disponibles.</li>
<li>Ofrecer estados de carga breves y específicos.</li>
<li>Gestionar errores sin mensajes técnicos.</li>
<li>Guardar la entrada original para que el usuario no la pierda.</li>
</ul>
<h2>Cuida el prompt como parte del producto</h2>
<p>Un prompt útil no intenta ser creativo. Define una tarea, un contexto, restricciones y el formato de salida.</p>
<p>Para clasificar una incidencia, por ejemplo:</p>
<pre><code>Clasifica la incidencia en una de estas categorías:
- facturacion
- acceso
- error_tecnico
- consulta_general

Devuelve únicamente JSON con:
category, priority y summary.

La prioridad debe ser baja, media o alta.
No inventes información que no esté presente en el texto.

</code></pre>
<p>Este tipo de instrucciones reduce respuestas decorativas y mejora la consistencia. También conviene incluir ejemplos reales de entradas ambiguas y revisar resultados con datos representativos de tu app.</p>
<p>Las configuraciones de seguridad de Gemini permiten ajustar filtros según las necesidades del producto, pero no sustituyen la validación de negocio ni una buena experiencia de error. (<a href="https://ai.google.dev/gemini-api/docs/safety-settings?utm_source=chatgpt.com" title="Safety settings | Gemini API | Google AI for Developers">Google AI for Developers</a>)</p>
<h2>Mide utilidad, no solo llamadas al modelo</h2>
<p>El éxito no es que los usuarios pulsen el botón de IA. Es que terminen antes la tarea para la que abrieron la app.</p>
<p>Mide señales como:</p>
<ul>
<li>Porcentaje de sugerencias aceptadas.</li>
<li>Campos corregidos antes de confirmar.</li>
<li>Tiempo hasta completar una tarea.</li>
<li>Tasa de abandono después de una sugerencia.</li>
<li>Errores de validación derivados de resultados generados.</li>
</ul>
<p>Con esos datos podrás detectar si Gemini está resolviendo un problema real o simplemente añadiendo una capa llamativa a una interfaz que ya funcionaba.</p>
<h2>Conclusión</h2>
<p>Integrar Gemini sin convertir tu app en un chatbot significa usar la IA con intención. El modelo debe aparecer cuando puede ahorrar tiempo, reducir fricción o transformar información difícil de procesar manualmente.</p>
<p>Empieza por una sola acción específica, devuelve datos estructurados, mantén al usuario en control y deja que la interfaz actual siga siendo protagonista. Así, Gemini deja de ser una conversación aislada y se convierte en una capacidad útil dentro de tu producto.</p>]]></content:encoded>
  </item>
  <item>
    <title>Por qué una arquitectura Cloud sólida es clave para el éxito de una aplicación móvil</title>
    <link>https://cursosdroid.com/articles/por-que-una-arquitectura-cloud-solida-es-clave-para-el-exito-de-una-aplicacion-movil</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/por-que-una-arquitectura-cloud-solida-es-clave-para-el-exito-de-una-aplicacion-movil</guid>
    <pubDate>Wed, 24 Jun 2026 10:38:54 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>La app móvil es solo la capa visible para el usuario. Una arquitectura Cloud bien diseñada permite escalar, proteger datos, integrar servicios y evolucionar el producto sin que el backend se convierta en un freno.</description>
    <category>Android</category>
    <category>iOS</category>
    <category>Cloud</category>
    <category>Arquitectura</category>
    <content:encoded><![CDATA[<p>Una aplicación móvil puede tener una interfaz impecable, animaciones fluidas y una experiencia de usuario cuidada. Sin embargo, gran parte de su éxito depende de elementos que el usuario no ve: autenticación, sincronización de datos, notificaciones, almacenamiento de archivos, analítica, pagos, seguridad y disponibilidad.</p>
<p>Ahí entra en juego la arquitectura Cloud. Ya sea con AWS, Google Cloud, Azure u otros proveedores, disponer de una base backend sólida es determinante para que una aplicación Android, iOS o multiplataforma pueda crecer sin comprometer rendimiento, seguridad ni velocidad de desarrollo.</p>
<h2>La aplicación móvil no funciona aislada</h2>
<p>En muchos proyectos, la app se conecta a varios servicios remotos. Incluso una aplicación aparentemente simple puede necesitar usuarios registrados, una base de datos, imágenes almacenadas en la nube o notificaciones push.</p>
<p>Una arquitectura Cloud bien planteada define cómo se comunican estas piezas:</p>
<ul>
<li>La aplicación móvil consume una API.</li>
<li>La API aplica reglas de negocio y autorización.</li>
<li>Los datos se almacenan de forma segura.</li>
<li>Los archivos se sirven desde un almacenamiento optimizado.</li>
<li>Los eventos importantes activan procesos asíncronos.</li>
<li>La monitorización detecta errores y degradaciones.</li>
</ul>
<p>Esta separación evita que la lógica crítica dependa exclusivamente del cliente móvil. La aplicación puede actualizarse gradualmente, mientras que los cambios urgentes en reglas de negocio o infraestructura pueden desplegarse desde el backend.</p>
<h2>Escalabilidad: crecer sin reconstruir el proyecto</h2>
<p>Uno de los mayores riesgos de una arquitectura improvisada aparece cuando el producto empieza a recibir usuarios reales. Un backend creado para una demo puede funcionar con cien usuarios, pero fallar cuando llegan miles de peticiones simultáneas.</p>
<p>Los proveedores Cloud permiten escalar recursos según la demanda. Esto puede implicar aumentar instancias de servidor, usar funciones serverless, distribuir contenido mediante una CDN o emplear bases de datos administradas con alta disponibilidad.</p>
<p>Por ejemplo, una app de reservas puede experimentar picos de tráfico durante una promoción. Si toda la infraestructura depende de un único servidor, la aplicación puede volverse lenta o dejar de responder. Con una arquitectura distribuida, es posible absorber ese pico sin intervenir manualmente en cada componente.</p>
<p>Una aproximación habitual es diseñar servicios sin estado. Es decir, que una petición pueda ser atendida por cualquiera de varias instancias disponibles, sin depender de memoria local o sesiones almacenadas en un único servidor.</p>
<pre><code class="language-kotlin">data class UserSession(
    val userId: String,
    val accessToken: String,
    val expiresAt: Long
)

fun isSessionValid(session: UserSession, now: Long): Boolean {
    return session.accessToken.isNotBlank() &#x26;&#x26; session.expiresAt > now
}

</code></pre>
<p>En este ejemplo, la app conserva la información mínima de sesión, pero la validación real de permisos debe resolverse en el backend. Así, cualquier instancia del servicio puede procesar una solicitud de forma consistente.</p>
<h2>Seguridad: proteger datos, usuarios y credenciales</h2>
<p>Las aplicaciones móviles se distribuyen en dispositivos que no controlamos. Por ese motivo, nunca deben considerarse un entorno seguro para almacenar secretos sensibles, claves privadas o reglas críticas de negocio.</p>
<p>Una arquitectura Cloud robusta ayuda a proteger la información mediante:</p>
<ul>
<li>Autenticación centralizada con OAuth 2.0, OpenID Connect o proveedores gestionados.</li>
<li>Autorización basada en roles, permisos o atributos.</li>
<li>Cifrado de datos en tránsito y en reposo.</li>
<li>Gestión segura de secretos y credenciales.</li>
<li>Auditoría de accesos y cambios relevantes.</li>
<li>Protección frente a ataques comunes, como abuso de APIs o intentos masivos de inicio de sesión.</li>
</ul>
<p>La regla práctica es sencilla: la app móvil puede solicitar una acción, pero el servidor debe decidir si esa acción está permitida.</p>
<p>Por ejemplo, no es suficiente ocultar un botón de administración en Android o iOS. Un usuario podría intentar llamar directamente al endpoint. La API debe validar el token, comprobar el rol y confirmar que la operación cumple todas las reglas necesarias.</p>
<h2>Rendimiento y experiencia de usuario</h2>
<p>La percepción de calidad de una app está muy relacionada con el tiempo de respuesta. Una pantalla puede estar técnicamente bien implementada, pero ofrecer una mala experiencia si necesita esperar demasiado a un servicio remoto.</p>
<p>La arquitectura Cloud afecta directamente a este punto. Algunas decisiones habituales son usar caché para datos consultados con frecuencia, servir imágenes y vídeos desde una CDN, procesar tareas pesadas en segundo plano y acercar los servicios a los usuarios mediante regiones adecuadas.</p>
<p>También es importante diseñar la app para fallar de forma controlada. La conectividad móvil no es estable: el usuario puede perder cobertura, cambiar de red o trabajar temporalmente sin conexión.</p>
<p>Una estrategia de sincronización puede incluir almacenamiento local, reintentos, colas de operaciones pendientes y resolución de conflictos. En Android, por ejemplo, una capa de repositorio puede decidir cuándo leer de caché y cuándo actualizar desde red.</p>
<h2>Observabilidad: detectar problemas antes de que afecten al negocio</h2>
<p>Cuando una aplicación crece, ya no basta con revisar logs manualmente. Necesitamos entender qué está ocurriendo en producción.</p>
<p>Una arquitectura Cloud madura incorpora métricas, trazas y registros centralizados. Esto permite responder preguntas útiles:</p>
<ul>
<li>¿Qué endpoint está fallando más?</li>
<li>¿Cuánto tarda una operación crítica?</li>
<li>¿Qué versión de la app genera más errores?</li>
<li>¿Hay una región con latencia elevada?</li>
<li>¿Qué ocurre cuando falla un proveedor externo?</li>
</ul>
<p>La observabilidad reduce el tiempo necesario para diagnosticar incidencias. También facilita tomar decisiones basadas en datos, en lugar de depender de intuiciones o reportes aislados de usuarios.</p>
<h2>Velocidad de desarrollo y evolución del producto</h2>
<p>Una buena plataforma Cloud no solo mejora la operación técnica. También acelera la entrega de funcionalidades.</p>
<p>Los servicios gestionados reducen tareas de infraestructura, como aplicar parches del sistema, crear copias de seguridad o configurar escalado manual. Esto permite que el equipo se concentre en el producto.</p>
<p>Además, una arquitectura modular facilita evolucionar partes concretas. Puede empezar con un backend monolítico bien organizado y, cuando sea necesario, separar procesos como notificaciones, procesamiento de imágenes, facturación o recomendaciones.</p>
<p>No es necesario crear decenas de microservicios desde el primer día. Lo importante es evitar acoplamientos innecesarios y mantener límites claros entre dominios.</p>
<h2>Conclusión</h2>
<p>Una aplicación móvil no termina en Android, iOS o Kotlin Multiplatform. El cliente es solo una parte de un sistema mayor que debe ser seguro, escalable, observable y capaz de evolucionar.</p>
<p>Invertir pronto en una arquitectura Cloud sólida evita problemas costosos cuando el producto gana usuarios. También mejora la experiencia del equipo, reduce riesgos operativos y permite lanzar nuevas funcionalidades con más confianza.</p>
<p>AWS, Google Cloud y Azure ofrecen herramientas suficientes para construir este tipo de plataformas. La decisión importante no es únicamente qué proveedor elegir, sino diseñar una arquitectura coherente con las necesidades actuales del proyecto y preparada para cambiar cuando el producto lo requiera.</p>]]></content:encoded>
  </item>
  <item>
    <title>StateFlow, SharedFlow y Compose State: cuándo usar cada uno</title>
    <link>https://cursosdroid.com/articles/stateflow-sharedflow-y-compose-state-cuando-usar-cada-uno</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/stateflow-sharedflow-y-compose-state-cuando-usar-cada-uno</guid>
    <pubDate>Wed, 24 Jun 2026 09:36:38 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>Aprende a distinguir estado persistente, eventos puntuales y estado local en Jetpack Compose. Elige entre StateFlow, SharedFlow y mutableStateOf con criterios claros y ejemplos prácticos.</description>
    <category>Android</category>
    <category>Kotlin</category>
    <category>Jetpack Compose</category>
    <category>Coroutines</category>
    <content:encoded><![CDATA[<p>En una app moderna con Kotlin y Jetpack Compose, es habitual combinar varias formas de representar datos reactivos. El problema aparece cuando usamos todas para lo mismo: un <code>SharedFlow</code> para pintar una pantalla, un <code>StateFlow</code> para lanzar navegación o un <code>mutableStateOf</code> dentro del <code>ViewModel</code> sin una razón clara.</p>
<p>La decisión se simplifica con una pregunta: <strong>¿estás representando un estado actual, un evento que ocurre una vez o un detalle local de la interfaz?</strong></p>
<p><code>StateFlow</code> encaja con estado observable y persistente. <code>SharedFlow</code> sirve para emisiones o eventos que se distribuyen entre varios consumidores. Compose State, como <code>mutableStateOf</code>, es ideal para estado local gestionado por la propia UI.</p>
<h2>La diferencia clave: estado frente a evento</h2>
<p>Un <strong>estado</strong> responde a “¿cómo está la pantalla ahora mismo?”. Por ejemplo:</p>
<ul>
<li>La lista de productos está cargando.</li>
<li>El usuario está autenticado.</li>
<li>El campo de búsqueda contiene un texto.</li>
<li>Hay un error visible en pantalla.</li>
</ul>
<p>Un <strong>evento</strong> responde a “¿qué acaba de pasar?”. Por ejemplo:</p>
<ul>
<li>Mostrar un <code>Snackbar</code>.</li>
<li>Navegar al detalle de un producto.</li>
<li>Abrir un selector de archivos.</li>
<li>Reproducir una animación puntual.</li>
</ul>
<p>La diferencia importa porque un estado debe poder recuperarse cuando Compose recompone la pantalla o cuando un nuevo colector empieza a observarlo. Un evento, en cambio, normalmente no debería repetirse automáticamente.</p>
<h2>Cuándo usar StateFlow</h2>
<p><code>StateFlow</code> es un flujo caliente que siempre conserva un valor actual. Todo nuevo colector recibe inmediatamente ese último valor, por lo que es una opción natural para exponer el estado de pantalla desde un <code>ViewModel</code>. También evita emitir valores consecutivos equivalentes. (<a href="https://developer.android.com/kotlin/flow/stateflow-and-sharedflow?utm_source=chatgpt.com" title="StateFlow and SharedFlow | Kotlin">Android Developers</a>)</p>
<p>La práctica habitual consiste en mantener un <code>MutableStateFlow</code> privado y exponer un <code>StateFlow</code> de solo lectura.</p>
<pre><code class="language-kotlin">data class ProductsUiState(
    val isLoading: Boolean = false,
    val products: List&#x3C;Product> = emptyList(),
    val errorMessage: String? = null
)

class ProductsViewModel(
    private val repository: ProductsRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(ProductsUiState())
    val uiState: StateFlow&#x3C;ProductsUiState> = _uiState.asStateFlow()

    fun loadProducts() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, errorMessage = null) }

            runCatching { repository.getProducts() }
                .onSuccess { products ->
                    _uiState.update {
                        it.copy(
                            isLoading = false,
                            products = products
                        )
                    }
                }
                .onFailure {
                    _uiState.update {
                        it.copy(
                            isLoading = false,
                            errorMessage = "No se pudieron cargar los productos"
                        )
                    }
                }
        }
    }
}

</code></pre>
<p>En Compose, recoge ese flujo de forma segura respecto al ciclo de vida con <code>collectAsStateWithLifecycle()</code>.</p>
<pre><code class="language-kotlin">@Composable
fun ProductsScreen(
    viewModel: ProductsViewModel
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when {
        uiState.isLoading -> CircularProgressIndicator()

        uiState.errorMessage != null -> {
            Text(text = uiState.errorMessage)
        }

        else -> {
            LazyColumn {
                items(uiState.products) { product ->
                    Text(product.name)
                }
            }
        }
    }
}

</code></pre>
<h3>Buenas situaciones para StateFlow</h3>
<p>Usa <code>StateFlow</code> cuando el dato:</p>
<ul>
<li>Tiene un valor actual relevante.</li>
<li>Debe sobrevivir a una recomposición o a una recreación de la interfaz.</li>
<li>Puede necesitarse al volver a entrar en una pantalla.</li>
<li>Representa el modelo completo o parcial de una UI.</li>
</ul>
<p>Ejemplos comunes: <code>UiState</code>, sesión de usuario, filtros activos, datos de una pantalla de detalle, preferencias o conectividad.</p>
<h2>Cuándo usar SharedFlow</h2>
<p><code>SharedFlow</code> también es un flujo caliente, pero está pensado para difundir emisiones a múltiples colectores. A diferencia de <code>StateFlow</code>, no necesita tener un valor inicial ni representa necesariamente un estado actual. (<a href="https://kotlinlang.org/docs/coroutines-flow.html?utm_source=chatgpt.com" title="Flows | Kotlin Documentation">Kotlin</a>)</p>
<p>Es útil para eventos efímeros: acciones que deben provocar un efecto, no modificar permanentemente la apariencia de una pantalla.</p>
<pre><code class="language-kotlin">sealed interface ProductsEvent {
    data class ShowMessage(val text: String) : ProductsEvent
    data class NavigateToDetail(val productId: String) : ProductsEvent
}

class ProductsViewModel : ViewModel() {

    private val _events = MutableSharedFlow&#x3C;ProductsEvent>()
    val events = _events.asSharedFlow()

    fun onProductSelected(productId: String) {
        viewModelScope.launch {
            _events.emit(ProductsEvent.NavigateToDetail(productId))
        }
    }

    fun onRefreshFailed() {
        viewModelScope.launch {
            _events.emit(
                ProductsEvent.ShowMessage("No se pudo actualizar la lista")
            )
        }
    }
}

</code></pre>
<p>La UI puede recoger esos eventos dentro de un <code>LaunchedEffect</code>.</p>
<pre><code class="language-kotlin">@Composable
fun ProductsRoute(
    viewModel: ProductsViewModel,
    onNavigateToDetail: (String) -> Unit
) {
    val snackbarHostState = remember { SnackbarHostState() }

    LaunchedEffect(Unit) {
        viewModel.events.collect { event ->
            when (event) {
                is ProductsEvent.NavigateToDetail -> {
                    onNavigateToDetail(event.productId)
                }

                is ProductsEvent.ShowMessage -> {
                    snackbarHostState.showSnackbar(event.text)
                }
            }
        }
    }

    Scaffold(
        snackbarHost = {
            SnackbarHost(hostState = snackbarHostState)
        }
    ) {
        ProductsScreen(viewModel = viewModel)
    }
}

</code></pre>
<h3>Precaución con eventos de navegación</h3>
<p><code>SharedFlow</code> no convierte automáticamente los eventos en “exactamente una vez”. La configuración de <code>replay</code>, el buffer y el momento en que empieza el colector afectan a qué emisiones recibe cada consumidor. <code>SharedFlow</code> distribuye los valores entre los colectores activos y puede configurarse para conservar emisiones recientes. (<a href="https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/?utm_source=chatgpt.com" title="SharedFlow | kotlinx.coroutines">Kotlin</a>)</p>
<p>Para eventos de UI, una configuración frecuente es dejar <code>replay = 0</code>. Así, al recrearse la pantalla, no se reproduce por accidente una navegación o un mensaje ya consumido.</p>
<h2>Cuándo usar Compose State</h2>
<p>Compose State se refiere a valores observables por Compose, como <code>mutableStateOf</code>, <code>mutableStateListOf</code> o <code>derivedStateOf</code>. Es la mejor herramienta para estado estrictamente local a una composición.</p>
<pre><code class="language-kotlin">@Composable
fun SearchBar(
    onSearch: (String) -> Unit
) {
    var query by rememberSaveable { mutableStateOf("") }

    OutlinedTextField(
        value = query,
        onValueChange = {
            query = it
            onSearch(it)
        },
        label = { Text("Buscar") }
    )
}

</code></pre>
<p>Aquí <code>query</code> pertenece al componente. No hace falta exponerlo desde un <code>ViewModel</code> si no se comparte con otras partes de la app ni representa una decisión de negocio.</p>
<p>Usa <code>remember</code> cuando el valor solo deba durar mientras el composable permanezca en composición. Usa <code>rememberSaveable</code> cuando también deba restaurarse tras cambios de configuración o recreación del proceso cuando el tipo sea guardable.</p>
<h3>Buenas situaciones para Compose State</h3>
<ul>
<li>Estado visual temporal: un menú desplegado, una pestaña seleccionada o una animación.</li>
<li>Texto de un campo antes de enviarlo al dominio.</li>
<li>Posición de scroll, foco o visibilidad de elementos.</li>
<li>Valores derivados solo para optimizar la composición.</li>
</ul>
<p>Evita almacenar datos de negocio importantes exclusivamente con <code>mutableStateOf</code> dentro de un composable. Al sacar y volver a crear esa pantalla, el estado podría desaparecer.</p>
<h2>Guía rápida de decisión</h2>
<table>
<thead>
<tr>
<th>Necesidad</th>
<th>Opción recomendada</th>
</tr>
</thead>
<tbody>
<tr>
<td>Representar el estado actual de una pantalla</td>
<td>StateFlow</td>
</tr>
<tr>
<td>Exponer estado desde un ViewModel</td>
<td>StateFlow</td>
</tr>
<tr>
<td>Lanzar un Snackbar, navegación o efecto puntual</td>
<td>SharedFlow</td>
</tr>
<tr>
<td>Compartir una señal entre varios colectores activos</td>
<td>SharedFlow</td>
</tr>
<tr>
<td>Controlar un detalle visual local de Compose</td>
<td>mutableStateOf</td>
</tr>
<tr>
<td>Mantener un valor durante recomposición</td>
<td>remember</td>
</tr>
<tr>
<td>Restaurar un valor local guardable</td>
<td>rememberSaveable</td>
</tr>
</tbody>
</table>
<h2>Una arquitectura práctica</h2>
<p>Una combinación frecuente y fácil de mantener es esta:</p>
<ul>
<li>El <code>ViewModel</code> expone un único <code>StateFlow&#x3C;UiState></code> para dibujar la pantalla.</li>
<li>El <code>ViewModel</code> expone un <code>SharedFlow&#x3C;UiEvent></code> para efectos puntuales.</li>
<li>Los composables usan Compose State para detalles locales de interacción.</li>
<li>La lógica de negocio y las fuentes de datos permanecen fuera de Compose.</li>
</ul>
<p>Esta separación reduce errores típicos: mensajes que reaparecen al rotar la pantalla, pantallas que no pueden reconstruirse o estados locales que terminan filtrándose a toda la arquitectura.</p>
<p>La regla final es sencilla: usa <code>StateFlow</code> para lo que <strong>es</strong>, <code>SharedFlow</code> para lo que <strong>ocurre</strong> y Compose State para lo que la interfaz <strong>necesita recordar localmente</strong>.</p>]]></content:encoded>
  </item>
  <item>
    <title>Cómo estructurar una app Android moderna en 2026</title>
    <link>https://cursosdroid.com/articles/como-estructurar-una-app-android-moderna-en-2026</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/como-estructurar-una-app-android-moderna-en-2026</guid>
    <pubDate>Wed, 24 Jun 2026 06:16:12 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>Una arquitectura Android moderna debe facilitar el crecimiento, las pruebas y la adaptación a múltiples pantallas. Descubre cómo combinar Compose, capas claras, módulos Gradle y navegación tipada sin crear una estructura innecesariamente compleja.</description>
    <category>Android</category>
    <category>Kotlin</category>
    <category>Jetpack Compose</category>
    <category>Arquitectura</category>
    <content:encoded><![CDATA[<p>Una app Android moderna no se organiza solo por carpetas. Se organiza para que el código sea fácil de entender, probar, ampliar y mantener cuando el proyecto deja de tener tres pantallas y empieza a crecer.</p>
<p>En 2026, la base recomendada sigue siendo separar claramente la interfaz, los datos y la lógica de negocio. Jetpack Compose encaja especialmente bien con este enfoque porque la UI puede renderizarse como una función del estado, mientras que los <code>ViewModel</code> exponen ese estado y coordinan las acciones del usuario. </p>
<h2>Parte de una arquitectura por capas</h2>
<p>La estructura más útil para la mayoría de aplicaciones tiene dos capas obligatorias y una opcional:</p>
<table>
<thead>
<tr>
<th>Capa</th>
<th>Responsabilidad</th>
</tr>
</thead>
<tbody>
<tr>
<td>UI</td>
<td>Mostrar estado, recoger eventos y gestionar lógica de presentación</td>
</tr>
<tr>
<td>Data</td>
<td>Obtener, guardar y sincronizar datos</td>
</tr>
<tr>
<td>Domain</td>
<td>Encapsular casos de uso complejos o reutilizados</td>
</tr>
</tbody>
</table>
<p>La capa <code>domain</code> no debe aparecer por obligación. Android recomienda usarla cuando aporta valor real, por ejemplo, cuando una regla de negocio se reutiliza entre varios <code>ViewModel</code> o requiere coordinación entre varios repositorios.</p>
<p>Una estructura inicial puede ser esta:</p>
<pre><code class="language-kotlin">app/
core/
  common/
  model/
  ui/
  data/
feature/
  home/
  profile/
  settings/

</code></pre>
<p>El módulo <code>app</code> compone la aplicación: configura la navegación global, inyección de dependencias y punto de entrada. Cada módulo <code>feature</code> contiene una funcionalidad visible para el usuario, como inicio, perfil o ajustes.</p>
<h2>Organiza cada funcionalidad alrededor de su caso de uso</h2>
<p>Evita una estructura global basada únicamente en tipos técnicos:</p>
<pre><code class="language-kotlin">ui/
viewmodel/
repository/
network/

</code></pre>
<p>Funciona al principio, pero obliga a recorrer todo el proyecto para entender una pantalla. Es preferible agrupar por funcionalidad y mantener dentro de ella sus elementos relacionados:</p>
<pre><code class="language-kotlin">feature/
  profile/
    data/
      ProfileRepository.kt
      ProfileRemoteDataSource.kt
    domain/
      GetProfileUseCase.kt
    ui/
      ProfileScreen.kt
      ProfileViewModel.kt
      ProfileUiState.kt

</code></pre>
<p>Así, una persona que necesite modificar el perfil puede localizar la mayor parte del código sin saltar por múltiples paquetes.</p>
<h2>Usa un flujo de datos unidireccional</h2>
<p>Compose funciona mejor cuando la pantalla recibe un estado inmutable y envía eventos hacia el <code>ViewModel</code>.</p>
<p>El <code>ViewModel</code> no debería exponer entidades de red ni detalles de Room directamente. Debe transformar esos datos en un modelo pensado para representar la interfaz, como <code>ProfileUiState</code>.</p>
<pre><code class="language-kotlin">data class ProfileUiState(
    val isLoading: Boolean = false,
    val userName: String = "",
    val email: String = "",
    val errorMessage: String? = null
)

class ProfileViewModel(
    private val getProfile: GetProfileUseCase
) : ViewModel() {

    private val _uiState = MutableStateFlow(ProfileUiState(isLoading = true))
    val uiState: StateFlow&#x3C;ProfileUiState> = _uiState.asStateFlow()

    fun loadProfile() {
        viewModelScope.launch {
            runCatching { getProfile() }
                .onSuccess { profile ->
                    _uiState.value = ProfileUiState(
                        userName = profile.name,
                        email = profile.email
                    )
                }
                .onFailure {
                    _uiState.value = ProfileUiState(
                        errorMessage = "No se pudo cargar el perfil"
                    )
                }
        }
    }
}

</code></pre>
<p>La pantalla se limita a observar el estado y emitir eventos:</p>
<pre><code class="language-kotlin">@Composable
fun ProfileRoute(
    viewModel: ProfileViewModel,
    onBack: () -> Unit
) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()

    ProfileScreen(
        state = state,
        onBack = onBack,
        onRetry = viewModel::loadProfile
    )
}

</code></pre>
<p>Esta separación simplifica las pruebas: puedes verificar la lógica del <code>ViewModel</code> sin levantar Compose ni depender de una <code>Activity</code>.</p>
<h2>Define repositorios como frontera de datos</h2>
<p>La capa de datos debe ocultar de dónde procede la información. Un <code>Repository</code> puede combinar API, caché, base de datos local y preferencias sin que la UI tenga que conocer esos detalles.</p>
<pre><code class="language-kotlin">interface ProfileRepository {
    suspend fun getProfile(): Profile
    suspend fun refreshProfile(): Profile
}

</code></pre>
<p>Una implementación puede decidir devolver primero datos locales y actualizar después desde red. El resto de la app depende de la interfaz, no de Retrofit, Room o una API concreta.</p>
<p>Android recomienda que los repositorios centralicen el acceso y los cambios sobre los datos, incluyendo la resolución de conflictos entre fuentes locales y remotas.</p>
<h2>Modulariza cuando el proyecto lo justifique</h2>
<p>Los módulos Gradle ayudan a reducir acoplamiento, mejorar el aislamiento de funcionalidades y permitir compilaciones más focalizadas. Sin embargo, una modularización excesiva también aumenta la configuración y la complejidad de navegación.</p>
<p>Una distribución práctica es:</p>
<pre><code class="language-kotlin">:app
:core:common
:core:model
:core:ui
:core:network
:core:database
:feature:home
:feature:profile
:feature:settings

</code></pre>
<p>Mantén estas reglas:</p>
<ul>
<li><code>feature</code> puede depender de módulos <code>core</code>.</li>
<li>Un módulo <code>feature</code> no debería depender directamente de otro <code>feature</code>.</li>
<li>Los modelos compartidos deben vivir en <code>core:model</code> o en una capa de dominio bien definida.</li>
<li>El módulo <code>app</code> integra las funcionalidades y decide la navegación de alto nivel.</li>
</ul>
<p>La guía oficial de Android indica que no existe una única estrategia válida de modularización: debe adaptarse al tamaño, equipo y necesidades reales del proyecto.</p>
<h2>Navegación tipada y adaptable</h2>
<p>Para aplicaciones nuevas basadas en Compose, Navigation 3 aporta una navegación centrada en Compose, control explícito sobre la pila de navegación y soporte para diseños adaptativos en distintos tamaños de ventana.</p>
<p>Evita rutas construidas manualmente con cadenas:</p>
<pre><code class="language-kotlin">"detail/$userId"

</code></pre>
<p>Prefiere destinos tipados y serializables. Esto reduce errores por parámetros mal escritos o tipos incorrectos y hace que los cambios sean más seguros durante el refactor.</p>
<p>La navegación debe vivir cerca de la capa de aplicación, mientras que cada funcionalidad expone sus destinos y contratos públicos. De este modo, una pantalla no necesita conocer la implementación interna de otra.</p>
<h2>Diseña pensando en múltiples dispositivos</h2>
<p>Una app Android ya no debería asumir que siempre se verá en un móvil vertical. Compose está orientado a crear interfaces adaptables para distintos tamaños de pantalla.</p>
<p>No dupliques pantallas para tablet, plegables o escritorio. Mantén el mismo estado y adapta la composición visual:</p>
<ul>
<li>Barra inferior en pantallas compactas.</li>
<li>Panel lateral en pantallas medianas o expandidas.</li>
<li>Dos paneles para listas y detalle cuando haya espacio.</li>
<li>Navegación independiente del diseño concreto.</li>
</ul>
<p>La regla es simple: el dominio y el estado no deberían cambiar porque cambie el ancho de la ventana.</p>
<h2>Deja preparada la puerta a Kotlin Multiplatform</h2>
<p>No todas las apps Android necesitan Kotlin Multiplatform, pero estructurar bien el proyecto facilita adoptarlo más adelante. Google respalda oficialmente KMP para compartir lógica de negocio entre Android e iOS.</p>
<p>Los mejores candidatos para compartir son:</p>
<ul>
<li>Modelos y reglas de negocio.</li>
<li>Casos de uso.</li>
<li>Clientes de red.</li>
<li>Repositorios con interfaces comunes.</li>
<li>Persistencia compatible, como Room para KMP cuando el caso lo requiera.</li>
</ul>
<p>La UI de Compose para Android puede mantenerse específica de plataforma mientras la lógica central evoluciona en un módulo compartido.</p>
<h2>Una estructura que evoluciona contigo</h2>
<p>La mejor arquitectura no es la más compleja, sino la que permite añadir una funcionalidad sin romper cinco capas ni duplicar reglas de negocio.</p>
<p>Empieza con UI, datos y <code>ViewModel</code> bien delimitados. Añade dominio cuando haya lógica relevante. Modulariza cuando el proyecto y el equipo lo necesiten. Mantén la navegación tipada, el estado inmutable y las dependencias dirigidas hacia abstracciones.</p>
<p>Con esa base, tu app estará preparada para crecer en funcionalidades, dispositivos y plataformas sin convertir cada cambio en una operación de alto riesgo.</p>]]></content:encoded>
  </item>
  <item>
    <title>Por qué Jetpack Navigation 3 es importante para las apps modernas con Compose</title>
    <link>https://cursosdroid.com/articles/por-que-jetpack-navigation-3-es-importante-para-las-apps-modernas-con-compose</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/por-que-jetpack-navigation-3-es-importante-para-las-apps-modernas-con-compose</guid>
    <pubDate>Wed, 24 Jun 2026 06:04:12 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>Jetpack Navigation 3 adopta un modelo de navegación centrado en el estado de Compose, con control explícito del back stack y soporte natural para interfaces adaptativas. Descubre por qué puede simplificar la arquitectura de tus próximas aplicaciones Android.</description>
    <category>Android</category>
    <category>Jetpack Compose</category>
    <category>Jetpack Navigation</category>
    <category>Arquitectura Android</category>
    <content:encoded><![CDATA[<p>La navegación suele parecer un detalle de interfaz hasta que una aplicación crece. Flujos de autenticación, enlaces profundos, pestañas, pantallas de detalle, diálogos, tablets y ventanas redimensionables convierten el simple hecho de “ir a otra pantalla” en una decisión de arquitectura.</p>
<p>Jetpack Navigation 3 es importante porque replantea esa navegación para el modelo declarativo de Jetpack Compose. En lugar de ocultar el estado de navegación tras un controlador, permite que la aplicación trate el back stack como lo que realmente es: estado observable que define qué contenido debe mostrarse.</p>
<h2>Navigation 3 encaja mejor con la filosofía de Compose</h2>
<p>Compose describe la interfaz como una función del estado. Cuando cambia el estado, la UI se recompone. Navigation 3 lleva esa misma idea a la navegación: el historial de pantallas es una lista de claves y la interfaz se actualiza al cambiar esa lista.</p>
<p>Esto reduce la distancia conceptual entre el estado de la aplicación y la navegación. No necesitas pensar en la navegación como un sistema separado de la UI; pasa a ser otra parte del estado que puedes modelar, observar y probar.</p>
<p>En términos simples, navegar hacia delante consiste en añadir una clave al back stack. Volver atrás consiste en eliminar la última clave.</p>
<pre><code class="language-kotlin">data object ProductList
data class ProductDetail(val id: String)

@Composable
fun StoreApp() {
    val backStack = remember {
        mutableStateListOf&#x3C;Any>(ProductList)
    }

    Button(
        onClick = {
            backStack.add(ProductDetail(id = "android-curso"))
        }
    ) {
        Text("Ver curso")
    }
}

</code></pre>
<p>La clave <code>ProductDetail</code> no contiene una pantalla completa ni lógica de presentación. Solo representa el destino y los datos mínimos necesarios para identificarlo. Esta separación ayuda a mantener una arquitectura más clara.</p>
<h2>El back stack deja de ser una caja negra</h2>
<p>Con enfoques tradicionales, el historial de navegación suele estar gestionado internamente por un <code>NavController</code>. Eso resulta cómodo en escenarios simples, pero puede dificultar casos avanzados: restaurar flujos concretos, coordinar varias columnas o mantener estados independientes por sección.</p>
<p>Navigation 3 adopta otra postura: <strong>la aplicación es propietaria del back stack</strong>. La librería observa ese estado y lo representa mediante <code>NavDisplay</code>.</p>
<p>Esta decisión es relevante porque aporta control real. Puedes decidir dónde vive el back stack, cómo se conserva, cómo se restaura y qué reglas de negocio afectan a cada transición.</p>
<p>Por ejemplo, una aplicación puede evitar que un usuario abandone un formulario con cambios sin guardar:</p>
<pre><code class="language-kotlin">fun navigateBack(
    backStack: MutableList&#x3C;Any>,
    hasPendingChanges: Boolean,
    onShowDiscardDialog: () -> Unit
) {
    if (hasPendingChanges) {
        onShowDiscardDialog()
    } else {
        backStack.removeLastOrNull()
    }
}

</code></pre>
<p>La navegación no queda desligada de la lógica del producto. Puede responder a permisos, cambios pendientes, autenticación o reglas de negocio sin forzar soluciones indirectas.</p>
<h2>Facilita interfaces adaptativas para móviles, tablets y plegables</h2>
<p>La importancia de Navigation 3 no se limita a cambiar de pantalla. También está pensada para mostrar más de un destino del back stack al mismo tiempo.</p>
<p>Este enfoque encaja especialmente bien con interfaces adaptativas. En un móvil compacto puedes mostrar una lista y, al abrir un elemento, navegar a una pantalla de detalle. En una tablet, la lista y el detalle pueden convivir en dos paneles sin tener que inventar una arquitectura de navegación paralela.</p>
<p>El back stack sigue expresando la intención del usuario: ha abierto una lista y ha seleccionado un detalle. Lo que cambia es la forma de presentar esa información según el tamaño de ventana disponible.</p>
<p>Esto evita diseñar una aplicación “para móvil” y después añadir excepciones para pantallas grandes. La navegación puede partir del mismo estado y adaptar su representación visual.</p>
<h2>Mejora la modularización de aplicaciones grandes</h2>
<p>En proyectos pequeños es habitual definir todas las rutas en un único archivo. En productos reales, esa estrategia acaba generando módulos centrales demasiado grandes, dependencias innecesarias y conflictos entre equipos.</p>
<p>Navigation 3 organiza el contenido navegable mediante entradas. Cada funcionalidad puede aportar sus propias entradas y resolver sus claves sin que toda la aplicación conozca los detalles internos de cada módulo.</p>
<p>Una estructura posible sería:</p>
<ul>
<li><code>feature-home</code>: define <code>HomeRoute</code> y su contenido.</li>
<li><code>feature-courses</code>: define <code>CourseListRoute</code> y <code>CourseDetailRoute</code>.</li>
<li><code>feature-profile</code>: define <code>ProfileRoute</code>.</li>
<li><code>app</code>: compone las entradas de todos los módulos y gestiona el flujo global.</li>
</ul>
<p>Esta separación ayuda a que una funcionalidad evolucione sin convertir el grafo de navegación en un punto de acoplamiento masivo.</p>
<h2>Hace más natural la persistencia del estado</h2>
<p>Los usuarios esperan volver a una app y encontrarla donde la dejaron. También esperan que una rotación de pantalla no reinicie su flujo actual.</p>
<p>Como Navigation 3 trabaja con claves, y no con pantallas completas almacenadas en el historial, resulta más directo guardar y restaurar el back stack. Una clave como <code>ProductDetail("android-curso")</code> tiene significado fuera de la composición actual: identifica un destino que la app puede reconstruir después.</p>
<p>Esto también obliga a tomar una buena decisión de diseño: el back stack debe contener referencias ligeras, no objetos pesados ni estados efímeros de pantalla.</p>
<h2>No elimina la necesidad de diseñar bien la navegación</h2>
<p>Navigation 3 ofrece más control, pero ese control también exige disciplina. Tener acceso directo al back stack no significa que cualquier composable deba modificarlo libremente.</p>
<p>Conviene encapsular las acciones de navegación en eventos claros, como <code>onCourseSelected</code>, <code>onProfileRequested</code> u <code>onBackPressed</code>. De ese modo, la UI expresa intenciones y una capa superior decide cómo actualizar el estado de navegación.</p>
<p>La librería tampoco sustituye decisiones de experiencia de usuario. Sigue siendo necesario definir correctamente qué destinos son principales, cuándo conservar una pestaña, cómo manejar enlaces profundos y qué comportamiento debe tener el botón Atrás.</p>
<h2>Una base preparada para el futuro de Compose</h2>
<p>Jetpack Navigation 3 es importante porque acerca la navegación a la forma de pensar que ya usamos en Compose: estado explícito, UI declarativa y composición flexible.</p>
<p>Para aplicaciones sencillas, el cambio puede parecer pequeño. Para productos con múltiples flujos, módulos, pantallas grandes o requisitos de restauración de estado, el beneficio es mayor: menos soluciones especiales, mejor control arquitectónico y una relación más directa entre la intención del usuario y la interfaz que se muestra.</p>
<p>Adoptarlo no consiste solo en cambiar una dependencia. Consiste en tratar la navegación como estado de primera clase dentro de la aplicación.</p>]]></content:encoded>
  </item>
  <item>
    <title>Google I/O 2026</title>
    <link>https://cursosdroid.com/articles/google-i-o-2026</link>
    <guid isPermaLink="true">https://cursosdroid.com/articles/google-i-o-2026</guid>
    <pubDate>Tue, 23 Jun 2026 21:07:26 GMT</pubDate>
    <dc:creator>Mario Ríos Holgado</dc:creator>
    <description>Google I/O 2026 reúne las novedades clave para el ecosistema Android: nuevas capacidades de IA, herramientas para desarrolladores y mejoras para crear experiencias más rápidas, inteligentes y multiplataforma.</description>
    <category>Android</category>
    <category>Kotlin</category>
    <content:encoded><![CDATA[<p>Google I/O 2026 ha dejado un mensaje claro para quienes desarrollamos en Android: la plataforma evoluciona hacia experiencias más inteligentes, adaptativas y distribuidas entre dispositivos. La inteligencia artificial ya no aparece solo como una función dentro de la app, sino como una capa del sistema capaz de entender contexto, ejecutar tareas y conectar servicios.</p>
<p>La consecuencia para los equipos Android es importante: además de construir buenas pantallas, habrá que diseñar funciones que puedan ser descubiertas, invocadas y ejecutadas de forma segura desde asistentes, widgets, coches, relojes y otros puntos de entrada.</p>
<h2>Android se convierte en un sistema de inteligencia</h2>
<p>Una de las ideas centrales del evento es la transición de Android desde un sistema operativo tradicional hacia un “sistema de inteligencia”. Gemini podrá automatizar determinadas tareas dentro de las aplicaciones, mientras que las nuevas <code>AppFunctions</code> permitirán a las apps exponer acciones y capacidades al sistema.</p>
<p>Estas funciones se apoyan en un enfoque compatible con Model Context Protocol, de modo que una aplicación puede ofrecer herramientas, datos y operaciones a agentes del dispositivo de una forma más estructurada. Por ejemplo, una app de viajes podría exponer acciones para consultar una reserva, mostrar una tarjeta de embarque o buscar el estado de un vuelo.</p>
<pre><code class="language-kotlin">data class FlightStatus(
    val code: String,
    val departureTime: String,
    val gate: String?,
    val delayed: Boolean
)

class FlightRepository {
    fun getStatus(code: String): FlightStatus {
        return FlightStatus(
            code = code,
            departureTime = "18:45",
            gate = "B12",
            delayed = false
        )
    }
}

</code></pre>
<p>El reto no consiste solo en conectar una API de IA. Las acciones deben ser pequeñas, verificables y seguras. Una buena <code>AppFunction</code> debería responder a una intención concreta, aplicar permisos correctos y evitar operaciones irreversibles sin confirmación del usuario. Google ha presentado estas APIs como una vista previa experimental, por lo que conviene explorarlas en prototipos antes de convertirlas en una dependencia crítica de producción. (<a href="https://developer.android.com/blog/posts/top-ai-on-android-updates-for-building-intelligent-experiences-from-google-i-o-26" title="Top AI on Android updates for building intelligent experiences from Google I/O ‘26  |  Android Developers&#x27; Blog">Android Developers</a>)</p>
<h2>Gemini Nano y la IA en el dispositivo</h2>
<p>La IA local sigue ganando importancia. Google mostró la vista previa de Gemini Nano 4 mediante AIcore, junto con mejoras en las APIs generativas de ML Kit. El objetivo es facilitar experiencias con menor latencia, mayor privacidad y menos dependencia de una conexión constante.</p>
<p>Este enfoque encaja especialmente bien en funciones como clasificación de texto, resúmenes de contenido local, asistencia contextual, extracción de información y procesamiento de cámara o multimedia. No todas las tareas requieren enviar datos a un servidor remoto, y la arquitectura de una app debería decidir dónde ejecutar cada modelo según coste, privacidad, calidad y disponibilidad.</p>
<h2>Compose First: el futuro de la interfaz Android</h2>
<p>Google ha confirmado una dirección muy explícita: Jetpack Compose pasa a ser el estándar principal para la interfaz, mientras que Views entra en modo de mantenimiento. Esto no significa que las apps existentes deban reescribirse de inmediato, pero sí que las nuevas guías, librerías y capacidades se orientarán primero a Compose. (<a href="https://developer.android.com/blog/posts/17-things-to-know-for-android-developers-at-google-i-o" title="17 Things to know for Android developers at Google I/O!  |  Android Developers&#x27; Blog">Android Developers</a>)</p>
<p>Para muchos equipos, la estrategia más razonable será migrar de forma incremental. Las nuevas pantallas, componentes reutilizables y experiencias adaptativas son buenos candidatos para Compose, mientras que las vistas heredadas pueden mantenerse hasta que exista una razón de producto o mantenimiento para sustituirlas.</p>
<pre><code class="language-kotlin">@Composable
fun BookingCard(
    destination: String,
    date: String,
    modifier: Modifier = Modifier
) {
    Card(modifier = modifier.fillMaxWidth()) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = destination,
                style = MaterialTheme.typography.titleLarge
            )
            Text(
                text = date,
                style = MaterialTheme.typography.bodyMedium
            )
        }
    }
}

</code></pre>
<p>El foco ya no es solo crear interfaces bonitas. Compose facilita construir diseños que se adapten a pantallas grandes, plegables, ventanas redimensionables y superficies emergentes.</p>
<h2>Android 17 exige aplicaciones más adaptativas</h2>
<p>Android 17 introduce cambios relevantes para quienes preparan la compatibilidad con API 37. Entre ellos destacan la redimensionabilidad obligatoria en pantallas grandes, el endurecimiento del audio en segundo plano, protecciones para códigos SMS OTP, transparencia de certificados activada por defecto y restricciones de acceso a la red local. (<a href="https://developer.android.com/blog/posts/17-things-to-know-for-android-developers-at-google-i-o" title="17 Things to know for Android developers at Google I/O!  |  Android Developers&#x27; Blog">Android Developers</a>)</p>
<p>La adaptación deja de ser un extra para tablets y foldables. Google plantea un estándar “adaptive first”: los usuarios alternan entre móvil, tableta, portátil, coche, reloj y experiencias XR durante el día. Una app que solo funciona bien en un teléfono vertical corre el riesgo de ofrecer una experiencia incompleta.</p>
<p>La recomendación práctica es revisar tres áreas:</p>
<ul>
<li>Diseños con <code>WindowSizeClass</code> y paneles adaptativos.</li>
<li>Navegación que soporte pantallas grandes y multitarea.</li>
<li>Pruebas en emuladores de tablet, plegables y ventanas redimensionables.</li>
</ul>
<h2>Productividad asistida por agentes en Android Studio</h2>
<p>La edición de Android Studio presentada en I/O 2026 refuerza el desarrollo asistido por agentes. Android CLI permite que herramientas de IA interactúen con tareas específicas de Android, como analizar archivos, resolver símbolos, detectar advertencias o renderizar previews de Compose. (<a href="https://developer.android.com/blog/posts/17-things-to-know-for-android-developers-at-google-i-o" title="17 Things to know for Android developers at Google I/O!  |  Android Developers&#x27; Blog">Android Developers</a>)</p>
<p>También se presentó la posibilidad de generar aplicaciones Android nativas desde Google AI Studio a partir de una descripción. El flujo incluye generación de proyectos Kotlin con Compose, vista previa en emulador integrado, despliegue en dispositivos físicos y exportación del proyecto para abrirlo en Android Studio. (<a href="https://android-developers.googleblog.com/2026/05/whats-new-android-developer-tools.html" title="Android Developers Blog: Android Studio I/O Edition: What’s new in Android Developer tools">Android Developers Blog</a>)</p>
<p>Esto puede acelerar prototipos, pruebas de concepto y generación de pantallas repetitivas. Sin embargo, no elimina la necesidad de revisar arquitectura, seguridad, accesibilidad, pruebas y comportamiento offline. La IA puede producir código rápidamente; el equipo sigue siendo responsable de decidir qué código merece llegar a producción.</p>
<h2>Rendimiento, memoria y herramientas de calidad</h2>
<p>Google también puso el foco en la eficiencia. Android 17 incorpora límites de memoria basados en la RAM del dispositivo para reducir problemas de fugas y consumo excesivo. En paralelo, Android Studio incorpora <code>R8 Configuration Analyzer</code>, una herramienta para identificar reglas <code>keep</code> demasiado amplias que impiden eliminar y optimizar código. (<a href="https://developer.android.com/blog/posts/building-premium-android-experiences-at-google-i-o-26" title="Building Premium Android Experiences at Google I/O ‘26  |  Android Developers&#x27; Blog">Android Developers</a>)</p>
<p>Este tipo de mejora tiene impacto directo en el usuario: menos tamaño de descarga, arranques más rápidos, menor consumo de memoria y menos cierres inesperados. La optimización ya no debería reservarse para la fase final del proyecto.</p>
<h2>Más superficies: widgets, coches, Wear OS y Play</h2>
<p>La presencia de una app ya no termina al cerrar su actividad principal. Google impulsa Jetpack Glance como un modelo compartido para widgets de móvil, Wear OS y coches, con <code>RemoteCompose</code> para crear experiencias ligeras y expresivas en superficies remotas. (<a href="https://developer.android.com/blog/posts/building-premium-android-experiences-at-google-i-o-26" title="Building Premium Android Experiences at Google I/O ‘26  |  Android Developers&#x27; Blog">Android Developers</a>)</p>
<p>En Google Play, la distribución también cambia. Las aplicaciones podrán ganar visibilidad dentro de Gemini, y Google planea facilitar el descubrimiento de contenidos mediante sugerencias y enlaces profundos hacia experiencias concretas de la app. Además, Play Shorts introduce un formato de vídeo vertical para mostrar el producto directamente en la tienda. (<a href="https://developer.android.com/blog/posts/i-o-2026-what-s-new-in-google-play" title="I/O 2026: What&#x27;s new in Google Play  |  Android Developers&#x27; Blog">Android Developers</a>)</p>
<h2>Qué priorizar después de Google I/O 2026</h2>
<p>La mejor respuesta a estas novedades no es adoptar todo de golpe. Conviene priorizar una hoja de ruta realista:</p>
<ol>
<li>Revisar la preparación para Android 17 y API 37.</li>
<li>Definir Compose como opción predeterminada para desarrollo nuevo.</li>
<li>Auditar layouts para tablets, plegables y ventanas grandes.</li>
<li>Identificar una o dos acciones candidatas para <code>AppFunctions</code>.</li>
<li>Medir tamaño, arranque y consumo de memoria con R8 y perfiles reales.</li>
<li>Evaluar cómo widgets, Gemini y Google Play pueden mejorar el descubrimiento de la app.</li>
</ol>
<p>Google I/O 2026 no redefine Android por una sola API, sino por la combinación de IA contextual, interfaces adaptativas, automatización del desarrollo y presencia en más dispositivos. El valor diferencial seguirá estando en construir experiencias claras, rápidas y confiables, pero ahora deberán funcionar también como parte de un ecosistema inteligente.</p>]]></content:encoded>
  </item>
  </channel>
</rss>
