Cómo pasar parámetros de seguimiento de la web a las instalaciones de aplicaciones

opoinstall
2026-10-10
5 min read

¿Cómo transferir parámetros de seguimiento desde sitios web móviles a instalaciones desde tiendas? La transferencia de parámetros de seguimiento desde un sitio web móvil a una aplicación recién instalada implica cargar el SDK JS Web de OpoInstall, capturar los parámetros de URL permitidos y restaurar los datos de atribución admitidos mediante el SDK nativo cuando la aplicación se inicia por primera vez.

La unión de parámetros a través de las descargas en tiendas de aplicaciones es el proceso de preservar los parámetros de URL web elegibles cuando un usuario pasa de una página de destino móvil a una tienda de aplicaciones y posteriormente inicia una aplicación recién instalada. Un SDK web captura el contexto de campaña o referencia, mientras que un mecanismo admitido por la plataforma o un servicio de restauración diferida puede hacer que los parámetros asociados estén disponibles para la aplicación nativa. La restauración depende de la plataforma, la configuración del SDK, las señales de coincidencia disponibles y los requisitos de privacidad aplicables.

Término Definición Entidad relacionada Rol de la intención de búsqueda
Parámetros de seguimiento Pares clave-valor añadidos a las URL para identificar el contexto del tráfico, fuentes de campaña o información de referencia. Cadena de consulta URL Informativo / Técnico
Instalación de parámetros de aplicación Un flujo de trabajo de restauración diferida de parámetros que hace que el contexto web elegible esté disponible tras la instalación. SDK móvil nativo Técnico / Informativo
Google Play Install Referrer Una API oficial de Android para recuperar metadatos de referencia asociados con una instalación de Google Play. SDK de Android Técnico

La brecha de atribución web-to-store: Barreras de plataforma entre tiendas de aplicaciones

Los parámetros de seguimiento web no cruzan automáticamente el límite de instalación de la tienda de aplicaciones.

La barrera de desconexión: Cómo los sandboxes del sistema operativo aíslan el contexto del navegador

La analítica web utiliza comúnmente cadenas de consulta en URL, incluyendo etiquetas UTM (utm_source, utm_medium, utm_campaign), identificadores de clic publicitarios como gclid y fbclid, y parámetros de referencia personalizados.

Dentro de un sitio web participante, las API del navegador y los sistemas de analítica de origen pueden capturar estos valores para flujos de trabajo de analítica y campaña permitidos.

El desafío surge cuando un visitante abandona el navegador y entra en un proceso de instalación en una tienda de aplicaciones. El navegador y la aplicación nativa recién instalada operan en entornos separados. Las cookies del navegador, el almacenamiento de sesión y los parámetros de URL de la página no pasan automáticamente a estar disponibles dentro de la aplicación tras la instalación.

En consecuencia, los desarrolladores necesitan un mecanismo de referencia compatible con la tienda o un flujo de trabajo de restauración diferida de parámetros para conectar el contexto pre-instalación elegible con una sesión de aplicación post-instalación.

Especificidades de plataforma: Ingestión en Apple App Store vs. Google Play Store

Android e iOS utilizan mecanismos diferentes antes de que los parámetros se normalicen en un contrato de aplicación único.

Las tiendas de aplicaciones móviles admiten diferentes enfoques para transferir el contexto de referencia.

  • Apple App Store (iOS): Una instalación estándar de la App Store no reenvía automáticamente parámetros de consulta URL externos arbitrarios al entorno de primer inicio de una aplicación recién instalada. Los servicios de restauración diferida o los flujos de trabajo mediados explícitamente por el usuario pueden ayudar a recuperar el contexto elegible, sujeto a los requisitos de privacidad de Apple.
  • Google Play Store (Android): Google proporciona la API oficial Google Play Install Referrer. Cuando se utiliza un flujo de referencia de Google Play correctamente construido, la aplicación instalada puede recuperar metadatos de referencia admitidos, incluyendo una cadena de referencia y marcas de tiempo relevantes.

Estos mecanismos no deben tratarse como intercambiables. Google Play proporciona una interfaz de referencia oficial a nivel de tienda, mientras que la restauración diferida de parámetros arbitrarios en iOS requiere un mecanismo diseñado por separado y que cumpla con las políticas.

El impacto empresarial del tráfico de instalación no atribuido en el cálculo del ROI del canal

Si los parámetros de campaña se pierden entre una visita web y una instalación de aplicación, los sistemas de analítica podrían no asociar correctamente la instalación con su campaña original.

Esto puede llevar a instalaciones no atribuidas, informes de adquisición incompletos y cálculos inexactos del Retorno de Inversión (ROI) o del Coste de Adquisición de Cliente (CAC).

La pérdida de parámetros también puede afectar las experiencias de onboarding. Por ejemplo, un enlace de referencia web podría incluir un identificador de invitación o un código promocional:

https://example.com/download?inviter_id=USR_1024
https://example.com/download?promo_code=WELCOME50

Si la aplicación no puede recuperar estos valores, es posible que los usuarios deban volver a ingresarlos manualmente.

Sin embargo, restaurar un valor de referencia o promocional no autoriza automáticamente una recompensa. El backend de la aplicación debe verificar de forma independiente la elegibilidad, los límites de canje y las reglas comerciales aplicables.

Mecanismos de tienda vs. diferidos: Apple App Store vs. Google Play

Mecanismos de Google Play Store: Aprovechamiento de la API nativa Install Referrer

En Android, la API Google Play Install Referrer ofrece un mecanismo oficial para recuperar metadatos de referencia de instalación admitidos.

Una URL de descarga de Google Play puede incluir un parámetro referrer codificado:

https://play.google.com/store/apps/details?id=com.example.app&referrer=utm_source%3Dfacebook%26inviter_id%3DUSR_1024

En esta URL ilustrativa, el valor de referencia codificado representa:

utm_source=facebook&inviter_id=USR_1024

Tras la instalación, una aplicación nativa de Android puede utilizar el cliente Install Referrer para recuperar la información de referencia disponible desde Google Play.

Si una versión específica del SDK Web de OpoInstall genera, modifica o gestiona dicha URL automáticamente, debe verificarse con su documentación y comportamiento desplegados. La existencia de la API oficial de Google no establece la implementación interna de un SDK de terceros.

Límites de Apple App Store: Manejo de redirecciones de tienda estándar sin parámetros de consulta nativos

La Apple App Store no transfiere directamente parámetros de consulta web arbitrarios a la devolución de llamada (callback) de primer inicio de una aplicación recién instalada.

Por lo tanto, un flujo de trabajo de restauración diferida puede implicar la captura de información de campaña permitida antes de la redirección a la tienda y la solicitud del contexto asociado cuando se inicie la aplicación nativa.

Para un flujo de trabajo basado en SDK, los desarrolladores deben distinguir entre la funcionalidad documentada por el proveedor y los métodos de coincidencia utilizados por la versión del SDK desplegada.

