Publicación y entrega de una app Android con Kotlin

Capítulo 13

Tiempo estimado de lectura: 9 minutos

+ Ejercicio

Objetivo del flujo de publicación

Publicar una app Android implica convertir tu proyecto en un paquete distribuible (APK o AAB) firmado, versionado y configurado para release, con un mínimo de información para el usuario (por ejemplo, pantalla “Acerca de”) y una verificación final de estabilidad. En este capítulo seguirás un flujo reproducible: preparar versiones → configurar build types y firmado → generar APK/AAB → validar permisos y ajustes mínimos → ejecutar checklist final.

1) Versionado: cómo se identifica tu app en cada entrega

Conceptos clave

  • versionCode: entero que debe aumentar en cada publicación. Android/Play lo usa para ordenar actualizaciones.
  • versionName: texto visible para el usuario (por ejemplo, 1.4.2).
  • applicationId: identificador único del paquete (por ejemplo, com.tuempresa.tuapp). No debe cambiar en producción porque rompe actualizaciones.

Dónde se configura (Gradle)

En proyectos modernos, la configuración suele estar en app/build.gradle.kts dentro de android { defaultConfig { ... } }. Ejemplo:

android {    namespace = "com.tuempresa.tuapp"    defaultConfig {        applicationId = "com.tuempresa.tuapp"        minSdk = 24        targetSdk = 34        versionCode = 12        versionName = "1.2.0"    }}

Estrategia práctica de versionado

  • Usa versionName con semver (MAJOR.MINOR.PATCH) para comunicar cambios.
  • Incrementa versionCode siempre, incluso si solo cambias metadatos.
  • Si tienes CI/CD, automatiza el incremento de versionCode (por ejemplo, con un script que lea el último valor y sume 1).

2) Build types y configuración de release

Qué son build types

Los buildTypes definen variantes de compilación. Normalmente tendrás debug para desarrollo y release para distribución. En release se suelen activar optimizaciones, ofuscación y se deshabilitan logs/flags de depuración.

Configuración recomendada en Gradle (Kotlin DSL)

Ejemplo típico de buildTypes con minificación y shrink de recursos:

android {    buildTypes {        debug {            applicationIdSuffix = ".debug"            versionNameSuffix = "-debug"            isDebuggable = true        }        release {            isDebuggable = false            isMinifyEnabled = true            isShrinkResources = true            proguardFiles(                getDefaultProguardFile("proguard-android-optimize.txt"),                "proguard-rules.pro"            )        }    }}

ProGuard/R8: reglas mínimas

Si usas librerías que dependen de reflexión/serialización, podrías necesitar reglas. Mantén el archivo proguard-rules.pro bajo control de versiones y añade reglas solo cuando detectes problemas reales (crashes en release, clases eliminadas, etc.).

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

Ejemplo de regla genérica (solo como referencia; ajústala a tu caso):

# Mantener clases usadas por reflexión (ejemplo) -keep class com.tuempresa.tuapp.model.** { *; }

3) Firmado: keystore, claves y seguridad

Por qué es obligatorio firmar

Android exige que los paquetes estén firmados. La firma identifica al autor y permite que futuras actualizaciones sean aceptadas como la misma app. Si pierdes la clave de firma, no podrás actualizar la app publicada con esa identidad.

Crear un keystore (desde Android Studio)

Flujo recomendado:

  • Abre BuildGenerate Signed Bundle / APK.
  • Elige Android App Bundle (AAB) o APK.
  • En la pantalla de firma, selecciona Create new... para crear un .jks.
  • Completa: ruta del keystore, contraseña, alias, contraseña del alias y datos del certificado.
  • Guarda el .jks en un lugar seguro (idealmente fuera del repositorio) y respáldalo.

Buenas prácticas de seguridad

  • No subas el .jks al repositorio.
  • No hardcodees contraseñas en Gradle.
  • Usa gradle.properties local o variables de entorno en CI.

Configurar signingConfigs en Gradle (sin exponer secretos)

Una forma práctica es leer valores desde propiedades. En tu máquina, define en ~/.gradle/gradle.properties (archivo local):

RELEASE_STORE_FILE=/ruta/segura/tuapp-release.jksRELEASE_STORE_PASSWORD=tu_passwordRELEASE_KEY_ALIAS=tu_aliasRELEASE_KEY_PASSWORD=tu_password_alias

Luego, en app/build.gradle.kts:

android {    signingConfigs {        create("release") {            val storeFilePath = providers.gradleProperty("RELEASE_STORE_FILE").orNull            if (storeFilePath != null) {                storeFile = file(storeFilePath)            }            storePassword = providers.gradleProperty("RELEASE_STORE_PASSWORD").orNull            keyAlias = providers.gradleProperty("RELEASE_KEY_ALIAS").orNull            keyPassword = providers.gradleProperty("RELEASE_KEY_PASSWORD").orNull        }    }    buildTypes {        release {            signingConfig = signingConfigs.getByName("release")        }    }}

Si compilas en CI, configura esas propiedades como secretos del sistema y expórtalas como variables de entorno o inyecta un gradle.properties temporal.

4) Elegir formato de entrega: APK vs AAB

Cuándo usar cada uno

  • AAB (Android App Bundle): recomendado para tiendas como Google Play. Permite entrega optimizada por dispositivo (split APKs) y reduce tamaño.
  • APK: útil para distribución directa, pruebas internas, MDM/enterprise o sideloading controlado.

Generar AAB/APK firmado desde Android Studio

  • Menú BuildGenerate Signed Bundle / APK.
  • Selecciona Android App Bundle o APK.
  • Elige el keystore/alias, selecciona el build type release.
  • Finaliza y localiza el artefacto generado (Android Studio muestra la ruta).

Generar por línea de comandos (útil para CI)

Desde la raíz del proyecto:

# AAB./gradlew :app:bundleRelease# APK./gradlew :app:assembleRelease

Los outputs suelen quedar en app/build/outputs/ (por ejemplo, bundle/release o apk/release).

5) Configuración de release: flags, endpoints y logging

Separar comportamiento por build type

Es común que debug use endpoints de staging, logs verbosos o herramientas internas, mientras que release debe ser más estricto. Puedes usar buildConfigField para inyectar constantes:

android {    buildTypes {        debug {            buildConfigField("String", "BASE_URL", "\"https://staging.api.tuapp.com/\"")        }        release {            buildConfigField("String", "BASE_URL", "\"https://api.tuapp.com/\"")        }    }}

Y consumirlo:

val baseUrl = BuildConfig.BASE_URL

Desactivar logs en release

Evita imprimir información sensible. Una práctica simple es encapsular logs y no ejecutarlos en release:

object AppLog {    fun d(tag: String, msg: String) {        if (BuildConfig.DEBUG) android.util.Log.d(tag, msg)    }}

6) Pantalla de ajustes mínimos: “Acerca de” y versión

Qué debe incluir

  • Nombre de la app.
  • Versión (versionName y opcionalmente versionCode).
  • Enlace a políticas/soporte (si aplica).
  • Licencias de terceros (si tu app lo requiere).

Obtener la versión desde el sistema

Puedes leer la versión desde BuildConfig.VERSION_NAME y BuildConfig.VERSION_CODE. En Android moderno, VERSION_CODE puede ser Int o Long según configuración; para mostrarlo, conviértelo a texto.

val versionName = BuildConfig.VERSION_NAMEval versionCode = BuildConfig.VERSION_CODE.toString()

Ejemplo práctico con Jetpack Compose

Una pantalla simple de ajustes con sección “Acerca de”:

@Composablefun SettingsScreen(    onOpenLicenses: (() -> Unit)? = null,) {    val versionName = BuildConfig.VERSION_NAME    val versionCode = BuildConfig.VERSION_CODE.toString()    androidx.compose.material3.Surface {        androidx.compose.foundation.layout.Column(            modifier = androidx.compose.ui.Modifier                .fillMaxSize()                .padding(16.dp)        ) {            androidx.compose.material3.Text(                text = "Ajustes",                style = androidx.compose.material3.MaterialTheme.typography.headlineSmall            )            androidx.compose.foundation.layout.Spacer(Modifier.height(16.dp))            androidx.compose.material3.Text(                text = "Acerca de",                style = androidx.compose.material3.MaterialTheme.typography.titleMedium            )            androidx.compose.foundation.layout.Spacer(Modifier.height(8.dp))            androidx.compose.material3.Text(text = "Versión: $versionName ($versionCode)")            if (onOpenLicenses != null) {                androidx.compose.foundation.layout.Spacer(Modifier.height(12.dp))                androidx.compose.material3.Button(onClick = onOpenLicenses) {                    androidx.compose.material3.Text("Licencias")                }            }        }    }}

Si ya tienes navegación, registra esta pantalla como un destino más y enlázala desde un ícono de ajustes o desde un menú.

7) Revisar permisos declarados antes de liberar

Por qué importa

