Ir al contenido

Analytics

El Deepdots Popup Native SDK incluye una capa de analytics integrada que recopila datos de comportamiento de tus usuarios y los envía a una integración dedicada en tu workspace de Deepdots. Comparte el modelo de eventos y el canal de backend con el SDK Web, así que los paneles se leen igual independientemente de la plataforma.

Los métodos helper (track, trackMessage, setMetric, …) tienen los mismos nombres en Android e iOS. Los ejemplos están en Kotlin; llama al método idéntico desde Swift con sintaxis Swift. Donde las plataformas difieren (inicialización, ciclo de vida, navegación) se muestran ambas.

Pasa un objeto analytics a InitOptions con el publicKey y el integration id de la integración creada en tu workspace de Deepdots. Sin él, el SDK funciona en modo dry-run: cada payload de evento se imprime en la consola (Logcat / Xcode) pero no se envía nada.

val options = InitOptions(
popupOptions = PopupOptions(publicKey = "<your-public-key>"),
analytics = AnalyticsKeys(
publicKey = "<your-analytics-public-key>",
integration = "<your-integration-id>",
),
provideLang = { "es" },
metadata = mapOf("userId" to "customer-123"), // opcional — identifica al usuario
)
val sdk = DeepdotsPopups().apply { initialize(options) }
let options = InitOptions(
popupOptions: PopupOptions(id: nil, publicKey: "<your-public-key>", companyId: nil),
provideLang: { Locale.current.language.languageCode?.identifier ?? "es" },
analytics: AnalyticsKeys(
publicKey: "<your-analytics-public-key>",
integration: "<your-integration-id>"
),
metadata: ["userId": "customer-123"]
)
let instance = DeepdotsSDK.DeepdotsPopups()
instance.initialize(options: options)

Los siguientes datos se recopilan con cero código extra una vez que el SDK está inicializado, el tracking activado y hayas cableado los hooks de ciclo de vida y navegación:

DatoCómoDónde aparece
Vistas de pantalla (deepdots_page_view)setPath() en cada navegaciónEvents
Tiempo de engagement activo (deepdots_user_engagement)Ciclo de vida foreground/backgroundEvents
Identidad de usuario persistente (user_id)Generada en el primer arranque, guardada en SharedPreferences (Android) / NSUserDefaults (iOS)Metadata
Tipo de dispositivo, versión de SO, modelo, versión de appSe obtienen de la plataformaContext
Idioma (deepdots_language)Resolver provideLang, con fallback al locale de la plataformaContext

Cada flush envía los eventos acumulados como un lote. El backend agrupa los lotes por sesión, así que ves una única línea de tiempo por visita, no un registro por flush.


A diferencia del navegador, el SDK nativo no puede observar la navegación ni las transiciones foreground/background por sí solo. El host debe cablear dos hooks: sin ellos no hay eventos page_view, ni tiempo de engagement, ni límites de sesión.

Llama a setPath(path) en cada cambio de pantalla. La primera llamada inicia el seguimiento de navegación; cada llamada posterior cierra el deepdots_page_view de la pantalla anterior (con su duración) y abre la siguiente:

sdk.setPath("/home")
sdk.setPath("/products/42") // cierra "/home" con su duración, abre "/products/42"

El path también alimenta los triggers de ruta de los popups y los popups de salida de ruta, así que mantenlo al día aunque solo te importen los popups.

Conecta el SDK al ciclo de vida de la app para que una sesión se abra en foreground y se cierre en background.

// En tu Activity / observador de ciclo de vida:
override fun onStart() { super.onStart(); sdk.onForeground() }
override fun onStop() { super.onStop(); sdk.onBackground() }
NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { _ in
instance.onForeground()
}
NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { _ in
instance.onBackground()
}

Una sesión es una visita continua. El backend es dueño del session id y cose los lotes por user_id. Ambos extremos se señalizan de forma explícita:

  • deepdots_session_start — en cada apertura de sesión: initialize(), volver a foreground, conceder consentimiento con setTrackingEnabled(true) y tras un cambio de usuario.
  • deepdots_session_end — al cerrar, con un reason. El último lote se envía con completed: true, que es lo que le indica al backend que el registro ha terminado.

El lote de cierre vacía todo lo que queda abierto, en orden: el deepdots_page_view de la pantalla actual, cualquier deepdots_mini_service_exit pendiente, el deepdots_user_engagement acumulado y por último deepdots_session_end.