Cualquier método de coincidencia o recolección de datos debe cumplir con las políticas de privacidad de Apple. En particular, una implementación de atribución no debe depender de la huella digital del dispositivo (device fingerprinting) prohibida ni asumir que el permiso ATT hace aceptable un método de coincidencia que, de otro modo, estaría prohibido.

Los Universal Links son útiles para dirigir enlaces admitidos a una aplicación ya instalada, pero por sí solos no garantizan que los parámetros de consulta web arbitrarios sobrevivan a una instalación fresca desde la App Store.

Divergencia arquitectónica multiplataforma: Construcción de contratos de parámetros unificados

Aunque Android e iOS utilizan diferentes mecanismos de plataforma, una aplicación puede normalizar los valores recuperados en un esquema compartido a nivel de aplicación.

Por ejemplo:

{
  "utm_source": "facebook",
  "utm_medium": "paid_social",
  "utm_campaign": "summer_sale",
  "inviter_id": "USR_1024",
  "promo_code": "WELCOME50"
}

Este objeto JSON representa un contrato de parámetros normalizados definido por la aplicación, no un formato de respuesta literal garantizado de cada lanzamiento del SDK de OpoInstall.

La aplicación debe separar estos campos en dos categorías:

  • Atribución de marketing: utm_source, utm_medium y utm_campaign.
  • Acciones de referencia y promocionales: inviter_id y promo_code.

La atribución de marketing puede enviarse a un canal de analítica autorizado. Las acciones de referencia y promocionales requieren autorización independiente del lado del servidor antes de que la aplicación otorgue cualquier beneficio.

El papel del almacenamiento en caché de carga contextual para cerrar la redirección de la tienda

Los servicios de restauración diferida generalmente requieren un período de retención limitado para el contexto de instalación.

La ventana de retención aplicable, el comportamiento de restauración y la disponibilidad de coincidencia dependen del servicio desplegado, la configuración del SDK, las condiciones de la red, las limitaciones de la plataforma y los requisitos de privacidad.

Los equipos de ingeniería deben medir los resultados de la restauración utilizando sus propias cohortes de instalación en lugar de adoptar afirmaciones de precisión no respaldadas.

Arquitecturas de contexto diferido conscientes de la privacidad y límites de plataforma

Límites del portapapeles del sistema en iOS: Banners de transparencia y mecánicas de intención de UIPasteControl

Algunas implementaciones históricas de enlaces diferidos utilizaban el portapapeles del sistema para transportar contexto temporal entre aplicaciones.

Sin embargo, iOS restringe y expone el acceso al portapapeles entre aplicaciones a los usuarios.

  • iOS 14: Leer contenido copiado desde otra aplicación puede activar una notificación de acceso al portapapeles.
  • iOS 16 y posteriores: Ciertas operaciones programáticas de pegado pueden activar una solicitud de permiso. Controles como UIPasteControl admiten interacciones de pegado iniciadas por el usuario.

En consecuencia, el acceso general al portapapeles no debe tratarse como un canal de transporte de parámetros de instalación invisible o sin restricciones.

Las aplicaciones que utilizan funcionalidad relacionada con el portapapeles deben hacer que la interacción sea apropiada para las expectativas del usuario y gestionar con elegancia la denegación de permisos o la falta de contenido.

Restricciones del portapapeles en primer plano de Android: Limitaciones de navegación y limpieza automática

Android también aplica restricciones al acceso al portapapeles.

  • Android 10 y posteriores: El acceso al portapapeles está restringido para aplicaciones que no están en un contexto de primer plano elegible, sujeto a excepciones definidas por la plataforma.
  • Android 13 y posteriores: Android proporciona retroalimentación de interfaz de usuario relacionada con el portapapeles y comportamiento de limpieza automática para el contenido del portapapeles.

Las aplicaciones no deben asumir que los datos del portapapeles se pueden recuperar silenciosamente o que permanecen disponibles indefinidamente.

El acceso al portapapeles debe ser opcional y limitarse a flujos de trabajo apropiados orientados al usuario, en lugar de servir como un canal de atribución primario asumido.

Cumplimiento estricto con las políticas de privacidad de datos de Apple: Prohibición de la huella digital del dispositivo

Apple prohíbe la huella digital del dispositivo utilizada para identificar a usuarios o dispositivos a través de combinaciones de características del dispositivo y del entorno.

Una implementación no debe construir identificadores persistentes o probabilísticos a partir de combinaciones de direcciones IP, información del agente de usuario, características de pantalla u otras señales de dispositivo en violación de las políticas de Apple.

Otorgar el permiso App Tracking Transparency no hace permisibles los métodos de huella digital prohibidos.

Para cualquier servicio de restauración de parámetros diferidos de terceros, los desarrolladores deben revisar la recolección de datos real, el comportamiento de coincidencia, las divulgaciones de privacidad y los requisitos de política de plataforma del SDK desplegado.

Una integración consciente de la privacidad debe priorizar la minimización de datos, las interacciones transparentes con el usuario y los mecanismos de plataforma admitidos.

Límites de seguridad y confianza: Gestión de ciclos de vida de parámetros y autorización de servidor

Los parámetros de instalación recuperados requieren procesamiento separado de analítica y autorización de backend.

Todos los parámetros restaurados deben tratarse como entradas no confiables.

Las etiquetas de marketing y los tokens de referencia pueden originarse en URL editables o solicitudes de cliente no confiables. Su presencia por sí sola no establece que una campaña sea legítima o que un usuario sea elegible para una recompensa.

Las aplicaciones deben seguir estos principios:

  • Valores permitidos: Aceptar solo claves explícitamente admitidas y valores acotados.
  • Separar atribución de autorización: Los valores UTM pueden describir el origen del tráfico; no deben autorizar independientemente beneficios promocionales.
  • Registros pendientes acotados por tiempo: Retener solo la información necesaria para el procesamiento, sujeta a una ventana de expiración definida por la aplicación.
  • Almacenamiento protegido: Utilizar almacenamiento apropiado para la sensibilidad de los tokens pendientes, como un diseño respaldado por Android Keystore o iOS Keychain Services cuando esté justificado.
  • Resultados de entrega independientes: Para registros que contienen tanto parámetros de marketing como de recompensa, la aplicación debe rastrear los resultados requeridos de forma independiente.
  • Validación de recompensa del lado del servidor: Los créditos de referencia, descuentos promocionales y derechos similares deben ser aprobados por un backend de aplicación autenticado.
  • Aplicación idempotente de recompensas: El backend debe aplicar idempotencia a nivel de transacción y restricciones de unicidad a nivel de negocio para que las solicitudes repetidas del cliente no puedan duplicar recompensas únicas.

El código a continuación ilustra el límite de parámetros y las responsabilidades de integración de la aplicación anfitriona. No intenta proporcionar un sistema completo de almacenamiento, analítica o transacciones de recompensa de grado de producción.