Los permisos afectan confianza del usuario, cumplimiento de políticas y fricción de instalación. Antes de publicar, asegúrate de declarar solo lo necesario.

Checklist de revisión de AndroidManifest.xml

  • Elimina permisos que ya no uses (por ejemplo, si quitaste una funcionalidad).
  • Verifica que los permisos “sensibles” estén justificados por una funcionalidad visible.
  • Revisa uses-feature si declaras hardware específico (cámara, GPS, etc.).
  • Confirma que no estás declarando permisos por dependencias que no necesitas (a veces se pueden remover con herramientas de manifest merge).

Cómo inspeccionar el manifest final (merged manifest)

En Android Studio, revisa el Merged Manifest del módulo app para ver el resultado final tras el merge de dependencias. Ahí podrás identificar de dónde viene cada permiso.

Ejemplo: remover un permiso agregado por una librería

Si una dependencia agrega un permiso no deseado, puedes removerlo con tools:node="remove" (requiere el namespace tools):

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools">    <uses-permission        android:name="android.permission.SOME_PERMISSION"        tools:node="remove" /></manifest>

Úsalo con cuidado: si la librería realmente lo necesita, podrías romper funcionalidad.

8) Checklist final de estabilidad antes de generar el paquete

Checklist técnico (reproducible)

ÁreaVerificaciónCómo comprobar
CompilaciónBuild release compila sin warnings críticos./gradlew :app:assembleRelease o :app:bundleRelease
ArranqueLa app abre en frío sin crashInstala el artefacto release en un dispositivo real
NavegaciónFlujos principales completosRecorre pantallas clave: login/onboarding/home/detalle
RedManejo de offline/erroresModo avión, red lenta, respuestas 4xx/5xx simuladas
PersistenciaMigraciones/lectura de datosActualizar desde versión anterior (si aplica) y validar datos
PermisosSolo los necesarios y con UX correctaRevisar manifest final y flujos que los solicitan
RendimientoSin ANR en flujos comunesUso normal + monitoreo básico en dispositivo
MinificaciónRelease funciona con R8Probar build minificado; revisar crashes por clases removidas
Logs/seguridadNo exponer tokens/PII en logsBuscar logs en código y validar que se deshabiliten en release
UITextos y layouts correctosProbar tamaños de pantalla y modo oscuro si aplica

Validación del artefacto generado

  • Verifica la firma: asegúrate de que el artefacto sea “signed”.
  • Verifica versión: que versionCode y versionName sean los esperados.
  • Instalación limpia: desinstala versiones previas e instala el release.
  • Actualización: si ya existe una versión instalada, instala la nueva para confirmar que actualiza correctamente (misma firma y mismo applicationId).

9) Flujo reproducible: del proyecto al paquete listo para liberar

Paso a paso

  1. Actualizar versionado en defaultConfig: incrementa versionCode y ajusta versionName.
  2. Revisar buildTypes: confirma que release tenga isDebuggable = false, minificación/shrink según tu necesidad y reglas R8 listas.
  3. Configurar firmado: crea/selecciona keystore y configura signingConfigs leyendo secretos desde propiedades.
  4. Revisar configuración de release: endpoints, flags y logging (evitar información sensible).
  5. Agregar/validar pantalla de ajustes mínimos: “Acerca de” con versión visible.
  6. Auditar permisos en el merged manifest: elimina permisos innecesarios y valida uses-feature.
  7. Ejecutar checklist de estabilidad en un dispositivo real y, si puedes, en al menos un emulador adicional.
  8. Generar artefacto: preferentemente AAB para tienda, o APK para distribución directa.
  9. Validar artefacto: firma, versión, instalación limpia y actualización.

Comandos mínimos para automatizar

# 1) Limpiar y compilar release./gradlew clean :app:bundleRelease# 2) Alternativa: APK./gradlew clean :app:assembleRelease

Con este flujo, tu proyecto queda preparado para entregar un paquete de release consistente, firmado y verificable.

Ahora responde el ejercicio sobre el contenido:

¿Qué combinación de acciones asegura que una nueva versión de una app se instale como actualización sobre la anterior (y no como una app distinta)?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Para que Android acepte la instalación como actualización, debe coincidir el applicationId y la firma (misma clave). Además, el versionCode debe incrementarse en cada publicación para ordenar correctamente las versiones.

Siguiente capítulo

Proyecto final: aplicación Android moderna en Kotlin de principio a fin

Arrow Right Icon
Portada de libro electrónico gratuitaAndroid desde Cero con Kotlin
93%

Android desde Cero con Kotlin

Nuevo curso

14 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.