reasonCuándo
backgroundLa app va a background (onBackground())
user_changesetUserId() cambió el usuario
tracking_disabledsetTrackingEnabled(false)
manualendSession()

Cierra la sesión de forma explícita: en el logout o al final de un flujo autocontenido. El siguiente evento rastreado abre una nueva.

sdk.endSession()

Reporta un cambio de usuario (login, logout o cambio de cuenta). Cierra la sesión del usuario anterior con reason: user_change, cambia la identidad y abre una sesión nueva, para que los dos usuarios nunca compartan línea de tiempo:

sdk.setUserId("customer-123") // login
sdk.setUserId() // logout — vuelve al id anónimo

Usa track(name, params?) para registrar cualquier evento de negocio. Usa nombres en snake_case en minúscula para mantener la coherencia con los eventos automáticos.

sdk.track("add_to_cart", mapOf("product_id" to "p-123", "value" to 49.9, "currency" to "EUR"))
sdk.track("checkout_started")

trackSearch registra una consulta y su número de resultados; el SDK deriva has_results del recuento.

sdk.trackSearch("running shoes", 0) // sin resultados — has_results: false
sdk.trackSearch("t-shirt", 142) // has_results: true
sdk.trackFindabilityFriction("checkout_address")

Agrupa los pasos relacionados bajo el mismo funnel y taskId para que el backend pueda calcular tasas de conversión:

sdk.trackFunnelStep("onboarding", "account_created", "task-42")
sdk.trackFunnelStep("onboarding", "profile_completed", "task-42")

Registra una interacción significativa: un momento que indica que el usuario obtuvo valor real de tu app. interactionType es la dimensión de agrupación, así que mantén un conjunto de nombres pequeño y estable (get_help, homepage, contact_support):

sdk.trackMeaningfulInteraction("get_help")
sdk.trackMeaningfulInteraction("homepage", mapOf("screen" to "/home"))

Cada llamada emite un evento deepdots_meaningful_interaction que alimenta el panel de Effectiveness.


Un mini-servicio es cualquier flujo acotado dentro de tu app (checkout, wizard de onboarding, chat de soporte). Señaliza los límites y el SDK registra la entrada, la salida y la duración:

sdk.enterMiniService("checkout", "home_banner")
// … el usuario completa o abandona …
sdk.exitMiniService("checkout") // emite mini_service_exit con la duración

Puede haber varios mini-servicios activos a la vez; cierra siempre cada uno por nombre. Cualquier survey mostrado mientras hay un mini-servicio activo recibe una etiqueta mini_service en la metadata, así puedes filtrar el CSAT por contexto de flujo.


setUserAttributes adjunta dimensiones de negocio al contexto de analytics del usuario, incluidas en cada flush posterior y acumulativas entre llamadas:

sdk.setUserAttributes(mapOf(
"plan" to "pro",
"registration_status" to "registered",
"sector" to "retail",
))

setContactAttributes envía los atributos a POST /sdk/popups/contact, creando o actualizando el registro de contacto del usuario. Es una función suspend y solo se dispara cuando se proporcionó un userId y el tracking está activo; devuelve true si se hizo un POST, false si los atributos no cambiaron (deduplicación).

val sent = sdk.setContactAttributes(mapOf("language" to "es", "age" to 34, "plan" to "premium"))

También puedes pasar contactAttributes en InitOptions para disparar la actualización en el arranque.


setMetric(key, value) registra un valor medible: una cantidad reportada junto al contexto del usuario, como el valor del carrito. Las métricas van en un campo metrics dedicado del payload, separado de los atributos de usuario.

sdk.setMetric("cart_value", 49.99)
sdk.setMetric("items_in_cart", 3)
  • Persistente — se reenvía en cada flush hasta que cambia.
  • Sobrescribe por clave — la misma clave reemplaza el valor anterior.
  • Coercionada a string en el envío.
  • Respeta el kill-switch — es un no-op mientras el tracking está desactivado.

Usa los atributos para el quién (dimensiones por las que agrupas) y las métricas para el cuánto (cantidades que mides).


Rastrea el ciclo de vida de las notificaciones de tu app (push e in-app) para que Deepdots pueda medir entrega, click-through y conversión. Llama a trackMessage en cada etapa del funnel:

sdk.trackMessage("delivered", id = "msg-42", title = "Summer Sale", channel = "push", campaign = "summer_sale")
sdk.trackMessage("clicked", id = "msg-42", title = "Summer Sale", channel = "push")
sdk.trackMessage("converted", id = "msg-42", title = "Summer Sale", channel = "push", value = 49.9, currency = "EUR")
ArgumentoTipoDescripción
stage"delivered" / "clicked" / "converted"Etapa del funnel del mensaje
idStringCorrelaciona las etapas del mismo mensaje
titleStringDimensión de agrupación de las métricas de Messaging
channel"push" / "in_app"Canal de entrega
campaignString?Nombre de la campaña (opcional)
value / currencyDouble? / String?Valor de conversión (típico en converted)
paramsMap?Pares clave/valor adicionales

Cada llamada emite un evento deepdots_message; el backend agrupa por title para calcular recuentos de entrega, CTR, usuarios únicos con click, tasa de conversión y usuarios con acción.

CTR y tasa de conversión son ratios sobre delivered, así que las etapas deben encajar:

  1. Envía las tres etapas. delivered sale cuando el mensaje llega al dispositivo, antes de que el usuario lo abra. Sin él no hay denominador.
  2. Usa el mismo id en las tres etapas. Único por envío, no por campaña.
  3. Un id, un canal. Una campaña enviada como push e in-app necesita dos id distintos que compartan el mismo campaign.
  4. Una llamada por etapa.

El SDK descarta las llamadas que rompen estas reglas en vez de reenviarlas, y avisa en la consola:

[DeepdotsPopups] trackMessage discarded (channel_conflict): message_id "msg-42" was already reported on channel "push"; discarding "in_app"
ReglaQué se descartareason
channel debe ser push o in_appCualquier otro valorinvalid_channel
Cada par (id, stage) se envía una vezLa 2ª llamada a la misma etapa del mismo mensajeduplicate_stage
Un id mantiene su canalEventos en un canal distinto al primero vistochannel_conflict

Las comprobaciones duran la sesión y son por dispositivo, con un tope de 500 message ids (se evictan los más antiguos). Si aparecen estos warnings, apuntan a un doble conteo real: corrige el punto de llamada.


Los errores no capturados del lado Kotlin se capturan automáticamente, se persisten en el almacenamiento y se reenvían en el siguiente arranque, así que el crash que terminó una sesión sigue llegando a Deepdots aunque el proceso muriera antes del siguiente flush. Salen como eventos deepdots_app_crash y alimentan las métricas de Stability.

Reporta los errores manejados manualmente:

try {
checkout()
} catch (e: Throwable) {
sdk.reportError(e, severity = "error", context = mapOf("screen" to "Checkout", "order_id" to "o-42"))
}
ArgumentoValoresDefault
severity"fatal" / "error" / "warning""error"
handledBooleantrue
contextmapa libre (prefijado ctx_ en el payload)

El reporte de crashes respeta el mismo kill-switch de consentimiento que el resto de analytics.


Pon trackingEnabled = false en InitOptions para arrancar con toda la analítica y el tracking de contacto desactivados: útil cuando necesitas consentimiento explícito primero.

val options = InitOptions(
popupOptions = PopupOptions(publicKey = "<your-public-key>"),
trackingEnabled = false,
)
// Más tarde, cuando el usuario da su consentimiento:
sdk.setTrackingEnabled(true)

setTrackingEnabled(false) cierra la sesión actual con reason: tracking_disabled y suspende todas las llamadas salientes: el dato recopilado antes del opt-out se entrega igualmente, no se descarta. setTrackingEnabled(true) las reanuda, asigna un user_id persistente si no había uno guardado y abre una sesión nueva.


Inspecciona el buffer actual sin hacer flush, o fuerza un flush (útil en desarrollo):

val preview = sdk.previewAnalytics() // el AnalyticsEnvelope que se enviaría
sdk.flushAnalytics() // envía ahora

Los flushes también ocurren automáticamente (periódicamente en foreground, por tamaño de buffer y en onBackground()). El canal está endurecido para que el lote de cierre no se pierda:

  • Reintenta fallos transitorios — un error de red o un 5xx / 408 / 429 devuelve el lote al principio del buffer, en orden cronológico, para reintentarlo en el siguiente flush.
  • Reporta fallos permanentes — un 4xx (por ejemplo un 406 de Contact desconocido) se loguea con su status y su cuerpo, y el lote se descarta en vez de fallar en silencio.
  • Mantiene un registro por visita — hasta que el backend devuelve un session id, los lotes se serializan en vez de enviarse en paralelo.