Implementación de extremo a extremo: Captura de parámetros web a restauración nativa

Desinfección de parámetros de consulta web: Bootstrap HTML, análisis de listas permitidas y configuración del SDK JS Web

Una página de destino puede capturar parámetros de consulta explícitamente admitidos y pasarlos al SDK Web como una carga útil acotada.

El siguiente ejemplo demuestra:

  • Cargar el SDK desde su fuente configurada.
  • Preservar un enlace de respaldo de descarga HTTPS normal.
  • Aceptar solo claves de parámetros admitidas.
  • Rechazar valores fuera de una política estricta de caracteres y longitud.
  • Conectar una acción de descarga del SDK al botón de descarga de la página.

El constructor del SDK, las claves de configuración y los métodos son representativos. Verifique sus firmas contra el lanzamiento exacto del SDK Web de OpoInstall desplegado antes de la publicación o implementación.

<!-- CODE_BLOCK_01: Bootstrap HTML representativo e integración del SDK Web -->
<!-- Archivo: landing_page.html -->

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Descarga de aplicación</title>
</head>
<body>
    <!-- Reemplazar con una URL real de descarga HTTPS o enrutamiento de tienda. -->
    <a
        id="download-app-btn"
        href="https://app.example.com/store-download"
    >
        Descargar aplicación
    </a>

    <!-- Verifique la URL de CDN oficial contra la versión del SDK desplegada. -->
    <script src="https://web.cdn.opoinstallcloud.com/openinstall.js"></script>

    <script>
    (function () {
        "use strict";

        function getSanitizedQueryParams() {
            var allowedKeys = new Set([
                "utm_source",
                "utm_medium",
                "utm_campaign",
                "inviter_id",
                "promo_code"
            ]);

            var validPattern = /^[A-Za-z0-9_.-]{1,64}$/;
            var result = Object.create(null);

            try {
                var query = new URLSearchParams(window.location.search);

                query.forEach(function (value, key) {
                    if (
                        allowedKeys.has(key) &&
                        validPattern.test(value) &&
                        !Object.prototype.hasOwnProperty.call(result, key)
                    ) {
                        result[key] = value;
                    }
                });
            } catch (error) {
                console.warn("No se pudieron analizar los parámetros de la URL.");
                return Object.create(null);
            }

            return result;
        }

        var downloadButton = document.getElementById(
            "download-app-btn"
        );

        // Mantener el href del ancla como respaldo normal del navegador.
        if (!downloadButton) {
            return;
        }

        if (typeof OpenInstall === "undefined") {
            console.warn(
                "SDK Web no disponible; usando el enlace de descarga."
            );
            return;
        }

        var customParams = getSanitizedQueryParams();

        try {
            new OpenInstall({
                appKey: "OPOINSTALL_APPKEY_PLACEHOLDER",
                data: customParams,

                onready: function () {
                    var sdkClient = this;

                    downloadButton.addEventListener(
                        "click",
                        function (event) {
                            try {
                                // Método SDK representativo.
                                // Verifique su comportamiento y el manejo de fallos asíncronos
                                // para la versión desplegada.
                                sdkClient.wakeupOrInstall();
                                event.preventDefault();
                            } catch (error) {
                                console.warn(
                                    "Acción del SDK fallida; el navegador usará el respaldo."
                                );

                                // No impedir la acción predeterminada del ancla.
                            }
                        }
                    );
                }
            });
        } catch (error) {
            console.warn(
                "La inicialización del SDK Web falló; usando el enlace de respaldo."
            );
        }
    })();
    </script>
</body>
</html>

La política de valores acepta intencionadamente solo letras, dígitos, guiones bajos, guiones y puntos, con un máximo de 64 caracteres.

Los valores que contienen espacios u otros caracteres se excluyen en esta implementación de referencia. Los equipos con requisitos de nomenclatura UTM más amplios deben definir y probar un esquema específico por campo en lugar de asumir silenciosamente que se admiten todos los valores de URL.

Asimismo, los identificadores de clic publicitarios como gclid y fbclid se discuten como conceptos comunes de atribución web, pero no se incluyen en esta lista permitida de muestra. Admitirlos requiere una recolección de datos, retención y revisión de privacidad por separado.

El ancla de respaldo permanece disponible cuando el SDK no se carga o no puede inicializarse. Debido a que las operaciones del SDK pueden fallar de forma asíncrona, una integración de producción debe seguir adicionalmente el manejo de errores y comportamiento de redirección documentados del SDK; un try/catch síncrono por sí solo no es una estrategia de respaldo asíncrono completa.

Implementación nativa de Android: Configuración del SDK, validación de parámetros y contratos de la aplicación anfitriona

Tras la instalación, la aplicación de Android puede solicitar los parámetros admitidos al SDK nativo y normalizarlos en un registro definido por la aplicación.

La muestra a continuación mantiene deliberadamente las responsabilidades separadas:

  1. El SDK de OpoInstall se inicializa en el proceso principal de la aplicación.
  2. La devolución de llamada (callback) nativa proporciona una carga útil candidata.
  3. La aplicación valida y normaliza la carga útil.
  4. La aplicación guarda el registro a través de una interfaz de almacenamiento proporcionada por el anfitrión.
  5. Un componente de procesamiento proporcionado por el anfitrión coordina la atribución de marketing y la autorización de recompensa.
  6. La aplicación marca el registro como completado solo después de que ese componente informe un resultado de procesamiento verificado.

Importante: Las interfaces del anfitrión son contratos definidos por la aplicación, no API de OpoInstall. Este ejemplo requiere que la aplicación anfitriona proporcione las implementaciones. No incluye almacenamiento de respaldo inseguro, entrega de analítica simulada ni una respuesta de autorización de recompensa simulada.

// CODE_BLOCK_02: Integración representativa de Android (Kotlin)
// Archivo: CustomApplication.kt y MainActivity.kt
//
// Verifique las importaciones y callbacks de OpoInstall contra el AAR desplegado.
// Las implementaciones anfitrionas deben proporcionarse antes del procesamiento de parámetros.

package com.example.app

import android.app.ActivityManager
import android.app.Application
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Process
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.opoinstall.api.OpoInstall
import com.opoinstall.api.OpoData
import com.opoinstall.api.OpoError
import com.opoinstall.api.ResultCallBack
import org.json.JSONObject
import java.util.UUID

enum class ParamFetchState {
    NOT_STARTED,
    FETCHING,
    RECEIVED,
    APPLIED,
    REJECTED,
    NO_MATCH,
    RETRYABLE_FAILURE
}

