¿Por qué firmar?

Android requiere que toda app esté firmada digitalmente antes de instalarse. La firma identifica al autor de la app y garantiza que las actualizaciones vienen de la misma persona — si alguien intercepta tu APK y lo modifica, la firma no va a coincidir y Android rechazará la instalación.

En desarrollo, Android Studio firma automáticamente con un debug keystore genérico. Para publicar en Play Store necesitás tu propio release keystore.

Crear el keystore

# Crear un keystore con keytool (incluido en el JDK)
keytool -genkeypair \
  -v \
  -keystore mi-app.keystore \
  -alias mi-app-key \
  -keyalg RSA \
  -keysize 2048 \
  -validity 10000

# El comando pedirá:
# - Contraseña del keystore
# - Contraseña de la clave
# - Nombre, organización, ciudad, país (para el certificado)

# También podés crearlo desde Android Studio:
# Build → Generate Signed Bundle / APK → Create new...

El keystore es irreemplazableSi perdés el keystore, perdés la capacidad de publicar actualizaciones de tu app en Play Store. Los usuarios tendrían que desinstalar e instalar la nueva versión. Guardalo en al menos dos lugares seguros (gestor de contraseñas, backup cifrado). Google recomienda usar Play App Signing — ellos guardan tu clave upload y mantienen una clave de firma separada.

Signing config en Gradle

// build.gradle (app)
android {
    signingConfigs {
        create("release") {
            storeFile = file("../keystore/mi-app.keystore")
            storePassword = "contraseña_del_keystore"
            keyAlias = "mi-app-key"
            keyPassword = "contraseña_de_la_clave"
        }
    }

    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
            minifyEnabled = true
            shrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Credenciales seguras — no en el código

Nunca pongas las contraseñas del keystore directamente en el build.gradle — ese archivo va al repositorio. Hay dos formas correctas:

// Opción 1: variables de entorno (ideal para CI/CD)
signingConfigs {
    create("release") {
        storeFile = file(System.getenv("KEYSTORE_PATH") ?: "../keystore/mi-app.keystore")
        storePassword = System.getenv("KEYSTORE_PASSWORD") ?: ""
        keyAlias = System.getenv("KEY_ALIAS") ?: "mi-app-key"
        keyPassword = System.getenv("KEY_PASSWORD") ?: ""
    }
}

// Opción 2: archivo local.properties (en .gitignore)
// En local.properties (NO commiteado):
// KEYSTORE_PASSWORD=mi_contraseña
// KEY_PASSWORD=mi_contraseña_clave

// En build.gradle:
import java.util.Properties
val localProps = Properties()
val localPropsFile = rootProject.file("local.properties")
if (localPropsFile.exists()) localProps.load(localPropsFile.inputStream())

signingConfigs {
    create("release") {
        storeFile = file("../keystore/mi-app.keystore")
        storePassword = localProps.getProperty("KEYSTORE_PASSWORD") ?: ""
        keyAlias = "mi-app-key"
        keyPassword = localProps.getProperty("KEY_PASSWORD") ?: ""
    }
}

Agregá el keystore al .gitignoreEl archivo .keystore tampoco debe ir al repositorio. Guardalo en un lugar seguro externo al repo. En el .gitignore: *.keystore, *.jks, local.properties.

Build types

Los build types permiten tener configuraciones diferentes para distintos entornos:

android {
    buildTypes {
        debug {
            applicationIdSuffix = ".debug"   // instala junto a release
            versionNameSuffix = "-debug"
            isDebuggable = true
            // No necesita signing config — usa el debug keystore automáticamente
        }

        // Build type personalizado para QA/staging
        create("staging") {
            initWith(getByName("debug"))       // hereda la config de debug
            applicationIdSuffix = ".staging"
            versionNameSuffix = "-staging"
            isDebuggable = true
            signingConfig = signingConfigs.getByName("debug")
            // Se puede conectar a un backend de staging
        }

        release {
            isMinifyEnabled = true
            isShrinkResources = true
            signingConfig = signingConfigs.getByName("release")
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Product flavors

Los flavors permiten generar múltiples variantes de la misma app — por ejemplo, versión free/paid, o versión para distintos clientes:

android {
    flavorDimensions += "version"

    productFlavors {
        create("free") {
            dimension = "version"
            applicationIdSuffix = ".free"
            versionNameSuffix = "-free"
            // Puede tener su propio set de resources en src/free/res/
        }
        create("paid") {
            dimension = "version"
            applicationIdSuffix = ".paid"
            versionNameSuffix = "-paid"
        }
    }
}

// En código Kotlin podés usar BuildConfig para comportamiento diferente:
if (BuildConfig.FLAVOR == "paid") {
    habilitarFuncionesPremium()
}

Build variants

La combinación de build types y flavors genera los build variants. Con los ejemplos anteriores tenés:

# Variantes generadas automáticamente:
freeDebug
freeStagging
freeRelease
paidDebug
paidStaging
paidRelease

# Cada variante tiene su propio APK/AAB
# Podés verlos en Android Studio → Build Variants (panel inferior izquierdo)

# Compilar una variante específica:
./gradlew assembleFreeRelease
./gradlew bundlePaidRelease    # genera AAB