# SDK iOS

## Para qué sirve

El SDK de iOS mide la experiencia real de quienes usan tu app: sesiones, pantallas, inicio de la app, pedidos de red, errores, crashes y los eventos de negocio que quieras contar. Lo ves en **Aplicaciones**.

- Swift, **iOS 13 o superior**. Paquete `lymen-sdk-ios`, módulo `LymenSdk`. Versión actual: **0.2.1**.

## Cuándo usarlo

Cuando tenés una app iOS propia. Antes, dala de alta en [Aplicaciones](https://lymen.app/docs/instalacion/aplicaciones/) (tipo **App iOS**): el paso **Instalar** te da el código listo para copiar, con la clave (`ly_pub_…`) y la dirección de tu cuenta ya puestas. Si tu app es React Native, mirá [SDK React Native](https://lymen.app/docs/instalacion/sdk-react-native/).

## Instalación (Swift Package Manager)

En Xcode: **File › Add Package Dependencies…**, pegá `https://github.com/Lymen-ops/lymen-sdk-ios` y elegí el producto **LymenSdk**. O en un `Package.swift`:

```swift
dependencies: [
  .package(url: "https://github.com/Lymen-ops/lymen-sdk-ios.git", exact: "0.2.1"),
],
targets: [
  .target(name: "MiApp", dependencies: [.product(name: "LymenSdk", package: "lymen-sdk-ios")]),
]
```

## Iniciar

Llamá a `LymenSdk.iniciar` **una sola vez**, lo antes posible y desde el hilo principal.

SwiftUI:

```swift
import LymenSdk
import SwiftUI

@main
struct TurnosApp: App {
  init() {
    LymenSdk.iniciar(Opciones(
      sitio: "ly_pub_XXXXXXXXXXXXXXXXXXXXXXXX",            // clave pública (no es secreta)
      endpoint: URL(string: "https://<tu-cuenta>-ingesta.lymen.app")!,
      servicio: "app-turnos",
      version: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0",
      entorno: "produccion"
    ))
  }
  var body: some Scene { WindowGroup { InicioView() } }
}
```

UIKit: se llama igual desde `application(_:didFinishLaunchingWithOptions:)`, con `vistasAutomaticas: true` si querés pantallas automáticas.

## Opciones

El muestreo, los toques y las direcciones de tus APIs conviene manejarlos desde el portal, en **Observar › Aplicaciones ›** (tu app) **› Configurar** (ver [Aplicaciones](https://lymen.app/docs/instalacion/aplicaciones/)): el cambio llega solo a los teléfonos, sin publicar una versión nueva de la app, y manda sobre lo que diga el código.

| 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` | Dirección de ingesta de tu cuenta. | — (**obligatorio**) | `URL(string: "https://<tu-cuenta>-ingesta.lymen.app")!` | Sin este dato la app no compila. Copialo del código que te da el portal. |
| `servicio` | Nombre del servicio. | El *bundle id* | Texto | Manda el **Nombre del servicio** cargado en el portal. |
| `version` | Versión de tu app. | — (obligatoria) | Texto | Permite comparar versiones. |
| `entorno` | Entorno. | `produccion` | Texto | Separa producción de pruebas. |
| `propagarA` | Orígenes que reciben `traceparent` (une el pedido del celular con la traza del servidor). | `[]` | `https://api.x.com`, `api.x.com` o `*.x.com` | Solo a esos. **Recomendado: cargalos en el portal**, en **Unir con lo que pasa en tu servidor**; lo del código se suma. |
| `muestreo` | Fracción de sesiones que se mandan. | `1` | De 0 a 1 | **Costo.** Se decide al empezar cada sesión. **Recomendado: el portal** (**Qué parte de las visitas se mide**), que manda sobre este valor. |
| `consentimiento` | Valor inicial. | `nil`: lo último que eligió el usuario, o `concedido` si nunca eligió | `.pendiente`, `.concedido`, `.denegado` | **Legal y privacidad.** `.pendiente` junta sin mandar; `.denegado` no manda nada y borra lo pendiente, la cola y cualquier crash guardado. |
| `capturarClics` | Registra los toques en controles. | `false` | `true` / `false` | **Privacidad.** Guarda el `accessibilityIdentifier` o la clase, nunca el texto. **Recomendado: el portal** (**Registrar los clics**), que manda sobre este valor. |
| `vistasAutomaticas` | Pantallas de UIKit automáticas. | `false` | `true` / `false` | Usa una técnica que intercepta `viewDidAppear`. En SwiftUI marcá las pantallas con `LymenSdk.vista`. |
| `capturarRed` | Mide los pedidos de URLSession. | `true` | `true` / `false` | Apagado, no hay datos de red ni `traceparent`. Para dejar de recibirlos sin publicar otra versión, apagá *Pedidos al servidor* en **Datos recibidos**. |
| `capturarCrashes` | Captura los crashes. | `true` | `true` / `false` | Ver "Crashes" más abajo. |
| `depuracion` | Mensajes `[lymen]` en la consola. | `false` | `true` / `false` | Solo desarrollo; nunca muestra datos del usuario. |

## API

```swift
LymenSdk.usuario("paciente-123")       // se manda como SHA-256; nil lo borra
LymenSdk.evento("turno.reservado", ["especialidad": "clinica", "monto": 15000, "moneda": "ARS"])
LymenSdk.error(error, ["pantalla": "medicos"])   // error manejado
LymenSdk.vista("Confirmar turno")
LymenSdk.consentimiento(.concedido)    // .pendiente | .concedido | .denegado
LymenSdk.log(.warn, "Reintento del pago")   // solo llega si en el portal están encendidos los mensajes
LymenSdk.origenSesion(.push)           // la sesión se abrió desde una notificación (o .link)
LymenSdk.enviarAhora()                 // manda ya lo juntado
```

Los eventos de negocio se llaman `entidad.accion` en minúsculas. Un nombre inválido se ignora. Mandá un mismo evento **desde la app o desde el backend, nunca desde los dos**.

## Qué mide

- **Sesión**: se renueva a los 30 minutos sin actividad o a las 4 horas; se guarda en disco, así reabrir la app enseguida sigue en la misma sesión.
- **Inicio de la app**: en frío, tibio y caliente.
- **Pantallas, pedidos de red, errores manejados y crashes.**
- **Clics**: solo si los activás.

## Crashes

Al crashear, el SDK guarda un reporte en disco y lo manda **en el próximo arranque**, atribuido a la sesión, la pantalla y el usuario del momento.

Limitaciones que conviene conocer:

- Captura señales (SIGABRT, SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP) y excepciones de Objective-C sin atrapar. Los errores de Swift (`fatalError`, índice fuera de rango, `!` sobre `nil`) llegan **sin el mensaje**.
- **No** captura cierres por SIGKILL: el sistema que cierra la app por tardar demasiado o por falta de memoria, ni cuando el usuario la cierra desde el selector de apps.
- El stack **no está simbolizado** (no se suben dSYM).
- Es esfuerzo razonable, no garantizado. Si usás otro capturador de crashes, pueden pisarse: usá uno solo.
- Con el depurador de Xcode conectado no se captura: probá desde el simulador o dispositivo sin depurador.

## Envío y reintentos

- Cola en disco con tope de **5 MB** (si se llena, se descartan los envíos más viejos).
- Sin red, se reintenta con espera creciente (2 s, 4 s, 8 s… hasta 5 minutos).
- Al pasar a segundo plano, el SDK pide unos segundos al sistema para guardar y probar un envío.

## Privacidad

- Las reglas de **Configuración › Privacidad** bajan al teléfono y se aplican antes de mandar; Lymen las vuelve a aplicar al recibir.
- Sin reglas descargadas se usan las de respaldo (DNI, email, tarjetas y CUIT/CUIL).
- No se captura el valor de campos de texto y no se graban sesiones. el id de usuario sale siempre hasheado.
- En la pestaña **Datos recibidos** de la app podés apagar tipos de dato; lo apagado no sale del teléfono. Los mensajes vienen apagados.

## Problemas comunes

| Síntoma | Qué hacer |
|---|---|
| No llegan datos | Revisá `sitio` y que `endpoint` apunte a tu cuenta. Activá `depuracion: true`. En **Configurar**, confirmá que esté marcado **Recibir datos**. |
| No hay crashes | Probá sin depurador conectado; se envían al siguiente arranque. |
| No hay pantallas | En UIKit, `vistasAutomaticas: true`; en SwiftUI, usá `LymenSdk.vista(...)`. |
| Doble conteo de eventos | Mandalos solo desde la app o solo desde el backend. |