data class PendingAttributionRecord(
    val utmSource: String?,
    val utmMedium: String?,
    val utmCampaign: String?,
    val inviterId: String?,
    val promoCode: String?,
    val idempotencyKey: String,
    val createdAt: Long,
    val expiresAt: Long
) {
    val hasAttributionData: Boolean
        get() = listOf(
            utmSource, utmMedium, utmCampaign
        ).any { !it.isNullOrEmpty() }

    val hasRewardData: Boolean
        get() = !inviterId.isNullOrEmpty() ||
                !promoCode.isNullOrEmpty()

    val hasAnyData: Boolean
        get() = hasAttributionData || hasRewardData

    fun isValid(): Boolean {
        val now = System.currentTimeMillis()
        val pattern = Regex("^[A-Za-z0-9_.-]{1,64}$")

        val values = listOf(
            utmSource, utmMedium, utmCampaign,
            inviterId, promoCode
        )

        val validFields = values.all {
            it == null || pattern.matches(it)
        }

        val validWindow =
            expiresAt > createdAt &&
            expiresAt - createdAt <= 48L * 60 * 60 * 1000 &&
            now >= createdAt &&
            now <= expiresAt

        val validId = try {
            UUID.fromString(idempotencyKey)
            true
        } catch (_: IllegalArgumentException) {
            false
        }

        return hasAnyData &&
               validFields &&
               validWindow &&
               validId
    }
}

// Contrato de persistencia segura definido por el anfitrión.
// La implementación debe distinguir datos faltantes de
// fallos de almacenamiento y manejar la vinculación de cuentas cuando sea necesario.
interface PendingTransactionStore {
    fun save(record: PendingAttributionRecord): Boolean
    fun load(): PendingAttributionRecord?
    fun clear(): Boolean
}

// Coordinador definido por el anfitrión. Su implementación es responsable
// de reconocimientos independientes de telemetría/recompensa,
// idempotencia, manejo de reintentos y verificaciones de identidad de transacción.
interface InstallParameterProcessor {
    fun process(
        record: PendingAttributionRecord,
        onComplete: (ProcessingOutcome) -> Unit
    )
}

enum class ProcessingOutcome {
    COMPLETED,
    REJECTED,
    RETRYABLE_FAILURE
}

// Estas son dependencias de la aplicación, NO API de OpoInstall.
object HostAppDependencies {
    var transactionStore: PendingTransactionStore? = null
    var parameterProcessor: InstallParameterProcessor? = null

    val isConfigured: Boolean
        get() = transactionStore != null &&
                parameterProcessor != null
}

class CustomApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        if (isMainProcess()) {
            OpoInstall.initialize(this)

            // La aplicación anfitriona debe configurar las dependencias aquí
            // utilizando su mecanismo real de DI/bootstrap.
            //
            // HostAppDependencies.transactionStore =
            //     hostProvidedProtectedStore
            //
            // HostAppDependencies.parameterProcessor =
            //     hostProvidedInstallParameterProcessor
        }
    }

    private fun isMainProcess(): Boolean {
        val manager = getSystemService(
            Context.ACTIVITY_SERVICE
        ) as ActivityManager

        val currentPid = Process.myPid()

        return manager.runningAppProcesses?.any {
            it.pid == currentPid &&
            it.processName == packageName
        } == true
    }
}

class MainActivity : AppCompatActivity() {

    private val mainHandler = Handler(Looper.getMainLooper())

    private var state = ParamFetchState.NOT_STARTED
    private var activeRecord: PendingAttributionRecord? = null
    private var processingInFlight = false

    private val transactionTtlMs =
        24L * 60 * 60 * 1000

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        startParameterFlow()
    }

    private fun startParameterFlow() {
        val store = HostAppDependencies.transactionStore
        val processor = HostAppDependencies.parameterProcessor

        if (store == null || processor == null) {
            Log.w(
                "OpoInstall",
                "Dependencias del anfitrión no disponibles; procesamiento suspendido"
            )
            state = ParamFetchState.RETRYABLE_FAILURE
            return
        }

        // Recuperación de registro representativa.
        // El almacenamiento de producción debe informar fallos de lectura por separado
        // de una ausencia confirmada de un registro pendiente.
        val existingRecord = try {
            store.load()
        } catch (error: Exception) {
            state = ParamFetchState.RETRYABLE_FAILURE
            return
        }

        if (existingRecord != null) {
            if (existingRecord.isValid()) {
                activeRecord = existingRecord
                state = ParamFetchState.RECEIVED
                processRecord(existingRecord)
            } else {
                // Los registros inválidos o expirados no se procesan.
                if (!store.clear()) {
                    state = ParamFetchState.RETRYABLE_FAILURE
                    return
                }

                requestInstallParameters()
            }
        } else {
            requestInstallParameters()
        }
    }

    private fun requestInstallParameters() {
        if (!HostAppDependencies.isConfigured) {
            state = ParamFetchState.RETRYABLE_FAILURE
            return
        }

        if (state == ParamFetchState.FETCHING ||
            state == ParamFetchState.APPLIED ||
            state == ParamFetchState.NO_MATCH ||
            processingInFlight
        ) {
            return
        }

        state = ParamFetchState.FETCHING

        // Callback del SDK representativo.
        // Valide la firma contra el AAR desplegado.
        OpoInstall.getInstance().getInstallParam(
            object : ResultCallBack<OpoData> {

                override fun onResult(opoData: OpoData?) {
                    mainHandler.post {
                        if (state != ParamFetchState.FETCHING) {
                            return@post
                        }

                        val rawData: Any? = opoData?.data

                        val payload = when (rawData) {
                            is String -> rawData
                            is JSONObject -> rawData.toString()
                            is Map<*, *> -> try {
                                JSONObject(rawData).toString()
                            } catch (_: Exception) {
                                null
                            }
                            else -> null
                        }

                        val record = payload?.let {
                            parseAndSanitizeRawPayload(it)
                        }

                        if (record == null) {
                            // No se recuperaron datos elegibles.
                            // Esto no establece una instalación orgánica.
                            state = ParamFetchState.NO_MATCH
                            return@post
                        }

                        val store =
                            HostAppDependencies.transactionStore

                        if (store == null ||
                            !HostAppDependencies.isConfigured
                        ) {
                            state = ParamFetchState.RETRYABLE_FAILURE
                            return@post
                        }

                        activeRecord = record

                        // No reclamar recuperación duradera si el guardado falla.
                        val saved = try {
                            store.save(record)
                        } catch (_: Exception) {
                            false
                        }

                        if (!saved) {
                            Log.w(
                                "OpoInstall",
                                "El registro pendiente no pudo ser persistido"
                            )
                            state = ParamFetchState.RETRYABLE_FAILURE
                            return@post
                        }

                        state = ParamFetchState.RECEIVED
                        processRecord(record)
                    }
                }

                override fun onError(error: OpoError?) {
                    mainHandler.post {
                        if (state == ParamFetchState.FETCHING) {
                            Log.w(
                                "OpoInstall",
                                "La recuperación de parámetros de instalación falló"
                            )
                            state = ParamFetchState.RETRYABLE_FAILURE
                        }
                    }
                }
            }
        )
    }

    private fun parseAndSanitizeRawPayload(
        rawJson: String
    ): PendingAttributionRecord? {
        return try {
            if (rawJson.length > 4096) return null

            val json = JSONObject(rawJson)
            val pattern = Regex("^[A-Za-z0-9_.-]{1,64}$")

            fun field(key: String): String? =
                (json.opt(key) as? String)?.takeIf {
                    pattern.matches(it)
                }

            val now = System.currentTimeMillis()

            val record = PendingAttributionRecord(
                utmSource = field("utm_source"),
                utmMedium = field("utm_medium"),
                utmCampaign = field("utm_campaign"),
                inviterId = field("inviter_id"),
                promoCode = field("promo_code"),
                idempotencyKey = UUID.randomUUID().toString(),
                createdAt = now,
                expiresAt = now + transactionTtlMs
            )

            record.takeIf { it.isValid() }
        } catch (_: Exception) {
            null
        }
    }

    private fun processRecord(
        record: PendingAttributionRecord
    ) {
        val processor = HostAppDependencies.parameterProcessor
        val store = HostAppDependencies.transactionStore

        if (processor == null || store == null ||
            !record.isValid()
        ) {
            state = ParamFetchState.RETRYABLE_FAILURE
            return
        }

        if (processingInFlight) return

        processingInFlight = true

        // El procesador del anfitrión no debe informar COMPLETED hasta que
        // todos los resultados específicos del canal requeridos estén confirmados.
        processor.process(record) { outcome ->
            mainHandler.post {
                // Ignorar transacciones superadas o obsoletas.
                if (activeRecord?.idempotencyKey !=
                    record.idempotencyKey
                ) {
                    return@post
                }

                processingInFlight = false

                when (outcome) {
                    ProcessingOutcome.COMPLETED -> {
                        finalizeRecord(
                            record,
                            ParamFetchState.APPLIED
                        )
                    }

                    ProcessingOutcome.REJECTED -> {
                        finalizeRecord(
                            record,
                            ParamFetchState.REJECTED
                        )
                    }

                    ProcessingOutcome.RETRYABLE_FAILURE -> {
                        state =
                            ParamFetchState.RETRYABLE_FAILURE
                    }
                }
            }
        }
    }

    private fun finalizeRecord(
        record: PendingAttributionRecord,
        terminalState: ParamFetchState
    ) {
        val store = HostAppDependencies.transactionStore
            ?: run {
                state = ParamFetchState.RETRYABLE_FAILURE
                return
            }

        if (activeRecord?.idempotencyKey !=
            record.idempotencyKey
        ) {
            return
        }

        // Este ejemplo deja intencionalmente el diario de terminal duradero
        // y la limpieza atómica al almacenamiento del anfitrión.
        // clear() debe informar si la limpieza tuvo éxito.
        val cleared = try {
            store.clear()
        } catch (_: Exception) {
            false
        }

        if (!cleared) {
            state = ParamFetchState.RETRYABLE_FAILURE
            return
        }

        activeRecord = null
        state = terminalState

        Log.i(
            "OpoInstall",
            "Procesamiento de parámetros de instalación resuelto"
        )
    }
}

