# SDK Android

## Para qué sirve

El SDK de Android mide la experiencia real de quienes usan tu app: sesiones, pantallas, tiempo de arranque, errores, cierres inesperados (*crashes*), apps trabadas (*ANR*), pedidos de red y eventos de negocio. Lo ves en **Aplicaciones**.

- Kotlin, `minSdk` 24. La app tiene que compilar con `compileSdk` 37.
- Versión actual: **0.2.1** (artefacto de unos 190 KB).

## Cuándo usarlo

Cuando tenés una app Android propia. Antes, dala de alta en [Aplicaciones](https://lymen.app/docs/instalacion/aplicaciones/) (tipo *Android*): ahí obtenés la clave (`ly_pub_…`) y el código listo para copiar. Si tu app es React Native, mirá [SDK React Native](https://lymen.app/docs/instalacion/sdk-react-native/).

## Instalación

```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        // más el repositorio donde tengas el SDK
    }
}

// app/build.gradle.kts
android {
    compileOptions {
        // Hace falta si minSdk < 26.
        isCoreLibraryDesugaringEnabled = true
    }
}
dependencies {
    implementation("app.lymen:lymen-sdk-android:0.2.1")
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
}
```

No hace falta configurar R8/ProGuard: el SDK trae sus propias reglas.

## Iniciar

Una sola vez, en `Application.onCreate()`:

```kotlin
class MiApp : Application() {
    override fun onCreate() {
        super.onCreate()
        LymenSdk.iniciar(
            this,
            Opciones(
                sitio = "ly_pub_XXXXXXXXXXXXXXXXXXXXXXXX",   // clave pública (no es secreta)
                endpoint = "https://<tu-cuenta>-ingesta.lymen.app",
                propagarA = listOf("api.tu-empresa.com"),
            ),
        )
    }
}
```

`iniciar` vuelve enseguida: solo instala lo necesario para medir el arranque y la primera pantalla; el resto se arma en segundo plano. Lo que mandes mientras tanto (`evento`, `usuario`, `error`) se guarda y sale después, con su hora original.

## Opciones

| Opción | Para qué sirve | Por defecto | Valores válidos | Impacto de cambiarlo |
|---|---|---|---|---|
| `sitio` | Clave pública de la app. | — (obligatoria) | `ly_pub_…` | Sin ella no se mide. |
| `endpoint` | Punto de entrada de Lymen. | Un valor de ejemplo que **no es el de tu cuenta** | `https://<tu-cuenta>-ingesta.lymen.app` | **Indicalo siempre.** Si no, los datos no llegan a tu cuenta. El SDK le agrega las rutas necesarias. |
| `servicio` | Nombre del servicio. | El paquete de la app | Texto | El servidor lo reemplaza por el nombre de servicio del alta. |
| `version` | Versión de la app. | `versionName` | Texto | Permite comparar versiones. |
| `entorno` | Entorno. | `produccion` | Texto | Separa producción de pruebas. |
| `propagarA` | Orígenes que reciben `traceparent` (la marca que une el pedido del celular con la traza del servidor). | Vacía | `api.x.com`, `api.x.com:8443`, `*.x.com` o `https://api.x.com` | Solo a esos orígenes. Se suma a lo configurado en el portal. |
| `muestreo` | Porción de sesiones que se mandan. | `1.0` | De 0 a 1 | **Costo.** Se decide al empezar cada sesión. Si el portal define un muestreo, **manda el del portal**. |
| `consentimiento` | Valor inicial. | `CONCEDIDO` (o lo último que eligió el usuario) | `PENDIENTE`, `CONCEDIDO`, `DENEGADO` | **Legal y privacidad.** `PENDIENTE` junta en memoria sin mandar; `DENEGADO` no manda nada y borra lo pendiente y la cola en disco. Si pedís permiso en tu app, iniciá en `PENDIENTE`. |
| `capturarClics` | Registra los toques. | `false` | `true` / `false` | **Privacidad.** Guarda el id del recurso tocado, nunca el texto. El portal puede mandar sobre este valor. |
| `vistasAutomaticas` | Detecta las pantallas (Activity y Fragment). | `true` | `true` / `false` | En `false`, marcá las pantallas a mano. |
| `depuracion` | Escribe en logcat con la etiqueta `lymen`. | `false` | `true` / `false` | Solo desarrollo. |

## API

```kotlin
LymenSdk.usuario("id-del-usuario")        // se manda el hash SHA-256; null lo borra
LymenSdk.evento("turno.reservado", mapOf("monto" to 15000, "moneda" to "ARS"))
LymenSdk.error(excepcion, mapOf("pantalla.accion" to "pagar"))   // error manejado
LymenSdk.vista("Pago")                    // pantalla manual
LymenSdk.consentimiento(Consentimiento.DENEGADO)
LymenSdk.log(NivelLog.WARN, "reintento del pago")   // apagado salvo que el portal prenda los mensajes
LymenSdk.sesionActual()                   // id de sesión, para mandarlo a tu backend
LymenSdk.enviarAhora()
```

Los eventos de negocio se llaman `entidad.accion` (minúsculas, números y `_`); si no, se ignoran. Mandá un mismo evento **desde la app o desde el backend, nunca desde los dos**, o se cuenta doble.

### Pantallas

- **Activity y Fragment**: automáticas. Si una Activity solo aloja fragments, cuenta el Fragment.
- **Jetpack Compose**: poné `VistaLymen("Nombre")` al principio de cada pantalla y marcá la Activity con `@SinVistaAutomatica` para que no cuente doble.
- **Manual**: `LymenSdk.vista("Nombre")`.

### Red (OkHttp)

```kotlin
val cliente = OkHttpClient.Builder()
    .addInterceptor(LymenOkHttp.interceptor())
    .build()
```

Cada pedido es un span con los atributos HTTP estándar. `traceparent` se agrega **solo** a los orígenes de `propagarA`.

## Qué mide

| Dato | Detalle |
|---|---|
| **Sesión** | Se renueva a los 30 minutos sin actividad o a las 4 horas. Sigue si la app se reinicia antes. Registra el origen: directo, enlace (*deep link*) o notificación. |
| **Inicio de la app** | En frío (desde que arranca el proceso), tibio (proceso vivo, Activity nueva) y caliente (vuelve a primer plano), hasta el primer cuadro. |
| **Errores** | Los que mandes con `error()`: tipo, mensaje, stack (hasta 8 KB) y huella para agrupar. |
| **Crashes** | Se guardan en disco antes de que la app se cierre y salen en el próximo arranque. |
| **ANR** | Cuando el hilo principal no responde por 5 segundos o más. Si el sistema cierra la app, sale en el próximo arranque. |
| **Pedidos de red** | Con el interceptor de OkHttp. |
| **Clics** | Solo si los activás. |

## Envío y reintentos

- Datos comprimidos, en lotes de hasta 50 elementos, cada 5 segundos o al juntar 50, y al pasar a segundo plano.
- **Cola en disco** con tope de 5 MB: si se llena, se descartan los lotes más viejos.
- Ante errores de red o del servidor se reintenta con espera creciente (5 s, 10 s, 20 s… hasta 5 minutos). Si la clave es inválida o la aplicación está pausada en el portal, se frenan los envíos 10 minutos.

## Privacidad

- Las reglas de **Privacidad** bajan del servidor (al iniciar y cada 10 minutos) y se aplican en el celular antes de mandar. El servidor las vuelve a aplicar.
- Sin reglas descargadas, se usan las de respaldo: DNI, email, tarjetas y CUIT/CUIL.
- No se captura el valor de ningún campo de texto y no se graban sesiones. `user.id` sale siempre hasheado.
- Desde el portal podés **apagar** tipos de dato (vistas, tiempos, errores, red, clics, sesiones, logs): lo apagado no sale del celular. Los logs vienen apagados.

## Problemas comunes

| Síntoma | Qué hacer |
|---|---|
| No llegan datos | Revisá `sitio` y `endpoint`; activá `depuracion = true` y mirá logcat con la etiqueta `lymen`. Confirmá que la app esté con **Recibir datos** en el portal. |
| Error de compilación por `compileSdk` | La app tiene que compilar con `compileSdk` 37. |
| La app compila con `minSdk` menor que 26 y falla | Activá el *desugaring* (`isCoreLibraryDesugaringEnabled = true`) y la dependencia `coreLibraryDesugaring`. |
| No veo los crashes enseguida | Se envían en el siguiente arranque de la app. |
| Se cuentan doble los eventos | Mandalos solo desde un lado: la app o el backend. |
