# Regla: Seguridad en Kotlin / Android

Esta regla es OBLIGATORIA para todo código Kotlin en Android o backend.
Los errores de seguridad en mobile son especialmente costosos: los APKs son
descargables y analizables por cualquier actor malicioso.

---

## ProGuard/R8: ofuscación obligatoria en release

R8 (sucesor de ProGuard) es la herramienta de ofuscación, shrinking y optimización
integrada en Gradle. Debe estar habilitada en todos los builds de release.

```kotlin
// build.gradle.kts — Android
android {
    buildTypes {
        release {
            isMinifyEnabled = true      // ofuscar y reducir código
            isShrinkResources = true    // eliminar recursos no usados
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}
```

Verificar que las reglas de ProGuard no excluyen clases sensibles innecesariamente.
Los modelos de datos serializados con Retrofit/Gson/Moshi necesitan reglas explícitas.

---

## EncryptedSharedPreferences para datos sensibles locales

`SharedPreferences` guarda datos en texto plano. NUNCA almacenar tokens, IDs de sesión
ni datos personales en SharedPreferences sin cifrado.

```kotlin
// MAL — texto plano, legible con adb o en dispositivos rooteados
sharedPreferences.edit().putString("access_token", token).apply()

// BIEN — cifrado con EncryptedSharedPreferences (Jetpack Security)
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val prefs = EncryptedSharedPreferences.create(
    context,
    "prefs_seguras",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
prefs.edit().putString("access_token", token).apply()
```

---

## No almacenar secretos en BuildConfig

Los valores en `BuildConfig` quedan en el DEX y son extraíbles con herramientas
como `jadx` o `apktool`. Un atacante puede recuperar la API key en minutos.

```kotlin
// MAL — la key queda en el APK descompilado
buildConfigField("String", "API_KEY", "\"sk-prod-abc123\"")

// BIEN — la key se obtiene del servidor en runtime, autenticando al usuario primero
// El backend actua como proxy: el cliente nunca toca la API key directamente
```

Para claves que el cliente DEBE conocer (ej: clave pública de SDK de pagos):
usar Android Keystore + certificate pinning como defensa adicional.

---

## Certificate Pinning en OkHttp

Sin certificate pinning, un atacante con un certificado CA fraudulento puede
interceptar el tráfico HTTPS (man-in-the-middle).

```kotlin
// BIEN — pinning por clave pública (más flexible que hash del certificado)
val certificatePinner = CertificatePinner.Builder()
    .add("api.miapp.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .add("api.miapp.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // backup pin
    .build()

val okHttpClient = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()
```

Incluir SIEMPRE un pin de respaldo (backup pin) para permitir rotación de certificados
sin dejar la app sin servicio. Documentar el proceso de rotación de pins.

---

## SQL con Room: queries parametrizados

Room previene SQL injection por defecto cuando se usan los parámetros correctamente.
NUNCA construir queries con concatenación de strings.

```kotlin
// MAL — SQL injection posible
@Dao
interface PedidoDao {
    @RawQuery
    fun buscarPorEstatus(query: SupportSQLiteQuery): List<Pedido>
}
// Uso peligroso: SimpleSQLiteQuery("SELECT * FROM pedidos WHERE estatus = '$estatus'")

// BIEN — parámetro tipado, Room lo escapa automáticamente
@Dao
interface PedidoDao {
    @Query("SELECT * FROM pedidos WHERE estatus = :estatus")
    suspend fun buscarPorEstatus(estatus: String): List<Pedido>
}
```

---

## Autenticación biométrica con BiometricPrompt

```kotlin
// BIEN — BiometricPrompt con manejo correcto de resultados
fun autenticarConBiometria(actividad: FragmentActivity, onExito: () -> Unit) {
    val executor = ContextCompat.getMainExecutor(actividad)
    val callback = object : BiometricPrompt.AuthenticationCallback() {
        override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
            onExito()
        }
        override fun onAuthenticationError(código: Int, mensaje: CharSequence) {
            // Registrar error, NO exponer detalles al usuario
            logger.warn("Biometric error $codigo")
        }
        override fun onAuthenticationFailed() {
            // Intento fallido — no bloquear todavía, BiometricPrompt maneja reintentos
        }
    }

    BiometricPrompt(actividad, executor, callback).authenticate(
        BiometricPrompt.PromptInfo.Builder()
            .setTitle("Confirmar identidad")
            .setNegativeButtonText("Cancelar")
            .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
            .build()
    )
}
```

---

## Network Security Config y validación de deep links

```xml
<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
    <!-- Solo en debug — nunca en release -->
    <debug-overrides>
        <trust-anchors>
            <certificates src="user" />
        </trust-anchors>
    </debug-overrides>
</network-security-config>
```

```kotlin
// Validar deep links — nunca confiar en el scheme/host sin verificar
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val uri = intent?.data ?: return
    if (uri.host != "miapp.com" || uri.scheme != "https") {
        logger.warn("Deep link invalido: $uri")
        finish()
        return
    }
    val pedidoId = uri.getQueryParameter("pedido_id")
        ?.let { UUID.fromString(it) }  // lanza si no es UUID válido
        ?: run { finish(); return }
}
```

---

## Checklist de seguridad Kotlin antes de abrir PR

- [ ] R8/ProGuard habilitado en builds de release
- [ ] Sin `SharedPreferences` sin cifrar para datos sensibles
- [ ] Sin secretos en `BuildConfig` o strings.xml
- [ ] Certificate pinning configurado con pin de respaldo
- [ ] Queries de Room con parámetros tipados (sin concatenación de strings)
- [ ] Deep links validados en host y scheme antes de procesar parámetros
- [ ] `clearTextTrafficPermitted="false"` en network security config
- [ ] Biometric auth usa `BiometricPrompt`, no APIs deprecadas