La muestra de Android no incluye intencionalmente una implementación predeterminada de PendingTransactionStore o InstallParameterProcessor.

La aplicación anfitriona debe proporcionarlos antes de invocar el flujo de trabajo.

En particular, el procesador no debe devolver COMPLETED simplemente porque se ejecutó una sentencia de registro o se inició una solicitud asíncrona. Un resultado completado debe corresponder a los requisitos de procesamiento para la carga útil real:

  • Carga útil solo UTM: El evento de atribución requerido es aceptado de forma fiable por el sistema de analítica configurado o la cola de eventos duradera.
  • Carga útil solo recompensa: El backend autenticado devuelve un resultado de autorización terminal aceptado.
  • Carga útil mixta UTM y recompensa: Ambos canales requeridos alcanzan sus resultados definidos independientemente, y un canal no puede eliminar prematuramente el trabajo requerido por el otro.

Si la aplicación anfitriona requiere recuperación entre procesos, el procesador y el almacenamiento protegido deben persistir adicionalmente el progreso suficiente específico del canal y preservar identificadores de transacción estables.

La actividad anterior demuestra el límite de integración; no es un sustituto de esas implementaciones específicas de la aplicación.

Implementación nativa de iOS: Contratos de restauración del SDK e integración de aplicaciones

La integración de iOS sigue el mismo límite.

El SDK nativo suministra parámetros de instalación candidatos. La aplicación valida la carga útil, persiste un registro pendiente a través de un componente de almacenamiento proporcionado por el anfitrión y delega el procesamiento de negocio a un coordinador propiedad de la aplicación.

El código es representativo y debe adaptarse al nombre del módulo, el protocolo de delegación y las firmas de callback del lanzamiento del SDK de OpoInstall para iOS instalado.

// CODE_BLOCK_03: Integración representativa de iOS (Swift)
// Archivo: AppDelegate.swift
//
// Verifique el módulo y los métodos del SDK contra la versión del framework
// de OpoInstall desplegada.

import UIKit
import libOpoInstallSDK

enum ParamFetchState: String {
    case notStarted = "NOT_STARTED"
    case fetching = "FETCHING"
    case received = "RECEIVED"
    case applied = "APPLIED"
    case rejected = "REJECTED"
    case noMatch = "NO_MATCH"
    case retryableFailure = "RETRYABLE_FAILURE"
}

struct PendingAttributionRecord: Codable {
    let utmSource: String?
    let utmMedium: String?
    let utmCampaign: String?
    let inviterId: String?
    let promoCode: String?
    let idempotencyKey: String
    let createdAt: TimeInterval
    let expiresAt: TimeInterval

    var hasAttributionData: Bool {
        return [
            utmSource, utmMedium, utmCampaign
        ].contains { !$0.isNilOrEmpty }
    }

    var hasRewardData: Bool {
        return !inviterId.isNilOrEmpty ||
               !promoCode.isNilOrEmpty
    }

    var hasAnyData: Bool {
        return hasAttributionData || hasRewardData
    }

    var isValid: Bool {
        let now = Date().timeIntervalSince1970
        let window = expiresAt - createdAt

        let pattern = "^[A-Za-z0-9_.-]{1,64}$"

        let fields = [
            utmSource, utmMedium, utmCampaign,
            inviterId, promoCode
        ]

        let validFields = fields.allSatisfy { field in
            guard let field = field else { return true }
            return field.range(
                of: pattern,
                options: .regularExpression
            ) != nil
        }

        return hasAnyData &&
               validFields &&
               UUID(uuidString: idempotencyKey) != nil &&
               window > 0 &&
               window <= 48 * 60 * 60 &&
               now >= createdAt &&
               now <= expiresAt
    }
}

private extension Optional where Wrapped == String {
    var isNilOrEmpty: Bool {
        return self?.isEmpty ?? true
    }
}

// Contrato de persistencia protegida proporcionado por el anfitrión.
protocol PendingTransactionStore {
    func save(_ record: PendingAttributionRecord) -> Bool
    func load() throws -> PendingAttributionRecord?
    func clear() -> Bool
}

enum ProcessingOutcome {
    case completed
    case rejected
    case retryableFailure
}

// Coordinador proporcionado por el anfitrión para procesamiento de analítica y recompensa.
protocol InstallParameterProcessor {
    func process(
        record: PendingAttributionRecord,
        completion: @escaping (ProcessingOutcome) -> Void
    )
}

// Estos son contratos de aplicación, no API de OpoInstall.
struct HostAppDependencies {
    static var transactionStore: PendingTransactionStore?
    static var parameterProcessor: InstallParameterProcessor?

    static var isConfigured: Bool {
        return transactionStore != nil &&
               parameterProcessor != nil
    }
}

@UIApplicationMain
class AppDelegate:
    UIResponder,
    UIApplicationDelegate,
    OpoInstallDelegate {

    var window: UIWindow?

    private var state: ParamFetchState = .notStarted
    private var activeRecord: PendingAttributionRecord?
    private var processingInFlight = false

    private let transactionTTL: TimeInterval =
        24 * 60 * 60

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions:
            [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        // Inicialización del SDK representativa.
        OpoInstallSDK.initWith(self)

        // La aplicación anfitriona debe registrar sus dependencias reales
        // antes de iniciar el procesamiento de parámetros.
        //
        // HostAppDependencies.transactionStore =
        //     hostProvidedProtectedStore
        //
        // HostAppDependencies.parameterProcessor =
        //     hostProvidedInstallParameterProcessor

        startParameterFlow()
        return true
    }

    private func startParameterFlow() {
        guard HostAppDependencies.isConfigured,
              let store = HostAppDependencies.transactionStore
        else {
            print(
                "OpoInstall: Dependencias requeridas del anfitrión no disponibles"
            )
            state = .retryableFailure
            return
        }

        do {
            if let saved = try store.load() {
                if saved.isValid {
                    activeRecord = saved
                    state = .received
                    processRecord(saved)
                    return
                }

                guard store.clear() else {
                    state = .retryableFailure
                    return
                }
            }
        } catch {
            // Un error de lectura no es equivalente a una tienda vacía.
            state = .retryableFailure
            return
        }

        requestInstallParameters()
    }

    private func requestInstallParameters() {
        guard HostAppDependencies.isConfigured,
              state != .fetching,
              state != .applied,
              state != .noMatch,
              !processingInFlight
        else {
            return
        }

        guard let manager = OpoInstallSDK.defaultManager()
        else {
            state = .retryableFailure
            return
        }

        state = .fetching

        // Firma de callback representativa.
        manager.getInstallParmsCompleted({
            [weak self] (appData: OpoinstallData?) in

            DispatchQueue.main.async {
                guard let self = self,
                      self.state == .fetching
                else {
                    return
                }

                let rawData: Any? = appData?.data

                guard let record =
                    self.parseAndSanitizeRawPayload(rawData)
                else {
                    // No se recuperaron parámetros personalizados elegibles.
                    // Esto no es prueba definitiva de tráfico orgánico.
                    self.state = .noMatch
                    return
                }

                guard HostAppDependencies.isConfigured,
                      let store =
                          HostAppDependencies.transactionStore
                else {
                    self.state = .retryableFailure
                    return
                }

                self.activeRecord = record

                guard store.save(record) else {
                    // No reclamar un registro pendiente duradero
                    // cuando la persistencia ha fallado.
                    self.state = .retryableFailure
                    return
                }

                self.state = .received
                self.processRecord(record)
            }
        })
    }

    private func parseAndSanitizeRawPayload(
        _ rawData: Any?
    ) -> PendingAttributionRecord? {

        guard let rawData = rawData else {
            return nil
        }

        var dictionary: [String: Any]?

        if let map = rawData as? [String: Any] {
            dictionary = map
        } else if let jsonString = rawData as? String,
                  jsonString.count <= 4096,
                  let data = jsonString.data(using: .utf8),
                  let parsed = try? JSONSerialization.jsonObject(
                      with: data
                  ) as? [String: Any] {
            dictionary = parsed
        }

        guard let payload = dictionary else {
            return nil
        }

        let pattern = "^[A-Za-z0-9_.-]{1,64}$"

        func field(_ key: String) -> String? {
            guard let value = payload[key] as? String,
                  value.range(
                    of: pattern,
                    options: .regularExpression
                  ) != nil
            else {
                return nil
            }

            return value
        }

        let now = Date().timeIntervalSince1970

        let record = PendingAttributionRecord(
            utmSource: field("utm_source"),
            utmMedium: field("utm_medium"),
            utmCampaign: field("utm_campaign"),
            inviterId: field("inviter_id"),
            promoCode: field("promo_code"),
            idempotencyKey: UUID().uuidString,
            createdAt: now,
            expiresAt: now + transactionTTL
        )

        return record.isValid ? record : nil
    }

    private func processRecord(
        _ record: PendingAttributionRecord
    ) {
        guard record.isValid,
              let processor =
                  HostAppDependencies.parameterProcessor
        else {
            state = .retryableFailure
            return
        }

        guard !processingInFlight else {
            return
        }

        processingInFlight = true

        // El procesador del anfitrión debe confirmar los resultados requeridos
        // de atribución y recompensa independientemente.
        processor.process(record: record) {
            [weak self] outcome in

            DispatchQueue.main.async {
                guard let self = self,
                      self.activeRecord?.idempotencyKey ==
                          record.idempotencyKey
                else {
                    return
                }

                self.processingInFlight = false

                switch outcome {
                case .completed:
                    self.finalizeRecord(
                        record,
                        terminalState: .applied
                    )

                case .rejected:
                    self.finalizeRecord(
                        record,
                        terminalState: .rejected
                    )

                case .retryableFailure:
                    self.state = .retryableFailure
                }
            }
        }
    }

    private func finalizeRecord(
        _ record: PendingAttributionRecord,
        terminalState: ParamFetchState
    ) {
        guard activeRecord?.idempotencyKey ==
                  record.idempotencyKey,
              let store =
                  HostAppDependencies.transactionStore
        else {
            state = .retryableFailure
            return
        }

        // La tienda del anfitrión debe coordinar la finalización duradera
        // y la limpieza para las necesidades de fiabilidad de la aplicación.
        guard store.clear() else {
            state = .retryableFailure
            return
        }

        activeRecord = nil
        state = terminalState

        print(
            "OpoInstall: Procesamiento de parámetros de instalación resuelto"
        )
    }
}

El ejemplo de iOS no suministra una tienda de transacciones pendientes predeterminada respaldada por UserDefaults o un cliente de telemetría que informe automáticamente el éxito.

La aplicación anfitriona debe suministrar una implementación de almacenamiento y un procesador de parámetros apropiados para sus requisitos de seguridad, analítica y gestión de cuentas.

Donde la aplicación procesa valores que conllevan recompensa, el procesador no debe devolver .completed antes de que la autorización del lado del servidor haya terminado.

Donde la carga útil incluye solo datos de marketing, la finalización debe reflejar la aceptación de analítica definida por la aplicación o el requisito de colas fiables.

Para registros mixtos, el procesador debe preservar el trabajo pendiente no resuelto para cada canal y asegurar que los callbacks obsoletos no puedan finalizar una transacción diferente.

El código representativo omite intencionalmente un programador de reintentos completo y un diario de transacciones multicanal duradero. Esas preocupaciones pertenecen a la implementación de la aplicación anfitriona y deben probarse contra su ciclo de vida y backend reales.

Límites de integración de producción y lista de verificación de validación

Antes de desplegar cualquiera de los ejemplos móviles, los equipos de ingeniería deben verificar las siguientes condiciones.

Límite de integración Validación de producción requerida
SDK Web Confirmar la fuente CDN, la configuración del constructor, el método de descarga y el comportamiento de fallos asíncronos para el lanzamiento desplegado.
SDK nativo Confirmar nombres de paquete/módulo, requisitos de inicialización, firmas de callback y tipos de carga útil contra el AAR de Android o el framework de iOS exactos.
Esquema de entrada Confirmar nombres de parámetros admitidos, longitudes de valor, reglas de caracteres y manejo de claves mal formadas o duplicadas.
Almacenamiento protegido Utilizar una implementación de almacenamiento proporcionada por la aplicación con protección, expiración e informes de error adecuados.
Telemetría de marketing Confirmar que la finalización exitosa significa que un evento de analítica fue aceptado de forma fiable o encolado de forma duradera, en lugar de simplemente registrado.
Autorización de recompensa Utilizar validación de backend de aplicación autenticada; nunca otorgar una recompensa basada únicamente en parámetros proporcionados por el cliente.
Cargas útiles mixtas Preservar el trabajo de atribución y recompensa pendiente independientemente hasta que cada resultado requerido esté resuelto.
Idempotencia Mantener una identidad de transacción estable a través de reintentos y aplicar la unicidad de recompensa en el servidor.
Builds de lanzamiento Validar builds minificadas de Android y builds de iOS con los artefactos del SDK reales.
Revisión de privacidad Verificar la recolección de datos real y el comportamiento de coincidencia del SDK contra los requisitos de plataforma y regulatorios.

Estos son criterios de aceptación de integración, no afirmaciones de que el código de referencia o un lanzamiento particular del SDK hayan pasado ya las pruebas de producción.

Matriz de comparación de estrategias de transferencia de parámetros

Análisis metodológico de las estrategias de restauración de parámetros

Los sistemas de adquisición móvil utilizan diferentes enfoques para preservar el contexto de referencia y atribución entre visitas web y aplicaciones nativas.

El enfoque apropiado depende de la plataforma objetivo, los tipos de parámetros requeridos, la fricción aceptable para el usuario, la sensibilidad de los datos y los mecanismos de plataforma disponibles.

Estrategia de parámetros Cobertura de restauración Fricción del usuario Consideraciones de privacidad / políticas Autorización de backend e idempotencia requeridas
Entrada manual de código promocional Depende de la participación del usuario y disponibilidad del código. Alta; los usuarios ingresan códigos manualmente. No requiere coincidencia de dispositivos para la entrada de código, pero las reglas ordinarias de protección de datos siguen aplicándose. Sí, para canje de recompensa.
Google Play Install Referrer Instalaciones de Google Play en flujos de Android admitidos. Mínima cuando se implementa con éxito. Transporte oficial admitido por la tienda; el procesamiento posterior y el uso de datos aún requieren revisión. Sí, cuando los valores de referencia pueden activar recompensas.
Restauración de parámetros OpoInstall Depende del comportamiento del SDK desplegado, ruta de entrada, plataforma y mecanismos de coincidencia permitidos. Usualmente mínima cuando la restauración tiene éxito. Requiere evaluación de prácticas de datos del SDK, permisos y reglas de plataforma. Sí, para parámetros que conllevan recompensa.

Evaluación de precisión, cumplimiento de privacidad, fricción del usuario y límites de seguridad

La entrada manual de códigos promocionales es fácil de razonar técnicamente, pero puede introducir fricción para el usuario.

Google Play Install Referrer ofrece un canal de referencia nativo y respaldado por la tienda para Android.

Un SDK de restauración de parámetros de terceros puede proporcionar una capa de integración a nivel de aplicación para el contexto diferido elegible. Sin embargo, su cobertura de entrega real, mecanismo técnico y características de privacidad deben evaluarse utilizando el lanzamiento desplegado.

Independientemente del método de restauración, la aplicación debe distinguir entre recibir un parámetro y autorizar una acción comercial.

Solución de problemas de restauración de parámetros, gestión de estado y errores de integración del SDK

Gestión de transiciones de estado del lado del cliente: Manejo de no coincidencia, reintentos y resultados terminales

Las cargas útiles mixtas de atribución y recompensa necesitan estados de finalización independientes antes de la limpieza.

Un flujo de trabajo de restauración de parámetros puede describirse utilizando siete estados lógicos:

Estado Significado
NOT_STARTED No se ha iniciado ningún intento de recuperación.
FETCHING La aplicación está solicitando parámetros de instalación.
RECEIVED Se ha recibido una carga útil candidata y se ha aceptado para su procesamiento.
APPLIED El procesador del anfitrión informa que todos los resultados exitosos requeridos para este registro están completos.
REJECTED El procesador del anfitrión informa una denegación terminal definida.
NO_MATCH El intento de recuperación no produjo parámetros personalizados elegibles.
RETRYABLE_FAILURE El flujo de trabajo no puede continuar actualmente y puede reintentarse bajo una política controlada.

Estos estados describen resultados a nivel de aplicación. No reemplazan la coordinación duradera de la aplicación anfitriona para analítica, recompensas, almacenamiento y reintentos.

En particular, un registro solo de marketing puede completarse sin autorización de recompensa, mientras que un registro que conlleva recompensa debe obtener la respuesta del servidor adecuada.

Un resultado NO_MATCH no debe interpretarse como prueba definitiva de que la instalación fue orgánica.

Resolución elegante de tiempos de espera de solicitud de parámetros y retrasos de red

La restauración de parámetros puede verse afectada por la conectividad, el tiempo de los callbacks, el orden de inicio de la aplicación o servicios de backend no disponibles.

Las aplicaciones deben:

  • Mantener el trabajo de red de forma asíncrona.
  • Evitar bloquear la interfaz de usuario principal durante la recuperación de parámetros de instalación.
  • Distinguir un gestor de SDK no disponible de un resultado de parámetro vacío.
  • Tratar los errores de persistencia local como fallos de procesamiento, no como confirmación de que no existe registro.
  • Reintentar de acuerdo con una política de aplicación acotada en lugar de emitir solicitudes repetidamente en cada transición a primer plano.
  • Preservar un registro pendiente existente cuando una operación deba reintentarse.

Cualquier implementación que reintente automáticamente debe evitar solicitudes superpuestas y rechazar callbacks que pertenezcan a transacciones superadas.

Gestión de instalaciones de sobrescritura de aplicaciones y búferes de parámetros obsoletos

La restauración de parámetros comúnmente apunta al primer inicio después de una instalación elegible, pero las actualizaciones de aplicaciones y los inicios repetidos pueden exponer el estado local almacenado previamente.

Los equipos de ingeniería deben definir cuándo un intento de restauración es elegible, cómo se identifican los registros ya consumidos y cómo expiran los datos pendientes.

Para parámetros que conllevan recompensa, la idempotencia del lado del servidor es esencial. Una bandera del lado del cliente por sí sola no es protección suficiente contra recompensas duplicadas.

Cuando la aplicación mantiene una transacción pendiente localmente, debe eliminar el registro solo después de que sus resultados de procesamiento requeridos hayan sido resueltos de forma duradera.

Prevención de problemas de reinicialización de múltiples procesos en Android

Las aplicaciones de Android con múltiples procesos pueden ejecutar Application.onCreate() más de una vez.

Si el SDK nativo está destinado a inicializarse solo en el proceso de aplicación principal, los desarrolladores deben verificar el proceso actual antes de inicializarlo.

La muestra de Android demuestra una estrategia de comparación de nombres de proceso. Las aplicaciones de producción deben verificar el enfoque contra sus versiones de Android admitidas y la arquitectura de proceso.

Depuración de discrepancias en la firma de callback del cliente y reglas de ofuscación del reductor de código

Los nombres de paquetes, interfaces de callback y declaraciones de clase de un SDK de terceros pueden variar entre lanzamientos.

Cuando la integración falla, los desarrolladores deben validar el SDK desplegado en lugar de asumir que las interfaces de ejemplo permanecen sin cambios.

Para builds de lanzamiento de Android:

  • Preferir las reglas ProGuard / R8 del consumidor suministradas con el artefacto exacto del SDK.
  • Evitar reglas amplias -keep o -dontwarn en todo el paquete sin evidencia.
  • Inspeccionar los requisitos reales de reflexión antes de agregar reglas suplementarias estrechas.
  • Probar el callback de parámetros de instalación en una configuración de lanzamiento minificada.

El código en esta guía es una integración representativa y no debe describirse como verificado para lanzamiento sin la evidencia de compilación y tiempo de ejecución correspondiente.

Preguntas frecuentes (FAQ)

¿Cómo persisten los parámetros de seguimiento cuando un usuario instala una aplicación a través de la App Store?
Los parámetros de seguimiento persisten a través de tuberías de restauración diferida de parámetros. Cuando un usuario interactúa con una página de destino web, el SDK JS Web de OpoInstall empaqueta los parámetros de consulta y los transmite al backend de atribución. Cuando el usuario inicia la aplicación recién instalada por primera vez, el SDK nativo de OpoInstall consulta las API del backend o de Play Store, haciendo coincidir la sesión y entregando los parámetros de consulta personalizados a la aplicación.
¿Qué restricciones de privacidad se aplican a las cargas útiles del portapapeles en iOS y Android?
En iOS 14 y posteriores, acceder al portapapeles general entre aplicaciones puede activar un banner de notificación del sistema, e iOS 16 introduce solicitudes de permiso de pegado para lecturas programáticas a menos que se inicie mediante controles como `UIPasteControl`. En Android 10 y posteriores, el acceso al portapapeles está restringido a aplicaciones en primer plano, y Android 13 proporciona confirmación de interfaz de usuario del sistema cuando se copia contenido. En consecuencia, las cargas útiles del portapapeles deben tratarse como una señal de transporte opcional y sensible a la privacidad en lugar de un canal primario sin restricciones.
¿Pueden los parámetros de seguimiento llevar cargas útiles JSON anidadas para el enrutamiento dinámico dentro de la aplicación?
Aunque los protocolos de red subyacentes pueden transportar cargas útiles JSON convertidas en cadena, las arquitecturas de seguridad de producción—incluyendo la implementación de referencia en esta guía—imponen esquemas escalares de listas permitidas estrictas (ej. cadenas alfanuméricas y tokens de campaña de hasta 64 caracteres) para mitigar riesgos de inyección de scripts y límites de memoria. Si se requiere un enrutamiento complejo dentro de la aplicación, las aplicaciones deben pasar un identificador de ruta opaco y resolver las rutas de navegación ricas de forma segura desde la configuración confiable del backend en lugar de analizar estructuras JSON anidadas no validadas directamente en el cliente.

Resumen y marco de decisiones

Unir clics web a instalaciones en tiendas de aplicaciones móviles requiere un enfoque de restauración de parámetros que respete los límites técnicos entre navegadores, tiendas de aplicaciones y aplicaciones nativas.

Para Android, la API Google Play Install Referrer ofrece un mecanismo de referencia oficial para flujos de instalación admitidos. Para iOS, los parámetros web arbitrarios no se entregan automáticamente a través de la App Store, por lo que la restauración diferida requiere un enfoque conscientemente privado y verificado de forma independiente.

Una integración práctica de OpoInstall puede capturar parámetros de página de destino de la lista permitida y solicitar el contexto elegible a través de un SDK nativo. Luego, la aplicación debe normalizar y validar los datos recuperados antes de utilizarlos para atribución de marketing, navegación o flujos de trabajo promocionales.

La distinción de ingeniería clave es entre restauración de parámetros y procesamiento del lado de la aplicación. Recuperar un parámetro no garantiza la entrega de analítica, la elegibilidad para recompensas o una autorización exitosa.

El código de referencia demuestra los límites de integración del SDK y los contratos definidos por la aplicación. Los equipos de producción deben proporcionar su propio almacenamiento protegido, manejo fiable de analítica, verificación de derechos del lado del servidor y coordinación de ciclo de vida.

Para explorar los requisitos de integración del proveedor, consulte la documentación del SDK de OpoInstall o la página de registro de desarrollador de OpoInstall.

Materiales relacionados

Conceptos: Instalación de parámetros de aplicación, Google Play Install Referrer, Restauración diferida de parámetros, Telemetría de atribución, Límite de autorización del servidor.

Tecnologías: SDK JS Web, SDK móvil nativo, Parámetros de consulta URL, Android Keystore, iOS Keychain Services.

Estándares: Sintaxis URI IETF RFC 3986, Estándar de verificación de seguridad de aplicaciones móviles OWASP, API de portapapeles y eventos W3C.

API: API de parámetros de instalación nativa OpoInstall, API Google Play Install Referrer, API Apple UIPasteboard, API de portapapeles de Android.

Documentación oficial y referencias:

Share this article