# Adapty Documentation (Full Content) > Complete documentation content across all platforms. Locale: es Generated on: 2026-07-24T13:01:55.983Z --- # ANDROID - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-07-24T13:01:55.573Z Total files: 41 --- # File: sdk-installation-android --- --- title: "Instalar y configurar el SDK de Android" description: "Guía paso a paso para instalar el SDK de Adapty en Android para aplicaciones con suscripciones." --- El SDK de Adapty incluye dos módulos clave para una integración fluida en tu aplicación móvil: - **Core Adapty**: Este SDK esencial es necesario para que Adapty funcione correctamente en tu aplicación. - **AdaptyUI**: Este módulo es necesario si utilizas el [Adapty Paywall Builder](adapty-paywall-builder), una herramienta visual sin código para crear paywalls multiplataforma fácilmente. AdaptyUI se activa automáticamente junto con el módulo principal. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una aplicación móvil? Echa un vistazo a nuestra [aplicación de muestra](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app), que muestra la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Requisitos \{#requirements\} Requisito mínimo del SDK: `minSdkVersion 21` :::info Adapty es compatible con Google Play Billing Library hasta la versión 8.x. Por defecto, Adapty trabaja con Google Play Billing Library v.7.0.0, pero si quieres forzar una versión posterior, puedes añadir la dependencia manualmente [aquí](https://developer.android.com/google/play/billing/integrate#dependency). ::: :::info Instalar el SDK es el paso 5 de la configuración de Adapty. Para que las compras funcionen en tu app, también necesitas conectar tu app a los stores, y luego crear productos, un paywall y un placement en el Adapty Dashboard. La [guía de inicio rápido](quickstart) explica todos los pasos necesarios. ::: ## Instala el SDK de Adapty \{#install-adapty-sdk\} Elige tu método de configuración de dependencias: - Gradle estándar: añade las dependencias a tu `build.gradle` **a nivel de módulo** - Si tu proyecto usa archivos `.gradle.kts`, añade las dependencias a tu `build.gradle.kts` a nivel de módulo - Si usas catálogos de versiones, añade las dependencias a tu archivo `libs.versions.toml` y luego referencíalas en `build.gradle.kts` [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Android.svg?style=flat&logo=android)](https://github.com/adaptyteam/AdaptySDK-Android/releases) ```groovy showLineNumbers dependencies { ... implementation platform('io.adapty:adapty-bom:') implementation 'io.adapty:android-sdk' // Only add this line if you plan to use Paywall Builder implementation 'io.adapty:android-ui' } ``` ```kotlin showLineNumbers dependencies { ... implementation(platform("io.adapty:adapty-bom:")) implementation("io.adapty:android-sdk") // Only add this line if you plan to use Paywall Builder: implementation("io.adapty:android-ui") } ``` ```toml showLineNumbers //libs.versions.toml [versions] .. adaptyBom = "" [libraries] .. adapty-bom = { module = "io.adapty:adapty-bom", version.ref = "adaptyBom" } adapty = { module = "io.adapty:android-sdk" } // Only add this line if you plan to use Paywall Builder: adapty-ui = { module = "io.adapty:android-ui" } //module-level build.gradle.kts dependencies { ... implementation(platform(libs.adapty.bom)) implementation(libs.adapty) // Only add this line if you plan to use Paywall Builder: implementation(libs.adapty.ui) } ``` Si la dependencia no se resuelve, asegúrate de tener `mavenCentral()` en tus scripts de Gradle.
Instrucciones para añadirlo Si tu proyecto no tiene `dependencyResolutionManagement` en tu `settings.gradle`, añade lo siguiente a tu `build.gradle` de nivel superior al final de repositories: ```groovy showLineNumbers title="top-level build.gradle" allprojects { repositories { ... mavenCentral() } } ``` De lo contrario, añade lo siguiente a tu `settings.gradle` en `repositories` de la sección `dependencyResolutionManagement`: ```groovy showLineNumbers title="settings.gradle" dependencyResolutionManagement { ... repositories { ... mavenCentral() } } ```
:::important Adapty Android SDK 4.0 es una versión preliminar. Gradle no selecciona versiones preliminares mediante rangos de versiones dinámicos (como `+` o `latest.release`), por lo que debes indicar la versión exacta. Establece la versión de `adapty-bom` en la versión preliminar 4.0 — por ejemplo `io.adapty:adapty-bom:4.0.0-beta.2`, o `adaptyBom = "4.0.0-beta.2"` en `libs.versions.toml`. El BOM resuelve automáticamente las versiones correspondientes de `android-sdk` y `android-ui`. Consulta [Migrar Adapty Android SDK a v4](migration-to-android-sdk-v4). ::: ## Activar el módulo de Adapty del SDK \{#activate-adapty-module-of-adapty-sdk\} ### Configuración básica \{#basic-setup\} Activa el SDK de Adapty en el código de tu app. :::note El SDK de Adapty solo necesita activarse una vez en tu app. ::: Para obtener tu **Public SDK Key**: 1. Ve al Adapty Dashboard y navega a [**App settings → General**](https://app.adapty.io/settings/general). 2. En la sección **Api keys**, copia la **Public SDK Key** (NO la Secret Key). 3. Reemplaza `"YOUR_PUBLIC_SDK_KEY"` en el código. O bien, obtenla de forma programática usando el [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` O directamente: ``` npx adapty auth login adapty apps list ``` - Asegúrate de usar la **Public SDK key** para inicializar Adapty; la **Secret key** solo debe usarse para la [API del lado del servidor](getting-started-with-server-side-api). - Las **SDK keys** son únicas para cada app, así que si tienes varias apps asegúrate de elegir la correcta. ```kotlin showLineNumbers // In your Application class class MyApplication : Application() { override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .build() ) } } ``` ```java showLineNumbers // In your Application class public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Adapty.activate( getApplicationContext(), new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .build() ); } } ``` :::important Espera a que `Adapty.activate` finalice antes de llamar a cualquier otro método del SDK de Adapty. Consulta el [orden de llamadas en el SDK de Android](android-sdk-call-order) para ver la secuencia completa. ::: Ahora configura los paywalls en tu app: - Si usas [Adapty Paywall Builder](adapty-paywall-builder), sigue la [guía de inicio rápido del Paywall Builder](android-quickstart-paywalls). - Si construyes tu propia interfaz de paywall, consulta la [guía de inicio rápido para paywalls personalizados](android-quickstart-manual). ## Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Si tienes pensado usar [Paywall Builder](adapty-paywall-builder), necesitas el módulo AdaptyUI. Se activa automáticamente al activar el módulo principal; no tienes que hacer nada más. ## Configurar Proguard \{#configure-proguard\} Antes de lanzar tu app en producción, añade `-keep class com.adapty.** { *; }` a tu configuración de Proguard. ## Configuración opcional \{#optional-setup\} ### Registro de actividad \{#logging\} #### Configura el sistema de registro \{#set-up-the-logging-system\} Adapty registra errores y otra información importante para ayudarte a entender qué está pasando. Los niveles disponibles son los siguientes: | Nivel | Descripción | | :----------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- | | `AdaptyLogLevel.NONE` | No se registrará nada. Valor predeterminado | | `AdaptyLogLevel.ERROR` | Solo se registrarán los errores | | `AdaptyLogLevel.WARN` | Se registrarán los errores y los mensajes del SDK que no causan errores críticos pero que merecen atención. | | `AdaptyLogLevel.INFO` | Se registrarán los errores, las advertencias y diversos mensajes informativos. | | `AdaptyLogLevel.VERBOSE` | Se registrará cualquier información adicional que pueda ser útil durante la depuración, como llamadas a funciones, consultas a la API, etc. | Puedes establecer el nivel de log en tu app antes de configurar Adapty. ```kotlin showLineNumbers Adapty.logLevel = AdaptyLogLevel.VERBOSE //recommended for development and the first production release ``` ```java showLineNumbers Adapty.setLogLevel(AdaptyLogLevel.VERBOSE); //recommended for development and the first production release ``` #### Redirigir los mensajes del sistema de logging \{#redirect-the-logging-system-messages\} Si por algún motivo necesitas enviar los mensajes de Adapty a tu sistema o guardarlos en un archivo, puedes sobrescribir el comportamiento predeterminado: ```kotlin showLineNumbers Adapty.setLogHandler { level, message -> //handle the log } ``` ```java showLineNumbers Adapty.setLogHandler((level, message) -> { //handle the log }); ``` ### Políticas de datos \{#data-policies\} Adapty no almacena datos personales de tus usuarios a menos que los envíes explícitamente, pero puedes implementar políticas adicionales de seguridad de datos para cumplir con las directrices de la store o del país. #### Desactivar la recopilación y el uso compartido de direcciones IP \{#disable-ip-address-collection-and-sharing\} Al activar el módulo de Adapty, establece `ipAddressCollectionDisabled` en `true` para desactivar la recopilación y el uso compartido de la dirección IP del usuario. El valor predeterminado es `false`. Usa este parámetro para mejorar la privacidad de los usuarios, cumplir con las normativas regionales de protección de datos (como GDPR o CCPA) o reducir la recopilación de datos innecesaria cuando las funciones basadas en IP no son necesarias para tu app. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build(); ``` #### Deshabilitar la recopilación y el uso compartido del ID de publicidad (Ad ID) \{#disable-advertising-id-ad-id-collection-and-sharing\} Al activar el módulo de Adapty, establece `adIdCollectionDisabled` en `true` para deshabilitar la recopilación del [ID de publicidad](https://support.google.com/googleplay/android-developer/answer/6048248) del usuario. El valor predeterminado es `false`. Usa este parámetro para cumplir con las políticas de Play Store, evitar que aparezca el aviso de permiso para el ID de publicidad, o si tu app no requiere atribución publicitaria ni análisis basados en Ad ID. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withAdIdCollectionDisabled(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withAdIdCollectionDisabled(true) .build(); ``` #### Configurar la caché de medios para AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} Por defecto, AdaptyUI almacena en caché los medios (como imágenes y vídeos) para mejorar el rendimiento y reducir el uso de red. Puedes personalizar la configuración de la caché proporcionando una configuración personalizada. Usa `AdaptyUI.configureMediaCache` para sobrescribir el tamaño de caché y el período de validez predeterminados. Esto es opcional: si no llamas a este método, se usarán los valores predeterminados (100 MB en disco, 7 días de validez). ```kotlin showLineNumbers val cacheConfig = MediaCacheConfiguration.Builder() .overrideDiskStorageSizeLimit(200L * 1024 * 1024) // 200 MB .overrideDiskCacheValidityTime(3.days) .build() AdaptyUI.configureMediaCache(cacheConfig) ``` ```java showLineNumbers MediaCacheConfiguration cacheConfig = new MediaCacheConfiguration.Builder() .overrideDiskStorageSizeLimit(200L * 1024 * 1024) // 200 MB .overrideDiskCacheValidityTime(TimeInterval.days(3)) .build(); AdaptyUI.configureMediaCache(cacheConfig); ``` **Parámetros:** | Parámetro | Presencia | Descripción | |-------------------------|-----------|-----------------------------------------------------------------------------| | diskStorageSizeLimit | opcional | Tamaño total de la caché en disco en bytes. Por defecto es 100 MB. | | diskCacheValidityTime | opcional | Durante cuánto tiempo se consideran válidos los archivos en caché. Por defecto es 7 días. | :::tip Puedes borrar la caché de medios en tiempo de ejecución usando `AdaptyUI.clearMediaCache(strategy)`, donde `strategy` puede ser `CLEAR_ALL` o `CLEAR_EXPIRED_ONLY`. ::: ### Establecer IDs de cuenta ofuscados \{#set-obfuscated-account-ids\} Google Play requiere IDs de cuenta ofuscados en ciertos casos de uso para mejorar la privacidad y seguridad de los usuarios. Estos IDs ayudan a Google Play a identificar las compras manteniendo el anonimato de la información del usuario, lo cual es especialmente importante para la prevención del fraude y el análisis. Es posible que necesites configurar estos IDs si tu app maneja datos sensibles de usuarios o si debes cumplir con normativas de privacidad específicas. Los IDs ofuscados permiten a Google Play rastrear las compras sin exponer los identificadores reales de los usuarios. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObfuscatedAccountId("YOUR_OBFUSCATED_ACCOUNT_ID") .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObfuscatedAccountId("YOUR_OBFUSCATED_ACCOUNT_ID") .build(); ``` ### Ejecutar Adapty en un proceso personalizado \{#run-adapty-in-a-custom-process\} Por defecto, Adapty solo puede ejecutarse en el proceso principal de tu app. Si tu app usa múltiples procesos, inicializa Adapty una sola vez; de lo contrario, puede producirse un comportamiento inesperado. Si necesitas ejecutar Adapty en un proceso diferente, especifícalo en tu configuración: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withProcessName(":custom") .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withProcessName(":custom") .build(); ``` Si intentas activar Adapty en otro proceso sin establecer este valor, el SDK registrará una advertencia y omitirá la activación. ### Habilitar niveles de acceso locales \{#enable-local-access-levels\} Por defecto, los [niveles de acceso locales](local-access-levels) están desactivados en Android. Para habilitarlos, establece `withLocalAccessLevelAllowed` en `true`: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withLocalAccessLevelAllowed(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withLocalAccessLevelAllowed(true) .build(); ``` ## Solución de problemas \{#troubleshooting\} #### Reglas de copia de seguridad en Android (configuración de Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Algunos SDKs (incluido Adapty) incluyen su propia configuración de Android Auto Backup. Si usas varios SDKs que definen reglas de copia de seguridad, el fusionador de manifiestos de Android puede fallar con un error relacionado con `android:fullBackupContent`, `android:dataExtractionRules` o `android:allowBackup`. Síntomas habituales del error: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/sample_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` Para resolver esto, necesitas: - Indicar al manifest merger que use los valores de tu app para los atributos relacionados con el backup. - Combinar las reglas de backup de Adapty y otros SDKs en un único archivo XML (o un par de archivos para Android 12+). #### 1. Añade el namespace `tools` a tu manifest \{#1-add-the-tools-namespace-to-your-manifest\} Si aún no está presente, añade el namespace `tools` a la etiqueta raíz ``: ```xml ... ``` #### 2. Sobreescribe los atributos de backup en `` \{#2-override-backup-attributes-in-application\} En el archivo `AndroidManifest.xml` de tu app, actualiza la etiqueta `` para que tu app proporcione los valores definitivos e indique al fusionador de manifiestos que reemplace los valores de la biblioteca: ```xml ... ``` Si algún SDK también establece `android:allowBackup`, inclúyelo en `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Crear archivos de reglas de copia de seguridad combinados \{#3-create-merged-backup-rules-files\} Crea archivos XML en `app/src/main/res/xml/` que combinen las reglas de Adapty con las reglas de otros SDKs. Android utiliza distintos formatos de reglas de copia de seguridad según la versión del sistema operativo, por lo que crear ambos archivos garantiza la compatibilidad con todas las versiones de Android que soporta tu app. :::note Los ejemplos a continuación muestran AppsFlyer como SDK de terceros de muestra. Sustituye o añade reglas para cualquier otro SDK que uses en tu app. ::: **Para Android 12 y superior** (usa el nuevo formato de reglas de extracción de datos): ```xml title="sample_data_extraction_rules.xml" ``` **Para Android 11 e inferior** (usa el formato de copia de seguridad completa heredado): ```xml title="sample_backup_rules.xml" ``` Con esta configuración: - Las exclusiones de copia de seguridad de Adapty (`AdaptySDKPrefs.xml`) se mantienen. - Las exclusiones de otros SDKs (por ejemplo, `appsflyer-data`) también se aplican. - El fusionador de manifiestos usa la configuración de tu app y ya no falla por atributos de copia de seguridad en conflicto. #### Las compras fallan al volver desde otra app \{#purchases-fail-after-returning-from-another-app\} Si la Activity que inicia el flujo de compra usa un `launchMode` diferente al predeterminado, Android puede recrearla o reutilizarla de forma incorrecta cuando el usuario vuelve desde Google Play, una app bancaria o un navegador. Esto puede provocar que el resultado de la compra se pierda o se trate como cancelado. Para que las compras funcionen correctamente, usa únicamente los modos de inicio `standard` o `singleTop` para la Activity que inicia el flujo de compra, y evita cualquier otro modo. En tu `AndroidManifest.xml`, asegúrate de que la Activity que inicia el flujo de compra esté configurada como `standard` o `singleTop`: ```xml ``` --- # File: android-quickstart-paywalls --- --- title: "Habilitar compras con Flow Builder en el SDK de Android" description: "Guía de inicio rápido para habilitar compras in-app con Adapty Flow Builder." --- Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – cualquier cosa que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Flows**](adapty-flow-builder) – secuencias de pantallas que presentan productos a los usuarios, creadas en el Flow Builder sin código. El SDK las recupera mediante `getFlow`. Si prefieres construir la UI en tu propio código, usa un paywall en su lugar — consulta [Implementar paywalls manualmente](android-quickstart-manual). - [**Placements**](placements) – dónde y cuándo muestras los flows en tu app (como `main`, `onboarding`, `settings`). Asignas flows a placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar flows distintos a diferentes usuarios. Adapty te ofrece tres formas de habilitar las compras en tu app. Elige la que mejor se adapte a tus necesidades: | Implementación | Complejidad | Cuándo usarlo | |---|---|---| | Adapty Flow Builder | ✅ Fácil | [Crea un flow completo y listo para compras en el builder sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona todo el proceso de compra, validación de recibos y gestión de suscripciones entre bastidores. | | Paywalls creados manualmente | 🟡 Media | Implementas la interfaz de tu paywall en el código de tu app, pero sigues obteniendo el objeto flow de Adapty para mantener flexibilidad en la oferta de productos. Consulta la [guía](android-quickstart-manual). | | Modo Observer | 🔴 Difícil | Ya tienes tu propia infraestructura de gestión de compras y quieres seguir usándola. Ten en cuenta que el modo Observer tiene sus limitaciones en Adapty. Consulta el [artículo](observer-vs-full-mode). | :::important **Los pasos a continuación muestran cómo implementar un flow creado en el Adapty Flow Builder.** Si prefieres construir la UI del paywall tú mismo, consulta [Implementar paywalls manualmente](android-quickstart-manual). ::: Para mostrar un flow creado en el Adapty Flow Builder, en el código de tu app solo necesitas: 1. **Obtener el flow**: Obténlo desde Adapty. 2. **Mostrarlo y dejar que Adapty gestione las compras por ti**: Muestra la vista en tu app. 3. **Gestionar las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app a ellas. Por ejemplo, abrir enlaces o cerrar el flow cuando los usuarios pulsen botones. ## Antes de empezar \{#before-you-start\} Antes de empezar, completa estos pasos: 1. [Conecta tu app a Google Play](initial-android) en el Adapty Dashboard. 2. [Crea tus productos](create-product) en Adapty. 3. [Crea un flow y añade productos](create-paywall). 4. [Crea un placement y añade tu flow](create-placement). 5. [Instala y activa el SDK](sdk-installation-android) en el código de tu app. Esta guía usa las APIs del SDK de Adapty para Android v4. :::tip La forma más rápida de completar estos pasos es seguir la [guía de inicio rápido](quickstart) o crear flows y placements usando el [CLI para desarrolladores](developer-cli-quickstart). ::: ## 1. Obtén el flow \{#1-get-the-flow\} Tus flows están asociados a placements configurados en el dashboard. Los placements te permiten mostrar distintos flows a diferentes audiencias o ejecutar [pruebas A/B](ab-tests). Para obtener un flow creado en el Adapty Flow Builder, necesitas: 1. Obtener el objeto `flow` mediante el ID del [placement](placements) usando el método `getFlow` y comprobar si tiene una configuración de vista. 2. Obtener la configuración de vista con el método `getFlowConfiguration`. La configuración de vista contiene los elementos de UI y el estilo necesarios para mostrar el flow. :::important Para obtener la configuración de la vista, debes activar el interruptor **Show on device** en el Flow Builder. De lo contrario, obtendrás una configuración de vista vacía y el flow no se mostrará. ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> if (result is AdaptyResult.Success) { val flow = result.value if (!flow.hasViewConfiguration) { return@getFlow } AdaptyUI.getFlowConfiguration(flow) { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value } } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); if (!flow.hasViewConfiguration()) { return; } AdaptyUI.getFlowConfiguration(flow, configResult -> { if (configResult instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) configResult).getValue(); // use loaded configuration } }); } }); ``` ## 2. Mostrar el flow \{#display-the-flow\} Ahora que tienes la configuración del flow, basta con añadir unas pocas líneas para mostrar tu flow. Para mostrar el flow visual en la pantalla del dispositivo, primero debes configurarlo. Para ello, llama al método `AdaptyUI.getFlowView()` o crea el `AdaptyFlowView` directamente: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, null, // products = null means auto-fetch eventListener, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, null, // products = null means auto-fetch eventListener, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, null, // products = null means auto-fetch eventListener ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml ... flowView.showFlow(flowConfiguration, products, eventListener); ``` ```xml showLineNumbers ``` Una vez que la vista se haya creado correctamente, puedes añadirla a la jerarquía de vistas y mostrarla en la pantalla del dispositivo. :::tip Para más detalles sobre cómo mostrar un flow, consulta nuestra [guía](android-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#handle-button-actions\} Cuando los usuarios hacen clic en los botones del flow, el SDK de Android gestiona automáticamente las compras, la restauración, el cierre del flow y la apertura de enlaces. Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren gestionar las acciones en tu código. O puede que quieras sobreescribir su comportamiento predeterminado. Por ejemplo, aquí se muestra el comportamiento predeterminado del botón de cierre. No necesitas añadirlo en el código, pero aquí puedes ver cómo se hace si fuera necesario. :::tip Lee nuestras guías sobre cómo gestionar [acciones](android-handle-paywall-actions) y [eventos](android-handling-events) de botones. ::: ```kotlin showLineNumbers title="Kotlin" override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ```java showLineNumbers @Override public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) { if (action instanceof AdaptyUI.Action.Close) { if (context instanceof Activity) { ((Activity) context).onBackPressed(); } } } ``` ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. [Prueba tus compras en Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. A continuación, debes [comprobar el nivel de acceso de los usuarios](android-check-subscription-status) para asegurarte de que muestras un paywall o das acceso a las funciones de pago a los usuarios correctos. ## Ejemplo completo \{#full-example\} Aquí tienes cómo integrar todos esos pasos en tu app. ```kotlin showLineNumbers title="Kotlin" class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Adapty.getFlow("YOUR_PLACEMENT_ID") { flowResult -> if (flowResult is AdaptyResult.Success) { val flow = flowResult.value if (!flow.hasViewConfiguration) { // Use custom logic return@getFlow } AdaptyUI.getFlowConfiguration(flow) { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value val flowView = AdaptyUI.getFlowView( this, flowConfiguration, null, // products = null means auto-fetch object : AdaptyFlowDefaultEventListener() { override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Close -> { (context as? Activity)?.onBackPressed() } } } } ) setContentView(flowView) } } } } } } ``` ```java showLineNumbers public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Adapty.getFlow("YOUR_PLACEMENT_ID", flowResult -> { if (flowResult instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) flowResult).getValue(); if (!flow.hasViewConfiguration()) { // Use custom logic return; } AdaptyUI.getFlowConfiguration(flow, configResult -> { if (configResult instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) configResult).getValue(); AdaptyFlowView flowView = AdaptyUI.getFlowView( this, flowConfiguration, null, // products = null means auto-fetch new AdaptyFlowDefaultEventListener() { @Override public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) { if (action instanceof AdaptyUI.Action.Close) { if (context instanceof Activity) { ((Activity) context).onBackPressed(); } } } } ); setContentView(flowView); } }); } }); } } ``` --- # File: android-check-subscription-status --- --- title: "Comprobar el estado de la suscripción en el SDK de Android" description: "Aprende cómo comprobar el estado de la suscripción en tu app de Android con Adapty." --- Para decidir si los usuarios pueden acceder al contenido de pago o ver un paywall, necesitas comprobar su [nivel de acceso](access-level) en el perfil. Este artículo muestra cómo acceder al estado del perfil para decidir qué deben ver los usuarios: si mostrarles un paywall o darles acceso a las funciones de pago. ## Obtener el estado de la suscripción \{#get-subscription-status\} Cuando decides si mostrar un paywall o contenido de pago a un usuario, compruebas su [nivel de acceso](access-level) en su perfil. Tienes dos opciones: - Llama a `getProfile` si necesitas los datos más recientes del perfil de inmediato (por ejemplo, al iniciar la app) o quieres forzar una actualización. - Configura **actualizaciones automáticas del perfil** para mantener una copia local que se refresca automáticamente cada vez que cambia el estado de la suscripción. ### Obtener el perfil \{#get-profile\} La forma más sencilla de obtener el estado de la suscripción es usar el método `getProfile` para acceder al perfil: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // check the access } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` ### Escuchar actualizaciones de la suscripción \{#listen-to-subscription-updates\} Para recibir automáticamente actualizaciones del perfil en tu app: 1. Usa `Adapty.setOnProfileUpdatedListener()` para escuchar los cambios en el perfil: Adapty llamará automáticamente a este método cada vez que cambie el estado de la suscripción del usuario. 2. Guarda los datos del perfil actualizado cuando se llame a este método, para poder usarlos en toda tu app sin realizar peticiones de red adicionales. ```kotlin class SubscriptionManager { private var currentProfile: AdaptyProfile? = null init { // Listen for profile updates Adapty.setOnProfileUpdatedListener { profile -> currentProfile = profile // Update UI, unlock content, etc. } } // Use stored profile instead of calling getProfile() fun hasAccess(): Boolean { return currentProfile?.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true } } ``` ```java public class SubscriptionManager { private AdaptyProfile currentProfile; public SubscriptionManager() { // Listen for profile updates Adapty.setOnProfileUpdatedListener(profile -> { this.currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() public boolean hasAccess() { if (currentProfile == null) { return false; } AdaptyAccessLevel premiumAccess = currentProfile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); return premiumAccess != null && premiumAccess.isActive(); } } ``` :::note Adapty llama automáticamente al listener de actualización del perfil cuando tu app arranca, proporcionando datos de suscripción en caché incluso si el dispositivo está sin conexión. ::: ## Conectar el perfil con la lógica del paywall \{#connect-profile-with-paywall-logic\} Cuando necesitas tomar decisiones inmediatas sobre mostrar paywalls o conceder acceso a funciones de pago, puedes comprobar el perfil del usuario directamente. Este enfoque es útil en situaciones como el inicio de la app, al entrar en secciones premium o antes de mostrar contenido específico. ```kotlin private fun initializePaywall() { loadPaywall { paywallView -> checkAccessLevel { result -> when (result) { is AdaptyResult.Success -> { if (!result.value && paywallView != null) { setContentView(paywallView) // Show paywall if no access } } is AdaptyResult.Error -> { if (paywallView != null) { setContentView(paywallView) // Show paywall if access check fails } } } } } } private fun checkAccessLevel(callback: ResultCallback) { Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val hasAccess = result.value.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true callback.onResult(AdaptyResult.Success(hasAccess)) } is AdaptyResult.Error -> { callback.onResult(AdaptyResult.Error(result.error)) } } } } ``` ```java private void initializePaywall() { loadPaywall(paywallView -> { checkAccessLevel(result -> { if (result instanceof AdaptyResult.Success) { boolean hasAccess = ((AdaptyResult.Success) result).getValue(); if (!hasAccess && paywallView != null) { setContentView(paywallView); // Show paywall if no access } } else if (result instanceof AdaptyResult.Error) { if (paywallView != null) { setContentView(paywallView); // Show paywall if access check fails } } }); }); } private void checkAccessLevel(ResultCallback callback) { Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); AdaptyAccessLevel premiumAccess = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); boolean hasAccess = premiumAccess != null && premiumAccess.isActive(); callback.onResult(AdaptyResult.success(hasAccess)); } else if (result instanceof AdaptyResult.Error) { callback.onResult(AdaptyResult.error(((AdaptyResult.Error) result).getError())); } }); } ``` ## Pasos siguientes \{#next-steps\} Ahora que sabes cómo hacer seguimiento del estado de la suscripción, aprende a [trabajar con perfiles de usuario](android-quickstart-identify) para asegurarte de que los usuarios pueden acceder a lo que han pagado. --- # File: android-quickstart-identify --- --- title: "Identificar usuarios en el SDK de Android" description: "Guía de inicio rápido para configurar Adapty en la gestión de suscripciones in-app en Android." --- :::important Esta guía es para ti si tienes tu propio sistema de autenticación. Aquí aprenderás a trabajar con perfiles de usuario en Adapty para asegurarte de que se alinee con tu sistema de autenticación existente. ::: La forma en que gestionas las compras de los usuarios depende del modelo de autenticación de tu app: - Si tu app no usa autenticación de backend y no almacena datos de usuario, consulta la [sección sobre usuarios anónimos](#anonymous-users). - Si tu app tiene (o tendrá) autenticación de backend, consulta la [sección sobre usuarios identificados](#identified-users). **Conceptos clave**: - Los **perfiles** son las entidades necesarias para que funcione el SDK. Adapty los crea automáticamente. - Pueden ser anónimos **(sin customer user ID)** o identificados **(con customer user ID)**. - Tú proporcionas el **customer user ID** para cruzar los perfiles de Adapty con tu sistema de autenticación interno. Estas son las diferencias entre usuarios anónimos e identificados: | | Usuarios anónimos | Usuarios identificados | |------------------------------|------------------------------------------------------------|--------------------------------------------------------------------------------------| | **Gestión de compras** | Restauración de compras a nivel de store | Mantienen el historial de compras entre dispositivos gracias a su customer user ID | | **Gestión de perfiles** | Nuevos perfiles en cada reinstalación | El mismo perfil en todas las sesiones y dispositivos | | **Persistencia de datos** | Los datos de usuarios anónimos están ligados a la instalación de la app | Los datos de usuarios identificados persisten entre instalaciones de la app | ## Usuarios anónimos \{#anonymous-users\} Si no tienes autenticación de backend, **no necesitas gestionar la autenticación en el código de la app**: 1. Cuando el SDK se activa en el primer arranque de la app, Adapty **crea un nuevo perfil para el usuario**. 2. Cuando el usuario compra algo en la app, esa compra queda **asociada a su perfil de Adapty y a su cuenta de la store**. 3. Cuando el usuario **reinstala** la app o la instala en un **nuevo dispositivo**, Adapty **crea un nuevo perfil anónimo al activarse**. 4. Si el usuario ya había realizado compras en tu app, por defecto se sincronizan automáticamente desde el App Store al activar el SDK. Con usuarios anónimos se crean nuevos perfiles en cada instalación, pero eso no es un problema porque, en los análisis de Adapty, puedes [configurar qué se considera una nueva instalación](general#4-installs-definition-for-analytics). Para usuarios anónimos, debes contar las instalaciones por **ID de dispositivo**. En este caso, cada instalación de la app en un dispositivo se cuenta como una instalación, incluidas las reinstalaciones. ## Usuarios identificados \{#identified-users\} Tienes dos opciones para identificar usuarios en la app: - [**Durante el login/registro:**](#during-loginsignup) Si los usuarios inician sesión después de que arranca tu app, llama a `identify()` con un customer user ID cuando se autentiquen. - [**Durante la activación del SDK:**](#during-the-sdk-activation) Si ya tienes un customer user ID almacenado cuando se lanza la app, envíalo al llamar a `activate()`. :::important Por defecto, cuando Adapty recibe una compra de un Customer User ID que en ese momento está asociado a otro Customer User ID, el nivel de acceso se comparte, de modo que ambos perfiles tienen acceso de pago. Puedes configurar este ajuste para transferir el acceso de pago de un perfil a otro o desactivar completamente el uso compartido. Consulta el [artículo](general#6-sharing-paid-access-between-user-accounts) para más detalles. ::: ### Durante el login/registro \{#during-loginsignup\} Si identificas a los usuarios después del arranque de la app (por ejemplo, tras iniciar sesión o registrarse), usa el método `identify` para establecer su customer user ID. - Si **no has usado este customer user ID antes**, Adapty lo vinculará automáticamente al perfil actual. - Si **ya has usado este customer user ID para identificar al usuario antes**, Adapty pasará a trabajar con el perfil asociado a ese customer user ID. :::important Los customer user IDs deben ser únicos para cada usuario. Si escribes el valor del parámetro directamente en el código, todos los usuarios se considerarán como uno solo. ::: Espera a que se ejecute el callback de finalización de `identify` antes de llamar a otros métodos del SDK. Las llamadas concurrentes pueden acabar en el perfil anónimo en lugar del identificado. Consulta [Orden de llamadas en el SDK de Android](android-sdk-call-order). ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user if (error == null) { // successful identify } } ``` ```java showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` ### Durante la activación del SDK \{#during-the-sdk-activation\} Si ya conoces el customer user ID cuando activas el SDK, puedes enviarlo en el método `activate` en lugar de llamar a `identify` por separado. Si conoces el customer user ID pero lo estableces solo después de la activación, eso significa que, al activarse, Adapty creará un nuevo perfil anónimo y cambiará al existente solo cuando llames a `identify`. Puedes pasar un customer user ID existente (uno que hayas usado antes) o uno nuevo. Si pasas uno nuevo, el perfil creado al activarse se vinculará automáticamente a ese customer user ID. :::note Por defecto, crear perfiles anónimos no afecta a los dashboards de análisis, porque las instalaciones se cuentan por ID de dispositivo. Un ID de dispositivo representa una única instalación de la app desde la store en un dispositivo y se regenera solo cuando la app se reinstala. No depende de si es una primera o repetida instalación, ni de si se usa un customer user ID existente. Crear un perfil (al activar el SDK o al cerrar sesión), iniciar sesión o actualizar la app sin reinstalarla no genera eventos de instalación adicionales. Si quieres contar instalaciones por usuarios únicos en lugar de por dispositivos, ve a **App settings** y configura [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. .build(); ``` ### Cerrar sesión de usuarios \{#log-users-out\} Si tienes un botón para cerrar la sesión de los usuarios, usa el método `logout`. :::important Cerrar la sesión de un usuario crea un nuevo perfil anónimo para ese usuario. ::: ```kotlin showLineNumbers Adapty.logout { error -> if (error == null) { // successful logout } } ``` ```java showLineNumbers Adapty.logout(error -> { if (error == null) { // successful logout } }); ``` :::info Para volver a iniciar sesión de los usuarios en la app, usa el método `identify`. ::: ### Permitir compras sin iniciar sesión \{#allow-purchases-without-login\} Si tus usuarios pueden realizar compras tanto antes como después de iniciar sesión en tu app, debes asegurarte de que mantengan el acceso después de iniciar sesión: 1. Cuando un usuario sin sesión realiza una compra, Adapty la vincula a su ID de perfil anónimo. 2. Cuando el usuario inicia sesión en su cuenta, Adapty pasa a trabajar con su perfil identificado. - Si es un customer user ID nuevo (por ejemplo, la compra se realizó antes del registro), Adapty asigna el customer user ID al perfil actual, por lo que se mantiene todo el historial de compras. - Si es un customer user ID existente (el customer user ID ya está vinculado a un perfil), debes obtener el nivel de acceso actual después del cambio de perfil. Puedes llamar a [`getProfile`](android-check-subscription-status) justo después de la identificación, o [escuchar las actualizaciones de perfil](android-check-subscription-status) para que los datos se sincronicen automáticamente. ## Próximos pasos \{#next-steps\} ¡Enhorabuena! Has implementado la lógica de pago in-app en tu app. ¡Te deseamos mucho éxito con la monetización de tu app! Para sacar aún más partido a Adapty, puedes explorar estos temas: - [**Pruebas**](troubleshooting-test-purchases): Asegúrate de que todo funciona como se espera - [**Onboardings**](android-onboardings): Engancha a los usuarios con onboardings e impulsa la retención - [**Integraciones**](configuration): Integra con servicios de atribución de marketing y análisis con una sola línea de código - [**Establecer atributos de perfil personalizados**](android-setting-user-attributes): Añade atributos personalizados a los perfiles de usuario y crea segmentos para lanzar pruebas A/B o mostrar distintos paywalls a diferentes usuarios --- # File: adapty-sdk-integration-skill-android --- --- title: "Integra Adapty en tu app Android con el skill de integración del SDK" description: "Usa el skill adapty-sdk-integration para integrar el SDK de Adapty en tu app Android de principio a fin con tu herramienta de codificación IA." --- :::important La habilidad está en beta. Si se queda bloqueada o se comporta de forma inesperada, sigue la [guía de integración paso a paso](adapty-cursor-android) — te lleva a través de cada etapa con la documentación adecuada. ::: La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. --- # File: adapty-cursor-android --- --- title: "Integra Adapty en tu app Android con ayuda de IA" description: "Guía paso a paso para integrar Adapty en tu app Android usando Cursor, Context7, ChatGPT, Claude u otras herramientas de IA." --- Esta guía te lleva paso a paso por la integración de Adapty en tu app Android usando una herramienta de codificación con IA — le proporcionas la documentación correcta de Adapty en el orden correcto. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Antes de empezar: configuración del dashboard \{#before-you-start-dashboard-setup\} Adapty requiere cierta configuración en el dashboard antes de escribir código con el SDK. Puedes hacerlo con una skill interactiva de LLM o manualmente desde el Dashboard. ### Enfoque con skill (recomendado) \{#skill-approach-recommended\} El skill de Adapty CLI permite que tu LLM configure tu app, productos, niveles de acceso, paywalls y placements directamente, sin tener que abrir el Dashboard en cada paso. Solo necesitas [conectar tu store](integrate-payments) en el Dashboard. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` Una vez añadido el skill, ejecuta `/adapty-cli` en tu agente. Te guiará paso a paso, incluyendo cuándo abrir el Dashboard para conectar tu store. ### Enfoque desde el dashboard Si prefieres configurarlo todo de forma manual, esto es lo que necesitas antes de escribir código. Tu LLM no puede consultar los valores del dashboard por ti — tendrás que proporcionarlos tú mismo. 1. **Conecta tu app store**: En el Adapty Dashboard, ve a **App settings → General**. Esto es obligatorio para que las compras funcionen. [Conectar Google Play](integrate-payments) 2. **Copia tu clave SDK pública**: En el Adapty Dashboard, ve a **App settings → General** y busca la sección **API keys**. En el código, esta es la cadena que pasas al constructor de configuración de Adapty. 3. **Crea al menos un producto**: En el Adapty Dashboard, ve a la página **Products**. No haces referencia a los productos directamente en el código — Adapty los entrega a través de paywalls. [Añadir productos](quickstart-products) 4. **Crea un paywall y un placement**: En el Adapty Dashboard, crea un paywall en la página **Paywalls** y asígnalo a un placement en la página **Placements**. En el código, el ID del placement es la cadena que pasas a `Adapty.getPaywall("YOUR_PLACEMENT_ID")`. [Crear paywall](quickstart-paywalls) 5. **Configura los niveles de acceso**: En el Adapty Dashboard, configúralos por producto en la página **Products**. En el código, la cadena que se comprueba es `profile.accessLevels["premium"]?.isActive`. El nivel de acceso `premium` predeterminado funciona para la mayoría de las apps. Si los usuarios de pago tienen acceso a distintas funciones según el producto (por ejemplo, un plan `basic` frente a un plan `pro`), [crea niveles de acceso adicionales](assigning-access-level-to-a-product) antes de empezar a programar. :::tip Una vez que tengas los cinco, estarás listo para escribir código. Dile a tu LLM: "Mi clave pública del SDK es X, mi ID de placement es Y" para que pueda generar el código correcto de inicialización y obtención del paywall. ::: ### Configura cuando estés listo \{#set-up-when-ready\} No son necesarios para empezar a programar, pero los necesitarás a medida que tu integración madure: - **Pruebas A/B**: Configúralas en la página **Placements**. No se requieren cambios en el código. [Pruebas A/B](ab-tests) - **Paywalls y placements adicionales**: Añade más llamadas `getPaywall` con diferentes IDs de placement. - **Integraciones de análisis**: Configúralas en la página **Integrations**. La configuración varía según la integración. Consulta [integraciones de análisis](analytics-integration) e [integraciones de atribución](attribution-integration). ## Alimenta a tu LLM con la documentación de Adapty \{#feed-adapty-docs-to-your-llm\} ### Usar Context7 (recomendado) [Context7](https://context7.com) es un servidor MCP que da a tu LLM acceso directo a la documentación actualizada de Adapty. Tu LLM obtiene automáticamente la documentación adecuada según lo que preguntes, sin necesidad de pegar URLs manualmente. Context7 funciona con **Cursor**, **Claude Code**, **Windsurf** y otras herramientas compatibles con MCP. Para configurarlo, ejecuta: ``` npx ctx7 setup ``` Esto detecta tu editor y configura el servidor Context7. Para la configuración manual, consulta el [repositorio de Context7 en GitHub](https://github.com/upstash/context7). Una vez configurado, haz referencia a la biblioteca de Adapty en tus prompts: ``` Use the adaptyteam/adapty-docs library to look up how to install the Android SDK ``` :::warning Aunque Context7 elimina la necesidad de pegar enlaces de documentación manualmente, el orden de implementación importa. Sigue el [resumen de implementación](#implementation-walkthrough) paso a paso para asegurarte de que todo funciona. ::: ### Usa la documentación en texto plano Puedes acceder a cualquier documento de Adapty en texto plano Markdown. Añade `.md` al final de su URL o haz clic en **Copy for LLM** bajo el título del artículo. Por ejemplo: [adapty-cursor-android.md](https://adapty.io/docs/es/adapty-cursor-android.md). Cada etapa del [recorrido de implementación](#implementation-walkthrough) a continuación incluye un bloque "Send this to your LLM" con enlaces `.md` para pegar. Para acceder a más documentación a la vez, consulta los [archivos de índice y subconjuntos por plataforma](#plain-text-doc-index-files) más abajo. ## Guía de implementación paso a paso \{#implementation-walkthrough\} El resto de esta guía recorre la integración de Adapty en el orden de implementación. Cada etapa incluye la documentación que debes enviar a tu LLM, qué deberías ver al terminar y los problemas más habituales. ### Planifica tu integración \{#plan-your-integration\} Antes de ponerte a escribir código, pídele a tu LLM que analice tu proyecto y cree un plan de implementación. Si tu herramienta de IA admite un modo de planificación (como el modo plan de Cursor o Claude Code), úsalo para que el LLM pueda leer tanto la estructura de tu proyecto como la documentación de Adapty antes de generar código. Indícale a tu LLM qué enfoque usas para las compras, ya que esto determina qué guías debe seguir: - [**Adapty Paywall Builder**](adapty-paywall-builder): Crea paywalls en el editor no-code de Adapty y el SDK los renderiza automáticamente. - [**Paywalls creadas manualmente**](android-making-purchases): Construyes tu propia UI de paywall en código, pero sigues usando Adapty para obtener productos y gestionar compras. - [**Modo Observer**](observer-vs-full-mode): Mantienes tu infraestructura de compras existente y usas Adapty solo para análisis e integraciones. ¿No sabes cuál elegir? Consulta la [tabla comparativa en la guía de inicio rápido](android-quickstart-paywalls). ### Instalar y configurar el SDK \{#install-and-configure-the-sdk\} Añade la dependencia del SDK de Adapty mediante Gradle en Android Studio y actívalo con tu clave SDK pública. Esta es la base — sin ella, nada más funcionará. **Guía:** [Instalar y configurar el SDK de Adapty](sdk-installation-android) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/sdk-installation-android.md ``` :::tip[Checkpoint] - **Esperado:** La app compila y se ejecuta. Logcat muestra el log de activación de Adapty. - **Problema frecuente:** "Public API key is missing" → verifica que hayas reemplazado el marcador de posición con tu clave real desde App settings. ::: ### Mostrar paywalls y gestionar compras \{#show-paywalls-and-handle-purchases\} Obtén un paywall por su ID de placement, muéstralo y gestiona los eventos de compra. Las guías que necesitas dependen de cómo gestionas las compras. Prueba cada compra en el sandbox a medida que avanzas — no esperes hasta el final. Consulta [Probar compras en sandbox](test-purchases-in-sandbox) para ver las instrucciones de configuración. **Guías:** - [Habilitar compras con paywalls (inicio rápido)](android-quickstart-paywalls) - [Obtener paywalls del Paywall Builder y su configuración](android-get-pb-paywalls) - [Mostrar paywalls](android-present-paywalls) - [Gestionar eventos de paywall](android-handling-events) - [Responder a acciones de botones](android-handle-paywall-actions) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/android-quickstart-paywalls.md - https://adapty.io/docs/es/android-get-pb-paywalls.md - https://adapty.io/docs/es/android-present-paywalls.md - https://adapty.io/docs/es/android-handling-events.md - https://adapty.io/docs/es/android-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Esperado:** El paywall aparece con los productos configurados. Al tocar un producto se activa el diálogo de compra sandbox. - **Problema frecuente:** Paywall vacío o error en `getPaywall` → verifica que el ID del placement coincida exactamente con el dashboard y que el placement tenga una audiencia asignada. ::: **Guías:** - [Activar compras en tu paywall personalizado (inicio rápido)](android-quickstart-manual) - [Obtener paywalls y productos](fetch-paywalls-and-products-android) - [Mostrar un paywall diseñado con Remote Config](present-remote-config-paywalls-android) - [Realizar compras](android-making-purchases) - [Restaurar compras](android-restore-purchase) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/android-quickstart-manual.md - https://adapty.io/docs/es/fetch-paywalls-and-products-android.md - https://adapty.io/docs/es/present-remote-config-paywalls-android.md - https://adapty.io/docs/es/android-making-purchases.md - https://adapty.io/docs/es/android-restore-purchase.md ``` :::tip[Checkpoint] - **Expected:** Tu paywall personalizado muestra los productos obtenidos de Adapty. Al pulsar un producto se activa el diálogo de compra en sandbox. - **Gotcha:** Array de productos vacío → verifica que el paywall tiene productos asignados en el dashboard y que el placement tiene una audiencia. ::: **Guías:** - [Descripción general del Observer mode](observer-vs-full-mode) - [Implementar el Observer mode](implement-observer-mode-android) - [Reportar transacciones en Observer mode](report-transactions-observer-mode-android) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/observer-vs-full-mode.md - https://adapty.io/docs/es/implement-observer-mode-android.md - https://adapty.io/docs/es/report-transactions-observer-mode-android.md ``` :::tip[Punto de control] - **Resultado esperado:** Tras una compra en sandbox usando tu flujo de compra existente, la transacción aparece en el **Event Feed** del Adapty Dashboard. - **Problema frecuente:** Si no aparecen eventos, verifica que estás reportando las transacciones a Adapty y que las notificaciones en tiempo real de Google Play están configuradas. ::: ### Comprobar el estado de la suscripción \{#check-subscription-status\} Después de una compra, comprueba en el perfil del usuario si hay un nivel de acceso activo para restringir el contenido premium. **Guía:** [Comprobar el estado de la suscripción](android-check-subscription-status) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/android-check-subscription-status.md ``` :::tip[Checkpoint] - **Expected:** After a sandbox purchase, `profile.accessLevels["premium"]?.isActive` returns `true`. - **Gotcha:** Empty `accessLevels` after purchase → check the product has an access level assigned in the dashboard. ::: ### Identificar usuarios \{#identify-users\} Vincula las cuentas de usuario de tu app con los perfiles de Adapty para que las compras persistan entre dispositivos. :::important Omite este paso si tu app no tiene autenticación. ::: **Guía:** [Identificar usuarios](android-quickstart-identify) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/android-quickstart-identify.md ``` :::tip[Checkpoint] - **Esperado:** Después de llamar a `Adapty.identify("your-user-id")`, la sección **Profiles** del dashboard muestra tu ID de usuario personalizado. - **Problema habitual:** Llama a `identify` después de la activación pero antes de obtener paywalls para evitar la atribución anónima del perfil. ::: ### Prepararse para el lanzamiento \{#prepare-for-release\} Una vez que tu integración funcione en el sandbox, repasa la lista de verificación de lanzamiento para asegurarte de que todo está listo para producción. **Guía:** [Lista de verificación de lanzamiento](release-checklist) Envía esto a tu LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/es/release-checklist.md ``` :::tip[Checkpoint] - **Esperado:** Todos los elementos de la lista confirmados: conexión con el store, notificaciones del servidor, flujo de compra, comprobaciones de nivel de acceso y requisitos de privacidad. - **Atención:** Si faltan las notificaciones en tiempo real de Google Play (Real-Time Developer Notifications), configúralas en **App settings → Android SDK** o los eventos no aparecerán en el dashboard. ::: ## Archivos de índice de documentación en texto plano \{#plain-text-doc-index-files\} Si necesitas darle a tu LLM un contexto más amplio que el de páginas individuales, ofrecemos archivos de índice que listan o combinan toda la documentación de Adapty: - [`llms.txt`](https://adapty.io/docs/es/llms.txt): Lista todas las páginas con enlaces `.md`. Es un [estándar emergente](https://llmstxt.org/) para hacer los sitios web accesibles a los LLMs. Ten en cuenta que para algunos agentes de IA (p. ej., ChatGPT) necesitarás descargar `llms.txt` y subirlo al chat como archivo. - [`llms-full.txt`](https://adapty.io/docs/es/llms-full.txt): Toda la documentación de Adapty combinada en un único archivo. Muy extenso — úsalo solo cuando necesites una visión completa. - Subconjuntos específicos de Android [`android-llms.txt`](https://adapty.io/docs/es/android-llms.txt) y [`android-llms-full.txt`](https://adapty.io/docs/es/android-llms-full.txt): Subconjuntos específicos de plataforma que ahorran tokens en comparación con el sitio completo. --- # File: android-get-pb-paywalls --- --- title: "Obtener flows y paywalls - Android" description: "Obtén flows y paywalls desde Adapty en tu app Android." --- Después de [diseñar tu flow o paywall con Paywall Builder](adapty-paywall-builder), puedes mostrarlo en tu app. El primer paso es obtener el flow o paywall asociado al placement y su configuración de vista, tal como se describe a continuación. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. :::
Antes de empezar a mostrar flows en tu app (haz clic para expandir) 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow/paywall e incorpora productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow/paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-android) en tu aplicación móvil.
## Obtener el flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall con el Flow Builder o el Paywall Builder, no tienes que preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Este tipo de flow o paywall ya incluye tanto lo que se muestra como la forma en que se muestra. Aun así, necesitas obtener su ID a través del placement, su configuración de vista, y luego presentarlo en tu app. Para garantizar un rendimiento óptimo, es fundamental obtener el flow o paywall y su [configuración de vista](android-get-pb-paywalls#fetch-the-view-configuration) lo antes posible, dejando tiempo suficiente para que las imágenes se descarguen antes de presentarlas al usuario. Para obtener un flow o paywall, utiliza el método `getFlow`: ```kotlin showLineNumbers ... Adapty.getFlow("YOUR_PLACEMENT_ID", loadTimeout = 10.seconds) { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow/paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers ... Adapty.getFlow("YOUR_PLACEMENT_ID", TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow/paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo intermitente que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.

Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.

El SDK de Adapty almacena los flows y paywalls localmente en dos capas: la caché actualizada regularmente descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtenerlos más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para asegurarse de que siempre obtengas la versión más reciente, garantizando la fiabilidad incluso cuando la conexión a internet es limitada.

| | **loadTimeout** | por defecto: 5 seg |

Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.

Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo límite especificado en `loadTimeout`, ya que la operación puede estar compuesta por diferentes solicitudes internamente.

Para Android: puedes crear un `TimeInterval` con funciones de extensión (como `5.seconds`, donde `.seconds` proviene de `import com.adapty.utils.seconds`), o `TimeInterval.seconds(5)`. Para no establecer ningún límite, usa `TimeInterval.INFINITE`.

| | Parámetro | Descripción | | :-------- | :---------- | | Flow | Un objeto `AdaptyFlow` que contiene el placement, los identificadores (`id`, `variationId`), el nombre, los Remote Configs y un flag `hasViewConfiguration` que indica si el flow incluye una configuración de vista. Para obtener los productos reales con fines de precarga, UI personalizada o comprobaciones programáticas, llama a `getPaywallProducts(flow)`. | ## Obtener la configuración de vista \{#fetch-the-view-configuration\} Tras obtener el flow o el paywall, comprueba si incluye una configuración de vista mediante `flow.hasViewConfiguration`. Este indicador distingue cómo se diseñó el placement en el Adapty Dashboard: - **`true`** — el placement fue diseñado en el **Flow Builder** (un flow) o en el **Paywall Builder** (un paywall). Adapty renderiza la interfaz por ti. Sigue los pasos a continuación para obtener la configuración de la vista y [presentar el flow o el paywall](android-present-paywalls). - **`false`** — el placement es un paywall personalizado sin interfaz del Builder. [Gestiónalo como un paywall de Remote Config](present-remote-config-paywalls-android). :::important Asegúrate de activar el interruptor **Show on device** en el Flow Builder. Si esta opción no está activada, la configuración de la vista no estará disponible para recuperar. ::: Usa el método `getFlowConfiguration` para cargar la configuración de la vista. ```kotlin showLineNumbers if (!flow.hasViewConfiguration) { // use your custom logic return } AdaptyUI.getFlowConfiguration(flow, loadTimeout = 10.seconds) { result -> when(result) { is AdaptyResult.Success -> { val flowConfiguration = result.value // use loaded configuration } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` | Parámetro | Presencia | Descripción | | :-------------- | :------------- | :----------------------------------------------------------- | | **flow** | obligatorio | Un objeto `AdaptyFlow` obtenido mediante `Adapty.getFlow`. | | **locale** |

opcional

predeterminado: idioma del dispositivo

| El identificador de la [localización](add-paywall-locale-in-adapty-paywall-builder), como código de idioma con una o dos subetiquetas separadas por `-` (p. ej., `en`, `pt-br`). Consulta [Localizaciones y códigos de idioma](android-localizations-and-locale-codes). | | **loadTimeout** | predeterminado: 5 seg | Este valor limita el tiempo de espera del método. Si se alcanza el límite, se devolverán los datos en caché o el fallback local. Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo indicado en `loadTimeout`, ya que la operación puede estar compuesta de varias solicitudes internas. |
Usa el método `getFlowConfiguration` para cargar la configuración de la vista. ```java showLineNumbers if (!flow.hasViewConfiguration()) { // use your custom logic return; } AdaptyUI.getFlowConfiguration(flow, TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) result).getValue(); // use loaded configuration } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Parámetro | Presencia | Descripción | | :-------------- | :------------- | :----------------------------------------------------------- | | **flow** | obligatorio | Un objeto `AdaptyFlow` obtenido a través de `Adapty.getFlow`. | | **locale** |

opcional

por defecto: idioma del dispositivo

| El identificador de la [localización](add-paywall-locale-in-adapty-paywall-builder), como código de idioma con una o dos subetiquetas separadas por `-` (p. ej., `en`, `pt-br`). Consulta [Localizaciones y códigos de idioma](android-localizations-and-locale-codes). | | **loadTimeout** | por defecto: 5 seg | Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local. Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo indicado en `loadTimeout`, ya que la operación puede incluir varias solicitudes internas. |
:::note Si usas varios idiomas, aprende cómo añadir una [localización en el Builder](add-paywall-locale-in-adapty-paywall-builder) y cómo usar los códigos de idioma correctamente [aquí](android-localizations-and-locale-codes). ::: Una vez cargado, [presenta el flow o el paywall](android-present-paywalls). ## Obtén un flow o paywall para la audiencia predeterminada y cárgalo más rápido \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Normalmente, los flows y paywalls se cargan casi de forma instantánea, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, si tienes muchas audiencias y placements, y tus usuarios tienen una conexión a internet lenta, puede que la carga tarde más de lo deseado. En esas situaciones, puede que quieras mostrar un flow o paywall predeterminado para garantizar una experiencia fluida en lugar de no mostrar nada. Para solucionar esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow o paywall del placement indicado para la audiencia **All Users**. Sin embargo, es importante entender que el enfoque recomendado es obtener el flow o paywall mediante el método `getFlow`, tal como se describe en la sección [Obtener flow/paywall](#fetch-flowpaywall) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad hacia atrás**: Si necesitas mostrar flows diferentes para distintas versiones de la app (la actual y las futuras), es posible que te encuentres con dificultades. Tendrás que diseñar flows que sean compatibles con la versión actual (legacy) o asumir que los usuarios con esa versión pueden tener problemas con flows que no se renderizan. - **Pérdida de targeting**: Todos los usuarios verán el mismo flow diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluyendo segmentación por país, atribución de marketing o atributos personalizados propios). Si estás dispuesto a aceptar estos inconvenientes a cambio de una obtención más rápida del flow o el paywall, usa el método `getFlowForDefaultAudience` como se indica a continuación. De lo contrario, sigue usando `getFlow` descrito [arriba](#fetch-flowpaywall). ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | predeterminado: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo intermitente que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.

Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.

| ## Personalizar assets \{#customize-assets\} Para personalizar imágenes y vídeos en tu flow o paywall, implementa los assets personalizados. Las imágenes hero y los vídeos tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de assets personalizado, te refieres a estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. Aquí tienes un ejemplo de cómo puedes proporcionar recursos personalizados mediante un diccionario sencillo: ```kotlin showLineNumbers val customAssets = AdaptyCustomAssets.of( "hero_image" to AdaptyCustomImageAsset.remote( url = "https://example.com/image.jpg", preview = AdaptyCustomImageAsset.file( FileLocation.fromAsset("images/hero_image_preview.png"), ) ), "hero_video" to AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), ), ) val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, ) ``` :::note Si no se encuentra un asset, el flow utilizará su apariencia predeterminada. ::: Para vídeos, puedes pasar opcionalmente una `resolution` para reservar espacio en el layout y establecer la relación de aspecto (`width / height`) antes de que el vídeo cargue: ```kotlin showLineNumbers AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), resolution = AdaptyCustomVideoAsset.Resolution(width = 1080, height = 1920), ) ```
Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el nuevo Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu aplicación móvil. El primer paso es obtener el paywall asociado al placement y su configuración de vista, tal como se describe a continuación. :::warning El nuevo Paywall Builder funciona con la versión 3.0 o superior del SDK de Android. ::: Por favor, ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Si estás implementando tus paywalls manualmente, consulta el tema [Obtener paywalls y productos para paywalls con Remote Config en tu app móvil](fetch-paywalls-and-products-android). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. :::
Antes de empezar a mostrar paywalls en tu app móvil (haz clic para expandir) 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-android) en tu aplicación móvil.
## Obtener un paywall diseñado con Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Si has [diseñado un paywall con el Paywall Builder](adapty-paywall-builder), no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Este tipo de paywall incluye tanto qué mostrar como cómo mostrarlo. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y, después, presentarlo en tu app. Para garantizar un rendimiento óptimo, es fundamental obtener el paywall y su [configuración de vista](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) lo antes posible, dejando tiempo suficiente para que las imágenes se descarguen antes de mostrárselas al usuario. Para obtener un paywall, usa el método `getPaywall`: ```kotlin showLineNumbers ... Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en", loadTimeout = 10.seconds) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers ... Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | requerido | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** |

opcional

predeterminado: `en`

|

El identificador de la [localización del paywall](add-paywall-locale-in-adapty-paywall-builder). Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.

Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.

Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos utilizarlos.

| | **fetchPolicy** | predeterminado: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En ese caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.

Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante limpieza manual.

El SDK de Adapty almacena los paywalls localmente en dos capas: la caché de actualización regular descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.

| | **loadTimeout** | predeterminado: 5 seg |

Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.

Ten en cuenta que, en casos excepcionales, este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede estar compuesta por distintas solicitudes internamente.

Para Android: puedes crear `TimeInterval` con funciones de extensión (como `5.seconds`, donde `.seconds` proviene de `import com.adapty.utils.seconds`), o `TimeInterval.seconds(5)`. Para no establecer ningún límite, usa `TimeInterval.INFINITE`.

| Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Un objeto [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) con una lista de IDs de productos, el identificador del paywall, el Remote Config y otras propiedades. | ## Obtener la configuración de vista de un paywall diseñado con Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Asegúrate de activar el interruptor **Show on device** en el Paywall Builder. Si esta opción no está activada, la configuración de vista no estará disponible para recuperar. ::: Tras obtener el paywall, comprueba si incluye un `ViewConfiguration`, lo que indica que fue creado con Paywall Builder. Esto te guiará sobre cómo mostrar el paywall. Si el `ViewConfiguration` está presente, trátalo como un paywall de Paywall Builder; si no, [trátalo como un paywall de Remote Config](present-remote-config-paywalls). Usa el método `getViewConfiguration` para cargar la configuración de la vista. ```kotlin showLineNumbers if (!paywall.hasViewConfiguration) { // use your custom logic return } AdaptyUI.getViewConfiguration(paywall, loadTimeout = 10.seconds) { result -> when(result) { is AdaptyResult.Success -> { val viewConfiguration = result.value // use loaded configuration } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` | Parámetro | Presencia | Descripción | | :-------------- | :----------------- | :----------------------------------------------------------- | | **paywall** | obligatorio | Un objeto `AdaptyPaywall` para obtener un controlador del paywall deseado. | | **loadTimeout** | predeterminado: 5 s | Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el respaldo local. Ten en cuenta que, en casos excepcionales, este método puede agotar el tiempo de espera ligeramente después del valor indicado en `loadTimeout`, ya que la operación puede constar de diferentes solicitudes internamente. | Usa el método `getViewConfiguration` para cargar la configuración de vista. ```java showLineNumbers if (!paywall.hasViewConfiguration()) { // use your custom logic return; } AdaptyUI.getViewConfiguration(paywall, TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyUI.LocalizedViewConfiguration viewConfiguration = ((AdaptyResult.Success) result).getValue(); // use loaded configuration } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Parámetro | Presencia | Descripción | | :----------------------- | :------------- | :----------------------------------------------------------- | | **paywall** | obligatorio | Un objeto `AdaptyPaywall` para obtener un controlador del paywall deseado. | | **loadTimeout** | por defecto: 5 seg | Este valor limita el tiempo de espera para este método. Si se alcanza el límite, se devolverán los datos en caché o el fallback local. Ten en cuenta que en casos excepcionales este método puede agotar el tiempo ligeramente después de lo especificado en `loadTimeout`, ya que la operación puede constar de distintas solicitudes internamente. | :::note Si utilizas varios idiomas, aprende cómo añadir una [localización en el Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) y cómo usar correctamente los códigos de idioma [aquí](android-localizations-and-locale-codes). ::: Una vez cargado, [muestra el paywall](android-present-paywalls). ## Obtén un paywall para la audiencia predeterminada y acelera la carga \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Normalmente, los paywalls se obtienen casi al instante, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, cuando tienes muchas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, la carga puede tardar más de lo deseado. En esos casos, puede que quieras mostrar un paywall predeterminado para garantizar una buena experiencia de usuario en lugar de no mostrar ninguno. Para resolver esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement indicado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, tal como se describe en la sección [Obtener información del paywall](#fetch-paywall-designed-with-paywall-builder) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes paywalls para distintas versiones de la app (actual y futuras), podrías enfrentarte a dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (legacy) o aceptar que los usuarios con esa versión puedan encontrarse con paywalls que no se renderizan correctamente. - **Pérdida de segmentación**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluyendo la basada en países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas para beneficiarte de una obtención más rápida del paywall, usa el método `getPaywallForDefaultAudience` de la siguiente manera. De lo contrario, utiliza `getPaywall` descrito [anteriormente](#fetch-paywall-designed-with-paywall-builder). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` :::note El método `getPaywallForDefaultAudience` está disponible a partir del Android SDK 2.11.3 ::: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | requerido | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** |

opcional

predeterminado: `en`

|

El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.

Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.

Consulta [Localizaciones y códigos de locale](localizations-and-locale-codes) para más información sobre los códigos de locale y cómo recomendamos utilizarlos.

| | **fetchPolicy** | predeterminado: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché cuando existan. En este caso, puede que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.

Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la aplicación o mediante una limpieza manual.

| ## Personaliza los recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu paywall, implementa los recursos personalizados. Las imágenes y vídeos hero tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizados, apuntas a estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga una imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. :::important Para usar esta función, actualiza el SDK de Adapty para Android a la versión 3.7.0 o superior. ::: Aquí tienes un ejemplo de cómo proporcionar assets personalizados mediante un diccionario simple: ```kotlin showLineNumbers val customAssets = AdaptyCustomAssets.of( "hero_image" to AdaptyCustomImageAsset.remote( url = "https://example.com/image.jpg", preview = AdaptyCustomImageAsset.file( FileLocation.fromAsset("images/hero_image_preview.png"), ) ), "hero_video" to AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), ), ) val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, customAssets, ) ``` :::note Si no se encuentra un recurso, el paywall usará su apariencia predeterminada. :::
--- # File: android-present-paywalls --- --- title: "Mostrar flows y paywalls - Android" description: "Presenta flows y paywalls a los usuarios en tu app de Android." --- Si has creado un flow o paywall, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Un flow o paywall ya contiene tanto lo que se debe mostrar como la forma en que debe mostrarse. :::warning Esta guía cubre flows y **paywalls del nuevo Paywall Builder** renderizados por Adapty. El proceso es diferente para los paywalls de Remote Config y el [modo Observer](observer-vs-full-mode). - Para presentar **paywalls de Remote Config**, consulta [Mostrar paywall diseñado con Remote Config](present-remote-config-paywalls). - Para presentar **paywalls en modo Observer**, consulta [Android - Presentar paywalls de Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) ::: Para obtener el objeto `flowConfiguration` utilizado a continuación, consulta [Obtener flows y paywalls](android-get-pb-paywalls). Para mostrar el flow visual en la pantalla del dispositivo, primero debes configurarlo. Para ello, llama al método `AdaptyUI.getFlowView()` o crea el `AdaptyFlowView` directamente: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml ... flowView.showFlow(flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver); ``` ```xml showLineNumbers ``` Una vez que la vista se haya creado correctamente, puedes añadirla a la jerarquía de vistas y mostrarla en la pantalla del dispositivo. Si obtienes `AdaptyFlowView` _sin_ llamar a `AdaptyUI.getFlowView()`, también necesitarás llamar al método `.showFlow()`. Para mostrar el flow visual en la pantalla del dispositivo, primero debes configurarlo. Para ello, usa esta función composable: ```kotlin showLineNumbers AdaptyFlowScreen( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------------------------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **flowConfiguration** | obligatorio | Proporciona un objeto `AdaptyUI.FlowConfiguration` con los detalles visuales del flow. Usa el método `AdaptyUI.getFlowConfiguration(flow)` para cargarlo. Consulta el tema [Fetch the view configuration](android-get-pb-paywalls#fetch-the-view-configuration) para más detalles. | | **products** | opcional | Proporciona un array de `AdaptyPaywallProduct` para optimizar el momento en que se muestran los productos en pantalla. Si se pasa `null`, AdaptyUI obtendrá los productos necesarios automáticamente. | | **eventListener** | opcional | Proporciona un `AdaptyFlowEventListener` para observar los eventos del flow. Se recomienda extender `AdaptyFlowDefaultEventListener` para facilitar su uso. Consulta el tema [Handle flow & paywall events](android-handling-events) para más detalles. | | **insets** | opcional |

Los insets son los espacios alrededor del flow que evitan que los elementos interactivos queden ocultos tras las barras del sistema.

Por defecto: `Unspecified`, lo que significa que Adapty ajustará los insets automáticamente, lo cual funciona bien para flows edge-to-edge.

Si tu flow no es edge-to-edge, puede que quieras definir insets personalizados. Consulta la sección [Change flow insets](android-present-paywalls#change-flow-insets) a continuación.

| | **customAssets** | opcional | Pasa un objeto `AdaptyCustomAssets` para reemplazar imágenes y vídeos en tu flow o paywall en tiempo de ejecución. Consulta [Customize assets](android-get-pb-paywalls#customize-assets) para más detalles. | | **tagResolver** | opcional | Usa `AdaptyUiTagResolver` para resolver etiquetas personalizadas dentro del texto del flow. Este resolver recibe un parámetro de etiqueta y lo resuelve en la cadena correspondiente. Consulta el tema Custom tags in Paywall Builder para más detalles. | | **timerResolver** | opcional | Pasa el resolver aquí si vas a utilizar funcionalidad de temporizador personalizado. | :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Cambiar los márgenes del flow \{#change-flow-insets\} Los márgenes son los espacios alrededor del flow que evitan que los elementos interactivos queden ocultos detrás de las barras del sistema. Por defecto, Adapty ajusta automáticamente estos márgenes, lo que funciona muy bien para flows edge-to-edge. Si tu flow no es edge-to-edge, puede que quieras definir márgenes personalizados: - Si ni la barra de estado ni la barra de navegación se superponen con el `AdaptyFlowView`, usa `AdaptyFlowInsets.None`. - Para configuraciones más personalizadas, por ejemplo si tu flow se superpone con la barra de estado superior pero no con la inferior, puedes establecer solo el `bottomInset` a `0`, como se muestra en el ejemplo siguiente: ```kotlin showLineNumbers //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view flowView.onReceiveSystemBarsInsets { insets -> val flowInsets = AdaptyFlowInsets.vertical(insets.top, 0) flowView.showFlow( flowConfiguration, products, eventListener, flowInsets, customAssets, tagResolver, timerResolver, ) } ``` ```java showLineNumbers ... ViewCompat.setOnApplyWindowInsetsListener(flowView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(flowView, null); AdaptyFlowInsets flowInsets = AdaptyFlowInsets.vertical(systemBarInsets.top, 0); flowView.showFlow(flowConfiguration, products, eventListener, flowInsets); return insets; }); ``` ## Usar el temporizador definido por el desarrollador \{#use-developer-defined-timer\} Para usar temporizadores definidos por el desarrollador en tu app, crea un objeto `timerResolver`—un diccionario o mapa que asocia temporizadores personalizados con los valores de cadena que los reemplazarán cuando el flow se renderice. Aquí tienes un ejemplo: ```kotlin showLineNumbers ... val customTimers = mapOf( "CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025 ) val timerResolver = AdaptyUiTimerResolver { timerId -> customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } ) } ``` ```java showLineNumbers ... Map customTimers = new HashMap<>(); customTimers.put( "CUSTOM_TIMER_NY", new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime() ); AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() { @NonNull @Override public Date timerEndAtDate(@NonNull String timerId) { Date date = customTimers.get(timerId); return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* en 1 hora */ } }; ``` En este ejemplo, `CUSTOM_TIMER_NY` es el **Timer ID** del temporizador definido por el desarrollador que configuraste en el Adapty dashboard. El `timerResolver` garantiza que tu app actualice dinámicamente el temporizador con el valor correcto, como `13d 09h 03m 34s` (calculado como la hora de fin del temporizador, por ejemplo el Año Nuevo, menos la hora actual). ## Usar etiquetas personalizadas \{#use-custom-tags\} Para usar etiquetas personalizadas en tu aplicación móvil, crea un objeto `tagResolver`—un diccionario o mapa que asocia etiquetas personalizadas con los valores de cadena que las reemplazarán cuando se renderice el flow. Aquí tienes un ejemplo: ```kotlin showLineNumbers val customTags = mapOf("USERNAME" to "John") val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] } ``` ```java showLineNumbers Map customTags = new HashMap<>(); customTags.put("USERNAME", "John"); AdaptyUiTagResolver tagResolver = customTags::get; ``` En este ejemplo, `USERNAME` es una etiqueta personalizada que introdujiste en el Adapty dashboard como ``. El `tagResolver` se asegura de que tu app reemplace dinámicamente esta etiqueta personalizada con el valor indicado, como `John`. Te recomendamos crear y completar el `tagResolver` justo antes de mostrar tu flow. Una vez listo, pásalo al método de AdaptyUI que uses para presentar el flow. ## Cambiar el color del indicador de carga del flow \{#change-flow-loading-indicator-color\} Puedes reemplazar el color predeterminado del indicador de carga de la siguiente forma: ```xml showLineNumbers title = "XML" ```
Si has personalizado un paywall con el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese paywall ya incluye tanto lo que se debe mostrar como la forma en que debe mostrarse. :::warning Esta guía es solo para **paywalls del nuevo Paywall Builder** que requieren SDK v3.0. El proceso para mostrar paywalls difiere según la versión del Paywall Builder utilizada, los paywalls de Remote Config y el [modo Observer](observer-vs-full-mode). - Para mostrar **paywalls de Remote Config**, consulta [Renderizar un paywall diseñado con Remote Config](present-remote-config-paywalls). - Para mostrar **paywalls en modo Observer**, consulta [Android - Mostrar paywalls del Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) ::: Para obtener el objeto `viewConfiguration` utilizado a continuación, consulta [Obtener paywalls del Paywall Builder y su configuración](android-get-pb-paywalls). Para mostrar el paywall visual en la pantalla del dispositivo, primero debes configurarlo. Para ello, llama al método `AdaptyUI.getPaywallView()` o crea el `AdaptyPaywallView` directamente: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) ``` ```kotlin showLineNumbers val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { showPaywall( viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver ); ``` ```java showLineNumbers AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.showPaywall(viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver); ``` ```xml showLineNumbers ``` Después de que la vista se haya creado correctamente, puedes añadirla a la jerarquía de vistas y mostrarla en la pantalla del dispositivo. Si obtienes `AdaptyPaywallView` _sin_ llamar a `AdaptyUI.getPaywallView()`, también tendrás que llamar al método `.showPaywall()`. Para mostrar el paywall visual en la pantalla del dispositivo, primero debes configurarlo. Para ello, utiliza esta función composable: ```kotlin showLineNumbers AdaptyPaywallScreen( viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------------------------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **viewConfiguration** | obligatorio | Proporciona un objeto `AdaptyUI.LocalizedViewConfiguration` con los detalles visuales del paywall. Usa el método `Adapty.getViewConfiguration(paywall)` para cargarlo. Consulta el tema [Fetch the visual configuration of paywall](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) para más detalles. | | **products** | opcional | Proporciona un array de `AdaptyPaywallProduct` para optimizar el momento en que se muestran los productos en pantalla. Si se pasa `null`, AdaptyUI obtendrá automáticamente los productos necesarios. | | **eventListener** | opcional | Proporciona un `AdaptyUiEventListener` para observar los eventos del paywall. Se recomienda extender `AdaptyUiDefaultEventListener` para facilitar su uso. Consulta el tema [Handling paywall events](android-handling-events) para más detalles. | | **insets** | opcional |

Los insets son los espacios alrededor del paywall que evitan que los elementos interactivos queden ocultos detrás de las barras del sistema.

Por defecto: `UNSPECIFIED`, lo que significa que Adapty ajustará los insets automáticamente, algo que funciona muy bien para paywalls edge-to-edge.

Si tu paywall no es edge-to-edge, puede que quieras definir insets personalizados. Puedes ver cómo hacerlo en la sección [Change paywall insets](android-present-paywalls#change-paywall-insets) más abajo.

| | **personalizedOfferResolver** | opcional | Para indicar precios personalizados ([más información](https://developer.android.com/google/play/billing/integrate#personalized-price)), implementa `AdaptyUiPersonalizedOfferResolver` y añade tu propia lógica que devuelva `true` para los `AdaptyPaywallProduct` cuyo precio sea personalizado, y `false` en caso contrario. | | **tagResolver** | opcional | Usa `AdaptyUiTagResolver` para resolver etiquetas personalizadas dentro del texto del paywall. Este resolver recibe un parámetro de etiqueta y lo resuelve a la cadena correspondiente. Consulta el tema Custom tags in Paywall Builder para más detalles. | | **timerResolver** | opcional | Pasa el resolver aquí si vas a usar la funcionalidad de temporizador personalizado. | :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Cambiar los márgenes del paywall \{#change-paywall-insets\} Los márgenes son los espacios alrededor del paywall que evitan que los elementos interactivos queden ocultos detrás de las barras del sistema. Por defecto, Adapty ajusta automáticamente estos márgenes, lo que funciona perfectamente para paywalls de borde a borde. Si tu paywall no es de borde a borde, puede que quieras definir márgenes personalizados: - Si ni la barra de estado ni la barra de navegación se superponen con `AdaptyPaywallView`, usa `AdaptyPaywallInsets.NONE`. - Para configuraciones más personalizadas, como cuando tu paywall se superpone con la barra de estado superior pero no con la inferior, puedes establecer solo el `bottomInset` a `0`, como se muestra en el ejemplo a continuación: ```kotlin showLineNumbers //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view paywallView.onReceiveSystemBarsInsets { insets -> val paywallInsets = AdaptyPaywallInsets.vertical(insets.top, 0) paywallView.showPaywall( viewConfiguration, products, eventListener, paywallInsets, personalizedOfferResolver, tagResolver, timerResolver, ) } ``` ```java showLineNumbers ... ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(paywallView, null); AdaptyPaywallInsets paywallInsets = AdaptyPaywallInsets.of(systemBarInsets.top, 0); paywallView.showPaywall(paywall, products, viewConfiguration, paywallInsets, productTitleResolver); return insets; }); ``` ## Usar temporizador definido por el desarrollador \{#use-developer-defined-timer\} Para usar temporizadores definidos por el desarrollador en tu app, crea un objeto `timerResolver` —un diccionario o mapa que asocia temporizadores personalizados con los valores de cadena que los reemplazarán cuando se renderice el paywall. Aquí tienes un ejemplo: ```kotlin showLineNumbers ... val customTimers = mapOf( "CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025 ) val timerResolver = AdaptyUiTimerResolver { timerId -> customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } ) } ``` ```java showLineNumbers ... Map customTimers = new HashMap<>(); customTimers.put( "CUSTOM_TIMER_NY", new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime() ); AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() { @NonNull @Override public Date timerEndAtDate(@NonNull String timerId) { Date date = customTimers.get(timerId); return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* in 1 hour */ } }; ``` En este ejemplo, `CUSTOM_TIMER_NY` es el **Timer ID** del temporizador definido por el desarrollador que configuraste en el Adapty dashboard. El `timerResolver` garantiza que tu app actualice dinámicamente el temporizador con el valor correcto, como `13d 09h 03m 34s` (calculado como la hora de finalización del temporizador, por ejemplo el Año Nuevo, menos la hora actual). ## Usa etiquetas personalizadas \{#use-custom-tags\} Para usar etiquetas personalizadas en tu app, crea un objeto `tagResolver`—un diccionario o mapa que asocia etiquetas personalizadas con los valores de cadena que las reemplazarán cuando se renderice el paywall. Aquí tienes un ejemplo: ```kotlin showLineNumbers val customTags = mapOf("USERNAME" to "John") val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] } ``` ```java showLineNumbers Map customTags = new HashMap<>(); customTags.put("USERNAME", "John"); AdaptyUiTagResolver tagResolver = customTags::get; ``` En este ejemplo, `USERNAME` es una etiqueta personalizada que introdujiste en el Adapty dashboard como ``. El `tagResolver` se encarga de que tu app reemplace dinámicamente esta etiqueta personalizada con el valor especificado, como `John`. Te recomendamos crear y rellenar el `tagResolver` justo antes de mostrar tu paywall. Una vez listo, pásalo al método de AdaptyUI que uses para presentar el paywall. ## Cambiar el color del indicador de carga del paywall \{#change-paywall-loading-indicator-color\} Puedes cambiar el color por defecto del indicador de carga de la siguiente manera: ```xml showLineNumbers title = "XML" ```
--- # File: android-handle-paywall-actions --- --- title: "Responder a acciones de flow - Android" description: "Gestiona acciones de botones de flows y paywalls en tu app Android." --- Si estás creando flows o paywalls con el Adapty Flow Builder o Paywall Builder, es fundamental configurar correctamente los botones: 1. Añade un [botón en el builder](paywall-buttons) y asígnale una acción predefinida o crea un ID de acción personalizado. 2. Escribe el código en tu app para gestionar cada acción que hayas asignado. Esta guía explica cómo gestionar acciones personalizadas y predefinidas en tu código. :::warning **Solo las compras, restauraciones, cierres de flow/paywall y la apertura de URLs se gestionan automáticamente.** El resto de acciones de botón requieren una implementación adecuada en el código de la app. ::: ## Cerrar flows y paywalls \{#close-flows-and-paywalls\} Para añadir un botón que cierre tu flow o paywall: 1. En el builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un handler para la acción `close`. :::info En el SDK de Android, la acción `close` cierra el flow o paywall por defecto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un flow podría desencadenar la apertura de otro. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ## Abrir URLs desde flows y paywalls \{#open-urls-from-flows-and-paywalls\} :::tip Si quieres añadir un grupo de enlaces (p. ej., términos de uso y restauración de compras), añade un elemento **Link** en el builder y trátalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu flow o paywall (p. ej., **Terms of use** o **Privacy policy**): 1. En el builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieres abrir. 2. En el código de tu app, implementa un handler para la acción `openUrl` que abra la URL recibida en un navegador. :::info En el SDK de Android, la acción `openUrl` activa la apertura de la URL por defecto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.OpenUrl -> { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior context.startActivity(intent) } } } ``` ## Gestionar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el builder, añade un botón, asígnale la acción **Custom** y asígnale un ID. 2. En el código de tu app, implementa un handler para el ID de acción que hayas creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro flow o paywall: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Custom -> { if (action.customId == "openNewPaywall") { // Display another flow or paywall } } } } ``` Si estás construyendo paywalls con el Adapty paywall builder, es fundamental configurar los botones correctamente: 1. Añade un [botón en el Paywall Builder](paywall-buttons) y asígnale una acción preexistente o crea un ID de acción personalizado. 2. Escribe código en tu app para gestionar cada acción que hayas asignado. Esta guía explica cómo gestionar acciones personalizadas y preexistentes en tu código. :::warning **Solo las compras, restauraciones, cierres de paywall y apertura de URLs se gestionan automáticamente.** El resto de acciones de botón requieren una implementación adecuada en el código de la app. ::: ## Cerrar paywalls \{#close-paywalls\} Para añadir un botón que cierre tu paywall: 1. En el Paywall Builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un manejador para la acción `close` que cierre el paywall. :::info En el SDK de Android, la acción `close` cierra el paywall por defecto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un paywall podría abrir otro. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ## Abrir URLs desde paywalls \{#open-urls-from-paywalls\} :::tip Si quieres añadir un grupo de enlaces (p. ej., términos de uso y restauración de compras), añade un elemento **Link** en el paywall builder y trátalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu paywall (p. ej., **Terms of use** o **Privacy policy**): 1. En el paywall builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieres abrir. 2. En el código de tu app, implementa un manejador para la acción `openUrl` que abra la URL recibida en un navegador. :::info En el SDK de Android, la acción `openUrl` activa la apertura de la URL de forma predeterminada. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.OpenUrl -> { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior context.startActivity(intent) } } } ``` ## Iniciar sesión en la app \{#log-into-the-app\} Para añadir un botón que permita a los usuarios iniciar sesión en tu app: 1. En el Paywall Builder, añade un botón y asígnale la acción **Login**. 2. En el código de tu app, implementa un handler para la acción `login` que identifique a tu usuario. ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Login -> { val intent = Intent(context, LoginActivity::class.java) context.startActivity(intent) } } } ``` ## Gestionar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el Paywall Builder, añade un botón, asígnale la acción **Custom** y asígnale un ID. 2. En el código de tu app, implementa un controlador para el ID de acción que hayas creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro paywall: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Custom -> { if (action.customId == "openNewPaywall") { // Display another paywall } } } } ``` --- # File: android-handling-events --- --- title: "Manejar eventos de flow y paywall - Android" description: "Maneja eventos de flow y paywall en tu app Android." --- :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de flows. También debes implementar el manejo de botones (cerrar el flow, abrir enlaces, etc.). Consulta nuestra [guía sobre el manejo de acciones de botones](android-handle-paywall-actions) para más detalles. ::: Los flows y paywalls configurados con el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder) no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan algunos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.) y notificaciones sobre acciones relacionadas con compras. A continuación aprenderás cómo responder a estos eventos. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: Si necesitas controlar o monitorizar los procesos que ocurren en la pantalla de compra, implementa los métodos de `AdaptyFlowEventListener`. Si quieres mantener el comportamiento predeterminado en algunos casos, puedes extender `AdaptyFlowDefaultEventListener` y sobreescribir solo los métodos que quieras cambiar. A continuación se muestran los valores predeterminados de `AdaptyFlowDefaultEventListener`. ### Eventos generados por el usuario \{#user-generated-events\} #### Selección de producto \{#product-selection\} Si se selecciona un producto para su compra (por el usuario o por el sistema), se invocará este método: ```kotlin showLineNumbers title="Kotlin" public override fun onProductSelected( product: AdaptyPaywallProduct, context: Context, ) {} ```
Ejemplo de evento (haz clic para expandir) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
#### Compra iniciada \{#started-purchase\} Si un usuario inicia el proceso de compra, se invocará este método: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseStarted( product: AdaptyPaywallProduct, context: Context, ) {} ```
Ejemplo de evento (Haz clic para expandir) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
El método no se invocará en el modo Observer. Consulta el tema [Android - Mostrar paywalls del Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles. #### Compra exitosa, cancelada o pendiente \{#successful-canceled-or-pending-purchase\} Si la compra se realiza correctamente, se invocará este método: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFinished( purchaseResult: AdaptyPurchaseResult, product: AdaptyPaywallProduct, context: Context, ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) context.getActivityOrNull()?.onBackPressed() } ```
Ejemplos de eventos (Haz clic para expandir) ```javascript // Successful purchase { "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Cancelled purchase { "purchaseResult": { "type": "UserCanceled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Pending purchase { "purchaseResult": { "type": "Pending" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
Recomendamos cerrar la pantalla en ese caso. El método no se invocará en el modo Observer. Consulta el tema [Android - Presentar paywalls del Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles. #### Compra fallida \{#failed-purchase\} Si una compra falla debido a un error, se invocará este método. Esto incluye errores de Google Play Billing (restricciones de pago, productos no válidos, fallos de red), fallos en la verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones del usuario activan `onPurchaseFinished` con un resultado cancelado en su lugar, y los pagos pendientes no activan este método. ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFailure( error: AdaptyError, product: AdaptyPaywallProduct, context: Context, ) {} ```
Ejemplo de evento (haz clic para expandir) ```javascript { "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
El método no se invocará en el modo Observer. Consulta el tema [Android - Mostrar paywalls del Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles. #### Navegación de pago web finalizada \{#finished-web-payment-navigation\} Este método se invoca tras intentar abrir un [web paywall](web-paywall) para un producto específico. Esto incluye tanto los intentos de navegación exitosos como los fallidos: ```kotlin showLineNumbers title="Kotlin" public override fun onFinishWebPaymentNavigation( product: AdaptyPaywallProduct?, error: AdaptyError?, context: Context, ) {} ``` **Parámetros:** | Parámetro | Descripción | |:------------|:-------------------------------------------------------------------------------------------------------------------| | **product** | Un `AdaptyPaywallProduct` para el cual se abrió el paywall web. Puede ser `null`. | | **error** | Un objeto `AdaptyError` si la navegación del paywall web falló; `null` si la navegación fue exitosa. |
Ejemplos de eventos (Haz clic para expandir) ```javascript // Successful navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "web_navigation_failed", "message": "Failed to open web paywall", "details": { "underlyingError": "Browser unavailable" } } } ```
#### Restauración exitosa \{#successful-restore\} Si la restauración de una compra se realiza correctamente, se invocará este método: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreSuccess( profile: AdaptyProfile, context: Context, ) {} ```
Ejemplo de evento (clic para expandir) ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ```
Recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de suscripción](android-listen-subscription-changes) para saber cómo comprobarlo. #### Restauración fallida \{#failed-restore\} Si `Adapty.restorePurchases()` falla, se invocará este método: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreFailure( error: AdaptyError, context: Context, ) {} ```
Ejemplo de evento (haz clic para expandir) ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ```
#### Actualizar suscripción \{#upgrade-subscription\} Cuando un usuario intenta comprar una nueva suscripción mientras hay otra activa, puedes controlar cómo se debe gestionar la nueva compra sobreescribiendo este método. Tienes dos opciones: 1. **Reemplaza la suscripción actual** con la nueva: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback, ): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived( AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) .build() ) return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` 2. **Mantener ambas suscripciones** (agregar la nueva por separado): ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback, ): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` :::note Si no sobreescribes este método, el comportamiento predeterminado es mantener ambas suscripciones activas (equivalente a usar `AdaptyPurchaseParameters.Empty`). ::: También puedes establecer parámetros de compra adicionales si es necesario: ```kotlin AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription .withOfferPersonalized(true) // optional - if using personalized pricing .build() ```
Ejemplo de evento (haz clic para expandir) ```javascript { "product": { "vendorProductId": "premium_yearly", "localizedTitle": "Premium Yearly", "localizedDescription": "Premium subscription for 1 year", "localizedPrice": "$99.99", "price": 99.99, "currencyCode": "USD" }, "subscriptionUpdateParams": { "replacementMode": "with_time_proration" } } ```
### Obtención de datos y renderizado \{#data-fetching-and-rendering\} #### Errores al cargar productos \{#product-loading-errors\} Si no pasas los productos durante la inicialización, AdaptyUI recuperará los objetos necesarios del servidor por sí solo. Si esta operación falla, AdaptyUI notificará el error invocando este método: ```kotlin showLineNumbers title="Kotlin" public override fun onLoadingProductsFailure( error: AdaptyError, context: Context, ): Boolean = false ```
Ejemplo de evento (Haz clic para expandir) ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ```
Si devuelves `true`, AdaptyUI repetirá la solicitud en 2 segundos. #### Errores de renderizado \{#rendering-errors\} Si ocurre un error durante el renderizado de la interfaz, se notificará mediante este método: ```kotlin showLineNumbers title="Kotlin" public override fun onError( error: AdaptyError, context: Context, ) {} ```
Ejemplo de evento (Haz clic para expandir) ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render flow interface", "details": { "underlyingError": "Invalid flow configuration" } } } ```
En una situación normal, estos errores no deberían ocurrir, así que si te encuentras con alguno, por favor comunícanoslo. ### Navegación \{#navigation\} #### Botón de retroceso del sistema \{#system-back-button\} Por defecto, un flow no se puede cerrar con el botón de retroceso del sistema ni con el gesto de deslizamiento — el usuario lo abandona a través de un camino que tú defines, como un botón **Close** o una acción `on_device_back` en el builder. Si quieres que el botón de retroceso del sistema cierre el flow, sobreescribe `onBackPressed` y devuelve `false` para que tu actividad o fragmento anfitrión gestione la pulsación: ```kotlin showLineNumbers title="Kotlin" public override fun onBackPressed(context: Context): Boolean { return false // let the host handle the back press (e.g. finish the activity or pop the fragment) } ``` Este callback se invoca solo cuando no hay ninguna acción `on_device_back` configurada para la pantalla actual — una acción configurada tiene precedencia y se gestiona internamente. Devuelve `true` para consumir la pulsación (comportamiento por defecto), o `false` para que se ejecute el propio manejo de retroceso del host. ### Eventos reservados \{#reserved-events\} `AdaptyFlowEventListener` declara algunos callbacks para funcionalidades que los flows aún no usan. No es necesario implementarlos — `AdaptyFlowDefaultEventListener` ya proporciona implementaciones vacías por defecto. | Método | Descripción | |:-------|:------------| | **onAnalyticEvent** | Reservado para eventos de análisis personalizados de un flow. Los flows aún no emiten estos eventos en tu código, por lo que no necesitas implementarlo. | | **onShowAppRate** | Reservado para solicitudes de valoración de la app desde un flow. Los flows aún no activan solicitudes de valoración, por lo que no necesitas implementarlo. | | **onShowRequestPermission** | Reservado para solicitudes de permisos del sistema (como notificaciones push o acceso a la cámara) desde un flow. Los flows aún no activan solicitudes de permisos, por lo que no necesitas implementarlo. |
:::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de paywall. También debes implementar el manejo de botones (cerrar el paywall, abrir enlaces, etc.). Consulta nuestra [guía sobre el manejo de acciones de botones](android-handle-paywall-actions) para más detalles. ::: Los paywall configurados con el [Paywall Builder](adapty-paywall-builder) no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan ciertos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el paywall. A continuación se explica cómo responder a estos eventos. :::warning Esta guía es exclusivamente para **paywall del nuevo Paywall Builder**, que requieren Adapty SDK v3.0 o posterior. ::: :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: Si necesitas controlar o monitorizar los procesos que ocurren en la pantalla de compra, implementa los métodos de `AdaptyUiEventListener`. Si quieres mantener el comportamiento predeterminado en algunos casos, puedes extender `AdaptyUiDefaultEventListener` y sobreescribir solo los métodos que desees cambiar. A continuación se muestran los valores predeterminados de `AdaptyUiDefaultEventListener`. ### Eventos generados por el usuario \{#user-generated-events\} #### Selección de producto \{#product-selection\} Si se selecciona un producto para la compra (por un usuario o por el sistema), se invocará este método: ```kotlin showLineNumbers title="Kotlin" public override fun onProductSelected( product: AdaptyPaywallProduct, context: Context, ) {} ```
Ejemplo de evento (haz clic para expandir) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
#### Compra iniciada \{#started-purchase\} Si un usuario inicia el proceso de compra, se invocará este método: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseStarted( product: AdaptyPaywallProduct, context: Context, ) {} ```
Ejemplo de evento (haz clic para expandir) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
El método no se invocará en el modo Observer. Consulta el tema [Android - Presentar paywalls del Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles. #### Compra exitosa, cancelada o pendiente \{#successful-canceled-or-pending-purchase\} Si la compra se realiza correctamente, se invocará este método: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFinished( purchaseResult: AdaptyPurchaseResult, product: AdaptyPaywallProduct, context: Context, ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) context.getActivityOrNull()?.onBackPressed() } ```
Ejemplos de eventos (Haz clic para expandir) ```javascript // Successful purchase { "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Cancelled purchase { "purchaseResult": { "type": "UserCanceled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Pending purchase { "purchaseResult": { "type": "Pending" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
Recomendamos cerrar la pantalla en ese caso. El método no se invocará en el modo Observer. Consulta el tema [Android - Presentar paywalls del Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles. #### Compra fallida \{#failed-purchase\} Si una compra falla debido a un error, se invocará este método. Esto incluye errores de Google Play Billing (restricciones de pago, productos no válidos, fallos de red), fallos en la verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones por parte del usuario activan `onPurchaseFinished` con un resultado de cancelado, y los pagos pendientes no activan este método. ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFailure( error: AdaptyError, product: AdaptyPaywallProduct, context: Context, ) {} ```
Ejemplo de evento (haz clic para expandir) ```javascript { "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
El método no se invocará en el modo Observer. Consulta el tema [Android - Mostrar paywalls de Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles. #### Navegación web de pago finalizada \{#finished-web-payment-navigation\} Este método se invoca tras un intento de abrir un [paywall web](web-paywall) para un producto específico. Esto incluye tanto los intentos de navegación exitosos como los fallidos: ```kotlin showLineNumbers title="Kotlin" public override fun onFinishWebPaymentNavigation( product: AdaptyPaywallProduct?, error: AdaptyError?, context: Context, ) {} ``` **Parámetros:** | Parámetro | Descripción | |:------------|:--------------------------------------------------------------------------------------------------------------------| | **product** | Un `AdaptyPaywallProduct` para el cual se abrió el paywall web. Puede ser `null`. | | **error** | Un objeto `AdaptyError` si la navegación del paywall web falló; `null` si la navegación fue correcta. |
Ejemplos de eventos (Haz clic para expandir) ```javascript // Successful navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "web_navigation_failed", "message": "Failed to open web paywall", "details": { "underlyingError": "Browser unavailable" } } } ```
#### Restaurar con éxito \{#successful-restore\} Si la restauración de una compra se realiza con éxito, se invocará este método: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreSuccess( profile: AdaptyProfile, context: Context, ) {} ```
Ejemplo de evento (haz clic para expandir) ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ```
Te recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de la suscripción](android-listen-subscription-changes) para aprender a comprobarlo. #### Restauración fallida \{#failed-restore\} Si `Adapty.restorePurchases()` falla, se invocará este método: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreFailure( error: AdaptyError, context: Context, ) {} ```
Ejemplo de evento (haz clic para expandir) ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ```
#### Actualizar suscripción \{#upgrade-subscription\} Cuando un usuario intenta comprar una nueva suscripción mientras otra suscripción está activa, puedes controlar cómo debe gestionarse la nueva compra sobreescribiendo este método. Tienes dos opciones: 1. **Reemplaza la suscripción actual** con la nueva: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived( AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) .build() ) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` 2. **Mantener ambas suscripciones** (añadir la nueva por separado): ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` :::note Si no sobreescribes este método, el comportamiento predeterminado es mantener ambas suscripciones activas (equivalente a usar `AdaptyPurchaseParameters.Empty`). ::: También puedes establecer parámetros de compra adicionales si lo necesitas: ```kotlin AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription .withOfferPersonalized(true) // optional - if using personalized pricing .build() ``` Si se compra una nueva suscripción mientras otra sigue activa, sobreescribe este método para reemplazar la actual con la nueva. Si la suscripción activa debe seguir activa y la nueva se añade por separado, llama a `onSubscriptionUpdateParamsReceived(null)`: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingSubscriptionUpdateParams( product: AdaptyPaywallProduct, context: Context, onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, ) { onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) } ```
Ejemplo de evento (haz clic para expandir) ```javascript { "product": { "vendorProductId": "premium_yearly", "localizedTitle": "Premium Yearly", "localizedDescription": "Premium subscription for 1 year", "localizedPrice": "$99.99", "price": 99.99, "currencyCode": "USD" }, "subscriptionUpdateParams": { "replacementMode": "with_time_proration" } } ```
### Obtención y renderizado de datos \{#data-fetching-and-rendering\} #### Errores al cargar productos \{#product-loading-errors\} Si no pasas los productos durante la inicialización, AdaptyUI los obtendrá del servidor por su cuenta. Si esta operación falla, AdaptyUI notificará el error invocando este método: ```kotlin showLineNumbers title="Kotlin" public override fun onLoadingProductsFailure( error: AdaptyError, context: Context, ): Boolean = false ```
Ejemplo de evento (Haz clic para expandir) ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ```
Si devuelves `true`, AdaptyUI repetirá la solicitud en 2 segundos. #### Errores de renderizado \{#rendering-errors\} Si se produce un error durante el renderizado de la interfaz, se notificará llamando a este método: ```kotlin showLineNumbers title="Kotlin" public override fun onRenderingError( error: AdaptyError, context: Context, ) {} ```
Ejemplo de evento (haz clic para expandir) ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ```
En condiciones normales, estos errores no deberían producirse, así que si encuentras alguno, por favor comunícanoslo.
--- # File: android-use-fallback-paywalls --- --- title: "Android - Usar paywalls de respaldo" description: "Gestiona los casos en que los usuarios están sin conexión o los servidores de Adapty no están disponibles." --- :::warning Los paywalls de respaldo son compatibles con Android SDK v2.11 y versiones posteriores. ::: Para mantener una experiencia de usuario fluida, es importante configurar [respaldos](/fallback-paywalls) para tus flows, [paywalls](paywalls) y [onboardings](onboardings). Esta precaución amplía las capacidades de la aplicación en caso de pérdida parcial o total de la conexión a internet. * **Si la aplicación no puede acceder a los servidores de Adapty:** Podrá mostrar un flow o paywall de respaldo, y acceder a la configuración local del onboarding. * **Si la aplicación no puede acceder a internet:** Podrá mostrar un flow o paywall de respaldo. Los onboardings incluyen contenido remoto y requieren conexión a internet para funcionar. :::important Antes de seguir los pasos de esta guía, [descarga](/local-fallback-paywalls) los archivos de configuración de respaldo desde Adapty. ::: ## Configuración \{#configuration\} 1. Mueve el archivo de configuración de respaldo al directorio `assets` o `res/raw` de tu proyecto Android. 2. Llama al método `.setFallback` **antes** de obtener el flow, paywall u onboarding de destino. ```kotlin showLineNumbers //if you put the 'android_fallback.json' file to the 'assets' directory val location = FileLocation.fromAsset("android_fallback.json") //or `FileLocation.fromAsset("/android_fallback.json")` if you placed it in a child folder of 'assets') //if you put the 'android_fallback.json' file to the 'res/raw' directory val location = FileLocation.fromResId(context, R.raw.android_fallback) //you can also pass a file URI val fileUri: Uri = //get Uri for the file with fallback paywalls val location = FileLocation.fromFileUri(fileUri) //pass the file location Adapty.setFallback(location, callback) ``` ```java showLineNumbers //if you put the 'android_fallback.json' file to the 'assets' directory FileLocation location = FileLocation.fromAsset("android_fallback.json"); //or `FileLocation.fromAsset("/android_fallback.json");` if you placed it in a child folder of 'assets') //if you put the 'android_fallback.json' file to the 'res/raw' directory FileLocation location = FileLocation.fromResId(context, R.raw.android_fallback); //you can also pass a file URI Uri fileUri = //get Uri for the file with fallback paywalls FileLocation location = FileLocation.fromFileUri(fileUri); //pass the file location Adapty.setFallback(location, callback); ``` | Parámetro | Descripción | | :----------- | :----------------------------------------------------------- | | **location** | El objeto [FileLocation](https://android.adapty.io/adapty/com.adapty.utils/-file-location/-companion/) para el archivo de configuración de respaldo | --- # File: android-localizations-and-locale-codes --- --- title: "Usar localizaciones y códigos de idioma en el SDK de Android" description: "Gestiona las localizaciones de tu app y los códigos de idioma para llegar a una audiencia global (Android)." --- ## Por qué es importante \{#why-this-is-important\} Hay varios escenarios en los que los códigos de idioma entran en juego; por ejemplo, cuando intentas obtener el paywall correcto para la localización actual de tu app. Como los códigos de idioma son complejos y pueden variar de una plataforma a otra, nos basamos en un estándar interno para todas las plataformas que admitimos. Sin embargo, precisamente por esa complejidad, es muy importante que entiendas exactamente qué le estás enviando a nuestro servidor para obtener la localización correcta y qué ocurre después, de modo que siempre recibas lo que esperas. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código se compone de subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués de Brasil), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de idioma \{#locale-code-matching\} Cuando Adapty recibe una llamada del SDK con el código de idioma y empieza a buscar la localización correspondiente de un paywall, ocurre lo siguiente: 1. La cadena de idioma entrante se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`). 2. A continuación, buscamos la localización cuyo código de idioma coincida exactamente. 3. Si no se encuentra ninguna coincidencia, tomamos la subcadena antes del primer guion (`pt` para `pt-br`) y buscamos la localización que coincida. 4. Si tampoco se encuentra ninguna coincidencia, devolvemos la localización predeterminada `en`. De este modo, un dispositivo iOS que envíe `'pt_BR'`, un dispositivo Android que envíe `pt-BR` y otro dispositivo que envíe `pt-br` obtendrán el mismo resultado. ## Implementar localizaciones: la forma recomendada \{#implementing-localizations-recommended-way\} Si te estás preguntando sobre las localizaciones, probablemente ya estés trabajando con los archivos de cadenas localizadas de tu proyecto. En ese caso, te recomendamos añadir un par clave-valor con el código de idioma de Adapty correspondiente en cada uno de esos archivos. Luego, extrae el valor de esa clave al llamar a nuestro SDK, así: ```kotlin showLineNumbers // 1. Modify your strings.xml files /* strings.xml - Spanish */ es /* strings.xml - Portuguese (Brazil) */ pt-br // 2. Extract and use the locale code val localeCode = context.getString(R.string.adapty_paywalls_locale) // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Así te aseguras de tener un control total sobre qué localización se recuperará para cada usuario de tu app. ## Implementar localizaciones: la otra forma \{#implementing-localizations-the-other-way\} Puedes obtener resultados similares (aunque no idénticos) sin definir explícitamente códigos de idioma para cada localización. Esto implica extraer el código de idioma de otros objetos que proporciona tu plataforma, así: ```kotlin showLineNumbers val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.resources.configuration.locales[0] else context.resources.configuration.locale val localeCode = locale.toLanguageTag() // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Ten en cuenta que no recomendamos este enfoque, ya que es difícil predecir exactamente qué recibirá el servidor de Adapty. Si aun así decides usarlo, asegúrate de haber cubierto todos los casos de uso relevantes. --- # File: android-web-paywall --- --- title: "Implementar web paywalls en Android SDK" description: "Configura un web paywall para cobrar sin las comisiones y revisiones de Play Store." --- :::important Antes de empezar, asegúrate de haber [configurado tu web paywall en el dashboard](web-paywall) e instalado Adapty SDK versión 3.15 o posterior. ::: ## Abrir web paywalls \{#open-web-paywalls\} Si estás trabajando con un paywall que desarrollaste tú mismo, necesitas gestionar los web paywalls mediante el método del SDK. El método `.openWebPaywall`: 1. Genera una URL única que permite a Adapty vincular un paywall concreto mostrado a un usuario específico con la página web a la que es redirigido. 2. Detecta cuando tus usuarios vuelven a la app y luego solicita `.getProfile` en intervalos cortos para determinar si los derechos de acceso del perfil se han actualizado. De esta forma, si el pago fue exitoso y los derechos de acceso se actualizaron, la suscripción se activa en la app casi de inmediato. :::note Cuando los usuarios vuelvan a la app, actualiza la interfaz para reflejar los cambios del perfil. Adapty recibirá y procesará los eventos de actualización del perfil. ::: ```kotlin showLineNumbers Adapty.openWebPaywall( activity = activity, product = product, ) { error -> if (error == null) { // the web paywall was opened successfully } else { // handle the error } } ``` :::note Existen dos versiones del método `openWebPaywall`: 1. `openWebPaywall(product)`, que genera las URLs por paywall y también añade los datos del producto a las URLs. 2. `openWebPaywall(paywall)`, que genera las URLs por paywall sin añadir los datos del producto a las URLs. Úsalo cuando tus productos en el paywall de Adapty sean distintos a los del web paywall. ::: ## Abrir web paywalls en un navegador in-app \{#open-web-paywalls-in-an-in-app-browser\} Por defecto, los web paywalls se abren en el navegador externo. Para ofrecer una experiencia de usuario fluida, puedes abrir los web paywalls en un navegador in-app. Esto muestra la página de compra web dentro de tu aplicación, permitiendo a los usuarios completar las transacciones sin cambiar de app. Para habilitarlo, establece el parámetro `presentation` en `AdaptyWebPresentation.InAppBrowser`: ```kotlin showLineNumbers Adapty.openWebPaywall( activity = activity, product = product, presentation = AdaptyWebPresentation.InAppBrowser, ) { error -> if (error == null) { // the web paywall was opened successfully } else { // handle the error val adaptyError = error } } ``` --- # File: android-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del Paywall Builder en el SDK de Android" description: "Solucionar problemas del Paywall Builder en el SDK de Android" --- Esta guía te ayuda a resolver problemas comunes al usar paywalls diseñados en el Adapty Paywall Builder en el SDK de Android. ## Fallo al obtener la configuración de un paywall \{#getting-a-paywall-configuration-fails\} **Problema**: El método `getViewConfiguration` no consigue recuperar la configuración del paywall. **Causa**: El paywall no está habilitado para mostrarse en el dispositivo en el Paywall Builder. **Solución**: Activa el botón **Show on device** en el Paywall Builder. ## El número de vistas del paywall es demasiado alto \{#the-paywall-view-number-is-too-big\} **Problema**: El contador de vistas del paywall muestra el doble del número esperado. **Motivo**: Es posible que estés llamando a `logShowFlow` (Android SDK v4+) / `logShowPaywall` en tu código, lo que duplica el recuento de vistas si estás usando el Paywall Builder o el Flow Builder. Para flows y paywalls creados con estas herramientas, el seguimiento de análisis se realiza automáticamente, por lo que no es necesario utilizar este método. **Solución**: Asegúrate de no llamar a `logShowFlow` (Android SDK v4+) / `logShowPaywall` en tu código si estás usando el Paywall Builder o el Flow Builder. ## Otros problemas \{#other-issues\} **Problema**: Experimentas otros problemas relacionados con el Paywall Builder que no se cubren aquí. **Solución**: Si es necesario, migra el SDK a la versión más reciente siguiendo las [guías de migración](android-sdk-migration-guides). Muchos problemas se resuelven en versiones más recientes del SDK. --- # File: android-quickstart-manual --- --- title: "Habilitar compras en tu paywall personalizado en Android SDK" description: "Integra el SDK de Adapty en tus paywalls personalizados de Android para habilitar compras in-app." --- Esta guía describe cómo integrar Adapty en tus paywalls personalizados. Mantén el control total sobre la implementación del paywall, mientras el SDK de Adapty obtiene los productos, gestiona las nuevas compras y restaura las anteriores. :::important **Esta guía es para desarrolladores que implementan paywalls personalizados.** Si buscas la forma más sencilla de habilitar compras, usa el [Adapty Flow Builder](android-quickstart-paywalls). Con Flow Builder, creas flows en un editor visual sin código, Adapty gestiona toda la lógica de compra automáticamente y puedes probar distintos diseños sin volver a publicar tu app. ::: ## Antes de empezar \{#before-you-start\} ### Configurar productos \{#set-up-products\} Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Products**](product) – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls**](paywalls) – configuraciones que definen qué productos ofrecer. En Adapty, los paywalls son la única forma de recuperar productos, pero este diseño te permite modificar productos, precios y ofertas sin tocar el código de tu app. - [**Placements**](placements) – dónde y cuándo muestras paywalls en tu app (como `main`, `onboarding`, `settings`). Configuras los paywalls para los placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar diferentes paywalls a diferentes usuarios. Asegúrate de entender estos conceptos aunque uses un paywall personalizado. En esencia, son simplemente la forma en que gestionas los productos que vendes en tu aplicación. Para implementar tu paywall personalizado, necesitarás crear un **paywall** y añadirlo a un **placement**. Esta configuración te permite recuperar tus productos. Para entender qué debes hacer en el dashboard, sigue la guía de inicio rápido [aquí](quickstart). ### Gestionar usuarios \{#manage-users\} Puedes trabajar con o sin autenticación de backend en tu lado. Sin embargo, el SDK de Adapty gestiona de forma diferente a los usuarios anónimos e identificados. Lee la [guía de inicio rápido de identificación](android-quickstart-identify) para entender las particularidades y asegurarte de trabajar correctamente con los usuarios. ## Paso 1. Obtener productos \{#step-1-get-products\} Para recuperar los productos de tu paywall personalizado, necesitas: 1. Obtener el objeto `flow` pasando el ID del [placement](placements) al método `getFlow`. 2. Obtener el array de productos para este flow usando el método `getPaywallProducts`. ```kotlin showLineNumbers fun loadPaywall() { Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value Adapty.getPaywallProducts(flow) { productResult -> when (productResult) { is AdaptyResult.Success -> { val products = productResult.value // Use products to build your custom paywall UI } is AdaptyResult.Error -> { val error = productResult.error // Handle the error } } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void loadPaywall() { Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); Adapty.getPaywallProducts(flow, productResult -> { if (productResult instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) productResult).getValue(); // Use products to build your custom paywall UI } else if (productResult instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) productResult).getError(); // Handle the error } }); } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); } ``` ## Paso 2. Aceptar compras \{#step-2-accept-purchases\} Cuando un usuario pulsa sobre un producto en tu paywall personalizado, llama al método `makePurchase` con el producto seleccionado. Esto gestionará el flow de compra y devolverá el perfil actualizado. ```kotlin showLineNumbers fun purchaseProduct(activity: Activity, product: AdaptyPaywallProduct) { Adapty.makePurchase(activity, product) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // Purchase successful, profile updated } is AdaptyPurchaseResult.UserCanceled -> { // User canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Purchase is pending (e.g., user will pay offline with cash) } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void purchaseProduct(Activity activity, AdaptyPaywallProduct product) { Adapty.makePurchase(activity, product, null, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); // Compra exitosa, perfil actualizado } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // El usuario canceló la compra } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // La compra está pendiente (p. ej., el usuario pagará en efectivo offline) } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Gestiona el error } }); } ``` ## Paso 3. Restaurar compras \{#step-3-restore-purchases\} Google Play y otras tiendas de aplicaciones exigen que todas las apps con suscripciones ofrezcan una forma de restaurar las compras. Llama al método `restorePurchases` cuando el usuario pulse el botón de restauración. Esto sincronizará su historial de compras con Adapty y devolverá el perfil actualizado. ```kotlin showLineNumbers fun restorePurchases() { Adapty.restorePurchases { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // Restore successful, profile updated } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void restorePurchases() { Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // Restore successful, profile updated } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); } ``` ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. [Prueba tus compras en Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. Para ver cómo funciona en una implementación lista para producción, consulta el [ProductListFragment.kt](https://github.com/adaptyteam/AdaptySDK-Android/blob/master/app/src/main/java/com/adapty/example/ProductListFragment.kt) en nuestra app de ejemplo, que demuestra la gestión de compras con manejo de errores adecuado, retroalimentación en la UI y gestión de suscripciones. A continuación, [comprueba si los usuarios han completado su compra](android-check-subscription-status) para determinar si mostrar el paywall o conceder acceso a las funciones de pago. --- # File: fetch-paywalls-and-products-android --- --- title: "Obtener paywalls y productos para paywalls de Remote Config en el SDK de Android" description: "Obtén paywalls y productos en el SDK de Android de Adapty para mejorar la monetización de usuarios." --- Antes de mostrar Remote Config y paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a Remote Config y paywalls personalizados. Para obtener orientación sobre cómo obtener flows o paywalls personalizados en el **Flow Builder** o **Paywall Builder**, consulta [Obtener flows y paywalls](android-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. :::
Antes de empezar a obtener flows y productos en tu app móvil (haz clic para expandir) 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow o paywall e incorpora los productos](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow o paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-android) en tu app móvil.
## Obtener información del flow \{#fetch-flow-information\} En Adapty, un [producto](product) actúa como combinación de productos de App Store y Google Play. Estos productos multiplataforma se integran en flows y paywalls, lo que te permite mostrarlos en placements específicos de tu app móvil. Para mostrar los productos, necesitas obtener un `AdaptyFlow` desde uno de tus [placements](placements) con el método `getFlow`. :::important **No uses IDs de producto escritos en el código.** El único ID que debes incluir en el código es el ID del placement. Los flows se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un flow devuelve dos productos hoy y tres mañana, muéstralos todos sin modificar el código. ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. || **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.

Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.

El SDK de Adapty almacena los flows y paywalls en dos capas: la caché actualizada periódicamente descrita anteriormente y los [paywalls de respaldo](android-use-fallback-paywalls). También usamos CDN para obtener flows y paywalls más rápido, y un servidor de respaldo independiente en caso de que el CDN no esté disponible.

| | **loadTimeout** | por defecto: 5 seg |

Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el respaldo local.

Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo de espera especificado en `loadTimeout`, ya que la operación puede consistir en diferentes solicitudes internamente.

| ¡No codifiques los IDs de producto de forma fija! Como los flows se configuran de forma remota, los productos disponibles, el número de productos y las ofertas especiales (como las pruebas gratuitas) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si inicialmente obtienes 2 productos, tu app debería mostrar esos 2 productos. Sin embargo, si más adelante obtienes 3 productos, tu app debería mostrar los 3 sin necesidad de cambios en el código. Lo único que tienes que codificar de forma fija es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Un objeto `AdaptyFlow` que contiene el placement, los identificadores (`id`, `variationId`), el nombre, un array `remoteConfigs` (una entrada por cada locale configurado) y un indicador `hasViewConfiguration`. Para obtener los productos del flow, llama a `getPaywallProducts(flow)`. | :::note En la v4, el parámetro `locale` ha pasado de `getFlow` a `getFlowConfiguration` (que solo se usa al renderizar con AdaptyUI). Para paywalls personalizados, todos los idiomas disponibles se devuelven juntos en `flow.remoteConfigs`; elige el que coincida con el idioma del dispositivo del usuario o con la configuración de tu app. ::: ## Obtener productos \{#fetch-products\} Una vez que tienes el flow, puedes consultar el array de productos que le corresponde: ```kotlin showLineNumbers Adapty.getPaywallProducts(flow) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // the requested products } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallProducts(flow, result -> { if (result instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) result).getValue(); // the requested products } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Lista de objetos [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) con: identificador de producto, nombre del producto, precio, moneda, duración de la suscripción y otras propiedades. | Al implementar tu propio diseño de flow, probablemente necesites acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/). A continuación se muestran las propiedades más utilizadas, pero consulta el documento enlazado para obtener información completa sobre todas las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país de la store seleccionado por el usuario, no en el idioma del dispositivo. | | **Price** | Para mostrar el precio en formato localizado, usa `product.price.localizedString`. Esta localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número con `product.price.amount`. El valor se mostrará en la moneda local. Para obtener el símbolo de la moneda correspondiente, usa `product.price.currencySymbol`. | | **Subscription Period** | Para mostrar el período (p. ej. semana, mes, año, etc.), usa `product.subscriptionDetails?.localizedSubscriptionPeriod`. Esta localización se basa en la configuración regional del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscriptionDetails?.subscriptionPeriod`. Desde ahí puedes acceder al enum `unit` para conocer la duración (es decir, DAY, WEEK, MONTH, YEAR o UNKNOWN). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral verías `MONTH` en la propiedad unit y `3` en numberOfUnits. | | **Introductory Offer** | Para mostrar un badge u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscriptionDetails?.introductoryOfferPhases`. Es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase encontrarás las siguientes propiedades útiles:
• `paymentMode`: un enum con los valores `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` y `UNKNOWN`. Las pruebas gratuitas son del tipo `FREE_TRIAL`.
• `price`: el precio con descuento como número. Para las pruebas gratuitas, busca `0` aquí.
• `localizedNumberOfPeriods`: una cadena localizada con la configuración regional del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días muestra `3 days` en este campo.
• `subscriptionPeriod`: alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona del mismo modo para las ofertas que lo descrito en la sección anterior.
• `localizedSubscriptionPeriod`: un período de suscripción formateado del descuento para la configuración regional del usuario. | ## Acelera la obtención de flows con el flow de audiencia predeterminada \{#speed-up-flow-fetching-with-default-audience-flow\} Normalmente, los flows se obtienen casi al instante, por lo que no necesitas preocuparte por optimizar este proceso. Sin embargo, cuando tienes muchas audiencias y placements, y tus usuarios tienen una conexión a internet débil, obtener un flow puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un flow predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar nada. Para resolver esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el flow mediante el método `getFlow`, como se detalla en la sección [Obtener información del flow](fetch-paywalls-and-products-android#fetch-flow-information) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar flows distintos para diferentes versiones de la app (la actual y las futuras), puede que te encuentres con dificultades. Tendrás que diseñar flows compatibles con la versión actual (legacy) o asumir que los usuarios con esa versión pueden tener problemas al no renderizarse los flows. - **Pérdida de targeting**: Todos los usuarios verán el mismo flow diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluida la segmentación por país, atribución de marketing o atributos personalizados propios). Si estás dispuesto a aceptar estas desventajas a cambio de una obtención más rápida del flow, usa el método `getFlowForDefaultAudience` de la siguiente manera. De lo contrario, utiliza `getFlow` descrito [anteriormente](fetch-paywalls-and-products-android#fetch-flow-information). ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. || **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo intermitente que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro utilizarla durante la sesión para evitar peticiones de red.

Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.

|
Antes de mostrar el Remote Config y los paywalls personalizados, debes obtener la información sobre ellos. Ten en cuenta que este tema se refiere al Remote Config y a los paywalls personalizados. Para obtener orientación sobre cómo obtener paywalls personalizados con el Paywall Builder, consulta [Obtener paywalls del Paywall Builder y su configuración](android-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. :::
Antes de empezar a obtener paywalls y productos en tu app (haz clic para expandir) 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en tu paywall](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-android) en tu app móvil.
## Obtener información del paywall \{#fetch-paywall-information\} En Adapty, un [producto](product) es una combinación de productos de App Store y Google Play. Estos productos multiplataforma se integran en paywalls, lo que te permite mostrarlos en placements concretos de tu app móvil. Para mostrar los productos, necesitas obtener un [Paywall](paywalls) de uno de tus [placements](placements) con el método `getPaywall`. :::important **No escribas product IDs en el código.** El único ID que debes incluir de forma fija es el ID del placement. Los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe manejar estos cambios de forma dinámica: si hoy un paywall devuelve dos productos y mañana tres, muéstralos todos sin necesidad de cambiar el código. ::: ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | requerido | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** |

opcional

predeterminado: `en`

|

El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.

Ejemplo: `en` indica inglés, `pt-br` representa el portugués de Brasil.

Consulta [Localizaciones y códigos de idioma](android-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos utilizarlos.

| | **fetchPolicy** | predeterminado: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen conexiones inestables, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché cuando existan. En ese caso, puede que los usuarios no reciban los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.

Ten en cuenta que la caché se mantiene intacta al reiniciar la aplicación y solo se borra cuando se reinstala la app o mediante una limpieza manual.

El SDK de Adapty almacena los paywalls en dos capas: la caché de actualización regular descrita anteriormente y los [paywalls de respaldo](android-use-fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.

| | **loadTimeout** | predeterminado: 5 seg |

Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.

Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede estar compuesta de distintas peticiones internamente.

| ¡No codifiques los IDs de producto de forma fija! Dado que los paywalls se configuran de forma remota, los productos disponibles, su número y las ofertas especiales (como las pruebas gratuitas) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si inicialmente obtienes 2 productos, tu app debería mostrar esos 2 productos. Sin embargo, si más adelante obtienes 3 productos, tu app debería mostrarlos todos sin necesidad de modificar el código. Lo único que debes codificar de forma fija es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Un objeto [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) con: una lista de IDs de producto, el identificador del paywall, Remote Config y otras propiedades. | ## Obtener productos \{#fetch-products\} Una vez que tienes el paywall, puedes consultar el array de productos que le corresponde: ```kotlin showLineNumbers Adapty.getPaywallProducts(paywall) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // the requested products } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallProducts(paywall, result -> { if (result instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) result).getValue(); // the requested products } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Lista de objetos [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) con: identificador de producto, nombre de producto, precio, moneda, duración de la suscripción y otras propiedades. | Al implementar tu propio diseño de paywall, probablemente necesites acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/). A continuación se muestran las propiedades más usadas, pero consulta el documento enlazado para ver todos los detalles de las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país de la store seleccionado por el usuario, no en la configuración regional del dispositivo. | | **Price** | Para mostrar el precio localizado, usa `product.price.localizedString`. Esta localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número con `product.price.amount`. El valor se proporcionará en la moneda local. Para obtener el símbolo de la moneda correspondiente, usa `product.price.currencySymbol`. | | **Subscription Period** | Para mostrar el período (por ejemplo, semana, mes, año, etc.), usa `product.subscriptionDetails?.localizedSubscriptionPeriod`. Esta localización se basa en la configuración regional del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscriptionDetails?.subscriptionPeriod`. Desde ahí puedes acceder al enum `unit` para conocer la duración (es decir, DAY, WEEK, MONTH, YEAR o UNKNOWN). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral verías `MONTH` en la propiedad unit y `3` en la propiedad numberOfUnits. | | **Introductory Offer** | Para mostrar un badge u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscriptionDetails?.introductoryOfferPhases`. Es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase encontrarás las siguientes propiedades útiles:
• `paymentMode`: un enum con los valores `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` y `UNKNOWN`. Las pruebas gratuitas serán del tipo `FREE_TRIAL`.
• `price`: el precio con descuento como número. Para pruebas gratuitas, busca `0` aquí.
• `localizedNumberOfPeriods`: una cadena localizada con la configuración regional del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días mostrará `3 days` en este campo.
• `subscriptionPeriod`: alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma manera para las ofertas que la sección anterior.
• `localizedSubscriptionPeriod`: un período de suscripción formateado del descuento según la configuración regional del usuario. | ## Acelera la carga del paywall con el paywall de audiencia predeterminada \{#speed-up-paywall-fetching-with-default-audience-paywall\} Normalmente, los paywalls se cargan casi de inmediato, por lo que no necesitas preocuparte por optimizar este proceso. Sin embargo, cuando tienes muchas audiencias y paywalls, y tus usuarios tienen una conexión a internet lenta, la carga de un paywall puede tardar más de lo esperado. En esas situaciones, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ningún paywall. Para solucionar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall mediante el método `getPaywall`, tal como se detalla en la sección [Obtener información del paywall](fetch-paywalls-and-products-android#fetch-paywall-information) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar distintos paywalls para diferentes versiones de la app (la actual y las futuras), puede que te encuentres con dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (heredada) o asumir que los usuarios con esa versión pueden encontrarse con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluida la segmentación por país, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas a cambio de una obtención más rápida del paywall, usa el método `getPaywallForDefaultAudience` de la siguiente manera. De lo contrario, sigue usando `getPaywall` descrito [anteriormente](fetch-paywalls-and-products-android#fetch-paywall-information). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` :::note El método `getPaywallForDefaultAudience` está disponible a partir de la versión 2.11.3 del SDK de Android. ::: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** |

opcional

predeterminado: `en`

|

El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.

Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.

Consulta [Localizaciones y códigos de idioma](android-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.

| | **fetchPolicy** | predeterminado: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché cuando existan. En este caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.

Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante limpieza manual.

|
--- # File: present-remote-config-paywalls-android --- --- title: "Renderizar paywall diseñado con Remote Config en el SDK de Android" description: "Descubre cómo presentar paywalls de Remote Config en el SDK de Adapty para Android y personalizar la experiencia del usuario." --- Si has personalizado un paywall usando Remote Config, necesitarás implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú decides qué incluir y cómo se muestra tu paywall. Adapty proporciona un método para obtener la configuración remota, dándote autonomía para mostrar tu paywall personalizado. ## Obtener la Remote Config de un flow y presentarla \{#get-flow-remote-config-and-present-it\} En la versión 4, un flow lleva una entrada `AdaptyRemoteConfig` por cada idioma configurado en el array `remoteConfigs`. Elige el idioma que coincida con la preferencia del usuario y lee los valores que necesites. ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value val config = flow.remoteConfigs.firstOrNull { it.locale == "en" } ?: flow.remoteConfigs.firstOrNull() val headerText = config?.dataMap?.get("header_text") as? String } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); AdaptyRemoteConfig config = null; for (AdaptyRemoteConfig remoteConfig : flow.getRemoteConfigs()) { if ("en".equals(remoteConfig.getLocale())) { config = remoteConfig; break; } } if (config == null && !flow.getRemoteConfigs().isEmpty()) { config = flow.getRemoteConfigs().get(0); } if (config != null && config.getDataMap().get("header_text") instanceof String) { String headerText = (String) config.getDataMap().get("header_text"); } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` En este punto, una vez que hayas recibido todos los valores necesarios, es momento de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a distintas pantallas de móvil y orientaciones, ofreciendo una experiencia fluida y fácil de usar en diferentes dispositivos. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls-android#track-paywall-view-events) como se describe a continuación, para que los análisis de Adapty puedan capturar información para los funnels y las pruebas A/B. ::: Una vez que hayas mostrado el paywall, continúa configurando el flujo de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu flow. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](android-making-purchases). Te recomendamos [crear un paywall de respaldo llamado fallback paywall](android-use-fallback-paywalls). Este paywall de respaldo se mostrará al usuario cuando no haya conexión a internet o caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Registrar eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus flows y paywalls. Aunque los datos de compras se recopilan automáticamente, registrar las visualizaciones requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un flow. Para registrar un evento de visualización, simplemente llama a `.logShowFlow(flow)`, y quedará reflejado en tus métricas de embudos y pruebas A/B. :::important No es necesario llamar a `.logShowFlow(flow)` si estás mostrando flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder). En esos casos, Adapty registra las visualizaciones automáticamente. ::: ```kotlin showLineNumbers Adapty.logShowFlow(flow) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :-------- | :------- |:-----------------------------------------------------------------------------------------| | **flow** | obligatorio | Un objeto `AdaptyFlow` obtenido mediante `Adapty.getFlow`. | Si has personalizado un paywall usando Remote Config, tendrás que implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú controlas qué se incluye y cómo se ve tu paywall. Te proporcionamos un método para obtener la configuración remota, dándote la autonomía de mostrar tu paywall personalizado configurado mediante Remote Config. ## Obtener el Remote Config de un paywall y mostrarlo \{#get-paywall-remote-config-and-present-it\} Para obtener el Remote Config de un paywall, accede a la propiedad `remoteConfig` y extrae los valores que necesitas. ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); AdaptyPaywall.RemoteConfig remoteConfig = paywall.getRemoteConfig(); if (remoteConfig != null) { if (remoteConfig.getDataMap().get("header_text") instanceof String) { String headerText = (String) remoteConfig.getDataMap().get("header_text"); } } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` En este punto, una vez que hayas recibido todos los valores necesarios, es hora de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a distintas pantallas de móviles y orientaciones, ofreciendo una experiencia fluida y fácil de usar en diferentes dispositivos. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls-android#track-paywall-view-events) como se describe a continuación, para que los análisis de Adapty puedan capturar información para funnels y pruebas A/B. ::: Cuando hayas terminado de mostrar el paywall, continúa configurando el flujo de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu paywall. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](android-making-purchases). Te recomendamos [crear un paywall de respaldo llamado fallback paywall](android-use-fallback-paywalls). Este paywall de respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Registrar eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus paywalls. Aunque recopilamos datos sobre las compras de forma automática, registrar las visualizaciones de paywall requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un paywall. Para registrar un evento de visualización de paywall, llama a `.logShowPaywall(paywall)` y se reflejará en las métricas de tu paywall en los embudos y las pruebas A/B. :::important No es necesario llamar a `.logShowPaywall(paywall)` si estás mostrando paywalls creados en el [Paywall Builder](adapty-paywall-builder). ::: ```kotlin showLineNumbers Adapty.logShowPaywall(paywall) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :-------- |:------------------------------------------------------------------------------------------------------------| | **paywall** | obligatorio | Un objeto [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | --- # File: android-making-purchases --- --- title: "Realizar compras in-app en Android SDK" description: "Guía sobre cómo gestionar compras in-app y suscripciones con Adapty." --- Mostrar paywalls en tu aplicación móvil es un paso esencial para ofrecer a los usuarios acceso a contenido o servicios premium. Sin embargo, simplemente mostrar estos paywalls solo es suficiente para gestionar compras si usas [Paywall Builder](adapty-paywall-builder) para personalizarlos. Si no usas el Paywall Builder, debes usar un método independiente llamado `.makePurchase()` para completar una compra y desbloquear el contenido deseado. Este método es la puerta de entrada para que los usuarios interactúen con los paywalls y procedan con sus transacciones. Si tu paywall tiene una oferta promocional activa para el producto que el usuario quiere comprar, Adapty la aplicará automáticamente en el momento de la compra. :::warning Ten en cuenta que la oferta introductoria se aplicará automáticamente solo si utilizas paywalls configurados con el Paywall Builder. En otros casos, deberás [verificar la elegibilidad del usuario para una oferta introductoria en iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Saltarte este paso puede provocar que tu app sea rechazada durante la revisión. Además, podría suponer cobrar el precio completo a usuarios que tienen derecho a una oferta introductoria. ::: Asegúrate de haber completado la [configuración inicial](quickstart) sin saltarte ningún paso. Sin ella, no podemos validar las compras. ## Realizar compra \{#make-purchase\} :::note **¿Usas [Paywall Builder](adapty-paywall-builder)?** Las compras se procesan automáticamente; puedes saltarte este paso. **¿Buscas una guía paso a paso?** Consulta la [guía de inicio rápido](android-implement-paywalls-manually) para instrucciones de implementación completas con todo el contexto. ::: ```kotlin showLineNumbers Adapty.makePurchase(activity, product, null) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // Grant access to the paid features } } is AdaptyPurchaseResult.UserCanceled -> { // Handle the case where the user canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Handle deferred purchases (e.g., the user will pay offline with cash) } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ``` ```java showLineNumbers Adapty.makePurchase(activity, product, null, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); if (premium != null && premium.isActive()) { // Grant access to the paid features } } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // Handle the case where the user canceled the purchase } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // Handle deferred purchases (e.g., the user will pay offline with cash) } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :-------- | :-------------------------------------------------------------------------------------------------- | | **Product** | obligatorio | Un objeto [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) obtenido del paywall. | Parámetros de la respuesta: | Parámetro | Descripción | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** |

Si la solicitud se ha completado correctamente, la respuesta contiene este objeto. Un objeto [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) proporciona información completa sobre los niveles de acceso, suscripciones y compras únicas de un usuario dentro de la app.

Comprueba el estado del nivel de acceso para determinar si el usuario tiene el acceso requerido a la app.

| :::warning **Nota:** si todavía usas una versión de StoreKit de Apple inferior a v2.0 y una versión del SDK de Adapty inferior a v2.9.0, debes proporcionar el [secreto compartido de Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret) en su lugar. Apple ha declarado este método como obsoleto. ::: ## Cambiar suscripción al realizar una compra \{#change-subscription-when-making-a-purchase\} Cuando un usuario elige una nueva suscripción en lugar de renovar la actual, el funcionamiento depende del store. En Google Play, la suscripción no se actualiza automáticamente: tienes que gestionar el cambio en el código de tu app como se describe a continuación. Para reemplazar la suscripción por otra en Android, llama al método `.makePurchase()` con el parámetro adicional: ```kotlin showLineNumbers Adapty.makePurchase( activity, product, AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(subscriptionUpdateParams) .build() ) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // successful cross-grade } is AdaptyPurchaseResult.UserCanceled -> { // user canceled the purchase flow } is AdaptyPurchaseResult.Pending -> { // the purchase has not been finished yet, e.g. user will pay offline by cash } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ``` Parámetro de solicitud adicional: | Parámetro | Presencia | Descripción | | :--------------------------- | :-------- | :----------------------------------------------------------- | | **subscriptionUpdateParams** | obligatorio | un objeto [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/). | ```java showLineNumbers Adapty.makePurchase( activity, product, new AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(subscriptionUpdateParams) .build(), result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); // successful cross-grade } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // user canceled the purchase flow } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // the purchase has not been finished yet, e.g. user will pay offline by cash } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); ``` Parámetro de solicitud adicional: | Parámetro | Presencia | Descripción | | :--------------------------- | :-------- | :----------------------------------------------------------- | | **subscriptionUpdateParams** | obligatorio | un objeto [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/). | Puedes leer más sobre las suscripciones y los modos de reemplazo en la documentación para desarrolladores de Google: - [Acerca de los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Recomendaciones de Google para los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Modo de reemplazo [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Nota: este método solo está disponible para actualizaciones de suscripción. No se admiten degradaciones. - Modo de reemplazo [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Nota: el cambio real de suscripción solo se producirá cuando finalice el período de facturación actual. ### Gestionar planes prepago \{#manage-prepaid-plans\} Si los usuarios de tu app pueden adquirir [planes prepago](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (por ejemplo, comprar una suscripción no renovable por varios meses), puedes habilitar las [transacciones pendientes](https://developer.android.com/google/play/billing/subscriptions#pending) para planes prepago. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withEnablePendingPrepaidPlans(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withEnablePendingPrepaidPlans(true) .build(); ``` --- # File: android-restore-purchase --- --- title: "Restaurar compras en una app para Android con el SDK" description: "Aprende cómo restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- Restaurar compras es una función que permite a los usuarios recuperar el acceso a contenido adquirido previamente, como suscripciones o compras in-app, sin que se les vuelva a cobrar. Esta función es especialmente útil para usuarios que han desinstalado y reinstalado la app o que han cambiado de dispositivo y quieren acceder a su contenido sin pagar de nuevo. :::note En los paywalls creados con [Paywall Builder](adapty-paywall-builder), las compras se restauran automáticamente sin que tengas que añadir código adicional. Si es tu caso, puedes saltarte este paso. ::: Para restaurar una compra cuando no usas [Paywall Builder](adapty-paywall-builder) para personalizar el paywall, llama al método `.restorePurchases()`: ```kotlin showLineNumbers Adapty.restorePurchases { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // successful access restore } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); if (profile != null) { AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); if (premium != null && premium.isActive()) { // successful access restore } } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Parámetros de respuesta: | Parámetro | Descripción | |---------|-----------| | **Profile** |

Un objeto [`AdaptyProfile`](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Este modelo contiene información sobre los niveles de acceso, suscripciones y compras únicas.

Comprueba el **estado del nivel de acceso** para determinar si el usuario tiene acceso a la app.

| :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: --- # File: implement-observer-mode-android --- --- title: "Implementar el modo Observer en el SDK de Android" description: "Implementa el modo Observer en Adapty para rastrear eventos de suscripción de usuarios en el SDK de Android." --- Si ya tienes tu propia infraestructura de compras y no estás listo para migrar completamente a Adapty, puedes explorar el [modo Observer](observer-vs-full-mode). En su forma básica, el modo Observer ofrece analíticas avanzadas e integración fluida con sistemas de atribución y analíticas. Si esto cubre tus necesidades, solo tienes que: 1. Activarlo al configurar el SDK de Adapty estableciendo el parámetro `observerMode` en `true`. Sigue las instrucciones de configuración para [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk). 2. [Reportar transacciones](report-transactions-observer-mode-android) desde tu infraestructura de compras existente a Adapty. ## Configuración del modo Observer \{#observer-mode-setup\} Activa el modo Observer si gestionas las compras y el estado de la suscripción por tu cuenta y usas Adapty para enviar eventos de suscripción y analíticas. :::important Cuando se ejecuta en modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlas tú mismo. ::: ```kotlin showLineNumbers class MyApplication : Application() { override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(true) //default false .build() ) } ``` ```java showLineNumbers public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Adapty.activate( applicationContext, new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(true) //default false .build() ); } ``` Parámetros: | Parámetro | Descripción | | --------------------------- | ------------------------------------------------------------ | | observerMode | Un valor booleano que controla el [modo Observer](observer-vs-full-mode). El valor por defecto es `false`. | ## Usar los paywalls de Adapty en el modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los paywalls y las funciones de pruebas A/B de Adapty, puedes hacerlo, pero requiere una configuración adicional en el modo Observer. Esto es lo que necesitas hacer además de los pasos anteriores: 1. Muestra los paywalls de la forma habitual para [paywalls con Remote Config](present-remote-config-paywalls-android). Para los paywalls del Paywall Builder, sigue las guías de configuración específicas para [Android](android-present-paywall-builder-paywalls-in-observer-mode). 3. [Asocia los paywalls](report-transactions-observer-mode-android) con las transacciones de compra. --- # File: report-transactions-observer-mode-android --- --- title: "Reportar transacciones en Observer Mode en el SDK de Android" description: "Reporta transacciones de compra en el Observer Mode de Adapty para obtener información de usuario y seguimiento de ingresos en el SDK de Android." --- En Observer Mode, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Debes reportar las transacciones desde tu app store. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` para reportar explícitamente cada transacción y que Adapty la reconozca. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el paywall que la generó, garantizando análisis precisos del paywall. ```kotlin showLineNumbers val transactionInfo = TransactionInfo.fromPurchase(purchase) Adapty.reportTransaction(transactionInfo, variationId) { result -> if (result is AdaptyResult.Success) { // success } } ``` Parámetros: | Parámetro | Presencia | Descripción | | --------------- | --------- | ------------------------------------------------------------ | | transactionInfo | requerido | El TransactionInfo de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación. | | variationId | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | ```java showLineNumbers TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase); Adapty.reportTransaction(transactionInfo, variationId, result -> { if (result instanceof AdaptyResult.Success) { // success } }); ``` Parámetros: | Parámetro | Presencia | Descripción | | --------------- | --------- | ------------------------------------------------------------ | | transactionInfo | requerido | El TransactionInfo de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación. | | variationId | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | En Observer Mode, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Debes reportar las transacciones desde tu app store o restaurarlas. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `restorePurchases` para reportar la transacción a Adapty. :::warning **¡No omitas la restauración de compras!** Si no llamas a `restorePurchases`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, vincula tu transacción con el paywall que originó la compra mediante el método `setVariationId`. Esto garantiza que la compra se atribuya correctamente al paywall que la desencadenó para un análisis preciso. Este paso solo es necesario si usas paywalls de Adapty. ```kotlin showLineNumbers Adapty.restorePurchases { result -> if (result is AdaptyResult.Success) { // success } } Adapty.setVariationId(transactionId, variationId) { error -> if (error == null) { // success } } ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | requerido | Identificador de cadena (`purchase.getOrderId`) de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación. | | variationId | requerido | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | ```java showLineNumbers Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { // success } }); Adapty.setVariationId(transactionId, variationId, error -> { if (error == null) { // success } }); ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | requerido | Identificador de cadena (`purchase.getOrderId`) de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación. | | variationId | requerido | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | **Reporte de transacciones** Usa `restorePurchases` para reportar una transacción a Adapty en Observer Mode, tal como se explica en la página [Restaurar compras en el código móvil](android-restore-purchase). :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `restorePurchases`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: **Asociar paywalls a transacciones** El SDK de Adapty no puede determinar el origen de las compras, ya que eres tú quien las procesa. Por lo tanto, si tienes intención de usar paywalls y/o pruebas A/B en Observer Mode, debes asociar la transacción proveniente de tu app store con el paywall correspondiente en el código de tu app móvil. Es importante hacerlo bien antes de publicar tu app, de lo contrario generará errores en los análisis. ```kotlin Adapty.setVariationId(transactionId, variationId) { error -> if (error == null) { // success } } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | requerido | Identificador de cadena (purchase.getOrderId de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación. | | variationId | requerido | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | ```java Adapty.setVariationId(transactionId, variationId, error -> { if (error == null) { // success } }); ``` | Parámetro | Presencia | Descripción | | ------------------------------------------------- | --------- | ------------------------------------------------------------ | | transactionId | requerido | Identificador de cadena (purchase.getOrderId de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación. | | variationId | requerido | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | --- # File: android-present-paywall-builder-paywalls-in-observer-mode --- --- title: "Mostrar paywalls de Paywall Builder en modo Observer en el SDK de Android" description: "Aprende cómo mostrar paywalls en modo observer usando el Paywall Builder de Adapty." --- Si has creado un flow o paywall con el Flow Builder o el Paywall Builder, no tienes que preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Ese flow o paywall ya contiene tanto lo que debe mostrarse como la forma en que debe mostrarse. :::warning Esta sección hace referencia únicamente al [modo Observer](observer-vs-full-mode). Si no trabajas en modo Observer, consulta el tema [Android - Presentar flows y paywalls](android-present-paywalls) en su lugar. :::
Antes de empezar a presentar flows (Haz clic para expandir) 1. Configura la integración inicial de Adapty [con Google Play](initial-android). 2. Instala y configura el SDK de Adapty. Asegúrate de establecer el parámetro `observerMode` en `true`. Consulta nuestras instrucciones por framework [para Android](sdk-installation-android). 3. [Crea productos](create-product) en el Adapty Dashboard. 4. [Configura flows o paywalls en los builders](create-paywall) y asígnales productos. 5. [Crea placements y asígna tus flows o paywalls](create-placement) en el Adapty Dashboard. 6. [Obtén los flows y su configuración](android-get-pb-paywalls) en el código de tu app.

1. Implementa el `AdaptyUiObserverModeHandler`. El evento `onPurchaseInitiated` te informará de que el usuario ha iniciado una compra. Puedes activar tu flujo de compra personalizado en respuesta a este callback: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, flow, flowView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, flow, flowView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` Para manejar restauraciones en modo Observer, sobreescribe `getRestoreHandler()`. Por defecto devuelve `null`, lo que utiliza el flow integrado de `Adapty.restorePurchases()` de Adapty. Para proporcionar tu propia implementación de restauración: ```kotlin showLineNumbers val observerModeHandler = object : AdaptyUiObserverModeHandler { // onPurchaseInitiated implementation (see above) override fun getRestoreHandler() = AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore -> onStartRestore() yourBillingClient.restorePurchases( onSuccess = { restoredPurchases -> onFinishRestore() //handle successful restore }, onError = { onFinishRestore() //handle error } ) } } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() { // onPurchaseInitiated implementation (see above) @Override public RestoreHandler getRestoreHandler() { return (onStartRestore, onFinishRestore) -> { onStartRestore.invoke(); yourBillingClient.restorePurchases( restoredPurchases -> { onFinishRestore.invoke(); //handle successful restore }, error -> { onFinishRestore.invoke(); //handle error } ); }; } }; ``` Recuerda invocar los siguientes callbacks para notificar a AdaptyUI sobre el proceso de compra o restauración. Esto es necesario para el correcto funcionamiento del flow, como mostrar el indicador de carga: | Callback | Descripción | | :----------------- |:-----------------------------------------------------------------------------------------------------| | onStartPurchase() | El callback debe invocarse para notificar a AdaptyUI que la compra ha comenzado. | | onFinishPurchase() | El callback debe invocarse para notificar a AdaptyUI que la compra ha finalizado. | | onStartRestore() | Opcional. El callback puede invocarse para notificar a AdaptyUI que la restauración ha comenzado. | | onFinishRestore() | Opcional. El callback puede invocarse para notificar a AdaptyUI que la restauración ha finalizado. | 2. Para mostrar el flow visual en la pantalla del dispositivo, primero debes configurarlo. Para ello, llama al método `AdaptyUI.getFlowView()` o crea el `AdaptyFlowView` directamente: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml ... flowView.showFlow(flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler); ``` ```xml showLineNumbers ``` Después de que la vista se haya creado correctamente, puedes añadirla a la jerarquía de vistas y mostrarla. Para hacerlo, usa esta función composable: ```kotlin showLineNumbers AdaptyFlowScreen( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **flowConfiguration** | requerido | Proporciona un objeto `AdaptyUI.FlowConfiguration` que contiene los detalles visuales del flow. Usa el método `AdaptyUI.getFlowConfiguration(flow)` para cargarlo. Consulta el tema [Fetch the view configuration](android-get-pb-paywalls#fetch-the-view-configuration) para más detalles. | | **products** | opcional | Proporciona un array de `AdaptyPaywallProduct` para optimizar el tiempo de visualización de los productos en pantalla. Si se pasa `null`, AdaptyUI obtendrá automáticamente los productos necesarios. | | **eventListener** | opcional | Proporciona un `AdaptyFlowEventListener` para observar los eventos del flow. Se recomienda extender `AdaptyFlowDefaultEventListener` para facilitar su uso. Consulta el tema [Handle flow & paywall events](android-handling-events) para más detalles. | | **insets** | opcional | Los insets son los espacios alrededor del flow que evitan que los elementos interactivos queden ocultos detrás de las barras del sistema. Por defecto: `Unspecified`, lo que permite que Adapty ajuste los insets automáticamente. Ver [Change flow insets](android-present-paywalls#change-flow-insets). | | **customAssets** | opcional | Pasa un objeto `AdaptyCustomAssets` para reemplazar imágenes y vídeos en tu flow o paywall en tiempo de ejecución. Consulta [Customize assets](android-get-pb-paywalls#customize-assets) para más detalles. | | **tagResolver** | opcional | Usa `AdaptyUiTagResolver` para resolver etiquetas personalizadas dentro del texto del flow. Este resolver toma un parámetro de etiqueta y lo convierte en la cadena de texto correspondiente. Consulta el tema Custom tags in Paywall Builder para más detalles. | | **observerModeHandler** | requerido para el modo Observer | El `AdaptyUiObserverModeHandler` que implementaste en el paso anterior. | :::warning No olvides [asociar los paywalls a las transacciones de compra](report-transactions-observer-mode-android). De lo contrario, Adapty no podrá determinar el flow de origen de la compra. :::
Antes de empezar a mostrar paywalls (haz clic para ampliar) 1. Configura la integración inicial de Adapty [con Google Play](initial-android) y [con App Store](initial_ios). 2. Instala y configura el SDK de Adapty. Asegúrate de establecer el parámetro `observerMode` en `true`. Consulta nuestras instrucciones específicas por framework [para Android](sdk-installation-android). 3. [Crea productos](create-product) en el Adapty Dashboard. 4. [Configura los paywalls, asígnales productos](create-paywall) y personalízalos usando el Paywall Builder en el Adapty Dashboard. 5. [Crea placements y asígnales tus paywalls](create-placement) en el Adapty Dashboard. 6. [Obtén los paywalls del Paywall Builder y su configuración](android-get-pb-paywalls) en el código de tu aplicación móvil.

1. Implementa el `AdaptyUiObserverModeHandler`. El evento `onPurchaseInitiated` te informará de que el usuario ha iniciado una compra. Puedes activar tu flujo de compra personalizado en respuesta a este callback: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` Para gestionar restauraciones en el modo Observador, sobreescribe `getRestoreHandler()`. Por defecto devuelve `null`, lo que usa el flow integrado `Adapty.restorePurchases()` de Adapty. Para proporcionar tu propia implementación de restauración: ```kotlin showLineNumbers val observerModeHandler = object : AdaptyUiObserverModeHandler { // onPurchaseInitiated implementation (see above) override fun getRestoreHandler() = AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore -> onStartRestore() yourBillingClient.restorePurchases( onSuccess = { restoredPurchases -> onFinishRestore() //handle successful restore }, onError = { onFinishRestore() //handle error } ) } } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() { // onPurchaseInitiated implementation (see above) @Override public RestoreHandler getRestoreHandler() { return (onStartRestore, onFinishRestore) -> { onStartRestore.invoke(); yourBillingClient.restorePurchases( restoredPurchases -> { onFinishRestore.invoke(); //handle successful restore }, error -> { onFinishRestore.invoke(); //handle error } ); }; } }; ``` Recuerda invocar los siguientes callbacks para notificar a AdaptyUI sobre el proceso de compra o restauración. Esto es necesario para que el paywall funcione correctamente, por ejemplo, para mostrar el loader: | Callback | Descripción | | :----------------- |:-----------------------------------------------------------------------------------------------------| | onStartPurchase() | El callback debe invocarse para notificar a AdaptyUI que la compra ha comenzado. | | onFinishPurchase() | El callback debe invocarse para notificar a AdaptyUI que la compra ha finalizado. | | onStartRestore() | Opcional. El callback puede invocarse para notificar a AdaptyUI que la restauración ha comenzado. | | onFinishRestore() | Opcional. El callback puede invocarse para notificar a AdaptyUI que la restauración ha finalizado. | 2. Para mostrar el paywall visual en la pantalla del dispositivo, primero debes configurarlo. Para ello, llama al método `AdaptyUI.getPaywallView()` o crea el `AdaptyPaywallView` directamente: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler, ) ``` ```kotlin showLineNumbers val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { showPaywall( viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler ); ``` ```java showLineNumbers AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.showPaywall(viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler); ``` ```xml showLineNumbers ``` Una vez creada la vista correctamente, puedes añadirla a la jerarquía de vistas y mostrarla. Para hacerlo, usa esta función composable: ```kotlin showLineNumbers AdaptyPaywallScreen( viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, ) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **Products** | opcional | Proporciona un array de `AdaptyPaywallProduct` para optimizar el tiempo de visualización de los productos en pantalla. Si se pasa `null`, AdaptyUI obtendrá automáticamente los productos necesarios. | | **ViewConfiguration** | obligatorio | Proporciona un objeto `AdaptyViewConfiguration` con los detalles visuales del paywall. Usa el método `Adapty.getViewConfiguration(paywall)` para cargarlo. Consulta el tema [Obtener la configuración visual del paywall](#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) para más detalles. | | **EventListener** | opcional | Proporciona un `AdaptyUiEventListener` para observar los eventos del paywall. Se recomienda extender `AdaptyUiDefaultEventListener` para mayor comodidad. Consulta el tema [Gestionar eventos del paywall](android-handling-events) para más detalles. | | **PersonalizedOfferResolver** | opcional | Para indicar precios personalizados ([más información](https://developer.android.com/google/play/billing/integrate#personalized-price)), implementa `AdaptyUiPersonalizedOfferResolver` y añade tu propia lógica que mapee `AdaptyPaywallProduct` a `true` si el precio del producto es personalizado, o `false` en caso contrario. | | **TagResolver** | opcional | Usa `AdaptyUiTagResolver` para resolver etiquetas personalizadas dentro del texto del paywall. Este resolver recibe un parámetro de etiqueta y lo resuelve en la cadena correspondiente. Consulta el tema Etiquetas personalizadas en Paywall Builder para más detalles. | | **ObserverModeHandler** | obligatorio para el modo Observer | El `AdaptyUiObserverModeHandler` que implementaste en el paso anterior. | | **variationId** | obligatorio | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | | **transaction** | obligatorio |

Para iOS, StoreKit 1: un objeto [`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction).

Para iOS, StoreKit 2: un objeto [Transaction](https://developer.apple.com/documentation/storekit/transaction).

Para Android: el identificador de cadena (`purchase.getOrderId()`) de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación.

|
Antes de empezar a mostrar paywalls (Haz clic para ampliar) 1. Configura la integración inicial de Adapty [con Google Play](initial-android) y [con App Store](initial_ios). 2. Instala y configura el SDK de Adapty. Asegúrate de establecer el parámetro `observerMode` en `true`. Consulta las instrucciones específicas para cada framework: [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk) y [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 3. [Crea productos](create-product) en el Adapty Dashboard. 4. [Configura los paywalls, asígnales productos](create-paywall) y personalízalos con el Paywall Builder en el Adapty Dashboard. 5. [Crea placements y asígnales tus paywalls](create-placement) en el Adapty Dashboard. 6. [Obtén los paywalls del Paywall Builder y su configuración](android-get-pb-paywalls) en el código de tu aplicación móvil.
1. Implementa el `AdaptyUiObserverModeHandler`. El callback de `AdaptyUiObserverModeHandler` (`onPurchaseInitiated`) te avisa cuando el usuario inicia una compra. Puedes activar tu flujo de compra personalizado en respuesta a este callback así: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` También recuerda invocar estos callbacks en AdaptyUI. Esto es necesario para el correcto funcionamiento del paywall, como mostrar el loader, entre otras cosas: | Callback en Kotlin | Callback en Java | Descripción | | :----------------- | :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------- | | onStartPurchase() | onStartPurchase.invoke() | Este callback debe invocarse para notificar a AdaptyUI que la compra ha comenzado. | | onFinishPurchase() | onFinishPurchase.invoke() | Este callback debe invocarse para notificar a AdaptyUI que la compra ha finalizado correctamente, que ha fallado, o que ha sido cancelada. | 2. Para mostrar el paywall visual, primero debes inicializarlo. Para ello, llama al método `AdaptyUI.getPaywallView()` o crea el `AdaptyPaywallView` directamente: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), eventListener, personalizedOfferResolver, tagResolver, observerModeHandler, ) //======= OR ======= val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { setEventListener(eventListener) setObserverModeHandler(observerModeHandler) showPaywall( viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), personalizedOfferResolver, tagResolver, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), eventListener, personalizedOfferResolver, tagResolver, observerModeHandler ); //======= OR ======= AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.setEventListener(eventListener); paywallView.setObserverModeHandler(observerModeHandler); paywallView.showPaywall(viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), personalizedOfferResolver); ``` ```xml showLineNumbers ``` Tras crear la vista correctamente, puedes añadirla a la jerarquía de vistas y mostrarla. Parámetros de la solicitud: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Products** | opcional | Proporciona un array de `AdaptyPaywallProduct` para optimizar el momento en que se muestran los productos en pantalla. Si se pasa `null`, AdaptyUI obtendrá automáticamente los productos necesarios. | | **ViewConfiguration** | obligatorio | Proporciona un objeto `AdaptyViewConfiguration` con los detalles visuales del paywall. Usa el método `Adapty.getViewConfiguration(paywall)` para cargarlo. Consulta el tema [Obtener la configuración visual del paywall](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) para más detalles. | | **Insets** | obligatorio | Define un objeto `AdaptyPaywallInsets` con información sobre el área solapada por las barras del sistema, creando márgenes verticales para el contenido. Si ni la barra de estado ni la de navegación se superponen al `AdaptyPaywallView`, pasa `AdaptyPaywallInsets.NONE`. En modo pantalla completa, donde las barras del sistema solapan parte de tu UI, obtén los insets como se indica debajo de la tabla. | | **EventListener** | opcional | Proporciona un `AdaptyUiEventListener` para observar los eventos del paywall. Se recomienda extender `AdaptyUiDefaultEventListener` para facilitar su uso. Consulta el tema [Gestionar eventos del paywall](android-handling-events) para más detalles. | | **PersonalizedOfferResolver** | opcional | Para indicar precios personalizados ([más información](https://developer.android.com/google/play/billing/integrate#personalized-price)), implementa `AdaptyUiPersonalizedOfferResolver` y pasa tu propia lógica que mapee `AdaptyPaywallProduct` a `true` si el precio del producto es personalizado, o `false` en caso contrario. | | **TagResolver** | opcional | Usa `AdaptyUiTagResolver` para resolver etiquetas personalizadas dentro del texto del paywall. Este resolver recibe un parámetro de etiqueta y lo resuelve a la cadena correspondiente. Consulta el tema de etiquetas personalizadas en Paywall Builder para más detalles. | | **ObserverModeHandler** | obligatorio para el modo Observer | El `AdaptyUiObserverModeHandler` que implementaste en el paso anterior. | | **variationId** | obligatorio | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | | **transaction** | obligatorio |

Para iOS, StoreKit 1: un objeto [`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction).

Para iOS, StoreKit 2: objeto [Transaction](https://developer.apple.com/documentation/storekit/transaction).

Para Android: identificador de cadena (`purchase.getOrderId()`) de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación.

| Para el modo de pantalla completa, donde las barras del sistema se superponen a parte de tu interfaz, obtén los insets de la siguiente manera: ```kotlin showLineNumbers import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view paywallView.onReceiveSystemBarsInsets { insets -> val paywallInsets = AdaptyPaywallInsets.of(insets.top, insets.bottom) paywallView.setEventListener(eventListener) paywallView.setObserverModeHandler(observerModeHandler) paywallView.showPaywall(viewConfig, products, paywallInsets, personalizedOfferResolver, tagResolver) } ``` ```java showLineNumbers import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; ... ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(paywallView, null); AdaptyPaywallInsets paywallInsets = AdaptyPaywallInsets.of(systemBarInsets.top, systemBarInsets.bottom); paywallView.setEventListener(eventListener); paywallView.setObserverModeHandler(observerModeHandler); paywallView.showPaywall(viewConfiguration, products, paywallInsets, personalizedOfferResolver, tagResolver); return insets; }); ``` Devuelve: | Objeto | Descripción | | :------------------ | :------------------------------------------------- | | `AdaptyPaywallView` | objeto que representa la pantalla del paywall solicitado. | :::warning No olvides [asociar paywalls a las transacciones de compra](report-transactions-observer-mode-android). De lo contrario, Adapty no podrá determinar el paywall de origen de la compra. :::
--- # File: android-troubleshoot-purchases --- --- title: "Solucionar problemas de compras en el SDK de Android" description: "Solucionar problemas de compras en el SDK de Android" --- Esta guía te ayuda a resolver los problemas más comunes al implementar compras manualmente en el SDK de Android. ## makePurchase se llama correctamente, pero el perfil no se actualiza \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Problema**: El método `makePurchase` se completa correctamente, pero el perfil del usuario y el estado de la suscripción no se actualizan en Adapty. **Causa**: Esto generalmente indica una configuración incompleta de Google Play Store. **Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android). ## makePurchase se invoca dos veces \{#makepurchase-is-invoked-twice\} **Problema**: El método `makePurchase` se llama varias veces para la misma compra. **Causa**: Esto suele ocurrir cuando el flujo de compra se activa varias veces debido a problemas en la gestión del estado de la interfaz o a interacciones muy rápidas del usuario. **Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android). ## AdaptyError.cantMakePayments en el modo observador \{#adaptyerrorcantmakepayments-in-observer-mode\} **Problema**: Recibes `AdaptyError.cantMakePayments` al usar `makePurchase` en el modo observador. **Causa**: En el modo observador, debes gestionar las compras por tu cuenta y no usar el método `makePurchase` de Adapty. **Solución**: Si usas `makePurchase` para las compras, desactiva el modo observador. Tienes que elegir entre usar `makePurchase` o gestionar las compras por tu cuenta en el modo observador. Consulta [Implementar el modo observador](implement-observer-mode-android) para más detalles. ## Error de Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Problema**: Recibes un error de facturación no disponible de Google Play Store. **Causa**: Este error no está relacionado con Adapty. Es un error de la biblioteca de facturación de Google Play que indica que la facturación no está disponible en el dispositivo. **Solución**: Este error no está relacionado con Adapty. Puedes encontrar más información en la documentación de Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## No se encuentran makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Problema**: Tienes problemas porque no se encuentran los `makePurchasesCompletionHandlers`. **Causa**: Esto suele estar relacionado con problemas en las pruebas en sandbox. **Solución**: Crea un nuevo usuario de sandbox e inténtalo de nuevo. Esto suele resolver los problemas con los manejadores de finalización de compras en entornos sandbox. ## Otros problemas \{#other-issues\} **Problema**: Experimentas otros problemas relacionados con compras que no se tratan anteriormente. **Solución**: Si es necesario, migra el SDK a la versión más reciente siguiendo las [guías de migración](android-sdk-migration-guides). Muchos problemas se resuelven en versiones más nuevas del SDK. --- # File: android-identifying-users --- --- title: "Identificar usuarios en el SDK de Android" description: "Identifica usuarios en Adapty para mejorar las experiencias de suscripción personalizadas (Android)." --- Adapty crea un ID de perfil interno para cada usuario. Sin embargo, si tienes tu propio sistema de autenticación, deberías definir tu propio Customer User ID. Puedes buscar usuarios por su Customer User ID en la sección [Perfiles](profiles-crm) y usarlo en la [API server-side](getting-started-with-server-side-api), que se enviará a todas las integraciones. ### Configurar el ID de usuario en la configuración \{#setting-customer-user-id-on-configuration\} Si tienes un ID de usuario durante la configuración, pásalo directamente como parámetro `customerUserId` al método `.activate()`: ```kotlin showLineNumbers Adapty.activate(applicationContext, "PUBLIC_SDK_KEY", customerUserId = "YOUR_USER_ID") ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Establecer el ID de usuario personalizado tras la configuración \{#setting-customer-user-id-after-configuration\} Si no tienes un ID de usuario en la configuración del SDK, puedes establecerlo más tarde en cualquier momento con el método `.identify()`. Los casos más habituales para usar este método son después del registro o la autenticación, cuando el usuario pasa de ser anónimo a estar autenticado. ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> if (error == null) { // successful identify } } ``` ```java showLineNumbers Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` Parámetros de la solicitud: - **Customer User ID** (obligatorio): un identificador de usuario de tipo string. :::warning Reenvío de datos significativos del usuario En algunos casos, como cuando un usuario inicia sesión en su cuenta de nuevo, los servidores de Adapty ya tienen información sobre ese usuario. En estos escenarios, el SDK de Adapty cambiará automáticamente para trabajar con el nuevo usuario. Si pasaste algún dato al usuario anónimo, como atributos personalizados o atribuciones de redes de terceros, debes volver a enviar esos datos para el usuario identificado. También es importante tener en cuenta que debes volver a solicitar todos los paywalls y productos después de identificar al usuario, ya que los datos del nuevo usuario pueden ser diferentes. ::: ### Cerrar sesión e iniciar sesión \{#logging-out-and-logging-in\} Puedes cerrar la sesión del usuario en cualquier momento llamando al método `.logout()`: ```kotlin showLineNumbers Adapty.logout { error -> if (error == null) { // successful logout } } ``` ```java showLineNumbers Adapty.logout(error -> { if (error == null) { // successful logout } }); ``` Luego puedes iniciar sesión con el usuario usando el método `.identify()`. ### Detectar usuarios en varios dispositivos \{#detect-users-across-devices\} Cuando el SDK se activa, lee automáticamente los derechos existentes del usuario desde StoreKit (iOS) o Google Play Billing (Android) y los sincroniza con el backend de Adapty. Una suscripción activa aparece en el perfil de Adapty sin que la app llame a `restorePurchases`. Lo que **no** ocurre automáticamente es reconocer que un perfil en un dispositivo nuevo pertenece al mismo usuario que el perfil en el dispositivo original. Adapty relaciona perfiles por Customer User ID, así que la continuidad de identidad depende de lo que uses como CUID. **Lo que Adapty puede detectar entre dispositivos** | Tu configuración | Lo que Adapty detecta | Lo que debes hacer | | --- | --- | --- | | Customer User ID = `device_id` (sin login en la app) | El nuevo dispositivo obtiene un CUID diferente y, por tanto, un perfil diferente. La suscripción se sincroniza con el nuevo perfil mediante un evento **Access level updated**, pero `subscription_started` no se dispara — el nuevo perfil se trata como heredero de la compra original. Los análisis basados en `subscription_started` contarán de menos a los usuarios que vuelven. | Usa un ID de cuenta estable como Customer User ID para que un usuario que regresa coincida con el perfil existente en todos los dispositivos. | | Customer User ID = ID de cuenta estable (login en cada dispositivo) | El SDK sincroniza automáticamente la suscripción en `activate()`, e `identify()` relaciona el perfil existente por CUID. | No se necesita configuración adicional — tanto la identidad como la suscripción se resuelven automáticamente. | | Heredero de Apple Family Sharing | El miembro de la familia recibe la suscripción solo a través de un evento **Access level updated** — `subscription_started` no se dispara. | Escucha el evento **Access level updated**. Consulta [Apple Family Sharing](apple-family-sharing) para ver la matriz de eventos completa. | | Misma cuenta de Apple/Google, distintos usuarios dentro de la app | El primer perfil que registra la compra se convierte en el principal. Los perfiles posteriores ven la suscripción a través de una cadena de herederos, con un evento **Access level updated**. | Exige login y elige un [modo de compartición](sharing-paid-access-between-user-accounts) que se adapte a tu modelo. | **Restaurar compras en un dispositivo nuevo** Muestra un botón "Restaurar compras" iniciado por el usuario en tu paywall. Apple App Review (directriz 3.1.1) lo exige, y actúa como alternativa cuando la sincronización automática no cubre algún caso límite. El botón debe llamar a `restorePurchases` en tu SDK. No es necesario llamar a `restorePurchases` de forma programática al primer inicio para el uso normal — el SDK ya ejecuta el equivalente en `activate()`. Reserva las llamadas programáticas para forzar una verificación de recibo actualizada, por ejemplo al depurar un acceso que falta después de que `activate()` haya completado. --- # File: android-setting-user-attributes --- --- title: "Establecer atributos de usuario en el SDK de Android" description: "Aprende a establecer atributos de usuario en Adapty para mejorar la segmentación de audiencias." --- Puedes añadir atributos opcionales como email, número de teléfono, etc., al usuario de tu app. Luego puedes usar esos atributos para crear [segmentos](segments) de usuarios o simplemente consultarlos en el CRM. ### Configurar atributos de usuario \{#setting-user-attributes\} Para establecer atributos de usuario, llama al método `.updateProfile()`: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.OTHER) .withBirthday(AdaptyProfile.Date(1970, 1, 3)) Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } ``` ```java showLineNumbers AdaptyProfileParameters.Builder builder = new AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.OTHER) .withBirthday(new AdaptyProfile.Date(1970, 1, 3)); Adapty.updateProfile(builder.build(), error -> { if (error != null) { // handle the error } }); ``` Ten en cuenta que los atributos que hayas establecido previamente con el método `updateProfile` no se restablecerán. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Lista de claves permitidas \{#the-allowed-keys-list\} Las claves `` permitidas de `AdaptyProfileParameters.Builder` y sus valores `` son los siguientes: | Clave | Valor | |---|-----| |

email

phoneNumber

firstName

lastName

| String | | gender | Enum, los valores permitidos son: `female`, `male`, `other` | | birthday | Date | ### Atributos personalizados de usuario \{#custom-user-attributes\} Puedes definir tus propios atributos personalizados, que normalmente están relacionados con el uso de tu app. Por ejemplo, en aplicaciones de fitness pueden ser el número de ejercicios por semana; en apps de aprendizaje de idiomas, el nivel de conocimiento del usuario, etc. Puedes usarlos en segmentos para crear paywalls y ofertas segmentadas, y también en analíticas para identificar qué métricas de producto influyen más en los ingresos. ```kotlin showLineNumbers builder.withCustomAttribute("key1", "value1") ``` ```java showLineNumbers builder.withCustomAttribute("key1", "value1"); ``` Para eliminar una clave existente, usa el método `.withRemoved(customAttributeForKey:)`: ```kotlin showLineNumbers builder.withRemovedCustomAttribute("key2") ``` ```java showLineNumbers builder.withRemovedCustomAttribute("key2"); ``` A veces necesitas saber qué atributos personalizados ya se han establecido. Para ello, usa el campo `customAttributes` del objeto `AdaptyProfile`. :::warning Ten en cuenta que el valor de `customAttributes` puede estar desactualizado, ya que los atributos de usuario pueden enviarse desde distintos dispositivos en cualquier momento, por lo que los atributos en el servidor podrían haber cambiado desde la última sincronización. ::: ### Límites \{#limits\} - Hasta 30 atributos personalizados por usuario - Los nombres de las claves tienen un máximo de 30 caracteres. El nombre de la clave puede incluir caracteres alfanuméricos y cualquiera de los siguientes: `_` `-` `.` - El valor puede ser una cadena de texto o un número decimal con un máximo de 50 caracteres. --- # File: android-listen-subscription-changes --- --- title: "Comprobar el estado de la suscripción en el SDK de Android" description: "Rastrea y gestiona el estado de la suscripción del usuario en Adapty para mejorar la retención de clientes en tu app de Android." --- Con Adapty, hacer seguimiento del estado de la suscripción es muy sencillo. No necesitas insertar manualmente los IDs de producto en tu código. En su lugar, puedes confirmar fácilmente el estado de la suscripción de un usuario comprobando si tiene un [nivel de acceso](access-level) activo. Antes de empezar a comprobar el estado de la suscripción, configura las [notificaciones en tiempo real para desarrolladores (RTDN)](enable-real-time-developer-notifications-rtdn). ## Nivel de acceso y el objeto AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Los niveles de acceso son propiedades del objeto [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Te recomendamos recuperar el perfil cuando tu app arranque, por ejemplo al [identificar a un usuario](android-identifying-users#setting-customer-user-id-on-configuration), y luego actualizarlo cada vez que se produzcan cambios. Así podrás usar el objeto de perfil sin tener que solicitarlo repetidamente. Para recibir notificaciones sobre actualizaciones del perfil, escucha los cambios tal como se describe en la sección [Escuchar actualizaciones del perfil, incluidos los niveles de acceso](android-listen-subscription-changes) más abajo. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Obtener el nivel de acceso desde el servidor \{#retrieving-the-access-level-from-the-server\} Para obtener el nivel de acceso desde el servidor, usa el método `.getProfile()`: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // check the access } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Parámetros de respuesta: | Parámetro | Descripción | | --------- | ------------------------------------------------------------ | | Profile |

Un objeto [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). En general, solo necesitas comprobar el estado del nivel de acceso del perfil para determinar si el usuario tiene acceso premium a la app.

El método `.getProfile` devuelve el resultado más actualizado posible, ya que siempre intenta consultar la API. Si por algún motivo (p. ej., sin conexión a internet) el SDK de Adapty no puede obtener información del servidor, se devolverán los datos de la caché. También es importante tener en cuenta que el SDK de Adapty actualiza la caché de `AdaptyProfile` con regularidad para mantener esta información lo más actualizada posible.

| El método `.getProfile()` te proporciona el perfil del usuario, a partir del cual puedes obtener el estado del nivel de acceso. Puedes tener varios niveles de acceso por app. Por ejemplo, si tienes una app de periódico y vendes suscripciones a distintos temas de forma independiente, puedes crear los niveles de acceso "sports" y "science". Sin embargo, la mayoría de las veces solo necesitarás un nivel de acceso; en ese caso, puedes utilizar simplemente el nivel de acceso "premium" predeterminado. A continuación se muestra un ejemplo para comprobar el nivel de acceso "premium" predeterminado: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value if (profile.accessLevels["premium"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("premium"); if (premium != null && premium.isActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` ### Escuchar actualizaciones del estado de la suscripción \{#listening-for-subscription-status-updates\} Cada vez que cambia la suscripción del usuario, Adapty lanza un evento. Para recibir mensajes de Adapty, necesitas hacer alguna configuración adicional: ```kotlin showLineNumbers Adapty.setOnProfileUpdatedListener { profile -> // handle any changes to subscription state } ``` ```java showLineNumbers t Adapty.setOnProfileUpdatedListener(profile -> { // handle any changes to subscription state }); ``` Adapty también lanza un evento al iniciar la aplicación. En ese caso, se pasará el estado de la suscripción en caché. ### Caché del estado de la suscripción \{#subscription-status-cache\} La caché implementada en el SDK de Adapty almacena el estado de la suscripción del perfil. Esto significa que, aunque el servidor no esté disponible, se puede acceder a los datos en caché para obtener información sobre el estado de la suscripción del perfil. No obstante, hay que tener en cuenta que no es posible solicitar datos directamente desde la caché. El SDK consulta periódicamente el servidor cada minuto para comprobar si hay actualizaciones o cambios relacionados con el perfil. Si existen modificaciones, como nuevas transacciones u otras actualizaciones, se enviarán a los datos en caché para mantenerlos sincronizados con el servidor. --- # File: kids-mode-android --- --- title: "Modo niños en el SDK de Android" description: "Activa el Modo niños fácilmente para cumplir con las políticas de Google. Sin GAID ni datos publicitarios en el SDK de Android." --- Si tu aplicación Android está destinada a niños, debes cumplir con las políticas de [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Si usas el SDK de Adapty, unos pocos pasos sencillos te ayudarán a configurarlo para cumplir con estas políticas y superar las revisiones de la app store. ## ¿Qué se necesita? \{#whats-required\} Debes configurar el SDK para desactivar la recopilación de: - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) - [Dirección IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Además, te recomendamos usar el customer user ID con cuidado. Un ID de usuario con formato `` se considerará recopilación de datos personales, al igual que el uso del correo electrónico. Para el Modo Infantil, la mejor práctica es utilizar identificadores aleatorios o anonimizados (por ejemplo, IDs con hash o UUIDs generados por el dispositivo) para garantizar el cumplimiento normativo. ## Activar el modo para niños \{#enabling-kids-mode\} ### Actualizaciones en el Adapty Dashboard En el Adapty Dashboard, debes desactivar la recogida de direcciones IP. Para ello, ve a [App settings](https://app.adapty.io/settings/general) y haz clic en **Disable IP address collection** dentro de **Collect users' IP address**. ### Actualizaciones en el código de tu aplicación móvil Para cumplir con las políticas, debes deshabilitar la recopilación del Android Advertising ID (AAID/GAID) y la dirección IP al inicializar el SDK de Adapty: **Kotlin:** ```kotlin showLineNumbers override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") // highlight-start .withAdIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() ) } ``` **Java:** ```java showLineNumbers @Override public void onCreate() { super.onCreate(); Adapty.activate( applicationContext, new AdaptyConfig.Builder("PUBLIC_SDK_KEY") // highlight-start .withAdIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() ); } ``` ### Actualizaciones en tu manifiesto de Android \{#updates-in-your-android-manifest\} :::note Si tu app está dirigida **exclusivamente** a niños y compila contra Android 13 (API 33) o superior, Google Play exige que no solicites el permiso `AD_ID`. Otro SDK de tu app (analíticas, atribución o anuncios) puede añadir este permiso mediante la fusión de manifiestos. Establecer `withAdIdCollectionDisabled(true)` impide que Adapty recopile el ID, pero no elimina un permiso que otro SDK haya declarado. ::: Para eliminar el permiso, añade lo siguiente dentro del elemento `` de `app/src/main/AndroidManifest.xml`. El elemento `` debe declarar `xmlns:tools="http://schemas.android.com/tools"`. ```xml showLineNumbers title="AndroidManifest.xml" ``` --- # File: android-get-onboardings --- --- title: "Obtener onboardings en el SDK de Android" description: "Aprende cómo recuperar onboardings en Adapty para Android." --- :::tip **A partir de la versión 4 del SDK**, puedes crear [flows](android-get-pb-paywalls) como una alternativa más potente a los onboardings. A diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia coherente con Android, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](android-get-pb-paywalls) y [Mostrar flows y paywalls](android-present-paywalls) para empezar. ::: Tras [diseñar la parte visual de tu onboarding](design-onboarding) con el builder en el Adapty Dashboard, puedes mostrarlo en tu app de Android. El primer paso es obtener el onboarding asociado al placement y su configuración de vista, como se describe a continuación. Antes de comenzar, asegúrate de que: 1. Tienes instalado el [SDK de Adapty para Android](sdk-installation-android) versión 3.8.0 o superior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). ## Obtener el onboarding \{#fetch-onboarding\} Cuando creas un [onboarding](onboardings) con nuestro editor sin código, se almacena como un contenedor con la configuración que tu app necesita obtener y mostrar. Este contenedor gestiona toda la experiencia: qué contenido aparece, cómo se presenta y cómo se procesan las interacciones del usuario (como respuestas a cuestionarios o entradas de formularios). El contenedor también registra automáticamente los eventos de analítica, por lo que no necesitas implementar un seguimiento de vistas por separado. Para un mejor rendimiento, obtén la configuración del onboarding con antelación para que las imágenes tengan tiempo suficiente de descargarse antes de mostrárselas a los usuarios. Para obtener un onboarding, utiliza el método `getOnboarding`: ```kotlin showLineNumbers Adapty.getOnboarding("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val onboarding = result.value // the requested onboarding } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** |

opcional

por defecto: `en`

|

El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.

Ejemplo: `en` significa inglés, `pt-br` representa el portugués brasileño.

Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.

| | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios trabajan con una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo intermitente que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.

Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se borra cuando se desinstala la app o mediante una limpieza manual.

El SDK de Adapty almacena los onboardings localmente en dos capas: la caché actualizada periódicamente descrita anteriormente y los onboardings de respaldo. También utilizamos CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la última versión de tus onboardings y asegurar la fiabilidad incluso cuando la conexión a internet es escasa.

| | **loadTimeout** | por defecto: 5 seg |

Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.

Ten en cuenta que en casos poco frecuentes este método puede agotar el tiempo de espera ligeramente después del valor especificado en `loadTimeout`, ya que la operación puede consistir en diferentes peticiones internamente.

Para Android: puedes crear un `TimeInterval` con funciones de extensión (como `5.seconds`, donde `.seconds` proviene de `import com.adapty.utils.seconds`), o `TimeInterval.seconds(5)`. Para no establecer ningún límite, usa `TimeInterval.INFINITE`.

| Parámetros de respuesta: | Parámetro | Descripción | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Un objeto [`AdaptyOnboarding`](https://android.adapty.io/adapty/com.adapty.models/-adapty-onboarding/) con: el identificador y la configuración del onboarding, Remote Config y otras propiedades. | ## Acelera la obtención del onboarding con el onboarding de audiencia predeterminada \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Normalmente, los onboardings se obtienen casi al instante, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, cuando tienes muchas audiencias y onboardings, y tus usuarios tienen una conexión a internet lenta, obtener un onboarding puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un onboarding predeterminado para garantizar una experiencia fluida en lugar de no mostrar ninguno. Para solucionar esto, puedes usar el método `getOnboardingForDefaultAudience`, que obtiene el onboarding del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el onboarding mediante el método `getOnboarding`, tal como se detalla en la sección [Obtener onboarding](#fetch-onboarding) anterior. :::warning Considera usar `getOnboarding` en lugar de `getOnboardingForDefaultAudience`, ya que este último tiene limitaciones importantes: - **Problemas de compatibilidad**: Puede causar problemas al dar soporte a varias versiones de la app, lo que obliga a diseños retrocompatibles o a asumir que las versiones anteriores podrían mostrarse incorrectamente. - **Sin personalización**: Solo muestra contenido para la audiencia "All Users", eliminando la segmentación por país, atribución o atributos personalizados. Si una obtención más rápida compensa estos inconvenientes en tu caso de uso, utiliza `getOnboardingForDefaultAudience` como se muestra a continuación. De lo contrario, usa `getOnboarding` como se describe [más arriba](#fetch-onboarding). ::: ```kotlin Adapty.getOnboardingForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val onboarding = result.value // Handle successful onboarding retrieval } is AdaptyResult.Error -> { val error = result.error // Handle error case } } } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** |

opcional

por defecto: `en`

|

El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.

Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.

Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.

| | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché si falla. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché cuando existan. En ese caso, puede que los usuarios no reciban los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos sin importar la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.

Ten en cuenta que la caché se conserva al reiniciar la app y solo se borra cuando se desinstala la aplicación o mediante una limpieza manual.

El SDK de Adapty almacena los onboardings localmente en dos capas: la caché de actualización periódica descrita anteriormente y los onboardings de respaldo. También usamos CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la última versión de tus onboardings y asegurar la fiabilidad incluso cuando la conexión a internet es limitada.

| --- # File: android-present-onboardings --- --- title: "Presentar onboardings en Android SDK" description: "Aprende a presentar onboardings en Android para una mejor captación de usuarios." --- :::tip **A partir del SDK v4**, puedes crear [flows](android-get-pb-paywalls) como una alternativa más potente a los onboardings. A diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, un aspecto coherente con Android, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](android-get-pb-paywalls) y [Mostrar flows y paywalls](android-present-paywalls) para empezar. ::: Antes de comenzar, asegúrate de que: 1. Has instalado el [SDK de Adapty para Android](sdk-installation-android) 3.8.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). Si has personalizado un onboarding con el Onboarding Builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese onboarding ya contiene tanto qué mostrar como cómo mostrarlo. Para mostrar el onboarding visual en la pantalla del dispositivo, primero debes configurarlo. Para ello, llama al método `AdaptyUI.getOnboardingView()` o crea el `OnboardingView` directamente: ```kotlin val onboardingView = AdaptyUI.getOnboardingView( activity = this, viewConfig = onboardingConfig, eventListener = eventListener ) ``` ```kotlin val onboardingView = AdaptyOnboardingView(activity) onboardingView.show( viewConfig = onboardingConfig, delegate = eventListener ) ``` ```java AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView( activity, onboardingConfig, eventListener ); ``` ```java AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity); onboardingView.show(onboardingConfig, eventListener); ``` ```xml ``` Una vez que la vista se haya creado correctamente, puedes añadirla a la jerarquía de vistas y mostrarla en la pantalla del dispositivo. Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :-------- | :------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **viewConfig** | obligatorio | La configuración del onboarding obtenida de `AdaptyUI.getOnboardingConfiguration()` | | **eventListener** | obligatorio | Una implementación de `AdaptyOnboardingEventListener` para gestionar los eventos del onboarding. Consulta [Gestión de eventos de onboarding](android-handle-onboarding-events) para más detalles. | ## Cambiar el color del indicador de carga \{#change-loading-indicator-color\} Puedes sobreescribir el color por defecto del indicador de carga de la siguiente manera: ```xml ``` ## Agrega transiciones suaves entre la pantalla de inicio y el onboarding \{#add-smooth-transitions-between-the-splash-screen-and-onboarding\} Por defecto, entre la pantalla de inicio y el onboarding verás la pantalla de carga hasta que el onboarding se haya cargado por completo. Sin embargo, si quieres que la transición sea más suave, puedes personalizarla y ampliar la pantalla de inicio o mostrar otra cosa. Para ello, crea `adapty_onboarding_placeholder_view.xml` en `res/layout` y define ahí un marcador de posición (lo que se mostrará mientras el onboarding se está cargando). Si defines un placeholder, el onboarding se cargará en segundo plano y se mostrará automáticamente cuando esté listo. ## Desactivar los márgenes del área segura \{#disable-safe-area-paddings\} Por defecto, la vista de onboarding aplica automáticamente márgenes del área segura para evitar elementos de la interfaz del sistema como la barra de estado y la barra de navegación. Sin embargo, si quieres desactivar este comportamiento y tener control total sobre el diseño, puedes hacerlo estableciendo el parámetro `safeAreaPaddings` en `false`. ```kotlin val onboardingView = AdaptyUI.getOnboardingView( activity = this, viewConfig = onboardingConfig, eventListener = eventListener, safeAreaPaddings = false ) ``` ```kotlin val onboardingView = AdaptyOnboardingView(activity) onboardingView.show( viewConfig = onboardingConfig, delegate = eventListener, safeAreaPaddings = false ) ``` ```java AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView( activity, onboardingConfig, eventListener, false ); ``` ```java AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity); onboardingView.show(onboardingConfig, eventListener, false); ``` Alternativamente, puedes controlar este comportamiento de forma global añadiendo un recurso booleano a tu app: ```xml false ``` Cuando `safeAreaPaddings` se establece en `false`, el onboarding se extenderá a pantalla completa sin ajustes automáticos de padding, dándote control total sobre el diseño y permitiendo que el contenido del onboarding ocupe todo el espacio de pantalla. ## Personalizar cómo se abren los enlaces en los onboardings \{#customize-how-links-open-in-onboardings\} :::important La personalización de cómo se abren los enlaces en los onboardings está disponible a partir de la versión 3.15.1 del SDK de Adapty. ::: Por defecto, los enlaces en los onboardings se abren en un navegador integrado en la app. Esto ofrece una experiencia fluida al mostrar las páginas web dentro de tu aplicación, sin que el usuario tenga que cambiar de app. Si prefieres abrir los enlaces en un navegador externo, puedes personalizar este comportamiento estableciendo el parámetro `externalUrlsPresentation` en `AdaptyWebPresentation.ExternalBrowser`: ```kotlin val onboardingConfig = AdaptyUI.getOnboardingConfiguration( onboarding = onboarding, externalUrlsPresentation = AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser ) ``` ```java AdaptyOnboardingConfiguration onboardingConfig = AdaptyUI.getOnboardingConfiguration( onboarding, AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser ); ``` --- # File: android-handle-onboarding-events --- --- title: "Gestionar eventos de onboarding en el SDK de Android" description: "Gestiona eventos relacionados con el onboarding en Android usando Adapty." --- :::tip **A partir del SDK v4**, puedes crear [flows](android-get-pb-paywalls) como una alternativa más potente a los onboardings. A diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia consistente con Android, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](android-get-pb-paywalls) y [Mostrar flows y paywalls](android-present-paywalls) para empezar. ::: Antes de comenzar, asegúrate de que: 1. Has instalado [Adapty Android SDK](sdk-installation-android) 3.8.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). Los onboardings configurados con el builder generan eventos a los que tu app puede responder. A continuación aprenderás cómo hacerlo. Para controlar o monitorizar los procesos que ocurren en la pantalla de onboarding dentro de tu app Android, implementa la interfaz `AdaptyOnboardingEventListener`. ## Acciones personalizadas \{#custom-actions\} En el builder, puedes añadir una acción **personalizada** a un botón y asignarle un ID. Luego, puedes usar ese ID en tu código y gestionarlo como una acción personalizada. Por ejemplo, si un usuario pulsa un botón personalizado, como **Login** o **Allow notifications**, el método delegado `onCustomAction` se activará con el ID de acción del builder. Puedes crear tus propios IDs, como "allowNotifications". ```kotlin showLineNumbers class YourActivity : AppCompatActivity() { private val eventListener = object : AdaptyOnboardingEventListener { override fun onCustomAction(action: AdaptyOnboardingCustomAction, context: Context) { when (action.actionId) { "allowNotifications" -> { // Request notification permissions } } } override fun onError(error: AdaptyOnboardingError, context: Context) { // Handle errors } // ... other required delegate methods } } ```
Ejemplo de evento (haz clic para expandir) ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ```
## Cerrar el onboarding \{#closing-onboarding\} El onboarding se considera cerrado cuando el usuario pulsa un botón con la acción **Close** asignada. Debes gestionar qué ocurre cuando el usuario cierra el onboarding. Por ejemplo: :::important Debes gestionar qué ocurre cuando el usuario cierra el onboarding. Por ejemplo, debes dejar de mostrar el onboarding. ::: Por ejemplo: ```kotlin override fun onCloseAction(action: AdaptyOnboardingCloseAction, context: Context) { // Dismiss the onboarding screen (context as? Activity)?.onBackPressed() } ```
Ejemplo de evento (haz clic para expandir) ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
## Abrir un paywall \{#opening-a-paywall\} :::tip Gestiona este evento para abrir un paywall si quieres abrirlo dentro del onboarding. Si quieres abrir un paywall después de que se cierre, hay una forma más directa de hacerlo: gestiona [`AdaptyOnboardingCloseAction`](#closing-onboarding) y abre un paywall sin depender de los datos del evento. ::: La forma más fluida de trabajar con paywalls en onboardings es hacer que el ID de acción sea igual al ID de placement del paywall. Así, después del evento `AdaptyOnboardingOpenPaywallAction`, puedes usar el ID de placement para obtener y abrir el paywall directamente: ```kotlin override fun onOpenPaywallAction(action: AdaptyOnboardingOpenPaywallAction, context: Context) { // Get the paywall using the placement ID from the action Adapty.getPaywall(placementId = action.actionId) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // Get the paywall configuration AdaptyUI.getViewConfiguration(paywall) { result -> when(result) { is AdaptyResult.Success -> { val paywallConfig = result.value // Create and present the paywall val paywallView = AdaptyUI.getPaywallView( activity = this, viewConfig = paywallConfig, products, eventListener = paywallEventListener ) // Add the paywall view to your layout binding.container.addView(paywallView) } is AdaptyResult.Error -> { val error = result.error // handle the error } } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } } ```
Ejemplo de evento (Haz clic para expandir) ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ```
## Finalización de la carga del onboarding \{#finishing-loading-onboarding\} Cuando el onboarding termina de cargarse, se invocará este método: ```kotlin override fun onFinishLoading(action: AdaptyOnboardingLoadedAction, context: Context) { // Handle loading completion } ```
Ejemplo de evento (haz clic para expandir) ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ```
## Eventos de navegación \{#navigation-events\} El método `onAnalyticsEvent` se llama cuando se producen distintos eventos de analítica durante el flow de onboarding. El objeto `event` puede ser de uno de los siguientes tipos: |Tipo | Descripción | |------------|-------------| | `OnboardingStarted` | Cuando el onboarding se ha cargado | | `ScreenPresented` | Cuando se muestra cualquier pantalla | | `ScreenCompleted` | Cuando se completa una pantalla. Incluye un `elementId` opcional (identificador del elemento completado) y un `reply` opcional (respuesta del usuario). Se activa cuando el usuario realiza cualquier acción para salir de la pantalla. | | `SecondScreenPresented` | Cuando se muestra la segunda pantalla | | `UserEmailCollected` | Se activa cuando se recoge el email del usuario a través del campo de entrada | | `OnboardingCompleted` | Se activa cuando un usuario llega a una pantalla con el ID `final`. Si necesitas este evento, asigna el ID `final` a la última pantalla. | | `Unknown` | Para cualquier tipo de evento no reconocido. Incluye `name` (el nombre del evento desconocido) y `meta` (metadatos adicionales) | Cada evento incluye información `meta` con los siguientes campos: | Campo | Descripción | |------------|-------------| | `onboardingId` | Identificador único del flow de onboarding | | `screenClientId` | Identificador de la pantalla actual | | `screenIndex` | Posición de la pantalla actual en el flow | | `totalScreens` | Número total de pantallas en el flow | A continuación se muestra un ejemplo de cómo usar los eventos de analytics para el seguimiento: ```kotlin override fun onAnalyticsEvent(event: AdaptyOnboardingAnalyticsEvent, context: Context) { when (event) { is AdaptyOnboardingAnalyticsEvent.OnboardingStarted -> { // Track onboarding start trackEvent("onboarding_started", event.meta) } is AdaptyOnboardingAnalyticsEvent.ScreenPresented -> { // Track screen presentation trackEvent("screen_presented", event.meta) } is AdaptyOnboardingAnalyticsEvent.ScreenCompleted -> { // Track screen completion with user response trackEvent("screen_completed", event.meta, event.elementId, event.reply) } is AdaptyOnboardingAnalyticsEvent.OnboardingCompleted -> { // Track successful onboarding completion trackEvent("onboarding_completed", event.meta) } is AdaptyOnboardingAnalyticsEvent.Unknown -> { // Handle unknown events trackEvent(event.name, event.meta) } // Handle other cases as needed } } ```
Ejemplos de eventos (Haz clic para expandir) ```javascript // OnboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // ScreenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // ScreenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // SecondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // UserEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // OnboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
--- # File: android-onboarding-input --- --- title: "Procesar datos de onboardings en el SDK de Android" description: "Guarda y usa los datos de los onboardings en tu app de Android con el SDK de Adapty." --- :::tip **A partir de la versión 4 del SDK**, puedes crear [flows](android-get-pb-paywalls) como alternativa más potente a los onboardings. A diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia consistente con Android, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](android-get-pb-paywalls) y [Mostrar flows y paywalls](android-present-paywalls) para empezar. ::: Cuando tus usuarios responden a una pregunta del quiz o introducen datos en un campo de entrada, se invocará el método `onStateUpdatedAction`. Puedes guardar o procesar el tipo de campo en tu código. Por ejemplo: ```kotlin override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Store user preferences or responses when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Select -> { // Handle single selection } is AdaptyOnboardingStateUpdatedParams.MultiSelect -> { // Handle multiple selections } is AdaptyOnboardingStateUpdatedParams.Input -> { // Handle text input } is AdaptyOnboardingStateUpdatedParams.DatePicker -> { // Handle date selection } } } ``` Consulta el formato de acción [aquí](https://android.adapty.io/adapty-ui/com.adapty.ui.onboardings.actions/-adapty-onboarding-state-updated-action/).
Ejemplos de datos guardados (el formato puede variar según tu implementación) ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ```
## Casos de uso \{#use-cases\} ### Enriquece los perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Si quieres vincular directamente los datos introducidos con el perfil del usuario y evitar pedirle la misma información dos veces, necesitas [actualizar el perfil de usuario](android-setting-user-attributes) con esos datos al gestionar la acción. Por ejemplo, pides a los usuarios que introduzcan su nombre en el campo de texto con el ID `name` y quieres establecer el valor de ese campo como el nombre del usuario. También les pides que introduzcan su correo electrónico en el campo `email`. En el código de tu app, podría verse así: ```kotlin showLineNumbers override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Store user preferences or responses when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Input -> { // Handle text input val builder = AdaptyProfileParameters.Builder() // Map elementId to appropriate profile field when (action.elementId) { "name" -> { when (val inputParams = params.params) { is AdaptyOnboardingInputParams.Text -> { builder.withFirstName(inputParams.value) } } } "email" -> { when (val inputParams = params.params) { is AdaptyOnboardingInputParams.Email -> { builder.withEmail(inputParams.value) } } } } Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } } } } ``` ### Personalizar paywalls según las respuestas \{#customize-paywalls-based-on-answers\} Usando cuestionarios en los onboardings, también puedes personalizar los paywalls que muestras a los usuarios después de que completen el onboarding. Por ejemplo, puedes preguntarles sobre su experiencia con el deporte y mostrar distintos CTAs y productos a diferentes grupos de usuarios. 1. [Añade un cuestionario](onboarding-quizzes) en el editor de onboarding y asigna IDs significativos a sus opciones. 2. Gestiona las respuestas del cuestionario según sus IDs y [establece atributos personalizados](android-setting-user-attributes) para los usuarios. ```kotlin showLineNumbers override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Handle quiz responses and set custom attributes when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Select -> { // Handle quiz selection val builder = AdaptyProfileParameters.Builder() // Map quiz responses to custom attributes when (action.elementId) { "experience" -> { // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.withCustomAttribute("experience", params.params.value) } } Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } } } } ``` 3. [Crea segmentos](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade [audiencias](audience) para cada segmento que hayas creado. 5. [Muestra un paywall](android-paywalls) para el placement en el código de tu app. Si tu onboarding tiene un botón que abre un paywall, implementa el código del paywall como [respuesta a la acción de ese botón](android-handle-onboarding-events#opening-a-paywall). --- # File: android-sdk-call-order --- --- title: "Orden de llamadas en el SDK de Android" description: "Evita perder el acceso premium, que falten datos de atribución y errores intermitentes de ADAPTY_NOT_INITIALIZED llamando a los métodos del SDK de Adapty en el orden correcto." --- `Adapty.activate()` debe completarse antes de llamar a cualquier otro método del SDK de Adapty. Hasta que finalice, el SDK no tiene estado. Cualquier llamada emitida antes o en paralelo con `activate()` falla con [`ADAPTY_NOT_INITIALIZED`](android-sdk-error-handling). Si tu app autentica usuarios y recoges un ID de usuario personalizado después del lanzamiento, llama a `Adapty.identify()` en ese momento. No llames a métodos de acción de usuario hasta que se ejecute el callback de finalización de `identify`. Las llamadas que compiten con él devuelven un error en su callback, o recaen sobre el perfil anónimo creado en la activación. Cuando esto ocurre, la atribución, los IDs de MMP como `appsflyer_id` y la propiedad de la instalación no siempre se transfieren al perfil identificado. Si tu app no autentica usuarios, omite `identify` y sigue trabajando con el perfil anónimo. Los SDKs de MMP y analíticas (AppsFlyer, Adjust, Branch, PostHog) siguen la misma regla. Inicialízalos primero y espera sus callbacks de UID antes de llamar a `Adapty.activate`. De lo contrario, el ID del MMP queda en un perfil anónimo temporal y no siempre se transfiere al perfil identificado. Para los detalles específicos de AppsFlyer, consulta [AppsFlyer](appsflyer). ## El orden correcto \{#the-correct-order\} Tu camino depende de dos cosas: cuándo conoces el ID de usuario personalizado y si usas un SDK de MMP o analíticas. - **Pasos 2 y 5**: Obligatorios para toda app. Activa el SDK y luego llama a los métodos del SDK. - **Pasos 1 y 3**: Necesarios solo si integras un SDK de MMP o analíticas (AppsFlyer, Adjust, Branch, PostHog). - **Paso 4**: Necesario solo si tu app autentica usuarios y recoge el ID de usuario personalizado después del lanzamiento. Si tienes el ID de usuario personalizado al lanzar la app, pásalo en el `AdaptyConfig.Builder` antes de llamar a `activate()` (paso 2a). Esta ruta nunca crea un perfil anónimo, por lo que el paso 4 no es necesario. | Paso | Llamada | Cuándo | Notas | |------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Inicializa tu SDK de MMP o analíticas (AppsFlyer, Adjust, PostHog, Branch) | Al lanzar la app, primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `Adapty.activate(context, AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | Al lanzar la app, tras el paso 1, si tienes el ID de usuario personalizado | Recomendado. Nunca se crea un perfil anónimo. | | 2b | `Adapty.activate(context, AdaptyConfig.Builder("KEY").build())` sin `customerUserId` | Al lanzar la app, tras el paso 1, si no tienes el ID de usuario personalizado (o nunca lo recoges) | Adapty crea un perfil anónimo. | | 3 | `Adapty.setIntegrationIdentifier("appsflyer_id", uid)` para cada MMP | Tras el paso 2, antes de cualquier llamada de acción de usuario | Necesario para que los IDs de MMP queden en el perfil correcto. | | 4 | `Adapty.identify("YOUR_USER_ID") { error -> ... }` | Tras el paso 3 (o el paso 2 si no hay MMP), antes del paso 5 — solo en la ruta 2b con autenticación | Usa el callback de finalización. Las llamadas concurrentes durante `identify` pueden recaer en el perfil anónimo. | | 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | Tras el paso 4 si llamas a `identify`; si no, tras el paso 3 (o el paso 2 si no hay MMP) | Estas llamadas necesitan un perfil estable. | :::important Saltarse estos pasos provoca pérdida de acceso premium para usuarios que regresan, que falte `appsflyer_id` en los perfiles, y que se devuelvan paywalls para la audiencia equivocada. ::: ## Instalaciones web2app y de embudo web \{#web2app-and-web-funnel-installs\} Si los usuarios compran en un checkout web (Stripe, Paddle) y luego instalan la app nativa, el primer `activate()` del dispositivo crea un nuevo perfil anónimo. Este perfil no está vinculado al perfil web. Si puedes resolver el ID de usuario personalizado antes del lanzamiento de la app (desde tu flujo de autenticación o el referrer de instalación), pásalo directamente en el `AdaptyConfig.Builder`. De lo contrario, la compra web será invisible en el dispositivo hasta que llames a `identify("YOUR_USER_ID")` y luego a `restorePurchases`. Para los metadatos que enviar con cada checkout web, consulta: - [Stripe](stripe) - [Paddle](paddle) --- # File: android-optimize-paywall-fetching --- --- title: "Optimizar la obtención de paywalls en el SDK de Android" description: "Obtén paywalls de Adapty de forma fiable: sincronización, caché y patrones de respaldo para Android." --- Una obtención fiable de paywalls en Android hace tres cosas: renderiza rápido, devuelve el paywall dirigido a la audiencia correcta y tiene un respaldo elegante cuando la red es lenta. Las reglas a continuación cubren los patrones de sincronización, caché y respaldo para conseguirlo. :::tip Las reglas asumen que `Adapty.activate()` y `Adapty.identify()` ya se han resuelto. Consulta [Orden de llamadas en el SDK de Android](android-sdk-call-order). ::: ## Reglas y errores comunes \{#rules-and-pitfalls\} | Haz esto | No hagas esto | Por qué | |---|---|---| | Obtén el placement que vas a mostrar. | No precargues todos los placements de forma concurrente al iniciar. | La precarga masiva bloquea el hilo principal y produce una pantalla en negro durante la ráfaga. | | Llama a `getPaywall` después de que la atribución haya tenido oportunidad de resolverse — por ejemplo, 1–2 segundos después de `activate` o tras que se dispare `setOnProfileUpdatedListener`. | No llames a `getPaywall` en `Application.onCreate()`. | La atribución aún no ha llegado. El paywall se resuelve contra la audiencia por defecto y omite silenciosamente los segmentos y la personalización de ASA. | | Establece un `loadTimeout` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | No esperes a `getPaywall` indefinidamente. | Sin un tiempo de espera, los usuarios con conectividad deficiente ven una pantalla en blanco hasta que la red responde — o cierran la app. | Consulta [Obtener paywalls y productos](fetch-paywalls-and-products-android) para la referencia de parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado. ## Ajustar para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte, regiones afectadas por enrutamiento): - Establece `fetchPolicy` en `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad` en cada obtención excepto la primera. - Configura un [paywall de respaldo](fallback-paywalls) para cada placement en el Adapty Dashboard. - Establece `loadTimeout` entre 3 y 5 segundos y acepta el respaldo cuando se agote el tiempo. - No condicionales la visualización del paywall a `getProfile`. Llama a `getPaywall` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: android-test --- --- title: "Prueba y lanzamiento en Android SDK" description: "Aprende a verificar el estado de suscripción en tu app Android con Adapty." --- Si ya has implementado el SDK de Adapty en tu app Android, querrás comprobar que todo está configurado correctamente y que las compras funcionan como se espera. Esto implica probar tanto la integración del SDK como el flujo de compra real con el entorno sandbox de Google Play. ## Prueba tu app \{#test-your-app\} Para realizar pruebas exhaustivas de tus compras in-app, incluyendo pruebas en sandbox y validación en closed track, consulta nuestra [guía de pruebas](testing-on-android). ## Prepárate para el lanzamiento \{#prepare-for-release\} Antes de enviar tu app al store, sigue la [Lista de verificación para el lanzamiento](release-checklist) para confirmar que: - La conexión al store y las notificaciones del servidor están configuradas - Las compras se completan y se reportan a Adapty - El acceso se desbloquea y se restaura correctamente - Se cumplen los requisitos de privacidad y revisión --- # File: android-sdk-error-handling --- --- title: "Gestionar errores en el SDK de Android" description: "Gestiona los errores del SDK de Android de forma eficaz con la guía de solución de problemas de Adapty." --- Cada error devuelto por el SDK es de tipo `AdaptyError`. :::tip **Activa los logs detallados antes de depurar.** La mayoría de los `AdaptyError` envuelven un error subyacente de Play Billing, de red o del backend. Con los logs detallados activados (`Adapty.logLevel = AdaptyLogLevel.VERBOSE` — consulta [Logging](sdk-installation-android#logging)), ese error envuelto se imprime en la consola, lo que normalmente te indica la causa real. ::: :::important Si estas soluciones no resuelven tu problema, consulta [Otros problemas](#other-issues) para ver los pasos que debes seguir antes de contactar con el soporte y así ayudarnos a asistirte de forma más eficiente. ::: | Error | Solución | |----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | UNKNOWN | Este error indica que ocurrió un error desconocido o inesperado. | | [ITEM_UNAVAILABLE](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_UNAVAILABLE()) | Este error ocurre principalmente durante la fase de pruebas. Puede significar que los productos no están en producción o que el usuario no pertenece al grupo de Testers en Google Play. | | ADAPTY_NOT_INITIALIZED | El SDK de Adapty no está activado.
Lo más habitual es que ocurra cuando una pantalla de inicio u otro hook de UI temprano llama a métodos de Adapty antes de que `Adapty.activate` haya terminado. El síntoma es intermitente y puede no reproducirse en un emulador porque los tiempos en dispositivos reales son distintos. Espera a que `Adapty.activate` finalice antes de ejecutar cualquier otra llamada al SDK. Consulta [Orden de llamadas en el SDK de Android](android-sdk-call-order) para ver la secuencia completa. También debes [configurar el SDK de Adapty](sdk-installation-android#activate-adapty-module-of-adapty-sdk) correctamente usando el método `Adapty.activate`. | | PROFILE_WAS_CHANGED | El perfil del usuario cambió durante la operación.
Esto ocurre cuando se llama a un método mientras `Adapty.identify` todavía está en curso: la llamada en vuelo aterriza en un perfil que está a punto de ser reemplazado y el SDK la rechaza. Espera a que `Adapty.identify` finalice antes de ejecutar otras llamadas al SDK. Consulta [Orden de llamadas en el SDK de Android](android-sdk-call-order). | | PRODUCT_NOT_FOUND | Este error indica que el producto solicitado para la compra no está disponible en el store. | | INVALID_JSON |

El JSON del paywall de respaldo local no es válido.

Corrige tu paywall en inglés predeterminado y luego reemplaza los paywalls locales no válidos. Consulta el tema [Personalizar el paywall con Remote Config](customize-paywall-with-remote-config) para saber cómo corregir un paywall, y [Definir paywalls de respaldo locales](fallback-paywalls) para saber cómo reemplazar los paywalls locales.

| |

CURRENT_SUBSCRIPTION_TO_UPDATE

\_NOT_FOUND_IN_HISTORY

| La suscripción original que debe reemplazarse no se encontró en las suscripciones activas. | | [BILLING_SERVICE_TIMEOUT](https://developer.android.com/google/play/billing/errors#service_timeout_error_code_-3) | Este error indica que la solicitud alcanzó el tiempo de espera máximo antes de que Google Play pudiera responder. Puede deberse, por ejemplo, a un retraso en la ejecución de la acción solicitada por la llamada a la Play Billing Library. | | [FEATURE_NOT_SUPPORTED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#FEATURE_NOT_SUPPORTED()) | La función solicitada no es compatible con la Play Store en el dispositivo actual. | | [BILLING_SERVICE_DISCONNECTED](https://developer.android.com/google/play/billing/errors#service_disconnected_error_code_-1) | Este error indica que la conexión de la app cliente con el servicio de Google Play Store a través del `BillingClient` se ha interrumpido. | | [BILLING_SERVICE_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#service_unavailable_error_code_2) | Este error indica que el servicio de facturación de Google Play no está disponible en este momento. En la mayoría de los casos significa que hay un problema de conexión de red entre el dispositivo cliente y los servicios de Google Play Billing. | | [BILLING_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) |

Este error indica que ocurrió un problema de facturación durante el proceso de compra. Las posibles causas son:

1. La app de Play Store en el dispositivo del usuario no está instalada o está desactualizada.

2. El usuario se encuentra en un país no compatible.

3. El usuario forma parte de una cuenta empresarial cuyo administrador ha desactivado las compras.

4. Google Play no pudo cargar el método de pago del usuario (por ejemplo, una tarjeta de crédito caducada).

5. El usuario no ha iniciado sesión en la app de Play Store.

| | [DEVELOPER_ERROR](https://developer.android.com/google/play/billing/errors#developer_error) | Este error indica que estás usando una API de forma incorrecta. | | [BILLING_ERROR](https://developer.android.com/google/play/billing/errors#error_error_code_6) | Este error indica un problema interno del propio Google Play. | | [ITEM_ALREADY_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_ALREADY_OWNED()) | El producto ya fue comprado anteriormente. | | [ITEM_NOT_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_NOT_OWNED()) | Este error indica que la acción solicitada sobre el ítem falló porque el usuario no es su propietario. | | [BILLING_NETWORK_ERROR](https://developer.android.com/google/play/billing/errors#network_error_error_code_12) | Este error indica que hubo un problema con la conexión de red entre el dispositivo y los sistemas de Play. | | NO_PRODUCT_IDS_FOUND |

Este error indica que ninguno de los productos del paywall está disponible en el store.

Si encuentras este error, sigue los pasos a continuación para resolverlo:

  1. Comprueba que todos los productos se hayan añadido al Adapty Dashboard.
  2. Asegúrate de que el **Package name** de tu app coincide con el de Google Play Console.
  3. Verifica que los identificadores de producto de los stores coincidan con los que has añadido al Dashboard. Ten en cuenta que los identificadores no deben incluir el Bundle ID, a menos que ya esté incluido en el store.
  4. Confirma que el estado de pago de la app sea **Active** en la configuración fiscal de Google. Asegúrate de que tu información fiscal esté actualizada y que tus certificados sean válidos.
  5. Comprueba que haya una cuenta bancaria vinculada a la app para que sea elegible para la monetización.
  6. Verifica si los productos están disponibles en tu región.
  7. Asegúrate de que tu app esté en uno de los tracks de prueba. El track **Internal testing** es la opción más sencilla, ya que no requiere revisión y mantiene la app oculta para los clientes.
| | NO_PURCHASES_TO_RESTORE | Este error indica que Google Play no encontró ninguna compra para restaurar. | | AUTHENTICATION_ERROR | Debes [configurar el SDK de Adapty](sdk-installation-android#activate-adapty-module-of-adapty-sdk) correctamente usando el método `Adapty.activate`. | | BAD_REQUEST | Solicitud incorrecta.
Asegúrate de haber completado todos los pasos necesarios para [integrarte con Google Play](google-play-store-connection-configuration). | | SERVER_ERROR | Error del servidor. | | REQUEST_FAILED | Este error indica un problema de red que no se puede definir con precisión. | | DECODING_FAILED | No fue posible decodificar la respuesta.
Revisa tu código y asegúrate de que los parámetros que envías son válidos. Por ejemplo, este error puede indicar que estás usando una clave API no válida. | | ANALYTICS_DISABLED | No podemos gestionar eventos de análisis porque los has [desactivado](analytics-integration#disabling-external-analytics-for-a-specific-customer). | | WRONG_PARAMETER | Este error indica que alguno de tus parámetros no es correcto: está en blanco cuando no puede estarlo, es del tipo incorrecto, etc. | ## Otros problemas \{#other-issues\} Si todavía no has encontrado una solución, los siguientes pasos pueden ser: - **Actualizar el SDK a la última versión**: Siempre recomendamos actualizar a las últimas versiones del SDK, ya que son más estables e incluyen correcciones de problemas conocidos. - **Contactar al equipo de soporte u obtener ayuda de otros desarrolladores** en el [foro de soporte](https://adapty.featurebase.app/). - **Contactar al equipo de soporte a través de [support@adapty.io](mailto:support@adapty.io) o por el chat**: Si no puedes actualizar el SDK o la actualización no resolvió el problema, contacta a nuestro equipo de soporte. Ten en cuenta que tu problema se resolverá más rápido si [activas el registro detallado](sdk-installation-android#logging) y compartes los logs con el equipo. También puedes adjuntar fragmentos de código relevantes. --- # File: migration-to-android-sdk-v4 --- --- title: "Migrar Adapty Android SDK a la versión 4.0" description: "Migra al Adapty Android SDK v4.0 reemplazando las APIs de paywall por APIs de flow, compatibles tanto con Flow Builder como con Paywall Builder." --- Adapty Android SDK 4.0 introduce los flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan tanto con el nuevo Flow Builder como con el Paywall Builder existente; no es necesario realizar ningún cambio de configuración en el Adapty Dashboard. ## Referencia rápida \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty.getPaywall(placementId, locale)` | `Adapty.getFlow(placementId)` | | `Adapty.getPaywallForDefaultAudience(placementId, locale)` | `Adapty.getFlowForDefaultAudience(placementId)` | | `AdaptyUI.getViewConfiguration(paywall)` | `AdaptyUI.getFlowConfiguration(flow, locale)` | | `AdaptyUI.LocalizedViewConfiguration` | `AdaptyUI.FlowConfiguration` | | `Adapty.getPaywallProducts(paywall)` | `Adapty.getPaywallProducts(flow)` | | `Adapty.logShowPaywall(paywall)` | `Adapty.logShowFlow(flow)` | | `AdaptyPaywall` | `AdaptyFlow` | | `AdaptyUI.getPaywallView(...)` | `AdaptyUI.getFlowView(...)` | | `AdaptyPaywallView` | `AdaptyFlowView` | | `AdaptyPaywallScreen` (Compose) | `AdaptyFlowScreen` | | `showPaywall(...)` | `showFlow(...)` | | `AdaptyPaywallInsets` | `AdaptyFlowInsets` | | `AdaptyUiEventListener` | `AdaptyFlowEventListener` | | `AdaptyUiDefaultEventListener` | `AdaptyFlowDefaultEventListener` | | `onPaywallShown` / `onPaywallClosed` | `onFlowShown` / `onFlowClosed` | | `onRenderingError` | `onError` | | `Adapty.updateAttribution(attribution, source)` (`source: String`) | `Adapty.updateAttribution(attribution, source)` (`source: AdaptyAttributionSource`) | | `Adapty.setIntegrationIdentifier(key, value)` | `Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier)` | `AdaptyPaywallProduct` mantiene su nombre — los productos siguen perteneciendo a un flow, y `getPaywallProducts` ahora recibe un `AdaptyFlow`. Los demás métodos de `AdaptyFlowEventListener` (`onProductSelected`, `onPurchaseStarted`, `onPurchaseFinished`, `onPurchaseFailure`, `onRestoreSuccess`, `onRestoreFailure`, `onActionPerformed`, `onAwaitingPurchaseParams`, `onLoadingProductsFailure`, etc.) mantienen sus nombres y firmas. ## Instalación \{#installation\} Establece la versión de `adapty-bom` en `4.0.0` (o posterior) y sincroniza el proyecto. El BOM resuelve automáticamente las versiones correspondientes de `android-sdk` y `android-ui`. Consulta [Instalar el SDK de Adapty](sdk-installation-android) para ver las declaraciones de dependencias. ## APIs eliminadas y en desuso \{#removed-and-deprecated-apis\} - **`Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized, callback)`** — eliminada. Esta sobrecarga fue marcada como obsoleta en la v3. Pasa las mismas opciones a través de `AdaptyPurchaseParameters` en su lugar: ```diff showLineNumbers - Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized) { result -> /* ... */ } + val params = AdaptyPurchaseParameters.Builder() + .withSubscriptionUpdateParams(subscriptionUpdateParams) + .withOfferPersonalized(isOfferPersonalized) + .build() + Adapty.makePurchase(activity, product, params) { result -> /* ... */ } ``` - **Los onboardings están obsoletos.** `AdaptyUI.getOnboardingView` y `AdaptyUI.getOnboardingConfiguration` están marcados como `@Deprecated` en la versión 4.0 — migra los onboardings a flows creados en el [Flow Builder](adapty-flow-builder). ## Obtener flows \{#fetching-flows\} ### getPaywall + getViewConfiguration → getFlow + getFlowConfiguration \{#getpaywall--getviewconfiguration--getflow--getflowconfiguration\} El tipo de retorno de fetch cambia de `AdaptyPaywall` a `AdaptyFlow`, y el cargador de configuración se renombra de `AdaptyUI.getViewConfiguration` a `AdaptyUI.getFlowConfiguration` (devolviendo `AdaptyUI.FlowConfiguration` en lugar de `AdaptyUI.LocalizedViewConfiguration`). El parámetro `locale` sale de la llamada de fetch y pasa a `getFlowConfiguration`: ```diff showLineNumbers - Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result -> + Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> if (result is AdaptyResult.Success) { - val paywall = result.value - if (!paywall.hasViewConfiguration) return@getPaywall - AdaptyUI.getViewConfiguration(paywall) { configResult -> + val flow = result.value + if (!flow.hasViewConfiguration) return@getFlow + AdaptyUI.getFlowConfiguration(flow, locale = "en") { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value } } } } ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` ahora recibe un `AdaptyFlow` devuelto por `Adapty.getFlow`: ```diff showLineNumbers - Adapty.getPaywallProducts(paywall) { result -> /* products */ } + Adapty.getPaywallProducts(flow) { result -> /* products */ } ``` ## Seguimiento de vistas de flows \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` ha sido renombrado a `logShowFlow` y ahora recibe un `AdaptyFlow` en lugar de un `AdaptyPaywall`. El evento sigue registrándose en la misma variación, por lo que las métricas de embudo y de prueba A/B existentes siguen funcionando sin cambios en el dashboard. ```diff showLineNumbers - Adapty.logShowPaywall(paywall) + Adapty.logShowFlow(flow) ``` Al igual que en v3, no es necesario llamar a este método cuando se muestran flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder) — Adapty registra esas vistas automáticamente. ## Mostrar flows \{#displaying-flows\} ### getPaywallView / AdaptyPaywallView → getFlowView / AdaptyFlowView Renombra el método de fábrica y el tipo de vista, y pasa la `AdaptyUI.FlowConfiguration`: ```diff showLineNumbers - val paywallView = AdaptyUI.getPaywallView( - activity, - viewConfiguration, - products, - eventListener, - ) + val flowView = AdaptyUI.getFlowView( + activity, + flowConfiguration, + products, + eventListener, + ) ``` Si creas la vista directamente, el método show también cambia de nombre: ```diff showLineNumbers - val paywallView = AdaptyPaywallView(activity) - paywallView.showPaywall(viewConfiguration, products, eventListener) + val flowView = AdaptyFlowView(activity) + flowView.showFlow(flowConfiguration, products, eventListener) ``` En los layouts XML, actualiza el tag de la vista: ```diff showLineNumbers - + ``` El parámetro opcional `personalizedOfferResolver` se ha eliminado de `getFlowView` / `showFlow` / `AdaptyFlowScreen`. Para indicar precios personalizados, configúralo por producto a través de `onAwaitingPurchaseParams` (`AdaptyPurchaseParameters.Builder().withOfferPersonalized(true)`). Un nuevo parámetro opcional `customAssets` te permite sobreescribir imágenes y vídeos en tiempo de ejecución — consulta [Personalizar assets](android-get-pb-paywalls#customize-assets). ### AdaptyPaywallScreen → AdaptyFlowScreen En Jetpack Compose, renombra el composable y actualiza el parámetro de configuración: ```diff showLineNumbers - AdaptyPaywallScreen( - viewConfiguration, + AdaptyFlowScreen( + flowConfiguration, products, eventListener, ) ``` ## Manejo de eventos \{#handling-events\} El listener de eventos se renombra de `AdaptyUiEventListener` a `AdaptyFlowEventListener` (y `AdaptyUiDefaultEventListener` a `AdaptyFlowDefaultEventListener`). La mayoría de los nombres de métodos no cambian; los callbacks de ciclo de vida y renderizado se renombran: ```diff showLineNumbers - class YourListener : AdaptyUiDefaultEventListener() { + class YourListener : AdaptyFlowDefaultEventListener() { - override fun onPaywallShown(context: Context) {} - override fun onPaywallClosed() {} + override fun onFlowShown(context: Context) {} + override fun onFlowClosed() {} - override fun onRenderingError(error: AdaptyError, context: Context) {} + override fun onError(error: AdaptyError, context: Context) {} } ``` Los cuerpos de los handlers existentes no necesitan cambios en el código — solo renombra el tipo y los overrides. `onError` se activa para los mismos errores de renderizado que `onRenderingError`, más otros errores de ejecución no relacionados con compras. Consulta [Gestionar eventos de flow y paywall](android-handling-events) para ver la lista completa de callbacks. v4 también añade un callback `onBackPressed(context): Boolean`, y su comportamiento predeterminado cambia cómo funciona el botón atrás del sistema. Anteriormente, el botón atrás (o el gesto de retroceso) se propagaba a tu actividad o fragmento, lo que normalmente cerraba el paywall. En v4, la implementación predeterminada consume la pulsación, por lo que **el botón atrás del sistema ya no cierra un flow por sí solo** — igual que en iOS, donde un flow no se puede cerrar con un gesto del sistema. Ofrece a los usuarios una salida explícita (un botón **Close** o una acción `on_device_back`), o sobrescribe `onBackPressed` para que devuelva `false` y restaurar el comportamiento anterior. Consulta [Botón atrás del sistema](android-handling-events#system-back-button) para más detalles. El manejador de compras predeterminado también deja de cerrar la pantalla. En v3, el `onPurchaseFinished` predeterminado cerraba el paywall tras cualquier compra finalizada que no fuera una cancelación del usuario (una compra exitosa o pendiente). En v4 no hace nada, por lo que **un flow permanece abierto después de una compra hasta que lo cierres manualmente**, lo que coincide con el comportamiento en iOS. Si dependías de ese cierre automático, cierra la pantalla tú mismo una vez que la compra finalice. Consulta [Compra exitosa, cancelada o pendiente](android-handling-events#successful-canceled-or-pending-purchase) para ver un ejemplo. ## Identificadores de atribución e integración \{#attribution-and-integration-identifiers\} ### updateAttribution El parámetro `source` cambia de `String` al nuevo tipo `AdaptyAttributionSource`, y `attribution` ahora es un `Map` (también hay una sobrecarga con `String` JSON disponible). Usa una de las fuentes predefinidas: ```diff showLineNumbers - Adapty.updateAttribution(attribution, "appsflyer") { error -> /* handle the error */ } + Adapty.updateAttribution(attribution, AdaptyAttributionSource.APPSFLYER) { error -> /* handle the error */ } ``` Fuentes predefinidas: `AdaptyAttributionSource.APPLE_ADS`, `.ADJUST`, `.APPSFLYER`, `.BRANCH`, `.TENJIN`. Para cualquier otra fuente, construye una a partir de un string: `AdaptyAttributionSource("your_source")`. ### setIntegrationIdentifier `setIntegrationIdentifier(key, value)` se reemplaza por un método que acepta uno o más valores `AdaptyIntegrationIdentifier`. Construye cada identificador con un método de conveniencia en lugar de pasar una clave de cadena sin formato: ```diff showLineNumbers - Adapty.setIntegrationIdentifier("appsflyer_id", appsFlyerId) { error -> /* handle the error */ } + Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.appsflyerId(appsFlyerId)) { error -> /* handle the error */ } ``` Puedes establecer varios identificadores en una sola llamada: ```kotlin showLineNumbers Adapty.setIntegrationIdentifier( listOf( AdaptyIntegrationIdentifier.appsflyerId(appsFlyerId), AdaptyIntegrationIdentifier.adjustDeviceId(adjustDeviceId), ) ) { error -> /* handle the error */ } ``` Reemplaza cada cadena de clave antigua por su método de conveniencia correspondiente: | Clave v3 | Método `AdaptyIntegrationIdentifier` de v4 | |---|---| | `"adjust_device_id"` | `adjustDeviceId(value)` | | `"airbridge_device_id"` | `airbridgeDeviceId(value)` | | `"amplitude_user_id"` | `amplitudeUserId(value)` | | `"amplitude_device_id"` | `amplitudeDeviceId(value)` | | `"appmetrica_device_id"` | `appmetricaDeviceId(value)` | | `"appmetrica_profile_id"` | `appmetricaProfileId(value)` | | `"appsflyer_id"` | `appsflyerId(value)` | | `"branch_id"` | `branchId(value)` | | `"facebook_anonymous_id"` | `facebookAnonymousId(value)` | | `"firebase_app_instance_id"` | `firebaseAppInstanceId(value)` | | `"mixpanel_user_id"` | `mixpanelUserId(value)` | | `"one_signal_subscription_id"` | `oneSignalSubscriptionId(value)` | | `"one_signal_player_id"` | `oneSignalPlayerId(value)` | | `"posthog_distinct_user_id"` | `posthogDistinctUserId(value)` | | `"pushwoosh_hwid"` | `pushwooshHWID(value)` | | `"tenjin_analytics_installation_id"` | `tenjinAnalyticsInstallationId(value)` | Para una clave que no esté en esta lista, construye el identificador directamente a partir de una `Key` personalizada: `AdaptyIntegrationIdentifier(AdaptyIntegrationIdentifier.Key("custom"), customValue)`. --- # File: migration-to-android-312 --- --- title: "Migrar el SDK de Adapty para Android a v3.12" description: "Migra al SDK de Adapty para Android v3.12 para obtener mejor rendimiento y nuevas funciones de monetización." --- En Adapty SDK 3.12.0, hemos eliminado el método `logShowOnboarding` del SDK. Si has estado usando este método, no estará disponible cuando actualices el SDK a la versión 3.12 o posterior. En su lugar, puedes [crear onboardings en el creador de onboardings sin código de Adapty](onboardings). Los análisis de estos onboardings se registran automáticamente y dispones de muchas opciones de personalización. --- # File: migration-to-android-310 --- --- title: "Guía de migración al SDK de Adapty para Android 3.10.0" description: "" --- El SDK de Adapty 3.10.0 es una versión mayor que introduce mejoras que pueden requerir algunos pasos de migración de tu parte: 1. `AdaptyUiPersonalizedOfferResolver` ha sido eliminado. Si lo estás usando, pásalo en el callback `onAwaitingPurchaseParams`. 2. Actualiza la firma del método `onAwaitingSubscriptionUpdateParams` para los paywalls del Paywall Builder. ## Actualizar el callback de parámetros de compra \{#update-purchase-parameters-callback\} El método `onAwaitingSubscriptionUpdateParams` ha sido renombrado a `onAwaitingPurchaseParams` y ahora usa `AdaptyPurchaseParameters` en lugar de `AdaptySubscriptionUpdateParameters`. Esto te permite especificar parámetros de reemplazo de suscripción (crossgrade) e indicar si el precio es personalizado ([más información](https://developer.android.com/google/play/billing/integrate#personalized-price)), junto con otros parámetros de compra. ```diff showLineNumbers - override fun onAwaitingSubscriptionUpdateParams( - product: AdaptyPaywallProduct, - context: Context, - onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, - ) { - onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) - } + override fun onAwaitingPurchaseParams( + product: AdaptyPaywallProduct, + context: Context, + onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, + ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { + onPurchaseParamsReceived( + AdaptyPurchaseParameters.Builder() + .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) + .withOfferPersonalized(true) + .build() + ) + return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked + } ``` Si no se necesitan parámetros adicionales, puedes usar simplemente: ```kotlin showLineNumbers + override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` --- # File: migration-to-android-sdk-34 --- --- title: "Migrar el SDK de Adapty para Android a la versión 3.4" description: "Migra al SDK de Adapty para Android v3.4 para obtener mejor rendimiento y nuevas funciones de monetización." --- El SDK de Adapty 3.4.0 es una versión principal que introduce mejoras que requieren pasos de migración por tu parte. ## Actualizar los archivos de paywall de respaldo \{#update-fallback-paywall-files\} Actualiza los archivos de paywall de respaldo para garantizar la compatibilidad con la nueva versión del SDK: 1. [Descarga los archivos de paywall de respaldo actualizados](fallback-paywalls) desde el Adapty Dashboard. 2. [Reemplaza los paywalls de respaldo existentes en tu app](android-use-fallback-paywalls) con los nuevos archivos. ## Actualizar la implementación del modo Observer \{#update-implementation-of-observer-mode\} Si usas el modo Observer, asegúrate de actualizar su implementación. En versiones anteriores, tenías que restaurar las compras para que Adapty reconociera las transacciones realizadas a través de tu propia infraestructura, ya que Adapty no tenía acceso directo a ellas en el modo Observer. Si usabas paywalls, también tenías que asociar manualmente cada transacción con el paywall que la había iniciado. En la nueva versión, debes reportar explícitamente cada transacción para que Adapty la reconozca. Si usas paywalls, también tienes que pasar el ID de variación para vincular la transacción al paywall utilizado. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: ```diff showLineNumbers - Adapty.restorePurchases { result -> - if (result is AdaptyResult.Success) { - // success - } - } - - Adapty.setVariationId(transactionId, variationId) { error -> - if (error == null) { - // success - } - } + val transactionInfo = TransactionInfo.fromPurchase(purchase) + + Adapty.reportTransaction(transactionInfo, variationId) { result -> + if (result is AdaptyResult.Success) { + // success + } + } ``` ```diff showLineNumbers - Adapty.restorePurchases(result -> { - if (result instanceof AdaptyResult.Success) { - // success - } - }); - - Adapty.setVariationId(transactionId, variationId, error -> { - if (error == null) { - // success - } - }); + TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase); + + Adapty.reportTransaction(transactionInfo, variationId, result -> { + if (result instanceof AdaptyResult.Success) { + // success + } + }); ``` --- # File: migration-to-android330 --- --- title: "Migrar el SDK de Adapty para Android a v3.3" description: "Migra al SDK de Adapty para Android v3.3 para mejorar el rendimiento y acceder a nuevas funciones de monetización." --- Adapty SDK 3.3.0 es una versión principal que incorpora algunas mejoras que, sin embargo, pueden requerir ciertos pasos de migración de tu parte. 1. Actualiza la forma en que gestionas las compras en paywalls no creadas con Paywall Builder. Deja de procesar los códigos de error `USER_CANCELED` y `PENDING_PURCHASE`. Una compra cancelada ya no se considera un error y ahora aparecerá en los resultados de compra sin error. 2. Reemplaza los eventos `onPurchaseCanceled` y `onPurchaseSuccess` por el nuevo evento `onPurchaseFinished` para paywalls creadas con Paywall Builder. Este cambio se debe a la misma razón: las compras canceladas ya no se tratan como errores y se incluirán en los resultados de compra sin error. 3. Cambia la firma del método `onAwaitingSubscriptionUpdateParams` para paywalls de Paywall Builder. 4. Actualiza el método utilizado para proporcionar paywalls de respaldo si pasas el URI de archivo directamente. 5. Actualiza las configuraciones de integración para Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase y Google Analytics, Mixpanel, OneSignal, Pushwoosh. ## Actualizar la compra \{#update-making-purchase\} Las compras canceladas y pendientes anteriormente se consideraban errores y devolvían los códigos `USER_CANCELED` y `PENDING_PURCHASE`, respectivamente. Ahora se usa una nueva clase `AdaptyPurchaseResult` para indicar compras canceladas, exitosas y pendientes. Actualiza el código de compra de la siguiente manera: ~~~diff Adapty.makePurchase(activity, product) { result -> when (result) { is AdaptyResult.Success -> { - val info = result.value - val profile = info?.profile - - if (profile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true) { - // Grant access to the paid features - } + when (val purchaseResult = result.value) { + is AdaptyPurchaseResult.Success -> { + val profile = purchaseResult.profile + if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { + // Grant access to the paid features + } + } + + is AdaptyPurchaseResult.UserCanceled -> { + // Handle the case where the user canceled the purchase + } + + is AdaptyPurchaseResult.Pending -> { + // Handle deferred purchases (e.g., the user will pay offline with cash + } + } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ~~~ Para ver el ejemplo de código completo, consulta la página [Realizar compras en la app móvil](android-making-purchases#make-purchase). ## Modificar eventos de compra del Paywall Builder \{#modify-paywall-builder-purchase-events\} 1. Añade el evento `onPurchaseFinished`: ```diff showLineNumbers + public override fun onPurchaseFinished( + purchaseResult: AdaptyPurchaseResult, + product: AdaptyPaywallProduct, + context: Context, + ) { + when (purchaseResult) { + is AdaptyPurchaseResult.Success -> { + // Grant access to the paid features + } + is AdaptyPurchaseResult.UserCanceled -> { + // Handle the case where the user canceled the purchase + } + is AdaptyPurchaseResult.Pending -> { + // Handle deferred purchases (e.g., the user will pay offline with cash) + } + } + } ``` Para ver el ejemplo de código completo, consulta [Compra exitosa, cancelada o pendiente](android-handling-events#successful-canceled-or-pending-purchase) y la descripción del evento. 2. Elimina el procesamiento del evento `onPurchaseCancelled`: ```diff showLineNumbers - public override fun onPurchaseCanceled( - product: AdaptyPaywallProduct, - context: Context, - ) {} ``` 3. Elimina `onPurchaseSuccess`: ```diff showLineNumbers - public override fun onPurchaseSuccess( - profile: AdaptyProfile?, - product: AdaptyPaywallProduct, - context: Context, - ) { - // Your logic on successful purchase - } ``` ## Cambiar la firma del método onAwaitingSubscriptionUpdateParams \{#change-the-signature-of-on-awaiting-subscription-update-params-method\} Ahora, si se compra una nueva suscripción mientras otra sigue activa, llama a `onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters...))` si la nueva suscripción debe reemplazar a la actualmente activa, o a `onSubscriptionUpdateParamsReceived(null)` si la suscripción activa debe permanecer y la nueva debe añadirse por separado: ```diff showLineNumbers - public override fun onAwaitingSubscriptionUpdateParams( - product: AdaptyPaywallProduct, - context: Context, - ): AdaptySubscriptionUpdateParameters? { - return AdaptySubscriptionUpdateParameters(...) - } + public override fun onAwaitingSubscriptionUpdateParams( + product: AdaptyPaywallProduct, + context: Context, + onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, + ) { + onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) + } ``` Consulta la sección de documentación [Actualización de suscripción](android-handling-events#upgrade-subscription) para ver el ejemplo de código final. ## Actualización para proporcionar paywalls de respaldo \{#update-providing-fallback-paywalls\} Si pasas una URI de archivo para proporcionar paywalls de respaldo, actualiza la forma en que lo haces así: ```diff showLineNumbers val fileUri: Uri = // Get the URI for the file with fallback paywalls - Adapty.setFallbackPaywalls(fileUri, callback) + Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback) ``` ```diff showLineNumbers Uri fileUri = // Get the URI for the file with fallback paywalls - Adapty.setFallbackPaywalls(fileUri, callback); + Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback); ``` ## Actualizar la configuración del SDK de integraciones de terceros \{#update-third-party-integration-sdk-configuration\} Para garantizar que las integraciones funcionen correctamente con el SDK de Android de Adapty 3.3.0 y versiones posteriores, actualiza las configuraciones del SDK para las siguientes integraciones tal como se describe en las secciones a continuación. ### Adjust Actualiza el código de tu app móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers - Adjust.getAttribution { attribution -> - if (attribution == null) return@getAttribution - - Adjust.getAdid { adid -> - if (adid == null) return@getAdid - - Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST, adid) { error -> - // Handle the error - } - } - } + Adjust.getAdid { adid -> + if (adid == null) return@getAdid + + Adapty.setIntegrationIdentifier("adjust_device_id", adid) { error -> + if (error != null) { + // Handle the error + } + } + } + + Adjust.getAttribution { attribution -> + if (attribution == null) return@getAttribution + + Adapty.updateAttribution(attribution, "adjust") { error -> + if (error != null) { + // Handle the error + } + } + } ``` ```diff showLineNumbers val config = AdjustConfig(context, adjustAppToken, environment) config.setOnAttributionChangedListener { attribution -> attribution?.let { attribution -> - Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST) { error -> + Adapty.updateAttribution(attribution, "adjust") { error -> if (error != null) { // Handle the error } } } } Adjust.onCreate(config) ``` ### AirBridge Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers Airbridge.getDeviceInfo().getUUID(object: AirbridgeCallback.SimpleCallback() { override fun onSuccess(result: String) { - val params = AdaptyProfileParameters.Builder() - .withAirbridgeDeviceId(result) - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("airbridge_device_id", result) { error -> + if (error != null) { + // Handle the error + } + } } override fun onFailure(throwable: Throwable) { } }) ``` ### Amplitude Actualiza el código de tu app móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers // For Amplitude maintenance SDK (obsolete) val amplitude = Amplitude.getInstance() val amplitudeDeviceId = amplitude.getDeviceId() val amplitudeUserId = amplitude.getUserId() //for actual Amplitude Kotlin SDK val amplitude = Amplitude( Configuration( apiKey = AMPLITUDE_API_KEY, context = applicationContext ) ) val amplitudeDeviceId = amplitude.store.deviceId val amplitudeUserId = amplitude.store.userId // - val params = AdaptyProfileParameters.Builder() - .withAmplitudeDeviceId(amplitudeDeviceId) - .withAmplitudeUserId(amplitudeUserId) - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("amplitude_user_id", amplitudeUserId) { error -> + if (error != null) { + // Handle the error + } + } + Adapty.setIntegrationIdentifier("amplitude_device_id", amplitudeDeviceId) { error -> + if (error != null) { + // Handle the error + } + } ``` ### AppMetrica Actualiza el código de tu aplicación como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers val startupParamsCallback = object: StartupParamsCallback { override fun onReceive(result: StartupParamsCallback.Result?) { val deviceId = result?.deviceId ?: return - val params = AdaptyProfileParameters.Builder() - .withAppmetricaDeviceId(deviceId) - .withAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID") - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceId) { error -> + if (error != null) { + // Handle the error + } + } + + Adapty.setIntegrationIdentifier("appmetrica_profile_id", "YOUR_ADAPTY_CUSTOMER_USER_ID") { error -> + if (error != null) { + // Handle the error + } + } } override fun onRequestError( reason: StartupParamsCallback.Reason, result: StartupParamsCallback.Result? ) { // Handle the error } } AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID)) ``` ### AppsFlyer Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers val conversionListener: AppsFlyerConversionListener = object : AppsFlyerConversionListener { override fun onConversionDataSuccess(conversionData: Map) { - Adapty.updateAttribution( - conversionData, - AdaptyAttributionSource.APPSFLYER, - AppsFlyerLib.getInstance().getAppsFlyerUID(context) - ) { error -> - if (error != null) { - // Handle the error - } - } + val uid = AppsFlyerLib.getInstance().getAppsFlyerUID(context) + Adapty.setIntegrationIdentifier("appsflyer_id", uid) { error -> + if (error != null) { + // Handle the error + } + } + Adapty.updateAttribution(conversionData, "appsflyer") { error -> + if (error != null) { + // Handle the error + } + } } } ``` ### Branch Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers // Login and update attribution Branch.getAutoInstance(this) .setIdentity("YOUR_USER_ID") { referringParams, error -> referringParams?.let { data -> - Adapty.updateAttribution(data, AdaptyAttributionSource.BRANCH) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.updateAttribution(data, "branch") { error -> + if (error != null) { + // Handle the error + } + } } } // Logout Branch.getAutoInstance(context).logout() ``` ### Facebook Ads Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Facebook Ads](facebook-ads#connect-your-app-to-facebook-ads). ```diff showLineNumbers - val builder = AdaptyProfileParameters.Builder() - .withFacebookAnonymousId(AppEventsLogger.getAnonymousAppDeviceGUID(context)) - - Adapty.updateProfile(builder.build()) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier( + "facebook_anonymous_id", + AppEventsLogger.getAnonymousAppDeviceGUID(context) + ) { error -> + if (error != null) { + // Handle the error + } + } ``` ### Firebase y Google Analytics \{#firebase-and-google-analytics\} Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración de Firebase y Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers // After Adapty.activate() FirebaseAnalytics.getInstance(context).appInstanceId.addOnSuccessListener { appInstanceId -> - Adapty.updateProfile( - AdaptyProfileParameters.Builder() - .withFirebaseAppInstanceId(appInstanceId) - .build() - ) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId) { error -> + if (error != null) { + // Handle the error + } + } } ``` ```diff showLineNumbers // After Adapty.activate() - FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withFirebaseAppInstanceId(appInstanceId) - .build(); - - Adapty.updateProfile(params, error -> { - if (error != null) { - // Handle the error - } - }); - }); + FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { + Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId, error -> { + if (error != null) { + // Handle the error + } + }); + }); ``` ### Mixpanel Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers - val params = AdaptyProfileParameters.Builder() - .withMixpanelUserId(mixpanelAPI.distinctId) - .build() - - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelAPI.distinctId) { error -> + if (error != null) { + // Handle the error + } + } ``` ### OneSignal Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers // SubscriptionID val oneSignalSubscriptionObserver = object: IPushSubscriptionObserver { override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) { - val params = AdaptyProfileParameters.Builder() - .withOneSignalSubscriptionId(state.current.id) - .build() - - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.current.id) { error -> if (error != null) { // Handle the error } } } } ``` ```diff showLineNumbers // SubscriptionID IPushSubscriptionObserver oneSignalSubscriptionObserver = state -> { - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withOneSignalSubscriptionId(state.getCurrent().getId()) - .build(); - Adapty.updateProfile(params, error -> { + Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.getCurrent().getId(), error -> { if (error != null) { // Handle the error } }); }; ``` ```diff showLineNumbers // PlayerID val osSubscriptionObserver = OSSubscriptionObserver { stateChanges -> stateChanges?.to?.userId?.let { playerId -> - val params = AdaptyProfileParameters.Builder() - .withOneSignalPlayerId(playerId) - .build() - - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("one_signal_player_id", playerId) { error -> if (error != null) { // Handle the error } - } } } ``` ```diff showLineNumbers // PlayerID OSSubscriptionObserver osSubscriptionObserver = stateChanges -> { OSSubscriptionState to = stateChanges != null ? stateChanges.getTo() : null; String playerId = to != null ? to.getUserId() : null; if (playerId != null) { - AdaptyProfileParameters params1 = new AdaptyProfileParameters.Builder() - .withOneSignalPlayerId(playerId) - .build(); - - Adapty.updateProfile(params1, error -> { + Adapty.setIntegrationIdentifier("one_signal_player_id", playerId, error -> { if (error != null) { // Handle the error } - }); } }; ``` ### Pushwoosh Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers - val params = AdaptyProfileParameters.Builder() - .withPushwooshHwid(Pushwoosh.getInstance().hwid) - .build() - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().hwid) { error -> if (error != null) { // Handle the error } } ``` ```diff showLineNumbers - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withPushwooshHwid(Pushwoosh.getInstance().getHwid()) - .build(); - - Adapty.updateProfile(params, error -> { + Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().getHwid(), error -> { if (error != null) { // Handle the error } }); ``` --- # File: migration-to-android-sdk-v3 --- --- title: "Migrar el SDK de Android de Adapty a la versión 3.0" description: "Migra al SDK de Android de Adapty v3.0 para obtener mejor rendimiento y nuevas funciones de monetización." --- Adapty SDK v3.0 introduces significant changes. This document outlines the key modifications to help you upgrade from v2.x to v3.0. We've made every effort to ensure backward compatibility, but some changes may require updates to your codebase. ## Renaming Paywalls to Placements In v3.0, we've renamed "paywalls" to "placements" throughout the SDK. This change reflects the concept that a "placement" is a specific location in your app where a paywall can be displayed. Here's a summary of the renaming: | Before (v2.x) | After (v3.0) | |---|---| | `getPaywalls()` | `getPlacements()` | | `Paywall` | `Placement` | | `paywallId` | `placementId` | | `paywallVariationId` | `placementVariationId` | ## Changes to `AdaptyPaywall` `AdaptyPaywall` in v3.0 contains the following properties: | Property | Type | Description | |---|---|---| | `placementId` | `String` | The ID of the placement in Adapty | | `variationId` | `String` | The ID of the variation in the A/B test | | `revision` | `Int` | The revision of the paywall | | `onboardingScreens` | `List` | The onboarding screens for the paywall | | `remoteConfig` | `AdaptyRemoteConfig?` | The remote config for the paywall | | `products` | `List` | The products associated with the paywall | ## Start Using Adapty Android SDK v3.0 Starting with v3.0, we recommend initializing the Adapty Android SDK in the `Application.onCreate()` method. This ensures the SDK is ready to use when your app launches. To migrate to Adapty Android SDK v3.0: 1. [Install Adapty Android SDK v3.x](sdk-installation-android). 2. Make the changes listed in the sections below. ## Changes to Adapty configuration ### Configuration builder changes The configuration builder was updated to allow for a more flexible setup. The new builder accepts an `appKey` parameter, which is your Adapty API key. The old builder accepted a `apiKey` parameter. ```kotlin title="App.kt" override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(false) .withCustomerUserId(customerUserId) .withIpAddressCollectionDisabled(false) .withIpAddressCollectionDisabled(false) .build() ) } ``` ```kotlin title="App.kt" override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(false) .withCustomerUserId(customerUserId) .withIpAddressCollectionDisabled(false) .build() ) } ``` ## Changes to paywalls ### Retrieving paywalls ```kotlin title="kotlin" Adapty.getPaywall(placementId, locale, loadTimeout) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // use paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```kotlin title="kotlin" Adapty.getPaywall(id, locale, fetchPolicy, loadTimeout) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // use paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` The `fetchPolicy` parameter has been removed from `getPaywall`. This parameter previously controlled whether the SDK fetched fresh data from the server or used a cached version. You can now control this via the configuration builder. ### Retrieving paywall products ```kotlin title="kotlin" Adapty.getPaywallProducts(paywall) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // use the products } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```kotlin title="kotlin" Adapty.getPaywallProducts(paywall) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // use the products } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ### Logging paywall display ```kotlin title="kotlin" Adapty.logShowPaywall(paywall) ``` ```kotlin title="kotlin" Adapty.logShowPaywall(paywall) ``` ## Changes to `AdaptyProfile` In v3.0, we've updated `AdaptyProfile`. The `customAttributes` property is now included in the `AdaptyProfile.CustomerUser` object rather than directly in the profile. ```kotlin title="kotlin" val profile: AdaptyProfile val customAttributes = profile.customerUser?.customAttributes ``` ```kotlin title="kotlin" val profile: AdaptyProfile val customAttributes = profile.customAttributes ``` ## Changes to `AdaptyPaywallProduct` In v3.0, we've updated `AdaptyPaywallProduct`. The `variationId` property has been removed and the `paywallVariationId` property has been renamed to `variationId`. ```kotlin title="kotlin" val product: AdaptyPaywallProduct val variationId = product.variationId ``` ```kotlin title="kotlin" val product: AdaptyPaywallProduct val variationId = product.paywallVariationId ``` ## Changes to `AdaptySubscriptionUpdateParameters` In v3.0, we've updated `AdaptySubscriptionUpdateParameters`. The `replacementMode` property has been renamed to `prorationMode`. ```kotlin title="kotlin" val params = AdaptySubscriptionUpdateParameters( oldSubVendorProductId, prorationMode ) ``` ```kotlin title="kotlin" val params = AdaptySubscriptionUpdateParameters( oldSubVendorProductId, replacementMode ) ``` ## Changes to `AdaptyPurchasedInfo` In v3.0, we've updated `AdaptyPurchasedInfo`. The response now includes the `profile` and, optionally, the `purchase` property. ```kotlin title="kotlin" Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized) { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value.profile val purchase = result.value.purchase } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```kotlin title="kotlin" Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized) { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value.profile } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` Adapty SDK v3.0 trae soporte para el nuevo e innovador [Adapty Paywall Builder](adapty-paywall-builder), la nueva versión de la herramienta no-code y fácil de usar para crear paywalls. Con su máxima flexibilidad y ricas capacidades de diseño, tus paywalls serán más efectivos y rentables. Los SDKs de Adapty se distribuyen como un BoM (Bill of Materials), lo que garantiza que las versiones del SDK de Adapty y del SDK de AdaptyUI en tu app sean siempre consistentes. Para migrar a v3.0, actualiza tu código de la siguiente manera: ```diff showLineNumbers dependencies { ... - implementation 'io.adapty:android-sdk:2.11.5' - implementation 'io.adapty:android-ui:2.11.3' + implementation platform('io.adapty:adapty-bom:3.0.4') + implementation 'io.adapty:android-sdk' + implementation 'io.adapty:android-ui' } ``` ```diff showLineNumbers dependencies { ... - implementation("io.adapty:android-sdk:2.11.5") - implementation("io.adapty:android-ui:2.11.3") + implementation(platform("io.adapty:adapty-bom:3.0.4")) + implementation("io.adapty:android-sdk") + implementation("io.adapty:android-ui") } ``` ```diff showLineNumbers //libs.versions.toml [versions] .. - adapty = "2.11.5" - adaptyUi = "2.11.3" + adaptyBom = "3.0.4" [libraries] .. - adapty = { group = "io.adapty", name = "android-sdk", version.ref = "adapty" } - adapty-ui = { group = "io.adapty", name = "android-ui", version.ref = "adaptyUi" } + adapty-bom = { module = "io.adapty:adapty-bom", version.ref = "adaptyBom" } + adapty = { module = "io.adapty:android-sdk" } + adapty-ui = { module = "io.adapty:android-ui" } //module-level build.gradle.kts dependencies { ... + implementation(libs.adapty.bom) implementation(libs.adapty) implementation(libs.adapty.ui) } ``` --- # End of Documentation _Generated on: 2026-07-24T13:01:55.605Z_ _Successfully processed: 41/41 files_ # API - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-07-24T13:01:55.608Z Total files: 18 --- # File: developer-cli --- --- title: "Developer CLI" description: "Descripción general del Developer CLI de Adapty." --- El **Adapty Developer CLI** es una herramienta de línea de comandos para gestionar tu cuenta de Adapty sin abrir el Dashboard. Proporciona las principales capacidades de configuración, accesibles desde tu terminal o entornos automatizados. **Qué puedes hacer con el CLI:** - Crear y configurar apps para iOS y Android en tu cuenta de Adapty - Definir niveles de acceso — los niveles de suscripción que tu app comprueba en tiempo de ejecución - Configurar productos y asociarlos a los IDs de App Store y Google Play - Crear paywalls y asignarles productos - Configurar placements para obtener paywalls a través del SDK :::link ¿Usas un asistente de IA o cliente MCP? Hay disponible un [skill de Adapty CLI](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) para ayudar a los LLMs a trabajar con el CLI. ::: --- # File: developer-cli-quickstart --- --- title: "Guía de inicio rápido para el Developer CLI de Adapty" description: "Configura tu cuenta de Adapty de principio a fin usando el Developer CLI: desde la creación de la app hasta un placement activo, en unos pocos comandos." --- :::link ¿Usas un asistente de IA? Hay una [skill del CLI de Adapty](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) disponible para ayudar a los LLMs a trabajar con el CLI. ::: El CLI de Adapty te permite configurar tu app por completo desde la línea de comandos. Úsalo como alternativa al [inicio rápido desde el dashboard](integrate-payments) si prefieres trabajar en la terminal o con [clientes MCP](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli). :::note Conectar Adapty a App Store Connect y Google Play todavía requiere una configuración puntual en el dashboard, que se explica en el paso 3. ::: Al terminar, tu app, nivel de acceso, producto, paywall y placement estarán todos visibles en el [Adapty Dashboard](https://app.adapty.io). ## 1. Instala el CLI \{#1-install-the-cli\} Requiere [Node.js](https://nodejs.org/en/download) 18 o posterior. Para instalar el CLI, ejecuta el comando: ```bash npm install -g adapty ``` O directamente: ```bash npx adapty auth login ``` ## 2. Autentícate \{#2-authenticate\} Ejecuta el comando de inicio de sesión para conectar el CLI con tu cuenta de Adapty. ```bash adapty auth login ``` El CLI abre una pestaña del navegador. Verifica que el código que aparece en la terminal coincida con el del navegador y haz clic en **Authorize**. La terminal confirmará cuando la autenticación se haya completado. ## 3. Crea tu app \{#3-create-your-app\} Una app en Adapty representa tu aplicación móvil. Una misma app de Adapty se conecta tanto a App Store como a Google Play, por lo que solo necesitas crear una, independientemente de en cuántos stores publiques. ```bash adapty apps create --title "My App" --platform ios --platform android --apple-bundle-id com.example.app --google-bundle-id com.example.app ``` ```bash adapty apps create --title "My App" --platform ios --apple-bundle-id com.example.app ``` ```bash adapty apps create --title "My App" --platform android --google-bundle-id com.example.app ``` El comando devuelve un ``. Usa este ID en todos los comandos siguientes. :::important Antes de continuar, conecta tu app a App Store Connect y Google Play en el Adapty Dashboard. Los IDs de producto de ambos stores son necesarios en el paso 5. - [Conectar App Store Connect](app-store-connection-configuration) - [Conectar Google Play](google-play-store-connection-configuration) ::: ## 4. Crea un nivel de acceso (opcional) \{#4-create-an-access-level-optional\} Los [niveles de acceso](access-level) controlan a qué puede acceder el usuario tras una compra. En lugar de comprobar si un usuario compró un producto concreto, tu app verifica si el usuario tiene un determinado nivel de acceso. Esto desacopla la lógica de tu app de los IDs de producto específicos. Con cada nueva app se crea automáticamente un nivel de acceso `premium`. **Para la mayoría de las apps puedes saltarte este paso.** Usa `premium` como ID de nivel de acceso en el paso 5. Solo ejecuta este comando si distintos productos desbloquean distintas funciones para diferentes grupos de usuarios; por ejemplo, si un suscriptor "Basic" y uno "Pro" tienen acceso a partes diferentes de la app. ```bash adapty access-levels create --app --sdk-id "pro" --title "Pro" ``` - `--sdk-id` es el identificador que usarás en el código de tu app para comprobar si una función debe estar disponible para el usuario (por ejemplo, `if user.hasAccessLevel("pro")`). Si te saltas este paso y usas el nivel de acceso por defecto, su `--sdk-id` es `premium`. - `--title` es una etiqueta de visualización para tu referencia en el Adapty Dashboard. El comando devuelve un ``. ## 5. Crea un producto \{#5-create-a-product\} En Adapty, un [producto](product) representa cualquier cosa que tu app vende: una suscripción o una compra única. Los elementos de App Store Connect y Google Play se pueden agrupar en un único producto de Adapty y gestionarse desde un solo lugar. Necesitarás los IDs de producto de cada store: el Apple product ID de App Store Connect, y el Android product ID y el base plan ID de Google Play Console. Consulta [Productos](quickstart-products) para saber dónde encontrarlos. Si te saltaste el paso 4, usa el `default_access_level.id` que devolvió el comando `apps create` en el paso 3 como tu ``. :::important Los IDs de producto del store que vinculas aquí (`--ios-product-id`, `--android-product-id`) no pueden modificarse una vez creados. Para usar IDs de producto del store diferentes, crea un nuevo producto. ::: ```bash adapty products create --app --title "My Product" --access-level-id --period monthly --ios-product-id --android-product-id --android-base-plan-id ``` ```bash adapty products create --app --title "My Product" --access-level-id --period monthly --ios-product-id ``` ```bash adapty products create --app --title "My Product" --access-level-id --period monthly --android-product-id --android-base-plan-id ``` El comando devuelve un ``. ## 6. Crea un paywall \{#6-create-a-paywall\} Un [paywall](paywalls) es el contenedor que alberga tus productos. En Adapty, los paywalls son la única forma de entregar productos a los usuarios. Todo producto debe estar en un paywall antes de poder aparecer en tu app. :::important Una vez que un paywall está vinculado a un placement, sus productos no se pueden cambiar. Para usar productos diferentes, crea un nuevo paywall y actualiza el placement para que apunte a él. ::: ```bash adapty paywalls create --app --title "My Paywall" --product-id ``` ```bash adapty paywalls create --app --title "My Paywall" --product-id --product-id ``` El comando devuelve un ``. ## 7. Crea un placement \{#7-create-a-placement\} Un [placement](placements) es el punto de tu app donde muestras un paywall. Lo único que tienes que escribir directamente en el código de tu app es el ID del placement. Todo lo demás —qué paywall mostrar y a qué usuarios— se gestiona desde el dashboard sin publicar una nueva versión de la app. `--developer-id` es la cadena que usarás más adelante en el código de tu app cuando le pidas a Adapty qué paywall mostrar en ese punto. Elige algo que describa la ubicación, como `"main"`, `"onboarding"` o `"settings"`. ```bash adapty placements create --app --title "Main" --developer-id "main" --audiences '[{"segment_ids":[],"paywall_id":"","priority":0}]' ``` El flag `--audiences` controla qué paywall se muestra a qué usuarios. El ejemplo anterior establece una audiencia por defecto única: todos los usuarios en este placement ven el mismo paywall. ## ¿Qué viene ahora? \{#whats-next\} Todas las entidades ya son visibles en el [Adapty Dashboard](https://app.adapty.io). A continuación: - [Diseña tu paywall](adapty-paywall-builder): usa el Paywall Builder sin código para añadir imágenes, diseño y textos al paywall que acabas de crear. - [Integra el SDK de Adapty](quickstart-sdk): añade el SDK a tu app para obtener y mostrar el placement. - Dirige distintos [segmentos](segments) de usuarios a diferentes paywalls: consulta [`placements update`](developer-cli-reference#adapty-placements-update) y [`segments list`](developer-cli-reference#adapty-segments-list) en la referencia completa. --- # File: developer-cli-authentication --- --- title: "Autenticación en el CLI para desarrolladores de Adapty" description: "Cómo autenticarse con el CLI para desarrolladores de Adapty." --- :::link ¿Usas un asistente de IA? Hay una [skill de Adapty CLI](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) disponible para ayudar a los LLMs a trabajar con el CLI. ::: El CLI requiere autenticación para llamar a la API de Adapty. ## Iniciar sesión \{#log-in\} Para iniciar sesión: 1. En tu terminal, ejecuta: ```bash adapty auth login ``` 2. El CLI muestra un código de verificación en formato `XXXX-XXXX` y abre el Adapty Dashboard en tu navegador. 3. En la página de autorización, confirma que el código coincide con lo que aparece en tu terminal. 4. Haz clic en **Authorize**. El navegador mostrará "CLI authorized! You can close this tab." 5. De vuelta en la terminal, el CLI confirma que estás autenticado. Si el código expira antes de que autorices, o si haces clic en **Deny**, ejecuta el siguiente comando de nuevo para reiniciar el proceso: ```bash adapty auth login ``` ## Gestionar la autenticación \{#manage-authentication\} ### Comprobar el estado de autenticación \{#check-authentication-status\} Para ver tu estado de autenticación actual, ejecuta: ```bash adapty auth status ``` Cuando estás autenticado, la salida muestra tu correo electrónico, un prefijo de token enmascarado y la ruta al archivo de configuración local: ``` Email: you@example.com Token: abcd1234**** Config: ~/.config/adapty/config.json ``` Cuando no estás autenticado: ``` Not authenticated. Run `adapty auth login`. ``` ### Verificar tu token \{#verify-your-token\} Para confirmar que tu token es válido y ver los detalles de tu cuenta, ejecuta: ```bash adapty auth whoami ``` A diferencia de `adapty auth status`, este comando realiza una solicitud en tiempo real al servidor para verificar el token. ### Cerrar sesión \{#log-out\} Para eliminar tus credenciales almacenadas localmente, ejecuta: ```bash adapty auth logout ``` Esto borra `~/.config/adapty/config.json`. El token sigue siendo válido en el servidor hasta que expira; si necesitas invalidarlo de inmediato, usa `adapty auth revoke` en su lugar. ### Revocar tu token \{#revoke-your-token\} Para invalidar el token en el servidor y eliminarlo localmente, ejecuta: ```bash adapty auth revoke ``` Úsalo cuando quieras invalidar un token por completo, por ejemplo, si crees que tus credenciales pueden haberse visto comprometidas. Tras revocarlo, ejecuta `adapty auth login` para volver a autenticarte. ## Errores de token \{#token-errors\} Si un token se revoca o deja de ser válido, los comandos del CLI devuelven un error 401. Para volver a autenticarte, ejecuta: ```bash adapty auth login ``` --- # File: developer-cli-reference --- --- title: "Referencia completa de la CLI para desarrolladores de Adapty" description: "Referencia completa de todos los comandos de la CLI para desarrolladores de Adapty." --- :::link ¿Usas un asistente de IA? Hay disponible una [skill de Adapty CLI](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) para ayudar a los LLMs a trabajar con la CLI. ::: Este artículo lista todos los comandos de la CLI de Adapty con sus argumentos, flags y valores aceptados. :::link Para configurar la autenticación y gestionar tokens, consulta [Autenticación](developer-cli-authentication). ::: ## Flags globales \{#global-flags\} Estos flags están disponibles en todos los comandos. | Flag | Descripción | |---|---| | `--json` | Mostrar la salida en JSON en lugar de texto formateado | | `--help` | Mostrar la ayuda del comando | Todos los comandos `list` también aceptan flags de paginación: | Flag | Por defecto | Descripción | |---|---|---| | `--page` | `1` | Número de página | | `--page-size` | `20` | Elementos por página (máx.: 100) | ## Apps \{#apps\} Gestiona las apps de tu cuenta de Adapty. Para la configuración desde el dashboard, consulta [App settings](general). ### adapty apps list \{#adapty-apps-list\} Lista todas las apps de tu cuenta de Adapty. ```bash adapty apps list ``` Acepta [flags de paginación](#global-flags). ### adapty apps get \{#adapty-apps-get\} Obtén los detalles de una app específica. ```bash adapty apps get ``` | Argumento | Descripción | |---|---| | `app-id` | ID de la app (UUID) | ### adapty apps create \{#adapty-apps-create\} Crea una nueva app. ```bash adapty apps create --title "My App" --platform ios --apple-bundle-id com.example.app ``` | Flag | Requerido | Descripción | |---|---|---| | `--title` | Sí | Título de la app | | `--platform` | Sí | Plataforma: `ios` o `android`. Repite para ambas: `--platform ios --platform android` | | `--apple-bundle-id` | Requerido con `--platform ios` | Bundle ID de Apple | | `--google-bundle-id` | Requerido con `--platform android` | Bundle ID de Google | ### adapty apps update \{#adapty-apps-update\} Actualiza una app existente. ```bash adapty apps update --title "New Name" ``` | Argumento | Descripción | |---|---| | `app-id` | ID de la app (UUID) | | Flag | Descripción | |---|---| | `--title` | Nuevo título de la app | | `--apple-bundle-id` | Nuevo bundle ID de Apple | | `--google-bundle-id` | Nuevo bundle ID de Google | Se requiere al menos un flag. `--platform` no se puede cambiar después de la creación. ## Niveles de acceso \{#access-levels\} ### adapty access-levels list \{#adapty-access-levels-list\} Lista todos los [niveles de acceso](access-level) de una app. ```bash adapty access-levels list --app ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | Acepta [flags de paginación](#global-flags). ### adapty access-levels get \{#adapty-access-levels-get\} Obtén los detalles de un [nivel de acceso](access-level) específico. ```bash adapty access-levels get --app ``` | Argumento | Descripción | |---|---| | `access-level-id` | ID del nivel de acceso (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | ### adapty access-levels create \{#adapty-access-levels-create\} Crea un nuevo [nivel de acceso](access-level). ```bash adapty access-levels create --app --sdk-id "pro" --title "Pro" ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--sdk-id` | Sí | Identificador utilizado en el código de la app para comprobar el acceso (por ejemplo, `"pro"` o `"premium"`) | | `--title` | Sí | Etiqueta de visualización en el Adapty Dashboard | ### adapty access-levels update \{#adapty-access-levels-update\} Actualiza un [nivel de acceso](access-level) existente. ```bash adapty access-levels update --app --title "Pro Access" ``` | Argumento | Descripción | |---|---| | `access-level-id` | ID del nivel de acceso (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | Sí | Nueva etiqueta de visualización | `--sdk-id` no se puede cambiar después de la creación. ## Productos \{#products\} ### adapty products list \{#adapty-products-list\} Lista todos los [productos](product) de una app. ```bash adapty products list --app ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | Acepta [flags de paginación](#global-flags). ### adapty products get \{#adapty-products-get\} Obtén los detalles de un [producto](product) específico. ```bash adapty products get --app ``` | Argumento | Descripción | |---|---| | `product-id` | ID del producto (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | ### adapty products create \{#adapty-products-create\} Crea un nuevo [producto](product). :::important Los IDs de producto de la store (`--ios-product-id`, `--android-product-id`, `--android-base-plan-id`) no se pueden cambiar después de la creación. Para usar IDs de producto de la store distintos, crea un nuevo producto. ::: ```bash adapty products create --app --title "Monthly" --access-level-id --period monthly --ios-product-id com.example.monthly ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | Sí | Título del producto | | `--access-level-id` | Sí | ID (UUID) del [nivel de acceso](access-level) que desbloquea este producto | | `--period` | Sí | Período de suscripción: `weekly`, `monthly`, `2_months`, `3_months`, `6_months`, `yearly`, `lifetime` | | `--ios-product-id` | Se requiere al menos una plataforma | ID del producto en App Store Connect | | `--android-product-id` | Se requiere al menos una plataforma | ID del producto en Google Play Console | | `--android-base-plan-id` | Requerido con `--android-product-id` salvo que `--period lifetime` | ID del plan base en Google Play Console | ### adapty products update \{#adapty-products-update\} Actualiza un [producto](product) existente. Los IDs de producto de la store (`--ios-product-id`, `--android-product-id`) no se pueden cambiar después de la creación y no están disponibles en este comando. Para usar IDs de producto de la store distintos, crea un nuevo producto. ```bash adapty products update --app --title "Monthly" --access-level-id ``` | Argumento | Descripción | |---|---| | `product-id` | ID del producto (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | No | Título del producto | | `--access-level-id` | No | ID (UUID) del [nivel de acceso](access-level) que desbloquea este producto | ## Paywalls \{#paywalls\} ### adapty paywalls list \{#adapty-paywalls-list\} Lista todos los [paywalls](paywalls) de una app. ```bash adapty paywalls list --app ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | Acepta [flags de paginación](#global-flags). ### adapty paywalls get \{#adapty-paywalls-get\} Obtén los detalles de un [paywall](paywalls) específico. ```bash adapty paywalls get --app ``` | Argumento | Descripción | |---|---| | `paywall-id` | ID del paywall (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | ### adapty paywalls create \{#adapty-paywalls-create\} Crea un nuevo [paywall](paywalls). ```bash adapty paywalls create --app --title "Default Paywall" --product-id ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | Sí | Título del paywall | | `--product-id` | Sí | ID (UUID) del [producto](product). Repite para varios productos: `--product-id --product-id ` | ### adapty paywalls update \{#adapty-paywalls-update\} Reemplaza todos los campos de un [paywall](paywalls) existente. :::important Una vez que un paywall está vinculado a un placement, sus productos no se pueden cambiar. Para usar productos distintos en un paywall en producción, crea un nuevo paywall y actualiza el placement para que apunte a él. ::: ```bash adapty paywalls update --app --title "Default Paywall" --product-id ``` Este comando reemplaza todos los campos del paywall, incluida la lista completa de productos. | Argumento | Descripción | |---|---| | `paywall-id` | ID del paywall (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | Sí | Título del paywall | | `--product-id` | Sí | ID (UUID) del [producto](product). Repite para varios productos: `--product-id --product-id ` | ### adapty paywalls placements \{#adapty-paywalls-placements\} Lista todos los [placements](placements) que actualmente usan un [paywall](paywalls) determinado. ```bash adapty paywalls placements --app ``` | Argumento | Descripción | |---|---| | `paywall-id` | ID del paywall (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | Usa este comando antes de reemplazar un paywall para ver qué placements se verían afectados. ## Placements \{#placements\} ### adapty placements list \{#adapty-placements-list\} Lista todos los [placements](placements) de una app. ```bash adapty placements list --app ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | Acepta [flags de paginación](#global-flags). ### adapty placements get \{#adapty-placements-get\} Obtén los detalles de un [placement](placements) específico. ```bash adapty placements get --app ``` | Argumento | Descripción | |---|---| | `placement-id` | ID del placement (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | La respuesta contiene un array `audiences`. Cada entrada es `{segment_ids, paywall_id, priority}`. La audiencia por defecto tiene `segment_ids: []` y el valor de prioridad más alto (se evalúa en último lugar). La salida formateada en texto también muestra un `Paywall ID` de nivel superior derivado de la audiencia por defecto, por comodidad. Con `--json` se devuelve la forma de la API sin modificar. ### adapty placements create \{#adapty-placements-create\} Crea un nuevo [placement](placements). ```bash adapty placements create --app --title "Main" --developer-id "main" --audiences '[{"segment_ids":[],"paywall_id":"","priority":0}]' ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | Sí | Título del placement | | `--developer-id` | Sí | Identificador de cadena utilizado en el código de la app para solicitar este [placement](placements) | | `--audiences` | Uno de los dos | Array JSON de entradas `{segment_ids, paywall_id, priority}`. Consulta [Forma de las audiencias](#audiences-shape) | | `--paywall-id` | Uno de los dos | **Obsoleto.** ID (UUID) del [paywall](paywalls). Se convierte en el cliente en una única audiencia por defecto | Pasa exactamente uno de `--audiences` o `--paywall-id`. Si se pasan ambos o ninguno, se produce un error. :::warning `--paywall-id` está obsoleto y se eliminará. Al usarlo, la CLI muestra una advertencia en stderr y convierte el valor en una audiencia por defecto. Para nuevas automatizaciones, usa `--audiences`. ::: ### adapty placements update \{#adapty-placements-update\} Reemplaza todos los campos de un [placement](placements) existente. ```bash adapty placements update --app --title "Main" --developer-id "main" --audiences '[{"segment_ids":[],"paywall_id":"","priority":0}]' ``` Este comando reemplaza todos los campos del placement, incluida la lista completa de audiencias. | Argumento | Descripción | |---|---| | `placement-id` | ID del placement (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | Sí | Título del placement | | `--developer-id` | Sí | Identificador de cadena utilizado en el código de la app para solicitar este [placement](placements) | | `--audiences` | Uno de los dos | Array JSON de entradas `{segment_ids, paywall_id, priority}`. Consulta [Forma de las audiencias](#audiences-shape) | | `--paywall-id` | Uno de los dos | **Obsoleto.** ID (UUID) del [paywall](paywalls). Reemplaza todas las audiencias con una única audiencia por defecto | :::warning Al usar `--paywall-id` se sobreescriben todas las audiencias del placement. Las audiencias específicas de segmento se eliminan. Para conservarlas, usa `--audiences` e incluye todas las entradas que quieras mantener. ::: #### Forma de las audiencias \{#audiences-shape\} El flag `--audiences` acepta un array JSON. Cada entrada tiene: | Campo | Tipo | Descripción | |---|---|---| | `segment_ids` | `string[]` | IDs de [segmento](segments) a los que se dirige esta audiencia. Longitud 0 o 1. Un array vacío marca la **audiencia por defecto**: el fallback para usuarios que no coinciden con ningún otro segmento | | `paywall_id` | `string` | ID (UUID) del [paywall](paywalls) que se muestra a los usuarios de esta audiencia | | `priority` | `number` | Basado en 0, único dentro del placement. Las audiencias se evalúan de menor a mayor; la audiencia por defecto debe tener el valor más alto | Un placement debe tener exactamente una audiencia por defecto. Ejemplo con una audiencia segmentada y una por defecto: ```bash adapty placements update --app --title "Main" --developer-id "main" \ --audiences '[{"segment_ids":[""],"paywall_id":"","priority":0},{"segment_ids":[],"paywall_id":"","priority":1}]' ``` Para reemplazar un paywall en varios placements sin perder el enrutamiento por segmento: 1. Encuentra los placements afectados: ```bash adapty paywalls placements --app ``` 2. Para cada uno, lee el array completo de `audiences`: ```bash adapty placements get --app --json ``` 3. Reemplaza los valores de `paywall_id` correspondientes en el cliente. 4. Escribe el payload modificado: ```bash adapty placements update --app --title "" --developer-id "<developer-id>" --audiences '<modified-payload>' ``` ## Segmentos \{#segments\} Los [segmentos](segments) son de solo lectura a través de la CLI. Créalos y edítalos en el [Adapty dashboard](https://app.adapty.io). Usa estos comandos para buscar los IDs de segmento al componer audiencias de placements. ### adapty segments list \{#adapty-segments-list\} Lista todos los [segmentos](segments) de una app. ```bash adapty segments list --app <app-id> ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | Acepta [flags de paginación](#global-flags). ### adapty segments get \{#adapty-segments-get\} Obtén los detalles de un [segmento](segments) específico. ```bash adapty segments get --app <app-id> <segment-id> ``` | Argumento | Descripción | |---|---| | `segment-id` | ID del segmento (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | La respuesta contiene `id`, `title` y `description`. Las reglas de filtro no están expuestas a través de esta API. ## Auth \{#auth\} | Comando | Descripción | |---|---| | `adapty auth login` | Autenticarse mediante el navegador usando el flujo de dispositivo | | `adapty auth logout` | Borrar las credenciales almacenadas localmente | | `adapty auth whoami` | Verificar el token con el servidor y mostrar información del usuario | | `adapty auth status` | Mostrar el estado de autenticación local sin hacer una llamada al servidor | | `adapty auth revoke` | Revocar el token en el servidor y borrarlo localmente | Consulta [Autenticación](developer-cli-authentication) para ver todos los detalles de cada comando. --- # File: getting-started-with-server-side-api --- --- title: "API del lado del servidor" description: "Empieza a usar la API del lado del servidor de Adapty para la gestión de suscripciones." --- :::tip ¿Usas un agente de programación con IA? Consulta [Verificar y conceder acceso a suscripciones desde tu backend](server-side-api-with-ai) para ver una guía completa en una sola página. ::: Con la API puedes: 1. Comprobar el estado de la suscripción de un usuario. 2. Activar la suscripción de un usuario con un nivel de acceso. 3. Obtener los atributos del usuario. 4. Establecer los atributos del usuario. 5. Obtener y actualizar configuraciones de paywall. <img src="/assets/shared/img/server.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note Para registrar eventos de suscripción, usa la integración [Webhook](webhook) en Adapty o intégrala directamente con tu servicio existente. ::: ## Caso 1: Sincronizar suscriptores entre web y móvil \{#case-1-sync-subscribers-between-web-and-mobile\} Si utilizas proveedores de pago web como Stripe, ChargeBee u otros, puedes sincronizar a tus suscriptores fácilmente. Así es como funciona: 1. <InlineTooltip tooltip="Asigna un ID único a cada usuario">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) y [Unity](unity-identifying-users)</InlineTooltip>. 2. [Comprueba su estado de suscripción](api-adapty/operations/getProfile) mediante la API. 3. Si el usuario tiene un plan freemium, muéstrale un paywall en tu sitio web. 4. Tras un pago exitoso, [actualiza el estado de la suscripción](api-adapty/operations/setTransaction) en Adapty mediante la API. 5. Tus suscriptores se mantendrán automáticamente sincronizados con tu app móvil. ## Caso 2: Conceder una suscripción \{#case-2-grant-a-subscription\} :::note Por razones de seguridad, no puedes conceder una suscripción a través del SDK. ::: Si vendes a través de tu propia tienda online, Amazon Appstore, Microsoft Store o cualquier otra plataforma que no sea Google Play ni App Store, tendrás que sincronizar esas transacciones con Adapty para proporcionar acceso y registrar la transacción en los análisis. 1. <InlineTooltip tooltip="Asigna un ID único a cada usuario">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), y [Unity](unity-identifying-users)</InlineTooltip>. 2. [Configura una store personalizada para tus productos en el Adapty Dashboard](custom-store). 3. Sincroniza la transacción con Adapty usando la solicitud de API [Set transaction](api-adapty/operations/setTransaction). ## Caso 3: Otorgar un nivel de acceso \{#case-3-grant-an-access-level\} Supongamos que estás ejecutando una promoción que ofrece una prueba gratuita de 7 días y quieres que la experiencia sea coherente en todas las plataformas. Para sincronizarlo con la app móvil: 1. <InlineTooltip tooltip="Asigna un ID único a cada usuario">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), y [Unity](unity-identifying-users)</InlineTooltip>. 2. Usa la API para [otorgar acceso premium](api-adapty/operations/grantAccessLevel) durante 7 días. Después de los 7 días, los usuarios que no se suscriban pasarán al nivel gratuito. ## Caso 4: Sincronizar propiedades y atributos personalizados de los usuarios \{#case-4-sync-users-properties-and-custom-attributes\} Si tienes atributos personalizados para tus usuarios —como el número de palabras aprendidas en una app de aprendizaje de idiomas—, también puedes sincronizarlos. 1. <InlineTooltip tooltip="Asigna un ID único a cada usuario">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), y [Unity](unity-identifying-users)</InlineTooltip>. 2. [Actualiza el atributo](api-adapty/operations/updateProfile) mediante la API o el SDK. Estos atributos personalizados se pueden usar para crear segmentos y ejecutar pruebas A/B. ## Caso 5: Gestionar configuraciones de paywall \{#case-5-manage-paywall-configurations\} Puedes [actualizar Remote Configs en paywalls](api-adapty/operations/updatePaywall) para ajustar dinámicamente el aspecto y comportamiento de tu paywall sin necesidad de redesplegar tu app. --- **Próximos pasos:** - Continúa con la [autorización para la API del lado del servidor](ss-authorization) - Solicitudes: - [Obtener perfil](api-adapty/operations/getProfile) - [Crear perfil](api-adapty/operations/createProfile) - [Actualizar perfil](api-adapty/operations/updateProfile) - [Eliminar perfil](api-adapty/operations/deleteProfile) - [Conceder nivel de acceso](api-adapty/operations/grantAccessLevel) - [Revocar nivel de acceso](api-adapty/operations/revokeAccessLevel) - [Establecer transacción](api-adapty/operations/setTransaction) - [Validar compra, proporcionar nivel de acceso al cliente e importar su historial de transacciones](api-adapty/operations/validateStripePurchase) - [Añadir identificadores de integración](api-adapty/operations/setIntegrationIdentifiers) - [Obtener paywall](api-adapty/operations/getPaywall) - [Listar paywalls](api-adapty/operations/listPaywalls) - [Actualizar paywall](api-adapty/operations/updatePaywall) --- # File: ss-authorization --- --- title: "Autorización y formato de solicitud de la API del servidor" description: "" --- ## Autorización \{#authorization\} Las solicitudes a la API deben autenticarse con tu clave de API secreta o pública como cabecera de autorización. Puedes encontrarlas en [**App Settings**](https://app.adapty.io/settings/general). El formato del valor es `Api-Key {your-secret-api-key}`, por ejemplo, `Api-Key secret_live_...`. :::important Las claves de API son específicas de cada app. Si tienes varias apps, asegúrate de usar claves distintas para cada una. ::: ## Formato de solicitud \{#request-format\} **Cabeceras** Las solicitudes a la API del servidor requieren cabeceras específicas y un cuerpo en JSON. Usa los detalles a continuación para estructurar tus solicitudes. | **Cabecera** | **Descripción** | | --------------------------- | ------------------------------------------------------------ | | **adapty-profile-id** | <p>El ID de perfil de Adapty del usuario. Visible en el campo **Adapty ID** en [Adapty Dashboard -> **Profiles**](https://app.adapty.io/profiles/users) -> página del perfil específico. </p><p>Es intercambiable con **adapty-customer-user-id**; usa cualquiera de los dos.</p> | | **adapty-customer-user-id** | <p>El ID del usuario en tu sistema. Visible en el campo **Customer user ID** en [Adapty Dashboard -> **Profiles**](https://app.adapty.io/profiles/users) -> página del perfil específico. </p><p>Es intercambiable con **adapty-profile-id**; usa cualquiera de los dos.</p><p> ⚠️ Solo funciona si <InlineTooltip tooltip="identificas usuarios en tu app">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), y [Unity](unity-identifying-users)</InlineTooltip> en el código de tu app usando el SDK de Adapty.</p> | | **adapty-platform** | (opcional) Especifica la plataforma del dispositivo en el que está instalada la app. Recomendamos establecer este parámetro en las solicitudes [Crear perfil](api-adapty/operations/createProfile) y [Actualizar perfil](api-adapty/operations/updateProfile) al modificar el objeto [Installation Meta](server-side-api-objects#installation-meta), ya que depende del dispositivo que utilice el usuario y un mismo usuario puede tener varios dispositivos. Valores posibles: `iOS`, `macOS`, `iPadOS`, `visionOS`, `Android` o `web`. | | **Content-Type** | Establécelo en `application/json` para que la API procese la solicitud. | **Cuerpo** La API espera un cuerpo en formato JSON con los datos necesarios para la solicitud. ## Límites de frecuencia \{#rate-limits\} Para evitar la limitación de velocidad, asegúrate de que el número de solicitudes (por app) se mantenga por debajo de 40 000 por minuto. Si se supera este límite, el sistema puede ralentizarse o bloquear temporalmente más solicitudes para mantener un rendimiento óptimo para todos los usuarios. ## Rotar claves de API \{#rotate-api-keys\} Si necesitas rotar las claves de API secretas: 1. En **Settings → General**, haz clic en **Generate new key** y luego en el icono de papelera junto a la clave antigua. 2. Actualiza la clave utilizada en tu app. --- **Próximos pasos — solicitudes:** - [Obtener perfil](api-adapty/operations/getProfile) - [Crear perfil](api-adapty/operations/createProfile) - [Actualizar perfil](api-adapty/operations/updateProfile) - [Eliminar perfil](api-adapty/operations/deleteProfile) - [Conceder nivel de acceso](api-adapty/operations/grantAccessLevel) - [Revocar nivel de acceso](api-adapty/operations/revokeAccessLevel) - [Establecer transacción](api-adapty/operations/setTransaction) - [Validar compra, proporcionar nivel de acceso al cliente e importar su historial de transacciones](api-adapty/operations/validateStripePurchase) - [Obtener paywall](api-adapty/operations/getPaywall) - [Listar paywalls](api-adapty/operations/listPaywalls) - [Actualizar paywall](api-adapty/operations/updatePaywall) --- # File: server-side-api-specs --- --- title: "Solicitudes a la API del lado del servidor" description: "Explora las especificaciones de la API del lado del servidor de Adapty para una integración avanzada." --- La API del lado del servidor de Adapty te permite acceder y gestionar tus datos de suscripción de forma programática, facilitando la integración con tus servicios e infraestructura existentes. Ya sea que estés sincronizando datos entre plataformas, otorgando niveles de acceso o validando compras en Stripe, esta API te proporciona las herramientas para mantener tus sistemas sincronizados y a tus usuarios activos. ## Colección y entorno de Postman \{#postman-collection-and-environment\} Para simplificar el uso de nuestra API del lado del servidor, hemos preparado una colección de Postman y un archivo de entorno que puedes descargar e importar en Postman. - **Colección de solicitudes**: Incluye todas las solicitudes disponibles en la API del lado del servidor de Adapty. Ten en cuenta que usa variables que puedes definir en el entorno. - **Entorno**: Contiene una lista de variables donde puedes definir los valores una sola vez. Hemos preparado un entorno unificado para la API del lado del servidor, la API web y la API de exportación de analíticas para facilitarte las cosas. Una vez que actives este entorno, Postman sustituirá automáticamente los valores de las variables definidas en tus solicitudes. :::tip [Descarga la colección y el entorno](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/Adapty_server_side_API_postman_collection.zip) ::: Para más información sobre cómo importar una colección y un entorno en Postman, consulta la [documentación de Postman](https://learning.postman.com/docs/getting-started/importing-and-exporting/importing-data/). ### Variables utilizadas \{#variables-used\} Hemos creado un entorno unificado para la API del lado del servidor, la API web y la API de exportación de analíticas para simplificar tu flujo de trabajo. A continuación se muestran las variables específicas de la API del lado del servidor: | Variable | Descripción | Valor de ejemplo | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------- | | secret_api_key | Puedes encontrarla en el campo **Secret key** en [**App settings**](https://app.adapty.io/settings/general). | `secret_live_Pj1P1xzM.2CvSvE1IalQRFjsWy6csBVNpH33atnod` | | adapty-customer-user-id | El ID de usuario utilizado en tu sistema. En el Adapty Dashboard, puedes encontrarlo en el campo **Customer user ID** del perfil. | `john.doe@example.com` | | adapty-profile-id | El ID de usuario asignado en Adapty. En el Adapty Dashboard, puedes encontrarlo en el campo **Adapty ID** del perfil. | `3286abd3-48b0-4e9c-a5f6-ac0a006333a6` | | Adapty-platform | La plataforma utilizada por el usuario en tu app. Valores posibles: `iOS`, `macOS`, `iPadOS`, `visionOS`, `Android`, `web`. | `iOS` | | stripe_token | Token de un objeto de Stripe que representa una compra única, como una suscripción (`sub_XXX`) o un Payment Intent (`pi_XXX`). | `sub_1JY8xLLy6P12345a` | **Próximos pasos — Solicitudes:** - [Obtener perfil](api-adapty/operations/getProfile) - [Crear perfil](api-adapty/operations/createProfile) - [Actualizar perfil](api-adapty/operations/updateProfile) - [Eliminar perfil](api-adapty/operations/deleteProfile) - [Conceder nivel de acceso](api-adapty/operations/grantAccessLevel) - [Revocar nivel de acceso](api-adapty/operations/revokeAccessLevel) - [Establecer transacción](api-adapty/operations/setTransaction) - [Validar compra, proporcionar nivel de acceso al cliente e importar su historial de transacciones](api-adapty/operations/validateStripePurchase) - [Añadir identificadores de integración](api-adapty/operations/setIntegrationIdentifiers) - [Obtener paywall](api-adapty/operations/getPaywall) - [Listar paywalls](api-adapty/operations/listPaywalls) - [Actualizar paywall](api-adapty/operations/updatePaywall) - [Crear transacción de moneda virtual](api-adapty/operations/createVirtualCurrencyTransaction) - [Listar transacciones de moneda virtual](api-adapty/operations/listVirtualCurrencyTransactions) - [Listar saldos de moneda virtual](api-adapty/operations/listVirtualCurrencyBalances) --- # File: api-guides --- --- title: "Guías de API" description: "Aprende a realizar tareas específicas usando la API del lado del servidor." --- En esta sección encontrarás guías que cubren diferentes casos de uso y te ayudan a realizar tareas específicas con la API del lado del servidor y el SDK de Adapty. <CustomDocCardList /> --- # File: sync-subscribers-from-web --- --- title: "Sincronizar compras entre web y móvil" description: "Sincroniza suscriptores en web y móvil." --- Si tus usuarios pueden comprar un producto en tu **sitio web**, puedes mantener sus niveles de acceso sincronizados automáticamente con tu **aplicación móvil**. En esta guía aprenderás cómo hacerlo usando la API y el SDK de Adapty. #### Caso de uso de ejemplo \{#sample-use-case\} Digamos que en tu app, los usuarios pueden registrarse con un plan freemium tanto en móvil como en web. Les permites actualizar al plan Premium en tu web a través de Stripe o Chargebee. Una vez que un usuario se suscribe en la web, quieres que obtenga acceso Premium en la app móvil de inmediato, sin esperar ni volver a iniciar sesión. De eso se encarga Adapty. ## Paso 1. Identificar usuarios \{#step-1-identify-users\} Adapty usa `customer_user_id` para identificar usuarios en todas las plataformas. Debes crear este ID una sola vez y pasarlo tanto al SDK móvil como al backend web. ### Registro desde la web \{#sign-up-from-web\} Cuando tus usuarios se registren en tu sitio web, necesitas crear un perfil para ellos en Adapty usando la API del lado del servidor. Consulta la referencia del método [aquí](api-adapty/operations/createProfile). ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/profile/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: YOUR_CUSTOMER_USER_ID' ``` ### Registrarse desde la app \{#sign-up-from-app\} Cuando tus usuarios se registran por primera vez desde la app, puedes pasar su customer user ID durante la activación del SDK, o si ya activaste el SDK antes de la etapa de registro, usa el método `identify` para crear un nuevo perfil y asignarle un customer user ID. :::important Si identificas a nuevos usuarios después de la activación del SDK, primero el SDK creará un perfil anónimo, ya que no puede funcionar sin ningún perfil. Luego, cuando identifiques al usuario y le asignes un nuevo customer user ID, se creará un nuevo perfil. Este comportamiento es completamente normal y no afectará a la precisión de los análisis. Lee más [aquí](ios-quickstart-identify). ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Unique for each user } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user if (error == null) { // successful identify } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Unique for each user .onSuccess { // successful identify } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## Paso 2. Verificar el estado de la suscripción mediante la API \{#step-2-check-subscription-status-via-api\} Cuando un usuario inicia sesión en tu sitio web, obtén su perfil de Adapty usando la API. Si el usuario no tiene una suscripción activa, puedes mostrarle un paywall. Consulta la referencia del método [aquí](api-adapty/operations/getProfile). ```bash curl --request GET \ --url https://api.adapty.io/api/v2/server-side-api/profile/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'adapty-customer-user-id: YOUR_USER_ID' \ ``` ## Paso 3. Mostrar un paywall en tu sitio web \{#step-3-display-a-paywall-on-your-website\} En tu sitio web, muestra un paywall a los usuarios freemium. Puedes usar cualquier proveedor de pagos (Stripe, Chargebee, LemonSqueezy, etc.). ## Paso 4. Actualizar el estado de la suscripción en Adapty \{#step-4-update-subscription-status-in-adapty\} Una vez completado el pago en tu sitio web, llama a la API de Adapty para actualizar el nivel de acceso del usuario según el producto que haya comprado. Consulta la referencia del método [aquí](api-adapty/operations/grantAccessLevel). ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/purchase/profile/grant/access-level/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: YOUR_USER_ID' \ --data '{ "access_level_id": "YOUR_ACCESS_LEVEL" }' ``` ## Paso 5. Sincronizar el estado en la app \{#step-5-sync-status-in-the-app\} Cuando el usuario abra tu app, recupera el perfil actualizado y desbloquea las funciones de pago. Necesitas obtener su perfil o sincronizarlo automáticamente. Luego, obtén el nivel de acceso a partir de él. A continuación puedes ver cómo obtener el perfil y comprobar su estado. Para más detalles, ve [aquí](ios-check-subscription-status). <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // check the access if (profile.getAccessLevels().get("YOUR_ACCESS_LEVEL") != null && profile.getAccessLevels().get("YOUR_ACCESS_LEVEL").getIsActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access if (profile.AccessLevels["YOUR_ACCESS_LEVEL"]?.IsActive ?? false) { // grant access to premium features } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> </Tabs> --- # File: sync-purchases-from-custom-stores --- --- title: "Sincronizar transacciones de stores personalizados" description: "Sincroniza transacciones de stores personalizados con Adapty para gestionar accesos y hacer seguimiento de ingresos." --- Si vendes suscripciones o compras in-app a través de **stores personalizadas** como Amazon Appstore, Microsoft Store o tu propia plataforma de pago, puedes sincronizar esas transacciones con Adapty para gestionar automáticamente los niveles de acceso y hacer seguimiento de los ingresos en tu analítica. En esta guía aprenderás a conectar las compras de stores personalizadas con Adapty usando el SDK y la API. #### Ejemplo de uso \{#sample-use-case\} Supongamos que distribuyes tu app en Amazon Appstore, o que tienes tu propia tienda web para ventas directas. Cuando un usuario completa una compra en estas plataformas, quieres: - Concederle acceso automáticamente a las funciones premium de tu app móvil - Registrar la transacción en los análisis de Adapty junto con los ingresos de App Store y Google Play - Activar integraciones y webhooks igual que con cualquier otra suscripción Eso es exactamente lo que esta integración te permite conseguir. ## Paso 1. Identifica a los usuarios \{#step-1-identify-users\} Adapty usa `customer_user_id` para identificar usuarios entre plataformas. Necesitas crear este ID una sola vez y pasárselo tanto al SDK como a tu backend web. Cuando tus usuarios se registren por primera vez desde la app, puedes pasar su customer user ID durante la activación del SDK, o si ya activaste el SDK de Adapty antes del registro, usa el método `identify` para crear un nuevo perfil y asignarle un customer user ID. :::important Si identificas nuevos usuarios después de la activación del SDK, el SDK creará primero un perfil anónimo (no puede funcionar sin uno). Cuando llames a `identify` con un customer user ID, se creará un nuevo perfil. Este comportamiento es normal y no afectará a la precisión de los análisis. Lee más [aquí](ios-quickstart-identify). ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Unique for each user } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user if (error == null) { // successful identify } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Unique for each user .onSuccess { // successful identify } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## Paso 2. Crea productos en una store personalizada en el Adapty Dashboard \{#step-2-create-products-in-a-custom-store-in-adapty-dashboard\} Para que Adapty relacione las transacciones de la store personalizada con tus productos, debes añadir los productos y configurar los detalles de la store personalizada para cada uno. 1. Ve a [**Products**](https://app.adapty.io/settings/general) desde el menú lateral del Adapty Dashboard y haz clic en **Create product**. O haz clic en un producto existente para editarlo. 2. Asegúrate de haber seleccionado el [nivel de acceso](access-level) que quieres conceder a los usuarios que compren el producto. 3. Haz clic en **+** y selecciona **Add a custom store**. 4. Haz clic en **Create new custom store**. 5. Dale un nombre a tu store (por ejemplo, "Amazon Appstore", "Microsoft Store" o "Web Store") y un ID. Haz clic en **Create custom store**. 6. Luego, haz clic en **Save changes** para vincular el producto al store personalizado. 7. Introduce el **Store product ID** del producto para asociarlo con algún producto de ese store. Después, haz clic en **Save**. ## Paso 3. Sincroniza las transacciones mediante la API \{#step-3-sync-transactions-via-api\} Cuando se completa una compra en tu store personalizado, debes sincronizarla con Adapty mediante la API de servidor. Esta llamada a la API: - Registra la transacción en Adapty - Otorga el nivel de acceso correspondiente al usuario - Activa las integraciones y webhooks que hayas configurado - Hace que la transacción aparezca en tus analíticas Consulta la referencia completa del método [aquí](api-adapty/operations/setTransaction). ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/purchase/set/transaction/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: YOUR_CUSTOMER_USER_ID' \ --data '{ "purchase_type": "PRODUCT_PERIOD", "store": "YOUR_CUSTOM_STORE", "environment": "production", "store_product_id": "YOUR_STORE_PRODUCT_ID", "store_transaction_id": "STORE_TRANSACTION_ID", "store_original_transaction_id": "ORIGINAL_TRANSACTION_ID", "price": { "country": "COUNTRY_CODE", "currency": "CURRENCY_CODE", "value": "YOUR_PRICE" }, "purchased_at": "2024-01-15T10:30:00Z" }' ``` :::important Parámetros importantes: - **store**: El ID de tu store personalizado del Paso 2 - **store_product_id**: El ID de producto del store del Paso 2 - **store_transaction_id**: Un identificador único para esta transacción - **purchased_at**: Marca de tiempo en formato ISO 8601 de cuándo se realizó la compra - **price**: El importe pagado por el usuario ::: ## Paso 4. Verificar el acceso en la app \{#step-4-verify-access-in-the-app\} Una vez sincronizada la transacción, el perfil del usuario se actualizará automáticamente con el nuevo nivel de acceso. Cuando el usuario abra tu app, obtén su perfil para comprobar el estado de su suscripción y desbloquear las funciones premium. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // check the access if (profile.getAccessLevels().get("YOUR_ACCESS_LEVEL") != null && profile.getAccessLevels().get("YOUR_ACCESS_LEVEL").getIsActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access if (profile.AccessLevels["YOUR_ACCESS_LEVEL"]?.IsActive ?? false) { // grant access to premium features } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> </Tabs> --- # File: grant-access-level --- --- title: "Otorgar niveles de acceso manualmente" description: "Desbloquea funciones de pago manualmente para usuarios o grupos de usuarios específicos" --- Si necesitas **desbloquear manualmente funciones premium** para usuarios o grupos de usuarios específicos, puedes hacerlo mediante la API de Adapty. Esto resulta útil para campañas promocionales, acceso para inversores o casos especiales de soporte al cliente. En esta guía aprenderás a identificar usuarios y concederles niveles de acceso de forma programática. #### Casos de uso - **Códigos promocionales**: Cuando un usuario introduce un código promocional válido en tu app, concédele acceso automático a las funciones premium. - **Acceso para inversores/beta testers**: Proporciona acceso premium a inversores o beta testers comprobando sus atributos personalizados. :::note **Códigos promocionales de Google Play**: Una compra realizada canjeando un código promocional de Google Play puede llegar sin un `orderId`. La validación de compras únicas (no suscripciones) de Adapty requiere un `orderId`, por lo que estos canjes no se validan ni se conceden automáticamente. Concede el acceso manualmente siguiendo los pasos que se indican a continuación — la API Server-Side no depende de un `orderId`. ::: ## Paso 1. Identifica a los usuarios \{#step-1-identify-users\} Adapty usa `customer_user_id` para identificar a los usuarios en todas las plataformas y dispositivos. Esto es fundamental para garantizar que los usuarios conserven su acceso después de reinstalar la app o cambiar de dispositivo. Solo necesitas crear este ID una vez. Cuando los usuarios se registran desde la app, puedes pasarles el customer user ID durante la activación del SDK, o usar el método `identify` si el SDK se activó antes del registro. :::important Si identificas nuevos usuarios después de la activación del SDK, el SDK primero creará un perfil anónimo (no puede funcionar sin uno). Cuando llames a `identify` con un customer user ID, se creará un nuevo perfil. Este comportamiento es normal y no afectará la precisión de las métricas. Lee más [aquí](ios-quickstart-identify). ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Unique for each user } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user if (error == null) { // successful identify } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Unique for each user .onSuccess { // successful identify } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## Paso 2. Otorgar nivel de acceso mediante la API \{#step-2-grant-access-level-via-api\} Una vez que el usuario está identificado con un `customer_user_id`, puedes otorgarle niveles de acceso mediante la API del servidor. Esta llamada a la API concede el nivel de acceso al usuario para que pueda acceder a las funciones de pago sin necesidad de realizar un pago real. Consulta la referencia completa del método [aquí](api-adapty/operations/grantAccessLevel). :::tip Puedes controlar el acceso de los usuarios añadiendo un atributo personalizado (por ejemplo, Beta tester o Investor) en el Adapty Dashboard. Cuando se inicie tu app, [comprueba este atributo en el perfil del usuario](subscription-status) para conceder acceso automáticamente. Para actualizar el acceso, simplemente cambia el atributo en el dashboard. ::: ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/purchase/profile/grant/access-level/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: CUSTOMER_USER_ID' \ --data '{ "access_level_id": "YOUR_ACCESS_LEVEL" }' ``` ## Paso 3. Verifica el acceso en la app \{#step-3-verify-access-in-the-app\} Tras conceder el acceso mediante la API, el perfil del usuario se actualizará automáticamente. Obtén su perfil para comprobar el estado de su suscripción y desbloquear las funciones premium. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access if profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // check the access if (profile.getAccessLevels().get("YOUR_ACCESS_LEVEL_ID") != null && profile.getAccessLevels().get("YOUR_ACCESS_LEVEL_ID").getIsActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access if (profile.AccessLevels["YOUR_ACCESS_LEVEL_ID"]?.IsActive ?? false) { // grant access to premium features } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive == true) { // grant access to premium features } } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> </Tabs> --- # File: web-api --- --- title: Adapty Web API description: "" --- La Web API es una extensión de la API del lado del servidor diseñada para usarse con aplicaciones web. Te permite recuperar el paywall correcto mediante su placement ID asociado y registrar las visualizaciones de paywall para un seguimiento preciso de las conversiones. Esto te ayuda a aprovechar las pruebas A/B y la personalización de paywalls disponibles en Adapty, además de identificar qué paywalls funcionan mejor. ## Caso de uso: Registrar una transacción desde tu app web y vincularla al paywall utilizado \{#use-case-record-a-transaction-from-your-web-app-and-link-it-to-the-used-paywall\} Supongamos que vendes productos en tu app web. Necesitas mostrar un paywall a tus usuarios, permitirles comprar un producto y luego añadir los detalles de la transacción a Adapty. Es fundamental vincular estas transacciones a los paywalls específicos a través de los cuales el usuario realizó la compra, para que tus análisis reflejen datos precisos. Esto se puede lograr fácilmente usando la API de Adapty. ### Requisitos previos \{#prerequisites\} 1. [Crea los productos](create-product) que usarás en el paywall dentro del Adapty Dashboard. 2. [Crea el paywall](create-paywall) en el Adapty Dashboard. [Usa Remote Config](customize-paywall-with-remote-config) para diseñar tu paywall web. 3. [Configura un placement](create-placement) y vincúlalo al paywall en el Adapty Dashboard. ### Pasos con la API de Adapty \{#steps-with-adapty-api\} 1. **Crear un perfil de usuario:** Adapty necesita tener un perfil antes de solicitar un paywall para poder personalizarlo según el usuario que lo solicita. Usa la solicitud [Create profile](api-adapty/operations/createProfile) para crear un perfil de usuario. 2. **Obtener y mostrar el paywall:** Cuando el usuario llegue al placement de tu app web donde debe mostrarse el paywall, usa la solicitud [Get paywall](api-web/operations/getPaywall) para recuperarlo mediante el [placement ID](placements). Como resultado, obtendrás un paywall para la [audiencia](audience) correspondiente a tu usuario. Muestra el paywall con tu código, usando los productos devueltos y (opcionalmente) el [Remote Config](customize-paywall-with-remote-config) de ese paywall. 3. **Registrar la visualización del paywall:** Usa [Record paywall view](api-web/operations/recordPaywallView) para registrar la visualización del paywall en Adapty y garantizar que tus análisis reflejen el evento con precisión. Esto es fundamental para hacer un seguimiento correcto de las conversiones. 4. **Registrar la compra:** Si el usuario completa una compra, envía los detalles de la transacción a Adapty mediante la API de Adapty. Incluye el **variation ID** en esta solicitud para vincular la transacción al paywall específico que se mostró. Como referencia, consulta nuestra página sobre [cómo asociar paywalls con transacciones en apps móviles](report-transactions-observer-mode): el mismo enfoque se aplica a las apps web. 5. **Añadir datos de atribución de marketing (si aplica):** Si dispones de datos de atribución de marketing (por ejemplo, detalles de campaña o anuncio), usa [Add attribution](api-web/operations/addAttribution) para incorporarlos al perfil de usuario y enriquecer los análisis y conocer mejor el rendimiento de tus anuncios en Adapty. --- **A continuación:** - Continúa con [Autorización de la Web API](web-api-authorization) - Solicitudes: - [Add attribution](api-web/operations/addAttribution) - [Get paywall](api-web/operations/getPaywall) - [Record paywall view](api-web/operations/recordPaywallView) --- # File: web-api-authorization --- --- title: Autorización y formato de solicitud para la Web API description: "" --- ## Autorización \{#authorization\} Las solicitudes a la API deben autenticarse con tu clave pública de API como cabecera **Authorization** con el valor `Api-Key {your_public_api_key}`, por ejemplo, `Api-Key public_live_...`. Puedes encontrar esta clave en el [Adapty Dashboard -> **App Settings** -> pestaña **General** -> sección **API keys**](https://app.adapty.io/settings/general). :::important Las claves de API son específicas de cada aplicación. Si tienes varias aplicaciones, asegúrate de usar claves distintas para cada una de ellas. ::: ## Formato de solicitud \{#request-format\} - **Cabecera Content-Type**: Establece la cabecera **Content-Type** en `application/json` para que la API procese tu solicitud. - **Body**: La API espera que la solicitud utilice el body en formato JSON. --- # File: web-api-requests --- --- title: " Solicitudes de Web API" description: "" --- La API del lado del servidor de Adapty te permite acceder y gestionar tus datos de suscripción de forma programática, facilitando la integración con tus servicios e infraestructura existentes. Ya sea que estés sincronizando datos entre plataformas, otorgando niveles de acceso o validando compras en Stripe, esta API ofrece las herramientas para mantener tus sistemas sincronizados y a tus usuarios activos. ## Colección y entorno de Postman \{#postman-collection-and-environment\} Para simplificar el uso de nuestra web API, hemos preparado una colección de Postman y un archivo de entorno que puedes descargar e importar en Postman. - **Colección de solicitudes**: Incluye todas las solicitudes disponibles en la web API de Adapty. Ten en cuenta que utiliza variables que puedes definir en el entorno. - **Entorno**: Contiene una lista de variables donde puedes definir los valores una sola vez. Hemos preparado un entorno unificado para la API del lado del servidor, la web API y la API de exportación de analíticas para facilitarte el trabajo. Una vez que actives este entorno, Postman sustituirá automáticamente los valores de las variables definidas en tus solicitudes. :::tip [Descarga la colección y el entorno](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/Adapty_Web_API_postman_collection.zip) ::: Para más información sobre cómo importar una colección y un entorno en Postman, consulta la [documentación de Postman](https://learning.postman.com/docs/getting-started/importing-and-exporting/importing-data/). ## Variables utilizadas \{#variables-used\} Hemos creado un entorno unificado para la API del lado del servidor, la web API y la API de exportación de analíticas para simplificar tu flujo de trabajo. A continuación se muestran las variables específicas de la web API: | Variable | Descripción | Valor de ejemplo | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------- | | public_api_key | Puedes encontrarla en el campo **Public SDK key** en [**App settings**](https://app.adapty.io/settings/general). | `public_live_Pj1P1xzM.2CvSvE1IalQRFjsWy6csBVNpH33atnod` | | adapty-customer-user-id | El ID de usuario utilizado en tu sistema. En el Adapty Dashboard, puedes encontrarlo en el campo **Customer user ID** del perfil. | `john.doe@example.com` | | adapty-profile-id | El ID de usuario asignado en Adapty. En el Adapty Dashboard, puedes encontrarlo en el campo **Adapty ID** del perfil. | `3286abd3-48b0-4e9c-a5f6-ac0a006333a6` | **Siguientes pasos: Solicitudes:** - [Obtener paywall](api-web/operations/getPaywall) - [Registrar vista de paywall](api-web/operations/recordPaywallView) - [Añadir atribución](api-web/operations/addAttribution) --- # File: export-analytics-api --- --- title: Exportar analíticas con API --- Exportar tus datos de análisis a CSV te da la flexibilidad de profundizar en las métricas de rendimiento de tu app, personalizar informes y analizar tendencias a lo largo del tiempo. Con la API de Adapty, puedes extraer fácilmente análisis detallados en formato CSV, lo que facilita el seguimiento, el intercambio y el refinamiento de tus datos. :::tip ¿Usas un agente de IA o LLM para extraer análisis? Consulta [Exporta tu análisis con un agente de IA](export-analytics-with-ai). ::: ## Primeros pasos con la API de exportación de análisis \{#getting-started-with-the-api-for-analytics-export\} Con la API de exportación de análisis puedes, por ejemplo: 1. **Analizar el MRR de campañas de marketing**: Mide el impacto de las campañas de marketing del año pasado en un país concreto para ver cuáles generaron más ingresos, con seguimiento semanal. Usa el método [Recuperar datos de análisis](api-export-analytics/operations/retrieveAnalyticsData) para esto. 2. **Seguimiento de la retención por cohorte a lo largo del tiempo**: Haz un seguimiento de la retención por cohorte para identificar los puntos de abandono y comparar cohortes a lo largo del tiempo, revelando tendencias y momentos clave donde las estrategias de engagement podrían mejorar la retención. Limitado a un store específico, un país concreto y un producto particular. Usa el método [Recuperar datos de cohorte](api-export-analytics/operations/retrieveCohortData) para esto. 3. **Evalúa las tasas de conversión por canal**: Analiza las tasas de conversión de los principales canales de adquisición para ver cuáles son más efectivos a la hora de impulsar las primeras compras. Esto ayuda a priorizar el gasto en marketing en los canales con mejor rendimiento. Usa el método [Retrieve conversion data](api-export-analytics/operations/retrieveConversionData) para esto. 4. **Revisar la tasa de cancelación**: Monitoriza la rapidez con la que los usuarios se dan de baja para detectar patrones de cancelación o evaluar el éxito de los esfuerzos de retención, centrándote en un país y un producto específicos. Usa el método [Recuperar datos del embudo](api-export-analytics/operations/retrieveFunnelData) para esto. 5. **Evalúa el LTV por segmento de usuario**: Identifica el valor de por vida de los distintos segmentos de usuario para saber qué grupos generan más ingresos a lo largo del tiempo. Céntrate en los segmentos de alto valor, como los suscriptores a largo plazo, y usa los resultados para perfeccionar las estrategias de adquisición. Utiliza el método [Recuperar datos de LTV](api-export-analytics/operations/retrieveLTVData) para esto. 6. **Verificar la retención por país**: Analiza las tasas de retención por región para identificar mercados con alta participación y orientar estrategias de localización o regionales. Usa el método [Retrieve retention data](api-export-analytics/operations/retrieveRetentionData) para esto. --- **Qué sigue**: - [Autorización y formato de solicitud](export-analytics-api-authorization) - [Solicitudes de la API de exportación de analíticas](export-analytics-api-requests) --- # File: export-analytics-api-authorization --- --- title: Autorización y formato de solicitud para la API de exportación de analíticas --- ## Autorización \{#authorization\} Necesitas autenticar tus solicitudes a la API con tu clave API secreta como cabecera de autorización. Puedes encontrarla en [App Settings](https://app.adapty.io/settings/general). El formato es `Api-Key {YOUR_SECRET_API_KEY}`, por ejemplo: `Api-Key secret_live_...`. :::important Las claves API son específicas de cada app. Si tienes varias apps, asegúrate de usar claves distintas para cada una. ::: ## Formato de solicitud \{#request-format\} **Cabeceras** Las solicitudes a la API del lado del servidor requieren cabeceras específicas y un cuerpo JSON. Usa los detalles a continuación para estructurar tus solicitudes: | Cabecera | Descripción | | ------------ | ------------------------------------------------------------ | | Content-Type | (Obligatorio) Establece `application/json` para que la API procese la solicitud. | | Adapty-Tz | (Opcional) Define la zona horaria para determinar cómo se agrupan y muestran los datos. Usa el [formato de la base de datos de zonas horarias IANA](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (p. ej., `Europe/Berlin`). | ## Cuerpo \{#body\} La API espera un cuerpo en formato JSON con los datos necesarios para la solicitud. ## Límites de velocidad \{#rate-limits\} El máximo es 2 solicitudes por segundo por clave API. Superar este límite devuelve un error `429 Too Many Requests`. ## Rotar claves API \{#rotate-api-keys\} Si necesitas rotar las claves API secretas: 1. En **Settings → General**, haz clic en **Generate new key** y luego en el icono de papelera junto a la clave antigua. 2. Actualiza la clave utilizada en tu app. --- **Próximos pasos: Solicitudes:** - [Recuperar datos de analíticas](api-export-analytics/operations/retrieveAnalyticsData) - [Recuperar datos de cohorte](api-export-analytics/operations/retrieveCohortData) - [Recuperar datos de conversión](api-export-analytics/operations/retrieveConversionData) - [Recuperar datos de embudo](api-export-analytics/operations/retrieveFunnelData) - [Recuperar datos de Valor de por Vida (LTV)](api-export-analytics/operations/retrieveLTVData) - [Recuperar datos de retención](api-export-analytics/operations/retrieveRetentionData) --- # File: export-analytics-api-requests --- --- title: Solicitudes de API para exportar análisis --- Exportar tus datos de análisis a CSV te da la flexibilidad de profundizar en las métricas de rendimiento de tu app, personalizar informes y analizar tendencias a lo largo del tiempo. Con la API de Adapty, puedes extraer análisis detallados en formato CSV fácilmente, lo que resulta práctico para hacer seguimiento, compartir y refinar tus datos según necesites. ## Colección y entorno de Postman \{#postman-collection-and-environment\} Para simplificar el uso de nuestra API al exportar datos de análisis, hemos preparado una colección de Postman y un archivo de entorno que puedes descargar e importar en Postman. - **Colección de solicitudes**: Incluye todas las solicitudes disponibles en la API de exportación de análisis de Adapty. Ten en cuenta que utiliza variables que puedes definir en el entorno. - **Entorno**: Contiene una lista de variables donde puedes definir valores una sola vez. Hemos preparado un entorno unificado para la API del lado del servidor, la API web y la API de exportación de análisis para facilitarte el trabajo. Una vez que actives este entorno, Postman sustituirá automáticamente los valores de las variables definidas en tus solicitudes. :::tip [Descarga la colección y el entorno](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/Adapty_export_analytics_API_postman_collection.zip) ::: Para más información sobre cómo importar una colección y un entorno en Postman, consulta la [documentación de Postman](https://learning.postman.com/docs/getting-started/importing-and-exporting/importing-data/). ### Variables utilizadas \{#variables-used\} Hemos creado un entorno unificado para la API del lado del servidor, la API web y la API de exportación de análisis para simplificar tu flujo de trabajo. A continuación se muestran las variables específicas de la API de exportación de análisis: | Variable | Descripción | Valor de ejemplo | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------- | | secret_api_key | Puedes encontrarla en el campo **Secret key** en [**App settings**](https://app.adapty.io/settings/general). | `secret_live_Pj1P1xzM.2CvSvE1IalQRFjsWy6csBVNpH33atnod` | **Solicitudes:** - [Recuperar datos de análisis](api-export-analytics/operations/retrieveAnalyticsData) - [Recuperar datos de cohorte](api-export-analytics/operations/retrieveCohortData) - [Recuperar datos de conversión](api-export-analytics/operations/retrieveConversionData) - [Recuperar datos de embudo](api-export-analytics/operations/retrieveFunnelData) - [Recuperar datos de Lifetime Value (LTV)](api-export-analytics/operations/retrieveLTVData) - [Recuperar datos de retención](api-export-analytics/operations/retrieveRetentionData) --- # End of Documentation _Generated on: 2026-07-24T13:01:55.619Z_ _Successfully processed: 17/18 files_ # CAPACITOR - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-07-24T13:01:55.620Z Total files: 45 --- # File: capacitor-sdk-overview --- --- title: "Capacitor SDK overview" description: "Aprende sobre el SDK de Adapty para Capacitor y sus características principales." --- [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Capacitor.svg?style=flat&logo=capacitor)](https://github.com/adaptyteam/AdaptySDK-Capacitor/releases) ¡Bienvenido! Estamos aquí para que las compras in-app sean pan comido 🚀 Hemos creado el [SDK de Adapty para Capacitor](https://github.com/adaptyteam/AdaptySDK-Capacitor/) para quitarte el dolor de cabeza de las compras in-app y que puedas centrarte en lo que mejor sabes hacer: crear aplicaciones increíbles. Esto es lo que gestionamos por ti: - Gestión de compras, validación de recibos y administración de suscripciones lista para usar - Crear y probar paywalls sin actualizar la app - Analíticas de compra detalladas con configuración cero: cohortes, LTV, churn y análisis de embudo incluidos - Estado de suscripción del usuario siempre actualizado en todas las sesiones y dispositivos - Integración con servicios de atribución y analíticas de marketing con una sola línea de código :::note Antes de meterte en el código, necesitarás integrar Adapty con App Store Connect y Google Play Console, y luego configurar los productos en el dashboard. Consulta nuestra [guía de inicio rápido](quickstart) para tenerlo todo configurado primero. ::: ## Primeros pasos \{#get-started\} For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. Esto es lo que cubriremos en la guía de integración: 1. [Instalar y configurar el SDK](sdk-installation-capacitor): Añade el SDK como [dependencia](https://www.npmjs.com/package/@adapty/capacitor) a tu proyecto y actívalo en el código. 2. [Habilitar compras a través de paywalls](capacitor-quickstart-paywalls): Configura el flujo de compra para que los usuarios puedan adquirir productos. 3. [Comprobar el estado de la suscripción](capacitor-check-subscription-status): Comprueba automáticamente el estado de suscripción del usuario y controla su acceso al contenido de pago. 4. [Identificar usuarios (opcional)](capacitor-quickstart-identify): Asocia los usuarios con sus perfiles de Adapty para garantizar que sus datos se almacenen de forma coherente en todos los dispositivos. ### Vélo en acción \{#see-it-in-action\} ¿Quieres ver cómo encaja todo? Aquí lo tienes: **Apps de ejemplo**: Consulta nuestros ejemplos completos que demuestran la configuración completa: - [React](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-react-example) - [Vue.js](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-vue-example) - [Angular](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-angular-example) - [Herramientas avanzadas de desarrollo](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/adapty-devtools) ## Conceptos principales \{#main-concepts\} Antes de meterte en el código, familiarízate con los conceptos clave que hacen funcionar Adapty. La ventaja del enfoque de Adapty es que solo los placements están hardcodeados en tu app. Todo lo demás —productos, diseños de paywalls, precios y ofertas— se puede gestionar de forma flexible desde el Adapty Dashboard sin actualizar la app: 1. **Producto** - Cualquier cosa disponible para comprar en tu app: suscripción, producto consumible o acceso de por vida. 2. **Flow o paywall** - Productos agrupados con configuración, asociados a un placement. Dos modalidades: - **[Flow](adapty-flow-builder)** - Interfaz visual sin código construida en Flow Builder. Adapty renderiza la UI y gestiona la compra por ti. - **[Paywall](paywalls)** - Sin configuración visual; tú construyes la UI en tu propio código y llamas a `makePurchase` tú mismo. Consulta [Implementar paywalls manualmente](capacitor-quickstart-manual). En el código del SDK, ambos se obtienen mediante el mismo método `getFlow`. 3. **Placement** - Un punto estratégico en el recorrido del usuario donde quieres mostrar un paywall. Piensa en los placements como el "dónde" y el "cuándo" de tu estrategia de monetización. Los placements más comunes son: - `main` - La ubicación principal de tu paywall - `onboarding` - Se muestra durante el flow de onboarding del usuario - `settings` - Accesible desde los ajustes de tu app Empieza con los básicos como `main` u `onboarding` en tu primera integración y luego piensa en qué otros puntos de la app los usuarios podrían estar listos para comprar. 4. **Perfil** - Cuando los usuarios compran un producto, a su perfil se le asigna un **nivel de acceso** que utilizas para definir el acceso a las funciones de pago. --- # File: sdk-installation-capacitor --- --- title: "Capacitor - Instalación y configuración del SDK de Adapty" description: "Guía paso a paso para instalar el SDK de Adapty en Capacitor para apps con suscripciones." --- El SDK de Adapty incluye dos módulos clave para una integración fluida en tu app de Capacitor: - **Core Adapty**: Este módulo es obligatorio para que Adapty funcione correctamente en tu app. - **AdaptyUI**: Este módulo es necesario si usas el [Adapty Paywall Builder](adapty-paywall-builder), una herramienta visual sin código para crear paywalls multiplataforma fácilmente. AdaptyUI se activa automáticamente junto con el módulo principal. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Requisitos \{#requirements\} El [SDK de Adapty para Capacitor](https://github.com/adaptyteam/AdaptySDK-Capacitor/) tiene los siguientes requisitos de versión: | Versión del SDK de Adapty | Versión de Capacitor | Versión de iOS | |---------------------------|----------------------|----------------| | 3.16.0+ | 8 | 15.0+ | | 3.15 | 7 | 14.0+ | Las versiones 6 e inferiores de Capacitor no son compatibles. Construir para iOS con Adapty SDK v4 (beta) requiere **Xcode 26** o posterior — el SDK nativo de iOS que utiliza está compilado con Swift tools 6.2. Los requisitos de iOS 15.0+, Capacitor 8 y Android minSdk 24 son los mismos que para SDK 3.16+. :::info A partir de SDK v3.17, Adapty SDK utiliza Google Play Billing Library v8.0.0 por defecto. ::: :::info Instalar el SDK es el paso 5 de la configuración de Adapty. Para que las compras funcionen en tu app, también necesitas conectar tu app a los stores, y luego crear productos, un paywall y un placement en el Adapty Dashboard. La [guía de inicio rápido](quickstart) explica todos los pasos necesarios. ::: ## Instalar el SDK de Adapty \{#install-adapty-sdk\} :::important Los pasos a continuación instalan Adapty SDK 3.x. SDK v4 (beta) — necesario para el [Flow Builder](adapty-flow-builder) y utilizado por la [guía de inicio rápido](capacitor-quickstart-paywalls) — se instala de forma diferente: sigue [Adapty SDK 4.0 (beta)](#adapty-sdk-40-beta) más abajo, o consulta la [guía de migración](migration-to-capacitor-sdk-v4). ::: [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Capacitor.svg?style=flat&logo=capacitor)](https://github.com/adaptyteam/AdaptySDK-Capacitor/releases) Instala el SDK de Adapty: ```sh npm install @adapty/capacitor npx cap sync ``` ### Adapty SDK 4.0 (beta) Capacitor SDK 4.0 — que añade soporte para [Flow Builder](adapty-flow-builder) — es una versión preliminar. Instala la versión exacta (npm no resuelve versiones preliminares con rangos caret/tilde), luego sincroniza: ```sh npm install @adapty/capacitor@4.0.0-beta.2 ``` ```sh npx cap sync ``` En iOS, la versión 4 obtiene los SDK nativos de Adapty únicamente a través de **Swift Package Manager** — el podspec de CocoaPods fue eliminado ([el repositorio de specs de CocoaPods pasa a ser de solo lectura en diciembre de 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)). El proyecto iOS de tu app debe usar la integración SPM de Capacitor: - Para las apps nuevas, añade la plataforma iOS con el gestor de paquetes SPM: ```sh npx cap add ios --packagemanager SPM ``` - Para las apps existentes, migra el proyecto iOS de CocoaPods a SPM siguiendo [la guía de Capacitor para usar SPM en un proyecto existente](https://capacitorjs.com/docs/ios/spm#using-spm-in-an-existing-capacitor-project). Para ver la lista completa de cambios de API en v4, consulta [Migrar el SDK de Capacitor de Adapty a v4](migration-to-capacitor-sdk-v4). ## Activar el módulo Adapty del SDK \{#activate-adapty-module-of-adapty-sdk\} :::note El SDK de Adapty solo necesita activarse una vez en tu app. ::: Para obtener tu **Public SDK Key**: 1. Ve al Adapty Dashboard y navega a [**App settings → General**](https://app.adapty.io/settings/general). 2. En la sección **Api keys**, copia la **Public SDK Key** (NO la Secret Key). 3. Reemplaza `"YOUR_PUBLIC_SDK_KEY"` en el código. O bien, obtenla de forma programática usando el [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` O directamente: ``` npx adapty auth login adapty apps list ``` - Asegúrate de usar la **Public SDK key** para inicializar Adapty; la **Secret key** solo debe usarse para la [API del lado del servidor](getting-started-with-server-side-api). - Las **SDK keys** son únicas para cada app, así que si tienes varias apps asegúrate de elegir la correcta. Copia el siguiente código en cualquier archivo de tu app para activar Adapty: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { // verbose logging is recommended for the development purposes and for the first production release logLevel: 'verbose', // in the development environment, use this variable to avoid multiple activation errors. Set it to your development environment variable __ignoreActivationOnFastRefresh: true, } }); console.log('Adapty activated successfully!'); } catch (error) { console.error('Failed to activate Adapty SDK:', error); } ``` :::important Espera a que `activate` se resuelva antes de llamar a cualquier otro método del SDK de Adapty. Consulta el [orden de llamadas en el SDK de Capacitor](capacitor-sdk-call-order) para ver la secuencia completa. ::: :::tip Para evitar errores de activación en el entorno de desarrollo, usa los [consejos](#development-environment-tips). ::: Ahora configura los paywalls en tu app: - Si usas [Adapty Paywall Builder](adapty-paywall-builder), sigue la [guía de inicio rápido del Paywall Builder](capacitor-quickstart-paywalls). - Si construyes tu propia interfaz de paywall, consulta la [guía de inicio rápido para paywalls personalizados](capacitor-quickstart-manual). ## Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Si planeas usar el [Paywall Builder](adapty-paywall-builder), necesitas el módulo AdaptyUI. Se activa automáticamente cuando activas el módulo principal; no necesitas hacer nada más. ## Configuración opcional \{#optional-setup\} ### Registro #### Configura el sistema de registro \{#set-up-the-logging-system\} Adapty registra errores y otra información importante para ayudarte a entender qué está ocurriendo. Hay los siguientes niveles disponibles: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | Solo se registrarán errores | | `warn` | Se registrarán errores y mensajes del SDK que no causan errores críticos, pero que merecen atención | | `info` | Se registrarán errores, advertencias y varios mensajes informativos | | `verbose` | Se registrará cualquier información adicional que pueda ser útil durante la depuración, como llamadas a funciones, consultas a la API, etc. | Puedes establecer el nivel de log en tu app antes o durante la configuración de Adapty: ```typescript showLineNumbers // Set log level before activation adapty.setLogLevel({ logLevel: 'verbose' }); // Or set it during configuration await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { logLevel: 'verbose', } }); ``` ### Políticas de datos \{#data-policies\} Adapty no almacena datos personales de tus usuarios a menos que los envíes explícitamente, pero puedes implementar políticas de seguridad de datos adicionales para cumplir con las directrices del store o del país. #### Desactivar la recopilación y el uso compartido de la dirección IP \{#disable-ip-address-collection-and-sharing\} Al activar el módulo de Adapty, establece `ipAddressCollectionDisabled` en `true` para desactivar la recopilación y el uso compartido de la dirección IP del usuario. El valor predeterminado es `false`. Usa este parámetro para mejorar la privacidad del usuario, cumplir con normativas regionales de protección de datos (como GDPR o CCPA) o reducir la recopilación de datos innecesaria cuando las funciones basadas en IP no son necesarias para tu app. ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { ipAddressCollectionDisabled: true, } }); ``` #### Deshabilitar la recopilación y el uso compartido del ID publicitario \{#disable-advertising-id-collection-and-sharing\} Al activar el módulo de Adapty, establece `ios.idfaCollectionDisabled` (iOS) o `android.adIdCollectionDisabled` (Android) en `true` para desactivar la recopilación de identificadores publicitarios. El valor predeterminado es `false`. Usa este parámetro para cumplir con las políticas de App Store/Play Store, evitar que se active el aviso de App Tracking Transparency, o si tu app no requiere atribución publicitaria ni análisis basados en IDs publicitarios. ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { ios: { idfaCollectionDisabled: true, }, android: { adIdCollectionDisabled: true, }, } }); ``` #### Configurar la caché de medios para AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} Por defecto, AdaptyUI almacena en caché los medios (como imágenes y vídeos) para mejorar el rendimiento y reducir el uso de red. Puedes personalizar la configuración de la caché proporcionando una configuración personalizada. Usa `mediaCache` para sobrescribir la configuración de caché predeterminada: ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { mediaCache: { memoryStorageTotalCostLimit: 200 * 1024 * 1024, // Optional: memory cache size in bytes memoryStorageCountLimit: 2147483647, // Optional: max number of items in memory diskStorageSizeLimit: 200 * 1024 * 1024, // Optional: disk cache size in bytes }, } }); ``` | Parámetro | Obligatorio | Descripción | |-----------|-------------|-------------| | memoryStorageTotalCostLimit | opcional | Tamaño total de la caché en memoria en bytes. El valor predeterminado depende de la plataforma. | | memoryStorageCountLimit | opcional | Límite de elementos en el almacenamiento en memoria. El valor predeterminado depende de la plataforma. | | diskStorageSizeLimit | opcional | Límite de tamaño de archivo en disco en bytes. El valor predeterminado depende de la plataforma. | ### Habilitar niveles de acceso locales (Android) \{#enable-local-access-levels-android\} Por defecto, los [niveles de acceso locales](local-access-levels) están habilitados en iOS y deshabilitados en Android. Para habilitarlos también en Android, establece `localAccessLevelAllowed` en `true`: ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { android: { localAccessLevelAllowed: true, }, } }); ``` ### Borrar datos al restaurar desde copia de seguridad \{#clear-data-on-backup-restore\} Cuando `clearDataOnBackup` se establece en `true`, el SDK detecta cuándo la app se restaura desde una copia de seguridad de iCloud y elimina todos los datos almacenados localmente por el SDK, incluyendo información de perfil en caché, detalles de productos y paywalls. El SDK se inicializa entonces con un estado limpio. El valor predeterminado es `false`. :::note Solo se elimina la caché local del SDK. El historial de transacciones con Apple y los datos de usuario en los servidores de Adapty no se modifican. ::: ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { ios: { clearDataOnBackup: true, }, } }); ``` ## Consejos para el entorno de desarrollo \{#development-environment-tips\} #### Solucionar errores de activación del SDK en el live-reload de Capacitor \{#troubleshoot-sdk-activation-errors-on-capacitors-live-reload\} Al desarrollar con el SDK de Adapty en Capacitor, es posible que encuentres el error: `Adapty can only be activated once. Ensure that the SDK activation call is not made more than once.` Esto ocurre porque la función de live-reload de Capacitor dispara múltiples llamadas de activación durante el desarrollo. Para evitarlo, usa la opción `__ignoreActivationOnFastRefresh` con el indicador del modo de desarrollo de Capacitor — variará según el bundle que estés usando. ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { // Set your development environment variable __ignoreActivationOnFastRefresh: true, } }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` ## Solución de problemas \{#troubleshooting\} #### Error de versión mínima de iOS \{#minimum-ios-version-error\} :::note Esto aplica a proyectos basados en CocoaPods con **SDK 3.x**. El SDK 4.0 se instala en iOS a través de Swift Package Manager únicamente (no hay `Podfile`) y requiere iOS 15.0 — establece tu deployment target en 15.0 en Xcode. ::: Si obtienes un error de versión mínima de iOS en SDK 3.x, actualiza tu Podfile: ```diff -platform :ios, min_ios_version_supported +platform :ios, '15.0' ``` #### Reglas de copia de seguridad de Android (configuración de Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Algunos SDKs (incluido Adapty) incluyen su propia configuración de Android Auto Backup. Si utilizas varios SDKs que definen reglas de copia de seguridad, el fusionador de manifiestos de Android puede fallar con un error relacionado con `android:fullBackupContent`, `android:dataExtractionRules` o `android:allowBackup`. Síntomas típicos del error: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Estos cambios deben realizarse en el directorio de la plataforma Android (normalmente en la carpeta `android/` de tu proyecto). ::: Para resolverlo, necesitas: - Indicar al fusionador de manifiestos que use los valores de tu app para los atributos relacionados con la copia de seguridad. - Crear archivos de reglas de copia de seguridad que combinen las reglas de Adapty con las de otros SDKs. #### 1. Añade el namespace `tools` a tu manifiesto \{#1-add-the-tools-namespace-to-your-manifest\} En tu archivo `AndroidManifest.xml`, asegúrate de que la etiqueta raíz `<manifest>` incluya tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. Sobreescribe los atributos de copia de seguridad en `<application>` \{#2-override-backup-attributes-in-application\} En el mismo archivo `AndroidManifest.xml`, actualiza la etiqueta `<application>` para que tu app proporcione los valores definitivos e indique al fusionador de manifiestos que reemplace los valores de las librerías: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` Si algún SDK también define `android:allowBackup`, inclúyelo en `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Crea los archivos de reglas de copia de seguridad combinadas \{#3-create-merged-backup-rules-files\} Crea archivos XML en el directorio `res/xml/` de tu proyecto Android que combinen las reglas de Adapty con las de otros SDKs. Android utiliza distintos formatos de reglas de copia de seguridad según la versión del sistema operativo, por lo que crear ambos archivos garantiza la compatibilidad con todas las versiones de Android que admite tu app. :::note Los ejemplos a continuación usan AppsFlyer como SDK de terceros de muestra. Reemplaza o añade reglas para cualquier otro SDK que uses en tu app. ::: **Para Android 12 y superior** (usa el nuevo formato de reglas de extracción de datos): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Para Android 11 e inferior** (usa el formato legado de contenido de copia de seguridad completa): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> :::tip Después de modificar archivos nativos de Android, ejecuta `npx cap sync android` para que Capacitor recoja los recursos actualizados si regeneras la plataforma. ::: #### Las compras fallan al volver desde otra app en Android \{#purchases-fail-after-returning-from-another-app-in-android\} Si la Activity que inicia el flow de compra usa un `launchMode` distinto al predeterminado, Android puede recrearla o reutilizarla incorrectamente cuando el usuario vuelve desde Google Play, una app bancaria o un navegador. Esto puede hacer que el resultado de la compra se pierda o se trate como cancelado. Para garantizar que las compras funcionen correctamente, usa solo los modos de lanzamiento `standard` o `singleTop` para la Activity que inicia el flujo de compra, y evita cualquier otro modo. En tu `AndroidManifest.xml`, asegúrate de que la Activity que inicia el flujo de compra esté configurada como `standard` o `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### Errores de compilación de Swift 6 causados por la sobreescritura de SWIFT_VERSION en Podfile \{#swift-6-build-errors-caused-by-podfile-swift_version-override\} :::note Esto se aplica a proyectos basados en CocoaPods con **SDK 3.x**. SDK 4.0 instala los SDKs nativos a través de Swift Package Manager, por lo que no hay ningún `Podfile` que ajustar. ::: Al compilar tu app Capacitor para iOS, es posible que veas errores de compilación de Swift 6 en los targets del pod de Adapty. Los síntomas típicos incluyen incompatibilidades de `@Sendable` en `AdaptyUIBuilderLogic`, falta de conformidad con `Sendable` en los tipos de Adapty, o errores de aislamiento de actores. Los pods de Adapty declaran `s.swift_version = '6.0'` y requieren Swift 6 para compilarse. Tu propio código de app puede quedarse en Swift 5 — solo los targets de los pods de Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`, `AdaptyLogger`, `AdaptyPlugin`) necesitan compilarse con Swift 6. La causa más común es un hook `post_install` en `ios/App/Podfile` que sobreescribe `SWIFT_VERSION` para cada target de pod: ```ruby showLineNumbers title="ios/App/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **Solución**: Excluye los targets del pod de Adapty del override: ```ruby showLineNumbers title="ios/App/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` Luego ejecuta `npx cap sync ios` y vuelve a compilar. Para verificarlo, abre `ios/App/Pods/Pods.xcodeproj`, selecciona el target del pod `Adapty` → **Build Settings** → **Swift Language Version**. Debería aparecer **Swift 6**. --- # File: capacitor-quickstart-paywalls --- --- title: "Habilitar compras con Flow Builder en el SDK de Capacitor" description: "Guía de inicio rápido para habilitar compras in-app con Adapty Flow Builder." --- Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Products**](product) – cualquier cosa que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Flows**](adapty-flow-builder) – secuencias de pantallas que presentan productos a los usuarios, construidas en el Flow Builder sin código. El SDK los recupera mediante `getFlow`. Si prefieres construir la interfaz en tu propio código, usa un paywall — consulta [Implementar paywalls manualmente](capacitor-quickstart-manual). - [**Placements**](placements) – dónde y cuándo mostrar flows en tu app (como `main`, `onboarding`, `settings`). Asocias flows a placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar diferentes flows a distintos usuarios. Adapty te ofrece tres formas de habilitar compras en tu aplicación. Selecciona la que mejor se adapte a tus requisitos: | Implementación | Complejidad | Cuándo usarla | |---|---|---| | Adapty Flow Builder | ✅ Fácil | [Creas un flow completo y listo para compras en el editor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona todo el proceso de compra, la validación de recibos y la gestión de suscripciones entre bastidores. | | Paywalls creados manualmente | 🟡 Media | Implementas la interfaz de tu paywall en el código de tu app, pero sigues obteniendo el objeto flow desde Adapty para mantener flexibilidad en la oferta de productos. Consulta la [guía](capacitor-quickstart-manual). | | Modo observador | 🔴 Difícil | Ya tienes tu propia infraestructura de gestión de compras y quieres seguir usándola. Ten en cuenta que el modo observador tiene sus limitaciones en Adapty. Consulta el [artículo](observer-vs-full-mode). | :::important **Los pasos a continuación muestran cómo implementar un flow creado en el Adapty Flow Builder.** Si prefieres crear la UI del paywall tú mismo, consulta [Implementar paywalls manualmente](capacitor-quickstart-manual). ::: Para mostrar un flow creado en el Adapty Flow Builder, en el código de tu app solo necesitas: 1. **Obtener el flow**: Obtenerlo desde Adapty. 2. **Mostrarlo y Adapty gestionará las compras por ti**: Muestra la vista en tu app. 3. **Gestionar las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app a ellas. Por ejemplo, abre enlaces o cierra el flow cuando los usuarios pulsen botones. ## Antes de empezar \{#before-you-start\} Antes de empezar, completa estos pasos: 1. Conecta tu app al [App Store](initial_ios) y/o [Google Play](initial-android) en el Adapty Dashboard. 2. [Crea tus productos](create-product) en Adapty. 3. [Crea un paywall y añade productos](create-paywall). 4. [Crea un placement y añade tu paywall](create-placement). 5. [Instala y activa el SDK de Adapty](sdk-installation-capacitor) en el código de tu app. :::tip La forma más rápida de completar estos pasos es seguir la [guía de inicio rápido](quickstart) o crear paywalls y placements usando el [CLI para desarrolladores](developer-cli-quickstart). ::: ## 1. Obtener el flow \{#1-get-the-flow\} Tus flows están asociados a placements configurados en el dashboard. Los placements te permiten mostrar flows distintos para diferentes audiencias o ejecutar [pruebas A/B](ab-tests). Para obtener un flow creado en el Adapty Flow Builder, obtén el objeto `flow` por el ID del [placement](placements) usando el método `getFlow`. El flow contiene los elementos de UI y los estilos necesarios para mostrarlo. ```typescript showLineNumbers title="Capacitor" try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', }); // the requested flow } catch (error) { // handle the error } ``` ## 2. Muestra el flow \{#2-display-the-flow\} Ahora que tienes el flow, basta con añadir unas pocas líneas para mostrarlo. Crea un `view` con el método `createFlowView`, configura sus manejadores de eventos y luego llama a `view.present()`. Cada `view` solo puede usarse una vez. Si necesitas mostrar el flow de nuevo, llama a `createFlowView` otra vez para crear una nueva instancia de `view`. ```typescript showLineNumbers title="Capacitor" try { const view = await createFlowView(flow); await view.setEventHandlers({ onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type === 'success'; // close the flow on a successful purchase, keep it open for cancelled or pending purchases }, }); await view.present(); } catch (error) { // handle the error } ``` :::tip Para más detalles sobre cómo mostrar un flow, consulta nuestra [guía](capacitor-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#3-handle-button-actions\} Cuando los usuarios hacen clic en los botones del flow, el SDK de Capacitor gestiona automáticamente las compras, la restauración, el cierre del flow y la apertura de URLs. Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren que gestiones las acciones en tu código. O bien, puede que quieras sobreescribir su comportamiento predeterminado. Por ejemplo, aquí está el comportamiento predeterminado del botón de cierre. No necesitas añadirlo en el código, pero aquí puedes ver cómo se hace si fuera necesario. ```typescript showLineNumbers title="Capacitor" const unsubscribe = await view.setEventHandlers({ onCloseButtonPress() { return true; // allow the flow to close }, }); ``` :::tip Lee nuestras guías sobre cómo gestionar [acciones](capacitor-handle-paywall-actions) y [eventos](capacitor-handling-events) de botones. ::: ## Siguientes pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox del App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. A continuación, necesitas [comprobar el nivel de acceso de los usuarios](capacitor-check-subscription-status) para asegurarte de mostrar un paywall o dar acceso a las funciones de pago a los usuarios correctos. ## Ejemplo completo \{#full-example\} Aquí tienes cómo integrar todos los pasos de esta guía en tu aplicación. ```typescript showLineNumbers title="Capacitor" export async function showFlow() { try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', }); const view = await createFlowView(flow); await view.setEventHandlers({ onCloseButtonPress() { return true; }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type === 'success'; // close the flow on a successful purchase, keep it open for cancelled or pending purchases }, }); await view.present(); } catch (error) { // handle any error that may occur during the process console.warn('Error showing flow:', error); } } ``` --- # File: capacitor-check-subscription-status --- --- title: "Comprobar el estado de la suscripción en el SDK de Capacitor" description: "Aprende a comprobar el estado de la suscripción en tu app de Capacitor con Adapty." --- Para decidir si los usuarios pueden acceder al contenido de pago o ver un paywall, necesitas comprobar su [nivel de acceso](access-level) en el perfil. Este artículo te muestra cómo acceder al estado del perfil para decidir qué deben ver los usuarios: si mostrarles un paywall o darles acceso a las funciones de pago. ## Obtener el estado de la suscripción \{#get-subscription-status\} Cuando decides si mostrar un paywall o contenido de pago a un usuario, compruebas su [nivel de acceso](access-level) en su perfil. Tienes dos opciones: - Llama a `getProfile` si necesitas los datos del perfil más recientes de inmediato (por ejemplo, al iniciar la app) o quieres forzar una actualización. - Configura **actualizaciones automáticas del perfil** para mantener una copia local que se actualice automáticamente cada vez que cambie el estado de la suscripción. ### Obtener el perfil \{#get-profile\} La forma más sencilla de obtener el estado de la suscripción es usar el método `getProfile` para acceder al perfil: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); } catch (error) { // handle the error } ``` ### Escuchar actualizaciones de suscripción \{#listen-to-subscription-updates\} Para recibir actualizaciones del perfil automáticamente en tu app: 1. Usa `adapty.addListener('onLatestProfileLoad')` para escuchar cambios en el perfil — Adapty llamará a este método automáticamente cada vez que cambie el estado de suscripción del usuario. 2. Guarda los datos del perfil actualizados cuando se llame a este método, para poder usarlos en toda tu app sin realizar peticiones de red adicionales. ```typescript showLineNumbers class SubscriptionManager { private currentProfile: any = null; constructor() { // Listen for profile updates adapty.addListener('onLatestProfileLoad', (data) => { this.currentProfile = data.profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() hasAccess(): boolean { return this.currentProfile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive ?? false; } } ``` :::note Adapty llama automáticamente al listener del evento `onLatestProfileLoad` cuando tu app se inicia, proporcionando datos de suscripción en caché incluso si el dispositivo está sin conexión. ::: ## Conectar el perfil con la lógica del paywall \{#connect-profile-with-paywall-logic\} Cuando necesites tomar decisiones inmediatas sobre mostrar paywalls o conceder acceso a funciones de pago, puedes consultar directamente el perfil del usuario. Este enfoque es útil en situaciones como el lanzamiento de la app, al acceder a secciones premium o antes de mostrar contenido específico. ```typescript showLineNumbers const checkAccessLevel = async () => { try { const profile = await adapty.getProfile(); return profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive === true; } catch (error) { console.warn('Error checking access level:', error); return false; // Show paywall if access check fails } }; const getAccessLevel = (profile: AdaptyProfile) => { return profile.accessLevels?.['YOUR_ACCESS_LEVEL']; }; const initializePaywall = async () => { try { await loadPaywall(); const hasAccess = await checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } catch (error) { console.warn('Error initializing paywall:', error); } }; ``` ## Próximos pasos \{#next-steps\} Ahora que sabes cómo hacer seguimiento del estado de la suscripción, aprende a [trabajar con perfiles de usuario](capacitor-quickstart-identify) para garantizar que puedan acceder a lo que han pagado. --- # File: capacitor-quickstart-identify --- --- title: "Identificar usuarios en el SDK de Capacitor" description: "Guía de inicio rápido para configurar Adapty en la gestión de suscripciones in-app en Capacitor." --- Cómo gestionas las compras de tus usuarios depende del modelo de autenticación de tu app: - Si tu app no usa autenticación de backend y no almacena datos de usuario, consulta la [sección sobre usuarios anónimos](#anonymous-users). - Si tu app tiene (o tendrá) autenticación de backend, consulta la [sección sobre usuarios identificados](#identified-users). :::tip **Conceptos clave**: - Los **perfiles** son las entidades necesarias para que el SDK funcione. Adapty los crea automáticamente. Pueden ser anónimos (sin customer user ID) o identificados (con customer user ID). - Los **customer user IDs** son identificadores opcionales **que tú creas** para que Adapty vincule a tus usuarios con sus perfiles de Adapty. ::: Esto es lo que distingue a los usuarios anónimos de los identificados: | | Usuarios anónimos | Usuarios identificados | |-------------------------|----------------------------------------------------------------|----------------------------------------------------------------------------------------------| | **Purchase management** | Restauración de compras a nivel de store | Mantienen el historial de compras en todos los dispositivos mediante su customer user ID | | **Profile management** | Nuevos perfiles en cada reinstalación | El mismo perfil en todas las sesiones y dispositivos | | **Data persistence** | Los datos de usuarios anónimos están ligados al dispositivo/instalación | Los datos de usuarios identificados persisten en todos los dispositivos y sesiones | ## Usuarios anónimos \{#anonymous-users\} Si no tienes autenticación en el backend, **no necesitas gestionar la autenticación en el código de la app**: 1. Cuando el SDK se activa en el primer lanzamiento de la app, Adapty **crea un nuevo perfil para el usuario**. 2. Cuando el usuario compra algo en la app, esa compra queda **asociada a su perfil de Adapty y a su cuenta en el store**. 3. Cuando el usuario **reinstala** la app o la instala desde un **nuevo dispositivo**, Adapty **crea un nuevo perfil vacío al activarse**. 4. Si el usuario ya había realizado compras en tu app, por defecto sus compras se sincronizan automáticamente desde el App Store al activar el SDK. :::note Las restauraciones desde copia de seguridad se comportan de forma diferente a las reinstalaciones. Por defecto, cuando un usuario restaura desde una copia de seguridad, el SDK conserva los datos en caché y no crea un nuevo perfil. Puedes configurar este comportamiento con el ajuste `clearDataOnBackup`. [Más información](sdk-installation-capacitor#clear-data-on-backup-restore). ::: ## Usuarios identificados \{#identified-users\} - Si un perfil todavía no tiene un customer user ID (es decir, **el usuario no ha iniciado sesión**), cuando envías un customer user ID, este se asocia a ese perfil. - Si se trata de una **reinstalación, inicio de sesión o instalación desde un nuevo dispositivo**, y ya has enviado su customer user ID anteriormente, no se crea un nuevo perfil. En su lugar, cambiamos al perfil existente asociado al customer user ID. Tienes dos opciones para identificar usuarios en la app: - [**Durante el inicio/registro de sesión:**](#during-loginsignup) Si los usuarios inician sesión después de que la app arranca, llama a `identify()` con el customer user ID cuando se autentiquen. - [**Durante la activación del SDK:**](#during-the-sdk-activation) Si ya tienes un customer user ID almacenado cuando la app se lanza, envíalo al llamar a `activate()`. :::important Por defecto, cuando Adapty recibe una compra de un Customer User ID que está asociado con otro Customer User ID, el nivel de acceso se comparte, de modo que ambos perfiles tienen acceso de pago. Puedes configurar este ajuste para transferir el acceso de pago de un perfil a otro o deshabilitar el uso compartido por completo. Consulta el [artículo](general#6-sharing-paid-access-between-user-accounts) para más detalles. ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Durante el inicio de sesión/registro \{#during-loginsignup\} Si identificas a los usuarios después de que se inicie la app (por ejemplo, después de que inicien sesión o se registren), usa el método `identify` para establecer su customer user ID. - Si **no has utilizado este customer user ID antes**, Adapty lo vinculará automáticamente al perfil actual. - Si **ya has utilizado este customer user ID para identificar al usuario**, Adapty cambiará al perfil asociado a ese customer user ID. :::tip Al crear un ID de usuario personalizado, guárdalo junto con los datos del usuario para poder enviar el mismo ID cuando inicie sesión desde nuevos dispositivos o reinstale la app. ::: Usa siempre `await` con `identify` antes de llamar a otros métodos del SDK. Las llamadas concurrentes generan `#3006 profileWasChanged` o aterrizan en el perfil anónimo. Consulta [Orden de llamadas en el SDK de Capacitor](capacitor-sdk-call-order). ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` ### Durante la activación del SDK \{#during-the-sdk-activation\} Si ya conoces el customer user ID en el momento de activar el SDK, puedes enviarlo directamente en el método `activate` en lugar de llamar a `identify` por separado. Si conoces el customer user ID pero lo asignas después de la activación, al activarse el SDK, Adapty creará un nuevo perfil vacío y cambiará al perfil existente solo cuando llames a `identify`. Puedes pasar un customer user ID existente (el que hayas usado antes) o uno nuevo. Si pasas uno nuevo, el perfil creado en la activación se vinculará automáticamente a ese customer user ID. :::tip Para excluir los perfiles vacíos creados de las analíticas del dashboard, ve a **App settings** y configura [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```typescript showLineNumbers await adapty.activate({ apiKey: "YOUR_PUBLIC_SDK_KEY", params: { customerUserId: "YOUR_USER_ID" } }); ``` ### Cerrar sesión de usuarios \{#log-users-out\} Si tienes un botón para cerrar la sesión de los usuarios, usa el método `logout`. Esto crea un nuevo ID de perfil anónimo para el usuario. ```typescript showLineNumbers try { await adapty.logout(); // successful logout } catch (error) { // handle the error } ``` :::info Para volver a iniciar sesión en la app, usa el método `identify`. ::: ### Permitir compras sin inicio de sesión \{#allow-purchases-without-login\} Si tus usuarios pueden realizar compras tanto antes como después de iniciar sesión en tu aplicación, no necesitas ninguna configuración adicional: Así es como funciona: 1. Cuando un usuario sin sesión iniciada realiza una compra, Adapty la vincula a su ID de perfil anónimo. 2. Cuando el usuario inicia sesión en su cuenta, Adapty pasa a trabajar con su perfil identificado. - Si el customer user ID ya existe (ya está vinculado a un perfil), Adapty sincroniza sus transacciones automáticamente. - Si es un customer user ID nuevo (por ejemplo, la compra se realizó antes del registro), Adapty asigna el customer user ID al perfil actual, por lo que todo el historial de compras se conserva. --- # File: adapty-sdk-integration-skill-capacitor --- --- title: "Integra Adapty en tu app de Capacitor con la habilidad de integración del SDK" description: "Usa la habilidad adapty-sdk-integration para integrar el SDK de Adapty en tu app de Capacitor de principio a fin con tu herramienta de codificación con IA." --- La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. :::important La skill está en beta. Si se detiene o se comporta de forma inesperada, sigue la [guía de integración paso a paso](adapty-cursor-capacitor) en su lugar: lleva a tu herramienta de IA por cada etapa con la documentación correcta. ::: La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. --- # File: adapty-cursor-capacitor --- --- title: "Integra Adapty en tu app de Capacitor con ayuda de IA" description: "Guía paso a paso para integrar Adapty en tu app de Capacitor usando Cursor, Context7, ChatGPT, Claude u otras herramientas de IA." --- Esta guía te lleva paso a paso por la integración de Adapty en tu app de Capacitor con una herramienta de codificación con IA — tú le proporcionas la documentación correcta de Adapty en el orden correcto. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Antes de empezar: configuración en el dashboard \{#before-you-start-dashboard-setup\} Adapty requiere cierta configuración en el dashboard antes de escribir código con el SDK. Puedes hacerlo con una skill LLM interactiva o manualmente desde el Dashboard. ### Enfoque con skill (recomendado) \{#skill-approach-recommended\} El skill de Adapty CLI permite que tu LLM configure tu app, productos, niveles de acceso, paywalls y placements directamente — sin necesidad de abrir el Dashboard en cada paso. Solo tienes que [conectar tus stores](integrate-payments) en el Dashboard. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` Una vez añadido el skill, ejecuta `/adapty-cli` en tu agente. Te guiará por cada paso — incluyendo cuándo abrir el Dashboard para conectar tus stores. ### Enfoque desde el dashboard \{#dashboard-approach\} Si prefieres configurarlo todo manualmente, esto es lo que necesitas antes de escribir código. Tu LLM no puede buscar valores del dashboard por ti — tendrás que proporcionárselos. 1. **Conecta tus stores**: En el Adapty Dashboard, ve a **App settings → General**. Conecta tanto App Store como Google Play si tu app de Capacitor es compatible con ambas plataformas. Esto es necesario para que las compras funcionen. [Conectar stores](integrate-payments) 2. **Copia tu clave pública del SDK**: En el Adapty Dashboard, ve a **App settings → General** y busca la sección **API keys**. En el código, es la cadena que pasas a `adapty.activate()`. 3. **Crea al menos un producto**: En el Adapty Dashboard, ve a la página **Products**. No haces referencia a los productos directamente en el código — Adapty los entrega a través de paywalls. [Añadir productos](quickstart-products) 4. **Crea un paywall y un placement**: En el Adapty Dashboard, crea un paywall en la página **Paywalls**, luego asígnalo a un placement en la página **Placements**. En el código, el ID del placement es la cadena que pasas a `adapty.getFlow()`. [Crear paywall](quickstart-paywalls) 5. **Configura los niveles de acceso**: En el Adapty Dashboard, configúralos por producto en la página **Products**. En el código, la cadena que se comprueba es `profile.accessLevels['premium']?.isActive`. El nivel de acceso `premium` predeterminado funciona para la mayoría de las apps. Si los usuarios de pago acceden a funciones distintas según el producto (por ejemplo, un plan `basic` frente a un plan `pro`), [crea niveles de acceso adicionales](assigning-access-level-to-a-product) antes de empezar a programar. :::tip Una vez que tengas los cinco, estás listo para escribir código. Dile a tu LLM: "Mi clave SDK pública es X, mi ID de placement es Y" para que pueda generar el código correcto de inicialización y obtención de flows. ::: ### Configura cuando estés listo \{#set-up-when-ready\} No son obligatorias para empezar a programar, pero las necesitarás a medida que tu integración madure: - **Pruebas A/B**: Configúralas en la página **Placements**. No se necesitan cambios de código. [Pruebas A/B](ab-tests) - **Paywalls y placements adicionales**: Añade más llamadas `getPaywall` con diferentes IDs de placement. - **Integraciones de analíticas**: Configúralas en la página **Integrations**. La configuración varía según la integración. Consulta [integraciones de analíticas](analytics-integration) e [integraciones de atribución](attribution-integration). ## Proporciona la documentación de Adapty a tu LLM \{#feed-adapty-docs-to-your-llm\} ### Usa Context7 (recomendado) [Context7](https://context7.com) es un servidor MCP que da a tu LLM acceso directo a la documentación actualizada de Adapty. Tu LLM obtiene automáticamente la documentación adecuada según lo que preguntes, sin necesidad de pegar URLs manualmente. Context7 funciona con **Cursor**, **Claude Code**, **Windsurf** y otras herramientas compatibles con MCP. Para configurarlo, ejecuta: ``` npx ctx7 setup ``` Esto detecta tu editor y configura el servidor de Context7. Para una configuración manual, consulta el [repositorio de Context7 en GitHub](https://github.com/upstash/context7). Una vez configurado, haz referencia a la biblioteca de Adapty en tus prompts: ``` Use the adaptyteam/adapty-docs library to look up how to install the Capacitor SDK ``` :::warning Aunque Context7 elimina la necesidad de pegar enlaces a la documentación manualmente, el orden de implementación es importante. Sigue el [recorrido de implementación](#implementation-walkthrough) paso a paso para asegurarte de que todo funciona correctamente. ::: ### Usa documentos en texto plano Puedes acceder a cualquier documento de Adapty en texto plano Markdown. Añade `.md` al final de su URL, o haz clic en **Copy for LLM** bajo el título del artículo. Por ejemplo: [adapty-cursor-capacitor.md](https://adapty.io/docs/es/adapty-cursor-capacitor.md). Cada etapa del [recorrido de implementación](#implementation-walkthrough) incluye un bloque "Send this to your LLM" con enlaces `.md` para pegar. Para obtener más documentación a la vez, consulta los [archivos de índice y subconjuntos por plataforma](#plain-text-doc-index-files) más abajo. ## Guía de implementación paso a paso \{#implementation-walkthrough\} El resto de esta guía recorre la integración de Adapty en el orden en que debes implementarla. Cada etapa incluye la documentación que debes enviar a tu LLM, qué deberías ver al terminar y los problemas más comunes. ### Planifica tu integración \{#plan-your-integration\} Antes de escribir código, pide a tu LLM que analice tu proyecto y cree un plan de implementación. Si tu herramienta de IA admite un modo de planificación (como el modo plan de Cursor o Claude Code), úsalo para que el LLM pueda leer tanto la estructura de tu proyecto como la documentación de Adapty antes de escribir nada. Indica a tu LLM qué enfoque usas para las compras, ya que esto determina las guías que debe seguir: - [**Adapty Flow Builder**](adapty-flow-builder): Creas flows en el editor no-code de Adapty y el SDK los renderiza automáticamente. - [**Paywalls creados manualmente**](capacitor-making-purchases): Creas tu propia interfaz de paywall en código, pero sigues usando Adapty para obtener productos y gestionar compras. - [**Modo observador**](observer-vs-full-mode): Mantienes tu infraestructura de compras existente y usas Adapty solo para análisis e integraciones. ¿No sabes cuál elegir? Lee la [tabla comparativa en la guía de inicio rápido](capacitor-quickstart-paywalls). ### Instala y configura el SDK \{#install-and-configure-the-sdk\} Añade la dependencia del SDK de Adapty con npm y actívalo con tu clave pública. Esta es la base — sin esto, nada más funciona. **Guía:** [Instala y configura el SDK de Adapty](sdk-installation-capacitor) :::info Este tutorial está orientado al SDK de Adapty para Capacitor v4 (beta) — la API que enseña el [quickstart](capacitor-quickstart-paywalls). v4 es una versión preliminar, así que asegúrate de que tu LLM fije la versión exacta (`npm install @adapty/capacitor@4.0.0-beta.2`) en lugar de instalar la última versión estable 3.x. Consulta la [sección de instalación del SDK 4.0](sdk-installation-capacitor#adapty-sdk-40-beta) y la [guía de migración](migration-to-capacitor-sdk-v4). ::: Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/sdk-installation-capacitor.md ``` :::tip[Checkpoint] - **Esperado:** La app compila y se ejecuta en iOS y Android. La consola muestra el log de activación de Adapty. - **Problema frecuente:** "Public API key is missing" → verifica que hayas reemplazado el marcador de posición con tu clave real de **App settings**. ::: ### Mostrar paywalls y gestionar compras \{#show-paywalls-and-handle-purchases\} Obtén un paywall por ID de placement, muéstralo y gestiona los eventos de compra. Las guías que necesitas dependen de cómo gestiones las compras. Prueba cada compra en el sandbox a medida que avances — no esperes al final. Consulta [Probar compras en sandbox](test-purchases-in-sandbox) para ver las instrucciones de configuración. <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Flow Builder" default> **Guías:** - [Activar compras usando flows (inicio rápido)](capacitor-quickstart-paywalls) - [Obtener flows y paywalls](capacitor-get-pb-paywalls) - [Mostrar flows y paywalls](capacitor-present-paywalls) - [Gestionar eventos](capacitor-handling-events) - [Responder a acciones](capacitor-handle-paywall-actions) ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/capacitor-quickstart-paywalls.md - https://adapty.io/docs/es/capacitor-get-pb-paywalls.md - https://adapty.io/docs/es/capacitor-present-paywalls.md - https://adapty.io/docs/es/capacitor-handling-events.md - https://adapty.io/docs/es/capacitor-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Esperado:** El flow aparece con los productos configurados. Pulsar un producto activa el diálogo de compra sandbox. - **Problema habitual:** Flow vacío o error de `getFlow` → verifica que el ID del placement coincide exactamente con el del dashboard y que el placement tiene una audiencia asignada. ::: </TabItem> <TabItem value="manual" label="Paywalls manuales"> **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](capacitor-quickstart-manual) - [Obtener paywalls y productos](fetch-paywalls-and-products-capacitor) - [Mostrar paywall diseñado con Remote Config](present-remote-config-paywalls-capacitor) - [Realizar compras](capacitor-making-purchases) - [Restaurar compras](capacitor-restore-purchase) ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/capacitor-quickstart-manual.md - https://adapty.io/docs/es/fetch-paywalls-and-products-capacitor.md - https://adapty.io/docs/es/present-remote-config-paywalls-capacitor.md - https://adapty.io/docs/es/capacitor-making-purchases.md - https://adapty.io/docs/es/capacitor-restore-purchase.md ``` :::tip[Checkpoint] - **Esperado:** Tu paywall personalizado muestra los productos obtenidos desde Adapty. Al pulsar un producto, aparece el diálogo de compra en sandbox. - **Problema frecuente:** Array de productos vacío → verifica que el paywall tenga productos asignados en el dashboard y que el placement tenga una audiencia. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Guías:** - [Descripción general del Observer mode](observer-vs-full-mode) - [Implementar el Observer mode](implement-observer-mode-capacitor) - [Reportar transacciones en el Observer mode](report-transactions-observer-mode-capacitor) ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/observer-vs-full-mode.md - https://adapty.io/docs/es/implement-observer-mode-capacitor.md - https://adapty.io/docs/es/report-transactions-observer-mode-capacitor.md ``` :::tip[Punto de control] - **Resultado esperado:** Tras una compra en sandbox usando tu flow de compra existente, la transacción aparece en el **Event Feed** del Adapty Dashboard. - **Atención:** Si no aparecen eventos, verifica que estás reportando las transacciones a Adapty y que las notificaciones del servidor están configuradas para ambas stores. ::: </TabItem> </Tabs> ### Comprobar el estado de la suscripción \{#check-subscription-status\} Después de una compra, comprueba el perfil del usuario para ver si tiene un nivel de acceso activo y así controlar el acceso al contenido premium. **Guía:** [Comprobar el estado de la suscripción](capacitor-check-subscription-status) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/capacitor-check-subscription-status.md ``` :::tip[Punto de control] - **Resultado esperado:** Tras una compra en sandbox, `profile.accessLevels['premium']?.isActive` devuelve `true`. - **Problema frecuente:** `accessLevels` vacío después de la compra → comprueba que el producto tiene un nivel de acceso asignado en el dashboard. ::: ### Identificar usuarios \{#identify-users\} Vincula las cuentas de usuario de tu app con los perfiles de Adapty para que las compras persistan entre dispositivos. :::important Omite este paso si tu app no tiene autenticación. ::: **Guía:** [Identificar usuarios](capacitor-quickstart-identify) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/capacitor-quickstart-identify.md ``` :::tip[Checkpoint] - **Esperado:** Tras llamar a `adapty.identify()`, la sección **Profiles** del dashboard muestra tu ID de usuario personalizado. - **Atención:** Llama a `identify` después de la activación pero antes de obtener los paywalls para evitar una atribución de perfil anónima. ::: ### Prepárate para el lanzamiento \{#prepare-for-release\} Una vez que tu integración funcione en el sandbox, repasa la lista de verificación de lanzamiento para asegurarte de que todo esté listo para producción. **Guía:** [Lista de verificación de lanzamiento](release-checklist) Envía esto a tu LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/es/release-checklist.md ``` :::tip[Checkpoint] - **Esperado:** Todos los elementos del checklist confirmados: conexiones con el store, notificaciones del servidor, flujo de compra, comprobaciones del nivel de acceso y requisitos de privacidad. - **Problema frecuente:** Notificaciones del servidor ausentes → configura las App Store Server Notifications en **App settings → iOS SDK** y las Google Play Real-Time Developer Notifications en **App settings → Android SDK**. ::: ## Archivos de índice de documentación en texto plano \{#plain-text-doc-index-files\} Si necesitas darle a tu LLM un contexto más amplio que el de páginas individuales, alojamos archivos de índice que listan o combinan toda la documentación de Adapty: - [`llms.txt`](https://adapty.io/docs/es/llms.txt): Lista todas las páginas con enlaces `.md`. Un [estándar emergente](https://llmstxt.org/) para hacer los sitios web accesibles a los LLMs. Ten en cuenta que para algunos agentes de IA (p. ej., ChatGPT) tendrás que descargar `llms.txt` y subirlo al chat como archivo. - [`llms-full.txt`](https://adapty.io/docs/es/llms-full.txt): Toda la documentación de Adapty combinada en un único archivo. Muy grande — úsalo solo cuando necesites una visión completa. - Específicos de Capacitor: [`capacitor-llms.txt`](https://adapty.io/docs/es/capacitor-llms.txt) y [`capacitor-llms-full.txt`](https://adapty.io/docs/es/capacitor-llms-full.txt): Subconjuntos específicos de la plataforma que ahorran tokens en comparación con el sitio completo. --- # File: capacitor-paywalls --- --- title: "Flows y paywalls - Capacitor" description: "Muestra y gestiona flows y paywalls creados con Adapty Flow Builder o Paywall Builder en tu app de Capacitor." --- ## Mostrar paywalls \{#display-paywalls\} ### Adapty Flow Builder y Paywall Builder \{#adapty-flow-builder--paywall-builder\} <CustomDocCardList ids={['capacitor-get-pb-paywalls', 'capacitor-present-paywalls', 'capacitor-handling-events', 'capacitor-handle-paywall-actions']} /> :::tip Para empezar rápidamente con los flows y paywalls de Adapty, consulta nuestra [guía de inicio rápido](capacitor-quickstart-paywalls). ::: ### Implementar paywalls manualmente \{#implement-paywalls-manually\} <CustomDocCardList ids={['capacitor-quickstart-manual', 'fetch-paywalls-and-products-capacitor', 'present-remote-config-paywalls-capacitor', 'capacitor-making-purchases']} /> Para más guías sobre cómo implementar paywalls y gestionar compras manualmente, consulta la [categoría](capacitor-implement-paywalls-manually). ## Funciones útiles \{#useful-features\} <CustomDocCardList ids={['capacitor-use-fallback-paywalls', 'capacitor-web-paywall']} /> --- # File: capacitor-get-pb-paywalls --- --- title: "Obtener flows y paywalls - Capacitor" description: "Obtén flows y paywalls de Adapty en tu aplicación Capacitor." --- <SDKv4> <MethodPromo method="getFlow" /> Después de [diseñar tu flow o paywall en el Paywall Builder](adapty-paywall-builder), puedes mostrarlo en tu aplicación móvil. El primer paso es obtener el flow o paywall asociado al placement y su configuración de vista, tal como se describe a continuación. Ten en cuenta que este tema hace referencia a flows y paywalls personalizados con el Paywall Builder. Si estás implementando tus paywalls de forma manual, consulta el tema [Obtener paywalls y productos para paywalls con Remote Config en tu aplicación móvil](fetch-paywalls-and-products-capacitor). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a mostrar flows y paywalls en tu app móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow/paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow/paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-capacitor) en tu app móvil. </details> ## Obtener un flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall usando el Flow Builder o el Paywall Builder, no tienes que preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese flow o paywall contiene tanto lo que se debe mostrar como la forma en que se debe mostrar. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y luego presentarlo en tu app. Obtén el flow o paywall y crea su [vista](capacitor-get-pb-paywalls#fetch-the-view-configuration) lo antes posible, idealmente mucho antes de mostrarlo. El método `createFlowView` carga la configuración de la vista y comienza a descargar y almacenar en caché sus imágenes en segundo plano. Cuanto antes lo llames, más tiempo tendrán esas descargas para completarse. Para cuando presentes el flow o paywall, su configuración e imágenes ya pueden estar en caché y listas para mostrarse. Para obtener un flow o paywall, usa el método `getFlow`: ```typescript showLineNumbers try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', }); // the requested flow/paywall } catch (error) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | |-------------------|--------|-----------| | **placementId** | requerido | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **fetchPolicy** | por defecto: `'reload_revalidating_cache_data'` | <p>Se pasa dentro del objeto opcional `params`. Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `'return_cache_data_else_load'` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se borra cuando la app se reinstala o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché de actualización periódica descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.</p> | | **loadTimeoutMs** | por defecto: 5 seg | <p>Se pasa dentro del objeto opcional `params`. Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede agotar el tiempo de espera ligeramente después de lo especificado en `loadTimeoutMs`, ya que la operación puede componerse de diferentes solicitudes internamente.</p> | **No codifiques los IDs de productos.** El único ID que debes codificar es el del placement. Los flows y los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un paywall devuelve dos productos hoy y tres mañana, muéstralos todos sin necesidad de modificar el código. Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | Un objeto `AdaptyFlow` con los identificadores del flow (`id`, `variationId`), nombre, placement, sus variaciones de paywall (`paywalls`) y los Remote Configs (`remoteConfigs`). | ## Obtener la configuración de la vista \{#fetch-the-view-configuration\} :::important Asegúrate de activar el botón **Show on device** en el builder. Si esta opción no está activada, la configuración de la vista no estará disponible para recuperar. ::: Si el placement se diseñó en el **Flow Builder** o en el **Paywall Builder**, Adapty renderiza la interfaz de usuario por ti. Crea la vista con `createFlowView` y luego [presenta el flow o el paywall](capacitor-present-paywalls). Si el placement es un paywall personalizado sin interfaz del Builder, [trátalo como un paywall de Remote Config](present-remote-config-paywalls-capacitor). En el SDK de Capacitor, llama a `createFlowView` directamente; no es necesario obtener primero la configuración de la vista. :::warning El resultado del método `createFlowView` solo puede usarse una vez. Si necesitas usarlo de nuevo, llama de nuevo al método `createFlowView`. Llamarlo dos veces sin recrearlo puede producir un error. ::: ```typescript showLineNumbers try { const view = await createFlowView(flow); } catch (error) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | :------------------- | :------- | :----------------------------------------------------------- | | **flow** | obligatorio | Un objeto `AdaptyFlow` para obtener un controlador para el flow/paywall deseado. | | **customTags** | opcional | Define un diccionario de etiquetas personalizadas y sus valores resueltos. Las etiquetas personalizadas actúan como marcadores de posición en el contenido, reemplazados dinámicamente con cadenas específicas para personalizar el contenido dentro del flow/paywall. Consulta el tema Etiquetas personalizadas en Paywall Builder para más detalles. | | **prefetchProducts** | opcional | Actívalo para optimizar el tiempo de visualización de los productos en pantalla. Cuando es `true`, AdaptyUI obtendrá automáticamente los productos necesarios. Valor por defecto: `true`. | | **android.enableSafeArea** | opcional | Solo para Android (ignorado en iOS). Anidado bajo la clave `android`. Cuando es `true`, la vista del flow aplica los márgenes de área segura. Valor por defecto: `true`. El valor por defecto es adecuado para la mayoría de los casos. | :::note Si utilizas varios idiomas, aprende cómo añadir una [localización de flow](add-paywall-locale-in-adapty-paywall-builder) y cómo usar los códigos de idioma correctamente [aquí](capacitor-localizations-and-locale-codes). ::: Una vez que tengas la vista, [presenta el flow/paywall](capacitor-present-paywalls). ## Obtén un flow o paywall para la audiencia predeterminada y acelera la carga \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} En general, los flows y paywalls se obtienen casi de forma instantánea, así que no necesitas preocuparte por optimizar este proceso. Sin embargo, cuando tienes muchas audiencias y placements, y tus usuarios tienen una conexión a internet lenta, la carga de un flow o paywall puede tardar más de lo deseable. En esos casos, puede que quieras mostrar un flow o paywall predeterminado para garantizar una experiencia fluida, en lugar de no mostrar nada. Para solucionar esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow o paywall del placement especificado para la audiencia **All Users**. Sin embargo, es importante entender que el enfoque recomendado es obtener el flow o paywall con el método `getFlow`, tal como se describe en la sección [Obtener flow/paywall](#fetch-flowpaywall) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar distintos paywalls para diferentes versiones de la app (la actual y las futuras), pueden surgir dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (heredada) o asumir que los usuarios con esa versión podrían encontrarse con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluyendo segmentación por país, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas a cambio de una obtención más rápida del flow o paywall, usa el método `getFlowForDefaultAudience` de la siguiente manera. De lo contrario, quédate con `getFlow` descrito [arriba](#fetch-flowpaywall). ::: ```typescript showLineNumbers try { const flow = await adapty.getFlowForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', }); // the requested flow/paywall } catch (error) { // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | predeterminado: `'reload_revalidating_cache_data'` | <p>Se pasa dentro del objeto opcional `params`. Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `'return_cache_data_else_load'` para devolver los datos en caché si existen. En este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la aplicación y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | ## Personalizar recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu flow/paywall, implementa los recursos personalizados. Las imágenes hero y los vídeos tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizados, identificas estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. Aquí tienes un ejemplo de cómo proporcionar recursos personalizados mediante un diccionario simple: ```typescript showLineNumbers const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; const view = await createFlowView(flow, { customAssets }); ``` :::note Si no se encuentra un recurso, el flow/paywall volverá a su apariencia predeterminada. ::: </SDKv4> <SDKv3> Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el nuevo Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu app. El primer paso es obtener el paywall asociado al placement y su configuración de vista, como se describe a continuación. Por favor, ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Para más información sobre cómo obtener paywalls de Remote Config, consulta el tema [Obtener paywalls y productos para paywalls de Remote Config en tu app móvil](fetch-paywalls-and-products-capacitor). <details> <summary>Antes de empezar a mostrar paywalls en tu app móvil (clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-capacitor) en tu aplicación móvil. </details> ## Obtén el paywall diseñado con Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Si has [diseñado un paywall con el Paywall Builder](adapty-paywall-builder), no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Este tipo de paywall contiene tanto lo que se debe mostrar como la forma en que debe mostrarse. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y, después, presentarlo en tu app. Para garantizar un rendimiento óptimo, es fundamental obtener el paywall y su [configuración de vista](capacitor-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) lo antes posible, dejando tiempo suficiente para que las imágenes se descarguen antes de mostrárselas al usuario. Para obtener un paywall, utiliza el método `getPaywall`: ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', }); // the requested paywall } catch (error) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | requerido | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-paywall-locale-in-adapty-paywall-builder). Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **params** | opcional | Parámetros adicionales para obtener el paywall. | **No escribas los IDs de producto en el código.** El único ID que debes incluir directamente en el código es el del placement. Los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un paywall devuelve dos productos hoy y tres mañana, muéstralos todos sin necesidad de modificar el código. Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Un objeto [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall) con una lista de IDs de producto, el identificador del paywall, el Remote Config y otras propiedades adicionales. | ## Obtener la configuración de vista del paywall diseñado con Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Asegúrate de activar el botón **Show on device** en el Paywall Builder. Si esta opción no está activada, la configuración de vista no estará disponible para recuperar. ::: Después de obtener el paywall, comprueba si incluye un `ViewConfiguration`, lo que indica que fue creado con Paywall Builder. Esto te indicará cómo mostrar el paywall. Si el `ViewConfiguration` está presente, trátalo como un paywall de Paywall Builder; si no, [trátalo como un paywall de Remote Config](present-remote-config-paywalls-capacitor). En el SDK de Capacitor, llama directamente al método `createPaywallView` sin necesidad de obtener primero la configuración de la vista manualmente. :::warning El resultado del método `createPaywallView` solo puede utilizarse una vez. Si necesitas usarlo de nuevo, llama al método `createPaywallView` otra vez. ::: ```typescript showLineNumbers if (paywall.hasViewConfiguration) { try { const view = await createPaywallView(paywall); } catch (error) { // handle the error } } else { // use your custom logic } ``` Parámetros: | Parámetro | Presencia | Descripción | | :------------------- | :-------- | :----------------------------------------------------------- | | **paywall** | obligatorio | Un objeto `AdaptyPaywall` para obtener un controlador del paywall deseado. | | **customTags** | opcional | Define un diccionario de etiquetas personalizadas y sus valores resueltos. Las etiquetas personalizadas actúan como marcadores de posición en el contenido del paywall y se reemplazan dinámicamente con cadenas específicas para personalizar el contenido. Consulta el tema Custom tags in paywall builder para más detalles. | | **prefetchProducts** | opcional | Actívalo para optimizar el momento en que se muestran los productos en pantalla. Si es `true`, AdaptyUI obtendrá automáticamente los productos necesarios. Valor predeterminado: `false`. | :::note Si utilizas varios idiomas, aprende cómo añadir una [localización en el Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) y cómo usar los códigos de idioma correctamente [aquí](capacitor-localizations-and-locale-codes). ::: Una vez que tengas la vista, [muestra el paywall](capacitor-present-paywalls). ## Obtén un paywall para la audiencia por defecto y acelera su carga \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Normalmente, los paywalls se obtienen casi de inmediato, por lo que no es necesario preocuparse por acelerar este proceso. Sin embargo, cuando tienes numerosas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, obtener un paywall puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un paywall por defecto para garantizar una experiencia de usuario fluida en lugar de no mostrar ningún paywall. Para abordar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall mediante el método `getPaywall`, tal como se detalla en la sección [Obtener información del paywall](#fetch-paywall-designed-with-paywall-builder) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar distintos paywalls para diferentes versiones de la app (la actual y futuras), tendrás que diseñar paywalls compatibles con la versión actual (legacy) o aceptar que los usuarios de esa versión puedan encontrarse con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que perderás la segmentación personalizada (incluida la basada en países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estos inconvenientes a cambio de una obtención más rápida del paywall, usa el método `getPaywallForDefaultAudience` de la siguiente manera. De lo contrario, utiliza `getPaywall` descrito [anteriormente](#fetch-paywall-designed-with-paywall-builder). ::: ```typescript showLineNumbers try { const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', }); // the requested paywall } catch (error) { // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | requerido | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` indica inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](capacitor-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **params** | opcional | Parámetros adicionales para obtener el paywall. | ## Personalizar recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu paywall, implementa los recursos personalizados. Las imágenes de héroe y los vídeos tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizado, seleccionas estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. A continuación se muestra un ejemplo de cómo puedes proporcionar recursos personalizados mediante un diccionario simple: ```typescript showLineNumbers const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; view = await createPaywallView(paywall, { customAssets }); ``` :::note Si no se encuentra un recurso, el paywall recurrirá a su apariencia predeterminada. ::: </SDKv3> --- # File: capacitor-present-paywalls --- --- title: "Mostrar flows y paywalls - Capacitor" description: "Presenta flows y paywalls a los usuarios en tu aplicación Capacitor con Adapty." --- <SDKv4> Si has creado un flow o paywall en el Flow Builder, no necesitas preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Un flow de este tipo contiene tanto lo que debe mostrarse como la forma en que debe hacerlo. Antes de empezar, asegúrate de que: 1. Has [creado un flow o paywall](create-paywall). 2. Lo has añadido a un [placement](placements). 3. Has [obtenido el flow y preparado la vista](capacitor-get-pb-paywalls). :::warning Esta guía es exclusivamente para **flows y paywalls creados con Paywall Builder**, que requieren SDK v4.0 o posterior. El proceso para presentar flows difiere para los paywalls de Remote Config. - Para presentar **paywalls de Remote Config**, consulta [Mostrar paywall diseñado con Remote Config](present-remote-config-paywalls-capacitor). ::: Para mostrar un flow o paywall como pantalla independiente, usa el método `view.present()` en el `view` creado por el método [`createFlowView`](capacitor-get-pb-paywalls#fetch-the-view-configuration). Cada `view` solo puede usarse una vez. Si necesitas mostrar el flow de nuevo, llama a `createFlowView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo no está permitido. Provocará un error. ::: ```typescript showLineNumbers const view = await createFlowView(flow); // Optional: handle flow events (close, purchase, restore, etc) // await view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` :::important Llamar a `setEventHandlers` varias veces sobreescribirá los handlers que proporciones, reemplazando tanto los predeterminados como los establecidos anteriormente para esos eventos específicos. ::: ## Configura el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se muestra el flow en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `'full_screen'` (por defecto) o `'page_sheet'`. En Android, los flows siempre se muestran como una actividad a pantalla completa. ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## Usar temporizadores definidos por el desarrollador \{#use-developer-defined-timer\} Para usar temporizadores definidos por el desarrollador en tu app, utiliza el `timerId`, en este ejemplo `CUSTOM_TIMER_NY`, el **Timer ID** del temporizador definido por el desarrollador que configuraste en el Adapty dashboard. Esto garantiza que tu app actualice dinámicamente el temporizador con el valor correcto, como `13d 09h 03m 34s` (calculado como la hora de finalización del temporizador, por ejemplo, el Año Nuevo, menos la hora actual). ```typescript showLineNumbers const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createFlowView(flow, { customTimers }); ``` En este ejemplo, `CUSTOM_TIMER_NY` es el **Timer ID** del temporizador definido por el desarrollador que configuraste en el Adapty Dashboard. El temporizador garantiza que tu app actualice dinámicamente el contador con el valor correcto, como `13d 09h 03m 34s` (calculado como la hora de finalización del temporizador, por ejemplo el Año Nuevo, menos la hora actual). ## Mostrar diálogo \{#show-dialog\} Usa este método en lugar de los diálogos de alerta nativos cuando se muestre una vista de flow en Android. En Android, las alertas normales aparecen detrás de la vista del flow, lo que las hace invisibles para los usuarios. Este método garantiza que el diálogo se muestre correctamente por encima del flow en todas las plataformas. ```typescript showLineNumbers try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the flow await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## Reemplazar una suscripción por otra \{#replace-one-subscription-with-another\} Cuando un usuario intenta comprar una nueva suscripción mientras ya tiene otra activa en Android, puedes controlar cómo debe gestionarse la nueva compra pasando parámetros de actualización de suscripción al crear la vista del flow. Para reemplazar la suscripción actual por la nueva, usa `productPurchaseParams` en `createFlowView` con los parámetros `oldSubVendorProductId` y `prorationMode`. ```typescript showLineNumbers const productPurchaseParams = flow.paywalls .flatMap((paywall) => paywall.productIdentifiers) .map((productId) => { const params: MakePurchaseParamsInput = {}; if (Capacitor.getPlatform() === 'android') { params.android = { subscriptionUpdateParams: { oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION', prorationMode: 'with_time_proration', }, }; } return { productId, params }; }); const view = await createFlowView(flow, { productPurchaseParams }); ``` </SDKv4> <SDKv3> Si has personalizado un paywall con el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese paywall contiene tanto qué mostrar como de qué forma mostrarlo. :::warning Esta guía es exclusivamente para **paywalls creados con el Paywall Builder**. El proceso para mostrar paywalls difiere en el caso de los paywalls con Remote Config. Para mostrar **paywalls con Remote Config**, consulta [Renderizar paywalls diseñados con Remote Config](present-remote-config-paywalls). ::: Para mostrar un paywall, usa el método `view.present()` en el `view` creado por el método [`createPaywallView`](capacitor-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Cada `view` solo puede usarse una vez. Si necesitas mostrar el paywall de nuevo, llama a `createPaywallView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo puede causar un error. ::: ```typescript showLineNumbers const view = await createPaywallView(paywall); view.setEventHandlers({ onUrlPress(url) { window.open(url, '_blank'); return false; }, }); try { await view.present(); } catch (error) { // handle the error } ``` ## Usar temporizadores definidos por el desarrollador \{#use-developer-defined-timer\} Para usar temporizadores definidos por el desarrollador en tu app, utiliza el `timerId`; en este ejemplo, `CUSTOM_TIMER_NY`, el **Timer ID** del temporizador definido por el desarrollador que configuraste en el Adapty dashboard. Esto garantiza que tu app actualice dinámicamente el temporizador con el valor correcto, como `13d 09h 03m 34s` (calculado como la hora de finalización del temporizador, por ejemplo, el Año Nuevo, menos la hora actual). ```typescript showLineNumbers const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createPaywallView(paywall, { customTimers }); ``` En este ejemplo, `CUSTOM_TIMER_NY` es el **Timer ID** del temporizador definido por el desarrollador que configuraste en el Adapty dashboard. El temporizador garantiza que tu app actualice dinámicamente el temporizador con el valor correcto, como `13d 09h 03m 34s` (calculado como la hora de finalización del temporizador, por ejemplo el Año Nuevo, menos la hora actual). ## Mostrar un diálogo \{#show-dialog\} Usa este método en lugar de los diálogos de alerta nativos cuando haya una vista de paywall en pantalla en Android. En Android, las alertas normales aparecen detrás de la vista del paywall, lo que las hace invisibles para los usuarios. Este método garantiza que el diálogo se muestre correctamente por encima del paywall en todas las plataformas. ```typescript showLineNumbers title="Capacitor" try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el paywall en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `'full_screen'` (por defecto) o `'page_sheet'`. ```typescript showLineNumbers await view.present({ iosPresentationStyle: 'page_sheet' }); ``` </SDKv3> --- # File: capacitor-handle-paywall-actions --- --- title: "Responder a acciones de flow - Capacitor" description: "Gestiona acciones de botones de flows y paywalls en Capacitor usando Adapty para una mejor monetización de la app." --- <SDKv4> Si estás desarrollando flows o paywalls con el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder) de Adapty, es fundamental configurar los botones correctamente: 1. Añade un [botón en el builder](paywall-buttons) y asígnale una acción existente o crea un ID de acción personalizado. 2. Escribe el código en tu app para gestionar cada acción que hayas asignado. Esta guía explica cómo gestionar acciones personalizadas y acciones existentes en tu código. :::warning **Las compras, restauraciones, el flow, el cierre de paywalls y la apertura de URLs se gestionan automáticamente.** Puedes configurar su comportamiento predeterminado o implementar respuestas para acciones personalizadas. ::: :::note Configurar un manejador para un evento reemplaza completamente su comportamiento predeterminado. Los manejadores que no configures conservan su comportamiento por defecto. ::: ## Cerrar flows y paywalls \{#close-flows-and-paywalls\} Para añadir un botón que cierre tu flow o paywall: 1. En el builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un handler para la acción `close` que cierre el flow o paywall. :::info En el SDK de Capacitor, la acción `close` activa el cierre del flow o paywall por defecto. Sin embargo, puedes sobreescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un flow podría activar la apertura de otro. ::: ```typescript showLineNumbers const view = await createFlowView(flow); const unsubscribe = await view.setEventHandlers({ onCloseButtonPress() { return true; // allow the flow to close }, }); ``` En Android, el botón **Back** del sistema y el gesto de retroceso activan un evento `onAndroidSystemBack` independiente. En el SDK v4, ya no cierra el flow por defecto. Devuelve `true` desde el handler si quieres que el botón **Back** cierre el flow: ```typescript showLineNumbers const unsubscribe = await view.setEventHandlers({ onAndroidSystemBack() { return true; // close the flow when the Back button is pressed }, }); ``` ## Abrir URLs desde flows y paywalls \{#open-urls-from-flows-and-paywalls\} :::tip Si quieres añadir un grupo de enlaces (por ejemplo, términos de uso y restauración de compras), añade un elemento **Link** en el builder y trátalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu flow o paywall (por ejemplo, **Terms of use** o **Privacy policy**): 1. En el builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieres abrir. 2. Si es necesario, en el código de tu app, implementa un handler para la acción `openUrl` que abra la URL recibida a tu manera. :::info En el SDK de Capacitor, al pulsar una URL se abre en el navegador nativo por defecto: el SDK llama a `adapty.openWebUrl({ url, openIn })`, respetando la opción **Open in** que hayas configurado en el builder, y mantiene el flow abierto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. ::: ```typescript showLineNumbers const unsubscribe = await view.setEventHandlers({ onUrlPress(url) { // Open the URL your own way, e.g. with the Capacitor Browser plugin Browser.open({ url }); return false; // keep the flow open }, }); ``` ## Gestionar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el builder, añade un botón, asígnale la acción **Custom** y dale un ID. 2. En el código de tu app, implementa un handler para el ID de acción que has creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro flow o paywall: ```typescript showLineNumbers const unsubscribe = await view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another flow or paywall } }, }); ``` </SDKv4> <SDKv3> Si estás creando paywalls con el Paywall Builder de Adapty, es fundamental configurar los botones correctamente: 1. Añade un [botón en el Paywall Builder](paywall-buttons) y asígnale una acción existente o crea un ID de acción personalizado. 2. Escribe código en tu app para gestionar cada acción que hayas asignado. Esta guía muestra cómo gestionar acciones personalizadas y predefinidas en tu código. ## Cerrar paywalls \{#close-paywalls\} Para añadir un botón que cierre tu paywall: 1. En el Paywall Builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un manejador para la acción `close` que descarte el paywall. :::info En el SDK de Capacitor, la acción `close` cierra el paywall de forma predeterminada. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un paywall podría activar la apertura de otro. ::: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { console.log('User closed paywall'); return true; // Allow the paywall to close } }); ``` ## Abrir URLs desde paywalls \{#open-urls-from-paywalls\} :::tip Si quieres añadir un grupo de enlaces (por ejemplo, términos de uso y restauración de compras), añade un elemento **Link** en el Paywall Builder y trátalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu paywall (por ejemplo, **Terms of use** o **Privacy policy**): 1. En el Paywall Builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieres abrir. 2. En el código de tu app, implementa un handler para la acción `openUrl` que abra la URL recibida en un navegador. :::info En el SDK de Capacitor, la acción `window.open` activa la apertura de la URL de forma predeterminada. Sin embargo, puedes sobreescribir este comportamiento en tu código si es necesario. ::: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onUrlPress(url) { window.open(url, '_blank'); return false; // Don't close the paywall }, }); ``` ## Iniciar sesión en la aplicación \{#log-into-the-app\} Para añadir un botón que permita a los usuarios iniciar sesión en tu aplicación: 1. En el paywall builder, añade un botón y asígnale la acción **Login**. 2. En el código de tu aplicación, implementa un manejador para la acción `login` que identifique a tu usuario. ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'login') { // Navigate to login screen console.log('User requested login'); } } }); ``` ## Gestiona acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el Paywall Builder, añade un botón, asígnale la acción **Custom** y dale un ID. 2. En el código de tu app, implementa un manejador para el ID de acción que hayas creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro paywall: ```typescript showLineNumbers const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another paywall } }, }); ``` </SDKv3> --- # File: capacitor-handling-events --- --- title: "Gestionar eventos de flow y paywall - Capacitor" description: "Gestiona eventos de flow y paywall en tu app de Capacitor con el SDK de Adapty." --- <SDKv4> :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de flows. También puedes configurar el manejo de botones (cerrar el flow, abrir enlaces, acciones personalizadas, etc.). Consulta nuestra [guía sobre el manejo de acciones de botones](capacitor-handle-paywall-actions) para más detalles. ::: Los flows y paywalls creados con el [Flow Builder](adapty-flow-builder) no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan algunos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selección de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el flow. A continuación se explica cómo responder a estos eventos. Para controlar o monitorizar los procesos que ocurren en la pantalla del flow dentro de tu app, implementa el método `view.setEventHandlers`: :::important Solo puedes establecer un handler por evento: llamar a `setEventHandlers` varias veces sobreescribirá los handlers que proporciones, reemplazando tanto los predeterminados como los establecidos previamente para esos eventos específicos. Los handlers que no establezcas conservarán su comportamiento predeterminado. `setEventHandlers` devuelve una función para cancelar la suscripción, y `view.dismiss()` elimina todos los handlers. ::: ```typescript showLineNumbers const view = await createFlowView(flow); const unsubscribe = await view.setEventHandlers({ onCloseButtonPress() { return true; // close the flow (default behavior) }, onAndroidSystemBack() { return true; // close the flow; by default, it stays open }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type === 'success'; // close the flow on a successful purchase, keep it open for cancelled or pending purchases }, onPurchaseStarted(product) { /***/ }, onPurchaseFailed(error, product) { /***/ }, onRestoreCompleted(profile) { /***/ }, onRestoreFailed(error) { /***/ }, onProductSelected(productId) { /***/ }, onError(error) { /***/ }, onLoadingProductsFailed(error) { /***/ }, onUrlPress(url, openIn) { adapty.openWebUrl({ url, openIn }).catch(console.warn); // same as the SDK default return false; // keep the flow open }, onAppeared() { /***/ }, onDisappeared() { /***/ }, onWebPaymentNavigationFinished() { /***/ }, }); ``` <Details> <summary>Ejemplos de eventos (haz clic para expandir)</summary> Los ejemplos a continuación muestran las propiedades disponibles en cada manejador, con valores ilustrativos en los comentarios. ```typescript // onUrlPress url; // 'https://example.com/terms' openIn; // 'browser_in_app' or 'browser_out_app' // onCustomAction actionId; // 'login' // onProductSelected productId; // 'premium_monthly' // onPurchaseStarted, onPurchaseCompleted, onPurchaseFailed product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price?.amount; // 9.99 product.price?.currencyCode; // 'USD' product.price?.localizedString; // '$9.99' // onPurchaseCompleted purchaseResult.type; // 'success', 'pending', or 'user_cancelled' if (purchaseResult.type === 'success') { purchaseResult.profile.accessLevels['premium']?.isActive; // true } // onRestoreCompleted profile.accessLevels['premium']?.isActive; // true // onPurchaseFailed, onRestoreFailed, onError, onLoadingProductsFailed error.message; // 'Purchase failed due to insufficient funds' ``` </Details> Puedes registrar solo los manejadores de eventos que necesites y omitir los que no. De este modo, no se crearán listeners para eventos no utilizados. No hay manejadores de eventos obligatorios. Los manejadores de eventos devuelven un booleano. Si devuelven `true`, el proceso de visualización se considera completado, por lo que la pantalla del flow se cierra y los listeners de eventos de esa vista se eliminan. Algunos manejadores de eventos tienen un comportamiento predeterminado que puedes modificar si lo necesitas: - `onCloseButtonPress`: cierra el flow cuando se pulsa el botón de cerrar. - `onUrlPress`: abre la URL pulsada en el navegador nativo mediante `adapty.openWebUrl`, respetando la opción **Open in** definida en el builder, y mantiene el flow abierto. - `onAndroidSystemBack`: mantiene el flow abierto cuando se pulsa el botón **Back**. Devuelve `true` para cerrarlo. - `onPurchaseCompleted`: mantiene el flow abierto tras completar una compra. Devuelve `true` para cerrarlo. - `onRestoreCompleted`: mantiene el flow abierto tras una restauración exitosa. Devuelve `true` para cerrarlo. - `onError`: cierra el flow si falla su renderizado. ### Controladores de eventos \{#event-handlers\} | Manejador de eventos | Descripción | |:-----------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | Se invoca cuando el usuario realiza una acción personalizada, por ejemplo, hace clic en un [botón personalizado](paywall-buttons). | | **onUrlPress** | Se invoca cuando el usuario hace clic en una URL dentro de tu flow. | | **onAndroidSystemBack** | Se invoca cuando el usuario pulsa el botón **Back** del sistema Android. El flow permanece abierto por defecto; devuelve `true` para cerrarlo. | | **onCloseButtonPress** | Se invoca cuando el botón de cierre es visible y el usuario lo pulsa. Se recomienda cerrar la pantalla del flow en este manejador. | | **onPurchaseCompleted** | Se invoca cuando la compra finaliza, ya sea con éxito, cancelada por el usuario o pendiente de aprobación. En caso de compra exitosa, proporciona un `AdaptyProfile` actualizado. Las cancelaciones del usuario y los pagos pendientes (p. ej., que requieren aprobación parental) activan este evento, no `onPurchaseFailed`. | | **onPurchaseStarted** | Se invoca cuando el usuario pulsa el botón de acción "Comprar" para iniciar el proceso de compra. | | **onPurchaseFailed** | Se invoca cuando una compra falla debido a errores (p. ej., restricciones de pago, productos no válidos, fallos de red, errores de verificación de transacciones). No se invoca en cancelaciones del usuario ni en pagos pendientes, que activan `onPurchaseCompleted` en su lugar. | | **onRestoreStarted** | Se invoca cuando el usuario inicia el proceso de restauración de compras. | | **onRestoreCompleted** | Se invoca cuando la restauración de compras se realiza correctamente y proporciona un `AdaptyProfile` actualizado. Se recomienda cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de la suscripción](capacitor-listen-subscription-changes) para saber cómo comprobarlo. | | **onRestoreFailed** | Se invoca cuando el proceso de restauración falla y proporciona un `AdaptyError`. | | **onProductSelected** | Se invoca cuando se selecciona cualquier producto en la vista del flow, lo que permite monitorizar qué selecciona el usuario antes de la compra. | | **onError** | Se invoca cuando ocurre un error durante el renderizado de la vista y proporciona un `AdaptyError`. Estos errores no deberían producirse, así que si encuentras uno, comunícanoslo. | | **onLoadingProductsFailed** | Se invoca cuando falla la carga de productos y proporciona un `AdaptyError`. Si no has establecido `prefetchProducts: true` al crear la vista, AdaptyUI recuperará los objetos necesarios del servidor por sí mismo. | | **onAppeared** | Se invoca cuando el flow se muestra al usuario. En iOS, también se invoca cuando el usuario pulsa el [botón de paywall web](web-paywall#step-2a-add-a-web-purchase-button) dentro de un flow y el paywall web se abre en un navegador in-app. | | **onDisappeared** | Se invoca cuando el usuario cierra el flow. En iOS, también se invoca cuando un [paywall web](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un flow en un navegador in-app desaparece de la pantalla. | | **onWebPaymentNavigationFinished** | Se invoca tras intentar abrir un [paywall web](web-paywall) para realizar una compra, independientemente de si tuvo éxito o falló. | | **onRequestAppReview** | Reservado para solicitudes de valoración de la app desde un flow. Los flows todavía no activan solicitudes de valoración, por lo que no es necesario implementarlo. | | **onAnalytics** | Reservado para eventos analíticos personalizados desde un flow. Los flows todavía no emiten estos eventos a tu código, por lo que no es necesario implementarlo. | | **onRequestPermission** | Reservado para solicitudes de permisos del sistema (como notificaciones push o acceso a la cámara) desde un flow. Los flows todavía no activan solicitudes de permisos, por lo que no es necesario implementarlo. | | **onObserverPurchaseInitiated** | Solo en modo observador: se invoca cuando el usuario pulsa el botón de compra en un flow. Adapty no realiza la compra — efectúala con tu propio código de compra y luego notifica la transacción a Adapty. Consulta [Gestionar compras en modo observador](#handle-purchases-in-observer-mode) más abajo. | | **onObserverRestoreInitiated** | Solo en modo observador: se invoca cuando el usuario pulsa el botón de restauración en un flow. Adapty no restaura — hazlo tú mismo y luego notifica las transacciones restauradas. Consulta [Gestionar compras en modo observador](#handle-purchases-in-observer-mode) más abajo. | ### Gestionar compras en modo observer \{#handle-purchases-in-observer-mode\} Si activaste el SDK en [modo Observer](implement-observer-mode-capacitor) (`observerMode: true`) y muestras un flow renderizado por Adapty, el SDK no realiza las compras por ti. Cuando un usuario pulsa el botón de compra o restauración, el SDK invoca `onObserverPurchaseInitiated` o `onObserverRestoreInitiated`, para que puedas realizar la compra o restauración con tu propio código. Consulta [Presentar flows en modo Observer](capacitor-present-flows-in-observer-mode) para ver la configuración completa. </SDKv4> <SDKv3> :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de paywalls. También debes implementar el manejo de botones (cerrar el paywall, abrir enlaces, etc.). Consulta nuestra [guía sobre el manejo de acciones de botones](capacitor-handle-paywall-actions) para más detalles. ::: Los paywall configurados con el [Paywall Builder](adapty-paywall-builder) no necesitan código adicional para realizar ni restaurar compras. Sin embargo, generan ciertos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el paywall. A continuación te explicamos cómo responder a estos eventos. Para controlar o monitorizar los procesos que ocurren en la pantalla del paywall dentro de tu app, implementa el método `view.setEventHandlers`: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { console.log('User closed paywall'); return true; // Allow the paywall to close }, onAndroidSystemBack() { console.log('User pressed back button'); return true; // Allow the paywall to close }, onAppeared() { console.log('Paywall appeared'); return false; // Don't close the paywall }, onDisappeared() { console.log('Paywall disappeared'); }, onPurchaseCompleted(purchaseResult, product) { console.log('Purchase completed:', purchaseResult); return purchaseResult.type !== 'user_cancelled'; // Close if not cancelled }, onPurchaseStarted(product) { console.log('Purchase started:', product); return false; // Don't close the paywall }, onPurchaseFailed(error, product) { console.error('Purchase failed:', error); return false; // Don't close the paywall }, onRestoreCompleted(profile) { console.log('Restore completed:', profile); return true; // Close the paywall after successful restore }, onRestoreFailed(error) { console.error('Restore failed:', error); return false; // Don't close the paywall }, onProductSelected(productId) { console.log('Product selected:', productId); return false; // Don't close the paywall }, onRenderingFailed(error) { console.error('Rendering failed:', error); return false; // Don't close the paywall }, onLoadingProductsFailed(error) { console.error('Loading products failed:', error); return false; // Don't close the paywall }, onUrlPress(url) { window.open(url, '_blank'); return false; // Don't close the paywall }, }); ``` <Details> <summary>Ejemplos de eventos (haz clic para ampliar)</summary> ```typescript // onCloseButtonPress { "event": "close_button_press" } // onAndroidSystemBack { "event": "android_system_back" } // onAppeared { "event": "paywall_shown" } // onDisappeared { "event": "paywall_closed" } // onUrlPress { "event": "url_press", "url": "https://example.com/terms" } // onCustomAction { "event": "custom_action", "actionId": "login" } // onProductSelected { "event": "product_selected", "productId": "premium_monthly" } // onPurchaseStarted { "event": "purchase_started", "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // onPurchaseCompleted - Success { "event": "purchase_completed", "purchaseResult": { "type": "success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // onPurchaseCompleted - Cancelled { "event": "purchase_completed", "purchaseResult": { "type": "user_cancelled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // onPurchaseFailed { "event": "purchase_failed", "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } // onRestoreCompleted { "event": "restore_completed", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } // onRestoreFailed { "event": "restore_failed", "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } // onRenderingFailed { "event": "rendering_failed", "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } // onLoadingProductsFailed { "event": "loading_products_failed", "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> Puedes registrar solo los manejadores de eventos que necesites y omitir los que no uses. Así no se crean listeners innecesarios. No hay manejadores de eventos obligatorios. Los manejadores de eventos devuelven un booleano. Si devuelven `true`, el proceso de visualización se considera completado, por lo que la pantalla del paywall se cierra y los listeners de eventos de esa vista se eliminan. Algunos manejadores de eventos tienen un comportamiento predeterminado que puedes sobrescribir si lo necesitas: - `onCloseButtonPress`: cierra el paywall cuando se pulsa el botón de cerrar. - `onAndroidSystemBack`: cierra el paywall cuando se pulsa el botón **Back**. - `onRestoreCompleted`: cierra el paywall tras una restauración exitosa. - `onPurchaseCompleted`: cierra el paywall salvo que el usuario haya cancelado. - `onRenderingFailed`: cierra el paywall si falla su renderizado. - `onUrlPress`: abre las URLs en el navegador del sistema y mantiene el paywall abierto. ### Manejadores de eventos \{#event-handlers\} | Manejador de eventos | Descripción | |:----------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | Se invoca cuando el usuario realiza una acción personalizada, p. ej., pulsa un [botón personalizado](paywall-buttons). | | **onUrlPress** | Se invoca cuando el usuario pulsa una URL en tu paywall. | | **onAndroidSystemBack** | Se invoca cuando el usuario pulsa el botón de sistema **Back** de Android. | | **onCloseButtonPress** | Se invoca cuando el botón de cierre está visible y el usuario lo pulsa. Se recomienda cerrar la pantalla del paywall en este manejador. | | **onPurchaseCompleted** | Se invoca cuando la compra finaliza, ya sea con éxito, cancelada por el usuario o pendiente de aprobación. En caso de compra exitosa, proporciona un `AdaptyProfile` actualizado. Las cancelaciones del usuario y los pagos pendientes (p. ej., requieren aprobación parental) disparan este evento, no `onPurchaseFailed`. | | **onPurchaseStarted** | Se invoca cuando el usuario pulsa el botón de acción "Comprar" para iniciar el proceso de compra. | | **onPurchaseCancelled** | Se invoca cuando el usuario inicia el proceso de compra y lo interrumpe manualmente (cancela el diálogo de pago). | | **onPurchaseFailed** | Se invoca cuando una compra falla por errores (p. ej., restricciones de pago, productos no válidos, fallos de red, fallos en la verificación de la transacción). No se invoca para cancelaciones del usuario ni pagos pendientes, que en su lugar disparan `onPurchaseCompleted`. | | **onRestoreStarted** | Se invoca cuando el usuario inicia un proceso de restauración de compras. | | **onRestoreCompleted** | Se invoca cuando la restauración de compras se completa con éxito y proporciona un `AdaptyProfile` actualizado. Se recomienda cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de suscripción](capacitor-listen-subscription-changes) para saber cómo comprobarlo. | | **onRestoreFailed** | Se invoca cuando el proceso de restauración falla y proporciona `AdaptyError`. | | **onProductSelected** | Se invoca cuando se selecciona cualquier producto en la vista del paywall, lo que te permite monitorizar lo que el usuario elige antes de la compra. | | **onAppeared** | Se invoca cuando la vista del paywall aparece en pantalla. En iOS, también se invoca cuando el usuario pulsa el [botón de paywall web](web-paywall#step-2a-add-a-web-purchase-button) dentro de un paywall y se abre un paywall web en un navegador integrado en la app. | | **onDisappeared** | Se invoca cuando la vista del paywall desaparece de la pantalla. En iOS, también se invoca cuando un [paywall web](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un paywall en un navegador integrado en la app desaparece de la pantalla. | | **onRenderingFailed** | Se invoca cuando ocurre un error durante el renderizado de la vista y proporciona `AdaptyError`. Estos errores no deberían producirse, así que si te encuentras con uno, comunícanoslo. | | **onLoadingProductsFailed** | Se invoca cuando la carga de productos falla y proporciona `AdaptyError`. Si no has establecido `prefetchProducts: true` en la creación de la vista, AdaptyUI recuperará los objetos necesarios del servidor por su cuenta. | </SDKv3> --- # File: capacitor-use-fallback-paywalls --- --- title: "Capacitor - Usar paywalls de respaldo" description: "Gestiona los casos en que los usuarios están sin conexión o los servidores de Adapty no están disponibles" --- Para mantener una experiencia de usuario fluida, es importante configurar [respaldos](/fallback-paywalls) para tus flows, [paywalls](paywalls) y [onboardings](onboardings). Esta precaución amplía las capacidades de la aplicación en caso de pérdida parcial o total de la conexión a internet. * **Si la aplicación no puede acceder a los servidores de Adapty:** Podrá mostrar un flow o paywall de respaldo, y acceder a la configuración local del onboarding. * **Si la aplicación no puede acceder a internet:** Podrá mostrar un flow o paywall de respaldo. Los onboardings incluyen contenido remoto y requieren conexión a internet para funcionar. :::important Antes de seguir los pasos de esta guía, [descarga](/local-fallback-paywalls) los archivos de configuración de respaldo desde Adapty. ::: ## Configuración \{#configuration\} ### Android 1. Añade el archivo de configuración de respaldo a tu aplicación. Selecciona uno de los siguientes directorios: * **android/app/src/main/assets/** * **android/app/src/main/res/raw/** :::note La carpeta `res/raw` tiene una convención de nombres especial (deben comenzar con una letra, sin mayúsculas, sin caracteres especiales excepto el guión bajo, y sin espacios en los nombres). ::: 2. Actualiza la propiedad `android` de la constante `FileLocation`: * Si el archivo está en el directorio `assets`, indica la ruta del archivo relativa a ese directorio. * Si el archivo está en el directorio `res/raw`, indica el nombre del archivo sin la extensión. ### iOS \{#ios\} 1. Añade el archivo JSON de respaldo al bundle de tu proyecto: abre el menú **File** en XCode y selecciona la opción **Add Files to "YourProjectName"**. 2. Pasa el nombre de tu archivo de configuración a la propiedad `ios` de la constante `FileLocation`. ## Ejemplo \{#example\} ```typescript showLineNumbers const fileLocation = { ios: { fileName: 'ios_fallback.json' }, android: { //if the file is located in 'android/app/src/main/assets/' relativeAssetPath: 'android_fallback.json' } }; await adapty.setFallback({ fileLocation }); ``` :::important `setFallback` debe ejecutarse antes de que el SDK obtenga el flow, paywall u onboarding de destino. ::: Parámetros: | Parámetro | Descripción | | :------------------- | :------------------------------------------------------- | | **fileLocation** | Objeto que representa la ubicación del archivo de configuración de respaldo. | --- # File: capacitor-localizations-and-locale-codes --- --- title: "Usar localizaciones y códigos de idioma en el SDK de Capacitor" description: "Aprende a localizar paywalls en tu app de Capacitor con el SDK de Adapty." --- <SDKv4> ## Por qué esto es importante \{#why-this-is-important\} Los códigos de idioma entran en juego cuando Adapty selecciona la localización para un flow y cuando lees un Remote Config para un paywall personalizado. Los códigos de idioma son complejos y pueden variar de una plataforma a otra, por lo que Adapty utiliza un estándar interno único para todas las plataformas que admite. Entender ese estándar te ayuda a predecir qué localización recibirá cada usuario. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código se compone de subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués (Brasil)), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de idioma \{#locale-code-matching\} Cuando Adapty busca la localización que corresponde al idioma de un usuario, ocurre lo siguiente: 1. La cadena de idioma se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. Adapty busca la localización con el código de idioma que coincida exactamente 3. Si no se encuentra ninguna coincidencia, Adapty toma la subcadena antes del primer guión (`pt` para `pt-br`) y busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, Adapty devuelve la localización predeterminada `en` De esta forma, `'pt_BR'`, `pt-BR` y `pt-br` resuelven a la misma localización. ## Implementación de localizaciones \{#implementing-localizations\} En SDK v4, no necesitas pasar un código de idioma al obtener un flow. - **Paywalls de Flow Builder y Paywall Builder**: Adapty resuelve la localización automáticamente a partir del dispositivo y las localizaciones que configuraste en el builder. Renderiza el flow con `createFlowView` — no se necesita código de idioma. - **Paywalls personalizados (Remote Config)**: `getFlow` devuelve todas las localizaciones configuradas en `flow.remoteConfigs`. Cada entrada tiene un código `lang` y un objeto `data`. Selecciona la entrada que corresponda al usuario, con tu propio fallback: ```typescript showLineNumbers const flow = await adapty.getFlow({ placementId: 'placement_id' }); const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0]; // read your values from config?.data ``` Las reglas de coincidencia de código de idioma descritas anteriormente explican cómo Adapty normaliza los códigos `lang` almacenados en cada Remote Config. </SDKv4> <SDKv3> ## Por qué esto es importante \{#why-this-is-important\} Hay varios escenarios en los que los códigos de idioma entran en juego; por ejemplo, cuando intentas obtener el paywall correcto para la localización actual de tu app. Como los códigos de idioma son complejos y pueden variar de una plataforma a otra, usamos un estándar interno para todas las plataformas que soportamos. Sin embargo, precisamente por esa complejidad, es fundamental que entiendas exactamente qué estás enviando a nuestro servidor para obtener la localización correcta y qué ocurre después, de modo que siempre recibas lo que esperas. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código está formado por subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués de Brasil), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de idioma \{#locale-code-matching\} Cuando Adapty recibe una llamada del SDK con el código de idioma y comienza a buscar la localización correspondiente de un paywall, ocurre lo siguiente: 1. La cadena de idioma recibida se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. Se busca la localización con el código de idioma que coincida exactamente 3. Si no se encuentra ninguna coincidencia, se toma la subcadena antes del primer guión (`pt` para `pt-br`) y se busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, se devuelve la localización predeterminada `en` De este modo, un dispositivo iOS que envíe `'pt_BR'`, un dispositivo Android que envíe `pt-BR` y otro dispositivo que envíe `pt-br` obtendrán el mismo resultado. ## Implementación de localizaciones: forma recomendada \{#implementing-localizations-recommended-way\} Si estás pensando en localizaciones, es probable que ya estés trabajando con los archivos de cadenas localizadas en tu proyecto. En ese caso, te recomendamos añadir un par clave-valor con el código de idioma de Adapty correspondiente en cada uno de tus archivos de localización. Después, extrae el valor de esa clave al llamar a nuestro SDK, así: ```javascript showLineNumbers // 1. Modify your localization files (e.g., using react-i18next) /* en.json */ { "adapty_paywalls_locale": "en" } /* es.json */ { "adapty_paywalls_locale": "es" } /* pt-BR.json */ { "adapty_paywalls_locale": "pt-br" } // 2. Extract and use the locale code const MyComponent = () => { const { t } = useTranslation(); const fetchPaywall = async () => { const locale = t('adapty_paywalls_locale'); // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; }; ``` Así te aseguras de tener control total sobre qué localización se recuperará para cada usuario de tu app. ## Implementación de localizaciones: el otro método \{#implementing-localizations-the-other-way\} Puedes obtener resultados similares (aunque no idénticos) sin definir explícitamente códigos de idioma para cada localización. Eso implicaría extraer un código de idioma de otros objetos que proporciona tu plataforma, así: ```javascript showLineNumbers const getLocaleCode = () => { if (Capacitor.getPlatform() === 'ios') { return navigator.language || 'en'; } else { return navigator.language || 'en'; } }; const fetchPaywall = async () => { const locale = getLocaleCode(); // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; ``` Ten en cuenta que no recomendamos este enfoque por varias razones: 1. En iOS, los idiomas preferidos y el locale actual no son idénticos. Si quieres que la localización se seleccione correctamente, tendrás que apoyarte en la lógica de Apple, que funciona de forma automática si usas el enfoque recomendado con archivos de cadenas localizadas, o recrearla manualmente. 2. Es difícil predecir exactamente qué recibirá el servidor de Adapty. Por ejemplo, en iOS es posible obtener un locale como `ar_OM@numbers='latn'` en un dispositivo y enviarlo a nuestro servidor. En ese caso, en lugar de la localización `ar-om` que buscabas, obtendrás `ar`, lo cual probablemente no sea lo esperado. Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: capacitor-web-paywall --- --- title: "Implementar paywalls web" description: "Aprende a implementar paywalls web en tu app de Capacitor con el SDK de Adapty." --- :::important Antes de comenzar, asegúrate de haber [configurado tu paywall web en el dashboard](web-paywall) y de tener instalada la versión 3.6.1 o posterior del SDK de Adapty. ::: ## Paywalls web abiertos \{#open-web-paywalls\} Si trabajas con un paywall que desarrollaste tú mismo, necesitas gestionar los paywalls web mediante el método del SDK. El método `.openWebPaywall`: 1. Genera una URL única que permite a Adapty vincular un paywall concreto mostrado a un usuario específico con la página web a la que es redirigido. 2. Rastrea cuándo tus usuarios regresan a la app y luego solicita `.getProfile` a intervalos cortos para determinar si los derechos de acceso del perfil se han actualizado. De este modo, si el pago se ha completado con éxito y los derechos de acceso se han actualizado, la suscripción se activa en la app casi de inmediato. ```typescript showLineNumbers try { await adapty.openWebPaywall({ paywallOrProduct: product }); } catch (error) { console.error('Failed to open web paywall:', error); } ``` :::note Hay dos versiones del método `openWebPaywall`: 1. `openWebPaywall({ paywallOrProduct: product })` que genera URLs por paywall y también añade los datos del producto a las URLs. 2. `openWebPaywall({ paywallOrProduct: paywall })` que genera URLs por paywall sin añadir los datos del producto a las URLs. Úsalo cuando los productos de tu paywall de Adapty sean diferentes de los del paywall web. En el SDK v4, la parte del paywall de `paywallOrProduct` toma un `AdaptyFlowPaywall` — una variante del paywall del flow obtenido. Comprueba que `flow.paywalls` no esté vacío antes de acceder por índice, por ejemplo `flow.paywalls[0]`. ::: #### Gestión de errores \{#handle-errors\} | Error | Descripción | Acción recomendada | |-----------------------------------------|----------------------------------------------------------------------|-------------------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | El paywall no tiene configurada una URL de compra web | Comprueba si el paywall está correctamente configurado en el Adapty Dashboard | | AdaptyError.productWithoutPurchaseUrl | El producto no tiene una URL de compra web | Verifica la configuración del producto en el Adapty Dashboard | | AdaptyError.failedOpeningWebPaywallUrl | No se pudo abrir la URL en el navegador | Revisa la configuración del dispositivo o proporciona un método de compra alternativo | | AdaptyError.failedDecodingWebPaywallUrl | No se pudieron codificar correctamente los parámetros en la URL | Verifica que los parámetros de la URL sean válidos y estén correctamente formateados | ## Obtén la URL del paywall web sin abrirlo \{#get-the-web-paywall-url-without-opening-it\} Si quieres mostrar la página de compra web tú mismo en lugar de dejar que el SDK la abra, usa `createWebPaywallUrl`. Devuelve la misma URL única que abriría `openWebPaywall`, así que puedes renderizarla en tu propio web view o gestionar la redirección como prefieras. Acepta el mismo argumento `paywallOrProduct`: una variante de paywall del flow obtenido (`AdaptyFlowPaywall`) o un `AdaptyPaywallProduct`. ```typescript showLineNumbers try { const url = await adapty.createWebPaywallUrl({ paywallOrProduct: product }); // open `url` in your own web view, or handle the redirect yourself } catch (error) { console.error('Failed to create web paywall URL:', error); } ``` :::note Para abrir una URL arbitraria (no un paywall web) a través del navegador nativo — por ejemplo, desde el botón de un flow — usa [`adapty.openWebUrl`](capacitor-handle-paywall-actions#open-urls-from-flows-and-paywalls) en su lugar. ::: ## Abrir web paywalls en un navegador in-app \{#open-web-paywalls-in-an-in-app-browser\} :::important Abrir web paywalls en un navegador in-app está disponible a partir de la versión 3.15 del SDK de Adapty. ::: Por defecto, los web paywalls se abren en el navegador externo. Para ofrecer una experiencia de usuario fluida, puedes abrir los web paywalls en un navegador in-app. Esto muestra la página de compra web dentro de tu aplicación, permitiendo a los usuarios completar las transacciones sin cambiar de app. Para activarlo, establece `openIn` como `WebPresentation.BrowserInApp` en `openWebPaywall`: ```typescript showLineNumbers try { await adapty.openWebPaywall({ paywallOrProduct: product, openIn: WebPresentation.BrowserInApp, // default – WebPresentation.BrowserOutApp }); } catch (error) { console.error('Failed to open web paywall:', error); } ``` --- # File: capacitor-present-flows-in-observer-mode --- --- title: "Presentar flows en modo observador en el SDK de Capacitor" description: "Presenta flows y paywalls del Paywall Builder en modo observador en tu aplicación de Capacitor mientras gestionas las compras con tu propio código." --- Si has personalizado un flow o paywall usando el builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese flow o paywall ya contiene tanto qué mostrar como cómo mostrarlo. :::warning Esta sección hace referencia únicamente al [modo Observer](observer-vs-full-mode). Si no trabajas en modo Observer, consulta el tema [Mostrar flows y paywalls](capacitor-present-paywalls). ::: :::info Esta funcionalidad requiere Adapty Capacitor SDK 4.0 o posterior — anteriormente solo estaba disponible en los SDKs nativos de iOS y Android. Consulta la [guía de migración](migration-to-capacitor-sdk-v4) para actualizar. ::: <details> <summary>Antes de empezar a mostrar flows (haz clic para expandir)</summary> 1. Configura la integración inicial de Adapty [con App Store](initial_ios) y [con Google Play](initial-android). 2. Instala y configura el SDK de Adapty. Asegúrate de establecer el parámetro `observerMode` en `true`. Consulta la [guía de instalación del SDK de Capacitor](sdk-installation-capacitor#activate-adapty-module-of-adapty-sdk). 3. [Crea productos](create-product) en el Adapty Dashboard. 4. [Configura flows o paywalls en los builders](create-paywall) y asígnales productos. 5. [Crea placements y asígnales tus flows o paywalls](create-placement). 6. [Obtén los flows y su configuración](capacitor-get-pb-paywalls) en el código de tu app. </details> En el modo Observer, el SDK no realiza compras por ti. Cuando un usuario pulsa el botón de compra o restauración en un flow o paywall renderizado por Adapty, el SDK invoca tu manejador de eventos `onObserverPurchaseInitiated` o `onObserverRestoreInitiated` en su lugar — realiza la compra o restauración con tu propio código allí. 1. Establece los manejadores de eventos del modo observer en la vista. A diferencia de otras plataformas, no hay un objeto resolver separado — los manejadores forman parte de los [manejadores de eventos](capacitor-handling-events) habituales, así que establécelos en cada vista que crees: ```typescript showLineNumbers title="Capacitor" import { adapty, createFlowView } from '@adapty/capacitor'; const view = await createFlowView(flow); const unsubscribe = await view.setEventHandlers({ onObserverPurchaseInitiated(product, onStartPurchase, onFinishPurchase) { onStartPurchase(); // the view shows its loading indicator myPurchaseApi(product.vendorProductId) .then((transactionId) => adapty.reportTransaction({ transactionId, variationId: flow.variationId }), ) .finally(() => onFinishPurchase()); // the view hides the loading indicator return false; // keep the flow open; dismiss it yourself after success }, onObserverRestoreInitiated(onStartRestore, onFinishRestore) { onStartRestore(); myRestoreApi().finally(() => onFinishRestore()); return false; }, }); ``` El handler `onObserverPurchaseInitiated` te informa de que el usuario ha iniciado una compra, y `onObserverRestoreInitiated` — de que el usuario ha iniciado una restauración. Activa tu flow de compra o restauración personalizado en respuesta. Además, recuerda invocar los siguientes callbacks para notificar a AdaptyUI sobre el proceso de compra o restauración. Esto es necesario para el comportamiento correcto del flow, como mostrar el loader, entre otras cosas: | Callback | Description | | :----------------- | :------------------------------------------------------------------------------- | | onStartPurchase() | Se debe invocar este callback para notificar a AdaptyUI que la compra ha comenzado. | | onFinishPurchase() | Se debe invocar este callback para notificar a AdaptyUI que la compra ha finalizado. | | onStartRestore() | Se debe invocar este callback para notificar a AdaptyUI que la restauración ha comenzado. | | onFinishRestore() | Se debe invocar este callback para notificar a AdaptyUI que la restauración ha finalizado. | 2. Presenta la vista del flow como de costumbre: [obtén el flow y crea su vista](capacitor-get-pb-paywalls), luego [preséntala](capacitor-present-paywalls). No se necesitan parámetros adicionales — los handlers se activan solo cuando el SDK fue activado con `observerMode: true`. :::warning No olvides [reportar la transacción y asociarla con el paywall](report-transactions-observer-mode-capacitor). De lo contrario, Adapty no reconocerá la transacción y no podrá determinar el paywall origen de la compra. ::: --- # File: capacitor-implement-paywalls-manually --- --- title: "Implementar paywalls manualmente" description: "Aprende a implementar paywalls manualmente en tu app de Capacitor con el SDK de Adapty." --- ## Aceptar compras \{#accept-purchases\} Si estás trabajando con paywalls que has implementado tú mismo, puedes delegar el manejo de las compras a Adapty usando el método `makePurchase`. De esta forma, gestionaremos todos los escenarios de usuario y solo tendrás que manejar los resultados de la compra. :::important `makePurchase` funciona con productos creados en el Adapty Dashboard. Asegúrate de configurar los productos y las formas de recuperarlos en el dashboard siguiendo la [guía de inicio rápido](quickstart). ::: <CustomDocCardList ids={['capacitor-quickstart-manual', 'fetch-paywalls-and-products-capacitor', 'present-remote-config-paywalls-capacitor', 'capacitor-making-purchases', 'capacitor-restore-purchase']} /> ## Modo observador \{#observer-mode\} Si quieres implementar tu propia lógica de manejo de compras desde cero pero aun así aprovechar los análisis avanzados de Adapty, puedes usar el modo observador. :::important Consulta las limitaciones del modo observador [aquí](observer-vs-full-mode). ::: <CustomDocCardList ids={['implement-observer-mode-capacitor', 'report-transactions-observer-mode-capacitor']} /> --- # File: capacitor-quickstart-manual --- --- title: "Habilitar compras en tu paywall personalizado con el SDK de Capacitor" description: "Integra el SDK de Adapty en tus paywalls personalizados de Capacitor para habilitar las compras in-app." --- Esta guía describe cómo integrar Adapty en tus paywalls personalizados. Mantén el control total sobre la implementación del paywall, mientras el SDK de Adapty obtiene los productos, gestiona las nuevas compras y restaura las anteriores. Esta guía utiliza las APIs del SDK de Adapty para Capacitor v4; si usas v3, consulta la [guía de migración](migration-to-capacitor-sdk-v4) para los nombres de métodos correspondientes. :::important **Esta guía es para desarrolladores que implementan paywalls personalizados.** Si quieres la forma más sencilla de habilitar compras, usa el [Adapty Paywall Builder](capacitor-quickstart-paywalls). Con Paywall Builder, creas paywalls en un editor visual sin código, Adapty gestiona toda la lógica de compra automáticamente y puedes probar distintos diseños sin volver a publicar tu app. ::: ## Antes de empezar \{#before-you-start\} ### Configurar productos \{#set-up-products\} Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls**](paywalls) – configuraciones que definen qué productos ofrecer. En Adapty, los paywalls son la única forma de recuperar productos, pero este diseño te permite modificar productos, precios y ofertas sin tocar el código de tu app. En SDK v4, las variaciones de paywall para un placement las lleva un objeto **flow** — obtienes un flow y consultas sus productos. - [**Placements**](placements) – dónde y cuándo mostrar paywalls en tu app (como `main`, `onboarding`, `settings`). Configuras los paywalls para los placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar diferentes paywalls a distintos usuarios. Asegúrate de entender estos conceptos aunque trabajes con tu propio paywall personalizado. Básicamente, son la forma en que gestionas los productos que vendes en tu app. Para implementar tu paywall personalizado, tendrás que crear un **paywall** y añadirlo a un **placement**. Esta configuración te permite recuperar tus productos. Para entender qué debes hacer en el dashboard, sigue la guía de inicio rápido [aquí](quickstart). ### Gestionar usuarios \{#manage-users\} Puedes trabajar con o sin autenticación de backend en tu lado. Sin embargo, el SDK gestiona de forma distinta a los usuarios anónimos e identificados. Lee la [guía de inicio rápido de identificación](capacitor-quickstart-identify) para entender las particularidades y asegurarte de que estás trabajando correctamente con los usuarios. ## Paso 1. Obtener productos \{#step-1-get-products\} Para obtener los productos de tu paywall personalizado, necesitas: 1. Obtener el objeto `flow` pasando el ID del [placement](placements) al método `getFlow`. 2. Obtener el array de productos para este flow usando el método `getPaywallProducts`. ```typescript showLineNumbers async function loadPaywall() { try { const flow: AdaptyFlow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); const products: AdaptyPaywallProduct[] = await adapty.getPaywallProducts({ flow }); // Use products to build your custom paywall UI } catch (error) { // Handle the error } } ``` ## Paso 2. Acepta compras \{#step-2-accept-purchases\} Cuando un usuario toca un producto en tu paywall personalizado, llama al método `makePurchase` con el producto seleccionado. Esto gestionará el flow de compra y devolverá el perfil actualizado. ```typescript showLineNumbers async function purchaseProduct(product: AdaptyPaywallProduct) { try { const result: AdaptyPurchaseResult = await adapty.makePurchase({ product }); if (result.type === 'success') { // Purchase successful, profile updated } else if (result.type === 'user_cancelled') { // User canceled the purchase } else if (result.type === 'pending') { // Purchase is pending (e.g., user will pay offline with cash) } } catch (error) { // Handle the error } } ``` ## Paso 3. Restaurar compras \{#step-3-restore-purchases\} Los stores exigen que todas las apps con suscripciones ofrezcan una forma de restaurar las compras anteriores. Llama al método `restorePurchases` cuando el usuario pulse el botón de restaurar. Esto sincronizará su historial de compras con Adapty y devolverá el perfil actualizado. ```typescript showLineNumbers async function restorePurchases() { try { const profile: AdaptyProfile = await adapty.restorePurchases(); // Restore successful, profile updated } catch (error) { // Handle the error } } ``` ## Paso 4. Comprueba el estado de la suscripción \{#step-4-check-the-subscription-status\} Después de una compra o restauración, comprueba el [nivel de acceso](access-level) del usuario para decidir si mostrar el paywall o desbloquear las funciones de pago. Los métodos `makePurchase` y `restorePurchases` ya devuelven el perfil actualizado; cuando necesites el estado actual en otro punto de la app, usa el método `getProfile`: ```typescript showLineNumbers async function hasPremiumAccess(): Promise<boolean> { try { const profile = await adapty.getProfile(); return profile.accessLevels?.['premium']?.isActive ?? false; } catch (error) { // Handle the error } return false; } ``` Para más formas de comprobar y monitorizar el estado de la suscripción, incluida la escucha de actualizaciones en tiempo real, consulta [Comprobar el estado de la suscripción](capacitor-check-subscription-status). ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox de App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. Para ver cómo funciona en una implementación lista para producción, consulta el [App.tsx](https://github.com/adaptyteam/AdaptySDK-Capacitor/blob/master/examples/adapty-devtools/src/screens/app/App.tsx) en nuestra app de ejemplo, que muestra el manejo de compras con gestión de errores adecuada, estados de carga e integración completa del SDK. --- # File: fetch-paywalls-and-products-capacitor --- --- title: "Obtener paywalls y productos para paywalls con Remote Config en el SDK de Capacitor" description: "Obtén paywalls y productos en el SDK de Capacitor de Adapty para mejorar la monetización de usuarios." --- <SDKv4> Antes de mostrar el Remote Config y los paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia al Remote Config y a los paywalls personalizados. Para obtener orientación sobre cómo recuperar flows o paywalls personalizados en el **Flow Builder** o en el **Paywall Builder**, consulta [Obtener flows de Flow Builder y paywalls de Paywall Builder y su configuración](capacitor-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener flows y productos en tu app (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow o paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow o paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-capacitor) en tu aplicación móvil. </details> ## Obtener información del flow \{#fetch-flow-information\} En Adapty, un [producto](product) es una combinación de productos tanto de App Store como de Google Play. Estos productos multiplataforma se integran en flows y paywalls, lo que te permite mostrarlos en placements concretos de tu app móvil. Para mostrar los productos, necesitas obtener un `AdaptyFlow` de uno de tus [placements](placements) con el método `getFlow`. :::important **No codifiques los IDs de producto.** El único ID que debes codificar es el del placement. Los flows se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un flow devuelve dos productos hoy y tres mañana, muéstralos todos sin modificar el código. ::: ```typescript showLineNumbers try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); // the requested flow } catch (error) { console.error('Failed to fetch flow:', error); } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | requerido | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **params.fetchPolicy** | <p>opcional</p><p>predeterminado: `'reload_revalidating_cache_data'`</p> | <p>Por defecto, el SDK intentará cargar datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `'return_cache_data_else_load'` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando la app se reinstala o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena flows y paywalls en dos capas: la caché de actualización regular descrita anteriormente y los [paywalls de respaldo](capacitor-use-fallback-paywalls). También usamos CDN para obtener flows y paywalls más rápido, y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus flows, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **params.loadTimeoutMs** | <p>opcional</p><p>predeterminado: 5000 ms</p> | <p>Este valor limita el tiempo de espera (en milisegundos) para este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el fallback local.</p><p></p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo de espera especificado en `loadTimeoutMs`, ya que la operación puede estar compuesta de diferentes solicitudes internamente.</p> | :::note En la v4, `getFlow` ya no acepta un parámetro `locale`. En el caso de paywalls personalizados, todos los idiomas disponibles se devuelven en el Remote Config del flow (`flow.remoteConfigs`); elige el que coincida con el dispositivo o la configuración del usuario. ::: ¡No pongas IDs de productos en el código! Como los flows se configuran de forma remota, los productos disponibles, el número de productos y las ofertas especiales (como períodos de prueba gratuitos) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si inicialmente obtienes 2 productos, tu app debería mostrar esos 2 productos. Sin embargo, si más adelante obtienes 3 productos, tu app debería mostrar los 3 sin necesidad de ningún cambio en el código. Lo único que tienes que poner en el código es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Un objeto `AdaptyFlow` que contiene el placement, los identificadores (`id`, `variationId`), el nombre, sus variaciones de paywall (`paywalls`) y un array `remoteConfigs` (una entrada por cada idioma configurado). Para obtener los productos del flow, llama a `getPaywallProducts({ flow })`. | ## Obtener productos \{#fetch-products\} Una vez que tienes el flow, puedes consultar el array de productos que le corresponde: ```typescript showLineNumbers try { const products = await adapty.getPaywallProducts({ flow }); // the requested products list } catch (error) { console.error('Failed to fetch products:', error); } ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Lista de objetos [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y otras propiedades adicionales. | Al implementar tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct). A continuación se ilustran las propiedades más utilizadas, pero consulta el documento enlazado para ver todos los detalles sobre las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país de la store seleccionado por el usuario, no en el idioma del dispositivo. | | **Price** | Para mostrar el precio localizado, usa `product.price?.localizedString`. Esta localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número con `product.price?.amount`. El valor se devuelve en la moneda local. Para obtener el símbolo de moneda correspondiente, usa `product.price?.currencySymbol`. | | **Subscription Period** | Para mostrar el período (p. ej., semana, mes, año, etc.), usa `product.subscription?.localizedSubscriptionPeriod`. Esta localización se basa en el idioma del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscription?.subscriptionPeriod`. Desde ahí puedes acceder a la propiedad `unit` para conocer la unidad (es decir, `'day'`, `'week'`, `'month'`, `'year'` o `'unknown'`). El valor `numberOfUnits` indica el número de unidades del período. Por ejemplo, en una suscripción trimestral verás `'month'` en la propiedad `unit` y `3` en `numberOfUnits`. | | **Introductory Offer** | Para mostrar un badge u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscription?.offer?.phases`. Es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase encontrarás las siguientes propiedades útiles:<br/>• `paymentMode`: una cadena con los valores `'free_trial'`, `'pay_as_you_go'`, `'pay_up_front'` y `'unknown'`. Las pruebas gratuitas serán del tipo `'free_trial'`.<br/>• `price`: el precio con descuento como número. En las pruebas gratuitas, busca `0` aquí.<br/>• `localizedNumberOfPeriods`: una cadena localizada según el idioma del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días muestra `'3 days'` en este campo.<br/>• `subscriptionPeriod`: alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma manera que se describe en la sección anterior.<br/>• `localizedSubscriptionPeriod`: el período de suscripción con descuento formateado según el idioma del usuario. | ## Acelera la obtención de flows con el flow de audiencia predeterminada \{#speed-up-flow-fetching-with-default-audience-flow\} Normalmente, los flows se obtienen casi de forma instantánea, por lo que no es necesario preocuparse por acelerar este proceso. Sin embargo, cuando tienes numerosas audiencias y placements, y tus usuarios tienen una conexión a internet lenta, la obtención de un flow puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un flow predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar nada. Para solucionar esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el flow con el método `getFlow`, tal como se describe en la sección [Obtener información del flow](fetch-paywalls-and-products-capacitor#fetch-flow-information) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` presenta algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar flows distintos para diferentes versiones de la app (la actual y las futuras), podrías encontrarte con dificultades. Tendrás que diseñar flows compatibles con la versión actual (heredada) o asumir que los usuarios con esa versión podrían tener problemas al renderizar los flows. - **Pérdida de targeting**: Todos los usuarios verán el mismo flow diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluida la segmentación por país, atribución de marketing o atributos personalizados propios). Si estás dispuesto a aceptar estos inconvenientes para beneficiarte de una recuperación más rápida del flow, utiliza el método `getFlowForDefaultAudience` como se describe a continuación. De lo contrario, sigue usando `getFlow` descrito [anteriormente](fetch-paywalls-and-products-capacitor#fetch-flow-information). ::: ```typescript showLineNumbers try { const flow = await adapty.getFlowForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', params: { fetchPolicy: 'reload_revalidating_cache_data' // Load from server, fallback to cache } }); // the requested flow } catch (error) { console.error('Failed to fetch default audience flow:', error); } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **params.fetchPolicy** | <p>opcional</p><p>por defecto: `'reload_revalidating_cache_data'`</p> | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `'return_cache_data_else_load'` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo intermitente que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | </SDKv4> <SDKv3> Antes de mostrar el Remote Config y los paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia al Remote Config y a los paywalls personalizados. Para obtener orientación sobre cómo obtener paywalls personalizados con el Paywall Builder, consulta [Obtener paywalls del Paywall Builder y su configuración](capacitor-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener paywalls y productos en tu aplicación móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en tu paywall](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-capacitor) en tu aplicación móvil. </details> ## Obtener información del paywall \{#fetch-paywall-information\} En Adapty, un [producto](product) es una combinación de productos del App Store y Google Play. Estos productos multiplataforma se integran en paywalls, lo que te permite mostrarlos en placements específicos de tu app móvil. Para mostrar los productos, necesitas obtener un [Paywall](paywalls) de uno de tus [placements](placements) con el método `getPaywall`. ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); // the requested paywall } catch (error) { console.error('Failed to fetch paywall:', error); } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](capacitor-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **params.fetchPolicy** | <p>opcional</p><p>por defecto: `'reload_revalidating_cache_data'`</p> | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `'return_cache_data_else_load'` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo irregular que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la aplicación y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | | **params.loadTimeoutMs** | <p>opcional</p><p>por defecto: 5000 ms</p> | <p>Este valor limita el tiempo de espera (en milisegundos) de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p></p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeoutMs`, ya que la operación puede estar compuesta por distintas peticiones internamente.</p> | **No incluyas product IDs en el código.** El único ID que debes incluir en el código es el del placement. Los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un paywall devuelve dos productos hoy y tres mañana, muéstralos todos sin necesidad de cambiar el código. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Un objeto [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall) con: una lista de IDs de productos, el identificador del paywall, Remote Config y otras propiedades. | ## Obtener productos \{#fetch-products\} Una vez que tienes el paywall, puedes consultar el array de productos que le corresponde: ```typescript showLineNumbers try { const products = await adapty.getPaywallProducts({ paywall }); // the requested products list } catch (error) { console.error('Failed to fetch products:', error); } ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Lista de objetos [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) con: identificador de producto, nombre del producto, precio, moneda, duración de la suscripción y otras propiedades. | Al implementar tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct). A continuación se muestran las propiedades más utilizadas, pero consulta el documento enlazado para obtener detalles completos sobre todas las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país del store seleccionado por el usuario, no en el idioma del dispositivo. | | **Price** | Para mostrar el precio localizado, usa `product.price?.localizedString`. Esta localización se basa en la información de idioma del dispositivo. También puedes acceder al precio como número mediante `product.price?.amount`. El valor se expresará en la moneda local. Para obtener el símbolo de moneda correspondiente, usa `product.price?.currencySymbol`. | | **Subscription Period** | Para mostrar el período (p. ej., semana, mes, año, etc.), usa `product.subscription?.localizedSubscriptionPeriod`. Esta localización se basa en el idioma del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscription?.subscriptionPeriod`. Desde ahí puedes acceder a la propiedad `unit` para conocer la unidad (es decir, `'day'`, `'week'`, `'month'`, `'year'` o `'unknown'`). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, en una suscripción trimestral verás `'month'` en la propiedad `unit` y `3` en la propiedad `numberOfUnits`. | | **Introductory Offer** | Para mostrar un badge u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscription?.offer?.phases`. Es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase encontrarás las siguientes propiedades útiles:<br/>• `paymentMode`: cadena de texto con los valores `'free_trial'`, `'pay_as_you_go'`, `'pay_up_front'` y `'unknown'`. Las pruebas gratuitas corresponden al tipo `'free_trial'`.<br/>• `price`: el precio con descuento como número. En las pruebas gratuitas, este valor será `0`.<br/>• `localizedNumberOfPeriods`: cadena localizada según el idioma del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días muestra `'3 days'` en este campo.<br/>• `subscriptionPeriod`: alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma manera para las ofertas que en la sección anterior.<br/>• `localizedSubscriptionPeriod`: período de suscripción del descuento formateado según el idioma del usuario. | ## Acelera la carga del paywall con el paywall de la audiencia predeterminada \{#speed-up-paywall-fetching-with-default-audience-paywall\} Normalmente, los paywalls se cargan casi al instante, por lo que no tienes que preocuparte por optimizar este proceso. Sin embargo, cuando tienes muchas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, cargar un paywall puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un paywall predeterminado para garantizar una buena experiencia de usuario en lugar de no mostrar ninguno. Para solucionar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement indicado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall mediante el método `getPaywall`, tal como se detalla en la sección [Obtener información del paywall](fetch-paywalls-and-products-capacitor#fetch-paywall-information) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes paywalls para distintas versiones de la app (la actual y las futuras), puedes encontrarte con dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (legacy) o asumir que los usuarios con esa versión podrían tener problemas con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluyendo por país, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas para beneficiarte de una obtención más rápida de paywalls, usa el método `getPaywallForDefaultAudience` como se indica a continuación. De lo contrario, utiliza `getPaywall` descrito [anteriormente](fetch-paywalls-and-products-capacitor#fetch-paywall-information). ::: ```typescript showLineNumbers try { const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data' // Load from server, fallback to cache } }); // the requested paywall } catch (error) { console.error('Failed to fetch default audience paywall:', error); } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` representa el inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](capacitor-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **params.fetchPolicy** | <p>opcional</p><p>por defecto: `'reload_revalidating_cache_data'`</p> | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que los usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `'return_cache_data_else_load'` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la aplicación y solo se borra al desinstalarla o mediante una limpieza manual.</p> | </SDKv3> --- # File: present-remote-config-paywalls-capacitor --- --- title: "Renderizar paywall diseñado con Remote Config en Capacitor SDK" description: "Descubre cómo presentar paywalls de Remote Config en Adapty Capacitor SDK para personalizar la experiencia del usuario." --- <SDKv4> Si has personalizado un flow con Remote Config, tendrás que implementar el renderizado en el código de tu app móvil para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú controlas qué se incluye y cómo se ve la vista del flow. Proporcionamos un método para obtener la configuración remota, dándote autonomía para mostrar tu flow personalizado configurado mediante Remote Config. ## Obtener el Remote Config del flow y mostrarlo \{#get-flow-remote-config-and-present-it\} En v4, un flow incluye una entrada `AdaptyRemoteConfig` por cada idioma configurado en el array `remoteConfigs`. Elige el idioma que coincida con las preferencias del usuario y lee los valores que necesites desde su `data`. ```typescript showLineNumbers try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0]; const headerText = config?.data?.['header_text']; } catch (error) { console.error('Failed to fetch flow:', error); } ``` En este punto, una vez que hayas recibido todos los valores necesarios, es momento de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a distintas pantallas y orientaciones de móvil, ofreciendo una experiencia fluida y fácil de usar en diferentes dispositivos. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls-capacitor#track-paywall-view-events) como se describe a continuación, para que los análisis de Adapty puedan recopilar información para funnels y pruebas A/B. ::: Una vez que hayas mostrado el flow, continúa con la configuración del flujo de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu flow. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](capacitor-making-purchases). Te recomendamos [crear un paywall de respaldo llamado fallback paywall](capacitor-use-fallback-paywalls). Este paywall de respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Registrar eventos de vista de flow \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus flows. Aunque los datos de compras se recopilan automáticamente, registrar las vistas de los flows requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un flow. Para registrar un evento de vista de flow, simplemente llama a `.logShowFlow({ flow })`, y quedará reflejado en las métricas de tu paywall en los embudos y pruebas A/B. :::important No es necesario llamar a `.logShowFlow({ flow })` si estás mostrando flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder). En esos casos, Adapty registra las visualizaciones automáticamente. ::: ```typescript showLineNumbers await adapty.logShowFlow({ flow }); ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :-------- | :-------- |:-------------------------------------------------------------------------------------| | **flow** | requerido | Un objeto `AdaptyFlow` obtenido mediante `adapty.getFlow({ placementId })`. | </SDKv4> <SDKv3> Si has personalizado un paywall mediante Remote Config, tendrás que implementar el renderizado en el código de tu aplicación móvil para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú decides qué incluir y cómo se ve la vista del paywall. Proporcionamos un método para obtener la configuración remota, dándote la autonomía de mostrar tu paywall personalizado configurado a través de Remote Config. ## Obtener el Remote Config de un paywall y mostrarlo \{#get-paywall-remote-config-and-present-it\} Para obtener el Remote Config de un paywall, accede a la propiedad `remoteConfig` y extrae los valores que necesitas. ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); const headerText = paywall.remoteConfig?.data?.['header_text']; } catch (error) { console.error('Failed to fetch paywall:', error); } ``` En este punto, una vez que hayas recibido todos los valores necesarios, es momento de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a distintos tamaños de pantalla y orientaciones de móvil, ofreciendo una experiencia fluida y fácil de usar en cualquier dispositivo. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls-capacitor#track-paywall-view-events-1) tal como se describe a continuación, para que los análisis de Adapty puedan capturar información para los embudos y las pruebas A/B. ::: Una vez que hayas terminado de mostrar el paywall, continúa configurando el flujo de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu paywall. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](capacitor-making-purchases). Te recomendamos [crear un paywall de respaldo llamado paywall de respaldo](capacitor-use-fallback-paywalls). Este respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Seguimiento de eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus paywalls. Aunque los datos de compras se recopilan automáticamente, registrar las visualizaciones de paywalls requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un paywall. Para registrar un evento de visualización de paywall, simplemente llama a `.logShowPaywall(paywall)`, y quedará reflejado en las métricas de tu paywall en los embudos y las pruebas A/B. :::important No es necesario llamar a `.logShowPaywall(paywall)` si estás mostrando paywalls creados en el [Paywall Builder](adapty-paywall-builder). ::: ```typescript showLineNumbers try { await adapty.logShowPaywall({ paywall }); } catch (error) { console.error('Failed to log paywall view:', error); } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :-------- | :--------------------------------------------------------- | | **paywall** | requerido | Un objeto [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall). | </SDKv3> --- # File: capacitor-making-purchases --- --- title: "Realizar compras en la app móvil con el SDK de Capacitor" description: "Guía para gestionar compras in-app y suscripciones con Adapty." --- Mostrar paywalls en tu app móvil es un paso esencial para ofrecer a los usuarios acceso a contenido o servicios premium. Sin embargo, con solo mostrarlos es suficiente para gestionar compras únicamente si usas [Paywall Builder](adapty-paywall-builder) para personalizar tus paywalls. Si no usas el Paywall Builder, debes usar un método independiente llamado `.makePurchase()` para completar una compra y desbloquear el contenido deseado. Este método es la puerta de entrada para que los usuarios interactúen con los paywalls y realicen sus transacciones. Si tu paywall tiene una oferta promocional activa para el producto que el usuario intenta comprar, Adapty la aplicará automáticamente en el momento de la compra. Asegúrate de haber [realizado la configuración inicial](quickstart) sin saltarte ningún paso. Sin ella, no podemos validar las compras. ## Realizar una compra \{#make-purchase\} :::note **¿Usas [Paywall Builder](adapty-paywall-builder)?** Las compras se procesan automáticamente, puedes saltarte este paso. **¿Buscas una guía paso a paso?** Consulta la [guía de inicio rápido](capacitor-implement-paywalls-manually) para ver las instrucciones de implementación completas con todo el contexto. ::: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product }); if (result.type === 'success') { const isSubscribed = result.profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features console.log('User is now subscribed!'); } } else if (result.type === 'user_cancelled') { console.log('Purchase cancelled by user'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { console.error('Purchase failed:', error); } ``` | Parámetro | Presencia | Descripción | | :---------- | :-------- |:----------------------------------------------------------------------------------------------------------------------------| | **product** | obligatorio | Un objeto [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) obtenido del flow mediante `getPaywallProducts`. | Parámetros de respuesta: | Parámetro | Descripción | |---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **result** | Un objeto [`AdaptyPurchaseResult`](https://capacitor.adapty.io/types/adaptypurchaseresult) con un campo `type` que indica el resultado de la compra (`'success'`, `'user_cancelled'` o `'pending'`) y un campo `profile` que contiene el [`AdaptyProfile`](https://capacitor.adapty.io/interfaces/adaptyprofile) actualizado en las compras exitosas. | ## Cambiar de suscripción al realizar una compra \{#change-subscription-when-making-a-purchase\} Cuando un usuario elige una nueva suscripción en lugar de renovar la actual, el funcionamiento depende del store: - En el App Store, la suscripción se actualiza automáticamente dentro del grupo de suscripciones. Si un usuario compra una suscripción de un grupo mientras ya tiene una suscripción de otro, ambas suscripciones estarán activas al mismo tiempo. - En Google Play, la suscripción no se actualiza automáticamente. Deberás gestionar el cambio en el código de tu aplicación tal como se describe a continuación. Para reemplazar una suscripción por otra en Android, llama al método `.makePurchase()` con el parámetro adicional: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product, params: { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } } }); if (result.type === 'success') { const isSubscribed = result.profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features console.log('Subscription updated successfully!'); } } else if (result.type === 'user_cancelled') { console.log('Purchase cancelled by user'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { console.error('Purchase failed:', error); } ``` Parámetro de solicitud adicional: | Parámetro | Presencia | Descripción | | :--------- | :-------- | :----------------------------------------------------------- | | **params** | opcional | Un objeto de tipo [`MakePurchaseParamsInput`](https://capacitor.adapty.io/types/makepurchaseparamsinput) que contiene parámetros de compra específicos de la plataforma. | La estructura `MakePurchaseParamsInput` incluye: ```typescript { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } } ``` Puedes leer más sobre suscripciones y modos de reemplazo en la documentación para desarrolladores de Google: - [Sobre los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Recomendaciones de Google para los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Modo de reemplazo [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Nota: este método solo está disponible para actualizaciones de suscripción. No se admiten las reducciones. - Modo de reemplazo [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Nota: el cambio de suscripción efectivo solo se producirá cuando finalice el período de facturación actual. ### Gestionar planes prepagos (Android) \{#manage-prepaid-plans-android\} Si los usuarios de tu app pueden adquirir [planes prepagos](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (por ejemplo, comprar una suscripción no renovable por varios meses), puedes habilitar las [transacciones pendientes](https://developer.android.com/google/play/billing/subscriptions#pending) para dichos planes. ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { android: { pendingPrepaidPlansEnabled: true, }, } }); ``` ## Canjear códigos de oferta en iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Sobre los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios concretos. A diferencia de las ofertas habituales, que se aplican de forma automática, los códigos de oferta se distribuyen fuera de la app — por email, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, accediendo a una URL de canje o a través de un diálogo dentro de la app. Para configurar códigos de oferta, abre una suscripción en App Store Connect y ve a su sección **Offer Codes**. Puedes crear [tres tipos](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) de códigos de oferta: - **Free** — la suscripción es gratuita durante un período determinado y la siguiente renovación se cobra al precio completo. - **Pay as you go** — el usuario paga un precio reducido en cada ciclo de facturación durante un período determinado y, después, la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único reducido por toda la duración de la oferta y, después, la suscripción se renueva al precio completo. No es necesario añadir los códigos de oferta a Adapty. Apple etiqueta cada transacción durante el período de la oferta con la categoría del código de oferta. Esto incluye el canje inicial y todas las renovaciones con descuento posteriores. Adapty detecta la etiqueta y registra cada transacción con la categoría de oferta `offer_code`. Cuando termina el período de oferta y la suscripción se renueva al precio completo, la etiqueta deja de estar presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Resolución de discrepancias en los ingresos \{#revenue-discrepancy-troubleshooting\} Si observas que una transacción con código de oferta aparece en Adapty al precio completo del producto en lugar del precio reducido, verifica lo siguiente en App Store Connect: - El código de oferta tiene los precios correctos configurados para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está configurado para el país o región específica del usuario. Apple envía el precio regional en la transacción. Si no hay ningún precio regional configurado para la oferta, Apple puede enviar el precio completo del producto. Puedes filtrar y verificar las transacciones con código de oferta en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds) mediante los filtros de tipo de oferta **Offer Code** y **Offer Discount Type**. #### Códigos promocionales heredados (obsoletos) \{#legacy-promo-codes-deprecated\} :::warning Apple dejó obsoletos los códigos promocionales para compras in-app en marzo de 2026. Los códigos de oferta los sustituyen con más funcionalidades: elegibilidad configurable, fechas de expiración y hasta 1 millón de códigos por trimestre. Si antes usabas códigos promocionales para compras in-app, migra a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app y versión) daban acceso gratuito a una suscripción. A diferencia de los códigos de oferta, Apple no incluía información de descuento en las transacciones con código promocional — enviaba el precio completo del producto en el recibo. Por ello, Adapty registraba estas transacciones al precio completo, lo que generaba discrepancias entre los análisis de Adapty y App Store Connect. Si ves transacciones históricas al precio completo que deberían haber sido gratuitas, es probable que provengan de códigos promocionales heredados. Como estos códigos ya están obsoletos, migra a los códigos de oferta para un seguimiento preciso de los ingresos. </Details> Para mostrar la hoja de canje de códigos en tu app: ```typescript showLineNumbers try { await adapty.presentCodeRedemptionSheet(); } catch (error) { console.error('Failed to present code redemption sheet:', error); } ``` :::danger Según nuestras observaciones, la hoja de canje de códigos de oferta en algunas apps puede no funcionar de forma fiable. Recomendamos redirigir al usuario directamente al App Store. Para hacer esto, debes abrir la URL con el siguiente formato: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: --- # File: capacitor-restore-purchase --- --- title: "Restaurar compras en la app móvil en Capacitor SDK" description: "Aprende cómo restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- Restaurar compras tanto en iOS como en Android es una funcionalidad que permite a los usuarios recuperar el acceso a contenido previamente adquirido, como suscripciones o compras in-app, sin que se les vuelva a cobrar. Esta funcionalidad es especialmente útil para usuarios que han desinstalado y reinstalado la app o han cambiado de dispositivo y quieren acceder a su contenido comprado anteriormente sin pagar de nuevo. :::note En paywalls creados con [Paywall Builder](adapty-paywall-builder), las compras se restauran automáticamente sin necesidad de código adicional por tu parte. Si ese es tu caso, puedes saltarte este paso. ::: Para restaurar una compra si no utilizas el [Paywall Builder](adapty-paywall-builder) para personalizar el paywall, llama al método `.restorePurchases()`: ```typescript showLineNumbers try { const profile = await adapty.restorePurchases(); const isSubscribed = profile.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Restore access to paid features console.log('Access restored successfully!'); } else { console.log('No active subscriptions found'); } } catch (error) { console.error('Failed to restore purchases:', error); } ``` Parámetros de respuesta: | Parámetro | Descripción | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **profile** | Un objeto [`AdaptyProfile`](https://capacitor.adapty.io/interfaces/adaptyprofile). Este modelo contiene información sobre niveles de acceso, suscripciones y compras que no son suscripciones. Comprueba el **estado del nivel de acceso** para determinar si el usuario tiene acceso a la app. | --- # File: implement-observer-mode-capacitor --- --- title: "Implementar el modo Observer en el SDK de Capacitor" description: "Implementa el modo Observer en Adapty para rastrear los eventos de suscripción de usuarios en el SDK de Capacitor." --- Si ya tienes tu propia infraestructura de compras y no estás listo para migrar completamente a Adapty, puedes explorar el [modo Observer](observer-vs-full-mode). En su forma básica, el modo Observer ofrece analíticas avanzadas e integración fluida con sistemas de atribución y analíticas. Si esto se ajusta a tus necesidades, solo tienes que: 1. Activarlo al configurar el SDK estableciendo el parámetro `observerMode` en `true`. Sigue las instrucciones de configuración para [Capacitor](sdk-installation-capacitor#activate-adapty-module-of-adapty-sdk). 2. [Notificar las transacciones](report-transactions-observer-mode-capacitor) desde tu infraestructura de compras existente a Adapty. :::tip En SDK v4, también puedes presentar flows y paywalls renderizados por Adapty en modo Observer: cuando un usuario pulsa el botón de compra o restauración, el SDK delega la acción a tu código para que realices la compra o restauración tú mismo. Consulta [Presentar flows en modo Observer](capacitor-present-flows-in-observer-mode). ::: ### Configuración del modo Observer \{#observer-mode-setup\} Activa el modo Observer si gestionas tú mismo las compras y el estado de las suscripciones, y utilizas Adapty únicamente para enviar eventos de suscripción y datos de analítica. :::important Cuando se ejecuta en el modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlas tú mismo. ::: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { observerMode: true // Enable observer mode } }); } catch (error) { console.error('Failed to activate Adapty:', error); } ``` | Parámetro | Descripción | | --------------------------- | ------------------------------------------------------------ | | **observerMode** | Un valor booleano que controla el [modo Observer](observer-vs-full-mode). El valor predeterminado es `false`. | ## Usar paywalls de Adapty en el modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los paywalls y las funciones de pruebas A/B de Adapty, puedes hacerlo, pero requiere una configuración adicional en el modo Observer. Esto es lo que necesitarás hacer además de los pasos anteriores: 1. Muestra los paywalls de la forma habitual para [paywalls con Remote Config](present-remote-config-paywalls-capacitor). 2. [Asocia los paywalls](report-transactions-observer-mode-capacitor) con las transacciones de compra. --- # File: report-transactions-observer-mode-capacitor --- --- title: "Reportar transacciones en Observer Mode en el SDK de Capacitor" description: "Reporta transacciones de compra en el Observer Mode de Adapty para obtener información sobre usuarios y realizar seguimiento de ingresos en el SDK de Capacitor." --- En Observer Mode, el SDK de Adapty no puede rastrear de forma autónoma las compras realizadas a través de tu sistema de compras existente. Necesitas reportar las transacciones desde tu store. Es fundamental configurar esto **antes** de lanzar tu app para evitar errores en los análisis. Usa `reportTransaction` para reportar explícitamente cada transacción y que Adapty pueda reconocerla. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el paywall que la originó, garantizando un análisis preciso del paywall. ```typescript showLineNumbers const variationId = paywall.variationId; try { await adapty.reportTransaction({ transactionId: 'your_transaction_id', variationId: variationId }); } catch (error) { console.error('Failed to report transaction:', error); } ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | **transactionId** | obligatorio | <ul><li> Para iOS: Identificador de la transacción.</li><li> Para Android: Identificador de cadena (`purchase.getOrderId`) de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación.</li></ul> | | **variationId** | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://capacitor.adapty.io/interfaces/adaptypaywall). | --- # File: capacitor-user --- --- title: "Usuarios y acceso" description: "Aprende a trabajar con usuarios y niveles de acceso en tu app de Capacitor con el SDK de Adapty." --- <CustomDocCardList /> --- # File: capacitor-identifying-users --- --- title: "Identificar usuarios en el SDK de Capacitor" description: "Aprende cómo identificar usuarios en tu app de Capacitor con el SDK de Adapty." --- Adapty crea un ID de perfil interno para cada usuario. Sin embargo, si tienes tu propio sistema de autenticación, deberías establecer tu propio Customer User ID. Puedes encontrar usuarios por su Customer User ID en la sección de [Perfiles](profiles-crm) y utilizarlo en la [API del lado del servidor](getting-started-with-server-side-api), que se enviará a todas las integraciones. ### Establecer el ID de usuario del cliente en la configuración \{#setting-customer-user-id-on-configuration\} Si tienes un ID de usuario durante la configuración, pásalo como parámetro `customerUserId` al método `.activate()`: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { customerUserId: 'YOUR_USER_ID' } }); } catch (error) { console.error('Failed to activate Adapty:', error); } ``` ### Establecer el ID de usuario tras la configuración \{#setting-customer-user-id-after-configuration\} Si no tienes un ID de usuario en la configuración del SDK, puedes establecerlo más adelante en cualquier momento con el método `.identify()`. Los casos más habituales para usar este método son tras el registro o la autenticación, cuando el usuario pasa de ser anónimo a estar autenticado. ```typescript showLineNumbers try { await adapty.identify({ customerUserId: 'YOUR_USER_ID' }); console.log('User identified successfully'); } catch (error) { console.error('Failed to identify user:', error); } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **customerUserId** | obligatorio | Identificador de usuario en formato string. | :::warning Reenvío de datos significativos del usuario En algunos casos, como cuando un usuario vuelve a iniciar sesión en su cuenta, los servidores de Adapty ya tienen información sobre ese usuario. En estos escenarios, el SDK de Adapty cambiará automáticamente para trabajar con el nuevo usuario. Si enviaste algún dato al usuario anónimo, como atributos personalizados o atribuciones de redes de terceros, deberás reenviar esos datos para el usuario identificado. Es importante destacar que debes volver a solicitar todos los paywalls y productos después de identificar al usuario, ya que los datos del nuevo usuario pueden ser diferentes. ::: ### Cerrar sesión e iniciar sesión \{#logging-out-and-logging-in\} Puedes cerrar la sesión del usuario en cualquier momento llamando al método `.logout()`: ```typescript showLineNumbers try { await adapty.logout(); console.log('User logged out successfully'); } catch (error) { console.error('Failed to logout user:', error); } ``` Luego puedes iniciar sesión con el usuario usando el método `.identify()`. ## Asignar `appAccountToken` (iOS) \{#assign-appaccounttoken-ios\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) es un **UUID** que te permite vincular las transacciones del App Store con la identidad interna de tus usuarios. StoreKit asocia este token a cada transacción, de modo que tu backend puede relacionar los datos del App Store con tus usuarios. Usa un UUID estable generado por usuario y reutilízalo para la misma cuenta en todos los dispositivos. Así te aseguras de que las compras y las notificaciones del App Store queden correctamente vinculadas. Puedes establecer el token de dos maneras: durante la activación del SDK o al identificar al usuario. :::important Siempre debes pasar `appAccountToken` junto con `customerUserId`. Si solo pasas el token, no se incluirá en la transacción. ::: ```typescript showLineNumbers // Durante la configuración: await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { customerUserId: 'YOUR_USER_ID', ios: { appAccountToken: "YOUR_APP_ACCOUNT_TOKEN" }, } }); // O al identificar usuarios await adapty.identify({ customerUserId: 'YOUR_USER_ID', params: { ios: { appAccountToken: 'YOUR_APP_ACCOUNT_TOKEN' }, } }); ``` ### Establecer IDs de cuenta ofuscados (Android) \{#set-obfuscated-account-ids-android\} Google Play requiere IDs de cuenta ofuscados en ciertos casos de uso para mejorar la privacidad y seguridad de los usuarios. Estos IDs ayudan a Google Play a identificar las compras manteniendo el anonimato de la información del usuario, lo que resulta especialmente importante para la prevención del fraude y el análisis. Es posible que necesites configurar estos IDs si tu aplicación maneja datos sensibles de los usuarios o si debes cumplir con normativas de privacidad específicas. Los IDs ofuscados permiten a Google Play rastrear las compras sin exponer los identificadores reales de los usuarios. ```typescript showLineNumbers // Durante la configuración: await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' }, } }); // O al identificar usuarios await adapty.identify({ customerUserId: 'YOUR_USER_ID', params: { android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' }, } }); ``` ## Detectar usuarios en varios dispositivos \{#detect-users-across-devices\} Cuando el SDK se activa, lee automáticamente los derechos existentes del usuario desde StoreKit (iOS) o Google Play Billing (Android) y los sincroniza con el backend de Adapty. Una suscripción activa aparece en el perfil de Adapty sin que la app llame a `restorePurchases`. Lo que **no** ocurre automáticamente es reconocer que un perfil en un dispositivo nuevo pertenece al mismo usuario que el perfil en el dispositivo original. Adapty relaciona perfiles por Customer User ID, así que la continuidad de identidad depende de lo que uses como CUID. **Lo que Adapty puede detectar entre dispositivos** | Tu configuración | Lo que Adapty detecta | Lo que debes hacer | | --- | --- | --- | | Customer User ID = `device_id` (sin login en la app) | El nuevo dispositivo obtiene un CUID diferente y, por tanto, un perfil diferente. La suscripción se sincroniza con el nuevo perfil mediante un evento **Access level updated**, pero `subscription_started` no se dispara — el nuevo perfil se trata como heredero de la compra original. Los análisis basados en `subscription_started` contarán de menos a los usuarios que vuelven. | Usa un ID de cuenta estable como Customer User ID para que un usuario que regresa coincida con el perfil existente en todos los dispositivos. | | Customer User ID = ID de cuenta estable (login en cada dispositivo) | El SDK sincroniza automáticamente la suscripción en `activate()`, e `identify()` relaciona el perfil existente por CUID. | No se necesita configuración adicional — tanto la identidad como la suscripción se resuelven automáticamente. | | Heredero de Apple Family Sharing | El miembro de la familia recibe la suscripción solo a través de un evento **Access level updated** — `subscription_started` no se dispara. | Escucha el evento **Access level updated**. Consulta [Apple Family Sharing](apple-family-sharing) para ver la matriz de eventos completa. | | Misma cuenta de Apple/Google, distintos usuarios dentro de la app | El primer perfil que registra la compra se convierte en el principal. Los perfiles posteriores ven la suscripción a través de una cadena de herederos, con un evento **Access level updated**. | Exige login y elige un [modo de compartición](sharing-paid-access-between-user-accounts) que se adapte a tu modelo. | **Restaurar compras en un dispositivo nuevo** Muestra un botón "Restaurar compras" iniciado por el usuario en tu paywall. Apple App Review (directriz 3.1.1) lo exige, y actúa como alternativa cuando la sincronización automática no cubre algún caso límite. El botón debe llamar a `restorePurchases` en tu SDK. No es necesario llamar a `restorePurchases` de forma programática al primer inicio para el uso normal — el SDK ya ejecuta el equivalente en `activate()`. Reserva las llamadas programáticas para forzar una verificación de recibo actualizada, por ejemplo al depurar un acceso que falta después de que `activate()` haya completado. --- # File: capacitor-setting-user-attributes --- --- title: "Establecer atributos de usuario en el SDK de Capacitor" description: "Aprende cómo actualizar los atributos de usuario y los datos del perfil en tu app de Capacitor con el SDK de Adapty." --- Puedes establecer atributos opcionales como email, número de teléfono, etc., para el usuario de tu app. Luego puedes usar estos atributos para crear [segmentos](segments) de usuarios o simplemente verlos en el CRM. ### Establecer atributos de usuario \{#setting-user-attributes\} Para establecer atributos de usuario, llama al método `.updateProfile()`: ```typescript showLineNumbers const params = { email: 'email@email.com', phoneNumber: '+18888888888', firstName: 'John', lastName: 'Appleseed', gender: 'other', birthday: new Date().toISOString(), }; try { await adapty.updateProfile(params); console.log('Profile updated successfully'); } catch (error) { console.error('Failed to update profile:', error); } ``` Ten en cuenta que los atributos que hayas establecido previamente con el método `updateProfile` no se restablecerán. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Lista de claves permitidas \{#the-allowed-keys-list\} A continuación se muestran las claves permitidas de `AdaptyProfileParameters` y sus valores: | Clave | Valor | |---|-----| | **email** | String | | **phoneNumber** | String | | **firstName** | String | | **lastName** | String | | **gender** | Enum, los valores permitidos son: `'female'`, `'male'`, `'other'` | | **birthday** | Cadena de fecha en formato ISO | ### Atributos de usuario personalizados \{#custom-user-attributes\} Puedes definir tus propios atributos personalizados. Generalmente están relacionados con el uso de tu app. Por ejemplo, en aplicaciones de fitness podrían ser el número de ejercicios por semana; en apps de aprendizaje de idiomas, el nivel de conocimiento del usuario, etc. Puedes usarlos en segmentos para crear paywalls y ofertas personalizadas, y también en análisis para identificar qué métricas de producto influyen más en los ingresos. ```typescript showLineNumbers try { await adapty.updateProfile({ codableCustomAttributes: { key_1: 'value_1', key_2: 2, }, }); console.log('Custom attributes updated successfully'); } catch (error) { console.error('Failed to update custom attributes:', error); } ``` Para eliminar claves existentes, pasa `null` como sus valores: ```typescript showLineNumbers try { // to remove keys, pass null as their values await adapty.updateProfile({ codableCustomAttributes: { key_1: null, key_2: null, }, }); console.log('Custom attributes removed successfully'); } catch (error) { console.error('Failed to remove custom attributes:', error); } ``` En ocasiones necesitas saber qué atributos personalizados ya están configurados. Para ello, utiliza el campo `customAttributes` del objeto `AdaptyProfile`. :::warning Ten en cuenta que el valor de `customAttributes` puede estar desactualizado, ya que los atributos de usuario pueden enviarse desde distintos dispositivos en cualquier momento, por lo que los atributos en el servidor podrían haber cambiado desde la última sincronización. ::: ### Límites \{#limits\} - Hasta 30 atributos personalizados por usuario - Los nombres de clave tienen un máximo de 30 caracteres. El nombre de la clave puede incluir caracteres alfanuméricos y cualquiera de los siguientes: `_` `-` `.` - El valor puede ser una cadena o un número flotante con un máximo de 50 caracteres. --- # File: capacitor-listen-subscription-changes --- --- title: "Verificar el estado de la suscripción en el SDK de Capacitor" description: "Rastrea y gestiona el estado de la suscripción de los usuarios en Adapty para mejorar la retención de clientes en tu app de Capacitor." --- Con Adapty, hacer seguimiento del estado de las suscripciones es muy sencillo. No necesitas insertar manualmente los IDs de producto en tu código. En su lugar, puedes verificar fácilmente el estado de suscripción de un usuario comprobando si tiene un [nivel de acceso](access-level) activo. <details> <summary>Antes de comprobar el estado de la suscripción (Haz clic para expandir)</summary> - Para iOS, configura las [notificaciones del servidor de App Store](enable-app-store-server-notifications) - Para Android, configura las [notificaciones en tiempo real para desarrolladores (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## Nivel de acceso y el objeto AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Los niveles de acceso son propiedades del objeto [AdaptyProfile](https://capacitor.adapty.io/interfaces/adaptyprofile). Te recomendamos obtener el perfil cuando tu app se inicia, por ejemplo al [identificar a un usuario](capacitor-identifying-users#setting-customer-user-id-on-configuration), y luego actualizarlo cada vez que se produzcan cambios. Así puedes usar el objeto de perfil sin necesidad de solicitarlo repetidamente. Para recibir notificaciones sobre actualizaciones del perfil, escucha los cambios de perfil tal como se describe en la sección [Escuchar actualizaciones del perfil, incluidos los niveles de acceso](capacitor-listen-subscription-changes) a continuación. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Recuperar el nivel de acceso desde el servidor \{#retrieving-the-access-level-from-the-server\} Para obtener el nivel de acceso desde el servidor, utiliza el método `.getProfile()`: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); console.log('Profile retrieved successfully'); } catch (error) { console.error('Failed to get profile:', error); } ``` Parámetros de respuesta: | Parámetro | Descripción | | --------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **profile** | Un objeto [AdaptyProfile](https://capacitor.adapty.io/interfaces/adaptyprofile). En general, solo necesitas comprobar el estado del nivel de acceso del perfil para determinar si el usuario tiene acceso premium a la app. El método `.getProfile` proporciona el resultado más actualizado, ya que siempre intenta consultar la API. Si por algún motivo (por ejemplo, sin conexión a internet), el SDK no puede obtener información del servidor, se devolverán los datos de la caché. También es importante tener en cuenta que el SDK actualiza la caché de `AdaptyProfile` regularmente para mantener esta información lo más actualizada posible. | El método `.getProfile()` te proporciona el perfil del usuario desde el que puedes obtener el estado del nivel de acceso. Puedes tener varios niveles de acceso por aplicación. Por ejemplo, si tienes una app de noticias y vendes suscripciones a distintos temas de forma independiente, puedes crear los niveles de acceso "sports" y "science". Pero la mayoría de las veces solo necesitarás uno, en cuyo caso puedes usar simplemente el nivel de acceso predeterminado "premium". A continuación se muestra un ejemplo para comprobar el nivel de acceso predeterminado "premium": ```typescript showLineNumbers try { const profile = await adapty.getProfile(); const isActive = profile.accessLevels?.['premium']?.isActive; if (isActive) { // Grant access to premium features console.log('User has premium access'); } else { console.log('User does not have premium access'); } } catch (error) { console.error('Failed to check subscription status:', error); } ``` ### Escuchar actualizaciones del estado de suscripción \{#listening-for-subscription-status-updates\} Cada vez que cambia la suscripción del usuario, Adapty lanza un evento. Para recibir mensajes de Adapty, necesitas realizar una configuración adicional: ```typescript showLineNumbers // Create an "onLatestProfileLoad" event listener adapty.addListener('onLatestProfileLoad', (data) => { const profile = data.profile; const isActive = profile.accessLevels?.['premium']?.isActive; if (isActive) { console.log('Subscription status updated: User has premium access'); } else { console.log('Subscription status updated: User does not have premium access'); } }); ``` Adapty también lanza un evento al inicio de la aplicación. En ese caso, se pasará el estado de suscripción almacenado en caché. ### Caché del estado de suscripción \{#subscription-status-cache\} El caché implementado en el SDK de Adapty almacena el estado de suscripción del perfil. Esto significa que, aunque el servidor no esté disponible, se puede acceder a los datos en caché para obtener información sobre el estado de suscripción del perfil. Sin embargo, es importante tener en cuenta que no es posible realizar solicitudes de datos directamente desde la caché. El SDK consulta periódicamente el servidor cada minuto para comprobar si hay actualizaciones o cambios relacionados con el perfil. Si hay modificaciones, como nuevas transacciones u otras actualizaciones, se enviarán a los datos en caché para mantenerlos sincronizados con el servidor. --- # File: capacitor-deal-with-att --- --- title: "Gestionar ATT en el SDK de Capacitor" description: "Empieza a usar Adapty en Capacitor para simplificar la configuración y gestión de suscripciones." --- Si tu aplicación usa el framework AppTrackingTransparency y muestra al usuario una solicitud de autorización para el seguimiento de la app, debes enviar el [estado de autorización](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) a Adapty. ```typescript showLineNumbers try { await adapty.updateProfile({ appTrackingTransparencyStatus: AppTrackingTransparencyStatus.Authorized, }); console.log('ATT status updated successfully'); } catch (error) { console.error('Failed to update ATT status:', error); } ``` :::warning Te recomendamos encarecidamente que envíes este valor lo antes posible cuando cambie; solo así los datos se enviarán a tiempo a las integraciones que hayas configurado. ::: --- # File: capacitor-onboardings --- --- title: "Onboardings" description: "Aprende a trabajar con onboardings en tu app de Capacitor con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos en SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](capacitor-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo — ofreciendo animaciones más fluidas, un aspecto nativo consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](capacitor-get-pb-paywalls) y [Mostrar flows y paywalls](capacitor-present-paywalls) para empezar. ::: <CustomDocCardList /> --- # File: capacitor-get-onboardings --- --- title: "Obtener onboardings en el SDK de Capacitor" description: "Aprende a recuperar onboardings en Adapty para Capacitor." --- :::warning **Los onboardings están obsoletos en la versión 4 del SDK y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](capacitor-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](capacitor-get-pb-paywalls) y [Mostrar flows y paywalls](capacitor-present-paywalls) para empezar. ::: Después de [diseñar la parte visual de tu onboarding](design-onboarding) con el builder en el Adapty Dashboard, puedes mostrarlo en tu aplicación Capacitor. El primer paso en este proceso es obtener el onboarding asociado al placement y su configuración de vista, tal como se describe a continuación. Antes de empezar, asegúrate de que: 1. Has [creado un onboarding](create-onboarding). 2. Has añadido el onboarding a un [placement](placements). ## Obtener el onboarding \{#fetch-onboarding\} Cuando creas un [onboarding](onboardings) con nuestro editor no-code, se almacena como un contenedor con la configuración que tu app necesita obtener y mostrar. Este contenedor gestiona toda la experiencia: qué contenido aparece, cómo se presenta y cómo se procesan las interacciones del usuario (como respuestas a cuestionarios o entradas de formularios). El contenedor también registra automáticamente los eventos de analítica, por lo que no necesitas implementar un seguimiento de vistas por separado. Para un mejor rendimiento, obtén la configuración del onboarding con antelación para que las imágenes tengan tiempo suficiente de descargarse antes de mostrárselas a los usuarios. Para obtener un onboarding, usa el método `getOnboarding`: ```typescript showLineNumbers try { const onboarding = await adapty.getOnboarding({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); console.log('Onboarding fetched successfully'); } catch (error) { console.error('Failed to fetch onboarding:', error); } ``` A continuación, llama al método `createOnboardingView` para crear una instancia de vista. :::warning El resultado del método `createOnboardingView` solo puede utilizarse una vez. Si necesitas volver a utilizarlo, llama al método `createOnboardingView` de nuevo. ::: ```typescript showLineNumbers if (onboarding.hasViewConfiguration) { try { const view = await createOnboardingView(onboarding); console.log('Onboarding view created successfully'); } catch (error) { console.error('Failed to create onboarding view:', error); } } else { // Use your custom logic console.log('Onboarding does not have view configuration'); } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto de una o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos utilizarlos.</p> | | **params.fetchPolicy** | <p>opcional</p><p>por defecto: `'reload_revalidating_cache_data'`</p> | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `'return_cache_data_else_load'` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro utilizarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | | **params.loadTimeoutMs** | <p>opcional</p><p>por defecto: 5000 ms</p> | <p>Este valor limita el tiempo de espera (en milisegundos) para este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo de espera especificado en `loadTimeoutMs`, ya que la operación puede estar compuesta por diferentes peticiones internamente.</p> | Parámetros de respuesta: | Parámetro | Descripción | |:----------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onboarding** | Un objeto [`AdaptyOnboarding`](https://capacitor.adapty.io/interfaces/adaptyonboarding) con: el identificador y la configuración del onboarding, Remote Config y otras propiedades. | ## Acelerar la obtención del onboarding con el onboarding de audiencia predeterminada \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Por lo general, los onboardings se obtienen casi al instante, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, si tienes muchas audiencias y onboardings, y tus usuarios tienen una conexión a internet lenta, obtener un onboarding puede tardar más de lo que quisieras. En esas situaciones, puede que prefieras mostrar un onboarding predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ninguno. Para solucionar esto, puedes usar el método `getOnboardingForDefaultAudience`, que obtiene el onboarding del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el onboarding mediante el método `getOnboarding`, tal como se detalla en la sección [Obtener el onboarding](#fetch-onboarding) anterior. :::warning Considera usar `getOnboarding` en lugar de `getOnboardingForDefaultAudience`, ya que este último tiene limitaciones importantes: - **Problemas de compatibilidad**: Puede generar conflictos al dar soporte a varias versiones de la app, lo que obliga a diseños compatibles con versiones anteriores o a asumir que las versiones antiguas podrían mostrarse incorrectamente. - **Sin personalización**: Solo muestra contenido para la audiencia "All Users", eliminando la segmentación por país, atribución o atributos personalizados. Si una obtención más rápida compensa estos inconvenientes en tu caso de uso, utiliza `getOnboardingForDefaultAudience` tal como se muestra a continuación. De lo contrario, usa `getOnboarding` como se describe [arriba](#fetch-onboarding). ::: ```typescript showLineNumbers try { const onboarding = await adapty.getOnboardingForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data' // Load from server, fallback to cache } }); console.log('Default audience onboarding fetched successfully'); } catch (error) { console.error('Failed to fetch default audience onboarding:', error); } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>predeterminado: `en`</p> | <p>El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto de uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **params.fetchPolicy** | <p>opcional</p><p>predeterminado: `'reload_revalidating_cache_data'`</p> | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que los usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `'return_cache_data_else_load'` para devolver los datos en caché si existen. En este caso, los usuarios puede que no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | --- # File: capacitor-present-onboardings --- --- title: "Mostrar onboardings en Capacitor SDK" description: "Descubre cómo mostrar onboardings en Capacitor para aumentar las conversiones y los ingresos." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](capacitor-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que proporciona animaciones más fluidas, un aspecto nativo coherente, tiempos de carga más rápidos y sin dependencia del entorno de ejecución de WebView. Consulta [Obtener flows y paywalls](capacitor-get-pb-paywalls) y [Mostrar flows y paywalls](capacitor-present-paywalls) para empezar. ::: Si has personalizado un onboarding usando el builder, no tienes que preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese onboarding ya incluye tanto lo que debe mostrarse como la forma en que debe mostrarse. Antes de empezar, asegúrate de que: 1. Has [creado un onboarding](create-onboarding). 2. Has añadido el onboarding a un [placement](placements). ## Presentar el onboarding \{#present-onboarding\} Para mostrar un onboarding, usa el método `view.present()` en el `view` creado por el método `createOnboardingView`. Cada `view` solo se puede usar una vez. Si necesitas mostrar el onboarding de nuevo, llama a `createOnboardingView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo puede producir un error. ::: ```typescript showLineNumbers try { const view = await createOnboardingView(onboarding); view.setEventHandlers({ onClose: (actionId, meta) => { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, onCustom: (actionId, meta) => { console.log('Custom action:', actionId); return false; // Don't close the onboarding } }); await view.present(); console.log('Onboarding presented successfully'); } catch (error) { console.error('Failed to present onboarding:', error); } ``` ## Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el onboarding en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `'full_screen'` (predeterminado) o `'page_sheet'`. ```typescript showLineNumbers await view.present({ iosPresentationStyle: 'page_sheet' }); ``` ## Personaliza cómo se abren los enlaces en los onboardings \{#customize-how-links-open-in-onboardings\} :::important La personalización de cómo se abren los enlaces en los onboardings está disponible a partir del SDK de Adapty v3.15. ::: Por defecto, los enlaces en los onboardings se abren en un navegador integrado en la app. Esto proporciona una experiencia de usuario fluida al mostrar las páginas web dentro de tu aplicación, permitiendo a los usuarios verlas sin cambiar de app. Si prefieres abrir los enlaces en un navegador externo, puedes personalizar este comportamiento estableciendo el parámetro `openIn` con el valor `browser_out_app`: ```typescript showLineNumbers await view.present({ openIn: 'browser_out_app' }); // default — browser_in_app ``` ## Próximos pasos \{#next-steps\} Una vez que hayas mostrado tu onboarding, querrás [gestionar las interacciones y eventos del usuario](capacitor-handling-onboarding-events). Aprende a manejar los eventos del onboarding para responder a las acciones del usuario y hacer seguimiento de los datos analíticos. --- # File: capacitor-handling-onboarding-events --- --- title: "Manejar eventos de onboarding en el SDK de Capacitor" description: "Maneja eventos relacionados con el onboarding en Capacitor usando Adapty." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](capacitor-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del entorno WebView. Consulta [Obtener flows y paywalls](capacitor-get-pb-paywalls) y [Mostrar flows y paywalls](capacitor-present-paywalls) para empezar. ::: Los onboardings configurados con el builder generan eventos a los que tu app puede responder. Usa el método `setEventHandlers` para gestionar estos eventos en la presentación de pantallas independientes. Antes de empezar, asegúrate de: 1. Haber [creado un onboarding](create-onboarding). 2. Haber añadido el onboarding a un [placement](placements). ## Configurar manejadores de eventos \{#set-up-event-handlers\} Para gestionar los eventos de los onboardings, usa el método `view.setEventHandlers`: ```typescript showLineNumbers try { const view = await createOnboardingView(onboarding); view.setEventHandlers({ onAnalytics(event, meta) { console.log('Analytics event:', event); }, onClose(actionId, meta) { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, onCustom(actionId, meta) { console.log('Custom action:', actionId); return false; // Don't close the onboarding }, onPaywall(actionId, meta) { console.log('Paywall action:', actionId); view.dismiss().then(() => { openPaywall(actionId); }); }, onStateUpdated(action, meta) { console.log('State updated:', action); }, onFinishedLoading(meta) { console.log('Onboarding finished loading'); }, onError(error) { console.error('Onboarding error:', error); }, }); await view.present(); } catch (error) { console.error('Failed to present onboarding:', error); } ``` ## Tipos de eventos \{#event-types\} Las siguientes secciones describen los distintos tipos de eventos que puedes gestionar. ### Gestionar acciones personalizadas \{#handle-custom-actions\} En el builder, puedes añadir una acción **custom** a un botón y asignarle un ID. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Luego, puedes usar este ID en tu código y gestionarlo como una acción personalizada. Por ejemplo, si el usuario pulsa un botón personalizado, como **Login** o **Allow notifications**, el manejador de eventos se activará con el parámetro `actionId` que coincide con el **Action ID** del builder. Puedes crear tus propios IDs, como "allowNotifications". ```typescript showLineNumbers view.setEventHandlers({ onCustom(actionId, meta) { switch (actionId) { case 'login': console.log('Login action triggered'); break; case 'allow_notifications': console.log('Allow notifications action triggered'); break; } return false; // Don't close the onboarding }, }); ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```json { "actionId": "allow_notifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ### Finalización de la carga del onboarding \{#finishing-loading-onboarding\} Cuando un onboarding termina de cargarse, se activa este evento: ```typescript showLineNumbers view.setEventHandlers({ onFinishedLoading(meta) { console.log('Onboarding loaded:', meta.onboardingId); }, }); ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ### Cierre del onboarding \{#closing-onboarding\} El onboarding se considera cerrado cuando el usuario pulsa un botón con la acción **Close** asignada. <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Ten en cuenta que debes gestionar qué ocurre cuando el usuario cierra el onboarding. Por ejemplo, debes dejar de mostrar el propio onboarding. ::: ```typescript showLineNumbers view.setEventHandlers({ onClose(actionId, meta) { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, }); ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ### Apertura de un paywall \{#opening-a-paywall\} :::tip Gestiona este evento para abrir un paywall si quieres hacerlo dentro del onboarding. Si quieres abrir un paywall después de que se cierre, hay una forma más directa de hacerlo: gestiona la acción de cierre y abre un paywall sin depender de los datos del evento. ::: La forma más fluida de trabajar con paywalls en onboardings es hacer que el ID de acción sea igual al ID del placement del paywall. Ten en cuenta que, en iOS, solo se puede mostrar una vista (paywall u onboarding) en pantalla al mismo tiempo. Si presentas un paywall sobre un onboarding, no podrás controlar el onboarding en segundo plano mediante código. Si intentas cerrar el onboarding, se cerrará el paywall en su lugar y el onboarding quedará visible. Para evitarlo, cierra siempre la vista del onboarding antes de presentar el paywall. ```typescript showLineNumbers view.setEventHandlers({ onPaywall(actionId, meta) { // Dismiss onboarding before presenting paywall view.dismiss().then(() => { openPaywall(actionId); }); }, }); async function openPaywall(placementId: string) { // Implement your paywall opening logic here } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ### Seguimiento de navegación \{#tracking-navigation\} Recibes un evento de analítica cuando ocurren distintos eventos de navegación durante el flow de onboarding: ```typescript showLineNumbers view.setEventHandlers({ onAnalytics(event, meta) { console.log('Analytics event:', event.type, meta.onboardingId); }, }); ``` El objeto `event` puede ser de uno de los siguientes tipos: | Tipo | Descripción | |------------|-------------| | `onboardingStarted` | Cuando el onboarding ha sido cargado | | `screenPresented` | Cuando se muestra cualquier pantalla | | `screenCompleted` | Cuando se completa una pantalla. Incluye el parámetro opcional `elementId` (identificador del elemento completado) y el opcional `reply` (respuesta del usuario). Se activa cuando el usuario realiza cualquier acción para salir de la pantalla. | | `secondScreenPresented` | Cuando se muestra la segunda pantalla | | `userEmailCollected` | Se activa cuando se recoge el correo electrónico del usuario a través del campo de entrada | | `onboardingCompleted` | Se activa cuando un usuario llega a una pantalla con el ID `final`. Si necesitas este evento, [asigna el ID `final` a la última pantalla](design-onboarding). | | `unknown` | Para cualquier tipo de evento no reconocido. Incluye `name` (el nombre del evento desconocido) y `meta` (metadatos adicionales) | Cada evento incluye información `meta` que contiene: | Campo | Descripción | |------------|-------------| | `onboardingId` | Identificador único del flow de onboarding | | `screenClientId` | Identificador de la pantalla actual | | `screenIndex` | Posición de la pantalla actual en el flow | | `screensTotal` | Número total de pantallas en el flow | <Details> <summary>Ejemplos de eventos (haz clic para expandir)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: capacitor-onboarding-input --- --- title: "Procesar datos de onboardings en Capacitor SDK" description: "Guarda y usa datos de onboardings en tu app de Capacitor con Adapty SDK." --- :::warning **Los onboardings están obsoletos en SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](capacitor-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, un aspecto nativo consistente, tiempos de carga más rápidos y sin dependencia del tiempo de ejecución de WebView. Consulta [Obtener flows y paywalls](capacitor-get-pb-paywalls) y [Mostrar flows y paywalls](capacitor-present-paywalls) para empezar. ::: Cuando tus usuarios responden a una pregunta del quiz o introducen datos en un campo de texto, se invocará el método `onStateUpdated`. Puedes guardar o procesar el tipo de campo en tu código. Por ejemplo: ```typescript view.setEventHandlers({ onStateUpdated(action, meta) { // Process data }, }); ``` Consulta el formato de la acción [aquí](https://capacitor.adapty.io/types/onboardingstateupdatedaction). <Details> <summary>Ejemplos de datos guardados (el formato puede variar según tu implementación)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## Casos de uso \{#use-cases\} ### Enriquecer perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Si quieres vincular inmediatamente los datos introducidos con el perfil del usuario y evitar pedirle la misma información dos veces, necesitas [actualizar el perfil del usuario](capacitor-setting-user-attributes) con los datos introducidos al gestionar la acción. Por ejemplo, si pides a los usuarios que introduzcan su nombre en el campo de texto con el ID `name` y quieres establecer ese valor como nombre del usuario, y además les pides que introduzcan su correo electrónico en el campo `email`, en el código de tu app puede quedar así: ```typescript showLineNumbers view.setEventHandlers({ onStateUpdated(action, meta) { // Store user preferences or responses if (action.elementType === 'input') { const profileParams: any = {}; // Map elementId to appropriate profile field switch (action.elementId) { case 'name': if (action.value.type === 'text') { profileParams.firstName = action.value.value; } break; case 'email': if (action.value.type === 'email') { profileParams.email = action.value.value; } break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile({ params: profileParams }).catch((error) => { // handle the error }); } } }, }); ``` ### Personalizar paywalls según las respuestas \{#customize-paywalls-based-on-answers\} Con los cuestionarios en los onboardings, también puedes personalizar los paywalls que muestras a los usuarios después de que completen el onboarding. Por ejemplo, puedes preguntarles sobre su experiencia con el deporte y mostrar distintas CTAs y productos a diferentes grupos de usuarios. 1. [Añade un cuestionario](onboarding-quizzes) en el editor de onboarding y asigna IDs significativos a sus opciones. 2. Gestiona las respuestas del cuestionario según sus IDs y [establece atributos personalizados](capacitor-setting-user-attributes) para los usuarios. ```typescript showLineNumbers view.setEventHandlers({ onStateUpdated(action, meta) { // Handle quiz responses and set custom attributes if (action.elementType === 'select') { const profileParams: any = {}; // Map quiz responses to custom attributes switch (action.elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) profileParams.codableCustomAttributes = { experience: action.value.value }; break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile({ params: profileParams }).catch((error) => { // handle the error }); } } }, }); ``` 3. [Crea segmentos](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade [audiencias](audience) para cada segmento que hayas creado. 5. [Muestra un paywall](capacitor-paywalls) para el placement en el código de tu app. Si tu onboarding tiene un botón que abre un paywall, implementa el código del paywall como [respuesta a la acción de ese botón](capacitor-handling-onboarding-events#opening-a-paywall). --- # File: capacitor-best-practices --- --- title: "Buenas prácticas en el SDK de Capacitor" description: "Patrones de referencia para integrar el SDK de Adapty en Capacitor: orden de llamadas, manejo de errores y otras reglas para entornos de producción." --- <CustomDocCardList /> --- # File: capacitor-sdk-call-order --- --- title: "Orden de llamadas en el SDK de Capacitor" description: "Evita perder acceso premium, atribución incorrecta y errores intermitentes #2002 llamando a los métodos del SDK de Adapty en el orden correcto." --- `adapty.activate()` debe completarse antes de llamar a cualquier otro método del SDK de Adapty. Hasta que se resuelva, el SDK no tiene estado. Cualquier llamada realizada antes o en paralelo con `activate()` fallará con [`#2002 notActivated`](capacitor-handle-errors#custom-network-codes). Si tu app autentica usuarios y recopilas un customer user ID después del lanzamiento, llama a `adapty.identify()` en ese momento. No llames a métodos de acción del usuario hasta que `identify` se resuelva. Las llamadas que compiten con él fallan con [`#3006 profileWasChanged`](capacitor-handle-errors#custom-network-codes) o se aplican al perfil anónimo creado en la activación. Cuando esto ocurre, la atribución, los IDs de MMP como `appsflyer_id` y la propiedad de la instalación no siempre se transfieren al perfil identificado. Si tu app no autentica usuarios, omite `identify` y sigue trabajando con el perfil anónimo. Los SDKs de MMP y analíticas (AppsFlyer, Adjust, Branch, PostHog) siguen la misma regla. Inicialízalos primero y espera sus callbacks de UID antes de llamar a `adapty.activate`. De lo contrario, el ID del MMP se asocia a un perfil anónimo temporal y no siempre se transfiere al perfil identificado. Para más detalles sobre AppsFlyer, consulta [AppsFlyer](appsflyer). ## El orden correcto \{#the-correct-order\} Tu proceso depende de dos factores: cuándo conoces el customer user ID y si usas un MMP o SDK de analítica. - **Pasos 2 y 5**: Obligatorios para todas las apps. Activa el SDK y luego llama a los métodos del SDK. - **Pasos 1 y 3**: Requeridos solo si integras un MMP o SDK de analítica (AppsFlyer, Adjust, Branch, PostHog). - **Paso 4**: Requerido solo si tu app autentica usuarios y obtiene el customer user ID después del lanzamiento. Si tienes el ID de usuario en el momento del lanzamiento de la app, pásalo directamente en `activate()` (paso 2a). Con este enfoque nunca se crea un perfil anónimo, por lo que el paso 4 es innecesario. | Paso | Llamada | Cuándo | Notas | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Inicializa tu MMP o SDK de analíticas (AppsFlyer, Adjust, PostHog, Branch) | Al iniciar la app, primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `adapty.activate({ apiKey: '...', params: { customerUserId: '...' } })` | Al iniciar la app, después del paso 1, si tienes el ID de usuario del cliente | Recomendado. No se crea ningún perfil anónimo. | | 2b | `adapty.activate({ apiKey: '...' })` sin `customerUserId` | Al iniciar la app, después del paso 1, si no tienes el ID de usuario del cliente (o nunca lo recopilas) | Adapty crea un perfil anónimo. | | 3 | `adapty.setIntegrationIdentifier({ key: '...', value: '...' })` para cada MMP | Después del paso 2, antes de cualquier llamada de acción del usuario | Necesario para que los IDs del MMP lleguen al perfil correcto. | | 4 | `await adapty.identify({ customerUserId: 'YOUR_USER_ID' })` | Después del paso 3 (o del paso 2 si no hay MMP), antes del paso 5 — solo en el camino 2b con autenticación | Usa siempre `await`. Las llamadas concurrentes durante `identify` producen `#3006 profileWasChanged`. | | 5 | `getPaywall` (`getFlow` en SDK v4), `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | Después del paso 4 si llamas a `identify`; de lo contrario después del paso 3 (o del paso 2 si no hay MMP) | Estas llamadas necesitan un perfil estable. | :::important Omitir estos pasos provoca que los usuarios que regresan pierdan el acceso premium, que falte el `appsflyer_id` en los perfiles y que los paywalls se muestren a la audiencia incorrecta. ::: ## Instalaciones desde web2app y web-funnel \{#web2app-and-web-funnel-installs\} Si los usuarios compran en un checkout web (Stripe, Paddle) y después instalan la app nativa, el primer `activate()` del dispositivo crea un nuevo perfil anónimo. Este perfil no está vinculado al perfil web. Si puedes resolver el customer user ID antes de lanzar la app (desde tu flujo de autenticación o el referrer de instalación), pásalo directamente en `activate()`. De lo contrario, la compra web será invisible en el dispositivo hasta que llames a `identify({ customerUserId: 'YOUR_USER_ID' })` y luego a `restorePurchases`. Para ver los metadatos que debes enviar con cada checkout web, consulta: - [Stripe](stripe) - [Paddle](paddle) --- # File: capacitor-optimize-paywall-fetching --- --- title: "Optimizar la obtención de paywalls en el SDK de Capacitor" description: "Obtén paywalls de Adapty de forma fiable: tiempos, caché y patrones de respaldo para Capacitor." --- Una obtención fiable de paywalls en Capacitor hace tres cosas: renderiza rápido, devuelve el paywall segmentado por audiencia y recurre al respaldo sin problemas cuando la red es lenta. Las reglas que se describen a continuación cubren los tiempos, la caché y los patrones de respaldo para conseguirlo. :::tip Las reglas asumen que `adapty.activate()` y `adapty.identify()` ya se han resuelto. Consulta [Orden de llamadas en el SDK de Capacitor](capacitor-sdk-call-order). ::: El consejo a continuación usa los nombres de métodos de v3. En el SDK v4, `getPaywall` pasa a llamarse `getFlow` (consulta la [guía de migración](migration-to-capacitor-sdk-v4)) — todas las reglas aplican sin cambios. ## Reglas y errores comunes \{#rules-and-pitfalls\} | Haz esto | No hagas esto | Por qué | |---|---|---| | Obtén el placement que estás a punto de mostrar. | Precarga todos los placements en paralelo al iniciar. | La precarga masiva bloquea el hilo principal y provoca una pantalla en negro durante la ráfaga. | | Llama a `getPaywall` después de que la atribución haya tenido tiempo de resolverse — por ejemplo, 1–2 segundos después de `activate` o tras dispararse el listener `onLatestProfileLoad`. | Llama a `getPaywall` al iniciar la app en `App.tsx`. | La atribución aún no ha llegado. El paywall se resuelve contra la audiencia por defecto y silenciosamente omite los segmentos y la personalización de ASA. | | Establece un `loadTimeoutMs` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | Esperes indefinidamente a `getPaywall`. | Sin un tiempo de espera límite, los usuarios con mala conectividad ven una pantalla en blanco hasta que la red responde, o cierran la app. | Consulta [Obtener paywalls y productos](fetch-paywalls-and-products-capacitor) para la referencia de los parámetros `fetchPolicy` y `loadTimeoutMs`, y [Placements](placements) para elegir el placement adecuado. ## Ajuste para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte, regiones afectadas por enrutamiento): - Establece `fetchPolicy: 'return_cache_data_else_load'` en cada obtención excepto la primera. - Configura un [paywall de respaldo](fallback-paywalls) para cada placement en el Adapty Dashboard. - Establece `loadTimeoutMs` entre 3000 y 5000 milisegundos y acepta el paywall de respaldo cuando se agote el tiempo. - No condicionales la visualización del paywall a `adapty.getProfile()`. Llama a `getPaywall` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: capacitor-show-aa-targeted-paywall --- --- title: "Mostrar un paywall dirigido por AA en el primer lanzamiento con Capacitor SDK" description: "Muestra un paywall de inmediato y mejóralo para usuarios de Apple Ads una vez aplicada la atribución en Capacitor, usando AdaptyProfile.appliedAttributionSources." --- La atribución de Apple Ads (AA) llega de forma asíncrona después de `adapty.activate()`. En el primer lanzamiento suele no haber llegado todavía, por lo que `getFlow` resuelve contra la audiencia predeterminada y los usuarios de Apple Ads se pierden el paywall segmentado por AA. En lugar de retrasar el paywall hasta que llegue la atribución, muestra uno inmediatamente y actualízalo una vez que se aplique la atribución de AA; así, los usuarios de Apple Ads ven la variante segmentada y el resto no espera. `AdaptyProfile.appliedAttributionSources` te indica cuándo se ha aplicado la atribución de AA. ## Antes de empezar \{#before-you-start\} Necesitas: - Adapty Capacitor SDK **3.17.1** o posterior. - Apple Ads configurado para la app en Adapty. Consulta [Apple Ads](apple-search-ads). ## Cómo funciona \{#how-it-works\} Tras llamar a `adapty.activate()`, el SDK solicita en segundo plano los datos de atribución de Apple Ads a Apple y reenvía el resultado al backend de Adapty. Cuando AA se convierte en la fuente de atribución activa del perfil, el SDK entrega un `AdaptyProfile` actualizado a tu listener `onLatestProfileLoad`, con `'apple_search_ads'` en el array `appliedAttributionSources`. Esto te permite cargar el paywall en dos pasos: 1. Llama a `getFlow` de inmediato. Como aún no se ha aplicado ninguna atribución, Adapty resuelve la solicitud contra la audiencia predeterminada, por lo que el usuario ve un paywall al instante. 2. Cuando aparezca `'apple_search_ads'`, vuelve a llamar a `getFlow`. Adapty resuelve ahora la solicitud contra la audiencia de Apple Ads y devuelve el paywall segmentado, que reemplaza al primero. `appliedAttributionSources` puede estar vacío o ausente. Eso significa que: - La atribución de Apple Ads aún no se ha procesado para este perfil, o - no ha llegado ninguna atribución. De cualquier manera, el paso 1 es seguro: Adapty resuelve la solicitud contra la audiencia que coincida con el estado actual del perfil, normalmente la audiencia predeterminada. El paso 2 solo se ejecuta una vez que `'apple_search_ads'` aparece. :::important En cada lanzamiento posterior, el perfil en caché ya incluye `'apple_search_ads'` en `appliedAttributionSources`, por lo que el primer `getFlow` ya devuelve el paywall segmentado por Apple Ads — no hay una segunda petición ni ningún cambio visible. El flow en dos pasos solo importa en el primer lanzamiento, mientras la atribución todavía está en proceso. ::: ## Implementación \{#implementation\} Muestra un paywall de inmediato, luego escucha `'apple_search_ads'` y actualiza el paywall cuando llegue. 1. **Activa el SDK.** Consulta [Instala y configura el SDK de Capacitor](sdk-installation-capacitor). 2. **Carga y muestra un paywall** con `getFlow` como de costumbre — no bloquees la ejecución esperando la atribución. 3. **Suscríbete a las actualizaciones del perfil** con `adapty.addListener('onLatestProfileLoad', …)` y comprueba si aparece `'apple_search_ads'`. Cuando aparezca, vuelve a obtener el paywall y muestra el actualizado. Si todavía no has configurado el listener, consulta [Escucha las actualizaciones de suscripción](capacitor-check-subscription-status#listen-to-subscription-updates): ```typescript const listener = await adapty.addListener('onLatestProfileLoad', async ({ profile }) => { if (!profile.appliedAttributionSources?.includes('apple_search_ads')) return; const targeted = await adapty.getFlow({ placementId }); // present the targeted flow in place of the first one }); // Call listener.remove() after the upgrade, or after a timeout (see below). ``` 4. **Detén la escucha tras un tiempo de espera.** La mayoría de los usuarios nunca reciben atribución de Apple Ads, así que elimina el listener pasado un tiempo en lugar de mantenerlo abierto durante toda la sesión. Configura un [paywall de respaldo](capacitor-use-fallback-paywalls) para el placement para que el usuario siempre vea algo si falla una solicitud. ## Ejemplo completo \{#complete-example\} `onAppleAdsAttribution` se resuelve cuando se aplica la atribución de Apple Ads, o se rechaza tras `timeoutMs`. El siguiente ejemplo carga un paywall de inmediato y lo vuelve a obtener cuando llega la atribución — los usuarios de Apple Ads ven el paywall personalizado y, si la atribución nunca llega, el primer paywall se mantiene: ```typescript const APPLE_ADS_SOURCE = 'apple_search_ads'; const placementId = 'YOUR_PLACEMENT_ID'; function hasAppleAdsAttribution(profile: AdaptyProfile): boolean { return profile.appliedAttributionSources?.includes(APPLE_ADS_SOURCE) ?? false; } /** * Resolves once Apple Ads attribution is applied to the profile. * Rejects with a timeout error if attribution never arrives within `timeoutMs`. * Call after `adapty.activate()`. */ export function onAppleAdsAttribution(timeoutMs: number): Promise<void> { return new Promise((resolve, reject) => { let timer: ReturnType<typeof setTimeout> | undefined; let handle: { remove: () => void } | undefined; const stop = () => { clearTimeout(timer); handle?.remove(); }; adapty .addListener('onLatestProfileLoad', ({ profile }) => { if (!hasAppleAdsAttribution(profile)) return; stop(); resolve(); }) .then(listener => { handle = listener; }); timer = setTimeout(() => { stop(); reject(new Error(`Apple Ads attribution timed out after ${timeoutMs}ms`)); }, timeoutMs); }); } let flow = await adapty.getFlow({ placementId }); onAppleAdsAttribution(30_000) .then(() => adapty.getFlow({ placementId })) .then(updated => { flow = updated; }) .catch(() => { console.log('Apple Ads attribution or loading failed'); }); ``` Al primer lanzamiento, un usuario de Apple Ads verá brevemente el paywall predeterminado antes de que se reemplace. Si presentas paywalls con el Paywall Builder, decide si volver a presentarlo es aceptable, o aplica la actualización solo antes de mostrar el paywall. Ajusta `timeoutMs` según el tiempo que quieras esperar — la atribución que está en camino suele llegar en pocos segundos tras el lanzamiento. Si tu app ya escucha `onLatestProfileLoad` para otros propósitos (por ejemplo, [comprobar el estado de la suscripción](capacitor-check-subscription-status#listen-to-subscription-updates)), no necesitas cambiarlo. `adapty.addListener` admite múltiples listeners independientes, así que este añade el suyo propio sin afectar a los demás. --- # File: capacitor-test --- --- title: "Test & release in Capacitor SDK" description: "Aprende cómo probar y publicar tu app de Capacitor con el SDK de Adapty." --- Si ya has implementado el SDK de Adapty en tu app de Capacitor, querrás comprobar que todo está correctamente configurado y que las compras funcionan como se espera en las plataformas iOS y Android. Esto implica probar tanto la integración del SDK como el flujo real de compras con el entorno sandbox de Apple y el entorno de pruebas de Google Play. ## Prueba tu app \{#test-your-app\} Para realizar pruebas exhaustivas de tus compras in-app, consulta nuestras guías de pruebas específicas por plataforma: [guía de pruebas iOS](test-purchases-in-sandbox) y [guía de pruebas Android](testing-on-android). ## Prepárate para el lanzamiento \{#prepare-for-release\} Antes de enviar tu app al store, sigue la [lista de verificación de lanzamiento](release-checklist) para confirmar que: - La conexión con el store y las notificaciones del servidor están configuradas - Las compras se completan y se reportan a Adapty - El acceso se desbloquea y se restaura correctamente - Se cumplen los requisitos de privacidad y revisión --- # File: kids-mode-capacitor --- --- title: "Modo Infantil en Capacitor SDK" description: "Activa fácilmente el Modo Infantil para cumplir con las políticas de Apple y Google. No se recopilan IDFA, GAID ni datos publicitarios en Capacitor SDK." --- Si tu aplicación de Capacitor está destinada a niños, debes seguir las políticas de [Apple](https://developer.apple.com/kids/) y [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Si usas el SDK de Adapty, unos pocos pasos sencillos te ayudarán a configurarlo para cumplir con estas políticas y superar las revisiones de las stores. ## ¿Qué se necesita? \{#whats-required\} Debes configurar el SDK para desactivar la recopilación de: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [Dirección IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Además, te recomendamos usar el customer user ID con cuidado. Un ID en formato `<FirstName.LastName>` se considerará claramente como recopilación de datos personales, al igual que usar un correo electrónico. Para el Modo Infantil, la mejor práctica es usar identificadores aleatorios o anonimizados (por ejemplo, IDs con hash o UUIDs generados por el dispositivo) para garantizar el cumplimiento normativo. ## Activar el Modo Infantil \{#enabling-kids-mode\} ### Cambios en el Adapty Dashboard \{#updates-in-the-adapty-dashboard\} En el Adapty Dashboard, debes deshabilitar la recopilación de direcciones IP. Para ello, ve a [App settings](https://app.adapty.io/settings/general) y haz clic en **Disable IP address collection** en **Collect users' IP address**. ### Actualizaciones en el código de tu app móvil \{#updates-in-your-mobile-app-code\} Para cumplir con las políticas, desactiva la recopilación del IDFA, GAID y dirección IP del usuario: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { // Disable IP address collection ipAddressCollectionDisabled: true, // Disable IDFA collection on iOS ios: { idfaCollectionDisabled: true }, // Disable Google Advertising ID collection on Android android: { adIdCollectionDisabled: true } } }); console.log('Adapty activated with Kids Mode enabled'); } catch (error) { console.error('Failed to activate Adapty with Kids Mode:', error); } ``` ### Configuraciones específicas por plataforma \{#platform-specific-configurations\} #### iOS \{#ios\} <SDKv4> Aunque deshabilites la recopilación de IDFA en el código (como se indica arriba), tu build sigue incluyendo los frameworks `AdSupport` y `AppTrackingTransparency`. La categoría Kids de la App Store no los permite. Y dado que el SDK v4 instala el SDK nativo de iOS a través de Swift Package Manager, no hay ningún paso en el Podfile para eliminarlos. Para cumplir con los requisitos de Apple, añade el comando `adapty-kids-mode` que incluye el SDK al `postinstall` de tu app. Habilita el trait `KidsMode` del SDK, que excluye ese código de la compilación. El comando se vuelve a aplicar en cada instalación: ```json showLineNumbers title="package.json" { "scripts": { "postinstall": "adapty-kids-mode" } } ``` Luego reinstala y vuelve a resolver los paquetes de iOS, y compila con **Xcode 26** o posterior: ```sh showLineNumbers title="Shell" npm install npx cap sync ios ``` Para desactivar el Modo Infantil, ejecuta `adapty-kids-mode disable` y vuelve a sincronizar. </SDKv4> <SDKv3> Si usas CocoaPods para iOS, también puedes activar el Modo Infantil a nivel nativo: 1. Actualiza tu Podfile: - Si **no** tienes una sección `post_install`, añade el bloque de código completo que aparece a continuación. - Si **sí** tienes una sección `post_install`, combina las líneas resaltadas en ella. ```ruby showLineNumbers title="Podfile" def adapty_enable_kids_mode(installer) installer.pods_project.targets.each do |target| next unless target.name == 'Adapty' target.build_configurations.each do |config| flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)' flags = flags.join(' ') if flags.is_a?(Array) config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} -DADAPTY_KIDS_MODE" end target.frameworks_build_phase.files.dup.each do |bf| target.frameworks_build_phase.remove_build_file(bf) if bf.display_name.to_s.include?('AdSupport') end end installer.pods_project.save Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xc| File.write(xc, File.read(xc).gsub(/\s*-framework\s+"?AdSupport"?/, '')) end end post_install do |installer| # ... keep your existing post_install body (Flutter adds one automatically) ... adapty_enable_kids_mode(installer) # <-- enable Adapty Kids Mode end ``` 2. Ejecuta el siguiente comando para aplicar los cambios: ```sh showLineNumbers title="Shell" pod install ``` </SDKv3> #### Android: eliminar el permiso de ID publicitario \{#android-remove-the-advertising-id-permission\} Establecer `adIdCollectionDisabled: true` (más arriba) evita que Adapty recopile el ID publicitario, pero el SDK sigue declarando el permiso `AD_ID`. Si tu app está dirigida **únicamente** a niños y compila contra Android 13 (API 33) o superior, Google Play te prohíbe incluso solicitarlo. Realiza dos adiciones en tu elemento `<manifest>`: 1. Declara el espacio de nombres `tools` (el manifiesto predeterminado de Capacitor no lo incluye). 2. Añade una entrada `<uses-permission>` para `AD_ID` con `tools:node="remove"` para eliminarlo. ```xml showLineNumbers title="android/app/src/main/AndroidManifest.xml" <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" /> </manifest> ``` ## Próximos pasos \{#next-steps\} Una vez que hayas activado el Modo Infantil, asegúrate de: 1. Probar tu app a fondo para garantizar que toda la funcionalidad funciona correctamente. 2. Revisar la política de privacidad de tu app para reflejar la recopilación de datos deshabilitada. 3. Enviar tu app a revisión con documentación clara sobre el cumplimiento del Modo Infantil. Para más información sobre los requisitos específicos de cada plataforma: - [Modo Infantil en iOS SDK](kids-mode) para detalles adicionales de configuración en iOS - [Modo Infantil en Android SDK](kids-mode-android) para detalles adicionales de configuración en Android --- # File: capacitor-reference --- --- title: "Referencia" description: "Documentación de referencia para el SDK de Adapty Capacitor." --- Esta página contiene documentación de referencia para el SDK de Adapty Capacitor. Elige el tema que necesitas: - **[Modelos del SDK](https://capacitor.adapty.io/)** - Modelos de datos y estructuras utilizados por el SDK - **[Gestión de errores](capacitor-handle-errors)** - Manejo de errores y solución de problemas --- # File: capacitor-handle-errors --- --- title: "Gestionar errores en el SDK de Capacitor" description: "Gestionar errores en el SDK de Capacitor." --- Cada error que devuelve el SDK es una instancia de `AdaptyError`. Aquí tienes un ejemplo: :::tip **Activa los logs detallados antes de depurar.** La mayoría de los `AdaptyError` envuelven un error subyacente de StoreKit, Play Billing, red o backend. Con los logs detallados activados (`adapty.setLogLevel({ logLevel: 'verbose' })` — consulta [Logging](sdk-installation-capacitor#logging)), ese error se muestra en la consola, lo que generalmente indica la causa real. La propiedad `detail` de `AdaptyError` se rellena independientemente del nivel de log — los logs detallados simplemente la muestran en la consola. ::: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product }); // Manejar el resultado de la compra if (result.type === 'success') { console.log('Compra exitosa:', result.profile); } else if (result.type === 'user_cancelled') { console.log('El usuario canceló la compra'); } else if (result.type === 'pending') { console.log('La compra está pendiente'); } } catch (error) { if (error instanceof AdaptyError) { console.error('Error de Adapty:', error.adaptyCode, error.localizedDescription); // Manejar códigos de error específicos switch (error.adaptyCode) { case ErrorCodeName.cantMakePayments: console.log('Las compras in-app no están permitidas en este dispositivo'); break; case ErrorCodeName.notActivated: console.log('El SDK de Adapty no está activado'); break; case ErrorCodeName.productPurchaseFailed: console.log('La compra falló:', error.detail); break; default: console.log('Se produjo otro error:', error.detail); } } else { console.error('Error no relacionado con Adapty:', error); } } ``` ## Propiedades del error \{#error-properties\} La clase `AdaptyError` expone las siguientes propiedades: | Propiedad | Tipo | Descripción | |----------|------|-------------| | `adaptyCode` | `number` | Código de error numérico (p. ej., `1003` para cantMakePayments) | | `localizedDescription` | `string` | Mensaje de error legible por el usuario | | `detail` | `string \| undefined` | Detalles adicionales del error (opcional) | | `message` | `string` | Mensaje de error completo con código y descripción | ## Códigos de error \{#error-codes\} El SDK exporta constantes y utilidades para trabajar con códigos de error: ### Constante ErrorCodeName \{#errorcodename-constant\} Mapea identificadores de cadena a códigos numéricos: ```typescript ErrorCodeName.cantMakePayments // 1003 ErrorCodeName.notActivated // 2002 ErrorCodeName.networkFailed // 2005 ``` ### Constante ErrorCode \{#errorcode-constant\} Mapea códigos numéricos a identificadores de cadena: ```typescript ErrorCode[1003] // 'cantMakePayments' ErrorCode[2002] // 'notActivated' ErrorCode[2005] // 'networkFailed' ``` ### Funciones de ayuda \{#helper-functions\} ```typescript // Get numeric code from string name: getErrorCode('cantMakePayments') // 1003 // Get string name from numeric code: getErrorPrompt(1003) // 'cantMakePayments' ``` ### Comparar códigos de error \{#comparing-error-codes\} **Importante:** `error.adaptyCode` es un **número**, así que compáralo directamente con códigos numéricos: ```typescript // Option 1: Use ErrorCodeName constant (recommended) ✅ if (error.adaptyCode === ErrorCodeName.cantMakePayments) { console.log('Cannot make payments'); } // Option 2: Compare with numeric literal ✅ if (error.adaptyCode === 1003) { console.log('Cannot make payments'); } // NOT like this ❌ - compares number to string and will never match if (error.adaptyCode === ErrorCode[1003]) { } ``` ## Manejador de errores global \{#global-error-handler\} Puedes configurar un manejador de errores global para capturar todos los errores de Adapty: ```typescript showLineNumbers // Configura el manejador de errores global AdaptyError.onError = (error: AdaptyError) => { console.error('Error global de Adapty:', { code: error.adaptyCode, message: error.localizedDescription, detail: error.detail }); // Gestiona tipos de error específicos de forma global if (error.adaptyCode === ErrorCodeName.notActivated) { // SDK no activado - puede que sea necesario reintentar la activación console.log('SDK no activado, intentando reactivar...'); } }; ``` ## Patrones comunes de manejo de errores \{#common-error-handling-patterns\} ### Gestión de errores en las compras \{#handle-purchase-errors\} ```typescript showLineNumbers async function handlePurchase(product: AdaptyPaywallProduct) { try { const result = await adapty.makePurchase({ product }); if (result.type === 'success') { console.log('Purchase successful:', result.profile); } else if (result.type === 'user_cancelled') { console.log('User cancelled the purchase'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { if (error instanceof AdaptyError) { switch (error.adaptyCode) { case ErrorCodeName.cantMakePayments: console.log('In-app purchases not allowed'); break; case ErrorCodeName.productPurchaseFailed: console.log('Purchase failed:', error.detail); break; default: console.error('Purchase error:', error.localizedDescription); } } } } ``` ### Gestión de errores de red \{#handle-network-errors\} ```typescript showLineNumbers async function fetchFlow(placementId: string) { try { const flow = await adapty.getFlow({ placementId }); return flow; } catch (error) { if (error instanceof AdaptyError) { switch (error.adaptyCode) { case ErrorCodeName.networkFailed: console.log('Network error, retrying...'); // Implement retry logic break; case ErrorCodeName.serverError: console.log('Server error:', error.detail); break; case ErrorCodeName.notActivated: console.log('SDK not activated'); break; default: console.error('Paywall fetch error:', error.localizedDescription); } } throw error; } } ``` ## Códigos de sistema de StoreKit \{#system-storekit-codes\} | Error | Código | Descripción | |-----|----|-----------| | unknown | 0 | Este error indica que ocurrió un error desconocido o inesperado. | | clientInvalid | 1 | Este código de error indica que el cliente no tiene permiso para realizar la acción solicitada. | | paymentCancelled | 2 | <p>Este código de error indica que el usuario canceló una solicitud de pago.</p><p>No se requiere ninguna acción, pero en términos de lógica de negocio, puedes ofrecer un descuento al usuario o recordárselo más adelante.</p> | | paymentInvalid | 3 | Este error indica que uno de los parámetros de pago no fue reconocido por la store. | | paymentNotAllowed | 4 | <p>Este código de error indica que el usuario no tiene permiso para autorizar pagos. Posibles razones:</p><p></p><p>- Los pagos no están disponibles en el país del usuario.</p><p>- El usuario es menor de edad.</p> | | storeProductNotAvailable | 5 | Este código de error indica que el producto solicitado no está disponible en la App Store. Asegúrate de que el producto esté disponible en el país correspondiente. | | cloudServicePermissionDenied | 6 | Este código de error indica que el usuario no ha permitido el acceso a la información del servicio en la nube. | | cloudServiceNetworkConnectionFailed | 7 | Este código de error indica que el dispositivo no pudo conectarse a la red. | | cloudServiceRevoked | 8 | Este código de error indica que el usuario ha revocado el permiso para usar este servicio en la nube. | | privacyAcknowledgementRequired | 9 | Este código de error indica que el usuario aún no ha aceptado la política de privacidad de la store. | | unauthorizedRequestData | 10 | Este código de error indica que la solicitud está construida de forma incorrecta. | | invalidOfferIdentifier | 11 | <p>El identificador de oferta no es válido. Posibles razones:</p><p></p><p>- No has configurado una oferta con ese identificador en la App Store.</p><p>- Has revocado la oferta.</p><p>- Hay un error tipográfico en el ID de la oferta.</p> | | invalidSignature | 12 | Este código de error indica que la firma en un descuento de pago no es válida. Asegúrate de haber rellenado el campo **In-app purchase Key ID** y de haber subido el archivo **In-App Purchase Private Key**. Consulta el tema [Configure App Store integration](app-store-connection-configuration) para más detalles. | | missingOfferParams | 13 | <p>Este error indica problemas con la integración de Adapty o con las ofertas.</p><p>Consulta [Configure App Store integration](app-store-connection-configuration) y [Offers](offers) para más detalles sobre cómo configurarlas.</p> | | invalidOfferPrice | 14 | Este código de error indica que el precio que especificaste en la store ya no es válido. Las ofertas siempre deben representar un precio con descuento. | ## Códigos personalizados de Android \{#custom-android-codes\} | Error | Código | Descripción | |-----|----|-----------| | adaptyNotInitialized | 20 | Necesitas configurar correctamente el SDK de Adapty mediante el método `Adapty.activate`. Aprende cómo hacerlo [para React Native](sdk-installation-reactnative). | | productNotFound | 22 | Este error indica que el producto solicitado para la compra no está disponible en la store. | | invalidJson | 23 | El JSON del paywall no es válido. Corrígelo en el Adapty Dashboard. Consulta el tema [Customize paywall with remote config](customize-paywall-with-remote-config) para más detalles sobre cómo corregirlo. | | currentSubscriptionToUpdateNotFoundInHistory | 24 | No se encontró la suscripción original que debe renovarse. | | pendingPurchase | 25 | Este error indica que el estado de la compra está pendiente en lugar de completado. Consulta la página [Handling pending transactions](https://developer.android.com/google/play/billing/integrate#pending) en la documentación para desarrolladores de Android para más detalles. | | billingServiceTimeout | 97 | Este error indica que la solicitud alcanzó el tiempo de espera máximo antes de que Google Play pudiera responder. Esto puede deberse, por ejemplo, a un retraso en la ejecución de la acción solicitada por la llamada a la Play Billing Library. | | featureNotSupported | 98 | La función solicitada no es compatible con la Play Store en el dispositivo actual. | | billingServiceDisconnected | 99 | Este error fatal indica que la conexión de la app cliente al servicio de Google Play Store a través del `BillingClient` se ha interrumpido. | | billingServiceUnavailable | 102 | Este error transitorio indica que el servicio de facturación de Google Play no está disponible en este momento. En la mayoría de los casos, significa que hay un problema de conexión de red entre el dispositivo cliente y los servicios de facturación de Google Play. | | billingUnavailable | 103 | <p>Este error indica que ocurrió un error de facturación del usuario durante el proceso de compra. Algunos ejemplos de cuándo puede ocurrir:</p><p></p><p>1\. La app de Play Store en el dispositivo del usuario está desactualizada.</p><p>2. El usuario está en un país no compatible.</p><p>3. El usuario es un empleado de empresa y su administrador ha deshabilitado las compras.</p><p>4. Google Play no puede cargar el método de pago del usuario. Por ejemplo, la tarjeta de crédito del usuario puede haber caducado.</p><p>5. El usuario no ha iniciado sesión en la app de Play Store.</p> | | developerError | 105 | Este es un error fatal que indica que estás usando una API de forma incorrecta. | | billingError | 106 | Este es un error fatal que indica un problema interno en Google Play. | | itemAlreadyOwned | 107 | El producto consumible ya ha sido comprado. | | itemNotOwned | 108 | Este error indica que la acción solicitada sobre el artículo falló. | ## Códigos personalizados de StoreKit \{#custom-storekit-codes\} | Error | Código | Descripción | |-----|----|-----------| | noProductIDsFound | 1000 | <p>Este error indica que ninguno de los productos del paywall está disponible en la store.</p><p>Si encuentras este error, sigue los pasos a continuación para resolverlo:</p><p></p><p>1. Comprueba que todos los productos se han añadido al Adapty Dashboard.</p><p>2. Asegúrate de que el Bundle ID de tu app coincide con el de Apple Connect.</p><p>3. Verifica que los identificadores de producto de las app stores coincidan con los que has añadido al Dashboard. Ten en cuenta que los identificadores no deben contener el Bundle ID, a menos que ya esté incluido en la store.</p><p>4. Confirma que el estado de pago de la app está activo en tu configuración fiscal de Apple. Asegúrate de que tu información fiscal está actualizada y que tus certificados son válidos.</p><p>5. Comprueba que hay una cuenta bancaria vinculada a la app para que pueda ser elegible para la monetización.</p><p>6. Verifica que los productos estén disponibles en todas las regiones. Además, asegúrate de que tus productos estén en estado **"Ready to Submit"**.</p> | | productRequestFailed | 1002 | <p>No se pueden obtener los productos disponibles en este momento. Posible razón:</p><p></p><p>- Aún no se ha creado ninguna caché y no hay conexión a internet al mismo tiempo.</p> | | cantMakePayments | 1003 | Las compras in-app no están permitidas en este dispositivo. | | noPurchasesToRestore | 1004 | Este error indica que Google Play no encontró ninguna compra que restaurar. | | cantReadReceipt | 1005 | <p>No hay ningún recibo válido disponible en el dispositivo. Esto puede ser un problema durante las pruebas en sandbox.</p><p>No se requiere ninguna acción, pero en términos de lógica de negocio, puedes ofrecer un descuento al usuario o recordárselo más adelante.</p> | | productPurchaseFailed | 1006 | La compra del producto falló. Esto envuelve un error subyacente de StoreKit: lee el error interno (o activa los registros detallados para verlo en la consola) para conocer la razón exacta. El error interno suele ser uno de los códigos de StoreKit 0–14 de la tabla anterior, siendo los más comunes `paymentCancelled`, `paymentInvalid`, `paymentNotAllowed` o `invalidOfferPrice`. Si no puedes identificar una razón concreta, prueba con un nuevo [perfil de sandbox](test-purchases-in-sandbox); si sigue fallando, contacta con el soporte de Apple. | | refreshReceiptFailed | 1010 | Este error indica que no se recibió el recibo. Solo aplica a StoreKit 1. | | receiveRestoredTransactionsFailed | 1011 | La restauración de la compra falló. | ## Códigos de red personalizados \{#custom-network-codes\} | Error | Código | Descripción | | :------------------- | :----- | :----------------------------------------------------------- | | notActivated | 2002 | Necesitas configurar correctamente el SDK de Adapty mediante el método `Adapty.activate`. Aprende cómo hacerlo [para React Native](sdk-installation-reactnative). | | badRequest | 2003 | Solicitud incorrecta. | | serverError | 2004 | Error del servidor. | | networkFailed | 2005 | La solicitud de red falló. | | decodingFailed | 2006 | Este error indica que falló la decodificación de la respuesta. | | encodingFailed | 2009 | Este error indica que falló la codificación de la solicitud. | | analyticsDisabled | 3000 | No podemos gestionar eventos de análisis porque los has desactivado. Consulta el tema [Analytics integration](analytics-integration) para más detalles. | | wrongParam | 3001 | Este error indica que alguno de tus parámetros no es correcto: está en blanco cuando no puede estarlo, tiene un tipo incorrecto, etc. | | activateOnceError | 3005 | No es posible llamar al método `.activate` más de una vez. | | profileWasChanged | 3006 | El perfil de usuario cambió durante la operación. | | fetchTimeoutError | 3101 | Este error significa que el paywall no se pudo obtener dentro del límite establecido. Para evitar esta situación, [configura los fallbacks locales](fetch-paywalls-and-products). | | operationInterrupted | 9000 | Esta operación fue interrumpida por el sistema. | --- # File: capacitor-sdk-migration-guides --- --- title: "Guías de migración del SDK de Capacitor" description: "Guías de migración para las versiones del SDK de Adapty para Capacitor." --- Esta página contiene todas las guías de migración para el SDK de Adapty para Capacitor. Elige la versión a la que quieres migrar para obtener instrucciones detalladas: - **[Migrar a v4.0 (beta)](migration-to-capacitor-sdk-v4)** - [**Migrar a v3.16**](migration-to-capacitor-316) --- # File: migration-to-capacitor-sdk-v4 --- --- title: "Migrar el SDK de Adapty Capacitor a v. 4.0" description: "Migra al SDK de Adapty Capacitor v4.0 (beta) reemplazando las APIs de paywall por APIs de flow, compatibles tanto con Flow Builder como con Paywall Builder." --- El SDK de Adapty Capacitor 4.0 (beta) introduce flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan tanto con el nuevo Flow Builder como con el Paywall Builder existente; no se requieren cambios de configuración en el Adapty Dashboard. ## Referencia rápida \{#quick-reference\} | v3 | v4 | |---|---| | `adapty.getPaywall({ placementId, locale?, params? })` | `adapty.getFlow({ placementId, params? })` | | `adapty.getPaywallForDefaultAudience({ placementId, locale?, params? })` | `adapty.getFlowForDefaultAudience({ placementId, params? })` | | `adapty.getPaywallProducts({ paywall })` | `adapty.getPaywallProducts({ flow })` | | `adapty.logShowPaywall({ paywall })` | `adapty.logShowFlow({ flow })` | | `AdaptyPaywall` (tipo) | `AdaptyFlow` + `AdaptyFlowPaywall` | | `createPaywallView(paywall, params?)` | `createFlowView(flow, params?)` | | `PaywallViewController` | `FlowViewController` | | `EventHandlers` (tipo) | `FlowEventHandlers` | | `CreatePaywallViewParamsInput` | `CreateFlowViewParamsInput` | | `onRenderingFailed` | `onError` | `AdaptyPaywallProduct` mantiene su nombre — los productos siguen perteneciendo a un flow, y `getPaywallProducts` también mantiene su nombre, ahora aceptando un `AdaptyFlow`. Los métodos `getFlow` y `getFlowForDefaultAudience` ya no reciben un parámetro `locale`. Las APIs de compra y perfil (`makePurchase`, `restorePurchases`, `getProfile`, `identify`, `updateProfile`) y los respaldos mediante `setFallback` no han cambiado. Los métodos de vista `present`, `dismiss`, `setEventHandlers` y `showDialog`, y los manejadores de eventos `onCloseButtonPress`, `onUrlPress`, `onCustomAction`, `onProductSelected`, `onPurchaseStarted`, `onPurchaseCompleted`, `onPurchaseFailed`, `onRestoreStarted`, `onRestoreCompleted`, `onRestoreFailed`, `onLoadingProductsFailed`, `onWebPaymentNavigationFinished` y `onAndroidSystemBack` mantienen los mismos nombres que en v3. Los métodos de onboarding siguen funcionando pero están obsoletos — consulta [Deprecación de la API de Onboarding](#onboarding-api-deprecation). Algunos comportamientos predeterminados han cambiado — consulta [Cambios en el comportamiento predeterminado](#default-behavior-changes). ## Versiones mínimas \{#minimum-versions\} Los requisitos de ejecución no han cambiado desde v3.16+: **iOS 15.0**, **Android minSdk 24** y **Capacitor 8**. No es necesario modificar el deployment target. Hay un nuevo requisito de compilación: **Xcode 26 o posterior** — el SDK nativo de Adapty para iOS 4.0.0-beta.2 incluido en esta versión usa Swift tools 6.2. v4 incluye los SDKs nativos de Adapty iOS 4.0.0-beta.2 y Android BOM 4.0.0-beta.1. ## Instalación \{#installation\} ### Actualizar el paquete \{#update-the-package\} La v4.0 es una versión preliminar, así que fija la versión exacta; npm no selecciona versiones preliminares mediante rangos con acento circunflejo o tilde: ```bash showLineNumbers npm install @adapty/capacitor@4.0.0-beta.2 ``` Luego sincroniza los proyectos nativos: ```bash showLineNumbers npx cap sync ``` ### iOS: solo Swift Package Manager [El repositorio de specs de CocoaPods pasará a ser de solo lectura en diciembre de 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/), por lo que a partir de la v4 se elimina el `AdaptyCapacitor.podspec` y el SDK se instala en iOS **únicamente a través de Swift Package Manager (SPM)**. El proyecto iOS de tu app debe utilizar la integración SPM de Capacitor: - Aplicaciones nuevas: añade la plataforma iOS con el gestor de paquetes SPM: ```bash showLineNumbers npx cap add ios --packagemanager SPM ``` - Aplicaciones existentes basadas en CocoaPods: migra el proyecto iOS siguiendo la [guía de Capacitor para usar SPM en un proyecto existente](https://capacitorjs.com/docs/ios/spm#using-spm-in-an-existing-capacitor-project). Consulta [Instalar el SDK de Adapty](sdk-installation-capacitor) para ver la configuración completa. ## Obtener flows \{#fetching-flows\} ### getPaywall → getFlow El tipo retornado cambia de `AdaptyPaywall` a `AdaptyFlow`, y la opción `locale` se elimina — cuando renderizas un flow, el idioma se resuelve automáticamente; para paywalls personalizados, todos los idiomas se devuelven en `flow.remoteConfigs`: ```diff showLineNumbers - const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en' }); + const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); ``` `getPaywallForDefaultAudience` se renombra de la misma forma: ```diff showLineNumbers - const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en' }); + const flow = await adapty.getFlowForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID' }); ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` mantiene su nombre pero ahora recibe un `AdaptyFlow`: ```diff showLineNumbers - const products = await adapty.getPaywallProducts({ paywall }); + const products = await adapty.getPaywallProducts({ flow }); ``` ## Modelo de datos \{#data-model\} `getFlow` devuelve un `AdaptyFlow` en lugar de un `AdaptyPaywall`, y la forma del objeto ha cambiado: | Campo v3 `AdaptyPaywall` | Campo v4 `AdaptyFlow` | Acción | |---|---|---| | `remoteConfig?` (único) | `remoteConfigs?: AdaptyRemoteConfig[]` (array) | Un flow lleva un Remote Config por idioma configurado. Lee el que coincida con el usuario: `flow.remoteConfigs?.find((c) => c.lang === 'en')`. | | `products` | `flow.paywalls[i].productIdentifiers` | Los identificadores de producto ahora están en cada variación del flow, no en el flow. | | `webPurchaseUrl?` | `flow.paywalls[i].webPurchaseUrl` | Movido del flow a cada variación de paywall. | | `version?: number` | `flowVersionId?: string` | Renombrado, y el tipo cambió de `number` a `string`. | | `hasViewConfiguration` | eliminado | Elimina cualquier comprobación de `hasViewConfiguration` en tu código — `createFlowView` ahora lanza un error en su lugar (ver [Mostrar flows](#displaying-flows)). | | `requestLocale` | eliminado | El locale ya no forma parte del modelo. | | _(nuevo)_ | `paywalls: AdaptyFlowPaywall[]` | Cada entrada es una variación de paywall en el flow. | | _(nuevo)_ | `responseCreatedAt: number` | Marca de tiempo de la respuesta del servidor, en milisegundos. | `hasViewConfiguration` y `requestLocale` permanecen en `AdaptyOnboarding` — solo el modelo de flow los elimina. Los identificadores de producto se han movido del flow a cada variación: ```diff showLineNumbers - const ids = paywall.products; + const ids = flow.paywalls[0].productIdentifiers; ``` ## Métodos de paywall web \{#web-paywall-methods\} `openWebPaywall` y `createWebPaywallUrl` mantienen sus nombres, pero la opción `paywallOrProduct` ahora acepta un `AdaptyFlowPaywall` (una variante de flow) en lugar de un `AdaptyPaywall`. Todavía puedes pasar un `AdaptyPaywallProduct`. Asegúrate de que `flow.paywalls` no esté vacío antes de leer la primera entrada: ```diff showLineNumbers const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); - await adapty.openWebPaywall({ paywallOrProduct: paywall }); + await adapty.openWebPaywall({ paywallOrProduct: flow.paywalls[0] }); ``` ## Seguimiento de vistas de flow \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` ha pasado a llamarse `logShowFlow` y ahora recibe un `AdaptyFlow`. El evento se sigue registrando contra la misma variación, por lo que las métricas de funnel y prueba A/B existentes siguen funcionando sin cambios en el dashboard. ```diff showLineNumbers - await adapty.logShowPaywall({ paywall }); + await adapty.logShowFlow({ flow }); ``` Al igual que en v3, no es necesario llamar a este método al mostrar flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder) — Adapty registra esas vistas automáticamente. ## Mostrar flows \{#displaying-flows\} ### createPaywallView → createFlowView \{#createpaywallview--createflowview\} Renombra la función factory y pasa el `AdaptyFlow`. El controlador devuelto pasa de llamarse `PaywallViewController` a `FlowViewController`, pero sus métodos (`present`, `dismiss`, `setEventHandlers`, `showDialog`) no cambian. El tipo de parámetros se renombra de `CreatePaywallViewParamsInput` a `CreateFlowViewParamsInput`: ```diff showLineNumbers - import { createPaywallView } from '@adapty/capacitor'; + import { createFlowView } from '@adapty/capacitor'; - const view = await createPaywallView(paywall); + const view = await createFlowView(flow); await view.present(); ``` `createFlowView` lanza un `AdaptyError` si el flow no tiene ninguna vista configurada; esto reemplaza la comprobación `hasViewConfiguration` de la v3: ```diff showLineNumbers - if (paywall.hasViewConfiguration) { - const view = await createPaywallView(paywall); - await view.present(); - } + try { + const view = await createFlowView(flow); + await view.present(); + } catch (error) { + // the flow has no view configured, or view creation failed + } ``` :::note Una vista de flow es de un solo uso: después de llamar a `dismiss()`, la vista se destruye y sus manejadores de eventos se eliminan, así que llama a `createFlowView` de nuevo para mostrar el flow otra vez. ::: ### Rellenos de área segura en Android \{#android-safe-area-paddings\} `CreateFlowViewParamsInput` añade un nuevo parámetro: `enableSafeArea`, que controla los rellenos de área segura en Android en tiempo de ejecución. Se anida bajo la clave `android` y tiene el valor predeterminado `true`: ```typescript showLineNumbers const view = await createFlowView(flow, { android: { enableSafeArea: true }, }); ``` ## Manejo de eventos \{#handling-events\} La interfaz del manejador de eventos pasa de llamarse `EventHandlers` a `FlowEventHandlers`, y un callback también cambia de nombre. El cuerpo de los manejadores existentes no necesita modificaciones — solo renombra: ```diff showLineNumbers - onRenderingFailed: (error) => { /* … */ }, + onError: (error) => { /* … */ }, ``` Todos los demás manejadores de eventos conservan sus nombres. Dos de ellos también reciben un segundo argumento: `onPurchaseCompleted` pasa a ser `(purchaseResult, product)` y `onPurchaseFailed` pasa a ser `(error, product)`, donde `product` es el `AdaptyPaywallProduct` involucrado. Consulta [Gestión de eventos de flow y paywall](capacitor-handling-events) para ver la lista completa. La v4 también añade algunas funcionalidades opcionales que puedes activar: - Los métodos `adapty.openWebUrl({ url, openIn })` y `adapty.requestAppReview()` respaldan los manejadores predeterminados `onUrlPress` y `onRequestAppReview`, de modo que las URLs y las solicitudes de reseña de la app se gestionan de forma nativa sin configuración adicional. Llámalos directamente solo si sustituyes esos manejadores. - Gestión de compras en modo Observer dentro de flows mediante los nuevos manejadores `onObserverPurchaseInitiated` / `onObserverRestoreInitiated`. Consulta [Presentar flows en modo Observer](capacitor-present-flows-in-observer-mode). ## Cambios en el comportamiento predeterminado \{#default-behavior-changes\} Estos cambios no generan errores de compilación, así que pruébalos en tiempo de ejecución: - **`onAndroidSystemBack`**: El comportamiento predeterminado cambió de cerrar la vista a mantenerla abierta. Para restaurar el comportamiento anterior, devuelve `true` desde el handler. - **`onPurchaseCompleted`**: El comportamiento predeterminado cambió de cerrar la vista (salvo que el usuario cancelara la compra) a mantenerla siempre abierta. Para restaurar el comportamiento anterior, devuelve `purchaseResult.type !== 'user_cancelled'` desde el handler. - **`onRestoreCompleted`**: El comportamiento predeterminado cambió de cerrar la vista tras una restauración exitosa a mantenerla abierta. Para restaurar el comportamiento anterior, devuelve `true` desde el handler. - **`onUrlPress`**: Ahora el comportamiento predeterminado abre la URL a través de la capa nativa, respetando la configuración de navegador integrado o externo del dashboard. Reemplaza el handler para abrir URLs tú mismo. - **Las vistas son de un solo uso**: Después de `dismiss()`, la vista se destruye. Llama a `createFlowView` de nuevo para mostrar el flow otra vez. ## APIs eliminadas \{#removed-apis\} ### Exportaciones eliminadas \{#removed-exports\} Estos símbolos ya no se exportan desde `@adapty/capacitor`. Elimina sus importaciones: - **`AdaptyPaywall`**: Usa `AdaptyFlow` y `AdaptyFlowPaywall` en su lugar. - **`ProductReference`**: Usa `AdaptyProductIdentifier`, léelo desde `flow.paywalls[i].productIdentifiers`. - **`AdaptyPaywallBuilder`**: Eliminado. Los flows y paywalls se renderizan de forma nativa. - **`AdaptyAndroidSubscriptionUpdateParameters`**: Usa la forma anidada de parámetros de compra `android` (ver más abajo). ### activate: lockMethodsUntilReady `lockMethodsUntilReady` (ya obsoleto y sin efecto en v3) ha sido eliminado. Quítalo de tu llamada a `activate` — mantenerlo ya no compila: ```diff showLineNumbers - await adapty.activate({ apiKey: 'PUBLIC_SDK_KEY', params: { lockMethodsUntilReady: true } }); + await adapty.activate({ apiKey: 'PUBLIC_SDK_KEY' }); ``` ### makePurchase: parámetros de Android \{#makepurchase-android-parameters\} La forma plana obsoleta de `MakePurchaseParamsInput` para Android ha sido eliminada; solo queda la forma anidada. Mueve los parámetros de compra de Android a `params: { android: { ... } }`. Consulta [Realizar compras](capacitor-making-purchases) para ver el ejemplo completo. ## Deprecación de la API de onboarding \{#onboarding-api-deprecation\} La API de onboarding heredada está deprecada en la v4.0 a favor del [Flow Builder](adapty-flow-builder). Sigue funcionando, pero se eliminará en una versión futura, así que planifica la migración de tus onboardings al Flow Builder. Símbolos deprecados: `getOnboarding`, `getOnboardingForDefaultAudience`, `createOnboardingView` y `OnboardingViewController`. --- # File: migration-to-capacitor-316 --- --- title: "Migrar el SDK de Adapty para Capacitor a v3.16" description: "Migra al SDK de Adapty para Capacitor v3.16 para obtener mejor rendimiento y nuevas funciones de monetización." --- A partir de Adapty SDK v3.16.0, se requiere Capacitor 8. Si necesitas Capacitor 7, usa Adapty SDK v3.15. Para actualizar al SDK de Capacitor v3.16, asegúrate de que tu proyecto use Capacitor 8. Si todavía usas Capacitor 7, tienes dos opciones: 1. **Actualiza a Capacitor 8**: Sigue la [guía oficial de migración de Capacitor](https://capacitorjs.com/docs/updating/8-0) para actualizar tu proyecto y luego instala Adapty SDK v3.16. 2. **Quédate en Adapty SDK v3.15**: Si actualizar a Capacitor 8 no es viable, continúa usando Adapty SDK v3.15, que es compatible con Capacitor 7. --- # End of Documentation _Generated on: 2026-07-24T13:01:55.649Z_ _Successfully processed: 45/45 files_ # FLUTTER - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-07-24T13:01:55.651Z Total files: 44 --- # File: sdk-installation-flutter --- --- title: "Instalar y configurar el SDK de Flutter" description: "Guía paso a paso para instalar el SDK de Adapty en Flutter para aplicaciones con suscripciones." --- El SDK de Adapty incluye dos módulos clave para una integración fluida en tu app Flutter: - **Core Adapty**: Este SDK esencial es necesario para que Adapty funcione correctamente en tu app. - **AdaptyUI**: Este módulo es necesario si usas el [Adapty Paywall Builder](adapty-paywall-builder), una herramienta visual sin código para crear paywalls multiplataforma fácilmente. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Consulta nuestra [app de ejemplo](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example), que muestra la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funciones básicas. ::: ## Requisitos \{#requirements\} El SDK de Adapty es compatible con iOS 13.0+, pero requiere iOS 15.0+ para funcionar correctamente con paywalls creados en el Paywall Builder. Adapty Flutter SDK 4.0 — que añade soporte para [Flow Builder](adapty-flow-builder) — eleva los requisitos mínimos a **iOS 15.0+**, **Xcode 26+** y **Flutter 3.32.0+** (Dart 3.8.0+). Consulta [Adapty SDK 4.0](#adapty-sdk-40-swift-package-manager) más abajo para ver los detalles de instalación. :::info Adapty es compatible con Google Play Billing Library hasta la versión 8.x. Por defecto, Adapty funciona con Google Play Billing Library v7.0.0, pero si quieres forzar una versión posterior, puedes añadir la dependencia manualmente siguiendo [este enlace](https://developer.android.com/google/play/billing/integrate#dependency). ::: :::info Instalar el SDK es el paso 5 de la configuración de Adapty. Para que las compras funcionen en tu app, también necesitas conectar tu app a los stores, y luego crear productos, un paywall y un placement en el Adapty Dashboard. La [guía de inicio rápido](quickstart) explica todos los pasos necesarios. ::: ## Instalar el SDK de Adapty \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Flutter.svg?style=flat&logo=flutter)](https://github.com/adaptyteam/AdaptySDK-Flutter/releases) :::important Los pasos a continuación instalan la última versión estable del SDK (3.x). Si necesitas la v4 — necesaria para el [Flow Builder](adapty-flow-builder) y utilizada en la [guía de inicio rápido](flutter-quickstart-paywalls) — sigue en su lugar [Adapty SDK 4.0: Swift Package Manager](#adapty-sdk-40-swift-package-manager) más abajo. ::: 1. Añade Adapty a tu archivo `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: ^<the latest SDK version> ``` 2. Ejecuta el siguiente comando para instalar las dependencias: ```bash showLineNumbers title="Terminal" flutter pub get ``` 3. Importa los SDK de Adapty en tu aplicación: ```dart showLineNumbers title="main.dart" import 'package:adapty_flutter/adapty_flutter.dart'; ``` ### Adapty SDK 4.0: Swift Package Manager Añade el SDK de Adapty Flutter 4.0, que incluye soporte para [Flow Builder](adapty-flow-builder), a tu `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: 4.0.0 ``` A partir de la v4, el SDK nativo de iOS ya no se distribuye a través de CocoaPods — el plugin lo obtiene únicamente a través de **Swift Package Manager** ([el repositorio de specs de CocoaPods pasa a ser de solo lectura en diciembre de 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)). Si usas Flutter 3.32–3.43, habilita el soporte de Swift Package Manager una sola vez: ```bash showLineNumbers title="Terminal" flutter config --enable-swift-package-manager ``` Flutter 3.44 y versiones posteriores activan Swift Package Manager por defecto, así que no es necesario hacer nada. Para ver los cambios de API en v4, consulta la [guía de migración](migration-to-flutter-sdk-v4). ## Activa el módulo Adapty del SDK \{#activate-adapty-module-of-adapty-sdk\} Activa el SDK de Adapty en el código de tu app. :::note El SDK de Adapty solo necesita activarse una vez en tu app. ::: Para obtener tu **Public SDK Key**: 1. Ve al Adapty Dashboard y navega a [**App settings → General**](https://app.adapty.io/settings/general). 2. En la sección **Api keys**, copia la **Public SDK Key** (NO la Secret Key). 3. Reemplaza `"YOUR_PUBLIC_SDK_KEY"` en el código. O bien, obtenla de forma programática usando el [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` O directamente: ``` npx adapty auth login adapty apps list ``` - Asegúrate de usar la **Public SDK key** para inicializar Adapty; la **Secret key** solo debe usarse para la [API del lado del servidor](getting-started-with-server-side-api). - Las **SDK keys** son únicas para cada app, así que si tienes varias apps asegúrate de elegir la correcta. ```dart showLineNumbers title="main.dart" void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override void initState() { _initializeAdapty(); super.initState(); } Future<void> _initializeAdapty() async { try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY'), ); } catch (e) { // handle the error } } Widget build(BuildContext context) { return Text("Hello"); } } ``` :::important Espera a que `activate` se resuelva antes de llamar a cualquier otro método del SDK de Adapty. Consulta el [orden de llamadas en el SDK de Flutter](flutter-sdk-call-order) para ver la secuencia completa. ::: Ahora configura los paywalls en tu app: - Si usas el [Adapty Paywall Builder](adapty-paywall-builder), primero [activa el módulo AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) a continuación, y luego sigue la [guía de inicio rápido del Paywall Builder](flutter-quickstart-paywalls). - Si creas tu propia interfaz de paywall, consulta la [guía de inicio rápido para paywalls personalizados](flutter-quickstart-manual). ## Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Si planeas usar [Paywall Builder](adapty-paywall-builder) y has [instalado el módulo AdaptyUI](sdk-installation-flutter#install-adapty-sdk), también necesitas activar AdaptyUI: :::note Las dependencias relacionadas con AdaptyUI se vinculan a tu app independientemente de si AdaptyUI está activado. ::: :::important En tu código, debes activar el módulo principal de Adapty antes de activar AdaptyUI. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withActivateUI(true), // This automatically activates AdaptyUI ); ``` ## Configuración opcional \{#optional-setup\} ### Registro #### Configurar el sistema de registro \{#set-up-the-logging-system\} Adapty registra errores y otra información importante para ayudarte a entender qué está ocurriendo. Los niveles disponibles son los siguientes: | Nivel | Descripción | | :----------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | | `AdaptyLogLevel.error` | Solo se registrarán los errores | | `AdaptyLogLevel.warn` | Se registrarán los errores y los mensajes del SDK que no causan errores críticos pero que conviene tener en cuenta. | | `AdaptyLogLevel.info` | Se registrarán los errores, las advertencias y varios mensajes informativos. Valor predeterminado | | `AdaptyLogLevel.verbose` | Se registrará cualquier información adicional que pueda ser útil durante la depuración, como llamadas a funciones, consultas a la API, etc. | | `AdaptyLogLevel.debug` | Se registrará información de depuración. | Puedes configurar el nivel de log en tu app antes de configurar Adapty: ```dart showLineNumbers title="main.dart" // Set log level before activation. // 'verbose' is recommended for development and the first production release await Adapty().setLogLevel(AdaptyLogLevel.verbose); // Or set it during configuration await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withLogLevel(AdaptyLogLevel.verbose), ); ``` ### Políticas de datos \{#data-policies\} Adapty no almacena datos personales de tus usuarios a menos que los envíes explícitamente, pero puedes implementar políticas adicionales de seguridad de datos para cumplir con las directrices de la store o del país. #### Deshabilitar la recopilación y el uso compartido de la dirección IP \{#disable-ip-address-collection-and-sharing\} Al activar el módulo de Adapty, establece `ipAddressCollectionDisabled` en `true` para deshabilitar la recopilación y el uso compartido de la dirección IP del usuario. El valor predeterminado es `false`. Usa este parámetro para mejorar la privacidad del usuario, cumplir con las regulaciones regionales de protección de datos (como el RGPD o la CCPA) o reducir la recopilación de datos innecesaria cuando las funciones basadas en IP no son necesarias para tu app. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withIpAddressCollectionDisabled(true), ); ``` #### Desactivar la recopilación y el uso compartido del ID publicitario \{#disable-advertising-id-collection-and-sharing\} Al activar el módulo de Adapty, establece `appleIdfaCollectionDisabled` (iOS) o `googleAdvertisingIdCollectionDisabled` (Android) en `true` para desactivar la recopilación de identificadores publicitarios. El valor por defecto es `false`. Usa este parámetro para cumplir con las políticas del App Store/Play Store, evitar que aparezca el aviso de App Tracking Transparency, o si tu app no necesita atribución publicitaria ni análisis basados en IDs publicitarios. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAppleIdfaCollectionDisabled(true) // iOS ..withGoogleAdvertisingIdCollectionDisabled(true), // Android ); ``` #### Configurar la caché de medios para AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} El módulo se activa automáticamente con el SDK. Si no utilizas el Paywall Builder y quieres desactivar el módulo AdaptyUI, pasa `withActivateUI(false)` durante la activación. De forma predeterminada, AdaptyUI almacena en caché los medios (como imágenes y vídeos) para mejorar el rendimiento y reducir el uso de red. Puedes personalizar la configuración de caché proporcionando una configuración personalizada. Usa `withMediaCacheConfiguration` para sobrescribir los límites de caché predeterminados. Esto es opcional: si no llamas a este método, se usarán los valores predeterminados (100 MB de tamaño en disco, sin límite de recuento en memoria). Sin embargo, si creas el objeto de configuración, todos sus parámetros son obligatorios. ```dart showLineNumbers title="main.dart" final mediaCacheConfig = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 200 * 1024 * 1024, // 200 MB memoryStorageCountLimit: 2147483647, // max int value diskStorageSizeLimit: 200 * 1024 * 1024, // 200 MB ); await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withMediaCacheConfiguration(mediaCacheConfig), ); ``` **Parámetros:** | Parámetro | Presencia | Descripción | |-------------------------|----------|-----------------------------------------------------------------------------| | memoryStorageTotalCostLimit | requerido | Tamaño total de la caché en memoria en bytes. El valor predeterminado es 100 MB. | | memoryStorageCountLimit | requerido | El límite de elementos de la memoria caché. El valor predeterminado es el valor máximo de int. | | diskStorageSizeLimit | requerido | El límite de tamaño de archivo en disco en bytes. El valor predeterminado es 100 MB. | ### Habilitar niveles de acceso locales (Android) \{#enable-local-access-levels-android\} Por defecto, los [niveles de acceso locales](local-access-levels) están habilitados en iOS y deshabilitados en Android. Para habilitarlos también en Android, establece `withGoogleLocalAccessLevelAllowed` en `true`: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withGoogleLocalAccessLevelAllowed(true), ); ``` ### Borrar datos al restaurar copia de seguridad \{#clear-data-on-backup-restore\} Cuando `appleClearDataOnBackup` se establece en `true`, el SDK detecta cuándo la app se restaura desde una copia de seguridad de iCloud y elimina todos los datos almacenados localmente por el SDK, incluida la información de perfil en caché, los detalles de productos y los paywalls. El SDK se inicializa entonces con un estado limpio. El valor predeterminado es `false`. :::note Solo se elimina la caché local del SDK. El historial de transacciones con Apple y los datos de usuario en los servidores de Adapty permanecen sin cambios. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAppleClearDataOnBackup(true) // default – false ); ``` ## Solución de problemas \{#troubleshooting\} #### Reglas de copia de seguridad de Android (configuración de Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Algunos SDKs (incluido Adapty) incluyen su propia configuración de Android Auto Backup. Si utilizas varios SDKs que definen reglas de copia de seguridad, el fusionador de manifiestos de Android puede fallar con un error relacionado con `android:fullBackupContent`, `android:dataExtractionRules` o `android:allowBackup`. Síntomas típicos del error: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Estos cambios deben realizarse en el directorio de la plataforma Android (normalmente en la carpeta `android/` de tu proyecto). ::: Para resolverlo, necesitas: - Indicar al fusionador de manifiestos que use los valores de tu app para los atributos relacionados con la copia de seguridad. - Crear archivos de reglas de copia de seguridad que combinen las reglas de Adapty con las de otros SDKs. #### 1. Añade el namespace `tools` a tu manifiesto \{#1-add-the-tools-namespace-to-your-manifest\} En tu archivo `AndroidManifest.xml`, asegúrate de que la etiqueta raíz `<manifest>` incluya tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. Sobreescribe los atributos de copia de seguridad en `<application>` \{#2-override-backup-attributes-in-application\} En el mismo archivo `AndroidManifest.xml`, actualiza la etiqueta `<application>` para que tu app proporcione los valores definitivos e indique al fusionador de manifiestos que reemplace los valores de las librerías: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` Si algún SDK también define `android:allowBackup`, inclúyelo en `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Crea los archivos de reglas de copia de seguridad combinadas \{#3-create-merged-backup-rules-files\} Crea archivos XML en el directorio `res/xml/` de tu proyecto Android que combinen las reglas de Adapty con las de otros SDKs. Android utiliza distintos formatos de reglas de copia de seguridad según la versión del sistema operativo, por lo que crear ambos archivos garantiza la compatibilidad con todas las versiones de Android que admite tu app. :::note Los ejemplos a continuación usan AppsFlyer como SDK de terceros de muestra. Reemplaza o añade reglas para cualquier otro SDK que uses en tu app. ::: **Para Android 12 y superior** (usa el nuevo formato de reglas de extracción de datos): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Para Android 11 e inferior** (usa el formato legado de contenido de copia de seguridad completa): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> #### Las compras fallan al volver desde otra aplicación en Android \{#purchases-fail-after-returning-from-another-app-in-android\} Si la Activity que inicia el flujo de compra usa un `launchMode` distinto al predeterminado, Android puede recrearla o reutilizarla incorrectamente cuando el usuario regresa desde Google Play, una aplicación bancaria o un navegador. Esto puede hacer que el resultado de la compra se pierda o se trate como cancelado. Para garantizar que las compras funcionen correctamente, usa únicamente los modos de inicio `standard` o `singleTop` para la Activity que inicia el flujo de compra, y evita cualquier otro modo. En tu `AndroidManifest.xml`, asegúrate de que la Activity que inicia el flow de compra tenga el modo de lanzamiento `standard` o `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### Errores de compilación de Swift 6 causados por la sobreescritura de SWIFT_VERSION en el Podfile \{#swift-6-build-errors-caused-by-podfile-swift-version-override\} Al compilar tu app de Flutter para iOS, es posible que veas errores de compilación de Swift 6 en los targets de los pods de Adapty. Los síntomas habituales incluyen incompatibilidades con `@Sendable` en `AdaptyUIBuilderLogic`, falta de conformidad con `Sendable` en los tipos de Adapty, o errores de aislamiento de actores. Los pods de Adapty declaran `s.swift_version = '6.0'` y requieren Swift 6 para compilarse. El código de tu propia app puede quedarse en Swift 5 — solo los targets de los pods de Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`, `AdaptyLogger`, `AdaptyPlugin`) necesitan compilarse con Swift 6. La causa más común es un hook `post_install` en `ios/Podfile` que sobreescribe `SWIFT_VERSION` para todos los targets de pods: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **Solución**: Excluye los targets del pod de Adapty del override: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` Luego ejecuta `pod install` desde el directorio `ios/` y vuelve a compilar. Para verificarlo, abre `ios/Pods/Pods.xcodeproj`, selecciona el target del pod `Adapty` → **Build Settings** → **Swift Language Version**. Debería mostrar **Swift 6**. --- # File: flutter-quickstart-paywalls --- --- title: "Habilitar compras con Flow Builder en el SDK de Flutter" description: "Guía de inicio rápido para habilitar compras in-app con Adapty Flow Builder." --- Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – cualquier cosa que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Flows**](adapty-flow-builder) – secuencias de pantallas que presentan productos a los usuarios, creadas en el Flow Builder sin código. El SDK los recupera mediante `getFlow`. Si prefieres crear la UI en tu propio código, usa un paywall — consulta [Implementar paywalls manualmente](flutter-quickstart-manual). - [**Placements**](placements) – dónde y cuándo mostrar flows en tu app (por ejemplo, `main`, `onboarding`, `settings`). Asocias flows a placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar distintos flows a diferentes usuarios. Adapty te ofrece tres formas de habilitar compras en tu app. Selecciona la que mejor se adapte a los requisitos de tu aplicación: | Implementación | Complejidad | Cuándo usarlo | |------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Adapty Flow Builder | ✅ Fácil | [Creas un flow completo y listo para compras en el constructor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona todo el flujo de compra, la validación de recibos y la gestión de suscripciones entre bastidores. | | Paywalls creados manualmente | 🟡 Medio | Implementas la UI de tu paywall en el código de tu app, pero sigues obteniendo el objeto flow desde Adapty para mantener flexibilidad en la oferta de productos. Consulta la [guía](flutter-quickstart-manual). | | Modo observador | 🔴 Difícil | Ya tienes tu propia infraestructura de gestión de compras y quieres seguir usándola. Ten en cuenta que el modo observador tiene sus limitaciones en Adapty. Consulta el [artículo](observer-vs-full-mode). | :::important **Los pasos a continuación muestran cómo implementar un flow creado en el Adapty Flow Builder.** Si prefieres construir la UI del paywall tú mismo, consulta [Implementar paywalls manualmente](flutter-quickstart-manual). ::: Para mostrar un flow creado en el Adapty Flow Builder, en el código de tu app solo necesitas: 1. **Obtener el flow**: Obténlo desde Adapty. 2. **Mostrarlo y Adapty gestionará las compras por ti**: Muestra la vista en tu app. 3. **Gestionar las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abrir enlaces o cerrar el flow cuando los usuarios pulsen botones. ## Antes de comenzar \{#before-you-start\} Antes de comenzar, completa estos pasos: 1. Conecta tu app al [App Store](initial_ios) y/o [Google Play](initial-android) en el Adapty Dashboard. 2. [Crea tus productos](create-product) en Adapty. 3. [Crea un flow y añade productos](create-paywall). 4. [Crea un placement y añade tu flow](create-placement). 5. [Instala y activa el SDK](sdk-installation-flutter) en el código de tu app. Esta guía usa las APIs del SDK de Adapty Flutter v4. :::tip La forma más rápida de completar estos pasos es seguir la [guía de inicio rápido](quickstart) o crear paywalls y placements usando la [CLI para desarrolladores](developer-cli-quickstart). ::: ## 1. Obtener el flow \{#1-get-the-flow\} Tus flows están asociados a placements configurados en el dashboard. Los placements te permiten ejecutar distintos flows para diferentes audiencias o realizar [pruebas A/B](ab-tests). Para obtener un flow creado en el Adapty Flow Builder, necesitas: 1. Obtener el objeto `flow` por el ID del [placement](placements) usando el método `getFlow` y comprobar si fue creado en el builder mediante la propiedad `hasViewConfiguration`. 2. Crear la vista del flow usando el método `createFlowView`. La vista contiene los elementos de UI y el estilo necesarios para mostrar el flow. :::important Para obtener la configuración de la vista, debes activar el botón **Show on device** en el builder. De lo contrario, obtendrás una configuración de vista vacía y el flow no se mostrará. ::: ```dart showLineNumbers try { // the requested flow final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final view = await AdaptyUI().createFlowView( flow: flow, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## 2. Mostrar el flow \{#display-the-flow\} Ahora que tienes la vista del flow, basta con añadir unas pocas líneas para mostrarlo. Para mostrar el flow, usa el método `view.present()` en la `view` creada por el método `createFlowView`. Cada `view` solo puede presentarse una vez: cuando la cierras, la vista se libera de la memoria. Si necesitas mostrar el flow de nuevo, llama a `createFlowView` una vez más para crear una nueva instancia de `view`. ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip Para más detalles sobre cómo mostrar un flow, consulta nuestra [guía](flutter-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#handle-button-actions\} Cuando los usuarios pulsan botones en el flow, el SDK de Flutter gestiona automáticamente las compras, la restauración, el cierre de la vista y la apertura de URLs. Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren gestionar las acciones en tu código. Para controlar o monitorizar los procesos en la pantalla del flow, implementa los métodos de `AdaptyUIFlowsEventsObserver` y establece el observer antes de mostrar cualquier pantalla. Si un usuario ha realizado alguna acción, se invocará `flowViewDidPerformAction` y tu app deberá responder según el ID de la acción. Tres métodos del observador son **obligatorios**: `flowViewDidFinishPurchase`, `flowViewDidFinishRestore` y `flowViewDidReceiveError` — la clase no compilará sin ellos. :::tip Lee nuestras guías sobre cómo manejar [acciones](flutter-handle-paywall-actions) y [eventos](flutter-handling-events) de botones. ::: Implementa el observer como un objeto dedicado y de larga duración, no como un widget. Dado que hay un único slot global de observer compartido en toda la app, vincularlo a un `State` provocaría una fuga de memoria (el SDK mantiene una referencia fuerte a él) y sería reemplazado silenciosamente cuando la siguiente pantalla se registre. Usar `extends` también hereda el comportamiento por defecto del SDK, de modo que, además de los tres métodos obligatorios, solo necesitas sobreescribir los callbacks que te interesen. ```dart showLineNumbers title="Flutter" // A dedicated, long-lived handler for flow events. // It does NOT live inside a Widget/State, so it never leaks and is never // silently replaced when screens are pushed or popped. class FlowEventsHandler extends AdaptyUIFlowsEventsObserver { // A single, app-wide instance — same idiom as Adapty() and AdaptyUI(). static final FlowEventsHandler _instance = FlowEventsHandler._(); factory FlowEventsHandler() => _instance; FlowEventsHandler._(); // This method is called when user performs an action on the flow UI. // Overriding it replaces the default behavior (dismiss on close, open URLs), // so keep those cases if you want to preserve it. @override void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): // close the flow on the Android back button view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } // Required: decide what happens after a purchase finishes @override void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { if (purchaseResult is! AdaptyPurchaseResultUserCancelled) { view.dismiss(); } } // Required: dismiss the flow once a restore succeeds @override void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { view.dismiss(); } // Required: handle rendering and other view errors @override void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { print('Flow error: $error'); view.dismiss(); } } ``` Registra el handler **una sola vez** al inicio de la app, antes de mostrar ningún flow: ```dart showLineNumbers title="Flutter" AdaptyUI().setFlowsEventsObserver(FlowEventsHandler()); ``` ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox de App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. Ahora necesitas [comprobar el nivel de acceso de los usuarios](flutter-check-subscription-status) para asegurarte de que muestras un paywall o concedes acceso a las funciones de pago a los usuarios correctos. ## Ejemplo completo \{#full-example\} Aquí puedes ver cómo integrar todos esos pasos en tu app. ```dart void main() { // Register a single, long-lived observer once, before any flow is shown. // It is intentionally a plain object (NOT a Widget/State): its lifetime is the // whole app, so it never leaks and is never silently replaced when screens are // pushed or popped. AdaptyUI().setFlowsEventsObserver(FlowEventsHandler()); runApp(MaterialApp(home: FlowScreen())); } /// A dedicated handler for AdaptyUI flow events. /// /// It `extends` [AdaptyUIFlowsEventsObserver] (rather than being implemented /// by a `State`), which gives you two things for free: /// * the SDK's sensible defaults for optional callbacks, so besides the three /// required methods you only override what you actually care about; /// * a lifecycle that is independent of the widget tree — there is no strong /// reference back into a `Widget`, so nothing leaks and there is nothing to /// unregister. /// /// Every callback receives the [AdaptyUIFlowView] it relates to, so handling /// flow actions never requires a `BuildContext` or widget state. class FlowEventsHandler extends AdaptyUIFlowsEventsObserver { // A single, app-wide instance — same idiom as Adapty() and AdaptyUI(). static final FlowEventsHandler _instance = FlowEventsHandler._(); factory FlowEventsHandler() => _instance; FlowEventsHandler._(); // Called when the user performs an action on the flow UI. @override void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): // close the flow on the Android back button view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): // Open the URL natively, honoring the dashboard browser setting. AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } // Required: decide what happens after a purchase finishes. @override void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { if (purchaseResult is! AdaptyPurchaseResultUserCancelled) { view.dismiss(); } } // Required: dismiss the flow once a restore succeeds. @override void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { view.dismiss(); } // Required: handle rendering and other view errors. @override void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { print('Flow error: $error'); view.dismiss(); } } class FlowScreen extends StatefulWidget { const FlowScreen({super.key}); @override State<FlowScreen> createState() => _FlowScreenState(); } class _FlowScreenState extends State<FlowScreen> { @override void initState() { super.initState(); _showFlowIfNeeded(); } Future<void> _showFlowIfNeeded() async { try { final flow = await Adapty().getFlow( placementId: 'YOUR_PLACEMENT_ID', ); if (!flow.hasViewConfiguration) return; final view = await AdaptyUI().createFlowView(flow: flow); await view.present(); } catch (_) { // Handle any errors (network, SDK issues, etc.) } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Adapty Flow Example')), body: Center( // Add a button to re-trigger the flow for testing purposes. child: ElevatedButton( onPressed: _showFlowIfNeeded, child: const Text('Show Flow'), ), ), ); } } ``` --- # File: flutter-check-subscription-status --- --- title: "Verificar el estado de la suscripción en el SDK de Flutter" description: "Aprende cómo verificar el estado de la suscripción en tu app de Flutter con Adapty." --- Para decidir si los usuarios pueden acceder al contenido de pago o ver un paywall, necesitas comprobar su [nivel de acceso](access-level) en el perfil. Este artículo te muestra cómo acceder al estado del perfil para decidir qué deben ver los usuarios: si mostrarles un paywall o darles acceso a las funciones de pago. ## Obtener el estado de la suscripción \{#get-subscription-status\} Cuando decides si mostrar un paywall o contenido de pago a un usuario, compruebas su [nivel de acceso](access-level) en su perfil. Tienes dos opciones: - Llama a `getProfile` si necesitas los datos más recientes del perfil de inmediato (por ejemplo, al lanzar la app) o quieres forzar una actualización. - Configura **actualizaciones automáticas del perfil** para mantener una copia local que se actualice automáticamente cada vez que cambie el estado de la suscripción. ### Obtener el perfil \{#get-profile\} La forma más sencilla de obtener el estado de la suscripción es usar el método `getProfile` para acceder al perfil: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Escucha de actualizaciones de suscripción \{#listen-to-subscription-updates\} Para recibir automáticamente actualizaciones del perfil en tu app: 1. Usa `Adapty().didUpdateProfileStream.listen()` para escuchar los cambios del perfil: Adapty llamará a este método automáticamente cada vez que cambie el estado de suscripción del usuario. 2. Guarda los datos del perfil actualizados cuando se llame a este método, para poder usarlos en toda tu app sin necesidad de hacer peticiones de red adicionales. ```dart class SubscriptionManager { AdaptyProfile? _currentProfile; SubscriptionManager() { // Listen for profile updates Adapty().didUpdateProfileStream.listen((profile) { _currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() bool hasAccess() { return _currentProfile?.accessLevels['premium']?.isActive ?? false; } } ``` :::note Adapty llama automáticamente al listener del stream de actualizaciones de perfil cuando tu app arranca, proporcionando datos de suscripción en caché incluso si el dispositivo está sin conexión. ::: ## Conectar el perfil con la lógica del paywall \{#connect-profile-with-paywall-logic\} Cuando necesites tomar decisiones inmediatas sobre mostrar paywalls o conceder acceso a funciones de pago, puedes consultar directamente el perfil del usuario. Este enfoque es útil en escenarios como el lanzamiento de la app, al acceder a secciones premium o antes de mostrar contenido específico. ```dart Future<bool> _checkAccessLevel() async { try { final profile = await Adapty().getProfile(); return profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false; } catch (e) { print('Error checking access level: $e'); return false; // Show paywall if access check fails } } Future<void> _initializePaywall() async { await _loadPaywall(); final hasAccess = await _checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } ``` ## Próximos pasos \{#next-steps\} Ahora que sabes cómo hacer seguimiento del estado de la suscripción, aprende a [trabajar con perfiles de usuario](flutter-quickstart-identify) para asegurarte de que pueden acceder a lo que han pagado. --- # File: flutter-quickstart-identify --- --- title: "Identificar usuarios en el SDK de Flutter" description: "Guía de inicio rápido para configurar Adapty para la gestión de suscripciones in-app en Flutter." --- :::important Esta guía es para ti si tienes tu propio sistema de autenticación. Aquí aprenderás a trabajar con perfiles de usuario en Adapty para que se alinee con tu sistema de autenticación existente. ::: La forma en que gestionas las compras de los usuarios depende del modelo de autenticación de tu app: - Si tu app no usa autenticación de backend y no almacena datos de usuario, consulta la [sección sobre usuarios anónimos](#anonymous-users). - Si tu app tiene (o tendrá) autenticación de backend, consulta la [sección sobre usuarios identificados](#identified-users). **Conceptos clave**: - Los **perfiles** son las entidades necesarias para que funcione el SDK. Adapty los crea automáticamente. - Pueden ser anónimos **(sin customer user ID)** o identificados **(con customer user ID)**. - Tú proporcionas el **customer user ID** para relacionar los perfiles de Adapty con tu sistema de autenticación interno. Esto es lo que diferencia a los usuarios anónimos de los identificados: | | Usuarios anónimos | Usuarios identificados | |------------------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------| | **Gestión de compras** | Restauración de compras a nivel de store | Mantienen el historial de compras en todos los dispositivos mediante su customer user ID | | **Gestión de perfiles** | Nuevo perfil en cada reinstalación | El mismo perfil en todas las sesiones y dispositivos | | **Persistencia de datos** | Los datos de usuarios anónimos están vinculados a la instalación | Los datos de usuarios identificados persisten entre instalaciones | ## Usuarios anónimos \{#anonymous-users\} Si no tienes autenticación de backend, **no necesitas gestionar la autenticación en el código de la app**: 1. Cuando se activa el SDK en el primer lanzamiento de la app, Adapty **crea un nuevo perfil para el usuario**. 2. Cuando el usuario realiza una compra en la app, esta compra se **asocia a su perfil de Adapty y a su cuenta del store**. 3. Cuando el usuario **reinstala** la app o la instala en un **dispositivo nuevo**, Adapty **crea un nuevo perfil anónimo al activarse**. 4. Si el usuario había realizado compras anteriormente en tu app, de forma predeterminada sus compras se sincronizan automáticamente desde el App Store al activarse el SDK. Con usuarios anónimos se crearán nuevos perfiles en cada instalación, pero no es un problema porque en los análisis de Adapty puedes [configurar qué se considerará una nueva instalación](general#4-installs-definition-for-analytics). Para usuarios anónimos, debes contar las instalaciones por **ID de dispositivo**. En este caso, cada instalación de la app en un dispositivo se cuenta como una instalación, incluidas las reinstalaciones. ## Usuarios identificados \{#identified-users\} Tienes dos opciones para identificar usuarios en la app: - [**Durante el login/registro:**](#during-loginsignup) Si los usuarios inician sesión después de que se inicia tu app, llama a `identify()` con un customer user ID cuando se autentiquen. - [**Durante la activación del SDK:**](#during-the-sdk-activation) Si ya tienes un customer user ID almacenado cuando se inicia la app, envíalo al llamar a `activate()`. :::important De forma predeterminada, cuando Adapty recibe una compra de un Customer User ID que está actualmente asociado a otro Customer User ID, el nivel de acceso se comparte, de modo que ambos perfiles tienen acceso de pago. Puedes configurar este ajuste para transferir el acceso de pago de un perfil a otro o desactivar el uso compartido por completo. Consulta el [artículo](general#6-sharing-paid-access-between-user-accounts) para más detalles. ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Durante el login/registro \{#during-loginsignup\} Si identificas a los usuarios después del lanzamiento de la app (por ejemplo, después de que inicien sesión o se registren), usa el método `identify` para establecer su customer user ID. - Si **no has usado este customer user ID antes**, Adapty lo vinculará automáticamente al perfil actual. - Si **ya has usado este customer user ID para identificar al usuario**, Adapty cambiará al perfil asociado a ese customer user ID. :::important Los customer user IDs deben ser únicos para cada usuario. Si defines el valor del parámetro de forma fija en el código, todos los usuarios se considerarán como uno solo. ::: Siempre usa `await` con `identify` antes de llamar a otros métodos del SDK. Las llamadas concurrentes generan `#3006 profileWasChanged` o se aplican al perfil anónimo. Consulta [Orden de llamadas en el SDK de Flutter](flutter-sdk-call-order). ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Durante la activación del SDK \{#during-the-sdk-activation\} Si ya conoces el customer user ID cuando activas el SDK, puedes enviarlo en el método `activate` en lugar de llamar a `identify` por separado. Si conoces el customer user ID pero lo defines solo después de la activación, Adapty creará un nuevo perfil anónimo al activarse y cambiará al existente solo después de que llames a `identify`. Puedes pasar un customer user ID existente (uno que hayas usado antes) o uno nuevo. Si pasas uno nuevo, el perfil creado al activarse se vinculará automáticamente al customer user ID. :::note De forma predeterminada, la creación de perfiles anónimos no afecta a los dashboards de análisis, porque las instalaciones se cuentan según los IDs de dispositivo. Un ID de dispositivo representa una única instalación de la app desde el store en un dispositivo y solo se regenera después de reinstalar la app. No depende de si es una primera o posterior instalación, ni de si se usa un customer user ID existente. Crear un perfil (al activar el SDK o al cerrar sesión), iniciar sesión o actualizar la app sin reinstalarla no genera eventos de instalación adicionales. Si quieres contar las instalaciones por usuarios únicos en lugar de por dispositivos, ve a **App settings** y configura [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```dart showLineNumbers" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID) // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. ); } catch (e) { // handle the error } ``` ### Cerrar sesión de usuarios \{#log-users-out\} Si tienes un botón para cerrar sesión de los usuarios, usa el método `logout`. :::important Cerrar la sesión de un usuario crea un nuevo perfil anónimo para ese usuario. ::: ```dart showLineNumbers try { await Adapty().logout(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` :::info Para volver a iniciar sesión en la app, usa el método `identify`. ::: ### Permitir compras sin inicio de sesión \{#allow-purchases-without-login\} Si tus usuarios pueden realizar compras tanto antes como después de iniciar sesión en tu app, debes asegurarte de que conserven el acceso después de iniciar sesión: 1. Cuando un usuario sin sesión iniciada realiza una compra, Adapty la vincula a su ID de perfil anónimo. 2. Cuando el usuario inicia sesión en su cuenta, Adapty cambia a trabajar con su perfil identificado. - Si es un nuevo customer user ID (por ejemplo, la compra se realizó antes del registro), Adapty asigna el customer user ID al perfil actual, por lo que se mantiene todo el historial de compras. - Si es un customer user ID existente (el customer user ID ya está vinculado a un perfil), debes obtener el nivel de acceso actual después del cambio de perfil. Puedes llamar a [`getProfile`](flutter-check-subscription-status) justo después de la identificación, o [escuchar las actualizaciones del perfil](flutter-check-subscription-status) para que los datos se sincronicen automáticamente. ## Siguientes pasos \{#next-steps\} ¡Enhorabuena! Has implementado la lógica de pagos in-app en tu app. ¡Te deseamos mucho éxito con la monetización de tu app! Para sacar aún más partido a Adapty, puedes explorar estos temas: - [**Pruebas**](troubleshooting-test-purchases): Verifica que todo funciona como se espera - [**Onboardings**](flutter-onboardings): Engancha a los usuarios con onboardings y mejora la retención - [**Integraciones**](configuration): Integra con servicios de atribución de marketing y análisis con una sola línea de código - [**Establecer atributos de perfil personalizados**](flutter-setting-user-attributes): Añade atributos personalizados a los perfiles de usuario y crea segmentos para lanzar pruebas A/B o mostrar diferentes paywalls a distintos usuarios --- # File: adapty-sdk-integration-skill-flutter --- --- title: "Integra Adapty en tu app Flutter con la skill de integración del SDK" description: "Usa la skill adapty-sdk-integration para integrar el SDK de Adapty en tu app Flutter de principio a fin con tu herramienta de codificación con IA." --- <AdaptySdkIntegrationSkill platform="Flutter" /> :::important La funcionalidad está en beta. Si se queda bloqueada o se comporta de forma inesperada, sigue la [guía de integración paso a paso](adapty-cursor-flutter) en su lugar: guía a tu herramienta de IA a través de cada etapa con la documentación correcta. ::: La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. --- # File: adapty-cursor-flutter --- --- title: "Integra Adapty en tu app Flutter con ayuda de IA" description: "Guía paso a paso para integrar Adapty en tu app Flutter usando Cursor, Context7, ChatGPT, Claude u otras herramientas de IA." --- Esta guía te lleva paso a paso por la integración de Adapty en tu app de Flutter usando una herramienta de codificación con IA — le proporcionas la documentación correcta de Adapty en el orden correcto. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Antes de empezar: configuración del dashboard \{#before-you-start-dashboard-setup\} Adapty requiere cierta configuración en el dashboard antes de escribir código con el SDK. Puedes hacerlo con una skill LLM interactiva o manualmente desde el Dashboard. ### Enfoque con skill (recomendado) \{#skill-approach-recommended\} El skill de Adapty CLI permite que tu LLM configure tu app, productos, niveles de acceso, paywalls y placements directamente, sin necesidad de abrir el Dashboard en cada paso. Solo tienes que [conectar tus stores](integrate-payments) en el Dashboard. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` Una vez añadido el skill, ejecuta `/adapty-cli` en tu agente. Te guiará por cada paso, incluyendo cuándo abrir el Dashboard para conectar tus stores. ### Enfoque desde el dashboard Si prefieres configurarlo todo manualmente, esto es lo que necesitas antes de escribir código. Tu LLM no puede consultar los valores del dashboard por ti — tendrás que proporcionarlos. 1. **Conecta tus app stores**: En el Adapty Dashboard, ve a **App settings → General**. Conecta tanto App Store como Google Play si tu app Flutter apunta a ambas plataformas. Esto es necesario para que las compras funcionen. [Conecta los app stores](integrate-payments) 2. **Copia tu clave SDK pública**: En el Adapty Dashboard, ve a **App settings → General** y busca la sección **API keys**. En el código, es la cadena que pasas a la configuración de Adapty. 3. **Crea al menos un producto**: En el Adapty Dashboard, ve a la página **Products**. No necesitas referenciar los productos directamente en el código — Adapty los entrega a través de paywalls. [Añadir productos](quickstart-products) 4. **Crea un paywall y un placement**: En el Adapty Dashboard, crea un paywall en la página **Paywalls** y asígnalo a un placement en la página **Placements**. En el código, el ID del placement es la cadena que pasas a `Adapty().getPaywall()`. [Crear un paywall](quickstart-paywalls) 5. **Configura los niveles de acceso**: En el Adapty Dashboard, configúralos por producto en la página **Products**. En el código, la cadena que se comprueba es `profile.accessLevels['premium']?.isActive`. El nivel de acceso `premium` predeterminado funciona para la mayoría de las apps. Si los usuarios de pago acceden a distintas funciones según el producto (por ejemplo, un plan `basic` frente a un plan `pro`), [crea niveles de acceso adicionales](assigning-access-level-to-a-product) antes de empezar a programar. :::tip Una vez que tengas los cinco, estarás listo para escribir código. Dile a tu LLM: "Mi clave pública del SDK es X, mi placement ID es Y" para que pueda generar el código correcto de inicialización y carga del paywall. ::: ### Configura cuando estés listo \{#set-up-when-ready\} Esto no es necesario para empezar a programar, pero lo necesitarás a medida que tu integración madure: - **Pruebas A/B**: Configúralas en la página **Placements**. No se requiere ningún cambio de código. [Pruebas A/B](ab-tests) - **Paywalls y placements adicionales**: Añade más llamadas `getPaywall` con distintos IDs de placement. - **Integraciones de analytics**: Configúralas en la página **Integrations**. La configuración varía según la integración. Consulta [integraciones de analytics](analytics-integration) e [integraciones de atribución](attribution-integration). ## Proporciona la documentación de Adapty a tu LLM \{#feed-adapty-docs-to-your-llm\} ### Usa Context7 (recomendado) [Context7](https://context7.com) es un servidor MCP que da a tu LLM acceso directo a la documentación actualizada de Adapty. Tu LLM obtiene automáticamente la documentación adecuada según lo que le preguntes, sin necesidad de pegar URLs manualmente. Context7 funciona con **Cursor**, **Claude Code**, **Windsurf** y otras herramientas compatibles con MCP. Para configurarlo, ejecuta: ``` npx ctx7 setup ``` Esto detecta tu editor y configura el servidor de Context7. Para la configuración manual, consulta el [repositorio de Context7 en GitHub](https://github.com/upstash/context7). Una vez configurado, haz referencia a la librería de Adapty en tus prompts: ``` Use the adaptyteam/adapty-docs library to look up how to install the Flutter SDK ``` :::warning Aunque Context7 elimina la necesidad de pegar enlaces a la documentación manualmente, el orden de implementación importa. Sigue el [tutorial de implementación](#implementation-walkthrough) paso a paso para asegurarte de que todo funciona correctamente. ::: ### Usa los docs en texto plano Puedes acceder a cualquier doc de Adapty en texto plano Markdown. Añade `.md` al final de su URL o haz clic en **Copy for LLM** debajo del título del artículo. Por ejemplo: [adapty-cursor-flutter.md](https://adapty.io/docs/es/adapty-cursor-flutter.md). Cada paso del [recorrido de implementación](#implementation-walkthrough) incluye un bloque "Send this to your LLM" con enlaces `.md` para pegar. Para acceder a más documentación a la vez, consulta los [archivos de índice y subconjuntos por plataforma](#plain-text-doc-index-files). ## Guía de implementación paso a paso \{#implementation-walkthrough\} El resto de esta guía recorre la integración de Adapty en el orden de implementación. Cada etapa incluye la documentación que debes enviar a tu LLM, lo que deberías ver al terminar y los problemas más frecuentes. ### Planifica tu integración \{#plan-your-integration\} Antes de escribir código, pídele a tu LLM que analice tu proyecto y cree un plan de implementación. Si tu herramienta de IA tiene un modo de planificación (como el modo plan de Cursor o Claude Code), úsalo para que el LLM pueda leer tanto la estructura de tu proyecto como la documentación de Adapty antes de generar cualquier código. Indícale a tu LLM qué enfoque usas para las compras — esto determina qué guías debe seguir: - [**Adapty Paywall Builder**](adapty-paywall-builder): Creas paywalls en el editor visual de Adapty y el SDK los renderiza automáticamente. - [**Paywalls creados manualmente**](flutter-making-purchases): Construyes tu propia interfaz de paywall en código, pero sigues usando Adapty para obtener productos y gestionar compras. - [**Modo observador**](observer-vs-full-mode): Mantienes tu infraestructura de compras existente y usas Adapty únicamente para analíticas e integraciones. ¿No sabes cuál elegir? Consulta la [tabla comparativa en la guía de inicio rápido](flutter-quickstart-paywalls). ### Instalar y configurar el SDK \{#install-and-configure-the-sdk\} Añade la dependencia del SDK de Adapty con `flutter pub add` y actívalo con tu clave pública de SDK. Es la base — sin esto, nada más funciona. **Guía:** [Instalar y configurar el SDK de Adapty](sdk-installation-flutter) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/sdk-installation-flutter.md ``` :::tip[Checkpoint] - **Esperado:** La app se compila y ejecuta correctamente en iOS y Android. La consola de depuración muestra el log de activación de Adapty. - **Problema frecuente:** "Public API key is missing" → comprueba que has reemplazado el marcador de posición con tu clave real de **App settings**. ::: ### Mostrar paywalls y gestionar compras \{#show-paywalls-and-handle-purchases\} Obtén un paywall por ID de placement, muéstralo y gestiona los eventos de compra. Las guías que necesitas dependen de cómo gestiones las compras. Prueba cada compra en el sandbox a medida que avanzas — no esperes hasta el final. Consulta [Probar compras en sandbox](test-purchases-in-sandbox) para ver las instrucciones de configuración. <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Paywall Builder" default> **Guías:** - [Habilitar compras con paywalls (inicio rápido)](flutter-quickstart-paywalls) - [Obtener paywalls del Paywall Builder y su configuración](flutter-get-pb-paywalls) - [Mostrar paywalls](flutter-present-paywalls) - [Gestionar eventos del paywall](flutter-handling-events) - [Responder a las acciones de los botones](flutter-handle-paywall-actions) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/flutter-quickstart-paywalls.md - https://adapty.io/docs/es/flutter-get-pb-paywalls.md - https://adapty.io/docs/es/flutter-present-paywalls.md - https://adapty.io/docs/es/flutter-handling-events.md - https://adapty.io/docs/es/flutter-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Esperado:** El paywall aparece con los productos configurados. Al pulsar un producto se activa el diálogo de compra en sandbox. - **Problema común:** Paywall vacío o error en `getPaywall` → verifica que el ID del placement coincida exactamente con el dashboard y que el placement tenga una audiencia asignada. ::: </TabItem> <TabItem value="manual" label="Paywalls manuales"> **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](flutter-quickstart-manual) - [Obtener paywalls y productos](fetch-paywalls-and-products-flutter) - [Mostrar un paywall diseñado con Remote Config](present-remote-config-paywalls-flutter) - [Realizar compras](flutter-making-purchases) - [Restaurar compras](flutter-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/es/flutter-quickstart-manual.md - https://adapty.io/docs/es/fetch-paywalls-and-products-flutter.md - https://adapty.io/docs/es/present-remote-config-paywalls-flutter.md - https://adapty.io/docs/es/flutter-making-purchases.md - https://adapty.io/docs/es/flutter-restore-purchase.md :::tip[Checkpoint] - **Expected:** Tu paywall personalizado muestra los productos obtenidos de Adapty. Al pulsar un producto se activa el diálogo de compra en sandbox. - **Gotcha:** Array de productos vacío → verifica que el paywall tenga productos asignados en el dashboard y que el placement tenga una audiencia. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Guías:** - [Descripción general del Observer mode](observer-vs-full-mode) - [Implementar Observer mode](implement-observer-mode-flutter) - [Reportar transacciones en Observer mode](report-transactions-observer-mode-flutter) :::tip[Checkpoint] - **Esperado:** Tras una compra en sandbox usando tu flow de compra existente, la transacción aparece en el **Event Feed** del Adapty Dashboard. - **Atención:** Sin eventos → verifica que estás reportando transacciones a Adapty y que las notificaciones del servidor están configuradas para ambas stores. ::: </TabItem> </Tabs> ### Verificar el estado de la suscripción \{#check-subscription-status\} Tras una compra, consulta el perfil de usuario para ver si hay un nivel de acceso activo y así controlar el acceso al contenido premium. **Guía:** [Verificar el estado de la suscripción](flutter-check-subscription-status) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/flutter-check-subscription-status.md ``` :::tip[Punto de control] - **Resultado esperado:** Después de una compra en sandbox, `profile.accessLevels['premium']?.isActive` devuelve `true`. - **Problema frecuente:** `accessLevels` vacío tras la compra → verifica que el producto tenga un nivel de acceso asignado en el dashboard. ::: ### Identificar usuarios \{#identify-users\} Vincula las cuentas de usuario de tu app con los perfiles de Adapty para que las compras persistan en todos los dispositivos. :::important Omite este paso si tu app no tiene autenticación. ::: **Guía:** [Identificar usuarios](flutter-quickstart-identify) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/flutter-quickstart-identify.md ``` :::tip[Checkpoint] - **Esperado:** Después de llamar a `Adapty().identify()`, la sección **Profiles** del dashboard muestra tu ID de usuario personalizado. - **Gotcha:** Llama a `identify` después de la activación pero antes de obtener los paywalls para evitar una atribución de perfil anónima. ::: ### Preparativos para el lanzamiento \{#prepare-for-release\} Una vez que tu integración funcione en el sandbox, repasa la lista de verificación de lanzamiento para asegurarte de que todo esté listo para producción. **Guía:** [Lista de verificación de lanzamiento](release-checklist) Envía esto a tu LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/es/release-checklist.md ``` :::tip[Checkpoint] - **Esperado:** Todos los elementos del checklist confirmados: conexiones a la store, notificaciones del servidor, flujo de compra, comprobaciones de nivel de acceso y requisitos de privacidad. - **Problema frecuente:** Notificaciones del servidor ausentes → configura App Store Server Notifications en **App settings → iOS SDK** y Google Play Real-Time Developer Notifications en **App settings → Android SDK**. ::: ## Archivos de índice de documentación en texto plano \{#plain-text-doc-index-files\} Si necesitas darle a tu LLM un contexto más amplio que el de páginas individuales, ofrecemos archivos de índice que listan o combinan toda la documentación de Adapty: - [`llms.txt`](https://adapty.io/docs/es/llms.txt): Lista todas las páginas con enlaces `.md`. Un [estándar emergente](https://llmstxt.org/) para hacer sitios web accesibles a los LLMs. Ten en cuenta que algunos agentes de IA (por ejemplo, ChatGPT) requieren descargar `llms.txt` y subirlo al chat como archivo. - [`llms-full.txt`](https://adapty.io/docs/es/llms-full.txt): Toda la documentación de Adapty combinada en un único archivo. Muy extenso — úsalo solo cuando necesites una visión completa. - [`flutter-llms.txt`](https://adapty.io/docs/es/flutter-llms.txt) y [`flutter-llms-full.txt`](https://adapty.io/docs/es/flutter-llms-full.txt) específicos de Flutter: Subconjuntos por plataforma que ahorran tokens en comparación con el sitio completo. --- # File: flutter-get-pb-paywalls --- --- title: "Obtener flows y paywalls - Flutter" description: "Obtén flows y paywalls de Adapty en tu app Flutter." --- <SDKv4> <MethodPromo method="getFlow" /> Después de [diseñar tu flow o paywall con Paywall Builder](adapty-paywall-builder), puedes mostrarlo en tu app. El primer paso es obtener el flow o paywall asociado al placement y su configuración de vista, tal como se describe a continuación. Ten en cuenta que este tema hace referencia a flows y paywalls personalizados con Paywall Builder. Si estás implementando tus paywalls de forma manual, consulta el tema [Obtener paywalls y productos para paywalls con Remote Config en tu app](fetch-paywalls-and-products-flutter). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a mostrar flows y paywalls en tu aplicación móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow/paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow/paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-flutter) en tu aplicación móvil. </details> ## Obtener el flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall con el Flow Builder o el Paywall Builder, no tienes que preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Un flow o paywall de este tipo contiene tanto lo que se debe mostrar como la forma en que debe mostrarse. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y, a continuación, presentarlo en tu app. Obtén el flow o paywall y crea su [vista](flutter-get-pb-paywalls#fetch-the-view-configuration) lo antes posible, idealmente mucho antes de mostrarlo. El método `createFlowView` carga la configuración de la vista y comienza a descargar y almacenar en caché sus imágenes en segundo plano. Cuanto antes lo llames, más tiempo tendrán esas descargas para completarse. Para cuando presentes el flow o paywall, su configuración e imágenes ya pueden estar en caché y listas para mostrarse. Para obtener un flow o paywall, usa el método `getFlow`: ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow/paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché de actualización periódica descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Un `Duration` que limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede agotar el tiempo ligeramente más tarde de lo especificado en `loadTimeout`, ya que la operación puede estar compuesta de diferentes peticiones internamente.</p> | ## Parámetros de respuesta \{#response-parameters\} | Parámetro | Descripción | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | Un objeto `AdaptyFlow` con los identificadores del flow (`instanceIdentity`, `variationId`), el nombre, el placement, sus variaciones de paywall (`paywalls`) y cualquier Remote Config (`remoteConfigs`). | ## Obtén la configuración de la vista \{#fetch-the-view-configuration\} :::important Asegúrate de activar el interruptor **Show on device** en el builder. Si esta opción no está activada, la configuración de la vista no estará disponible para recuperar. ::: Si el placement fue diseñado en el **Flow Builder** o en el **Paywall Builder**, Adapty renderiza la interfaz por ti — la propiedad `hasViewConfiguration` del flow obtenido es `true`. Crea la vista con `createFlowView` y luego [presenta el flow o paywall](flutter-present-paywalls). Si el placement es un paywall personalizado sin interfaz de Builder (`hasViewConfiguration` es `false`), [gestiónalo como un paywall de Remote Config](present-remote-config-paywalls-flutter). :::warning El resultado del método `createFlowView` solo se puede presentar una vez. Si necesitas presentarlo de nuevo, llama al método `createFlowView` otra vez. ::: ```dart showLineNumbers try { final view = await AdaptyUI().createFlowView(flow: flow); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | :------------------- | :-------- | :----------------------------------------------------------- | | **flow** | obligatorio | Un objeto `AdaptyFlow` para obtener una vista del flow/paywall deseado. | | **customTags** | opcional | Define un mapa de etiquetas personalizadas y sus valores resueltos. Las etiquetas personalizadas actúan como marcadores de posición en el contenido, reemplazados dinámicamente por cadenas específicas para personalizar el contenido dentro del flow/paywall. Consulta el tema [Custom tags in paywall builder](custom-tags-in-paywall-builder) para más detalles. | | **preloadProducts** | opcional | Actívalo para optimizar el tiempo de visualización de los productos en pantalla. Cuando es `true`, AdaptyUI obtendrá automáticamente los productos necesarios. Por defecto: `false`. | | **loadTimeout** | opcional | Una `Duration` que limita el tiempo de carga de la configuración de la vista. Si se alcanza el tiempo límite, se usarán los datos en caché o el fallback local. | :::note Si utilizas varios idiomas, aprende cómo añadir una [localización de flow](add-paywall-locale-in-adapty-paywall-builder) y cómo usar los códigos de idioma correctamente [aquí](flutter-localizations-and-locale-codes). ::: Una vez que tengas la vista, [presenta el flow/paywall](flutter-present-paywalls). ## Obtén un flow o paywall para la audiencia predeterminada y cárgalo más rápido \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Por lo general, los flows y paywalls se cargan casi de inmediato, así que no necesitas preocuparte por acelerar este proceso. Sin embargo, si tienes muchas audiencias y placements, y tus usuarios tienen una conexión a internet lenta, la carga de un flow o paywall puede tardar más de lo deseado. En esos casos, puede que quieras mostrar un flow o paywall predeterminado para garantizar una experiencia fluida, en lugar de no mostrar nada. Para abordar esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow o paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el flow o paywall mediante el método `getFlow`, tal como se detalla en la sección [Obtener flow/paywall](#fetch-flowpaywall) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes paywalls para distintas versiones de la app (la actual y las futuras), puedes encontrarte con dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (heredada) o aceptar que los usuarios con esa versión puedan tener problemas con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluyendo por países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estos inconvenientes para beneficiarte de una obtención más rápida del flow o paywall, usa el método `getFlowForDefaultAudience` de la siguiente manera. De lo contrario, quédate con `getFlow` descrito [arriba](#fetch-flowpaywall). ::: ```dart showLineNumbers try { final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow/paywall } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro utilizarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | ## Personalizar recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu flow/paywall, implementa los recursos personalizados. Las imágenes y vídeos principales tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizados, apuntas a estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. Aquí tienes un ejemplo de cómo puedes proporcionar assets personalizados mediante un diccionario sencillo: ```dart final customAssets = { // Show a local image using a custom ID 'custom_image': AdaptyCustomAsset.localImageAsset( assetId: 'assets/images/image_name.png', ), // Show a local video with a preview image 'hero_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/custom_video.mp4', ), }; try { final view = await AdaptyUI().createFlowView( flow: flow, customAssets: customAssets, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note Si no se encuentra un recurso, el flow/paywall volverá a su apariencia predeterminada. ::: ## Configurar temporizadores definidos por el desarrollador \{#set-up-developer-defined-timers\} Para usar temporizadores personalizados en tu app, pasa un mapa `customTimers` al método `createFlowView`. Cada clave del mapa es un ID de temporizador, y su valor es un objeto `DateTime` que define cuándo termina el temporizador. Aquí tienes un ejemplo: ```dart showLineNumbers try { final view = await AdaptyUI().createFlowView( flow: flow, customTimers: { 'CUSTOM_TIMER_6H': DateTime.now().add(const Duration(seconds: 3600 * 6)), 'CUSTOM_TIMER_NY': DateTime(2027, 1, 1), // New Year 2027 }, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` En este ejemplo, `CUSTOM_TIMER_NY` y `CUSTOM_TIMER_6H` son los **Timer ID**s de los temporizadores definidos por el desarrollador que configuraste en el Adapty Dashboard. El mapa `customTimers` garantiza que tu app actualice dinámicamente cada temporizador con el valor correcto. Por ejemplo: - `CUSTOM_TIMER_NY`: El tiempo restante hasta el final del temporizador, como el día de Año Nuevo. - `CUSTOM_TIMER_6H`: El tiempo restante en un período de 6 horas que comenzó cuando el usuario abrió el flow. </SDKv4> <SDKv3> Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el nuevo Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu app móvil. El primer paso es obtener el paywall asociado al placement junto con su configuración de vista, tal como se describe a continuación. :::warning El nuevo Paywall Builder requiere la versión 3.3.0 o superior del SDK de Flutter. ::: Por favor, ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Si estás implementando tus paywalls manualmente, consulta el tema [Obtener paywalls y productos para paywalls con Remote Config en tu aplicación móvil](fetch-paywalls-and-products-flutter). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a mostrar paywalls en tu aplicación móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-flutter) en tu app móvil. </details> ## Obtener el paywall diseñado con Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Si has [diseñado un paywall con el Paywall Builder](adapty-paywall-builder), no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Este tipo de paywall incluye tanto lo que se muestra como la forma en que se muestra. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y, después, presentarlo en tu app. Para garantizar un rendimiento óptimo, es fundamental obtener el paywall y su [configuración de vista](flutter-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) lo antes posible, dejando tiempo suficiente para que las imágenes se descarguen antes de mostrárselas al usuario. Para obtener un paywall, usa el método `getPaywall`: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>predeterminado: `en`</p> | <p>El identificador de la [localización del paywall](add-paywall-locale-in-adapty-paywall-builder). Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](flutter-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | predeterminado: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché de actualización regular descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.</p> | | **loadTimeout** | predeterminado: 5 seg | <p>Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos raros este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede estar compuesta por diferentes solicitudes internamente.</p><p>Para Android: puedes crear `TimeInterval` con funciones de extensión (como `5.seconds`, donde `.seconds` proviene de `import com.adapty.utils.seconds`), o `TimeInterval.seconds(5)`. Para no establecer ningún límite, usa `TimeInterval.INFINITE`.</p> | Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Un objeto [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) con una lista de IDs de productos, el identificador del paywall, Remote Config y otras propiedades. | ## Obtener la configuración de vista de un paywall diseñado con Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Asegúrate de activar el interruptor **Show on device** en el Paywall Builder. Si esta opción no está activada, la configuración de vista no estará disponible para recuperar. ::: Después de obtener el paywall, comprueba si incluye un `ViewConfiguration`, lo que indica que fue creado con Paywall Builder. Esto te guiará sobre cómo mostrar el paywall. Si el `ViewConfiguration` está presente, trátalo como un paywall de Paywall Builder; si no, [trátalo como un paywall de Remote Config](present-remote-config-paywalls-flutter). ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` Una vez que tengas la vista, [presenta el paywall](flutter-present-paywalls). ## Obtén un paywall para la audiencia predeterminada y cárgalo más rápido \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Normalmente, los paywalls se cargan casi al instante, por lo que no necesitas preocuparte por optimizar este proceso. Sin embargo, cuando tienes muchas audiencias y paywalls, y tus usuarios tienen una conexión a internet lenta, la carga de un paywall puede tardar más de lo deseable. En esos casos, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia de usuario fluida, en lugar de no mostrar ningún paywall. Para solucionar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement indicado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, tal como se describe en la sección [Obtener información del paywall](flutter-get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar paywalls distintos para diferentes versiones de la app (la actual y las futuras), puede que te encuentres con dificultades. Tendrás que diseñar paywalls compatibles con la versión actual (legacy) o asumir que los usuarios con esa versión podrían tener problemas al no renderizarse los paywalls. - **Pérdida de segmentación**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluida la basada en países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas para beneficiarte de una obtención más rápida del paywall, usa el método `getPaywallForDefaultAudience` de la siguiente manera. De lo contrario, utiliza `getPaywall` descrito [anteriormente](#fetch-paywall-designed-with-paywall-builder). ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` :::note El método `getPaywallForDefaultAudience` está disponible a partir de la versión 3.2.0 del SDK de Flutter. ::: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de configuración regional](localizations-and-locale-codes) para más información sobre los códigos de configuración regional y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no reciban los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala o mediante una limpieza manual.</p> | ## Personaliza los recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu paywall, implementa los recursos personalizados. Las imágenes y vídeos hero tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizado, apuntas a estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. :::important Para usar esta función, actualiza el SDK de Flutter de Adapty a la versión 3.8.0 o superior. ::: Aquí tienes un ejemplo de cómo puedes proporcionar assets personalizados mediante un diccionario simple: ```dart final customAssets = { // Show a local image using a custom ID 'custom_image': AdaptyCustomAsset.localImageAsset( assetId: 'assets/images/image_name.png', ), // Show a local video with a preview image 'hero_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/custom_video.mp4', ), }; try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customAssets: customAssets, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note Si no se encuentra un recurso, el paywall volverá a su apariencia predeterminada. ::: ## Configura los temporizadores definidos por el desarrollador \{#set-up-developer-defined-timers\} Para usar temporizadores personalizados en tu aplicación móvil, pasa un mapa `customTimers` al método `createPaywallView`. Cada clave del mapa es un ID de temporizador, y su valor es un objeto `DateTime` que define cuándo termina el temporizador. Aquí tienes un ejemplo: ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customTimers: { 'CUSTOM_TIMER_6H': DateTime.now().add(const Duration(seconds: 3600 * 6)), 'CUSTOM_TIMER_NY': DateTime(2025, 1, 1), // New Year 2025 }, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` En este ejemplo, `CUSTOM_TIMER_NY` y `CUSTOM_TIMER_6H` son los **Timer ID**s de los temporizadores definidos por el desarrollador que configuraste en el Adapty Dashboard. El mapa `customTimers` asegura que tu app actualice dinámicamente cada temporizador con el valor correcto. Por ejemplo: - `CUSTOM_TIMER_NY`: El tiempo restante hasta el final del temporizador, como el Año Nuevo. - `CUSTOM_TIMER_6H`: El tiempo restante en un período de 6 horas que comenzó cuando el usuario abrió el paywall. </SDKv3> --- # File: flutter-present-paywalls --- --- title: "Mostrar flows y paywalls - Flutter" description: "Presenta flows y paywalls en apps Flutter usando las funciones de monetización de Adapty." --- <SDKv4> Si has diseñado un flow o paywall con el Flow Builder o el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Ese flow o paywall ya incluye tanto qué mostrar como cómo mostrarlo. :::warning Esta guía es para flows y paywalls creados con Paywall Builder. Para presentar **paywalls con Remote Config**, consulta [Renderizar un paywall diseñado con Remote Config](present-remote-config-paywalls-flutter). ::: El SDK de Flutter de Adapty ofrece dos formas de presentar flows y paywalls: - **Pantalla independiente** - **Widget embebido** ## Presentar como pantalla independiente \{#present-as-standalone-screen\} Para mostrar un flow o paywall como pantalla independiente, usa el método `view.present()` en el `view` creado por el método [`createFlowView`](flutter-get-pb-paywalls#fetch-the-view-configuration). Cada `view` solo puede presentarse una vez: después de cerrarlo, la vista se libera de la memoria. Si necesitas mostrar el flow o paywall de nuevo, llama a `createFlowView` otra vez para crear una nueva instancia de `view`. ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Cerrar el flow o el paywall \{#dismiss-the-flow-or-paywall\} Cuando necesites cerrar el flow o el paywall mediante código, usa el método `dismiss()`: ```dart showLineNumbers title="Flutter" try { await view.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note Al cerrar una vista, esta se libera de la memoria y ya no puede volver a mostrarse. En su lugar, crea una nueva con `createFlowView`. ::: ### Mostrar diálogo \{#show-dialog\} Usa este método en lugar de los diálogos de alerta nativos cuando se muestra un flow o una vista de paywall en Android. En Android, las alertas normales aparecen detrás de la vista, lo que las hace invisibles para los usuarios. Este método garantiza que el diálogo se muestre correctamente por encima del flow o el paywall en todas las plataformas. ```dart showLineNumbers title="Flutter" try { final action = await view.showDialog( title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', ); if (action == AdaptyUIDialogActionType.secondary) { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (e) { // handle error } ``` ### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el flow o el paywall en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `AdaptyUIIOSPresentationStyle.fullScreen` (predeterminado) o `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Incorporar en la jerarquía de widgets \{#embed-in-widget-hierarchy\} Para incorporar un flow o paywall dentro de tu árbol de widgets existente, usa el widget `AdaptyUIFlowPlatformView` directamente en tu jerarquía de widgets de Flutter. ```dart showLineNumbers title="Flutter" AdaptyUIFlowPlatformView( flow: flow, // The flow object you fetched onDidAppear: (view) { }, onDidDisappear: (view) { }, onDidPerformAction: (view, action) { }, onDidSelectProduct: (view, productId) { }, onDidStartPurchase: (view, product) { }, onDidFinishPurchase: (view, product, purchaseResult) { }, onDidFailPurchase: (view, product, error) { }, onDidStartRestore: (view) { }, onDidFinishRestore: (view, profile) { }, onDidFailRestore: (view, error) { }, onDidReceiveError: (view, error) { }, onDidFailLoadingProducts: (view, error) { }, onDidFinishWebPaymentNavigation: (view, product, error) { }, ) ``` :::note Para que la vista de plataforma de Android funcione, asegúrate de que tu `MainActivity` extiende `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: </SDKv4> <SDKv3> Si has personalizado un paywall con el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese tipo de paywall incluye tanto lo que debe mostrarse como la forma en que debe mostrarse. :::warning Esta guía es solo para **paywalls del nuevo Paywall Builder**, que requieren SDK v3.2.0 o posterior. El proceso para presentar paywalls varía según la versión del Paywall Builder con la que se diseñaron y para los paywalls de Remote Config. - Para presentar **paywalls de Remote Config**, consulta [Renderizar un paywall diseñado con Remote Config](present-remote-config-paywalls-flutter). ::: El SDK de Adapty para Flutter ofrece dos formas de presentar paywalls: - **Pantalla independiente** - **Widget integrado** ## Presentar como pantalla independiente \{#present-as-standalone-screen\} Para mostrar un paywall como pantalla independiente, usa el método `view.present()` en la `view` creada por el método [`createPaywallView`](flutter-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Cada `view` solo puede usarse una vez. Si necesitas mostrar el paywall de nuevo, llama a `createPaywallView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar la misma `view` sin recrearla puede provocar un error `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Cierra el paywall \{#dismiss-the-paywall\} Cuando necesites cerrar el paywall mediante código, usa el método `dismiss()`: ```dart showLineNumbers title="Flutter" try { await view.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Mostrar un diálogo \{#show-dialog\} Usa este método en lugar de los diálogos de alerta nativos cuando se muestra una vista de paywall en Android. En Android, las alertas normales aparecen detrás de la vista del paywall, lo que las hace invisibles para el usuario. Este método garantiza que el diálogo se muestre correctamente por encima del paywall en todas las plataformas. ```dart showLineNumbers title="Flutter" try { final action = await view.showDialog( title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', ); if (action == AdaptyUIDialogActionType.secondary) { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (e) { // handle error } ``` ### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el paywall en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `AdaptyUIIOSPresentationStyle.fullScreen` (por defecto) o `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Insertar en la jerarquía de widgets \{#embed-in-widget-hierarchy\} Para insertar un paywall dentro de tu árbol de widgets existente, utiliza el widget `AdaptyUIPaywallPlatformView` directamente en tu jerarquía de widgets de Flutter. ```dart showLineNumbers title="Flutter" AdaptyUIPaywallPlatformView( paywall: paywall, // The paywall object you fetched onDidAppear: (view) { }, onDidDisappear: (view) { }, onDidPerformAction: (view, action) { }, onDidSelectProduct: (view, productId) { }, onDidStartPurchase: (view, product) { }, onDidFinishPurchase: (view, product, purchaseResult) { }, onDidFailPurchase: (view, product, error) { }, onDidStartRestore: (view) { }, onDidFinishRestore: (view, profile) { }, onDidFailRestore: (view, error) { }, onDidFailRendering: (view, error) { }, onDidFailLoadingProducts: (view, error) { }, onDidFinishWebPaymentNavigation: (view, product, error) { }, ) ``` :::note Para que la vista de plataforma Android funcione, asegúrate de que tu `MainActivity` extienda `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: </SDKv3> --- # File: flutter-handle-paywall-actions --- --- title: "Responder a acciones de botones en Flutter SDK" description: "Gestiona las acciones de botones de paywall en Flutter usando Adapty para una mejor monetización de tu app." --- <SDKv4> Si estás creando flows o paywalls con el builder de Adapty, es fundamental configurar los botones correctamente: 1. Añade un [botón en el builder](paywall-buttons) y asígnale una acción existente o crea un ID de acción personalizado. 2. Escribe código en tu app para gestionar cada acción que hayas asignado. Esta guía muestra cómo gestionar acciones personalizadas y existentes en tu código. :::warning **El cierre de la vista y la apertura de URLs se gestionan automáticamente** por la implementación predeterminada de `flowViewDidPerformAction`, y el propio SDK procesa las compras y restauraciones. El resto de acciones de botón, como iniciar sesión o abrir otro flow, requieren implementar las respuestas correspondientes en el código de la app. Ten en cuenta que la respuesta a las compras y restauraciones *completadas* se gestiona en los callbacks de observador requeridos — consulta [Gestionar eventos de flow y paywall](flutter-handling-events). ::: ## Cerrar flows y paywalls \{#close-flows-and-paywalls\} Para añadir un botón que cierre tu flow o paywall, en el builder añade un botón y asígnale la acción **Close**. No se necesita código: la implementación predeterminada de `flowViewDidPerformAction` cierra la vista cuando recibe `CloseAction`. :::info El botón **Back** del sistema Android ya no cierra la vista por defecto. Se entrega a `flowViewDidPerformAction` como `AndroidSystemBackAction` — gestiónalo tú mismo si quieres que el botón de retroceso cierre el flow o el paywall. ::: Sobreescribe `flowViewDidPerformAction` si necesitas un comportamiento personalizado — por ejemplo, para cerrar también la vista al pulsar el botón de retroceso del sistema Android, como en v3: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } ``` :::warning Sobrescribir `flowViewDidPerformAction` reemplaza completamente la implementación predeterminada — conserva los casos `CloseAction` y `OpenUrlAction` si quieres mantener el comportamiento predeterminado de cierre y apertura de URLs. ::: ## Abrir URLs desde flows y paywalls \{#open-urls-from-flows-and-paywalls\} :::tip Si quieres añadir un grupo de enlaces (por ejemplo, términos de uso y restauración de compras), añade un elemento **Link** en el builder y trátalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace (por ejemplo, **Términos de uso** o **Política de privacidad**), añade un botón en el builder, asígnale la acción **Open URL** e introduce la URL que quieres abrir. No se necesita código: la implementación predeterminada de `flowViewDidPerformAction` abre la URL de forma nativa mediante `AdaptyUI().openUrl`, respetando la configuración de navegador interno o externo del dashboard. El comportamiento predeterminado es suficiente para la mayoría de los casos. Si aun así quieres abrir las URLs tú mismo, sobreescribe el handler: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): view.dismiss(); break; case OpenUrlAction(url: final url): // Open the URL in whatever way fits your app break; default: break; } } ``` ## Iniciar sesión en la app \{#log-into-the-app\} Para añadir un botón que permita a los usuarios iniciar sesión en tu app: 1. En el builder, añade un botón y asígnale la acción **Login**. 2. En el código de tu app, implementa un manejador para la acción `login` que identifique a tu usuario. ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'login'): // Navigate to your login screen in whatever way fits your app break; default: break; } } ``` ## Gestionar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el builder, añade un botón, asígnale la acción **Custom** y dale un ID. 2. En el código de tu app, implementa un handler para el ID de acción que hayas creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro flow o paywall: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'openNewPaywall'): // Display another flow or paywall break; default: break; } } ``` </SDKv4> <SDKv3> Si estás creando paywalls con el Paywall Builder de Adapty, es fundamental configurar los botones correctamente: 1. Añade un [botón en el Paywall Builder](paywall-buttons) y asígnale una acción existente o crea un ID de acción personalizado. 2. Escribe código en tu app para gestionar cada acción que hayas asignado. Esta guía explica cómo gestionar acciones personalizadas y predefinidas en tu código. :::warning **Solo las compras y restauraciones se gestionan automáticamente.** El resto de acciones de botón, como cerrar paywalls o abrir enlaces, requieren implementar las respuestas correspondientes en el código de la app. ::: ## Cerrar paywalls \{#close-paywalls\} Para añadir un botón que cierre tu paywall: 1. En el Paywall Builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un handler para las acciones `CloseAction` y `AndroidSystemBackAction`. :::info En el SDK de Flutter, las acciones `CloseAction` y `AndroidSystemBackAction` cierran el paywall por defecto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un paywall podría desencadenar la apertura de otro. ::: ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; default: break; } } ``` ## Abrir URLs desde paywalls \{#open-urls-from-paywalls\} :::tip Si quieres añadir un grupo de enlaces (por ejemplo, términos de uso y restauración de compras), añade un elemento **Link** en el Paywall Builder y trátalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu paywall (por ejemplo, **Terms of use** o **Privacy policy**): 1. En el Paywall Builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieras abrir. 2. En el código de tu app, implementa un handler para la acción `openUrl` que abra la URL recibida en un navegador. ```dart // You have to install url_launcher plugin in order to handle urls: // https://pub.dev/packages/url_launcher void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case OpenUrlAction(url: final url): final Uri uri = Uri.parse(url); launchUrl(uri, mode: LaunchMode.inAppBrowserView); break; default: break; } } ``` ## Iniciar sesión en la app \{#log-into-the-app\} Para añadir un botón que permita a los usuarios iniciar sesión en tu app: 1. En el paywall builder, añade un botón y asígnale la acción **Login**. 2. En el código de tu app, implementa un manejador para la acción `login` que identifique a tu usuario. ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'login'): // Navigate to your login screen in whatever way fits your app break; default: break; } } ``` ## Gestionar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el Paywall Builder, añade un botón, asígnale la acción **Custom** y asígnale un ID. 2. En el código de tu app, implementa un manejador para el ID de acción que has creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro paywall: ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'openNewPaywall'): // Display another paywall break; default: break; } } ``` </SDKv3> --- # File: flutter-handling-events --- --- title: "Flutter - Gestionar eventos de flow y paywall" description: "Descubre cómo gestionar eventos relacionados con suscripciones en Flutter usando Adapty para rastrear interacciones de usuario de forma efectiva." --- <SDKv4> :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado. Cerrar la vista y abrir enlaces se gestiona mediante la implementación predeterminada de `flowViewDidPerformAction` — consulta nuestra [guía sobre el manejo de acciones de botones](flutter-handle-paywall-actions) para sobreescribirla o para manejar acciones de botones personalizados. ::: Los flows y paywalls configurados con el builder no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan algunos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el flow o paywall. A continuación encontrarás cómo responder a estos eventos. Para controlar o supervisar los procesos que ocurren en la pantalla del flow o paywall dentro de tu app, implementa los métodos de `AdaptyUIFlowsEventsObserver` y establece el observer antes de presentar cualquier pantalla: ```dart showLineNumbers title="Flutter" AdaptyUI().setFlowsEventsObserver(this); ``` Tres métodos de observer son **obligatorios** — tu clase no compilará sin ellos: `flowViewDidFinishPurchase`, `flowViewDidFinishRestore` y `flowViewDidReceiveError`. El resto de métodos son opcionales. Para desconectar un observer previamente configurado, pasa `null` a `setFlowsEventsObserver`. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: Los ejemplos de eventos que aparecen a continuación muestran las propiedades disponibles en cada objeto, con valores ilustrativos en los comentarios. ### Eventos generados por el usuario \{#user-generated-events\} #### View appeared \{#view-appeared\} Este método se invoca cuando la vista del flow o el paywall aparece en pantalla. :::note En iOS, también se invoca cuando el usuario pulsa el [botón de web paywall](web-paywall#step-2a-add-a-web-purchase-button) dentro de un paywall y el web paywall se abre en un navegador in-app. ::: ```dart showLineNumbers title="Flutter" void flowViewDidAppear(AdaptyUIFlowView view) { } ``` #### View disappeared \{#view-disappeared\} Este método se invoca cuando la vista del flow o el paywall se cierra en pantalla. :::note En iOS, también se invoca cuando un [web paywall](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un paywall en un navegador in-app desaparece de la pantalla. ::: ```dart showLineNumbers title="Flutter" void flowViewDidDisappear(AdaptyUIFlowView view) { } ``` #### Selección de producto \{#product-selection\} Si se selecciona un producto para su compra (por un usuario o por el sistema), se invocará este método: ```dart showLineNumbers title="Flutter" void flowViewDidSelectProduct(AdaptyUIFlowView view, String productId) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```dart void flowViewDidSelectProduct(AdaptyUIFlowView view, String productId) { // productId is a String: productId; // 'premium_monthly' } ``` </Details> #### Compra iniciada \{#started-purchase\} Si un usuario inicia el proceso de compra, se invocará este método: ```dart showLineNumbers title="Flutter" void flowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) { } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```dart void flowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price.amount; // 9.99 (double) product.price.currencyCode; // 'USD' product.price.localizedString; // '$9.99' } ``` </Details> #### Compra finalizada \{#finished-purchase\} Este método es **obligatorio**. Se invoca cuando una compra tiene éxito, el usuario cancela su compra, o la compra parece estar pendiente: ```dart showLineNumbers title="Flutter" void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // successful purchase break; case AdaptyPurchaseResultPending(): // purchase is pending break; case AdaptyPurchaseResultUserCancelled(): // user cancelled the purchase break; default: break; } } ``` <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```dart void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) break; case AdaptyPurchaseResultPending(): // no additional data break; case AdaptyPurchaseResultUserCancelled(): // no additional data break; } } ``` </Details> :::info A diferencia de v3, este método no tiene un comportamiento predeterminado: la vista ya no se cierra automáticamente tras una compra exitosa. Decide tú mismo qué ocurre a continuación: continuar el flow o llamar a `view.dismiss()`. Consulta [Responder a las acciones de los botones](flutter-handle-paywall-actions) para más detalles sobre cómo cerrar una pantalla. ::: #### Navegación de pago web finalizada \{#finished-web-payment-navigation\} Este método se invoca tras un intento de abrir un [paywall web](web-paywall) para un producto específico. Esto incluye tanto los intentos de navegación exitosos como los fallidos: ```dart showLineNumbers title="Flutter" void flowViewDidFinishWebPaymentNavigation(AdaptyUIFlowView view, AdaptyPaywallProduct? product, AdaptyError? error) { } ``` **Parámetros:** | Parámetro | Descripción | |:------------|:-------------------------------------------------------------------------------------------------------------------------| | **product** | Un `AdaptyPaywallProduct` para el que se abrió el paywall web. Puede ser `null`. | | **error** | Un objeto `AdaptyError` si la navegación del paywall web falló; `null` si la navegación fue correcta. | #### Compra fallida \{#failed-purchase\} Este método se invoca cuando una compra falla (por ejemplo, debido a problemas de pago o errores de red). **No** se activa para cancelaciones iniciadas por el usuario ni para transacciones pendientes; esas las gestiona `flowViewDidFinishPurchase`: ```dart showLineNumbers title="Flutter" void flowViewDidFailPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyError error) { } ``` #### Restauración iniciada \{#started-restore\} Si un usuario inicia el proceso de restauración, se invocará este método: ```dart showLineNumbers title="Flutter" void flowViewDidStartRestore(AdaptyUIFlowView view) { } ``` #### Restauración exitosa \{#successful-restore\} Este método es **obligatorio**. Si la restauración de una compra se completa con éxito, se invocará: ```dart showLineNumbers title="Flutter" void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { } ``` <Details> <summary>Ejemplo de evento (haz clic para ampliar)</summary> ```dart void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) profile.subscriptions['premium_monthly']?.isActive; // true profile.subscriptions['premium_monthly']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) } ``` </Details> Recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de la suscripción](flutter-listen-subscription-changes) para aprender a verificarlo y el tema [Responder a las acciones de los botones](flutter-handle-paywall-actions) para aprender a cerrar una pantalla. #### Restauración fallida \{#failed-restore\} Si la restauración de una compra falla, se invocará este método: ```dart showLineNumbers title="Flutter" void flowViewDidFailRestore(AdaptyUIFlowView view, AdaptyError error) { } ``` ### Obtención y renderizado de datos \{#data-fetching-and-rendering\} #### Errores de carga de productos \{#product-loading-errors\} Si no pasas el array de productos durante la inicialización, AdaptyUI recuperará los objetos necesarios del servidor por sí solo. Si esta operación falla, AdaptyUI notificará el error invocando este método: ```dart showLineNumbers title="Flutter" void flowViewDidFailLoadingProducts(AdaptyUIFlowView view, AdaptyError error) { } ``` #### Errores de vista \{#view-errors\} Este método es **obligatorio**. Reemplaza el método `paywallViewDidFailRendering` de la v3: los errores que ocurren durante el renderizado de la interfaz, así como otros errores de la vista, se notifican llamando a este método. Una vez que lo implementes, el cierre de la vista queda a tu criterio — recomendamos cerrarla cuando se produzcan este tipo de errores, que es también lo que hace el comportamiento predeterminado del SDK cuando no hay ningún observador configurado: ```dart showLineNumbers title="Flutter" void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { // log the error and dismiss the broken view view.dismiss(); } ``` En condiciones normales, no deberían producirse errores de renderizado, así que si te encuentras con alguno, háznoslo saber. ### Eventos de análisis \{#analytics-events\} El método opcional `flowViewDidReceiveAnalyticEvent` está reservado para eventos de análisis personalizados de un flow. Por ahora, los flows no emiten estos eventos a tu código, por lo que no necesitas implementarlo. ### Gestionar compras en modo observador \{#handle-purchases-in-observer-mode\} Si activaste el SDK en [modo observador](implement-observer-mode-flutter) y muestras un flow o paywall renderizado por Adapty, el SDK no realiza las compras por ti. Cuando el usuario pulsa el botón de compra o restauración, el SDK llama a tu `AdaptyUIObserverModeResolver` en su lugar. Consulta [Presentar flows en modo observador](flutter-present-flows-in-observer-mode) para ver la configuración completa. ### Manejar solicitudes del sistema \{#handle-system-requests\} `AdaptyUISystemRequestsHandler` (registrado mediante `AdaptyUI().setSystemRequestsHandler(...)`) está reservado para solicitudes del sistema generadas por un flow: solicitudes de permisos del SO (como notificaciones push o acceso a la cámara) y solicitudes de valoración en el App Store. Los flows aún no activan estas solicitudes, por lo que no es necesario registrar un handler. Si registras uno, ten en cuenta que `handlePermission` es el método obligatorio de la clase: solicita el permiso con tu propio código y luego devuelve `AdaptyUIPermissionResult.granted()` o `AdaptyUIPermissionResult.denied()`; `handleAppReviewRequest` es opcional. </SDKv4> <SDKv3> :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de paywalls. También debes implementar el manejo de botones (cerrar el paywall, abrir enlaces, etc.). Consulta nuestra [guía sobre el manejo de acciones de botones](flutter-handle-paywall-actions) para más detalles. ::: Los paywalls configurados con el [Paywall Builder](adapty-paywall-builder) no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan algunos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selección de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el paywall. A continuación te explicamos cómo responder a estos eventos. :::warning Esta guía es exclusivamente para **paywalls del nuevo Paywall Builder** que requieren Adapty SDK v3.0 o posterior. ::: Para controlar o supervisar los procesos que ocurren en la pantalla del paywall dentro de tu aplicación móvil, implementa los métodos de `AdaptyUIPaywallsEventsObserver` y establece el observer antes de mostrar cualquier pantalla: ```dart showLineNumbers title="Flutter" AdaptyUI().setPaywallsEventsObserver(this); ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: Los ejemplos de eventos a continuación muestran las propiedades disponibles en cada objeto, con valores ilustrativos en los comentarios. ### Eventos generados por el usuario \{#user-generated-events\} #### Paywall appeared \{#paywall-appeared\} Este método se invoca cuando la vista del paywall aparece en pantalla. :::note En iOS, también se invoca cuando el usuario pulsa el [botón de web paywall](web-paywall#step-2a-add-a-web-purchase-button) dentro de un paywall y el web paywall se abre en un navegador integrado. ::: ```dart showLineNumbers title="Flutter" void paywallViewDidAppear(AdaptyUIPaywallView view) { } ``` #### Paywall disappeared \{#paywall-disappeared\} Este método se invoca cuando la vista del paywall se cierra y desaparece de la pantalla. :::note En iOS, también se invoca cuando un [web paywall](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un paywall en un navegador in-app desaparece de la pantalla. ::: ```dart showLineNumbers title="Flutter" void paywallViewDidDisappear(AdaptyUIPaywallView view) { } ``` #### Selección de producto \{#product-selection\} Si se selecciona un producto para su compra (por el usuario o por el sistema), se invocará este método: ```dart showLineNumbers title="Flutter" void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```dart void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) { // productId is a String: productId; // 'premium_monthly' } ``` </Details> #### Compra iniciada \{#started-purchase\} Si un usuario inicia el proceso de compra, se invocará este método: ```dart showLineNumbers title="Flutter" void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```dart void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price.amount; // 9.99 (double) product.price.currencyCode; // 'USD' product.price.localizedString; // '$9.99' } ``` </Details> #### Compra finalizada \{#finished-purchase\} Este método se invoca cuando una compra tiene éxito, el usuario cancela su compra, o la compra parece estar pendiente: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // successful purchase break; case AdaptyPurchaseResultPending(): // purchase is pending break; case AdaptyPurchaseResultUserCancelled(): // user cancelled the purchase break; default: break; } } ``` <Details> <summary>Ejemplos de eventos (haz clic para expandir)</summary> ```dart void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) break; case AdaptyPurchaseResultPending(): // no additional data break; case AdaptyPurchaseResultUserCancelled(): // no additional data break; } } ``` </Details> Recomendamos cerrar la pantalla en ese caso. Consulta [Responder a las acciones de botones](flutter-handle-paywall-actions) para obtener más información sobre cómo cerrar una pantalla de paywall. #### Navegación de pago web finalizada \{#finished-web-payment-navigation\} Este método se invoca tras un intento de abrir un [web paywall](web-paywall) para un producto específico. Esto incluye tanto los intentos de navegación exitosos como los fallidos: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct? product, AdaptyError? error) { } ``` **Parámetros:** | Parámetro | Descripción | |:------------|:---------------------------------------------------------------------------------------------------------------------| | **product** | Un `AdaptyPaywallProduct` para el que se abrió el paywall web. Puede ser `null`. | | **error** | Un objeto `AdaptyError` si la navegación del paywall web falló; `null` si la navegación fue correcta. | <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```dart void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct? product, AdaptyError? error) { // product — AdaptyPaywallProduct?: product?.vendorProductId; // 'premium_monthly' if (error == null) { // navigation succeeded } else { // error — AdaptyError: error.code; // AdaptyErrorCode.networkFailed (2005) error.message; // 'Network request failed' error.detail; // platform-specific underlying error, or null } } ``` </Details> #### Compra fallida \{#failed-purchase\} Este método se invoca cuando una compra falla (por ejemplo, por problemas de pago o errores de red). **No** se activa para cancelaciones iniciadas por el usuario ni para transacciones pendientes; esas se gestionan mediante `paywallViewDidFinishPurchase`: ```dart showLineNumbers title="Flutter" void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```dart void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' // error — AdaptyError: error.code; // AdaptyErrorCode.productPurchaseFailed (1006) error.message; // 'Product purchase failed.' error.detail; // platform-specific underlying error, or null } ``` </Details> #### Restauración iniciada \{#started-restore\} Si un usuario inicia el proceso de restauración, se invocará este método: ```dart showLineNumbers title="Flutter" void paywallViewDidStartRestore(AdaptyUIPaywallView view) { } ``` #### Restauración exitosa \{#successful-restore\} Si la restauración de una compra se realiza con éxito, se invocará este método: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```dart void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) profile.subscriptions['premium_monthly']?.isActive; // true profile.subscriptions['premium_monthly']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) } ``` </Details> Recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de la suscripción](flutter-listen-subscription-changes) para aprender cómo comprobarlo y el tema [Responder a las acciones de los botones](flutter-handle-paywall-actions) para aprender cómo cerrar una pantalla de paywall. #### Fallo al restaurar \{#failed-restore\} Si la restauración de una compra falla, se invocará este método: ```dart showLineNumbers title="Flutter" void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```dart void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.receiveRestoredTransactionsFailed (1011) error.message; // 'Error occurred in the process of restoring purchases.' error.detail; // platform-specific underlying error, or null } ``` </Details> ### Obtención y renderizado de datos \{#data-fetching-and-rendering\} #### Errores al cargar productos \{#product-loading-errors\} Si no pasas el array de productos durante la inicialización, AdaptyUI recuperará los objetos necesarios del servidor por sí mismo. Si esta operación falla, AdaptyUI reportará el error invocando este método: ```dart showLineNumbers title="Flutter" void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```dart void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.productRequestFailed (1002) error.message; // 'Unable to fetch available In-App Purchase products at the moment.' error.detail; // platform-specific underlying error, or null } ``` </Details> #### Errores de renderizado \{#rendering-errors\} Si se produce un error durante el renderizado de la interfaz, se notificará llamando a este método. De manera predeterminada (desde v3.15.2), el paywall se cierra automáticamente cuando se produce un error de renderizado, pero puedes cambiar este comportamiento si lo necesitas. ```dart showLineNumbers title="Flutter" void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { // Default behavior: view.dismiss() // Override with custom logic if needed, for example: // - Log the error // - Show an error message to the user } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```dart void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.jsException (4105) error.message; // 'An exception was thrown from JS during AdaptyUI flow execution.' error.detail; // platform-specific underlying error, or null // Default behavior: view.dismiss() } ``` </Details> En condiciones normales, estos errores no deberían ocurrir, así que si te encuentras con alguno, por favor, haznos saber. </SDKv3> --- # File: flutter-use-fallback-paywalls --- --- title: "Flutter - Usar paywalls de respaldo" description: "Gestiona los casos en que los usuarios están sin conexión o los servidores de Adapty no están disponibles" --- :::warning Los paywalls de respaldo son compatibles con Flutter SDK v2.11 y versiones posteriores. ::: Para mantener una experiencia de usuario fluida, es importante configurar [respaldos](/fallback-paywalls) para tus flows, [paywalls](paywalls) y [onboardings](onboardings). Esta precaución amplía las capacidades de la aplicación en caso de pérdida parcial o total de la conexión a internet. * **Si la aplicación no puede acceder a los servidores de Adapty:** Podrá mostrar un flow o paywall de respaldo, y acceder a la configuración local del onboarding. * **Si la aplicación no puede acceder a internet:** Podrá mostrar un flow o paywall de respaldo. Los onboardings incluyen contenido remoto y requieren conexión a internet para funcionar. :::important Antes de seguir los pasos de esta guía, [descarga](/local-fallback-paywalls) los archivos de configuración de respaldo desde Adapty. ::: ## Configuración \{#configuration\} 1. Añade los archivos de configuración de respaldo al directorio `assets` de la aplicación en la raíz del proyecto. 2. Llama al método `.setFallback` **antes** de obtener el paywall o onboarding objetivo. ```dart showLineNumbers title="Flutter" final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; try { await Adapty().setFallback(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros: | Parámetro | Descripción | | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **assetId** | Ruta al archivo de configuración de respaldo. | :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: --- # File: flutter-localizations-and-locale-codes --- --- title: "Usar localizaciones y códigos de idioma en el SDK de Flutter" description: "Gestiona las localizaciones de la app y los códigos de idioma para llegar a una audiencia global." --- <SDKv4> ## Por qué esto es importante \{#why-this-is-important\} Los códigos de idioma entran en juego cuando Adapty selecciona la localización para un flow y cuando lees un Remote Config para un paywall personalizado. Los códigos de idioma son complicados y pueden variar de una plataforma a otra, por lo que Adapty se basa en un estándar interno único para todas las plataformas que soporta. Entender ese estándar te ayuda a predecir qué localización recibirá un usuario. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código consiste en subtags en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués (Brasil)), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de idioma \{#locale-code-matching\} Cuando Adapty busca la localización que coincide con el idioma de un usuario, ocurre lo siguiente: 1. La cadena de idioma se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan con guiones (`-`) 2. Adapty busca la localización con el código de idioma que coincida exactamente 3. Si no se encuentra ninguna coincidencia, Adapty toma la subcadena antes del primer guión (`pt` para `pt-br`) y busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, Adapty devuelve la localización predeterminada `en` De este modo, `'pt_BR'`, `pt-BR` y `pt-br` se resuelven a la misma localización. ## Implementación de localizaciones \{#implementing-localizations\} En SDK v4, no es necesario pasar un código de idioma al obtener un flow. - **Paywalls de Flow Builder y Paywall Builder**: Adapty resuelve la localización automáticamente a partir del dispositivo y las localizaciones que hayas configurado en el builder. Renderiza el flow con `createFlowView` — no se necesita ningún código de idioma. - **Paywalls personalizados (Remote Config)**: `getFlow` devuelve todas las localizaciones configuradas en `flow.remoteConfigs`. Cada entrada tiene un código `locale` y el contenido de la configuración (cadena `data`, o el `dictionary` ya procesado). Selecciona la entrada que coincida con el usuario, aplicando tu propio mecanismo de respaldo: ```dart showLineNumbers final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final config = flow.remoteConfigs.firstWhereOrNull((c) => c.locale == 'en') ?? flow.remoteConfig; // the first remote config, if present // read your values from config?.dictionary ``` Las reglas de coincidencia de código de idioma descritas anteriormente explican cómo Adapty normaliza los códigos `locale` almacenados en cada Remote Config. </SDKv4> <SDKv3> ## Por qué esto es importante \{#why-this-is-important\} Hay algunos escenarios en los que los códigos de idioma entran en juego — por ejemplo, cuando intentas obtener el paywall correcto para la localización actual de tu app. Como los códigos de idioma son complicados y pueden variar de una plataforma a otra, nos basamos en un estándar interno para todas las plataformas que soportamos. Sin embargo, precisamente por esa complejidad, es muy importante que entiendas exactamente qué estás enviando a nuestro servidor para obtener la localización correcta, y qué ocurre a continuación — así siempre recibirás lo que esperas. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty usa una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código está formado por subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués (Brasil)), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de configuración regional \{#locale-code-matching\} Cuando Adapty recibe una llamada desde el SDK con el código de configuración regional y busca la localización correspondiente de un paywall, ocurre lo siguiente: 1. La cadena de configuración regional entrante se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. Se busca la localización cuyo código de configuración regional coincida exactamente 3. Si no se encuentra ninguna coincidencia, se toma la subcadena anterior al primer guión (`pt` para `pt-br`) y se busca la localización que coincida con ella 4. Si tampoco se encuentra ninguna coincidencia, se devuelve la localización predeterminada `en` De este modo, un dispositivo iOS que envió `'pt_BR'`, un dispositivo Android que envió `pt-BR` y otro dispositivo que envió `pt-br` obtendrán el mismo resultado. ## Implementación de localizaciones: forma recomendada \{#implementing-localizations-recommended-way\} Si estás pensando en las localizaciones, lo más probable es que ya trabajes con archivos de cadenas localizadas en tu proyecto. En ese caso, te recomendamos añadir un par clave-valor con el código de idioma de Adapty correspondiente en cada uno de tus archivos para las localizaciones. Después, extrae el valor de esa clave al llamar a nuestro SDK, así: ```dart showLineNumbers // 1. Modify your app_en.arb, app_es.arb, app_pt_br.arb files /* app_en.arb */ "adapty_paywalls_locale": "en", /* app_es.arb */ "adapty_paywalls_locale": "es", /* app_pt_br.arb */ "adapty_paywalls_locale": "pt-br", // 2. Extract and use the locale code final locale = AppLocalizations.of(context)!.adapty_paywalls_locale; // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` De esta forma tienes control total sobre qué localización se recuperará para cada usuario de tu app. ## Implementando las localizaciones: la otra forma \{#implementing-localizations-the-other-way\} Puedes obtener resultados similares (aunque no idénticos) sin definir explícitamente los códigos de idioma para cada localización. Esto implicaría extraer un código de idioma de otros objetos que tu plataforma proporciona, así: ```dart showLineNumbers final locale = Localizations.localeOf(context).languageCode; // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Ten en cuenta que no recomendamos este enfoque por las siguientes razones: 1. En iOS, los idiomas preferidos y la configuración regional actual no son idénticos. Si quieres que la localización se seleccione correctamente, tendrás que apoyarte en la lógica de Apple, que funciona de forma nativa si usas el enfoque recomendado con archivos de cadenas localizadas, o bien recrearla tú mismo. 2. Es difícil predecir exactamente qué recibirá el servidor de Adapty. Por ejemplo, en iOS es posible obtener una configuración regional como `ar_OM@numbers='latn'` en el dispositivo y enviarla a nuestro servidor. Para esa llamada no obtendrás la localización `ar-om` que buscabas, sino `ar`, lo cual probablemente no es lo esperado. Aun así, si decides usar este enfoque, asegúrate de haber cubierto todos los casos de uso relevantes. </SDKv3> --- # File: flutter-web-paywall --- --- title: "Implementar paywalls web en Flutter SDK" description: "Configura un paywall web para cobrar sin las comisiones y auditorías de la App Store." --- :::important Antes de empezar, asegúrate de haber [configurado tu paywall web en el dashboard](web-paywall) e instalado la versión 3.6.1 o posterior del SDK de Adapty. ::: Si trabajas con un paywall desarrollado por ti mismo, debes gestionar los paywalls web mediante el método del SDK. El método `.openWebPaywall`: 1. Genera una URL única que permite a Adapty vincular un paywall específico mostrado a un usuario concreto con la página web a la que es redirigido. 2. Detecta cuándo el usuario vuelve a la app y, a continuación, llama a `.getProfile` a intervalos cortos para determinar si los derechos de acceso del perfil han sido actualizados. De este modo, si el pago se realizó correctamente y los derechos de acceso se han actualizado, la suscripción se activa en la app casi de inmediato. ```dart showLineNumbers title="Flutter" try { await Adapty().openWebPaywall(product: <YOUR_PRODUCT>); // The web paywall will be opened } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle other errors } ``` :::note Existen dos versiones del método `openWebPaywall`: 1. `openWebPaywall(product)` que genera URLs por paywall y añade los datos del producto a las URLs también. 2. `openWebPaywall(paywall)` que genera URLs por paywall sin añadir los datos del producto a las URLs. Úsalo cuando tus productos en el paywall de Adapty difieran de los del web paywall. En el SDK v4, el parámetro `paywall` recibe un `AdaptyFlowPaywall` — una variante de paywall del flow obtenido. Comprueba que `flow.paywalls` no esté vacío antes de acceder a él por índice, por ejemplo `flow.paywalls[0]`. ::: #### Gestionar errores \{#handle-errors\} | Error | Descripción | Acción recomendada | |-----------------------------------------|---------------------------------------------------------------------|-------------------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | El paywall no tiene una URL de compra web configurada | Comprueba si el paywall está correctamente configurado en el Adapty Dashboard | | AdaptyError.productWithoutPurchaseUrl | El producto no tiene una URL de compra web | Verifica la configuración del producto en el Adapty Dashboard | | AdaptyError.failedOpeningWebPaywallUrl | No se pudo abrir la URL en el navegador | Comprueba la configuración del dispositivo o proporciona un método de compra alternativo | | AdaptyError.failedDecodingWebPaywallUrl | No se pudieron codificar correctamente los parámetros en la URL | Verifica que los parámetros de la URL sean válidos y estén correctamente formateados | ## Abrir paywalls web en un navegador integrado \{#open-web-paywalls-in-an-in-app-browser\} :::important Abrir paywalls web en un navegador integrado está disponible a partir del SDK de Adapty v3.15. ::: Por defecto, los paywalls web se abren en el navegador externo. Para ofrecer una experiencia de usuario más fluida, puedes abrirlos en un navegador integrado. Esto muestra la página de compra web dentro de tu aplicación, permitiendo que los usuarios completen las transacciones sin cambiar de app. Para activarlo, establece el parámetro `in` en `.inAppBrowser`: ```dart showLineNumbers try { await Adapty().openWebPaywall( product: <YOUR_PRODUCT>, openIn: AdaptyWebPresentation.inAppBrowser, ); // The web paywall will be opened in the in-app browser } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle other errors } ``` --- # File: flutter-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del Paywall Builder en Flutter SDK" description: "Solucionar problemas del Paywall Builder en Flutter SDK" --- Esta guía te ayuda a resolver los problemas más comunes al usar paywalls diseñados en el Adapty Paywall Builder con el Flutter SDK. ## Error al obtener la configuración de un paywall \{#getting-a-paywall-configuration-fails\} **Problema**: El método `createPaywallView` no puede recuperar la configuración del paywall. **Motivo**: El paywall no está habilitado para mostrarse en el dispositivo en el Paywall Builder. **Solución**: Activa el toggle **Show on device** en el Paywall Builder. <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## El número de visualizaciones del paywall es demasiado alto \{#the-paywall-view-number-is-too-big\} **Problema**: El recuento de visualizaciones del paywall muestra el doble del número esperado. **Motivo**: Es posible que estés llamando a `logShowFlow` (Flutter SDK v4+) / `logShowPaywall` en tu código, lo que duplica el recuento de visualizaciones si estás usando el Paywall Builder o el Flow Builder. En los flows y paywalls creados con estas herramientas, el seguimiento de análisis es automático, por lo que no necesitas usar este método. **Solución**: Asegúrate de no llamar a `logShowFlow` (Flutter SDK v4+) / `logShowPaywall` en tu código si estás usando el Paywall Builder o el Flow Builder. ## Otros problemas \{#other-issues\} **Problema**: Experimentas otros problemas relacionados con el Paywall Builder que no se tratan más arriba. **Solución**: Migra el SDK a la versión más reciente siguiendo las [guías de migración](flutter-sdk-migration-guides) si es necesario. Muchos problemas se resuelven en versiones más recientes del SDK. --- # File: flutter-present-flows-in-observer-mode --- --- title: "Presentar flows en modo Observer en el SDK de Flutter" description: "Presenta flows y paywalls del Paywall Builder en modo Observer en tu app de Flutter mientras gestionas las compras con tu propio código." --- Si has personalizado un flow o paywall con el builder, no necesitas preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Ese flow o paywall ya contiene tanto qué mostrar como cómo mostrarlo. :::warning Esta sección hace referencia únicamente al [modo Observer](observer-vs-full-mode). Si no trabajas en modo Observer, consulta el tema [Mostrar flows y paywalls](flutter-present-paywalls). ::: :::info Esta funcionalidad requiere el SDK de Adapty para Flutter 4.0 o posterior; antes solo estaba disponible en los SDKs nativos de iOS y Android. Consulta la [guía de migración](migration-to-flutter-sdk-v4) para actualizar. ::: <details> <summary>Antes de empezar a mostrar flows (haz clic para expandir)</summary> 1. Configura la integración inicial de Adapty [con App Store](initial_ios) y [con Google Play](initial-android). 2. Instala y configura el SDK de Adapty. Asegúrate de establecer el parámetro `observerMode` en `true`. Consulta la [guía de instalación del SDK de Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk). 3. [Crea productos](create-product) en el Adapty Dashboard. 4. [Configura flows o paywalls en los builders](create-paywall) y asígnales productos. 5. [Crea placements y asígnales tus flows o paywalls](create-placement). 6. [Obtén los flows y su configuración](flutter-get-pb-paywalls) en el código de tu aplicación móvil. </details> En el modo Observer, el SDK no realiza las compras por ti. Cuando un usuario pulsa el botón de compra o restauración en un flow o paywall renderizado por Adapty, el SDK llama a tu `AdaptyUIObserverModeResolver` en su lugar — realiza la compra o restauración con tu propio código ahí. 1. Implementa el `AdaptyUIObserverModeResolver`: ```dart showLineNumbers title="Flutter" class MyObserverModeResolver extends AdaptyUIObserverModeResolver { @override void observerModeDidInitiatePurchase( AdaptyUIFlowView view, AdaptyPaywallProduct product, void Function() onStartPurchase, void Function() onFinishPurchase, ) { onStartPurchase(); // the view shows its loading indicator // make the purchase with your own code, then: onFinishPurchase(); // the view hides the loading indicator } @override void observerModeDidInitiateRestore( AdaptyUIFlowView view, void Function() onStartRestore, void Function() onFinishRestore, ) { onStartRestore(); // restore purchases with your own code, then: onFinishRestore(); } } ``` El método `observerModeDidInitiatePurchase` te informa de que el usuario ha iniciado una compra, y `observerModeDidInitiateRestore` — de que el usuario ha iniciado una restauración. Activa tu flow de compra o restauración personalizado en respuesta. Además, recuerda invocar los siguientes callbacks para notificar a AdaptyUI sobre el proceso de compra o restauración. Esto es necesario para que el flow funcione correctamente, por ejemplo, para mostrar el loader, entre otras cosas: | Callback | Descripción | | :----------------- | :--------------------------------------------------------------------------------------------------- | | onStartPurchase() | El callback debe invocarse para notificar a AdaptyUI que la compra ha comenzado. | | onFinishPurchase() | El callback debe invocarse para notificar a AdaptyUI que la compra ha finalizado. | | onStartRestore() | El callback debe invocarse para notificar a AdaptyUI que la restauración ha comenzado. | | onFinishRestore() | El callback debe invocarse para notificar a AdaptyUI que la restauración ha finalizado. | 2. Registra el resolver antes de mostrar cualquier pantalla: ```dart showLineNumbers title="Flutter" AdaptyUI().setObserverModeResolver(MyObserverModeResolver()); ``` 3. Crea y presenta la vista del flow como de costumbre: [obtén el flow y crea su vista](flutter-get-pb-paywalls), luego [preséntala](flutter-present-paywalls). No se necesitan parámetros adicionales — una vez registrado el resolver, todo flow o paywall renderizado por Adapty enrutará las compras y restauraciones a través de él. :::warning No olvides [informar la transacción y asociarla con el paywall](report-transactions-observer-mode-flutter). De lo contrario, Adapty no reconocerá la transacción y no podrá determinar el paywall de origen de la compra. ::: --- # File: flutter-quickstart-manual --- --- title: "Habilitar compras en tu paywall personalizado con Flutter SDK" description: "Integra el SDK de Adapty en tus paywalls personalizados de Flutter para habilitar compras in-app." --- Esta guía describe cómo integrar Adapty en tus paywalls personalizados. Mantén el control total sobre la implementación del paywall, mientras el SDK de Adapty obtiene los productos, gestiona las nuevas compras y restaura las anteriores. Esta guía usa las APIs del SDK de Adapty Flutter v4; si usas la v3, consulta la [guía de migración](migration-to-flutter-sdk-v4) para los nombres de métodos correspondientes. :::important **Esta guía es para desarrolladores que implementan paywalls personalizados.** Si quieres la forma más sencilla de habilitar compras, usa el [Adapty Paywall Builder](flutter-quickstart-paywalls). Con Paywall Builder, creas paywalls en un editor visual sin código, Adapty gestiona toda la lógica de compra automáticamente y puedes probar diferentes diseños sin volver a publicar tu app. ::: ## Antes de comenzar \{#before-you-start\} ### Configurar productos \{#set-up-products\} Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls**](paywalls) – configuraciones que definen qué productos ofrecer. En Adapty, los paywalls son la única forma de obtener productos, pero este diseño te permite modificar productos, precios y ofertas sin tocar el código de tu app. En SDK v4, las variaciones de paywall para un placement las lleva un objeto **flow** — obtienes un flow y consultas sus productos. - [**Placements**](placements) – dónde y cuándo mostrar los paywalls en tu app (como `main`, `onboarding`, `settings`). Configuras los paywalls para los placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar distintos paywalls a diferentes usuarios. Asegúrate de entender estos conceptos aunque trabajes con tu propio paywall. Básicamente, son la forma de gestionar los productos que vendes en tu app. Para implementar tu paywall personalizado, tendrás que crear un **paywall** y añadirlo a un **placement**. Esta configuración te permite recuperar tus productos. Para saber qué hacer en el dashboard, sigue la guía de inicio rápido [aquí](quickstart). ### Gestionar usuarios \{#manage-users\} Puedes trabajar con o sin autenticación en tu backend. No obstante, el SDK de Adapty gestiona de manera diferente a los usuarios anónimos e identificados. Lee la [guía de inicio rápido de identificación](flutter-quickstart-identify) para entender las particularidades y asegurarte de que estás gestionando los usuarios correctamente. ## Paso 1. Obtener productos \{#step-1-get-products\} Para obtener los productos de tu paywall personalizado, necesitas: 1. Obtener el objeto `flow` pasando el ID del [placement](placements) al método `getFlow`. 2. Obtener el array de productos de ese flow usando el método `getPaywallProducts`. ```dart showLineNumbers Future<void> loadPaywall() async { try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final products = await Adapty().getPaywallProducts(flow: flow); // Use products to build your custom paywall UI } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Paso 2. Aceptar compras \{#step-2-accept-purchases\} Cuando un usuario pulsa sobre un producto en tu paywall personalizado, llama al método `makePurchase` con el producto seleccionado. Esto gestionará el flow de compra y devolverá el perfil actualizado. ```dart showLineNumbers Future<void> purchaseProduct(AdaptyPaywallProduct product) async { try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // Purchase successful, profile updated break; case AdaptyPurchaseResultUserCancelled(): // User canceled the purchase break; case AdaptyPurchaseResultPending(): // Purchase is pending (e.g., user will pay offline with cash) break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Paso 3. Restaurar compras \{#step-3-restore-purchases\} Los stores exigen que todas las aplicaciones con suscripciones ofrezcan una forma de restaurar las compras. Llama al método `restorePurchases` cuando el usuario pulse el botón de restauración. Esto sincronizará su historial de compras con Adapty y devolverá el perfil actualizado. ```dart showLineNumbers Future<void> restorePurchases() async { try { final profile = await Adapty().restorePurchases(); // Restore successful, profile updated } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Paso 4. Comprueba el estado de la suscripción \{#step-4-check-the-subscription-status\} Tras una compra o restauración, comprueba el [nivel de acceso](access-level) del usuario para decidir si mostrar el paywall o desbloquear las funciones de pago. Los métodos `makePurchase` y `restorePurchases` ya devuelven el perfil actualizado; cuando necesites el estado actual en cualquier otro punto de la app, usa el método `getProfile`: ```dart showLineNumbers Future<bool> hasPremiumAccess() async { try { final profile = await Adapty().getProfile(); return profile.accessLevels['premium']?.isActive ?? false; } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } return false; } ``` Para más formas de comprobar y monitorear el estado de la suscripción, incluida la escucha de actualizaciones en tiempo real, consulta [Comprobar el estado de la suscripción](flutter-check-subscription-status). ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox de App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. Para ver cómo funciona esto en una implementación lista para producción, consulta el [PurchasesObserver](https://github.com/adaptyteam/AdaptySDK-Flutter/blob/master/example/lib/purchase_observer.dart) en nuestra app de ejemplo, que muestra el manejo de compras con gestión de errores adecuada, observadores de UI e integración completa del SDK. --- # File: fetch-paywalls-and-products-flutter --- --- title: "Obtener paywalls y productos para paywalls con Remote Config en Flutter SDK" description: "Obtén paywalls y productos en Adapty Flutter SDK para mejorar la monetización de usuarios." --- <SDKv4> Antes de mostrar Remote Config y paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a Remote Config y paywalls personalizados. Para obtener orientación sobre cómo obtener flows y paywalls personalizados con Paywall Builder, consulta [Obtener flows y paywalls](flutter-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener paywalls y productos en tu app (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en tu paywall](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-flutter) en tu aplicación móvil. </details> ## Obtener información del flow \{#fetch-flow-information\} En Adapty, un [producto](product) es una combinación de productos de App Store y Google Play. Estos productos multiplataforma se integran en paywalls, lo que te permite mostrarlos en placements concretos de tu app móvil. Para mostrar los productos, necesitas obtener un `AdaptyFlow` desde uno de tus [placements](placements) con el método `getFlow`. :::important **No escribas los IDs de producto en el código.** El único ID que debes incluir en el código es el ID del placement. Los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si hoy un paywall devuelve dos productos y mañana tres, muéstralos todos sin necesidad de modificar el código. ::: ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls en dos capas: la caché actualizada regularmente descrita anteriormente y los [paywalls de respaldo](flutter-use-fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el respaldo local.</p><p></p><p>Ten en cuenta que en casos excepcionales este método puede agotar el tiempo de espera ligeramente después del valor especificado en `loadTimeout`, ya que la operación puede estar compuesta de diferentes solicitudes internamente.</p> | :::note En v4, `getFlow` no acepta el parámetro `locale`. Para paywalls personalizados, todas las localizaciones disponibles se devuelven en los Remote Configs del flow (`flow.remoteConfigs`): elige la que corresponda al idioma del dispositivo o a la configuración de la app. Consulta [Localizaciones y códigos de idioma](flutter-localizations-and-locale-codes). ::: Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Un objeto `AdaptyFlow` con los identificadores del flow (`instanceIdentity`, `variationId`), nombre, placement, sus variaciones de paywall (`paywalls`) y cualquier Remote Config (`remoteConfigs`). | ## Obtener productos \{#fetch-products\} Una vez que tengas el flow, puedes consultar el array de productos que le corresponde: ```dart showLineNumbers try { final products = await Adapty().getPaywallProducts(flow: flow); // the requested products array } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Lista de objetos [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y otras propiedades. | Al implementar tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html). A continuación se muestran las propiedades más utilizadas, pero consulta el documento enlazado para obtener información completa sobre todas las propiedades disponibles. | Propiedad | Descripción | |-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país de la store seleccionado por el usuario, no en el idioma del dispositivo. | | **Price** | Para mostrar el precio localizado, usa `product.price.localizedString`. Esta localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número usando `product.price.amount`. El valor se proporcionará en la moneda local. Para obtener el símbolo de moneda correspondiente, usa `product.price.currencySymbol`. | | **Subscription Period** | Para mostrar el período (por ejemplo, semana, mes, año, etc.), usa `product.subscription?.localizedPeriod`. Esta localización se basa en el idioma del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscription?.period`. Desde ahí puedes acceder al enum `unit` para conocer la duración (es decir, día, semana, mes, año o desconocido). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral, verás `AdaptyPeriodUnit.month` en la propiedad `unit` y `3` en la propiedad `numberOfUnits`. | | **Introductory Offer** | Para mostrar una insignia u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscription?.offer?.phases`. Es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase se encuentran las siguientes propiedades útiles:<br/>• `paymentMode`: un enum con los valores `AdaptyPaymentMode.freeTrial`, `AdaptyPaymentMode.payAsYouGo`, `AdaptyPaymentMode.payUpFront` y `AdaptyPaymentMode.unknown`. Las pruebas gratuitas son del tipo `AdaptyPaymentMode.freeTrial`.<br/>• `price`: el precio con descuento como número. Para las pruebas gratuitas, el valor será `0`.<br/>• `localizedNumberOfPeriods`: una cadena localizada según el idioma del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días muestra `3 days` en este campo.<br/>• `subscriptionPeriod`: alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma manera para las ofertas que lo descrito en la sección anterior.<br/>• `localizedSubscriptionPeriod`: el período de suscripción del descuento formateado según el idioma del usuario. | ## Acelera la obtención de flows con el flow de audiencia predeterminada \{#speed-up-flow-fetching-with-default-audience-flow\} Normalmente, los flows se obtienen casi de forma instantánea, así que no tienes que preocuparte por optimizar este proceso. Sin embargo, cuando tienes muchas audiencias y placements, y tus usuarios tienen una conexión a internet lenta, la obtención de un flow puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un flow predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar nada. Para abordar esto, puedes utilizar el método `getFlowForDefaultAudience`, que obtiene el flow del placement indicado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el flow mediante el método `getFlow`, tal como se detalla en la sección [Obtener información del flow](fetch-paywalls-and-products-flutter#fetch-flow-information) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes paywalls para distintas versiones de la app (la actual y las futuras), puedes encontrarte con dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (legacy) o asumir que los usuarios de esa versión podrían tener problemas con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluyendo segmentación por países, atribución de marketing o atributos personalizados propios). Si estás dispuesto a aceptar estas desventajas para beneficiarte de una obtención más rápida del flow, usa el método `getFlowForDefaultAudience` de la siguiente manera. De lo contrario, usa `getFlow` descrito [anteriormente](fetch-paywalls-and-products-flutter#fetch-flow-information). ::: ```dart showLineNumbers try { final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En ese caso, es posible que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo irregular que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro utilizarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la aplicación y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | </SDKv4> <SDKv3> Antes de mostrar Remote Config y paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a Remote Config y paywalls personalizados. Para obtener orientación sobre cómo recuperar paywalls creados con Paywall Builder, consulta [Obtener paywalls de Paywall Builder y su configuración](flutter-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener paywalls y productos en tu app (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en tu paywall](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-flutter) en tu aplicación móvil. </details> ## Obtener información del paywall \{#fetch-paywall-information\} En Adapty, un [producto](product) es una combinación de productos tanto de App Store como de Google Play. Estos productos multiplataforma se integran en paywalls, lo que te permite mostrarlos en placements específicos de tu aplicación móvil. Para mostrar los productos, necesitas obtener un [Paywall](paywalls) desde uno de tus [placements](placements) con el método `getPaywall`. :::important **No escribas los IDs de producto en el código.** El único ID que debes incluir en el código es el del placement. Los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un paywall devuelve dos productos hoy y tres mañana, muéstralos todos sin necesidad de modificar el código. ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>valor por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](flutter-localizations-and-locale-codes) para obtener más información sobre los códigos de idioma y cómo recomendamos utilizarlos.</p> | | **fetchPolicy** | valor por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la aplicación y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls en dos capas: la caché de actualización regular descrita anteriormente y los [paywalls de respaldo](flutter-use-fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | valor por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el límite, se devolverán los datos en caché o el fallback local.</p><p></p><p>Ten en cuenta que, en casos excepcionales, este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede estar compuesta por varias peticiones internas.</p> | ¡No escribas los IDs de productos en el código! Dado que los paywalls se configuran de forma remota, los productos disponibles, el número de productos y las ofertas especiales (como las pruebas gratuitas) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si inicialmente obtienes 2 productos, tu app debe mostrar esos 2 productos. Sin embargo, si más adelante obtienes 3 productos, tu app debe mostrar los 3 sin necesidad de ningún cambio en el código. Lo único que tienes que escribir en el código es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Un objeto [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) con: una lista de IDs de productos, el identificador del paywall, Remote Config y otras propiedades. | ## Obtener productos \{#fetch-products\} Una vez que tengas el paywall, puedes consultar el array de productos que le corresponde: ```dart showLineNumbers try { final products = await Adapty().getPaywallProducts(paywall: paywall); // the requested products array } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Lista de objetos [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y otras propiedades. | Al implementar tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html). A continuación se muestran las propiedades más utilizadas; consulta el documento enlazado para obtener todos los detalles sobre las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país de la store seleccionado por el usuario, no en el idioma del dispositivo. | | **Price** | Para mostrar el precio en formato localizado, usa `product.price.localizedString`. Esta localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número mediante `product.price.amount`. El valor se proporcionará en la moneda local. Para obtener el símbolo de moneda correspondiente, usa `product.price.currencySymbol`. | | **Subscription Period** | Para mostrar el período (por ejemplo, semana, mes, año, etc.), usa `product.subscription?.localizedPeriod`. Esta localización se basa en la configuración regional del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscription?.period`. Desde ahí puedes acceder al enum `unit` para obtener la duración (es decir, día, semana, mes, año o desconocido). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral verías `AdaptyPeriodUnit.month` en la propiedad unit y `3` en la propiedad numberOfUnits. | | **Introductory Offer** | Para mostrar un distintivo u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscription?.offer?.phases`. Es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase encontrarás las siguientes propiedades útiles:<br/>• `paymentMode`: un enum con los valores `AdaptyPaymentMode.freeTrial`, `AdaptyPaymentMode.payAsYouGo`, `AdaptyPaymentMode.payUpFront` y `AdaptyPaymentMode.unknown`. Las pruebas gratuitas serán del tipo `AdaptyPaymentMode.freeTrial`.<br/>• `price`: el precio con descuento como número. En las pruebas gratuitas, el valor será `0`.<br/>• `localizedNumberOfPeriods`: una cadena localizada según la configuración regional del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días muestra `3 days` en este campo.<br/>• `subscriptionPeriod`: alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma manera para las ofertas que lo descrito en la sección anterior.<br/>• `localizedSubscriptionPeriod`: el período de suscripción del descuento formateado según la configuración regional del usuario. | ## Acelera la obtención de paywalls con el paywall de audiencia predeterminada \{#speed-up-paywall-fetching-with-default-audience-paywall\} Normalmente, los paywalls se obtienen casi al instante, por lo que no tienes que preocuparte por acelerar este proceso. Sin embargo, en casos en los que tienes muchas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, obtener un paywall puede tardar más de lo deseado. En esas situaciones, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ningún paywall. Para solucionar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall mediante el método `getPaywall`, tal como se detalla en la sección [Obtener información del paywall](fetch-paywalls-and-products-flutter#fetch-paywall-information) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes paywalls para distintas versiones de la app (la actual y futuras), puedes encontrarte con dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (antigua) o asumir que los usuarios con esa versión pueden tener problemas con paywalls que no se rendericen correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluyendo segmentación por país, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estos inconvenientes para beneficiarte de una carga más rápida del paywall, usa el método `getPaywallForDefaultAudience` de la siguiente manera. De lo contrario, utiliza `getPaywall` descrito [anteriormente](fetch-paywalls-and-products-flutter#fetch-paywall-information). ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` :::note El método `getPaywallForDefaultAudience` está disponible a partir de la versión 3.2.0 del SDK de Flutter. ::: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>predeterminado: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` representa el inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](flutter-localizations-and-locale-codes) para obtener más información sobre los códigos de idioma y cómo recomendamos utilizarlos.</p> | | **fetchPolicy** | predeterminado: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la aplicación y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | </SDKv3> --- # File: present-remote-config-paywalls-flutter --- --- title: "Renderizar paywall diseñado con Remote Config en Flutter SDK" description: "Descubre cómo presentar paywalls de Remote Config en Adapty Flutter SDK para personalizar la experiencia del usuario." --- <SDKv4> Si has personalizado un paywall usando Remote Config, tendrás que implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Dado que Remote Config ofrece flexibilidad adaptada a tus necesidades, tú decides qué incluir y cómo se ve tu paywall. Te proporcionamos un método para obtener la configuración remota, dándote la autonomía de mostrar tu paywall personalizado configurado mediante Remote Config. ## Obtener el Remote Config del paywall y mostrarlo \{#get-paywall-remote-config-and-present-it\} En la v4, el flow incluye una lista `remoteConfigs` — un Remote Config por cada localización configurada. Elige la entrada que coincida con el idioma del usuario y extrae los valores necesarios. Consulta [Localizaciones y códigos de idioma](flutter-localizations-and-locale-codes) para seleccionar la localización correcta. ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // one entry per configured localization; fall back to the first one final config = flow.remoteConfigs.firstWhereOrNull((c) => c.locale == 'en') ?? flow.remoteConfig; final String? headerText = config?.dictionary?['header_text'] as String?; } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` En este punto, una vez que hayas recibido todos los valores necesarios, es momento de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a diferentes pantallas y orientaciones de dispositivos móviles, ofreciendo una experiencia fluida y fácil de usar en distintos dispositivos. :::warning Asegúrate de registrar el evento de visualización del paywall como se describe a continuación, para que las analíticas de Adapty puedan capturar información para embudos y pruebas A/B. ::: Cuando hayas terminado de mostrar el paywall, continúa con la configuración del flujo de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu paywall. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](flutter-making-purchases). Te recomendamos [crear un paywall de respaldo llamado paywall de respaldo](flutter-use-fallback-paywalls). Este paywall se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Registrar eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus paywalls. Aunque recopilamos los datos de compras automáticamente, registrar las visualizaciones de paywall requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un paywall. Para registrar un evento de visualización de paywall, simplemente llama a `.logShowFlow(flow: flow)`, y se reflejará en las métricas de tu paywall en los embudos y las pruebas A/B. :::important No es necesario llamar a `.logShowFlow(flow: flow)` si estás mostrando flows o paywalls creados en el [builder](adapty-paywall-builder). ::: ```dart showLineNumbers try { await Adapty().logShowFlow(flow: flow); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :------- |:----------------------------------------------------------------------| | **flow** | requerido | Un objeto `AdaptyFlow`. | </SDKv4> <SDKv3> Si has personalizado un paywall con Remote Config, necesitas implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú decides qué se incluye y cómo se ve tu paywall. Te proporcionamos un método para obtener la configuración remota, dándote autonomía total para mostrar tu paywall personalizado configurado mediante Remote Config. ## Obtener el Remote Config del paywall y mostrarlo \{#get-paywall-remote-config-and-present-it\} Para obtener el Remote Config de un paywall, accede a la propiedad `remoteConfig` y extrae los valores que necesites. ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID"); final String? headerText = paywall.remoteConfig?.dictionary?['header_text'] as String?; } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` En este punto, una vez que hayas recibido todos los valores necesarios, es momento de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a distintos tamaños de pantalla y orientaciones de móvil, ofreciendo una experiencia fluida y fácil de usar en diferentes dispositivos. :::warning Asegúrate de registrar el evento de visualización del paywall como se describe a continuación, para que los análisis de Adapty puedan recopilar información para los funnels y las pruebas A/B. ::: Cuando termines de mostrar el paywall, continúa con la configuración del flujo de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu paywall. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](flutter-making-purchases). Te recomendamos [crear un paywall de respaldo llamado fallback paywall](flutter-use-fallback-paywalls). Este paywall de respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Registrar eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus paywalls. Aunque los datos de compras se recopilan automáticamente, registrar las visualizaciones de paywall requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un paywall. Para registrar un evento de visualización de paywall, simplemente llama a `.logShowPaywall(paywall)`, y quedará reflejado en las métricas de tu paywall en los embudos y las pruebas A/B. :::important No es necesario llamar a `.logShowPaywall(paywall)` si estás mostrando paywalls creados con el [Paywall Builder](adapty-paywall-builder). ::: ```dart showLineNumbers try { final result = await Adapty().logShowPaywall(paywall: paywall); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :------- |:----------------------------------------------------------------------| | **paywall** | obligatorio | Un objeto [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). | </SDKv3> --- # File: flutter-making-purchases --- --- title: "Realizar compras in-app en Flutter SDK" description: "Guía para gestionar compras in-app y suscripciones con Adapty." --- Mostrar paywalls en tu app móvil es un paso esencial para ofrecer a los usuarios acceso a contenido o servicios premium. Sin embargo, simplemente mostrar estos paywalls es suficiente para gestionar las compras solo si usas el [Paywall Builder](adapty-paywall-builder) para personalizar tus paywalls. Si no usas el Paywall Builder, debes utilizar un método separado llamado `.makePurchase()` para completar una compra y desbloquear el contenido deseado. Este método es la puerta de entrada para que los usuarios interactúen con los paywalls y realicen las transacciones que desean. Si tu paywall tiene una oferta promocional activa para el producto que el usuario intenta comprar, Adapty la aplicará automáticamente en el momento de la compra. :::warning Ten en cuenta que la oferta introductoria se aplicará automáticamente solo si utilizas los paywalls configurados con el Paywall Builder. En otros casos, deberás [verificar la elegibilidad del usuario para una oferta introductoria en iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Omitir este paso puede provocar que tu app sea rechazada durante el proceso de publicación. Además, podría suponer que se cobre el precio completo a usuarios que son elegibles para una oferta introductoria. ::: Asegúrate de haber completado la [configuración inicial](quickstart) sin saltarte ningún paso. Sin ella, no podemos validar las compras. ## Realizar una compra \{#make-purchase\} :::note **¿Usas el [Paywall Builder](adapty-paywall-builder)?** Las compras se procesan automáticamente; puedes saltarte este paso. **¿Buscas una guía paso a paso?** Consulta la [guía de inicio rápido](flutter-implement-paywalls-manually) para instrucciones completas de implementación con todo el contexto. ::: ```dart showLineNumbers try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): if (profile.accessLevels['premium']?.isActive ?? false) { // Grant access to the paid features } break; case AdaptyPurchaseResultPending(): break; case AdaptyPurchaseResultUserCancelled(): break; default: break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | obligatorio | Un objeto [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) obtenido del paywall. | Parámetros de la respuesta: | Parámetro | Descripción | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Si la solicitud se ha completado correctamente, la respuesta contiene este objeto. Un objeto [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html) proporciona información completa sobre los niveles de acceso, suscripciones y compras únicas de un usuario dentro de la app.</p><p>Comprueba el estado del nivel de acceso para determinar si el usuario tiene el acceso requerido a la app.</p> | :::warning **Nota:** si todavía usas una versión de StoreKit de Apple inferior a la v2.0 y una versión del SDK de Adapty inferior a la v2.9.0, debes proporcionar el [secreto compartido de Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret) en su lugar. Apple ha deprecado este método. ::: ## Cambiar suscripción al realizar una compra \{#change-subscription-when-making-a-purchase\} Cuando un usuario elige una nueva suscripción en lugar de renovar la actual, el funcionamiento depende de la tienda: - En App Store, la suscripción se actualiza automáticamente dentro del grupo de suscripciones. Si un usuario compra una suscripción de un grupo mientras ya tiene activa una suscripción de otro grupo, ambas estarán activas al mismo tiempo. - En Google Play, la suscripción no se actualiza automáticamente. Tendrás que gestionar el cambio en el código de tu app, tal como se describe a continuación. Para reemplazar una suscripción por otra en Android, llama al método `.makePurchase()` con el parámetro adicional: ```dart showLineNumbers try { final subscriptionUpdateParams = AdaptyAndroidSubscriptionUpdateParameters( 'OLD_PRODUCT_ID', AdaptyAndroidSubscriptionUpdateReplacementMode.immediateWithTimeProration, ); final result = await Adapty().makePurchase( product: product, parameters: AdaptyPurchaseParameters( subscriptionUpdateParams: subscriptionUpdateParams, ), ); // successful cross-grade } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` Parámetro de solicitud adicional: | Parámetro | Presencia | Descripción | | :--------------------------- | :------- |:--------------------------------------------------------------------------------------------------------| | **parameters** | requerido | un objeto `AdaptyPurchaseParameters` con su campo `subscriptionUpdateParams` configurado como un objeto [`AdaptyAndroidSubscriptionUpdateParameters`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyAndroidSubscriptionUpdateParameters-class.html). | Puedes obtener más información sobre las suscripciones y los modos de reemplazo en la documentación de Google Developer: - [Acerca de los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Recomendaciones de Google para los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Modo de reemplazo [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Nota: este método solo está disponible para actualizaciones de suscripción. No se admiten cambios a un plan inferior. - Modo de reemplazo [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Nota: el cambio de suscripción real solo se producirá cuando finalice el período de facturación actual. ## Canjear códigos de oferta en iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Sobre los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios concretos. A diferencia de las ofertas habituales, que se aplican de forma automática, los códigos de oferta se distribuyen fuera de la app — por email, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, accediendo a una URL de canje o a través de un diálogo dentro de la app. Para configurar códigos de oferta, abre una suscripción en App Store Connect y ve a su sección **Offer Codes**. Puedes crear [tres tipos](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) de códigos de oferta: - **Free** — la suscripción es gratuita durante un período determinado y la siguiente renovación se cobra al precio completo. - **Pay as you go** — el usuario paga un precio reducido en cada ciclo de facturación durante un período determinado y, después, la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único reducido por toda la duración de la oferta y, después, la suscripción se renueva al precio completo. No es necesario añadir los códigos de oferta a Adapty. Apple etiqueta cada transacción durante el período de la oferta con la categoría del código de oferta. Esto incluye el canje inicial y todas las renovaciones con descuento posteriores. Adapty detecta la etiqueta y registra cada transacción con la categoría de oferta `offer_code`. Cuando termina el período de oferta y la suscripción se renueva al precio completo, la etiqueta deja de estar presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Resolución de discrepancias en los ingresos \{#revenue-discrepancy-troubleshooting\} Si observas que una transacción con código de oferta aparece en Adapty al precio completo del producto en lugar del precio reducido, verifica lo siguiente en App Store Connect: - El código de oferta tiene los precios correctos configurados para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está configurado para el país o región específica del usuario. Apple envía el precio regional en la transacción. Si no hay ningún precio regional configurado para la oferta, Apple puede enviar el precio completo del producto. Puedes filtrar y verificar las transacciones con código de oferta en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds) mediante los filtros de tipo de oferta **Offer Code** y **Offer Discount Type**. #### Códigos promocionales heredados (obsoletos) \{#legacy-promo-codes-deprecated\} :::warning Apple dejó obsoletos los códigos promocionales para compras in-app en marzo de 2026. Los códigos de oferta los sustituyen con más funcionalidades: elegibilidad configurable, fechas de expiración y hasta 1 millón de códigos por trimestre. Si antes usabas códigos promocionales para compras in-app, migra a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app y versión) daban acceso gratuito a una suscripción. A diferencia de los códigos de oferta, Apple no incluía información de descuento en las transacciones con código promocional — enviaba el precio completo del producto en el recibo. Por ello, Adapty registraba estas transacciones al precio completo, lo que generaba discrepancias entre los análisis de Adapty y App Store Connect. Si ves transacciones históricas al precio completo que deberían haber sido gratuitas, es probable que provengan de códigos promocionales heredados. Como estos códigos ya están obsoletos, migra a los códigos de oferta para un seguimiento preciso de los ingresos. </Details> Para mostrar la hoja de canje de códigos en tu app: ```dart showLineNumbers try { await Adapty().presentCodeRedemptionSheet(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` :::danger Según nuestras observaciones, la hoja de canje de códigos de oferta puede no funcionar de forma fiable en algunas apps. Recomendamos redirigir al usuario directamente a la App Store. Para hacer esto, necesitas abrir la URL con el siguiente formato: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ### Gestionar planes de prepago (Android) \{#manage-prepaid-plans-android\} Si los usuarios de tu app pueden adquirir [planes de prepago](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (por ejemplo, comprar una suscripción no renovable por varios meses), puedes habilitar las [transacciones pendientes](https://developer.android.com/google/play/billing/subscriptions#pending) para dichos planes. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withGoogleEnablePendingPrepaidPlans(true), ); ``` --- # File: flutter-restore-purchase --- --- title: "Restaurar compras en una app móvil con Flutter SDK" description: "Aprende a restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- Restaurar compras tanto en iOS como en Android es una funcionalidad que permite a los usuarios recuperar el acceso a contenido adquirido anteriormente, como suscripciones o compras in-app, sin que se les vuelva a cobrar. Esta funcionalidad es especialmente útil para quienes han desinstalado y reinstalado la app, o han cambiado de dispositivo, y quieren acceder a su contenido sin pagar de nuevo. :::note En los paywalls creados con [Paywall Builder](adapty-paywall-builder), las compras se restauran automáticamente sin necesidad de código adicional por tu parte. Si ese es tu caso, puedes saltarte este paso. ::: Para restaurar una compra si no usas el [Paywall Builder](adapty-paywall-builder) para personalizar el paywall, llama al método `.restorePurchases()`: ```dart showLineNumbers try { final profile = await Adapty().restorePurchases(); if (profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false) { // successful access restore } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros de respuesta: | Parámetro | Descripción | |---------|-----------| | **Profile** | <p>Un objeto [`AdaptyProfile`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). Este modelo contiene información sobre niveles de acceso, suscripciones y compras únicas.</p><p>Comprueba el **estado del nivel de acceso** para determinar si el usuario tiene acceso a la app.</p> | :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: --- # File: implement-observer-mode-flutter --- --- title: "Implementar el modo Observer en Flutter SDK" description: "Implementa el modo Observer en Adapty para rastrear eventos de suscripción de usuarios en Flutter SDK." --- Si ya tienes tu propia infraestructura de compras y no estás listo para migrar completamente a Adapty, puedes explorar el [Modo observador](observer-vs-full-mode). En su forma básica, el Modo observador ofrece análisis avanzados e integración fluida con sistemas de atribución y análisis. Si esto se ajusta a tus necesidades, solo tienes que: 1. Activarlo al configurar el SDK estableciendo el parámetro `observerMode` en `true`. Sigue las instrucciones de configuración para [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk). 2. [Informar las transacciones](report-transactions-observer-mode-flutter) desde tu infraestructura de compras existente a Adapty. ## Configuración del modo Observer \{#observer-mode-setup\} Activa el modo Observer si gestionas las compras y el estado de las suscripciones por tu cuenta y usas Adapty para enviar eventos de suscripción y analíticas. :::important Cuando se ejecuta en modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlas tú mismo. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withObserverMode(true) // Enable observer mode ..withLogLevel(AdaptyLogLevel.verbose), ); ``` Parámetros: | Parámetro | Descripción | | --------------------------- | ------------------------------------------------------------ | | observerMode | Un valor booleano que controla el [modo Observer](observer-vs-full-mode). El valor predeterminado es `false`. | ## Uso de los paywalls de Adapty en el modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los paywalls y las funciones de pruebas A/B de Adapty, puedes hacerlo, aunque requiere algo de configuración adicional en el modo Observer. Esto es lo que necesitas hacer además de los pasos anteriores: 1. Muestra los paywalls como de costumbre para los [paywalls de Remote Config](present-remote-config-paywalls-flutter). 3. [Asocia los paywalls](report-transactions-observer-mode-flutter) con las transacciones de compra. :::tip En el SDK v4, también puedes presentar flows y paywalls renderizados por Adapty en modo Observer: registra un `AdaptyUIObserverModeResolver` para realizar la compra o restauración con tu propio código cuando el usuario pulse el botón correspondiente. Consulta [Presentar flows en modo Observer](flutter-present-flows-in-observer-mode). ::: --- # File: report-transactions-observer-mode-flutter --- --- title: "Reportar transacciones en Observer Mode en Flutter SDK" description: "Reporta transacciones de compra en el Observer Mode de Adapty para obtener información sobre usuarios y seguimiento de ingresos en Flutter SDK." --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+ (current)" default> En el modo Observador, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Debes informar las transacciones desde tu app store. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` para informar explícitamente cada transacción y que Adapty pueda reconocerla. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el paywall que la originó, lo que garantiza un análisis preciso del paywall. ```dart showLineNumbers try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | requerido | <ul><li> Para iOS: Identificador de la transacción.</li><li> Para Android: Identificador de tipo string `purchase.getOrderId` de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación.</li></ul> | | variationId | opcional | Identificador de tipo string de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x (legacy)" default> En el modo Observer, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Debes informar las transacciones desde tu store o restaurarlas. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` en ambas plataformas para informar explícitamente cada transacción, y usa `restorePurchases` en Android como paso adicional para asegurarte de que Adapty la reconozca. :::warning **¡No omitas el reporte de transacciones ni la restauración de compras!** Si no llamas a estos métodos, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el paywall que la originó y garantiza la precisión de los análisis del paywall. ```dart showLineNumbers // every time when calling transaction.finish() if (Platform.isAndroid) { try { await Adapty().restorePurchases(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } } try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | obligatorio | <ul><li> Para iOS, StoreKit 1: un objeto [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</li><li> Para iOS, StoreKit 2: objeto [Transaction](https://developer.apple.com/documentation/storekit/transaction).</li><li> Para Android: identificador de tipo String (purchase.getOrderId de la compra, donde purchase es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación).</li></ul> | | variationId | opcional | El identificador de tipo String de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). | </TabItem> <TabItem value="old2" label="Adapty SDK up to 3.2.x (legacy)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> **Notificación de transacciones** - Las versiones hasta 3.1.x escuchan automáticamente las transacciones en el App Store, por lo que no es necesario notificarlas manualmente. - La versión 3.2 no es compatible con el modo Observer. </TabItem> <TabItem value="kotlin" label="Android and Android-based cross-platforms" default> **Notificación de transacciones** Usa `restorePurchases` para reportar una transacción a Adapty en el Modo Observador, tal como se explica en la página [Restaurar compras en el código móvil](flutter-restore-purchase). :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `restorePurchases`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: </TabItem> </Tabs> **Asociación de paywalls a transacciones** El SDK de Adapty no puede determinar el origen de las compras, ya que eres tú quien las procesa. Por ello, si planeas usar paywalls y/o pruebas A/B en modo Observer, necesitas asociar la transacción proveniente de tu store con el paywall correspondiente en el código de tu app. Es importante hacerlo correctamente antes de publicar la app; de lo contrario, causará errores en los análisis. ```dart final transactionId = transaction.transactionIdentifier final variationId = paywall.variationId try { await Adapty().setVariationId('transactionId', variationId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> </Tabs> --- # File: flutter-troubleshoot-purchases --- --- title: "Solucionar problemas de compras en Flutter SDK" description: "Solucionar problemas de compras en Flutter SDK" --- Esta guía te ayuda a resolver los problemas más comunes al implementar compras manualmente en el Flutter SDK. ## makePurchase se ejecuta correctamente, pero el perfil no se actualiza \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Problema**: El método `makePurchase` se completa correctamente, pero el perfil del usuario y el estado de la suscripción no se actualizan en Adapty. **Causa**: Esto normalmente indica una configuración incompleta de Google Play Store. **Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android). ## makePurchase se invoca dos veces \{#makepurchase-is-invoked-twice\} **Problema**: El método `makePurchase` se está llamando varias veces para la misma compra. **Causa**: Esto suele ocurrir cuando el flujo de compra se dispara varias veces debido a problemas de gestión del estado de la UI o interacciones rápidas del usuario. **Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android). ## AdaptyError.cantMakePayments en modo observer \{#adaptyerror-cantmakepayments-in-observer-mode\} **Problema**: Recibes `AdaptyError.cantMakePayments` al usar `makePurchase` en modo observer. **Causa**: En modo observer, debes gestionar las compras por tu cuenta, no usar el método `makePurchase` de Adapty. **Solución**: Si usas `makePurchase` para las compras, desactiva el modo observer. Tienes que elegir entre usar `makePurchase` o gestionar las compras por tu cuenta en modo observer. Consulta [Implementar el modo Observer](implement-observer-mode-flutter) para más detalles. ## Error de Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Problema**: Recibes un error de facturación no disponible de Google Play Store. **Causa**: Este error no está relacionado con Adapty. Es un error de la biblioteca de facturación de Google Play que indica que la facturación no está disponible en el dispositivo. **Solución**: Este error no está relacionado con Adapty. Puedes consultarlo en la documentación de Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## No se encuentran makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Problema**: Tienes problemas porque no se encuentran los `makePurchasesCompletionHandlers`. **Causa**: Esto suele estar relacionado con problemas durante las pruebas en sandbox. **Solución**: Crea un nuevo usuario sandbox e inténtalo de nuevo. Esto resuelve con frecuencia los problemas del completion handler de compras en sandbox. ## Otros problemas \{#other-issues\} **Problema**: Tienes otros problemas relacionados con compras que no están cubiertos arriba. **Solución**: Si es necesario, migra el SDK a la versión más reciente siguiendo las [guías de migración](flutter-sdk-migration-guides). Muchos problemas se resuelven en versiones más recientes del SDK. --- # File: flutter-identifying-users --- --- title: "Identificar usuarios en el SDK de Flutter" description: "Identifica usuarios en Adapty para mejorar las experiencias personalizadas de suscripción." --- Adapty crea un ID de perfil interno para cada usuario. Sin embargo, si tienes tu propio sistema de autenticación, debes establecer tu propio Customer User ID. Puedes encontrar usuarios por su Customer User ID en la sección [Perfiles](profiles-crm) y usarlo en la [API de servidor](getting-started-with-server-side-api), que se enviará a todas las integraciones. ### Configurar el ID de usuario del cliente durante la inicialización \{#setting-customer-user-id-on-configuration\} Si tienes un ID de usuario en el momento de la configuración, simplemente pásalo como parámetro `customerUserId` al método `.activate()`: ```dart showLineNumbers title="Dart" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID) ); } catch (e) { // handle the error } ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Configurar el ID de usuario del cliente después de la configuración \{#setting-customer-user-id-after-configuration\} Si no tienes un ID de usuario en la configuración del SDK, puedes establecerlo más adelante en cualquier momento con el método `.identify()`. Los casos más habituales para usar este método son tras el registro o la autenticación, cuando el usuario pasa de ser anónimo a estar autenticado. ```dart showLineNumbers try { await Adapty().identify(customerUserId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros de la solicitud: - **Customer User ID** (obligatorio): un identificador de usuario de tipo string. :::warning Reenvío de datos significativos del usuario En algunos casos, como cuando un usuario vuelve a iniciar sesión en su cuenta, los servidores de Adapty ya tienen información sobre ese usuario. En estos escenarios, el SDK de Adapty cambiará automáticamente para trabajar con el nuevo usuario. Si enviaste algún dato al usuario anónimo, como atributos personalizados o atribuciones de redes de terceros, deberás reenviar esos datos para el usuario identificado. También es importante tener en cuenta que debes volver a solicitar todos los paywalls y productos después de identificar al usuario, ya que los datos del nuevo usuario pueden ser diferentes. ::: ### Cerrar sesión e iniciar sesión \{#logging-out-and-logging-in\} Puedes cerrar la sesión del usuario en cualquier momento llamando al método `.logout()`: ```dart showLineNumbers try { await Adapty().logout(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` Luego puedes iniciar sesión con el usuario usando el método `.identify()`. ## Asignar `appAccountToken` (iOS) [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) es un **UUID** que te permite vincular las transacciones de App Store con la identidad interna de tus usuarios. StoreKit asocia este token con cada transacción, de modo que tu backend puede relacionar los datos de App Store con tus usuarios. Usa un UUID estable generado por usuario y reutilízalo para la misma cuenta en todos los dispositivos. Esto garantiza que las compras y las notificaciones de App Store queden correctamente vinculadas. Puedes establecer el token de dos formas: durante la activación del SDK o al identificar al usuario. :::important Siempre debes pasar `appAccountToken` junto con `customerUserId`. Si solo pasas el token, no se incluirá en la transacción. ::: ```dart showLineNumbers // Durante la configuración: try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID, iosAppAccountToken: "YOUR_APP_ACCOUNT_TOKEN") ); } catch (e) { // handle the error } // O al identificar usuarios try { await Adapty().identify(customerUserId, iosAppAccountToken: "YOUR_APP_ACCOUNT_TOKEN"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Establecer IDs de cuenta ofuscados (Android) \{#set-obfuscated-account-ids-android\} Google Play requiere IDs de cuenta ofuscados en ciertos casos de uso para mejorar la privacidad y seguridad de los usuarios. Estos IDs ayudan a Google Play a identificar las compras manteniendo el anonimato de la información del usuario, lo que resulta especialmente importante para la prevención del fraude y el análisis de datos. Es posible que necesites establecer estos IDs si tu app maneja datos sensibles de los usuarios o si debes cumplir con normativas de privacidad específicas. Los IDs ofuscados permiten a Google Play rastrear las compras sin exponer los identificadores reales de los usuarios. ```dart showLineNumbers // Durante la configuración: try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID, androidObfuscatedAccountId: "OBFUSCATED_ACCOUNT_ID") ); } catch (e) { // handle the error } // O al identificar usuarios try { await Adapty().identify(customerUserId, androidObfuscatedAccountId: "OBFUSCATED_ACCOUNT_ID"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ## Detectar usuarios en varios dispositivos \{#detect-users-across-devices\} Cuando el SDK se activa, lee automáticamente los derechos existentes del usuario desde StoreKit (iOS) o Google Play Billing (Android) y los sincroniza con el backend de Adapty. Una suscripción activa aparece en el perfil de Adapty sin que la app llame a `restorePurchases`. Lo que **no** ocurre automáticamente es reconocer que un perfil en un dispositivo nuevo pertenece al mismo usuario que el perfil en el dispositivo original. Adapty relaciona perfiles por Customer User ID, así que la continuidad de identidad depende de lo que uses como CUID. **Lo que Adapty puede detectar entre dispositivos** | Tu configuración | Lo que Adapty detecta | Lo que debes hacer | | --- | --- | --- | | Customer User ID = `device_id` (sin login en la app) | El nuevo dispositivo obtiene un CUID diferente y, por tanto, un perfil diferente. La suscripción se sincroniza con el nuevo perfil mediante un evento **Access level updated**, pero `subscription_started` no se dispara — el nuevo perfil se trata como heredero de la compra original. Los análisis basados en `subscription_started` contarán de menos a los usuarios que vuelven. | Usa un ID de cuenta estable como Customer User ID para que un usuario que regresa coincida con el perfil existente en todos los dispositivos. | | Customer User ID = ID de cuenta estable (login en cada dispositivo) | El SDK sincroniza automáticamente la suscripción en `activate()`, e `identify()` relaciona el perfil existente por CUID. | No se necesita configuración adicional — tanto la identidad como la suscripción se resuelven automáticamente. | | Heredero de Apple Family Sharing | El miembro de la familia recibe la suscripción solo a través de un evento **Access level updated** — `subscription_started` no se dispara. | Escucha el evento **Access level updated**. Consulta [Apple Family Sharing](apple-family-sharing) para ver la matriz de eventos completa. | | Misma cuenta de Apple/Google, distintos usuarios dentro de la app | El primer perfil que registra la compra se convierte en el principal. Los perfiles posteriores ven la suscripción a través de una cadena de herederos, con un evento **Access level updated**. | Exige login y elige un [modo de compartición](sharing-paid-access-between-user-accounts) que se adapte a tu modelo. | **Restaurar compras en un dispositivo nuevo** Muestra un botón "Restaurar compras" iniciado por el usuario en tu paywall. Apple App Review (directriz 3.1.1) lo exige, y actúa como alternativa cuando la sincronización automática no cubre algún caso límite. El botón debe llamar a `restorePurchases` en tu SDK. No es necesario llamar a `restorePurchases` de forma programática al primer inicio para el uso normal — el SDK ya ejecuta el equivalente en `activate()`. Reserva las llamadas programáticas para forzar una verificación de recibo actualizada, por ejemplo al depurar un acceso que falta después de que `activate()` haya completado. --- # File: flutter-setting-user-attributes --- --- title: "Establecer atributos de usuario en Flutter SDK" description: "Aprende a establecer atributos de usuario en Adapty para mejorar la segmentación de audiencias." --- Puedes añadir atributos opcionales como correo electrónico, número de teléfono, etc., al perfil del usuario de tu app. Después puedes usar esos atributos para crear [segmentos](segments) de usuarios o simplemente consultarlos en el CRM. ### Configuración de atributos de usuario \{#setting-user-attributes\} Para establecer atributos de usuario, llama al método `.updateProfile()`: ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setEmail("email@email.com") ..setPhoneNumber("+18888888888") ..setFirstName('John') ..setLastName('Appleseed') ..setGender(AdaptyProfileGender.other) ..setBirthday(DateTime(1970, 1, 3)); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Ten en cuenta que los atributos que hayas configurado previamente con el método `updateProfile` no se restablecerán. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Lista de claves permitidas \{#the-allowed-keys-list\} A continuación se muestran las claves `<Key>` permitidas de `AdaptyProfileParameters.Builder` y sus valores `<Value>`: | Clave | Valor | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | Enum, los valores permitidos son: `female`, `male`, `other` | | birthday | Date | ### Atributos de usuario personalizados \{#custom-user-attributes\} Puedes definir tus propios atributos personalizados, normalmente relacionados con el uso de tu app. Por ejemplo, en apps de fitness podrían ser el número de ejercicios por semana; en apps de aprendizaje de idiomas, el nivel de conocimiento del usuario, etc. Puedes usarlos en segmentos para crear paywalls y ofertas dirigidas, y también en análisis para descubrir qué métricas de producto influyen más en los ingresos. ```dart showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..setCustomStringAttribute('value1', 'key1') ..setCustomDoubleAttribute(1.0, 'key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Para eliminar una clave existente, usa el método `.withRemoved(customAttributeForKey:)`: ```dart showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..removeCustomAttribute('key1') ..removeCustomAttribute('key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` A veces necesitas saber qué atributos personalizados ya se han instalado. Para ello, usa el campo `customAttributes` del objeto `AdaptyProfile`. :::warning Ten en cuenta que el valor de `customAttributes` puede estar desactualizado, ya que los atributos del usuario pueden enviarse desde distintos dispositivos en cualquier momento, por lo que los atributos en el servidor podrían haber cambiado desde la última sincronización. ::: ### Límites \{#limits\} - Hasta 30 atributos personalizados por usuario - Los nombres de clave pueden tener hasta 30 caracteres. El nombre de la clave puede incluir caracteres alfanuméricos y cualquiera de los siguientes: `_` `-` `.` - El valor puede ser una cadena de texto o un número decimal con un máximo de 50 caracteres. --- # File: flutter-listen-subscription-changes --- --- title: "Verificar el estado de la suscripción en el SDK de Flutter" description: "Consulta y gestiona el estado de la suscripción de los usuarios en Adapty para mejorar la retención de clientes en tu app de Flutter." --- Con Adapty, hacer seguimiento del estado de la suscripción es muy sencillo. No tienes que insertar manualmente los IDs de producto en tu código. En su lugar, puedes confirmar el estado de suscripción de un usuario fácilmente comprobando si tiene un [nivel de acceso](access-level) activo. <details> <summary>Antes de empezar a comprobar el estado de la suscripción (haz clic para expandir)</summary> - Para iOS, configura las [notificaciones del servidor de App Store](enable-app-store-server-notifications) - Para Android, configura las [notificaciones en tiempo real para desarrolladores (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## Nivel de acceso y el objeto AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Los niveles de acceso son propiedades del objeto [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). Te recomendamos obtener el perfil cuando se inicie tu app, por ejemplo al [identificar un usuario](flutter-identifying-users#setting-customer-user-id-on-configuration), y actualizarlo cada vez que se produzcan cambios. Así podrás usar el objeto de perfil sin tener que solicitarlo repetidamente. Para recibir notificaciones sobre actualizaciones del perfil, escucha los cambios del perfil tal como se describe en la sección [Escuchar actualizaciones del perfil, incluidos los niveles de acceso](flutter-listen-subscription-changes) a continuación. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Recuperar el nivel de acceso desde el servidor \{#retrieving-the-access-level-from-the-server\} Para obtener el nivel de acceso desde el servidor, usa el método `.getProfile()`: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros de respuesta: | Parámetro | Descripción | | --------- | ------------------------------------------------------------ | | Profile | <p>Un objeto [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). En general, solo tienes que comprobar el estado del nivel de acceso del perfil para determinar si el usuario tiene acceso premium a la app.</p><p></p><p>El método `.getProfile` proporciona el resultado más actualizado, ya que siempre intenta consultar la API. Si por algún motivo (por ejemplo, sin conexión a internet), el SDK de Adapty no puede obtener información del servidor, se devolverán los datos de la caché. También es importante tener en cuenta que el SDK de Adapty actualiza la caché de `AdaptyProfile` de forma periódica para mantener esta información lo más actualizada posible.</p> | El método `.getProfile()` te proporciona el perfil del usuario a partir del cual puedes obtener el estado del nivel de acceso. Puedes tener múltiples niveles de acceso por app. Por ejemplo, si tienes una app de noticias y vendes suscripciones a diferentes temáticas de forma independiente, puedes crear niveles de acceso "sports" y "science". Pero en la mayoría de los casos solo necesitarás un nivel de acceso; en ese caso, puedes usar simplemente el nivel de acceso predeterminado "premium". A continuación se muestra un ejemplo para verificar el nivel de acceso predeterminado "premium": ```dart showLineNumbers try { final profile = await Adapty().getProfile(); if (profile?.accessLevels['premium']?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Escuchar actualizaciones del estado de suscripción \{#listening-for-subscription-status-updates\} Cada vez que cambia la suscripción del usuario, Adapty lanza un evento. Para recibir mensajes de Adapty, necesitas realizar una configuración adicional: ```dart showLineNumbers Adapty().didUpdateProfileStream.listen((profile) { // handle any changes to subscription state }); ``` Adapty también lanza un evento al iniciar la aplicación. En ese caso, se pasará el estado de suscripción almacenado en caché. ### Caché del estado de suscripción \{#subscription-status-cache\} La caché implementada en el SDK de Adapty almacena el estado de suscripción del perfil. Esto significa que, aunque el servidor no esté disponible, se puede acceder a los datos en caché para obtener información sobre el estado de suscripción del perfil. Sin embargo, es importante tener en cuenta que no es posible realizar solicitudes de datos directamente desde la caché. El SDK consulta el servidor periódicamente cada minuto para comprobar si hay actualizaciones o cambios relacionados con el perfil. Si hay modificaciones, como nuevas transacciones u otras actualizaciones, se enviarán a los datos en caché para mantenerlos sincronizados con el servidor. --- # File: flutter-deal-with-att --- --- title: "Gestionar ATT en Flutter SDK" description: "Empieza con Adapty en Flutter para simplificar la configuración y gestión de suscripciones." --- Si tu aplicación utiliza el framework AppTrackingTransparency y muestra al usuario una solicitud de autorización de seguimiento, debes enviar el [estado de autorización](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) a Adapty. ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setAppTrackingTransparencyStatus(AdaptyIOSAppTrackingTransparencyStatus.authorized); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` :::warning Recomendamos encarecidamente que envíes este valor lo antes posible cuando cambie; solo así los datos se transmitirán a tiempo a las integraciones que hayas configurado. ::: --- # File: kids-mode-flutter --- --- title: "Modo Kids en Flutter SDK" description: "Activa fácilmente el Modo Kids para cumplir con las políticas de Apple y Google. Sin recopilación de IDFA, GAID ni datos de publicidad en Flutter SDK." --- Si tu aplicación Flutter está destinada a niños, debes seguir las políticas de [Apple](https://developer.apple.com/kids/) y [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Si usas el SDK de Adapty, unos pocos pasos sencillos te ayudarán a configurarlo para cumplir con estas políticas y superar las revisiones de las tiendas. ## ¿Qué se requiere? \{#whats-required\} Debes configurar el SDK para deshabilitar la recopilación de: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [Dirección IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Además, te recomendamos usar el customer user ID con cuidado. Un ID de usuario en formato `<FirstName.LastName>` se considerará recopilación de datos personales, al igual que el uso del correo electrónico. Para el modo Kids, la mejor práctica es usar identificadores aleatorios o anonimizados (por ejemplo, IDs con hash o UUIDs generados por el dispositivo) para garantizar el cumplimiento normativo. ## Habilitar el Modo Kids \{#enabling-kids-mode\} ### Cambios en el Adapty Dashboard \{#updates-in-the-adapty-dashboard\} En el Adapty Dashboard, debes deshabilitar la recopilación de direcciones IP. Para hacerlo, ve a [App settings](https://app.adapty.io/settings/general) y haz clic en **Disable IP address collection** bajo **Collect users' IP address**. ### Actualizaciones en el código de tu aplicación móvil \{#updates-in-your-mobile-app-code\} Para cumplir con las políticas, desactiva la recopilación del IDFA del usuario (para iOS), GAID/AAID (para Android) y la dirección IP. **Android: Actualiza la configuración del SDK** ```dart showLineNumbers title="Dart" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') // highlight-start ..withGoogleAdvertisingIdCollectionDisabled(true) // set to `true` ..withIpAddressCollectionDisabled(true), // set to `true` // highlight-end ); } catch (e) { // handle the error } ``` **iOS: Habilitar el Modo Niños en SDK v4** :::important En SDK v4, el SDK nativo de iOS se instala a través de Swift Package Manager, y el Modo Niños se activa mediante el trait de Swift `KidsMode`, que elimina en tiempo de compilación todo el código de IDFA, AdSupport y AppTrackingTransparency. Esto requiere **Xcode 26** o posterior. ::: En SDK v4, usa el paquete `adapty_flutter_kids` en lugar de `adapty_flutter` en tu `pubspec.yaml`. Es una variante del plugin con Modo Niños activado, la misma API pública y la misma versión — la única diferencia es que su SDK nativo de iOS se compila con el trait `KidsMode`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter_kids: 4.0.0 ``` Tu código Dart permanece igual — solo actualiza el import con el nuevo nombre del paquete: ```dart showLineNumbers title="Dart" ``` **iOS: Activar el modo Kids con CocoaPods (SDK v3)** 1. Actualiza tu Podfile: - Si **no** tienes una sección `post_install`, añade el bloque de código completo que aparece a continuación. - Si **ya** tienes una sección `post_install`, incorpora las líneas resaltadas en ella. ```ruby showLineNumbers title="Podfile" def adapty_enable_kids_mode(installer) installer.pods_project.targets.each do |target| next unless target.name == 'Adapty' target.build_configurations.each do |config| flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)' flags = flags.join(' ') if flags.is_a?(Array) config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} -DADAPTY_KIDS_MODE" end target.frameworks_build_phase.files.dup.each do |bf| target.frameworks_build_phase.remove_build_file(bf) if bf.display_name.to_s.include?('AdSupport') end end installer.pods_project.save Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xc| File.write(xc, File.read(xc).gsub(/\s*-framework\s+"?AdSupport"?/, '')) end end post_install do |installer| # ... keep your existing post_install body (Flutter adds one automatically) ... adapty_enable_kids_mode(installer) # <-- enable Adapty Kids Mode end ``` 2. Aplica los cambios ejecutando ```sh showLineNumbers title="Shell" pod install ``` --- # File: flutter-get-onboardings --- --- title: "Obtener onboardings en el SDK de Flutter" description: "Aprende cómo recuperar onboardings en Adapty para Flutter." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](flutter-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, un aspecto nativo consistente, tiempos de carga más rápidos y sin dependencia del entorno WebView. Consulta [Obtener flows y paywalls](flutter-get-pb-paywalls) y [Mostrar flows y paywalls](flutter-present-paywalls) para empezar. ::: Después de [diseñar la parte visual de tu onboarding](design-onboarding) con el builder en el Adapty Dashboard, puedes mostrarlo en tu app de Flutter. El primer paso en este proceso es obtener el onboarding asociado con el placement y su configuración de vista, tal como se describe a continuación. Antes de empezar, asegúrate de: 1. Haber instalado el [SDK de Adapty para Flutter](sdk-installation-flutter) en su versión 3.8.0 o superior. 2. Haber [creado un onboarding](create-onboarding). 3. Haber añadido el onboarding a un [placement](placements). ## Obtener el onboarding \{#fetch-onboarding\} Cuando creas un [onboarding](onboardings) con nuestro editor sin código, se almacena como un contenedor con la configuración que tu app necesita obtener y mostrar. Este contenedor gestiona toda la experiencia: qué contenido aparece, cómo se presenta y cómo se procesan las interacciones del usuario (como respuestas a cuestionarios o entradas de formularios). El contenedor también registra automáticamente los eventos de analítica, por lo que no necesitas implementar un seguimiento de vistas por separado. Para un mejor rendimiento, obtén la configuración del onboarding con antelación para que las imágenes tengan tiempo suficiente de descargarse antes de mostrárselas a los usuarios. Para obtener un onboarding, utiliza el método `getOnboarding`: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboarding(placementId: "YOUR_PLACEMENT_ID"); } on AdaptyError catch (e) { //handle error } catch (e) { //handle error } ``` A continuación, llama al método `createOnboardingView` para obtener la vista que mostrarás. :::warning El resultado del método `createOnboardingView` solo puede usarse una vez. Si necesitas usarlo de nuevo, llama al método `createOnboardingView` nuevamente. Llamarlo dos veces sin recrearlo puede provocar el error `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers try { final onboardingView = await Adapty().createOnboardingView(onboarding: onboarding); } on AdaptyError catch (e) { //handle error } catch (e) { //handle error } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché de actualización periódica descrita anteriormente y los onboardings de respaldo. También usamos CDN para cargar los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus onboardings, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede agotar el tiempo ligeramente después de lo especificado en `loadTimeout`, ya que la operación puede consistir en diferentes peticiones internamente.</p> | ## Parámetros de respuesta \{#response-parameters\} | Parámetro | Descripción | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Un objeto [`AdaptyOnboarding`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyOnboarding-class.html) con: el identificador y la configuración del onboarding, el Remote Config y otras propiedades. | ## Acelera la obtención del onboarding con el onboarding de audiencia predeterminada \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Normalmente, los onboardings se obtienen casi de forma instantánea, por lo que no necesitas preocuparte por optimizar este proceso. Sin embargo, cuando tienes numerosas audiencias y onboardings, y tus usuarios tienen una conexión a internet débil, obtener un onboarding puede tardar más de lo deseable. En esos casos, puede que quieras mostrar un onboarding predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ningún onboarding. Para solucionar esto, puedes usar el método `getOnboardingForDefaultAudience`, que obtiene el onboarding del placement especificado para la audiencia **All Users**. Sin embargo, es importante entender que el enfoque recomendado es obtener el onboarding mediante el método `getOnboarding`, tal como se detalla en la sección [Obtener el onboarding](#fetch-onboarding) anterior. :::warning Considera usar `getOnboarding` en lugar de `getOnboardingForDefaultAudience`, ya que este último tiene limitaciones importantes: - **Problemas de compatibilidad**: Puede ocasionar problemas al dar soporte a múltiples versiones de la app, lo que requiere diseños retrocompatibles o asumir que las versiones antiguas podrían mostrarse incorrectamente. - **Sin personalización**: Solo muestra contenido para la audiencia "All Users", eliminando la segmentación por país, atribución o atributos personalizados. Si la mayor velocidad de carga compensa estos inconvenientes para tu caso de uso, utiliza `getOnboardingForDefaultAudience` como se muestra a continuación. De lo contrario, usa `getOnboarding` tal como se describe [anteriormente](#fetch-onboarding). ::: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboardingForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` Parámetros: | Parámetro | Presencia | Descripción | |-----------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En ese caso, puede que los usuarios no reciban los datos más recientes, pero tendrán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se borra cuando se desinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché de actualización periódica descrita anteriormente y los onboardings de respaldo. También utilizamos CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus onboardings, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | --- # File: flutter-present-onboardings --- --- title: "Presentar onboardings en el SDK de Flutter" description: "Aprende cómo presentar onboardings de forma efectiva para aumentar las conversiones." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](flutter-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](flutter-get-pb-paywalls) y [Mostrar flows y paywalls](flutter-present-paywalls) para empezar. ::: Si has personalizado un onboarding con el builder, no necesitas preocuparte por renderizarlo en el código de tu app Flutter para mostrárselo al usuario. Ese onboarding contiene tanto lo que debe mostrarse como la forma en que debe mostrarse. Antes de empezar, asegúrate de que: 1. Has instalado [Adapty Flutter SDK](sdk-installation-flutter) 3.8.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). El SDK de Adapty para Flutter ofrece dos formas de presentar onboardings: - **Pantalla independiente** - **Widget embebido** ## Mostrar como pantalla independiente \{#present-as-standalone-screen\} Para mostrar un onboarding como pantalla independiente, usa el método `onboardingView.present()` en el `onboardingView` creado por el método `createOnboardingView`. Cada `view` solo puede usarse una vez. Si necesitas mostrar el onboarding de nuevo, llama a `createOnboardingView` otra vez para crear una nueva instancia de `onboardingView`. :::warning Reutilizar el mismo `onboardingView` sin recrearlo puede provocar un error `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers title="Flutter" try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Cerrar el onboarding \{#dismiss-the-onboarding\} Cuando necesites cerrar el onboarding por código, usa el método `dismiss()`: ```dart showLineNumbers title="Flutter" try { await onboardingView.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el onboarding en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `AdaptyUIIOSPresentationStyle.fullScreen` (predeterminado) o `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await onboardingView.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Integrar en la jerarquía de widgets \{#embed-in-widget-hierarchy\} Para integrar un onboarding dentro de tu árbol de widgets existente, usa el widget `AdaptyUIOnboardingPlatformView` directamente en tu jerarquía de widgets de Flutter. ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, // The onboarding object you fetched onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` :::note Para que la platform view de Android funcione, asegúrate de que tu `MainActivity` extienda `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: ## Cargador durante el onboarding \{#loader-during-onboarding\} Al presentar un onboarding, puede que notes una breve pantalla de carga entre tu pantalla de presentación y el onboarding mientras se inicializa la vista subyacente. Puedes gestionar esto de distintas formas según tus necesidades. #### Controla la pantalla de presentación con onDidFinishLoading \{#control-splash-screen-using-ondidfinishloading\} :::note Este enfoque solo está disponible al incrustar el onboarding como widget. No está disponible para la presentación en pantalla independiente. ::: El enfoque multiplataforma recomendado es mantener visible la pantalla de carga o superposición personalizada hasta que el onboarding esté completamente cargado, y luego ocultarla manualmente. Al usar el widget embebido, superpón tu propio widget sobre él y ocúltalo cuando se dispare `onDidFinishLoading`: ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Hide your custom splash screen or overlay here }, // ... other callbacks ) ``` ### Personalizar el loader nativo \{#customize-native-loader\} :::important Este enfoque es específico de plataforma y requiere mantener código de UI nativo. No se recomienda a menos que ya mantengas capas nativas separadas en tu app. ::: Si necesitas personalizar el loader predeterminado, puedes reemplazarlo con layouts específicos de cada plataforma. Este enfoque requiere implementaciones separadas para Android e iOS: - **iOS**: Añade `AdaptyOnboardingPlaceholderView.xib` a tu proyecto de Xcode - **Android**: Crea `adapty_onboarding_placeholder_view.xml` en `res/layout` y define ahí un placeholder ## Personalizar cómo se abren los enlaces en los onboardings \{#customize-how-links-open-in-onboardings\} :::important La personalización de cómo se abren los enlaces en los onboardings es compatible a partir de Adapty SDK v3.15.1. ::: Por defecto, los enlaces en los onboardings se abren en un navegador integrado en la app. Esto ofrece una experiencia fluida al mostrar las páginas web dentro de tu aplicación, sin que el usuario tenga que cambiar de app. Si prefieres abrir los enlaces en un navegador externo, puedes personalizar este comportamiento estableciendo el parámetro `externalUrlsPresentation` en `AdaptyWebPresentation.externalBrowser`: <Tabs> <TabItem value="standalone" label="Pantalla independiente" default> ```dart showLineNumbers title="Flutter" final onboardingView = await AdaptyUI().createOnboardingView( onboarding: onboarding, externalUrlsPresentation: AdaptyWebPresentation.externalBrowser, // default – AdaptyWebPresentation.inAppBrowser ); try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="embedded" label="Widget embebido"> ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, externalUrlsPresentation: AdaptyWebPresentation.externalBrowser, // default – AdaptyWebPresentation.inAppBrowser onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` </TabItem> </Tabs> ## Desactivar los márgenes de área segura (Android) \{#disable-safe-area-paddings-android\} Por defecto, en dispositivos Android, la vista de onboarding aplica automáticamente márgenes de área segura para evitar elementos de la interfaz del sistema como la barra de estado y la barra de navegación. Sin embargo, si quieres desactivar este comportamiento y tener control total sobre el diseño, puedes hacerlo añadiendo un recurso booleano a tu app: 1. Ve a `android/app/src/main/res/values`. Si no existe el archivo `bools.xml`, créalo. 2. Añade el siguiente recurso: ```xml <resources> <bool name="adapty_onboarding_enable_safe_area_paddings">false</bool> </resources> ``` Ten en cuenta que los cambios se aplican globalmente a todos los onboardings de tu app. --- # File: flutter-handling-onboarding-events --- --- title: "Gestionar eventos de onboarding en Flutter SDK" description: "Gestiona los eventos relacionados con onboarding en Flutter usando Adapty." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](flutter-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, un aspecto y comportamiento nativos consistentes, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](flutter-get-pb-paywalls) y [Mostrar flows y paywalls](flutter-present-paywalls) para empezar. ::: Los onboardings configurados con el builder generan eventos a los que tu app puede responder. La forma de gestionar estos eventos depende del enfoque de presentación que estés usando: - **Presentación a pantalla completa**: Requiere configurar un observador de eventos global que gestione los eventos de todas las vistas de onboarding - **Widget embebido**: Gestiona los eventos a través de parámetros de callback inline directamente en el widget Antes de empezar, asegúrate de que: 1. Has instalado el [SDK de Flutter de Adapty](sdk-installation-flutter) 3.8.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). ## Eventos de presentación en pantalla completa \{#full-screen-presentation-events\} ### Configurar el observador de eventos \{#set-up-event-observer\} Para gestionar eventos de onboardings en pantalla completa, implementa `AdaptyUIOnboardingsEventsObserver` y configúralo antes de presentarlo: ```dart showLineNumbers title="Flutter" AdaptyUI().setOnboardingsEventsObserver(this); try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Gestionar eventos \{#handle-events\} Implementa estos métodos en tu observador: ```dart showLineNumbers title="Flutter" void onboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, ) { // Onboarding finished loading } void onboardingViewDidFailWithError( AdaptyUIOnboardingView view, AdaptyError error, ) { // Handle loading errors } void onboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Handle close action view.dismiss(); } void onboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Dismiss onboarding before presenting paywall view.dismiss().then((_) { _openPaywall(actionId); }); } void onboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Handle custom actions } void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Handle user input updates } void onboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent event, ) { // Track analytics events } ``` ## Eventos del widget integrado \{#embedded-widget-events\} Al usar `AdaptyUIOnboardingPlatformView`, puedes gestionar eventos a través de parámetros de callback inline directamente en el widget. Ten en cuenta que los eventos se enviarán tanto a los callbacks del widget como al observador global (si está configurado), pero el observador global es opcional: ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Onboarding finished loading }, onDidFailWithError: (error) { // Handle loading errors }, onCloseAction: (meta, actionId) { // Handle close action }, onPaywallAction: (meta, actionId) { _openPaywall(actionId); }, onCustomAction: (meta, actionId) { // Handle custom actions }, onStateUpdatedAction: (meta, elementId, params) { // Handle user input updates }, onAnalyticsEvent: (meta, event) { // Track analytics events }, ) ``` ## Tipos de eventos \{#event-types\} Las siguientes secciones describen los distintos tipos de eventos que puedes gestionar, independientemente del enfoque de presentación que estés usando. ### Gestionar acciones personalizadas \{#handle-custom-actions\} En el editor, puedes añadir una acción **personalizada** a un botón y asignarle un ID. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Luego, puedes usar este ID en tu código y gestionarlo como una acción personalizada. Por ejemplo, si el usuario pulsa un botón personalizado, como **Login** o **Allow notifications**, se activará el método delegado `onboardingController` con el caso `.custom(id:)` y el parámetro `actionId` corresponderá al **Action ID** definido en el builder. Puedes crear tus propios IDs, como "allowNotifications". ```dart // Full-screen presentation void onboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { switch (actionId) { case 'login': _login(); break; case 'allow_notifications': _allowNotifications(); break; } } // Embedded widget onCustomAction: (meta, actionId) { _handleCustomAction(actionId); } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ### Finalización de la carga del onboarding \{#finishing-loading-onboarding\} Cuando un onboarding termina de cargarse, se activa este evento: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, ) { print('Onboarding loaded: ${meta.onboardingId}'); } // Embedded widget onDidFinishLoading: (meta) { print('Onboarding loaded: ${meta.onboardingId}'); } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ### Cierre del onboarding \{#closing-onboarding\} El onboarding se considera cerrado cuando el usuario pulsa un botón con la acción **Close** asignada. <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Ten en cuenta que debes gestionar qué ocurre cuando el usuario cierra el onboarding. Por ejemplo, debes dejar de mostrar el propio onboarding. ::: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { await view.dismiss(); } // Embedded widget onCloseAction: (meta, actionId) { Navigator.of(context).pop(); } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ### Abrir un paywall \{#opening-a-paywall\} :::tip Maneja este evento para abrir un paywall si quieres abrirlo dentro del onboarding. Si quieres abrir un paywall después de cerrarlo, hay una forma más directa de hacerlo: maneja la acción de cierre y abre un paywall sin depender de los datos del evento. ::: La forma más fluida de trabajar con paywalls en onboardings es hacer que el ID de acción sea igual al ID de placement del paywall: Ten en cuenta que, en iOS, solo se puede mostrar una vista (paywall u onboarding) en pantalla a la vez. Si presentas un paywall encima de un onboarding, no podrás controlar el onboarding en segundo plano de forma programática. Si intentas cerrar el onboarding, se cerrará el paywall en su lugar y el onboarding quedará visible. Para evitarlo, cierra siempre la vista del onboarding antes de presentar el paywall. ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Dismiss onboarding before presenting paywall view.dismiss().then((_) { _openPaywall(actionId); }); } Future<void> _openPaywall(String actionId) async { // Implement your paywall opening logic here } // Embedded widget onPaywallAction: (meta, actionId) { _openPaywall(actionId); } ``` <Details> <summary>Ejemplo de evento (Clic para expandir)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ### Rastreo de navegación \{#tracking-navigation\} Recibes un evento de análisis cuando ocurren varios eventos relacionados con la navegación durante el flow de onboarding: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent event, ) { trackEvent(event.type, meta.onboardingId); } // Embedded widget onAnalyticsEvent: (meta, event) { trackEvent(event.type, meta.onboardingId); } ``` El objeto `event` puede ser uno de los siguientes tipos: | Tipo | Descripción | |------------|-------------| | `onboardingStarted` | Cuando el onboarding se ha cargado | | `screenPresented` | Cuando se muestra cualquier pantalla | | `screenCompleted` | Cuando se completa una pantalla. Incluye `elementId` opcional (identificador del elemento completado) y `reply` opcional (respuesta del usuario). Se activa cuando el usuario realiza cualquier acción para salir de la pantalla. | | `secondScreenPresented` | Cuando se muestra la segunda pantalla | | `userEmailCollected` | Se activa cuando se recoge el correo electrónico del usuario mediante el campo de entrada | | `onboardingCompleted` | Se activa cuando un usuario llega a una pantalla con el ID `final`. Si necesitas este evento, [asigna el ID `final` a la última pantalla](design-onboarding). | | `unknown` | Para cualquier tipo de evento no reconocido. Incluye `name` (el nombre del evento desconocido) y `meta` (metadatos adicionales) | Cada evento incluye información `meta` que contiene: | Campo | Descripción | |------------|-------------| | `onboardingId` | Identificador único del flow de onboarding | | `screenClientId` | Identificador de la pantalla actual | | `screenIndex` | Posición de la pantalla actual en el flow | | `screensTotal` | Número total de pantallas en el flow | <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: flutter-onboarding-input --- --- title: "Procesar datos de onboardings en Flutter SDK" description: "Guarda y utiliza datos de onboardings en tu app de Flutter con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](flutter-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo — lo que ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](flutter-get-pb-paywalls) y [Mostrar flows y paywalls](flutter-present-paywalls) para empezar. ::: Cuando tus usuarios responden a una pregunta de quiz o introducen sus datos en un campo de entrada, se invocará el método `onStateUpdatedAction`. Puedes guardar o procesar el tipo de campo en tu código. Por ejemplo: ```dart // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Process data } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Process data } ``` Consulta el formato de acción [aquí](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyUIOnboardingPlatformView/onStateUpdatedAction.html). <Details> <summary>Formas de las propiedades para cada tipo de parámetros (haz clic para expandir)</summary> ```dart void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // elementId is a String: elementId; // 'preference_selector' // meta — AdaptyUIOnboardingMeta: meta.onboardingId; // 'onboarding_123' meta.screenClientId; // 'preferences_screen' meta.screenIndex; // 1 meta.screensTotal; // 3 // params is one of the AdaptyOnboardingsStateUpdatedParams subclasses: switch (params) { case AdaptyOnboardingsSelectParams(:final id, :final value, :final label): // a single selected option id; // 'option_1' value; // 'premium' label; // 'Premium Plan' break; case AdaptyOnboardingsMultiSelectParams(:final params): // a list of selected options, each an AdaptyOnboardingsSelectParams params; // [(id: 'interest_1', value: 'sports', label: 'Sports'), (id: 'interest_2', value: 'music', label: 'Music')] break; case AdaptyOnboardingsInputParams(:final input): switch (input) { case AdaptyOnboardingsTextInput(:final value): value; // 'John Doe' break; case AdaptyOnboardingsEmailInput(:final value): value; // 'user@example.com' break; case AdaptyOnboardingsNumberInput(:final value): value; // 25.0 (a double) break; } break; case AdaptyOnboardingsDatePickerParams(:final day, :final month, :final year): day; // 15 month; // 6 year; // 1990 break; } } ``` </Details> ## Casos de uso \{#use-cases\} ### Enriquecer perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Si quieres vincular directamente los datos de entrada con el perfil del usuario y evitar pedirle la misma información dos veces, necesitas [actualizar el perfil del usuario](flutter-setting-user-attributes) con los datos de entrada al gestionar la acción. Por ejemplo, le pides al usuario que introduzca su nombre en el campo de texto con el ID `name`, y quieres establecer el valor de ese campo como el nombre del usuario. También le pides que introduzca su correo electrónico en el campo `email`. En el código de tu app, puede quedar así: ```dart showLineNumbers // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Store user preferences or responses if (params is AdaptyOnboardingsInputParams) { final builder = AdaptyProfileParametersBuilder(); // Map elementId to appropriate profile field switch (elementId) { case 'name': if (params.input is AdaptyOnboardingsTextInput) { builder.setFirstName((params.input as AdaptyOnboardingsTextInput).value); } break; case 'email': if (params.input is AdaptyOnboardingsEmailInput) { builder.setEmail((params.input as AdaptyOnboardingsEmailInput).value); } break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Store user preferences or responses if (params is AdaptyOnboardingsInputParams) { final builder = AdaptyProfileParametersBuilder(); // Map elementId to appropriate profile field switch (elementId) { case 'name': if (params.input is AdaptyOnboardingsTextInput) { builder.setFirstName((params.input as AdaptyOnboardingsTextInput).value); } break; case 'email': if (params.input is AdaptyOnboardingsEmailInput) { builder.setEmail((params.input as AdaptyOnboardingsEmailInput).value); } break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } ``` ### Personalizar paywalls en función de las respuestas \{#customize-paywalls-based-on-answers\} Usando cuestionarios en los onboardings, también puedes personalizar los paywalls que muestras a los usuarios una vez que completan el onboarding. Por ejemplo, puedes preguntarles sobre su experiencia con el deporte y mostrar diferentes CTAs y productos a distintos grupos de usuarios. 1. [Añade un cuestionario](onboarding-quizzes) en el editor de onboarding y asigna IDs significativos a sus opciones. 2. Gestiona las respuestas del cuestionario según sus IDs y [establece atributos personalizados](flutter-setting-user-attributes) para los usuarios. ```dart showLineNumbers // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Handle quiz responses and set custom attributes if (params is AdaptyOnboardingsSelectParams) { final builder = AdaptyProfileParametersBuilder(); // Map quiz responses to custom attributes switch (elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.setCustomStringAttribute(params.value, 'experience'); break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Handle quiz responses and set custom attributes if (params is AdaptyOnboardingsSelectParams) { final builder = AdaptyProfileParametersBuilder(); // Map quiz responses to custom attributes switch (elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.setCustomStringAttribute(params.value, 'experience'); break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } ``` 3. [Crea segmentos](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade [audiencias](audience) para cada segmento que hayas creado. 5. [Muestra un paywall](flutter-paywalls) para el placement en el código de tu app. Si tu onboarding tiene un botón que abre un paywall, implementa el código del paywall como [respuesta a la acción de ese botón](flutter-handling-onboarding-events#opening-a-paywall). --- # File: flutter-sdk-call-order --- --- title: "Orden de llamadas en Flutter SDK" description: "Evita perder acceso premium, datos de atribución o errores intermitentes #2002 llamando a los métodos del SDK de Adapty en el orden correcto." --- `Adapty().activate()` debe completarse antes de llamar a cualquier otro método del SDK de Adapty. Hasta que se resuelva, el SDK no tiene estado. Cualquier llamada realizada antes o en paralelo con `activate()` fallará con [`#2002 notActivated`](error-handling-on-flutter-react-native-unity#custom-network-codes). Si tu app autentica usuarios y recopilas un customer user ID después del lanzamiento, llama a `Adapty().identify()` en ese momento. No llames a métodos de acción del usuario hasta que `identify` se resuelva. Las llamadas que compitan con él o bien fallan con [`#3006 profileWasChanged`](error-handling-on-flutter-react-native-unity#custom-network-codes), o recaen sobre el perfil anónimo creado en la activación. Cuando esto ocurre, la atribución, los IDs de MMP como `appsflyer_id` y la titularidad de la instalación no siempre se transfieren al perfil identificado. Si tu app no autentica usuarios, omite `identify` y sigue trabajando con el perfil anónimo. Los SDKs de MMP y analíticas (AppsFlyer, Adjust, Branch, PostHog) siguen la misma regla. Inicialízalos primero y espera sus callbacks de UID antes de llamar a `Adapty().activate`. De lo contrario, el ID del MMP se asigna a un perfil anónimo temporal y no siempre se transfiere al perfil identificado. Para más detalles sobre AppsFlyer, consulta [AppsFlyer](appsflyer). ## El orden correcto \{#the-correct-order\} Tu camino depende de dos cosas: cuándo conoces el customer user ID y si usas un MMP o SDK de analíticas. - **Pasos 2 y 5**: Obligatorios para todas las apps. Activa el SDK y luego llama a los métodos del SDK. - **Pasos 1 y 3**: Necesarios solo si integras un MMP o SDK de analíticas (AppsFlyer, Adjust, Branch, PostHog). - **Paso 4**: Necesario solo si tu app autentica usuarios y recoge el customer user ID después del lanzamiento. Si tienes el ID de usuario en el momento del lanzamiento de la app, pásalo directamente a `activate()` (paso 2a). Esta ruta nunca crea un perfil anónimo, por lo que el paso 4 no es necesario. | Paso | Llamada | Cuándo | Notas | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Inicializa tu MMP o SDK de analíticas (AppsFlyer, Adjust, PostHog, Branch) | Al arrancar la app, primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `Adapty().activate(configuration: ...)` con `withCustomerUserId` configurado en la configuración | Al arrancar la app, después del paso 1, si tienes el customer user ID | Recomendado. No se crea ningún perfil anónimo. | | 2b | `Adapty().activate(configuration: ...)` sin `withCustomerUserId` | Al arrancar la app, después del paso 1, si no tienes el customer user ID (o nunca lo recopilas) | Adapty crea un perfil anónimo. | | 3 | `Adapty().setIntegrationIdentifier(key: ..., value: ...)` para cada MMP | Después del paso 2, antes de cualquier llamada de acción del usuario | Necesario para que los IDs del MMP queden en el perfil correcto. | | 4 | `await Adapty().identify(customerUserId)` | Después del paso 3 (o el paso 2 si no hay MMP), antes del paso 5 — solo en la ruta 2b con autenticación | Usa siempre `await`. Las llamadas concurrentes durante `identify` producen `#3006 profileWasChanged`. | | 5 | `getPaywall` (`getFlow` en SDK v4), `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | Después del paso 4 si llamas a `identify`; si no, después del paso 3 (o el paso 2 si no hay MMP) | Estas llamadas necesitan un perfil estable. | :::important Omitir estos pasos provoca que los usuarios recurrentes pierdan el acceso premium, que falte el `appsflyer_id` en los perfiles y que se devuelvan paywalls para la audiencia incorrecta. ::: ## Instalaciones web2app y embudo web \{#web2app-and-web-funnel-installs\} Si los usuarios compran en un checkout web (Stripe, Paddle) e instalan después la app nativa, el primer `activate()` del dispositivo crea un nuevo perfil anónimo. Este perfil no está vinculado al perfil web. Si puedes resolver el customer user ID antes del lanzamiento de la app (desde tu flujo de autenticación o el referrer de instalación), pásalo directamente a `activate()`. De lo contrario, la compra web será invisible en el dispositivo hasta que llames a `identify("YOUR_USER_ID")` y luego a `restorePurchases`. Para los metadatos que debes enviar con cada checkout web, consulta: - [Stripe](stripe) - [Paddle](paddle) --- # File: flutter-optimize-paywall-fetching --- --- title: "Optimizar la obtención de paywalls en Flutter SDK" description: "Obtén paywalls de Adapty de forma fiable: timing, caché y patrones de respaldo para Flutter." --- Una obtención de paywall fiable en Flutter hace tres cosas: renderiza rápido, devuelve el paywall orientado a la audiencia y recurre al respaldo con elegancia cuando la red es lenta. Las reglas que se describen a continuación cubren el timing, la caché y los patrones de respaldo para conseguirlo. :::tip Las reglas asumen que `Adapty().activate()` y `Adapty().identify()` ya han sido resueltos. Consulta [Orden de llamadas en el SDK de Flutter](flutter-sdk-call-order). ::: Los consejos a continuación usan los nombres de métodos de la v3. En el SDK v4, `getPaywall` pasa a llamarse `getFlow` y el tipo de política de obtención a `AdaptyFlowFetchPolicy` — todas las reglas aplican sin cambios. ## Reglas y errores comunes \{#rules-and-pitfalls\} | Haz esto | No hagas esto | Por qué | |---|---|---| | Obtén el placement que vas a mostrar. | Precargues todos los placements de forma concurrente al inicio. | La precarga masiva bloquea el hilo principal y produce una pantalla en negro durante el pico. | | Llama a `getPaywall` después de que la atribución haya tenido tiempo de resolverse — por ejemplo, 1–2 segundos después de `activate` o tras dispararse `didUpdateProfileStream`. | Llames a `getPaywall` en `main()` antes de `runApp`. | La atribución aún no ha llegado. El paywall se resuelve contra la audiencia por defecto y omite en silencio los segmentos y la personalización de ASA. | | Establece un `loadTimeout` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | Esperes indefinidamente en `getPaywall`. | Sin un timeout, los usuarios con mala conectividad ven una pantalla en blanco hasta que se resuelve la red — o cierran la app. | Consulta [Obtener paywalls y productos](fetch-paywalls-and-products-flutter) para la referencia de los parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado. ## Ajuste para mala conectividad \{#tune-for-poor-connectivity\} Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte, regiones afectadas por enrutamiento): - Establece `fetchPolicy: AdaptyPaywallFetchPolicy.returnCacheDataElseLoad` en cada carga excepto la primera. - Configura un [paywall de respaldo](fallback-paywalls) para cada placement en el Adapty Dashboard. - Establece `loadTimeout` en 3–5 segundos y acepta el respaldo cuando se agote el tiempo. - No condicionales la visualización del paywall a `getProfile()`. Llama a `getPaywall` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: flutter-show-aa-targeted-paywall --- --- title: "Mostrar un paywall dirigido por Apple Ads en el primer lanzamiento en Flutter SDK" description: "Espera brevemente la atribución de Apple Ads antes de mostrar el paywall en el primer lanzamiento en Flutter, usando la audiencia predeterminada si se agota el tiempo. Usa AdaptyProfile.appliedAttributionSources." --- La atribución de Apple Ads (AA) llega de forma asíncrona después de `Adapty().activate()`. En el primer lanzamiento normalmente aún no ha llegado, por lo que si llamas a `getPaywall` de inmediato, Adapty resuelve la solicitud contra la audiencia predeterminada y los usuarios de Apple Ads se pierden tu paywall segmentado por AA. En lugar de mostrar un paywall y luego reemplazarlo, espera brevemente a que llegue la atribución de AA antes de mostrar nada: muestra el paywall segmentado si la atribución llega dentro de un breve tiempo de espera, o el paywall de la audiencia predeterminada si no llega. `AdaptyProfile.appliedAttributionSources` te indica cuándo se ha aplicado la atribución de AA. ## Antes de comenzar \{#before-you-start\} Necesitas: - Adapty Flutter SDK **3.17.0** o posterior. - Apple Ads configurado para la app en Adapty. Consulta [Apple Ads](apple-search-ads). ## Cómo funciona \{#how-it-works\} Tras ejecutar `Adapty().activate()`, el SDK solicita en segundo plano la atribución de Apple Ads a Apple y envía el resultado al backend de Adapty. Cuando AA se convierte en la fuente de atribución activa del perfil, el SDK entrega un `AdaptyProfile` actualizado al listener `didUpdateProfileStream`, con `AdaptyAttributionSource.appleAds` en su lista `appliedAttributionSources`. En el primer lanzamiento, esto te da dos resultados que gestionar: 1. **La atribución llega dentro del tiempo de espera.** Llama a `getPaywall` — Adapty resuelve la solicitud con la audiencia de Apple Ads y devuelve el paywall de destino. 2. **El tiempo de espera se agota primero.** Muestra en su lugar el paywall de la audiencia predeterminada, para que los usuarios sin atribución de Apple Ads no tengan que esperar. `getPaywallForDefaultAudience` lo devuelve sin esperar a la segmentación. `appliedAttributionSources` puede estar vacío. Eso significa que: - La atribución de Apple Ads aún no se ha procesado para este perfil, o - no ha llegado ninguna atribución. De cualquier modo, `getPaywallForDefaultAudience` es seguro de llamar; devuelve el paywall de la audiencia predeterminada independientemente del estado del perfil. :::important La espera solo se aplica al primer lanzamiento. Una vez que se ha registrado la atribución de Apple Ads, queda almacenada en el perfil de forma permanente. En cada lanzamiento posterior, el perfil en caché ya lleva `AdaptyAttributionSource.appleAds` en `appliedAttributionSources`, por lo que la ruta de atribución se resuelve de inmediato y `getPaywall` devuelve el paywall segmentado para Apple Ads sin ningún retraso. ::: ## Implementación \{#implementation\} En el primer lanzamiento, espera a `AdaptyAttributionSource.appleAds` y aplica un timeout estricto — si la atribución de Apple Ads nunca llega, esos usuarios igualmente necesitan ver un paywall. 1. **Activa el SDK.** Consulta [Instalar y configurar el SDK de Flutter](sdk-installation-flutter). 2. **Suscríbete a las actualizaciones del perfil** con `Adapty().didUpdateProfileStream.listen(…)`. Si aún no has configurado el listener, consulta [Escuchar actualizaciones de suscripción](flutter-check-subscription-status#listen-to-subscription-updates). 3. **Observa `AdaptyAttributionSource.appleAds` en `appliedAttributionSources`.** Cuando aparezca, carga el paywall con `getPaywall` — Adapty devuelve la variante segmentada por AA: ```dart final subscription = Adapty().didUpdateProfileStream.listen((profile) async { if (!profile.appliedAttributionSources.contains(AdaptyAttributionSource.appleAds)) return; final paywall = await Adapty().getPaywall(placementId: placementId); // presenta el paywall segmentado, luego cancela la suscripción y el temporizador }); ``` `didUpdateProfileStream` es un stream de difusión y no reproduce eventos anteriores, así que comprueba también el perfil actual una vez con `getProfile()`. En reinicios de la app, la atribución ya almacenada se aplica en el momento y no vuelve a emitirse. 4. **Inicia un temporizador de 3 a 5 segundos en paralelo con la suscripción.** Si el temporizador se activa antes de que aparezca `AdaptyAttributionSource.appleAds`, carga el paywall de la audiencia por defecto con `getPaywallForDefaultAudience`. Muestra el que se resuelva primero y cancela la otra ruta, para que el paywall no se obtenga dos veces. Configura un [paywall de respaldo](flutter-use-fallback-paywalls) para el placement, de modo que el usuario nunca se quede bloqueado si la solicitud de red falla. ## Ejemplo completo \{#complete-example\} La implementación a continuación compite la atribución contra un tiempo de espera, precarga en paralelo el paywall de la audiencia predeterminada y devuelve el paywall que corresponda. El llamador espera una única función — sin listeners ni indicadores de estado que gestionar en el punto de llamada: - Si la atribución llega antes del `timeout`, devuelve el paywall segmentado a través de `getPaywall`. - Si el `timeout` expira primero, devuelve el paywall precargado de la audiencia predeterminada a través de `getPaywallForDefaultAudience`. ```dart title="apple_ads_paywall.dart" /// Returns the Apple Ads-segmented paywall if attribution is applied within /// [timeout], otherwise the default-audience paywall. Call after Adapty().activate(). Future<AdaptyPaywall> getPaywallOrDefault({ required String placementId, required Duration timeout, }) { // Prefetch the default-audience paywall right away so the timeout path resolves // without an extra network round-trip. `getPaywallForDefaultAudience` skips the // wait for segmentation data. `..ignore()` keeps an unused prefetch from surfacing // as an unhandled error; the error still reaches the caller if this paywall wins. final defaultPaywall = Adapty().getPaywallForDefaultAudience(placementId: placementId)..ignore(); final completer = Completer<AdaptyPaywall>(); late final StreamSubscription<AdaptyProfile> subscription; late final Timer timer; void resolve(Future<AdaptyPaywall> paywall) { if (completer.isCompleted) return; timer.cancel(); subscription.cancel(); completer.complete(paywall); } void onProfile(AdaptyProfile profile) { if (profile.appliedAttributionSources.contains(AdaptyAttributionSource.appleAds)) { resolve(Adapty().getPaywall(placementId: placementId)); } } // Attribution path: react to profile updates as attribution is applied. subscription = Adapty().didUpdateProfileStream.listen(onProfile); // The stream is a broadcast stream and doesn't replay, so check the current // profile too — on relaunches attribution is already stored and won't re-emit. Adapty().getProfile().then(onProfile).ignore(); // Timeout path: fall back to the prefetched default-audience paywall. timer = Timer(timeout, () => resolve(defaultPaywall)); return completer.future; } ``` Llama esto desde tu splash screen y luego muestra el paywall cuando se resuelva: ```dart try { final paywall = await getPaywallOrDefault( placementId: 'YOUR_PLACEMENT_ID', timeout: const Duration(seconds: 5), ); // present the paywall } on AdaptyError catch (adaptyError) { // handle the error or show a fallback paywall } catch (e) { // handle the error } ``` Ajusta `timeout` según el tiempo que estés dispuesto a hacer esperar a los usuarios antes de que aparezca cualquier paywall. La mayoría de los usuarios no tienen atribución de Apple Ads, así que esperan el timeout completo: entre 3 y 5 segundos es un equilibrio razonable. La atribución, cuando llega, suele hacerlo en pocos segundos tras el lanzamiento. Si tu app ya escucha `didUpdateProfileStream` para otros fines (por ejemplo, [comprobar el estado de la suscripción](flutter-check-subscription-status#listen-to-subscription-updates)), no necesitas modificarla. `didUpdateProfileStream` es un broadcast stream, por lo que admite múltiples listeners independientes sin que unos afecten a los otros. --- # File: flutter-test --- --- title: "Test & release in Flutter SDK" description: "Aprende cómo comprobar el estado de suscripción en tu app Flutter con Adapty." --- Si ya has implementado el SDK de Adapty en tu app Flutter, querrás comprobar que todo está configurado correctamente y que las compras funcionan como se espera en las plataformas iOS y Android. Esto implica probar tanto la integración del SDK como el flujo de compra real con el entorno sandbox de Apple y el entorno de pruebas de Google Play. ## Prueba tu app \{#test-your-app\} Para realizar pruebas exhaustivas de tus compras in-app, consulta nuestras guías de pruebas por plataforma: [guía de pruebas para iOS](test-purchases-in-sandbox) y [guía de pruebas para Android](testing-on-android). ## Prepárate para el lanzamiento \{#prepare-for-release\} Antes de enviar tu app al store, sigue la [lista de verificación para el lanzamiento](release-checklist) para confirmar que: - La conexión con el store y las notificaciones del servidor están configuradas - Las compras se completan y se notifican a Adapty - El acceso se desbloquea y se restaura correctamente - Se cumplen los requisitos de privacidad y revisión --- # File: InvalidProductIdentifiers-flutter --- --- title: "Solución para el error Code-1000 noProductIDsFound en Flutter SDK" description: "Resuelve errores de identificador de producto inválido al gestionar suscripciones en Adapty." --- El error con código 1000, `noProductIDsFound`, indica que ninguno de los productos que solicitaste en el paywall está disponible para su compra en la App Store, aunque estén listados allí. Este error puede aparecer en ocasiones acompañado de una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin un error, puedes ignorarla sin problema. Si encuentras el error `noProductIDsFound`, sigue estos pasos para resolverlo: ## Paso 1. Comprueba el Bundle ID \{#step-2-check-bundle-id\} 1. Abre [App Store Connect](https://appstoreconnect.apple.com/apps). Selecciona tu app y ve a la sección **General** → **App Information**. 2. Copia el **Bundle ID** en la subsección **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Abre la pestaña [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) desde el menú superior de Adapty y pega el valor copiado en el campo **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Vuelve a la página **App information** en App Store Connect y copia el **Apple ID** desde allí. 5. En la página [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) del Adapty Dashboard, pega el ID en el campo **Apple app ID**. ## Paso 2. Comprueba los productos \{#step-3-check-products\} 1. Ve a **App Store Connect** y navega a [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) en el menú de la izquierda. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripciones. Verás tus productos listados en la sección **Subscriptions**. 3. Asegúrate de que el producto que estás probando esté marcado como **Ready to Submit**. <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Compara el ID del producto de la tabla con el que aparece en la pestaña [**Products**](https://app.adapty.io/products) del Adapty Dashboard. Si los IDs no coinciden, copia el ID del producto de la tabla y [crea un producto](create-product) con ese ID en el Adapty Dashboard. <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 3. Comprueba la disponibilidad del producto \{#step-4-check-product-availability\} 1. Vuelve a **App Store Connect** y abre la misma sección **Subscriptions**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripciones para ver tus productos. 3. Selecciona el producto que estás probando. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hasta la sección **Availability** y comprueba que todos los países y regiones requeridos estén listados. <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 4. Comprueba los precios del producto \{#step-5-check-product-prices\} 1. De nuevo, ve a la sección **Monetization** → **Subscriptions** en **App Store Connect**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripciones. 3. Selecciona el producto que estás probando. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hacia abajo hasta **Subscription Pricing** y despliega la sección **Current Pricing for New Subscribers**. <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Asegúrate de que todos los precios requeridos estén listados. <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 5. Comprueba que el estado de pago de la app, la cuenta bancaria y los formularios fiscales estén activos \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. En la página de inicio de [**App Store Connect**](https://appstoreconnect.apple.com/), haz clic en **Business**. <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Selecciona el nombre de tu empresa. <img src="/assets/shared/img/business-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Desplázate hacia abajo y comprueba que tu **Paid Apps Agreement**, tu **Bank Account** y tus **Tax forms** aparezcan como **Active**. <img src="/assets/shared/img/appstore-connect-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Siguiendo estos pasos, deberías poder resolver la advertencia `InvalidProductIdentifiers` y tener tus productos disponibles en la store. ## Paso 6. Recrea el producto si está bloqueado \{#step-6-recreate-the-product-if-its-stuck\} Es posible que los pasos 1–5 pasen todos correctamente —estado `Approved`, Bundle ID coincidente, API key válida— y aun así el SDK devuelva `1000 noProductIDsFound`. En ese caso, puede que el producto esté bloqueado en el registro de Apple. El registro de productos de Apple entra ocasionalmente en un estado en el que un producto existe en la interfaz de App Store Connect pero no está expuesto en la ruta de búsqueda de StoreKit. Elimina el producto en App Store Connect y vuelve a crearlo con el mismo ID de producto. Espera hasta 24 horas tras la recreación para que los cambios se propaguen. --- # File: cantMakePayments-flutter --- --- title: "Solución al error Code-1003 cantMakePayment en Flutter SDK" description: "Resuelve el error de realización de pagos al gestionar suscripciones en Adapty." --- El error 1003, `cantMakePayments`, indica que no es posible realizar compras in-app en este dispositivo. Si encuentras el error `cantMakePayments`, normalmente se debe a una de estas razones: - Restricciones del dispositivo: El error no está relacionado con Adapty. Consulta las soluciones más abajo. - Configuración del modo Observer: El método `makePurchase` y el modo Observer no pueden usarse al mismo tiempo. Consulta la sección más abajo. ## Problema: Restricciones del dispositivo \{#issue-device-restrictions\} | Problema | Solución | |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| | Restricciones de Screen Time | Desactiva las restricciones de compras in-app en [Screen Time](https://support.apple.com/en-us/102470) | | Cuenta suspendida | Contacta con el soporte de Apple para resolver problemas con la cuenta | | Restricciones regionales | Usa una cuenta de App Store de una región compatible | ## Problema: Usar el modo Observer y makePurchase a la vez \{#issue-using-both-observer-mode-and-makepurchase\} Si usas `makePurchases` para gestionar las compras, no necesitas el modo Observer. El [modo Observer](observer-vs-full-mode) solo es necesario si implementas la lógica de compra tú mismo. Por lo tanto, si usas `makePurchase`, puedes eliminar sin problema la activación del modo Observer del código de inicialización del SDK. --- # File: migration-to-flutter-sdk-v4 --- --- title: "Migrar Adapty Flutter SDK a v. 4.0" description: "Migra al Adapty Flutter SDK v4.0 reemplazando las APIs de paywall con APIs de flow, compatibles tanto con Flow Builder como con Paywall Builder." --- Adapty Flutter SDK 4.0 introduce flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan tanto con el nuevo Flow Builder como con el Paywall Builder existente — no se requieren cambios de configuración en el Adapty Dashboard. ## Referencia rápida \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty().getPaywall(placementId: id)` | `Adapty().getFlow(placementId: id)` | | `Adapty().getPaywallForDefaultAudience(placementId: id)` | `Adapty().getFlowForDefaultAudience(placementId: id)` | | `Adapty().getPaywallProducts(paywall: paywall)` | `Adapty().getPaywallProducts(flow: flow)` | | `Adapty().logShowPaywall(paywall: paywall)` | `Adapty().logShowFlow(flow: flow)` | | `AdaptyPaywall` (tipo) | `AdaptyFlow` | | `AdaptyPaywallFetchPolicy` (tipo) | `AdaptyFlowFetchPolicy` | | `AdaptyUI().createPaywallView(paywall: paywall)` | `AdaptyUI().createFlowView(flow: flow)` | | `AdaptyUIPaywallView` (tipo) | `AdaptyUIFlowView` | | `AdaptyUIPaywallPlatformView` (widget) | `AdaptyUIFlowPlatformView` | | `AdaptyUI().presentPaywallView(view)` / `dismissPaywallView(view)` | `AdaptyUI().presentFlowView(view)` / `dismissFlowView(view)` | | `AdaptyUIPaywallsEventsObserver` | `AdaptyUIFlowsEventsObserver` | | `AdaptyUI().setPaywallsEventsObserver(observer)` | `AdaptyUI().setFlowsEventsObserver(observer)` | | callbacks `paywallViewDid*` | callbacks `flowViewDid*` | | `paywallViewDidFailRendering` | `flowViewDidReceiveError` | `AdaptyPaywallProduct` mantiene su nombre — los productos siguen perteneciendo a un flow, y `getPaywallProducts` ahora recibe un `AdaptyFlow`. Ya no se pasa un `locale` al recuperar un flow. Las APIs de compra y perfil (`makePurchase`, `restorePurchases`, `getProfile`, `identify`, etc.) no han cambiado, y tampoco los métodos de vista `present`, `dismiss` y `showDialog`. Algunos comportamientos por defecto han cambiado — consulta [Cambios en el comportamiento por defecto](#default-behavior-changes). ## Versiones mínimas \{#minimum-versions\} El SDK de Adapty para Flutter 4.0 eleva los requisitos mínimos: - **iOS 15.0** — el objetivo de despliegue mínimo para iOS, aumentado desde iOS 13.0. - **Xcode 26** o superior — el SDK nativo de iOS usa Swift tools 6.2. - **Flutter 3.32.0** (Dart 3.8.0) o superior. ## Instalación \{#installation\} ### Actualizar el paquete \{#update-the-package\} El paquete que instales depende de si tu app usa el Modo Infantil. Para la mayoría de las apps, actualiza `adapty_flutter` a la v4.0 en tu `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: 4.0.0 ``` Si tu app usa el Modo Infantil, especifica `adapty_flutter_kids` en su lugar: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter_kids: 4.0.0 ``` Este paquete **autónomo** elimina el código de IDFA y seguimiento de anuncios para cumplir con los requisitos del App Store. Actualiza la ruta de importación de Dart a `package:adapty_flutter_kids/adapty_flutter.dart`. Por lo demás, la migración es exactamente igual que la del paquete normal. El modo Kids también requiere que desactives la recopilación de direcciones IP en el Adapty Dashboard — consulta [Kids Mode](kids-mode-flutter) para ver la configuración completa. ### iOS: los SDKs nativos ahora se distribuyen a través de Swift Package Manager [El repositorio de specs de CocoaPods pasará a ser de solo lectura en diciembre de 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/), por lo que a partir de la v4 el SDK nativo de iOS **ya no se distribuye a través de CocoaPods** — el plugin lo obtiene únicamente a través de **Swift Package Manager**. Si usas Flutter 3.32–3.43, activa el soporte de Swift Package Manager una sola vez: ```bash flutter config --enable-swift-package-manager ``` Flutter 3.44 y versiones posteriores activan Swift Package Manager por defecto, así que no es necesario hacer nada en ese caso. ## Obtener flows \{#fetching-flows\} ### getPaywall → getFlow El tipo devuelto cambia de `AdaptyPaywall` a `AdaptyFlow`, y ya no se pasa un `locale` — cuando renderizas un flow, la localización se resuelve automáticamente; para paywalls personalizados, todos los idiomas configurados se devuelven en `flow.remoteConfigs`: ```diff showLineNumbers - final paywall = await Adapty().getPaywall(placementId: 'YOUR_PLACEMENT_ID', locale: 'en'); + final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); ``` `getPaywallForDefaultAudience` se renombra de la misma manera: ```diff showLineNumbers - final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID', locale: 'en'); + final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); ``` El tipo de política de obtención cambia su nombre de `AdaptyPaywallFetchPolicy` a `AdaptyFlowFetchPolicy`; sus opciones (`reloadRevalidatingCacheData`, `returnCacheDataElseLoad`, `returnCacheDataIfNotExpiredElseLoad`) no cambian. ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` mantiene su nombre pero ahora recibe un `AdaptyFlow` mediante el parámetro `flow`: ```diff showLineNumbers - final products = await Adapty().getPaywallProducts(paywall: paywall); + final products = await Adapty().getPaywallProducts(flow: flow); ``` ## Modelo de datos \{#data-model\} `getFlow` devuelve un `AdaptyFlow` en lugar de un `AdaptyPaywall`, y la estructura del objeto ha cambiado: | Miembro de `AdaptyPaywall` v3 | Miembro de `AdaptyFlow` v4 | Acción | |---|---|---| | `remoteConfig` (único, nullable) | `remoteConfigs` (lista) | Un flow lleva un Remote Config por idioma configurado. El getter `remoteConfig` sigue existiendo y devuelve la primera entrada; para seleccionar un idioma concreto, busca en `remoteConfigs` por su `locale`. | | `productIdentifiers` | `productIdentifiers` | Se conserva, pero ahora se recopila en todas las variaciones de paywall del flow. Los identificadores por variación están en `flow.paywalls[i].productIdentifiers`. | | `hasViewConfiguration` | `hasViewConfiguration` | Sin cambios. | | `placementId` (obsoleto) | eliminado | Usa `flow.placement.id`. | | `revision` (obsoleto) | eliminado | Usa `flow.placement.revision`. | | `vendorProductIds` (obsoleto) | eliminado | Usa `productIdentifiers`. | | _(nuevo)_ | `paywalls` (lista de `AdaptyFlowPaywall`) | Cada entrada es una variación de paywall en el flow, con su propio `name`, `variationId` y `productIdentifiers`. | `AdaptyPaywallViewConfiguration` ya no está expuesto — la configuración de la vista ahora es opaca. Elimina cualquier referencia a este tipo. ## Métodos de paywall web \{#web-paywall-methods\} `openWebPaywall` y `createWebPaywallUrl` mantienen sus nombres, pero el parámetro `paywall` ahora recibe un `AdaptyFlowPaywall` (una variante de flow) en lugar de un `AdaptyPaywall`. También puedes seguir pasando un `AdaptyPaywallProduct`. ```diff showLineNumbers final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); - await Adapty().openWebPaywall(paywall: paywall); + if (flow.paywalls.isNotEmpty) { + await Adapty().openWebPaywall(paywall: flow.paywalls[0]); + } ``` ## Seguimiento de vistas de flows \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` ha pasado a llamarse `logShowFlow` y ahora recibe un `AdaptyFlow`. El evento sigue registrándose contra la misma variación, por lo que las métricas de embudo y prueba A/B existentes siguen funcionando sin cambios en el dashboard. ```diff showLineNumbers - await Adapty().logShowPaywall(paywall: paywall); + await Adapty().logShowFlow(flow: flow); ``` Al igual que en v3, no es necesario llamar a este método cuando se muestran flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder) — Adapty registra esas vistas automáticamente. ## Mostrar flows \{#displaying-flows\} ### createPaywallView → createFlowView Renombra el método y pasa el `AdaptyFlow` mediante el parámetro `flow`. El resto de parámetros (`loadTimeout`, `preloadProducts`, `customTags`, `customTimers`, `customAssets`, `productPurchaseParams`) no cambian, ni tampoco los métodos de la vista `present`, `dismiss` y `showDialog`: ```diff showLineNumbers - final view = await AdaptyUI().createPaywallView(paywall: paywall); + final view = await AdaptyUI().createFlowView(flow: flow); await view.present(); ``` ### AdaptyUIPaywallView → AdaptyUIFlowView El tipo de vista ha sido renombrado. Su propiedad `paywallVariationId` (obsoleta) ha sido eliminada — usa `variationId` en su lugar: ```diff showLineNumbers - void flowViewDidAppear(AdaptyUIPaywallView view) { + void flowViewDidAppear(AdaptyUIFlowView view) { ``` ### AdaptyUIPaywallPlatformView → AdaptyUIFlowPlatformView Si incrustas la vista como un widget en tu árbol de widgets, renómbrala y pasa el parámetro `flow`. Los callbacks de eventos (`onDidAppear`, `onDidFinishPurchase`, etc.) conservan sus nombres: ```diff showLineNumbers - AdaptyUIPaywallPlatformView( - paywall: paywall, + AdaptyUIFlowPlatformView( + flow: flow, onDidFinishPurchase: (view, product, purchaseResult) { /* … */ }, ) ``` :::note Una vista de flow creada con `createFlowView` es de un solo uso: tras llamar a `dismiss()`, la vista se libera de la memoria y no se puede volver a mostrar — llama a `createFlowView` de nuevo para presentar el flow otra vez. ::: ## Manejo de eventos \{#handling-events\} La clase de observador se renombra de `AdaptyUIPaywallsEventsObserver` a `AdaptyUIFlowsEventsObserver`, su método de registro de `setPaywallsEventsObserver` a `setFlowsEventsObserver`, y todos los callbacks `paywallViewDid*` pasan a llamarse `flowViewDid*`: ```diff showLineNumbers - class MyObserver extends AdaptyUIPaywallsEventsObserver { + class MyObserver extends AdaptyUIFlowsEventsObserver { @override - void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { + void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { // … } } - AdaptyUI().setPaywallsEventsObserver(this); + AdaptyUI().setFlowsEventsObserver(this); ``` Ahora hay tres callbacks **obligatorios** — tu observer no compilará sin ellos: - **`flowViewDidFinishPurchase`**: Era opcional en v3, donde el comportamiento por defecto era cerrar la vista tras una compra. Ahora decides qué ocurre: continuar el flow o llamar a `view.dismiss()`. - **`flowViewDidFinishRestore`**: Obligatorio, igual que en v3. - **`flowViewDidReceiveError`**: Reemplaza a `paywallViewDidFailRendering` y ahora también recibe otros errores de la vista. Dos cambios menores: - `setFlowsEventsObserver` (y `setOnboardingsEventsObserver`) ahora aceptan `null` para desasociar un observador previamente configurado, por lo que el SDK ya no lo retiene. - El nuevo callback opcional `flowViewDidReceiveAnalyticEvent` está reservado para eventos analíticos personalizados de un flow. Los flows aún no emiten estos eventos a tu código, por lo que no necesitas implementarlo. La v4 también añade funcionalidades opcionales a las que puedes suscribirte: - `AdaptyUI().setObserverModeResolver(...)` con un `AdaptyUIObserverModeResolver` — gestiona las compras y restauraciones iniciadas desde flows cuando el SDK funciona en [modo Observer](implement-observer-mode-flutter). Anteriormente, esto solo estaba disponible en los SDKs nativos de iOS y Android. Consulta [Presentar flows en modo Observer](flutter-present-flows-in-observer-mode). - `AdaptyUI().setSystemRequestsHandler(...)` con un `AdaptyUISystemRequestsHandler` — reservado para solicitudes del sistema desde un flow (permisos del SO y solicitudes de valoración en el App Store). Los flows aún no activan estas solicitudes, por lo que no es necesario registrar un handler. ## APIs eliminadas \{#removed-apis\} Estos símbolos fueron declarados obsoletos en la versión 3.x y se eliminan en la v4: ### setFallbackPaywalls → setFallback ```diff showLineNumbers - await Adapty().setFallbackPaywalls(assetId); + await Adapty().setFallback(assetId); ``` ### withIdfaCollectionDisabled → withAppleIdfaCollectionDisabled ```diff showLineNumbers configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') - ..withIdfaCollectionDisabled(true), + ..withAppleIdfaCollectionDisabled(true), ``` ### Otros miembros eliminados - **`AdaptyPurchaseResultSuccess.jwsTransaction`**: Usa `appleJwsTransaction`. - **`AdaptyUIFlowView.paywallVariationId`**: Usa `variationId`. - **`AdaptyUIObserver` y `AdaptyUI().setObserver(...)`**: Usa `AdaptyUIFlowsEventsObserver` y `setFlowsEventsObserver(...)`. ## Cambios en el comportamiento predeterminado \{#default-behavior-changes\} Estos cambios no causan errores de compilación, así que pruébalos en tiempo de ejecución: - **Compra exitosa**: En v3, el comportamiento predeterminado de `paywallViewDidFinishPurchase` cerraba la vista. En v4, `flowViewDidFinishPurchase` es obligatorio y no tiene comportamiento predeterminado — cierra la vista tú mismo si eso es lo que quieres. - **Botón Atrás del sistema Android**: Ya no cierra un flow de forma predeterminada. La acción se entrega a `flowViewDidPerformAction` como `AndroidSystemBackAction` — gestiónala ahí si quieres que el botón Atrás cierre el flow. - **Apertura de URLs**: El comportamiento predeterminado de `flowViewDidPerformAction` ahora gestiona `OpenUrlAction` abriendo la URL de forma nativa (respetando la configuración de navegador interno o externo del dashboard), además de cerrar la vista con `CloseAction`. Sobreescribe el callback para gestionar las URLs tú mismo. - **Errores de vista**: `flowViewDidReceiveError` es obligatorio, y el cierre depende de tu implementación. Si tu integración de v3 dependía del cierre automático de la vista al producirse errores de renderizado, llama a `view.dismiss()` en este callback. - **Ciclo de vida de la vista**: Cerrar una vista de flow u onboarding la libera de la memoria. Una vista cerrada no puede volver a mostrarse — crea una nueva en su lugar. ## Obsolescencia de la API de onboarding \{#onboarding-api-deprecation\} La API de onboarding heredada está obsoleta en v4.0 en favor del [Flow Builder](adapty-flow-builder). Sigue funcionando, y tu IDE marca los símbolos obsoletos mediante sus anotaciones `@Deprecated` — no hay advertencias en tiempo de ejecución. Estos símbolos se eliminarán en una versión futura, así que planifica la migración de tus onboardings al Flow Builder. Símbolos obsoletos: `getOnboarding`, `getOnboardingForDefaultAudience`, `createOnboardingView`, `presentOnboardingView`, `dismissOnboardingView`, `setOnboardingsEventsObserver`, `AdaptyOnboarding`, `AdaptyUIOnboardingView`, `AdaptyUIOnboardingPlatformView`, `AdaptyUIOnboardingsEventsObserver`, y los modelos de estado, entrada y analíticas de onboarding. --- # File: flutter-migration-guide-310 --- --- title: "Guía de migración al SDK de Adapty para Flutter 3.10.0" description: "" --- El SDK de Adapty 3.10.0 es una versión principal que incorpora mejoras que, sin embargo, pueden requerir algunos pasos de migración por tu parte: 1. Actualiza el método `makePurchase` para usar `AdaptyPurchaseParameters` en lugar de parámetros individuales. 2. Reemplaza `vendorProductIds` por `productIdentifiers` en el modelo `AdaptyPaywall`. ## Actualizar el método makePurchase \{#update-makepurchase-method\} El método `makePurchase` ahora usa `AdaptyPurchaseParameters` en lugar de los argumentos individuales `subscriptionUpdateParams` e `isOfferPersonalized`. Esto proporciona mayor seguridad de tipos y permite una mayor extensibilidad de los parámetros de compra en el futuro. ```diff showLineNumbers - final purchaseResult = await adapty.makePurchase( - product: product, - subscriptionUpdateParams: subscriptionUpdateParams, - isOfferPersonalized: true, - ); + final parameters = AdaptyPurchaseParametersBuilder() + ..setSubscriptionUpdateParams(subscriptionUpdateParams) + ..setIsOfferPersonalized(true) + ..setObfuscatedAccountId('your-account-id') + ..setObfuscatedProfileId('your-profile-id'); + final purchaseResult = await adapty.makePurchase( + product: product, + parameters: parameters.build(), + ); ``` Si no necesitas parámetros adicionales, puedes usar simplemente: ```dart showLineNumbers final purchaseResult = await adapty.makePurchase( product: product, ); ``` ## Actualizar el uso del modelo AdaptyPaywall \{#update-adaptyp-aywall-model-usage\} La propiedad `vendorProductIds` ha quedado obsoleta en favor de `productIdentifiers`. La nueva propiedad devuelve objetos `AdaptyProductIdentifier` en lugar de cadenas de texto simples, lo que ofrece información de producto con una estructura más organizada. ```diff showLineNumbers - paywall.vendorProductIds.map((vendorId) => - ListTextTile(title: vendorId) - ).toList() + paywall.productIdentifiers.map((productId) => + ListTextTile(title: productId.vendorProductId) + ).toList() ``` El objeto `AdaptyProductIdentifier` proporciona acceso al ID del producto del proveedor a través de la propiedad `vendorProductId`, manteniendo la misma funcionalidad y ofreciendo una mejor estructura para mejoras futuras. ## Compatibilidad con versiones anteriores \{#backward-compatibility\} Ambos cambios mantienen la compatibilidad con versiones anteriores: - Los parámetros antiguos en `makePurchase` están obsoletos, pero siguen funcionando - La propiedad `vendorProductIds` está obsoleta, pero sigue siendo accesible - El código existente seguirá funcionando, aunque verás advertencias de obsolescencia Te recomendamos actualizar tu código para usar las nuevas API y garantizar la compatibilidad futura, además de aprovechar la mayor seguridad de tipos y extensibilidad. --- # File: flutter-migration-guide-38 --- --- title: "Migrar el SDK de Adapty para Flutter a v. 3.8" description: "Migra al SDK de Adapty para Flutter v3.8 para obtener mejor rendimiento y nuevas funciones de monetización." --- El SDK 3.8.0 de Adapty es una versión mayor que incluye mejoras que pueden requerir algunos pasos de migración por tu parte. 1. Actualiza los nombres de la clase observadora y sus métodos. 2. Actualiza el nombre del método de paywalls de respaldo. 3. Actualiza el nombre de la clase de vista en los métodos de manejo de eventos. ## Actualiza los nombres de la clase observadora y sus métodos \{#update-observer-class-and-method-names\} La clase observadora y su método de registro han sido renombrados: ```diff showLineNumbers - class MyObserver extends AdaptyUIObserver { + class MyObserver extends AdaptyUIPaywallsEventsObserver { @override void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) { // Handle action } } // Register observer - AdaptyUI().setObserver(this); + AdaptyUI().setPaywallsEventsObserver(this); ``` ## Actualiza el nombre del método de paywalls de respaldo \{#update-fallback-paywalls-method-name\} El método para configurar los paywalls de respaldo ha sido simplificado: ```diff showLineNumbers try { - await Adapty.setFallbackPaywalls(assetId); + await Adapty.setFallback(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## Actualiza el nombre de la clase de vista en los métodos de gestión de eventos \{#update-view-class-name-in-event-handling-methods\} Todos los métodos de gestión de eventos ahora utilizan la nueva clase `AdaptyUIPaywallView` en lugar de `AdaptyUIView`: ```diff showLineNumbers - void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) + void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) - void paywallViewDidSelectProduct(AdaptyUIView view, AdaptyPaywallProduct product) + void paywallViewDidSelectProduct(AdaptyUIPaywallView view, AdaptyPaywallProduct product) - void paywallViewDidStartPurchase(AdaptyUIView view, AdaptyPaywallProduct product) + void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) - void paywallViewDidFinishPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyProfile profile) + void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyProfile profile) - void paywallViewDidFailPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyError error) + void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) - void paywallViewDidFinishRestore(AdaptyUIView view, AdaptyProfile profile) + void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) - void paywallViewDidFailRestore(AdaptyUIView view, AdaptyError error) + void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) - void paywallViewDidFailLoadingProducts(AdaptyUIView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error) + void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error) - void paywallViewDidFailRendering(AdaptyUIView view, AdaptyError error) + void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) ``` --- # File: migration-to-flutter-sdk-34 --- --- title: "Migrar Adapty Flutter SDK a la v. 3.4" description: "Migra al Adapty Flutter SDK v3.4 para mejor rendimiento y nuevas funciones de monetización." --- Adapty SDK 3.4.0 es una versión mayor que introduce mejoras que requieren pasos de migración por tu parte. ## Actualizar los archivos de paywall de respaldo \{#update-fallback-paywall-files\} Actualiza tus archivos de paywall de respaldo para garantizar la compatibilidad con la nueva versión del SDK: 1. [Descarga los archivos de paywall de respaldo actualizados](fallback-paywalls) desde el Adapty Dashboard. 2. [Reemplaza los paywalls de respaldo existentes en tu app](flutter-use-fallback-paywalls) con los nuevos archivos. ## Actualizar la implementación del Observer Mode \{#update-implementation-of-observer-mode\} Si utilizas el Observer Mode, asegúrate de actualizar su implementación. Anteriormente se usaban distintos métodos para reportar transacciones a Adapty. En la nueva versión, el método `reportTransaction` debe usarse de forma consistente tanto en Android como en iOS. Este método reporta explícitamente cada transacción a Adapty, garantizando que sea reconocida. Si se utilizó un paywall, pasa el ID de variación para vincular la transacción con él. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: ```diff showLineNumbers - // every time when calling transaction.finish() - if (Platform.isAndroid) { - try { - await Adapty().restorePurchases(); - } on AdaptyError catch (adaptyError) { - // handle the error - } catch (e) { - } - } try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` --- # File: migration-to-flutter330 --- --- title: "Migrar el SDK de Flutter de Adapty a v3.3" description: "Migra al SDK de Flutter de Adapty v3.3 para mejorar el rendimiento y acceder a nuevas funciones de monetización." --- Adapty SDK 3.3.0 es una versión mayor que trae varias mejoras que, sin embargo, pueden requerir algunos pasos de migración por tu parte. 1. Actualiza el método para proporcionar paywalls de respaldo. 2. Elimina el método `getProductsIntroductoryOfferEligibility`. 3. Actualiza las configuraciones de integración para Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase and Google Analytics, Mixpanel, OneSignal, Pushwoosh. 4. Actualiza la implementación del modo Observer. ## Método actualizado para proporcionar paywalls de respaldo \{#update-method-for-providing-fallback-paywalls\} Antes, el método requería el paywall de respaldo como una cadena JSON (`jsonString`), pero ahora recibe la ruta al archivo de respaldo local (`assetId`) en su lugar. ```diff showLineNumbers import 'dart:async' show Future; import 'dart:io' show Platform; -import 'package:flutter/services.dart' show rootBundle; -final filePath = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; -final jsonString = await rootBundle.loadString(filePath); +final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; try { - await adapty.setFallbackPaywalls(jsonString); + await adapty.setFallbackPaywalls(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Para ver el ejemplo de código completo, consulta la página [Usar paywalls de respaldo](flutter-use-fallback-paywalls). ## Elimina el método `getProductsIntroductoryOfferEligibility` \{#remove-getproductsintroductoryoffereligibility-method\} Antes del SDK de Adapty iOS 3.3.0, el objeto de producto siempre incluía las ofertas, independientemente de si el usuario era elegible. Tenías que verificar la elegibilidad manualmente antes de usar la oferta. Ahora, el objeto de producto solo incluye una oferta si el usuario es elegible. Esto significa que ya no necesitas verificar la elegibilidad: si hay una oferta presente, el usuario es elegible. ## Actualiza la configuración del SDK de integraciones de terceros \{#update-third-party-integration-sdk-configuration\} Para garantizar que las integraciones funcionen correctamente con el SDK de Adapty Flutter 3.3.0 y versiones posteriores, actualiza las configuraciones de tu SDK para las siguientes integraciones tal como se describe en las secciones a continuación. ### Adjust Actualiza el código de tu app móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers import 'package:adjust_sdk/adjust.dart'; import 'package:adjust_sdk/adjust_config.dart'; try { final adid = await Adjust.getAdid(); if (adid == null) { // handle the error } + await Adapty().setIntegrationIdentifier( + key: "adjust_device_id", + value: adid, + ); final attributionData = await Adjust.getAttribution(); var attribution = Map<String, String>(); if (attributionData.trackerToken != null) attribution['trackerToken'] = attributionData.trackerToken!; if (attributionData.trackerName != null) attribution['trackerName'] = attributionData.trackerName!; if (attributionData.network != null) attribution['network'] = attributionData.network!; if (attributionData.adgroup != null) attribution['adgroup'] = attributionData.adgroup!; if (attributionData.creative != null) attribution['creative'] = attributionData.creative!; if (attributionData.clickLabel != null) attribution['clickLabel'] = attributionData.clickLabel!; if (attributionData.costType != null) attribution['costType'] = attributionData.costType!; if (attributionData.costAmount != null) attribution['costAmount'] = attributionData.costAmount!.toString(); if (attributionData.costCurrency != null) attribution['costCurrency'] = attributionData.costCurrency!; if (attributionData.fbInstallReferrer != null) attribution['fbInstallReferrer'] = attributionData.fbInstallReferrer!; - Adapty().updateAttribution( - attribution, - source: AdaptyAttributionSource.adjust, - networkUserId: adid, - ); + await Adapty().updateAttribution(attribution, source: "adjust"); } catch (e) { // handle the error } on AdaptyError catch (adaptyError) { // handle the error } ``` ### AirBridge Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers import 'package:airbridge_flutter_sdk/airbridge_flutter_sdk.dart'; final deviceUUID = await Airbridge.state.deviceUUID; try { - final builder = AdaptyProfileParametersBuilder() - ..setAirbridgeDeviceId(deviceUUID); - await Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "airbridge_device_id", + value: deviceUUID, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### Amplitude Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers import 'package:amplitude_flutter/amplitude.dart'; final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); final deviceId = await amplitude.getDeviceId(); final userId = await amplitude.getUserId(); try { - final builder = AdaptyProfileParametersBuilder() - ..setAmplitudeDeviceId(deviceId) - ..setAmplitudeUserId(userId); - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "amplitude_user_id", + value: userId, + ); + await Adapty().setIntegrationIdentifier( + key: "amplitude_device_id", + value: deviceId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### AppMetrica Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers import 'package:appmetrica_plugin/appmetrica_plugin.dart'; final deviceId = await AppMetrica.deviceId; if (deviceId != null) { try { - final builder = AdaptyProfileParametersBuilder() - ..setAppmetricaDeviceId(deviceId) - ..setAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID"); - - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "appmetrica_device_id", + value: deviceId, + ); + await Adapty().setIntegrationIdentifier( + key: "appmetrica_profile_id", + value: "YOUR_ADAPTY_CUSTOMER_USER_ID", + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` ### AppsFlyer Actualiza el código de tu app móvil como se indica a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers import 'package:appsflyer_sdk/appsflyer_sdk.dart'; AppsflyerSdk appsflyerSdk = AppsflyerSdk(<YOUR_OPTIONS>); appsflyerSdk.onInstallConversionData((data) async { try { final appsFlyerUID = await appsFlyerSdk.getAppsFlyerUID(); - await Adapty().updateAttribution( - data, - source: AdaptyAttributionSource.appsflyer, - networkUserId: appsFlyerUID, - ); + await Adapty().setIntegrationIdentifier( + key: "appsflyer_id", + value: appsFlyerUID, + ); + + await Adapty().updateAttribution(data, source: "appsflyer"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } }); appsflyerSdk.initSdk( registerConversionDataCallback: true, registerOnAppOpenAttributionCallback: true, registerOnDeepLinkingCallback: true, ); ``` ### Branch Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers FlutterBranchSdk.initSession().listen((data) async { try { + await Adapty().setIntegrationIdentifier( + key: "branch_id", + value: <BRANCH_IDENTITY_ID>, + ); - await Adapty().updateAttribution(data, source: AdaptyAttributionSource.branch); + await Adapty().updateAttribution(data, source: "branch"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ); ``` ### Firebase y Google Analytics \{#firebase-and-google-analytics\} Actualiza el código de tu aplicación móvil como se indica a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Firebase y Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers final appInstanceId = await FirebaseAnalytics.instance.appInstanceId; try { - final builder = AdaptyProfileParametersBuilder() - ..setFirebaseAppInstanceId(appInstanceId); - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "firebase_app_instance_id", + value: appInstanceId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### Mixpanel Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo completo, consulta la [configuración del SDK para la integración con Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers final mixpanel = await Mixpanel.init("Your Token", trackAutomaticEvents: true); final distinctId = await mixpanel.getDistinctId(); try { - final builder = AdaptyProfileParametersBuilder() - ..setMixpanelUserId(distinctId); - await Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "mixpanel_user_id", + value: distinctId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### OneSignal Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers OneSignal.shared.setSubscriptionObserver((changes) { final playerId = changes.to.userId; if (playerId != null) { - final builder = - AdaptyProfileParametersBuilder() - ..setOneSignalPlayerId(playerId); - // ..setOneSignalSubscriptionId(playerId); try { - Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "one_signal_player_id", + value: playerId, + ); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle error } } }); ``` ### Pushwoosh Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers final hwid = await Pushwoosh.getInstance.getHWID; - final builder = AdaptyProfileParametersBuilder() - ..setPushwooshHWID(hwid); try { - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "pushwoosh_hwid", + value: hwid, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## Actualizar la implementación del modo Observer \{#update-observer-mode-implementation\} Actualiza cómo vinculas los paywalls con las transacciones. Antes, usabas el método `setVariationId` para asignar el `variationId`. Ahora puedes incluir el `variationId` directamente al registrar la transacción con el nuevo método `reportTransaction`. Consulta el ejemplo de código final en [Asociar paywalls con transacciones de compra en el modo Observer](report-transactions-observer-mode-flutter). :::warning No olvides registrar la transacción usando el método `reportTransaction`. Si omites este paso, Adapty no reconocerá la transacción, no otorgará niveles de acceso, no la incluirá en los análisis y no la enviará a las integraciones. ¡Este paso es esencial! ::: ```diff showLineNumbers try { - await Adapty().setVariationId("YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID"); + // every time when calling transaction.finish() + await Adapty().reportTransaction( + "YOUR_TRANSACTION_ID", + variationId: "PAYWALL_VARIATION_ID", // optional + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` --- # File: migration-to-flutter-sdk-v3 --- --- title: "Migrar el SDK de Flutter de Adapty a v3.0" description: "Migra al SDK de Flutter de Adapty v3.0 para mejorar el rendimiento y acceder a nuevas funciones de monetización." --- Adapty SDK v3.0 trae soporte para el nuevo y emocionante [Adapty Paywall Builder](adapty-paywall-builder), la nueva versión de la herramienta no-code y fácil de usar para crear paywalls. Con su máxima flexibilidad y ricas capacidades de diseño, tus paywalls serán más efectivos y rentables. :::info Ten en cuenta que la librería AdaptyUI está obsoleta y ahora forma parte de AdaptySDK. ::: ## Eliminar el SDK de AdaptyUI \{#remove-adaptyui-sdk\} 1. AdaptyUI pasa a ser un módulo dentro del SDK de Adapty, así que elimina `adapty_ui_flutter` de tu archivo `pubspec.yaml`: ```diff showLineNumbers dependencies: + adapty_flutter: ^3.2.1 - adapty_flutter: ^2.10.3 - adapty_ui_flutter: ^2.1.3 ``` 2. Ejecuta: ```bash showLineNumbers title="Bash" flutter pub get ``` ## Configurar los SDK de Adapty \{#configure-adapty-sdks\} Antes era necesario usar los archivos `Adapty-Info.plist` y `AndroidManifest.xml` para configurar el SDK de Adapty. Ahora ya no es necesario usar archivos adicionales. En su lugar, puedes proporcionar todos los parámetros requeridos durante la activación. Solo tienes que configurar el SDK de Adapty una vez, normalmente al inicio del ciclo de vida de tu app. ### Activar el módulo Adapty del SDK \{#activate-adapty-module-of-adapty-sdk\} 1. Elimina la importación del SDK AdaptyUI de tu aplicación de la siguiente manera: ```diff showLineNumbers import 'package:adapty_flutter/adapty_flutter.dart'; - import 'package:adapty_ui_flutter/adapty_ui_flutter.dart'; ``` 2. Actualiza la activación del SDK de Adapty así: ```diff showLineNumbers try { - Adapty().activate(); + await Adapty().activate( + configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') + ..withLogLevel(AdaptyLogLevel.debug) + ..withObserverMode(false) + ..withCustomerUserId(null) + ..withIpAddressCollectionDisabled(false) + ..withIdfaCollectionDisabled(false), + ); } catch (e) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | ----------------------------------- | --------- | ------------------------------------------------------------ | | **PUBLIC_SDK_KEY** | obligatorio | La clave que puedes encontrar en el campo **Public SDK key** de la configuración de tu app en Adapty: [**App settings**-> pestaña **General** -> subsección **API keys**](https://app.adapty.io/settings/general) | | **withLogLevel** | opcional | Adapty registra errores y otra información esencial para darte visibilidad sobre el funcionamiento de tu app. Los niveles disponibles son:<ul><li> error: Solo se registran los errores.</li><li> warn: Se registran los errores y los mensajes del SDK que no causan errores críticos, pero que conviene tener en cuenta.</li><li> info: Se registran los errores, advertencias y mensajes informativos relevantes, como los que registran el ciclo de vida de los distintos módulos.</li><li> verbose: Se registra cualquier información adicional que pueda ser útil durante la depuración, como llamadas a funciones, consultas a la API, etc.</li></ul> | | **withObserverMode** | opcional | <p>Un valor booleano que controla el [modo Observer](observer-vs-full-mode). Actívalo si gestionas las compras y el estado de las suscripciones por tu cuenta y usas Adapty solo para enviar eventos de suscripción y analíticas.</p><p>El valor por defecto es `false`.</p><p></p><p>🚧 Al ejecutarse en modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlas tú mismo.</p> | | **withCustomerUserId** | opcional | Un identificador del usuario en tu sistema. Lo enviamos en los eventos de suscripción y analítica para atribuir los eventos al perfil correcto. También puedes buscar clientes por `customerUserId` en el menú [**Profiles and Segments**](https://app.adapty.io/profiles/users). | | **withIdfaCollectionDisabled** | opcional | <p>Establécelo en `true` para deshabilitar la recopilación y el uso compartido del IDFA.</p><p>el uso compartido de la dirección IP del usuario.</p><p>El valor por defecto es `false`.</p><p>Para más detalles sobre la recopilación del IDFA, consulta la sección [Integración de analíticas](analytics-integration#disable-collection-of-advertising-identifiers).</p> | | **withIpAddressCollectionDisabled** | opcional | <p>Establécelo en `true` para deshabilitar la recopilación y el uso compartido de la dirección IP del usuario.</p><p>El valor por defecto es `false`.</p> | ### Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Solo necesitas configurar el módulo AdaptyUI si tienes pensado usar el [Paywall Builder](adapty-paywall-builder): ```dart showLineNumbers title="Dart" try { final mediaCache = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 100 * 1024 * 1024, // 100MB memoryStorageCountLimit: 2147483647, // 2^31 - 1, max int value in Dart diskStorageSizeLimit: 100 * 1024 * 1024, // 100MB ); await AdaptyUI().activate( configuration: AdaptyUIConfiguration(mediaCache: mediaCache), observer: <AdaptyUIObserver Implementation>, ); } catch (e) { // handle the error } ``` Ten en cuenta que la configuración de AdaptyUI es opcional: puedes activar el módulo AdaptyUI sin su configuración. Sin embargo, si usas la configuración, todos los parámetros son obligatorios. Parámetros: | Parámetro | Presencia | Descripción | | :------------------------------ | :------- | :----------------------------------------------------------- | | **memoryStorageTotalCostLimit** | requerido | Límite de coste total del almacenamiento en bytes. | | **memoryStorageCountLimit** | requerido | Límite de cantidad de elementos en el almacenamiento en memoria. | | **diskStorageSizeLimit** | requerido | Límite de tamaño de archivo en disco del almacenamiento en bytes. 0 significa sin límite. | --- # End of Documentation _Generated on: 2026-07-24T13:01:55.682Z_ _Successfully processed: 44/44 files_ # IOS - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-07-24T13:01:55.684Z Total files: 44 --- # File: sdk-installation-ios --- --- title: "Instalar y configurar el SDK de iOS" description: "Guía paso a paso para instalar el SDK de Adapty en iOS para apps con suscripciones." --- El SDK de Adapty incluye dos módulos principales para integrarse fácilmente en tu app móvil: - **Core Adapty**: Este SDK esencial es necesario para que Adapty funcione correctamente en tu app. - **AdaptyUI**: Este módulo opcional es necesario si usas el [Adapty Paywall Builder](adapty-paywall-builder), una herramienta sin código y fácil de usar para crear paywalls multiplataforma. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: Para ver un recorrido completo de la implementación, también puedes ver los vídeos: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="iOS (SwiftUI)" default> <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/cSChHc8k2zA?si=KhNFhqXccIzYwTcm" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> </div> </TabItem> <TabItem value="uikit" label="iOS (UIKit)" default> <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/WEUnlaAjSI0?si=sjXKVVb56tEHDKzJ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> </div> </TabItem> </Tabs> ## Requisitos \{#requirements\} Aunque el SDK es técnicamente compatible con iOS 13.0+ para el módulo principal, en la práctica se requiere iOS 15.0+ porque: - Todas las funciones de StoreKit 2 requieren iOS 15.0+ - El módulo AdaptyUI solo es compatible con iOS 15.0+ :::important Se requiere Adapty SDK 3.15.7+ al compilar con Xcode 26.4 o posterior. ::: :::info Instalar el SDK es el paso 5 de la configuración de Adapty. Para que las compras funcionen en tu app, también necesitas conectar tu app a los stores, y luego crear productos, un paywall y un placement en el Adapty Dashboard. La [guía de inicio rápido](quickstart) explica todos los pasos necesarios. ::: ## Instalar el SDK de Adapty \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-iOS.svg?style=flat&logo=apple)](https://github.com/adaptyteam/AdaptySDK-iOS/releases) El SDK de Adapty se instala a través de Swift Package Manager. En Xcode, ve a **File** -> **Add Package Dependency...**. Ten en cuenta que los pasos para añadir dependencias de paquetes pueden variar entre versiones de Xcode, así que consulta la documentación de Xcode si es necesario. 1. Introduce la URL del repositorio: ``` https://github.com/adaptyteam/AdaptySDK-iOS.git ``` 2. Selecciona la versión (se recomienda la última versión estable) y haz clic en **Add Package**. 3. En la ventana **Choose Package Products**, selecciona los módulos que necesites: - **Adapty** (módulo principal) - **AdaptyUI** (opcional - solo si planeas usar Paywall Builder) :::note Nota: - Para habilitar el [Modo Kids](kids-mode) en SDK 3.x, selecciona **Adapty_KidsMode** en lugar de **Adapty**. En SDK 4.0 y versiones posteriores, selecciona los módulos normales — el Modo Kids se habilita mediante el trait de paquete `KidsMode`. - No selecciones ningún otro paquete de la lista, no los necesitarás. ::: 4. Haz clic en **Add Package** para completar la instalación. 5. **Verifica la instalación:** En el navegador de tu proyecto, deberías ver "Adapty" (y "AdaptyUI" si lo seleccionaste) en **Package Dependencies**. :::important Adapty iOS SDK 4.0 es una versión preliminar. Swift Package Manager no resuelve versiones beta mediante la regla **Up to Next Major Version** (`from:`), por lo que debes fijar la versión exacta. En Xcode, establece la **Dependency Rule** en **Exact Version** e introduce `4.0.0-beta.2`. En `Package.swift`, usa `.exact("4.0.0-beta.2")`. Consulta [Migrar el SDK de Adapty iOS a v4](migration-to-ios-sdk-v4). ::: ## Activa el módulo Adapty del SDK \{#activate-adapty-module-of-adapty-sdk\} Activa el SDK en el código de tu app. :::note El SDK de Adapty solo necesita activarse una vez en tu app. ::: Para obtener tu **Public SDK Key**: 1. Ve al Adapty Dashboard y navega a [**App settings → General**](https://app.adapty.io/settings/general). 2. En la sección **Api keys**, copia la **Public SDK Key** (NO la Secret Key). 3. Reemplaza `"YOUR_PUBLIC_SDK_KEY"` en el código. O bien, obtenla de forma programática usando el [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` O directamente: ``` npx adapty auth login adapty apps list ``` - Asegúrate de usar la **Public SDK key** para inicializar Adapty; la **Secret key** solo debe usarse para la [API del lado del servidor](getting-started-with-server-side-api). - Las **SDK keys** son únicas para cada app, así que si tienes varias apps asegúrate de elegir la correcta. <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers @main struct YourApp: App { init() { // Configure Adapty SDK let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard Adapty.logLevel = .verbose // recommended for development and the first production release let config = configurationBuilder.build() // Activate Adapty SDK asynchronously Task { do { try await Adapty.activate(with: config) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } var body: some Scene { WindowGroup { // Your content view } } } } ``` </TabItem> <TabItem value="swift" label="UIKit" default> ```swift showLineNumbers // In your AppDelegate class: // If you only use an AppDelegate, place the following code in the // application(_:didFinishLaunchingWithOptions:) method. // If you use a SceneDelegate, place the following code in the // scene(_:willConnectTo:options:) method. Task { do { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(logLevel: .verbose) // recommended for development and the first production release let config = configurationBuilder.build() try await Adapty.activate(with: config) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } ``` </TabItem> </Tabs> :::important Espera a que `activate` se resuelva antes de llamar a cualquier otro método del SDK de Adapty. Consulta el [orden de llamadas en el SDK de iOS](ios-sdk-call-order) para ver la secuencia completa. ::: Ahora configura los paywalls en tu app: - Si usas [Adapty Paywall Builder](adapty-paywall-builder), primero [activa el módulo AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) a continuación y luego sigue la [guía de inicio rápido del Paywall Builder](ios-quickstart-paywalls). - Si construyes tu propia interfaz de paywall, consulta la [guía de inicio rápido para paywalls personalizados](ios-quickstart-manual). ## Activar el módulo AdaptyUI del SDK \{#activate-adaptyui-module-of-adapty-sdk\} Si tienes previsto usar el [Paywall Builder](adapty-paywall-builder) y has [instalado el módulo AdaptyUI](sdk-installation-ios#install-adapty-sdk), también necesitas activar AdaptyUI. :::important En tu código, debes activar el módulo central de Adapty antes de activar AdaptyUI. ::: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers title="Swift" @main struct YourApp: App { init() { // ...ConfigurationBuilder steps // Activate Adapty SDK asynchronously Task { do { try await Adapty.activate(with: config) try await AdaptyUI.activate() } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } // main body... } } ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift showLineNumbers title="UIKit" // If you only use an AppDelegate, place the following code in the // application(_:didFinishLaunchingWithOptions:) method. // If you use a SceneDelegate, place the following code in the // scene(_:willConnectTo:options:) method. Task { do { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(logLevel: .verbose) // recommended for development let config = configurationBuilder.build() try await Adapty.activate(with: config) try await AdaptyUI.activate() } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } ``` </TabItem> </Tabs> :::tip Opcionalmente, al activar AdaptyUI, puedes [sobrescribir la configuración de caché predeterminada para los paywalls](#set-up-media-cache-configuration-for-adaptyui). ::: ## Configuración opcional \{#optional-setup\} ### Registro #### Configura el sistema de registro \{#set-up-the-logging-system\} Adapty registra errores e información importante para ayudarte a entender qué está ocurriendo. Los niveles disponibles son los siguientes: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | Solo se registrarán los errores | | `warn` | Se registrarán los errores y los mensajes del SDK que no provocan errores críticos pero que merecen atención | | `info` | Se registrarán los errores, las advertencias y varios mensajes informativos | | `verbose` | Se registrará cualquier información adicional que pueda ser útil durante la depuración, como llamadas a funciones, consultas a la API, etc. | ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(logLevel: .verbose) // recommended for development ``` #### Redirigir los mensajes del sistema de logging \{#redirect-the-logging-system-messages\} Si necesitas enviar los mensajes de log de Adapty a tu sistema o guardarlos en un archivo, usa el método `setLogHandler` e implementa tu lógica de logging personalizada dentro de él. Este handler recibe registros de log que contienen el contenido del mensaje y el nivel de severidad. ```swift showLineNumbers title="Swift" Adapty.setLogHandler { record in writeToLocalFile("Adapty \(record.level): \(record.message)") } ``` ### Políticas de datos \{#data-policies\} Adapty no almacena datos personales de tus usuarios a menos que los envíes explícitamente, pero puedes implementar políticas de seguridad de datos adicionales para cumplir con las directrices de la store o del país. #### Deshabilitar la recopilación y el uso compartido de IDFA \{#disable-idfa-collection-and-sharing\} Al activar el módulo de Adapty, establece `idfaCollectionDisabled` en `true` para deshabilitar la recopilación y el uso compartido de IDFA. Usa este parámetro para cumplir con las directrices de revisión de la App Store o evitar que aparezca el aviso de App Tracking Transparency cuando tu app no necesita el IDFA. El valor predeterminado es `false`. Para más detalles sobre la recopilación del IDFA, consulta la sección [Integración de analíticas](analytics-integration#disable-collection-of-advertising-identifiers). ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(idfaCollectionDisabled: true) ``` #### Desactivar la recopilación y el uso compartido de la IP \{#disable-ip-collection-and-sharing\} Al activar el módulo de Adapty, establece `ipAddressCollectionDisabled` en `true` para deshabilitar la recopilación y el intercambio de la dirección IP del usuario. El valor predeterminado es `false`. Usa este parámetro para mejorar la privacidad del usuario, cumplir con las normativas regionales de protección de datos (como GDPR o CCPA), o reducir la recopilación de datos innecesaria cuando las funciones basadas en IP no son necesarias para tu app. ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(ipAddressCollectionDisabled: true) ``` #### Configuración de caché de medios para paywalls en AdaptyUI Ten en cuenta que la configuración de AdaptyUI es opcional. Puedes activar el módulo AdaptyUI sin su configuración. Sin embargo, si usas la configuración, todos los parámetros son obligatorios. ```swift showLineNumbers title="Swift" // Configure AdaptyUI let adaptyUIConfiguration = AdaptyUI.Configuration( mediaCacheConfiguration: .init( memoryStorageTotalCostLimit: 100 * 1024 * 1024, memoryStorageCountLimit: .max, diskStorageSizeLimit: 100 * 1024 * 1024 ) ) // Activate AdaptyUI AdaptyUI.activate(configuration: adaptyUIConfiguration) ``` Parámetros: | Parámetro | Presencia | Descripción | | :-------------------------- | :-------- | :----------------------------------------------------------- | | memoryStorageTotalCostLimit | requerido | Límite de coste total del almacenamiento en bytes. | | memoryStorageCountLimit | requerido | Límite de número de elementos del almacenamiento en memoria. | | diskStorageSizeLimit | requerido | Límite de tamaño de archivo en disco del almacenamiento en bytes. 0 significa sin límite. | ### Comportamiento de finalización de transacciones \{#transaction-finishing-behavior\} :::info Esta función está disponible a partir de la versión 3.12.0 del SDK. ::: Por defecto, Adapty finaliza automáticamente las transacciones tras una validación exitosa. Sin embargo, si necesitas una validación avanzada de transacciones (como validación de recibos en el servidor, detección de fraude o lógica de negocio personalizada), puedes configurar el SDK para que use la finalización manual de transacciones. ```swift showLineNumbers title="Swift" let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(transactionsFinishBehavior: .manual) // .auto is the default ``` Consulta más detalles sobre cómo finalizar transacciones en la [guía](ios-transaction-management). ### Borrar datos al restaurar desde copia de seguridad \{#clear-data-on-backup-restore\} Cuando `clearDataOnBackup` se establece en `true`, el SDK detecta cuándo la app se restaura desde una copia de seguridad de iCloud y elimina todos los datos del SDK almacenados localmente, incluida la información de perfil en caché, los detalles de productos y los paywalls. El SDK se inicializa entonces con un estado limpio. El valor predeterminado es `false`. :::note Solo se elimina la caché local del SDK. El historial de transacciones con Apple y los datos de usuario en los servidores de Adapty permanecen sin cambios. ::: ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(clearDataOnBackup: true) // default – false ``` ## Solución de problemas \{#troubleshooting\} #### Error de concurrencia de Swift 6 con Tuist \{#swift-6-concurrency-error-with-tuist\} Al compilar con [Tuist](https://tuist.dev/), puedes encontrarte con errores de compilación de concurrencia estricta de Swift 6. Los síntomas habituales incluyen desajustes del atributo `@Sendable` en `AdaptyUIBuilderLogic` u otros errores de Sendability entre módulos. Esto ocurre porque Tuist genera proyectos Xcode a partir de paquetes SPM pero no conserva el ajuste `swift-tools-version: 6.0`. Como resultado, algunos targets de Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`) se compilan con las reglas de Swift 5 mientras que otros usan Swift 6, lo que genera incompatibilidades de `@Sendable` entre módulos. **Solución**: Actualiza al SDK de Adapty **3.15.5** o posterior, que resuelve el problema independientemente de las versiones mixtas del lenguaje Swift. **Alternativa**: Si no puedes actualizar, establece explícitamente Swift 6 para los tres targets de Adapty en tu configuración de Tuist: ```swift showLineNumbers targetSettings: [ "Adapty": .init().swiftVersion("6"), "AdaptyUI": .init().swiftVersion("6"), "AdaptyUIBuilder": .init().swiftVersion("6"), ] ``` --- # File: ios-quickstart-paywalls --- --- title: "Habilitar compras con Flow Builder en iOS SDK" description: "Guía de inicio rápido para habilitar compras in-app con Adapty Flow Builder." --- Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Flows**](adapty-flow-builder) – secuencias de pantallas que presentan productos a los usuarios, creadas en el Flow Builder sin código. El SDK los recupera mediante `getFlow`. Si prefieres construir la interfaz en tu propio código, usa un paywall en su lugar — consulta [Implementar paywalls manualmente](ios-quickstart-manual). - [**Placements**](placements) – dónde y cuándo muestras los flows en tu app (por ejemplo, `main`, `onboarding`, `settings`). Vincula los flows a los placements en el dashboard y luego solicítalos por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar flows distintos a diferentes usuarios. Adapty te ofrece tres formas de habilitar compras en tu aplicación. Selecciona la que mejor se adapte a tus requisitos: | Implementación | Complejidad | Cuándo usarlo | |---|---|---| | Adapty Flow Builder | ✅ Fácil | [Creas un flow completo y listo para compras en el editor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona todo el proceso de compra, la validación de recibos y la gestión de suscripciones. | | Paywalls creados manualmente | 🟡 Media | Implementas la UI de tu paywall en el código de tu app, pero sigues obteniendo el objeto flow desde Adapty para mantener flexibilidad en los productos ofrecidos. Consulta la [guía](ios-quickstart-manual). | | Modo Observer | 🔴 Difícil | Ya tienes tu propia infraestructura de gestión de compras y quieres seguir usándola. Ten en cuenta que el modo observer tiene sus limitaciones en Adapty. Consulta el [artículo](observer-vs-full-mode). | :::important **Los pasos a continuación muestran cómo implementar un flow creado en el Adapty Flow Builder.** Si prefieres construir la UI del paywall tú mismo, consulta [Implementar paywalls manualmente](ios-quickstart-manual). ::: Para mostrar un flow creado en el Adapty Flow Builder, en el código de tu app solo necesitas: 1. **Obtener el flow**: Obtenlo desde Adapty. 2. **Mostrarlo y Adapty gestionará las compras por ti**: Muestra la vista en tu app. 3. **Gestionar las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abrir enlaces o cerrar el flow cuando los usuarios pulsen botones. ## Antes de empezar \{#before-you-start\} Antes de empezar, completa estos pasos: 1. [Conecta tu app con la App Store](initial_ios) en el Adapty Dashboard. 2. [Crea tus productos](create-product) en Adapty. 3. [Crea un flow y añade productos a él](create-paywall). 4. [Crea un placement y añade tu flow a él](create-placement). 5. [Instala y activa el SDK de Adapty](sdk-installation-ios) en el código de tu app. Esta guía usa las APIs del SDK de Adapty iOS v4. ## 1. Obtener el flow \{#1-get-the-flow\} Tus flows están asociados a placements configurados en el dashboard. Los placements te permiten ejecutar diferentes flows para distintas audiencias o ejecutar [pruebas A/B](ab-tests). Para obtener un flow creado en el Adapty Flow Builder, necesitas: 1. Obtener el objeto `flow` por el ID del [placement](placements) usando el método `getFlow` y comprobar si tiene una configuración de vista. 2. Obtener la configuración de vista usando el método `getFlowConfiguration`. Contiene los elementos de UI y el estilo necesarios para mostrar el flow. ```swift func loadFlow() async { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") guard flow.hasViewConfiguration else { print("Flow doesn't have a view configuration") return } flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow) } ``` ## 2. Mostrar el flow \{#2-display-the-flow\} Ahora que tienes la configuración del flow, basta con añadir unas pocas líneas para mostrarlo. <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> En SwiftUI, al mostrar el flow, también necesitas gestionar los eventos. `didFinishPurchase`, `didFailPurchase`, `didFinishRestore`, `didFailRestore` y `didReceiveError` son obligatorios. Durante las pruebas, puedes copiar el código del fragmento de abajo para registrar estos eventos. :::tip El flow no se cierra automáticamente tras una compra exitosa. En `didFinishPurchase`, establece tu binding de presentación en `false` para cerrarlo, o no hagas nada para que el flow continúe a sus siguientes pantallas. ::: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didFinishPurchase: { product, purchaseResult in print("Purchase finished successfully") flowPresented = false // or do nothing to let the flow continue }, didFailPurchase: { product, error in print("Purchase failed: \(error)") }, didFinishRestore: { profile in print("Restore finished successfully") }, didFailRestore: { error in print("Restore failed: \(error)") }, didReceiveError: { error in flowPresented = false print("Flow error: \(error)") } ) ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift func presentFlow(with config: AdaptyUI.FlowConfiguration) { let flowController = try AdaptyUI.flowController( with: config, delegate: self ) present(flowController, animated: true) } ``` Implementa `AdaptyFlowControllerDelegate` para gestionar eventos. Como mínimo, implementa los cuatro métodos que no tienen implementación por defecto. Ten en cuenta que el controlador no se cierra automáticamente tras una compra exitosa — ciérralo en `didFinishPurchase`, o no hagas nada para dejar que el flow continúe a sus siguientes pantallas: ```swift extension YourViewController: AdaptyFlowControllerDelegate { func flowController(_ controller: AdaptyFlowController, didFinishPurchase product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) { if !purchaseResult.isPurchaseCancelled { controller.dismiss(animated: true) // or do nothing to let the flow continue } } func flowController(_ controller: AdaptyFlowController, didFailPurchase product: AdaptyPaywallProduct, error: AdaptyError) { print("Purchase failed: \(error)") } func flowController(_ controller: AdaptyFlowController, didFinishRestoreWith profile: AdaptyProfile) { print("Restore finished successfully") } func flowController(_ controller: AdaptyFlowController, didFailRestoreWith error: AdaptyError) { print("Restore failed: \(error)") } } ``` </TabItem> </Tabs> :::info Para más detalles sobre cómo mostrar un flow, consulta nuestra [guía](ios-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#handle-button-actions\} Cuando los usuarios pulsan botones, el SDK de iOS gestiona automáticamente las compras, la restauración, el cierre del flow y la apertura de enlaces. Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren que gestiones las acciones en tu código. O puede que quieras sobreescribir su comportamiento por defecto. Por ejemplo, así es como gestionar el botón de cierre. En UIKit, el SDK descarta el controlador automáticamente cuando se dispara `.close` — sobreescríbelo solo si quieres un comportamiento personalizado. En SwiftUI, debes establecer tu binding `isPresented` a `false` tú mismo. :::tip Lee nuestras guías sobre cómo gestionar [acciones](handle-paywall-actions) y [eventos](ios-handling-events) de los botones. ::: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false // dismiss the flow when the user taps close default: break } }, didFinishPurchase: { product, purchaseResult in flowPresented = false }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift extension YourViewController: AdaptyFlowControllerDelegate { func flowController(_ controller: AdaptyFlowController, didPerform action: AdaptyUI.Action) { switch action { case .close: controller.dismiss(animated: true) // default behavior — override only if needed default: break } } } ``` </TabItem> </Tabs> ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. [Prueba tus compras en modo sandbox](test-purchases-in-sandbox) para asegurarte de que puedes completar una compra de prueba desde el paywall. Ahora necesitas [comprobar el nivel de acceso de los usuarios](ios-check-subscription-status) para asegurarte de mostrar un paywall o dar acceso a las funciones de pago a los usuarios correctos. ## Ejemplo completo \{#full-example\} Aquí puedes ver cómo integrar todos los pasos de esta guía en tu app. <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> ```swift struct ContentView: View { @State private var flowPresented = false @State private var flowConfiguration: AdaptyUI.FlowConfiguration? @State private var isLoading = false @State private var hasInitialized = false var body: some View { VStack { if isLoading { ProgressView("Loading...") } else { Text("Your App Content") } } .task { guard !hasInitialized else { return } await initializeFlow() hasInitialized = true } .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false default: break } }, didFinishPurchase: { product, purchaseResult in print("Purchase finished successfully") flowPresented = false // or do nothing to let the flow continue }, didFailPurchase: { product, error in print("Purchase failed: \(error)") }, didFinishRestore: { profile in print("Restore finished successfully") }, didFailRestore: { error in print("Restore failed: \(error)") }, didReceiveError: { error in print("Flow error: \(error)") flowPresented = false } ) } private func initializeFlow() async { isLoading = true defer { isLoading = false } await loadFlow() flowPresented = true } private func loadFlow() async { do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") guard flow.hasViewConfiguration else { print("Flow doesn't have a view configuration") return } flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow) } catch { print("Failed to load: \(error)") } } } ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift class ViewController: UIViewController { private var flowConfiguration: AdaptyUI.FlowConfiguration? override func viewDidLoad() { super.viewDidLoad() Task { await initializeFlow() } } private func initializeFlow() async { do { flowConfiguration = try await loadFlow() if let flowConfiguration { await MainActor.run { presentFlow(with: flowConfiguration) } } } catch { print("Error initializing: \(error)") } } private func loadFlow() async throws -> AdaptyUI.FlowConfiguration? { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") guard flow.hasViewConfiguration else { print("Flow doesn't have a view configuration") return nil } return try await AdaptyUI.getFlowConfiguration(forFlow: flow) } private func presentFlow(with config: AdaptyUI.FlowConfiguration) { guard let flowController = try? AdaptyUI.flowController( with: config, delegate: self ) else { return } present(flowController, animated: true) } } extension ViewController: AdaptyFlowControllerDelegate { func flowController(_ controller: AdaptyFlowController, didFinishPurchase product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) { if !purchaseResult.isPurchaseCancelled { controller.dismiss(animated: true) // or do nothing to let the flow continue } } func flowController(_ controller: AdaptyFlowController, didFailPurchase product: AdaptyPaywallProduct, error: AdaptyError) { print("Purchase failed for \(product.vendorProductId): \(error)") guard error.adaptyErrorCode != .paymentCancelled else { return } let message = switch error.adaptyErrorCode { case .paymentNotAllowed: "Purchases are not allowed on this device." default: "Purchase failed. Please try again." } let alert = UIAlertController(title: "Purchase Error", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) present(alert, animated: true) } func flowController(_ controller: AdaptyFlowController, didFinishRestoreWith profile: AdaptyProfile) { print("Restore finished successfully") controller.dismiss(animated: true) } func flowController(_ controller: AdaptyFlowController, didFailRestoreWith error: AdaptyError) { print("Restore failed: \(error)") } func flowController(_ controller: AdaptyFlowController, didReceiveError error: AdaptyUIError) { print("Flow error: \(error)") controller.dismiss(animated: true) } } ``` </TabItem> </Tabs> --- # File: ios-check-subscription-status --- --- title: "Comprobar el estado de suscripción en el SDK de iOS" description: "Aprende a comprobar el estado de suscripción en tu app de iOS con Adapty." --- Para decidir si los usuarios pueden acceder al contenido de pago o ver un paywall, necesitas comprobar su [nivel de acceso](access-level) en el perfil. Este artículo te muestra cómo acceder al estado del perfil para decidir qué deben ver los usuarios: si mostrarles un paywall o concederles acceso a las funciones de pago. ## Obtener el estado de suscripción \{#get-subscription-status\} Cuando decides si mostrar un paywall o contenido de pago a un usuario, compruebas su [nivel de acceso](access-level) en su perfil. Tienes dos opciones: - Llamar a `getProfile` si necesitas los datos más recientes del perfil de inmediato (como al iniciar la app) o quieres forzar una actualización. - Configurar **actualizaciones automáticas del perfil** para mantener una copia local que se actualice automáticamente cada vez que cambie el estado de la suscripción. :::important Por defecto, el nivel de acceso `premium` ya existe en Adapty. Si no necesitas configurar más de un nivel de acceso, puedes usar simplemente `premium`. ::: ### Obtener el perfil \{#get-profile\} La forma más sencilla de obtener el estado de suscripción es usar el método `getProfile` para acceder al perfil: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> </Tabs> ### Escuchar actualizaciones de suscripción \{#listen-to-subscription-updates\} Si quieres recibir actualizaciones del perfil automáticamente en tu app: 1. Conforma el protocolo `AdaptyDelegate` en el tipo que prefieras e implementa el método `didLoadLatestProfile`. Adapty llamará a este método automáticamente cada vez que cambie el estado de suscripción del usuario. En el ejemplo siguiente usamos un tipo `SubscriptionManager` para gestionar los flujos de suscripción y el perfil del usuario. Este tipo puede inyectarse como dependencia o configurarse como singleton en una app UIKit, o añadirse al entorno SwiftUI desde la estructura principal de la app. 2. Guarda los datos del perfil actualizado cuando se llame a este método, para poder usarlos en toda tu app sin necesidad de realizar peticiones de red adicionales. ```swift class SubscriptionManager: AdaptyDelegate { nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { let hasAccess = profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false // Update UI, unlock content, etc. } } // Set delegate after Adapty activation Adapty.delegate = subscriptionManager ``` :::note Adapty llama automáticamente a `didLoadLatestProfile` cuando se inicia tu app, proporcionando datos de suscripción en caché aunque el dispositivo esté sin conexión. ::: ## Conectar el perfil con la lógica del paywall \{#connect-profile-with-paywall-logic\} Cuando necesitas tomar decisiones inmediatas sobre mostrar paywalls o conceder acceso a funciones de pago, puedes comprobar el perfil del usuario directamente. Este enfoque es útil en situaciones como el inicio de la app, al entrar en secciones premium o antes de mostrar contenido específico. <Tabs> <TabItem value="swiftui" label="SwiftUI" default> ```swift private func checkAccessLevel() async -> Bool { do { let profile = try await Adapty.getProfile() return profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false } catch { print("Error checking access level: \(error)") return false } } // In your initialization logic: let hasAccess = await checkAccessLevel() if !hasAccess { paywallPresented = true // Show paywall if no access } ``` </TabItem> <TabItem value="uikit" label="UIKit"> ```swift private func checkAccessLevel() async throws -> Bool { let profile = try await Adapty.getProfile() return profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false } // In your initialization logic: let hasAccess = try await checkAccessLevel() if !hasAccess { presentPaywall(with: paywallConfiguration) } ``` </TabItem> </Tabs> ## Próximos pasos \{#next-steps\} Ahora que sabes cómo seguir el estado de suscripción, [aprende a trabajar con perfiles de usuario](ios-quickstart-identify) para asegurarte de que se integra con tu sistema de autenticación existente y los permisos de acceso compartido de pago. Si no tienes tu propio sistema de autenticación, no es ningún problema: Adapty gestionará los usuarios por ti, aunque puedes leer la [guía](ios-quickstart-identify) para entender cómo funciona Adapty con usuarios anónimos. --- # File: ios-quickstart-identify --- --- title: "Identificar usuarios en el SDK de iOS" description: "Guía de inicio rápido para configurar Adapty en la gestión de suscripciones in-app." --- :::important Esta guía es para ti si tienes tu propio sistema de autenticación. Aquí aprenderás a trabajar con perfiles de usuario en Adapty para que se alinee con tu sistema de autenticación existente. ::: La forma de gestionar las compras de los usuarios depende del modelo de autenticación de tu app: - Si tu app no usa autenticación backend y no almacena datos de usuario, consulta la [sección sobre usuarios anónimos](#anonymous-users). - Si tu app tiene (o tendrá) autenticación backend, consulta la [sección sobre usuarios identificados](#identified-users). **Conceptos clave**: - Los **perfiles** son las entidades que necesita el SDK para funcionar. Adapty los crea automáticamente. - Pueden ser anónimos **(sin customer user ID)** o identificados **(con customer user ID)**. - Proporcionas el **customer user ID** para cruzar los perfiles de Adapty con tu sistema de autenticación interno. Estas son las diferencias entre usuarios anónimos e identificados: | | Usuarios anónimos | Usuarios identificados | |------------------------------|----------------------------------------------------------------|-------------------------------------------------------------------------------------| | **Gestión de compras** | Restauración de compras a nivel de store | Mantiene el historial de compras entre dispositivos mediante su customer user ID | | **Gestión de perfiles** | Nuevos perfiles en cada reinstalación | El mismo perfil entre sesiones y dispositivos | | **Persistencia de datos** | Los datos de usuarios anónimos están ligados a la instalación | Los datos de usuarios identificados persisten entre instalaciones de la app | ## Usuarios anónimos \{#anonymous-users\} Si no tienes autenticación backend, **no necesitas gestionar la autenticación en el código de la app**: 1. Cuando el SDK se activa en el primer inicio de la app, Adapty **crea un nuevo perfil para el usuario**. 2. Cuando el usuario realiza una compra en la app, esta se **asocia con su perfil de Adapty y su cuenta del store**. 3. Cuando el usuario **reinstala** la app o la instala en un **nuevo dispositivo**, Adapty **crea un nuevo perfil anónimo al activarse**. 4. Si el usuario ya ha realizado compras en tu app, por defecto, sus compras se sincronizan automáticamente desde el App Store al activarse el SDK. :::note Las restauraciones desde copia de seguridad se comportan de forma diferente a las reinstalaciones. Por defecto, cuando un usuario restaura desde una copia de seguridad, el SDK conserva los datos en caché y no crea un nuevo perfil. Puedes configurar este comportamiento con el ajuste `clearDataOnBackup`. [Más información](sdk-installation-ios#clear-data-on-backup-restore). ::: Con usuarios anónimos se crearán nuevos perfiles en cada instalación, pero eso no es un problema porque, en los análisis de Adapty, puedes [configurar qué se considerará una nueva instalación](general#4-installs-definition-for-analytics). Para los usuarios anónimos, debes contar las instalaciones por **ID de dispositivo**. En este caso, cada instalación de la app en un dispositivo se cuenta como una instalación, incluidas las reinstalaciones. ## Usuarios identificados \{#identified-users\} Tienes dos opciones para identificar usuarios en la app: - [**Durante el inicio de sesión/registro:**](#during-loginsignup) Si los usuarios inician sesión después de que arranque tu app, llama a `identify()` con un customer user ID cuando se autentiquen. - [**Durante la activación del SDK:**](#during-the-sdk-activation) Si ya tienes un customer user ID almacenado cuando se inicia la app, envíalo al llamar a `activate()`. :::important Por defecto, cuando Adapty recibe una compra de un Customer User ID que actualmente está asociado a otro Customer User ID, el nivel de acceso se comparte, de modo que ambos perfiles tienen acceso de pago. Puedes configurar este ajuste para transferir el acceso de pago de un perfil a otro o desactivar el uso compartido por completo. Consulta el [artículo](general#6-sharing-paid-access-between-user-accounts) para más detalles. ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Durante el inicio de sesión/registro \{#during-loginsignup\} Si identificas a los usuarios después del inicio de la app (por ejemplo, tras iniciar sesión o registrarse), usa el método `identify` para establecer su customer user ID. - Si **no has usado este customer user ID antes**, Adapty lo vinculará automáticamente al perfil actual. - Si **ya has usado este customer user ID para identificar al usuario**, Adapty pasará a trabajar con el perfil asociado a ese customer user ID. :::important Los customer user IDs deben ser únicos para cada usuario. Si defines el valor del parámetro de forma fija, todos los usuarios se considerarán como uno solo. ::: Siempre usa `await` con `identify` antes de llamar a otros métodos del SDK. Las llamadas concurrentes producen `#3006 profileWasChanged` o recaen sobre el perfil anónimo. Consulta [Orden de llamadas en el SDK de iOS](ios-sdk-call-order). <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Unique for each user } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> </Tabs> ### Durante la activación del SDK \{#during-the-sdk-activation\} Si ya conoces un customer user ID cuando activas el SDK, puedes enviarlo en el método `activate` en lugar de llamar a `identify` por separado. Si conoces un customer user ID pero lo estableces solo después de la activación, eso significará que, al activarse, Adapty creará un nuevo perfil anónimo y pasará al existente solo después de que llames a `identify`. Puedes pasar un customer user ID existente (uno que hayas usado antes) o uno nuevo. Si pasas uno nuevo, el nuevo perfil creado al activarse se vinculará automáticamente al customer user ID. :::note Por defecto, la creación de perfiles anónimos no afecta a los dashboards de análisis, porque las instalaciones se cuentan según los IDs de dispositivo. Un ID de dispositivo representa una única instalación de la app desde el store en un dispositivo y se regenera solo después de que la app se reinstale. No depende de si es una primera instalación o una repetida, ni de si se usa un customer user ID existente. Crear un perfil (al activar el SDK o cerrar sesión), iniciar sesión o actualizar la app sin reinstalarla no genera eventos de instalación adicionales. Si quieres contar instalaciones basándote en usuarios únicos en lugar de dispositivos, ve a **App settings** y configura [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // Place in the app main struct for SwiftUI or in AppDelegate for UIKit let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // Place in the app main struct for SwiftUI or in AppDelegate for UIKit let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } ``` </TabItem> </Tabs> ### Cerrar sesión de usuarios \{#log-users-out\} Si tienes un botón para que los usuarios cierren sesión, usa el método `logout`. :::important Cerrar la sesión de un usuario crea un nuevo perfil anónimo para ese usuario. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.logout() } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.logout { error in if error == nil { // successful logout } } ``` </TabItem> </Tabs> :::info Para volver a iniciar sesión en la app, usa el método `identify`. ::: ### Permitir compras sin inicio de sesión \{#allow-purchases-without-login\} Si tus usuarios pueden realizar compras tanto antes como después de iniciar sesión en tu app, debes asegurarte de que mantendrán el acceso después de iniciar sesión: 1. Cuando un usuario sin sesión iniciada realiza una compra, Adapty la vincula a su ID de perfil anónimo. 2. Cuando el usuario inicia sesión en su cuenta, Adapty pasa a trabajar con su perfil identificado. - Si es un customer user ID nuevo (por ejemplo, la compra se realizó antes del registro), Adapty asigna el customer user ID al perfil actual, por lo que se mantiene todo el historial de compras. - Si es un customer user ID existente (el customer user ID ya está vinculado a un perfil), debes obtener el nivel de acceso actual después del cambio de perfil. Puedes llamar a [`getProfile`](ios-check-subscription-status) justo después de la identificación, o [escuchar las actualizaciones del perfil](ios-check-subscription-status) para que los datos se sincronicen automáticamente. ## Próximos pasos \{#next-steps\} ¡Enhorabuena! Has implementado la lógica de pago in-app en tu app. ¡Te deseamos todo lo mejor con la monetización! Para sacar aún más partido a Adapty, puedes explorar estos temas: - [**Testing**](test-purchases-in-sandbox): Comprueba que todo funciona como se espera - [**Onboardings**](ios-onboardings): Engancha a los usuarios con onboardings e impulsa la retención - [**Integraciones**](configuration): Integra con servicios de atribución de marketing y análisis con una sola línea de código - [**Establecer atributos de perfil personalizados**](setting-user-attributes): Añade atributos personalizados a los perfiles de usuario y crea segmentos para lanzar pruebas A/B o mostrar diferentes paywalls a distintos usuarios --- # File: adapty-sdk-integration-skill --- --- title: "Integra Adapty en tu app iOS con la skill de integración del SDK" description: "Usa la skill adapty-sdk-integration para integrar el SDK de Adapty en tu app iOS de principio a fin con tu herramienta de codificación con IA." --- <AdaptySdkIntegrationSkill platform="iOS" /> :::important La skill está en beta. Si se queda parada o se comporta de forma inesperada, sigue la [guía de integración paso a paso](adapty-cursor) en su lugar: lleva a tu herramienta de IA por cada etapa con la documentación adecuada. ::: La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. --- # File: adapty-cursor --- --- title: "Integra Adapty en tu app iOS con ayuda de IA" description: "Una guía paso a paso para integrar Adapty en tu app iOS usando Cursor, Context7, ChatGPT, Claude u otras herramientas de IA." --- Esta guía te lleva paso a paso por la integración de Adapty en tu app iOS con una herramienta de codificación con IA — le proporcionas la documentación correcta de Adapty en el orden correcto. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Antes de empezar: configuración del dashboard \{#before-you-start-dashboard-setup\} Adapty requiere algo de configuración en el dashboard antes de escribir cualquier código del SDK. Puedes hacerlo con una skill de LLM interactiva o manualmente desde el Dashboard. ### Enfoque con skill (recomendado) \{#skill-approach-recommended\} La skill de la CLI de Adapty permite que tu LLM configure tu app, productos, niveles de acceso, paywalls y placements directamente, sin necesidad de abrir el Dashboard en cada paso. Solo necesitas [conectar tu store](integrate-payments) en el Dashboard. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` Una vez añadida la skill, ejecuta `/adapty-cli` en tu agente. Te guiará por cada paso, incluido cuándo abrir el Dashboard para conectar tu store. ### Enfoque desde el dashboard \{#dashboard-approach\} Si prefieres configurarlo todo manualmente, esto es lo que necesitas antes de escribir código. Tu LLM no puede buscar valores del dashboard por ti — tendrás que proporcionarlos tú mismo. 1. **Conecta tu store**: En el Adapty Dashboard, ve a **App settings → General**. Esto es necesario para que las compras funcionen. [Conectar App Store](integrate-payments) 2. **Copia tu clave pública del SDK**: En el Adapty Dashboard, ve a **App settings → General** y busca la sección **API keys**. En el código, esta es la cadena que pasas a `Adapty.activate("YOUR_PUBLIC_SDK_KEY")`. 3. **Crea al menos un producto**: En el Adapty Dashboard, ve a la página **Products**. No haces referencia a los productos directamente en el código — Adapty los entrega a través de flows o paywalls. [Añadir productos](quickstart-products) 4. **Crea un flow o paywall y un placement**: En el Adapty Dashboard, crea un flow (o un paywall si vas a construir la interfaz tú mismo), luego asígnalo a un placement en la página **Placements**. En el código, el ID del placement es el string que pasas a `Adapty.getFlow("YOUR_PLACEMENT_ID")`. [Crear flow](quickstart-paywalls) 5. **Configura los niveles de acceso**: En el Adapty Dashboard, configúralos por producto en la página **Products**. En el código, la cadena que se comprueba es `profile.accessLevels["premium"]`. El nivel de acceso `premium` predeterminado funciona para la mayoría de las apps. Si los usuarios de pago acceden a funciones distintas según el producto (por ejemplo, un plan `basic` frente a un plan `pro`), [crea niveles de acceso adicionales](assigning-access-level-to-a-product) antes de empezar a programar. :::tip Una vez que tengas los cinco, estarás listo para escribir código. Dile a tu LLM: "Mi clave SDK pública es X, mi placement ID es Y" para que pueda generar el código correcto de inicialización y obtención del paywall. ::: ### Configura cuando estés listo \{#set-up-when-ready\} Esto no es obligatorio para empezar a programar, pero lo necesitarás a medida que tu integración madure: - **Pruebas A/B**: Configúralas en la página **Placements**. No requieren cambios en el código. [Pruebas A/B](ab-tests) - **Paywalls y placements adicionales**: Añade más llamadas a `getPaywall` con diferentes IDs de placement. - **Integraciones de analíticas**: Configúralas en la página **Integrations**. La configuración varía según la integración. Consulta las [integraciones de analíticas](analytics-integration) y las [integraciones de atribución](attribution-integration). ## Proporciona documentación de Adapty a tu LLM \{#feed-adapty-docs-to-your-llm\} ### Usa Context7 (recomendado) [Context7](https://context7.com) es un servidor MCP que da a tu LLM acceso directo a la documentación actualizada de Adapty. Tu LLM obtiene automáticamente la documentación adecuada según lo que preguntes, sin necesidad de pegar URLs manualmente. Context7 funciona con **Cursor**, **Claude Code**, **Windsurf** y otras herramientas compatibles con MCP. Para configurarlo, ejecuta: ``` npx ctx7 setup ``` Esto detecta tu editor y configura el servidor de Context7. Para la configuración manual, consulta el [repositorio de Context7 en GitHub](https://github.com/upstash/context7). Una vez configurado, referencia la librería de Adapty en tus prompts: ``` Use the adaptyteam/adapty-docs library to look up how to install the iOS SDK ``` :::warning Aunque Context7 elimina la necesidad de pegar enlaces a la documentación manualmente, el orden de implementación es importante. Sigue el [tutorial de implementación](#implementation-walkthrough) paso a paso para asegurarte de que todo funciona. ::: ### Usa la documentación en texto plano \{#use-plain-text-docs\} Puedes acceder a cualquier página de la documentación de Adapty como Markdown en texto plano. Añade `.md` al final de su URL o haz clic en **Copy for LLM** bajo el título del artículo. Por ejemplo: [adapty-cursor.md](https://adapty.io/docs/es/adapty-cursor.md). Cada etapa del [tutorial de implementación](#implementation-walkthrough) incluye un bloque "Envía esto a tu LLM" con enlaces `.md` para pegar. Para obtener más documentación a la vez, consulta los [archivos índice y los subconjuntos por plataforma](#plain-text-doc-index-files) más abajo. ## Tutorial de implementación \{#implementation-walkthrough\} El resto de esta guía recorre la integración de Adapty en orden de implementación. Cada etapa incluye la documentación que debes enviar a tu LLM, qué deberías ver al terminar y los problemas más frecuentes. ### Planifica tu integración \{#plan-your-integration\} Antes de escribir código, pide a tu LLM que analice tu proyecto y cree un plan de implementación. Si tu herramienta de IA admite un modo de planificación (como el modo plan de Cursor o Claude Code), úsalo para que el LLM pueda leer tanto la estructura de tu proyecto como la documentación de Adapty antes de generar ningún código. Indica a tu LLM qué enfoque usas para las compras, ya que esto determina qué guías debe seguir: - [**Adapty Flow Builder**](adapty-flow-builder): Creas flows en el editor no-code de Adapty y el SDK los renderiza automáticamente. - [**Paywalls creados manualmente**](ios-quickstart-manual): Construyes tu propia UI de paywall en código, pero sigues usando Adapty para obtener productos y gestionar compras. - [**Modo Observer**](observer-vs-full-mode): Mantienes tu infraestructura de compras existente y usas Adapty solo para analíticas e integraciones. ¿No sabes cuál elegir? Lee la [tabla comparativa en la guía de inicio rápido](ios-quickstart-paywalls). ### Instalar y configurar el SDK Instala el paquete del SDK de Adapty mediante Swift Package Manager en Xcode y actívalo con tu clave pública del SDK. Esta es la base: sin ella, nada más funciona. **Guía:** [Instalar y configurar el SDK de Adapty](sdk-installation-ios) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/sdk-installation-ios.md ``` :::tip[Checkpoint] - **Expected:** La app compila y se ejecuta. La consola de Xcode muestra el log de activación de Adapty. - **Gotcha:** "Public API key is missing" → comprueba que reemplazaste el placeholder con tu clave real de **App settings**. ::: ### Mostrar flows o paywalls y gestionar compras \{#show-flows-or-paywalls-and-handle-purchases\} Obtén un flow o paywall por ID de placement, muéstralo y gestiona los eventos de compra. Las guías que necesitas dependen de cómo gestiones las compras. Prueba cada compra en el sandbox a medida que avanzas — no esperes hasta el final. Consulta [Pruebas de compra en sandbox](test-purchases-in-sandbox) para ver las instrucciones de configuración. <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Flow Builder" default> **Guías:** - [Activar compras con Flow Builder (inicio rápido)](ios-quickstart-paywalls) - [Obtener flows y su configuración](get-pb-paywalls) - [Mostrar flows](ios-present-paywalls) - [Gestionar eventos de flow](ios-handling-events) - [Responder a acciones de botones](handle-paywall-actions) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/ios-quickstart-paywalls.md - https://adapty.io/docs/es/get-pb-paywalls.md - https://adapty.io/docs/es/ios-present-paywalls.md - https://adapty.io/docs/es/ios-handling-events.md - https://adapty.io/docs/es/handle-paywall-actions.md ``` :::tip[Punto de control] - **Resultado esperado:** El flow aparece con los productos configurados. Al pulsar un producto se muestra el diálogo de compra en sandbox. - **Problema frecuente:** Flow vacío o error en `getFlow` → verifica que el ID del placement coincida exactamente con el del dashboard y que el placement tenga una audiencia asignada. ::: </TabItem> <TabItem value="manual" label="Paywalls manuales"> **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](ios-quickstart-manual) - [Obtener paywalls y productos](fetch-paywalls-and-products) - [Mostrar paywall diseñado con Remote Config](present-remote-config-paywalls) - [Realizar compras](making-purchases) - [Restaurar compras](restore-purchase) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/ios-quickstart-manual.md - https://adapty.io/docs/es/fetch-paywalls-and-products.md - https://adapty.io/docs/es/present-remote-config-paywalls.md - https://adapty.io/docs/es/making-purchases.md - https://adapty.io/docs/es/restore-purchase.md ``` :::tip[Checkpoint] - **Esperado:** Tu paywall personalizado muestra los productos obtenidos desde Adapty. Al pulsar un producto, se activa el diálogo de compra en sandbox. - **Problema frecuente:** Array de productos vacío → verifica que el paywall tenga productos asignados en el dashboard y que el placement tenga una audiencia. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Guías:** - [Descripción general del Observer mode](observer-vs-full-mode) - [Implementar el Observer mode](implement-observer-mode) - [Reportar transacciones en el Observer mode](report-transactions-observer-mode) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/observer-vs-full-mode.md - https://adapty.io/docs/es/implement-observer-mode.md - https://adapty.io/docs/es/report-transactions-observer-mode.md ``` :::tip[Checkpoint] - **Esperado:** Tras una compra en sandbox usando tu flujo de compra existente, la transacción aparece en el **Event Feed** del Adapty Dashboard. - **Problema frecuente:** Sin eventos → verifica que estás reportando las transacciones a Adapty y que las App Store Server Notifications están configuradas. ::: </TabItem> </Tabs> ### Comprueba el estado de la suscripción \{#check-subscription-status\} Tras una compra, comprueba el perfil del usuario para detectar un nivel de acceso activo y restringir el contenido premium. **Guía:** [Comprobar el estado de la suscripción](ios-check-subscription-status) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/ios-check-subscription-status.md ``` :::tip[Punto de control] - **Resultado esperado:** Tras una compra en sandbox, `profile.accessLevels["premium"]?.isActive` devuelve `true`. - **Problema frecuente:** `accessLevels` vacío tras la compra → comprueba que el producto tiene un nivel de acceso asignado en el dashboard. ::: ### Identificar usuarios \{#identify-users\} Vincula las cuentas de usuario de tu app a los perfiles de Adapty para que las compras persistan entre dispositivos. :::important Omite este paso si tu app no tiene autenticación. ::: **Guía:** [Identificar usuarios](ios-quickstart-identify) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/ios-quickstart-identify.md ``` :::tip[Checkpoint] - **Esperado:** Después de llamar a `Adapty.identify("your-user-id")`, la sección **Profiles** del dashboard muestra tu ID de usuario personalizado. - **Problema frecuente:** Llama a `identify` después de la activación pero antes de obtener los paywalls para evitar una atribución de perfil anónima. ::: ### Prepararse para el lanzamiento \{#prepare-for-release\} Cuando tu integración funcione en el sandbox, repasa el checklist de lanzamiento para asegurarte de que todo está listo para producción. **Guía:** [Checklist de lanzamiento](release-checklist) Envía esto a tu LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/es/release-checklist.md ``` :::tip[Checkpoint] - **Expected:** Todos los elementos del checklist confirmados: conexión al store, notificaciones del servidor, flujo de compra, verificaciones de nivel de acceso y requisitos de privacidad. - **Gotcha:** Si faltan las notificaciones del servidor de App Store → configúralas en **App settings → iOS SDK** o los eventos no aparecerán en el dashboard. ::: ## Archivos de índice de documentación en texto plano \{#plain-text-doc-index-files\} Si necesitas dar a tu LLM un contexto más amplio que el de páginas individuales, ofrecemos archivos de índice que listan o combinan toda la documentación de Adapty: - [`llms.txt`](https://adapty.io/docs/es/llms.txt): Lista todas las páginas con enlaces `.md`. Es un [estándar emergente](https://llmstxt.org/) para hacer los sitios web accesibles a los LLM. Ten en cuenta que para algunos agentes de IA (p. ej., ChatGPT) tendrás que descargar `llms.txt` y subirlo al chat como archivo. - [`llms-full.txt`](https://adapty.io/docs/es/llms-full.txt): Toda la documentación de Adapty combinada en un único archivo. Es muy grande: úsalo solo cuando necesites una visión completa. - [`ios-llms.txt`](https://adapty.io/docs/es/ios-llms.txt) e [`ios-llms-full.txt`](https://adapty.io/docs/es/ios-llms-full.txt) específicos de iOS: subconjuntos por plataforma que ahorran tokens en comparación con el sitio completo. --- # File: get-pb-paywalls --- --- title: "Obtener flows y paywalls - iOS" description: "Obtén flows y paywalls de Adapty en tu app de iOS." --- <SDKv4> <MethodPromo method="getFlow" /> Tras [diseñar tu flow o tu paywall con el Paywall Builder](adapty-paywall-builder), puedes mostrarlo en tu aplicación móvil. El primer paso es obtener el flow o el paywall asociado al placement y su configuración de vista, tal como se describe a continuación. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una aplicación móvil? Consulta nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow/paywall e incorpora productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow/paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-ios) en tu app móvil. </details> ## Obtener el flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall con el Flow Builder o el Paywall Builder, no tienes que preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese flow o paywall ya incluye tanto lo que se debe mostrar como la forma en que debe mostrarse. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y luego presentarlo en tu app. Obtén el flow o paywall y su [configuración de vista](get-pb-paywalls#fetch-the-view-configuration) lo antes posible — idealmente mucho antes de presentarlo. En cuanto obtengas la configuración de vista, el SDK comienza a descargar y cachear sus imágenes en segundo plano. Cuanto antes la obtengas, más tiempo tendrán esas descargas para completarse. Para cuando presentes el flow o paywall, su configuración e imágenes ya pueden estar en caché y listas para mostrarse. Para obtener un flow o paywall, usa el método `getFlow`: <Tabs> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") // el flow/paywall solicitado } catch { // gestiona el error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // el flow/paywall solicitado case let .failure(error): // gestiona el error } } ``` </TabItem> </Tabs> Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no reciban los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo intermitente que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché actualizada periódicamente que se describe arriba y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede consistir en diferentes peticiones internamente.</p> | Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :---------- | | Flow | Un objeto `AdaptyFlow` que contiene el placement, identificadores (`id`, `variationId`), nombre, Remote Configs y un indicador `hasViewConfiguration` que señala si el flow incluye una configuración de vista. Para obtener los productos reales con fines de precarga, UI personalizada o comprobaciones programáticas, llama a `getPaywallProducts(flow:)`. | ## Obtener la configuración de vista \{#fetch-the-view-configuration\} Después de obtener el flow o el paywall, comprueba si incluye una configuración de vista mediante `flow.hasViewConfiguration`. Este flag distingue cómo se diseñó el placement en el Adapty Dashboard: - **`true`** — el placement fue diseñado en el **Flow Builder** (un flow) o en el **Paywall Builder** (un paywall). Adapty renderiza la interfaz por ti. Continúa con los pasos a continuación para obtener la configuración de vista y [mostrar el flow o el paywall](ios-present-paywalls). - **`false`** — el placement es un paywall personalizado sin interfaz del Builder. Usa el método `getFlowConfiguration` para cargar la configuración de la vista. ```swift showLineNumbers guard flow.hasViewConfiguration else { // handle as remote config paywall return } let flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow) ``` Parámetros: | Parámetro | Presencia | Descripción | | :----------------------- | :------------- | :---------- | | **forFlow** | obligatorio | Un objeto `AdaptyFlow` obtenido mediante `Adapty.getFlow`. | | **locale** | <p>opcional</p><p>por defecto: `nil`</p> | El identificador de la [localización del paywall](add-paywall-locale-in-adapty-paywall-builder). Se espera como un código de idioma con una o dos subetiquetas separadas por `-` (p. ej., `en`, `pt-br`). Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes). | | **loadTimeout** | por defecto: 5 seg | Este valor limita el tiempo de espera para este método. Si se alcanza el límite, se devolverán los datos en caché o el fallback local. Ten en cuenta que en casos poco frecuentes este método puede superar ligeramente el tiempo indicado en `loadTimeout`, ya que la operación puede constar de distintas solicitudes internamente. | | **products** | opcional | Proporciona un array de objetos `AdaptyPaywallProduct` para optimizar el momento en que se muestran los productos en pantalla. Si se pasa `nil`, AdaptyUI obtendrá automáticamente los productos necesarios. | | **systemRequestsHandler** | opcional | Un objeto que implementa `AdaptySystemRequestsHandler` y gestiona las solicitudes de permisos del sistema y valoraciones activadas por acciones del flow. Solo es necesario si tu flow incluye este tipo de acciones. | | **assetsResolver** | opcional | Un diccionario `[String: AdaptyCustomAsset]` que sobreescribe imágenes y vídeos en el flow/paywall. Consulta [Personalizar assets](#customize-assets). | | **timerResolver** | opcional | Un objeto que implementa `AdaptyTimerResolver` y proporciona fechas de finalización para los temporizadores definidos por el desarrollador. Consulta [Configurar temporizadores definidos por el desarrollador](#set-up-developer-defined-timers). | Una vez cargado, [presenta el flow/paywall](ios-present-paywalls). ## Obtén un flow o paywall para la audiencia predeterminada y cárgalo más rápido \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Por lo general, los flows y paywalls se obtienen casi al instante, por lo que no es necesario preocuparse por acelerar este proceso. Sin embargo, si tienes muchas audiencias y placements, y tus usuarios tienen una conexión a internet lenta, obtener un flow o paywall puede tardar más de lo que desearías. En esos casos, puede que quieras mostrar un flow o paywall predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar nada. Para solucionar esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow o paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el flow o paywall con el método `getFlow`, tal como se detalla en la sección [Obtener información del paywall](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar distintos paywalls para diferentes versiones de la app (la actual y las futuras), puedes encontrarte con dificultades. Tendrás que diseñar paywalls compatibles con la versión actual (legacy) o asumir que los usuarios de esa versión podrían tener problemas con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluyendo por país, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas limitaciones a cambio de una obtención más rápida del flow o del paywall, usa el método `getFlowForDefaultAudience` de la siguiente manera. De lo contrario, quédate con `getFlow` descrito [anteriormente](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder). ::: ```swift showLineNumbers Adapty.getFlowForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // the requested flow case let .failure(error): // handle the error } } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | ## Personalizar recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu paywall/flow, implementa los recursos personalizados. Las imágenes y vídeos destacados tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizados, apuntas a estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Muestra una imagen o vídeo diferente a algunos usuarios. - Muestra una imagen de vista previa local mientras se carga la imagen principal remota. - Muestra una imagen de vista previa antes de reproducir un vídeo. - Indica la resolución en píxeles del vídeo para que el reproductor reserve espacio en el layout (relación de aspecto = `width / height`) antes de que cargue el vídeo. Pasa `nil` para omitirlo. A continuación se muestra un ejemplo de cómo puedes proporcionar assets personalizados mediante un diccionario sencillo: ```swift showLineNumbers let customAssets: [String: AdaptyCustomAsset] = [ // Show a local image using a custom ID "custom_image": .image( .uiImage(value: UIImage(named: "image_name")!) ), // Show a local preview image while a remote main image is loading "hero_image": .image( .remote( url: URL(string: "https://example.com/image.jpg")!, preview: UIImage(named: "preview_image") ) ), // Show a local video with a preview image and a known resolution "hero_video": .video( .file( url: Bundle.main.url(forResource: "custom_video", withExtension: "mp4")!, preview: .uiImage(value: UIImage(named: "video_preview")!), resolution: CGSize(width: 1080, height: 1920) ) ), ] let flowConfig = try await AdaptyUI.getFlowConfiguration( forFlow: flow, assetsResolver: customAssets ) ``` :::note Si no se encuentra un asset, el paywall/flow volverá a su apariencia predeterminada. ::: ## Configurar temporizadores definidos por el desarrollador \{#set-up-developer-defined-timers\} Para usar temporizadores personalizados en tu app, crea un objeto que implemente el protocolo `AdaptyTimerResolver`. Este objeto define cómo debe renderizarse cada temporizador personalizado. Si lo prefieres, puedes usar directamente un diccionario `[String: Date]`, ya que ya cumple con este protocolo. Aquí tienes un ejemplo: ```swift showLineNumbers @MainActor struct AdaptyTimerResolverImpl: AdaptyTimerResolver { func timerEndAtDate(for timerId: String) -> Date { switch timerId { case "CUSTOM_TIMER_6H": Date(timeIntervalSinceNow: 3600.0 * 6.0) // 6 hours case "CUSTOM_TIMER_NY": Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)) ?? Date(timeIntervalSinceNow: 3600.0) default: Date(timeIntervalSinceNow: 3600.0) // 1 hour } } } ``` En este ejemplo, `CUSTOM_TIMER_NY` y `CUSTOM_TIMER_6H` son los **Timer ID** de los temporizadores definidos por el desarrollador que configuraste en el Adapty Dashboard. El `timerResolver` garantiza que tu app actualice dinámicamente cada temporizador con el valor correcto. Por ejemplo: - `CUSTOM_TIMER_NY`: El tiempo restante hasta que finalice el temporizador, como el Año Nuevo. - `CUSTOM_TIMER_6H`: El tiempo restante en un período de 6 horas que comenzó cuando el usuario abrió el paywall. </SDKv4> <SDKv3> Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu app móvil. El primer paso es obtener el paywall asociado al placement y su configuración de vista, tal como se describe a continuación. Ten en cuenta que este tema hace referencia a paywalls personalizados con el Paywall Builder. Si implementas tus paywalls manualmente, consulta [Obtener paywalls y productos para paywalls con Remote Config](fetch-paywalls-and-products). :::tip Antes de empezar a mostrar paywalls en tu aplicación móvil, asegúrate de que ya has: <details> <summary>Antes de empezar a mostrar paywalls en tu aplicación móvil</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-ios) en tu aplicación móvil. </details> ## Obtén el paywall diseñado con Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Si has [diseñado un paywall con el Paywall Builder](adapty-paywall-builder), no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Este tipo de paywall incluye tanto qué mostrar como cómo mostrarlo. Aun así, necesitas obtener su ID a través del placement, su configuración de vista, y luego presentarlo en tu app. Para garantizar un rendimiento óptimo, es fundamental obtener el paywall y su [configuración de vista](get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) lo antes posible, dejando tiempo suficiente para que las imágenes se descarguen antes de mostrárselas al usuario. Para obtener un paywall, usa el método `getPaywall`: <Tabs> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywall("YOUR_PLACEMENT_ID") // the requested paywall } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-paywall-locale-in-adapty-paywall-builder). Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos utilizarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché de actualización periódica descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls y que el servicio sea fiable incluso cuando la conexión a internet sea escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el límite, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede estar compuesta por distintas solicitudes internas.</p> | Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :---------- | | Paywall | Un objeto [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall) con una lista de IDs de productos, el identificador del paywall, el Remote Config y varias otras propiedades. | ## Obtener la configuración de vista de un paywall creado con Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Asegúrate de activar el botón **Show on device** en el Paywall Builder. Si esta opción no está activada, la configuración de vista no estará disponible para recuperar. ::: Después de obtener el paywall, comprueba si incluye una configuración de vista, lo que indica que fue creado con Paywall Builder. Esto te indicará cómo mostrar el paywall. Si la configuración de vista está presente, trátalo como un paywall de Paywall Builder; si no, [trátalo como un paywall de Remote Config](present-remote-config-paywalls). Usa el método `getPaywallConfiguration` para cargar la configuración de la vista. ```swift showLineNumbers guard paywall.hasViewConfiguration else { // use your custom logic return } do { let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall, products: products ) // use loaded configuration } catch { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | :----------------------- | :------------- | :---------- | | **paywall** | obligatorio | Un objeto `AdaptyPaywall` para obtener un controlador del paywall deseado. | | **loadTimeout** | por defecto: 5 seg | Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local. Ten en cuenta que en casos excepcionales este método puede agotar el tiempo ligeramente después de lo especificado en `loadTimeout`, ya que la operación puede constar de distintas solicitudes internamente. | | **products** | opcional | Proporciona un array de objetos `AdaptyPaywallProduct` para optimizar el tiempo de visualización de los productos en pantalla. Si se pasa `nil`, AdaptyUI obtendrá automáticamente los productos necesarios. | :::note Si estás usando varios idiomas, aprende cómo añadir una [localización en el Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) y cómo usar los códigos de idioma correctamente [aquí](localizations-and-locale-codes). ::: Una vez cargado, [muestra el paywall](ios-present-paywalls). ## Obtén un paywall para la audiencia predeterminada y acelera la carga \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Normalmente, los paywalls se obtienen casi de forma instantánea, así que no necesitas preocuparte por optimizar este proceso. Sin embargo, cuando tienes muchas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, la carga de un paywall puede tardar más de lo deseable. En esos casos, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia fluida en lugar de no mostrar ninguno. Para solucionar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement indicado para la audiencia **All Users**. Sin embargo, es importante entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, tal como se detalla en la sección [Obtener información del paywall](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar paywalls diferentes para distintas versiones de la app (la actual y las futuras), puede que te encuentres con dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (legacy), o asumir que los usuarios con esa versión podrían tener problemas con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluida la basada en países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estos inconvenientes para beneficiarte de una obtención más rápida del paywall, usa el método `getPaywallForDefaultAudience` de la siguiente manera. De lo contrario, quédate con `getPaywall` descrito [anteriormente](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder). ::: ```swift showLineNumbers Adapty.getPaywallForDefaultAudience(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` :::note El método `getPaywallForDefaultAudience` está disponible a partir de la versión 2.11.2 del SDK de iOS. ::: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | requerido | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>valor por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | valor por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de lo inestable que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | ## Personalizar recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu paywall, implementa recursos personalizados. Las imágenes y vídeos hero tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizados, seleccionas estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. :::important Para usar esta función, actualiza el SDK de Adapty para iOS a la versión 3.7.0 o superior. ::: Aquí tienes un ejemplo de cómo puedes proporcionar assets personalizados mediante un diccionario sencillo: ```swift showLineNumbers let customAssets: [String: AdaptyCustomAsset] = [ // Show a local image using a custom ID "custom_image": .image( .uiImage(value: UIImage(named: "image_name")!) ), // Show a local preview image while a remote main image is loading "hero_image": .image( .remote( url: URL(string: "https://example.com/image.jpg")!, preview: UIImage(named: "preview_image") ) ), // Show a local video with a preview image "hero_video": .video( .file( url: Bundle.main.url(forResource: "custom_video", withExtension: "mp4")!, preview: .uiImage(value: UIImage(named: "video_preview")!) ) ), ] let paywallConfig = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall, assetsResolver: customAssets ) ``` :::note Si no se encuentra un asset, el paywall volverá a su apariencia predeterminada. ::: ## Configurar temporizadores definidos por el desarrollador \{#set-up-developer-defined-timers\} Para usar temporizadores personalizados en tu app, crea un objeto que implemente el protocolo `AdaptyTimerResolver`. Este objeto define cómo se debe renderizar cada temporizador personalizado. Si lo prefieres, puedes usar directamente un diccionario `[String: Date]`, ya que ya es compatible con este protocolo. Aquí tienes un ejemplo: ```swift showLineNumbers @MainActor struct AdaptyTimerResolverImpl: AdaptyTimerResolver { func timerEndAtDate(for timerId: String) -> Date { switch timerId { case "CUSTOM_TIMER_6H": Date(timeIntervalSinceNow: 3600.0 * 6.0) // 6 hours case "CUSTOM_TIMER_NY": Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)) ?? Date(timeIntervalSinceNow: 3600.0) default: Date(timeIntervalSinceNow: 3600.0) // 1 hour } } } ``` En este ejemplo, `CUSTOM_TIMER_NY` y `CUSTOM_TIMER_6H` son los **Timer ID**s de los temporizadores definidos por el desarrollador que configuraste en el Adapty Dashboard. El `timerResolver` garantiza que tu app actualice dinámicamente cada temporizador con el valor correcto. Por ejemplo: - `CUSTOM_TIMER_NY`: El tiempo restante hasta el fin del temporizador, como el día de Año Nuevo. - `CUSTOM_TIMER_6H`: El tiempo que queda en un período de 6 horas que empezó cuando el usuario abrió el paywall. </SDKv3> --- # File: ios-present-paywalls --- --- title: "Mostrar flows y paywalls - iOS" description: "Presenta flows y paywalls a los usuarios en tu app iOS." --- <SDKv4> <MethodPromo method="getFlow" label="Display flows and paywalls" /> Si has creado un flow o paywall, no necesitas preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Tanto el contenido como la forma en que debe presentarse ya están definidos dentro del propio flow o paywall. Para obtener el objeto `AdaptyUI.FlowConfiguration` que se usa a continuación, consulta [Obtener flows y paywalls](get-pb-paywalls). ## Presentar flows y paywalls en SwiftUI \{#present-flows-and-paywalls-in-swiftui\} ### Presentar como vista modal \{#present-as-a-modal-view\} Para mostrar un flow o paywall en la pantalla del dispositivo como una vista modal, usa el modificador `.flow` en SwiftUI. La llamada mínima requiere `isPresented`, `flowConfiguration` y los cinco callbacks obligatorios: ```swift showLineNumbers title="SwiftUI" .flow( isPresented: $flowPresented, flowConfiguration: <AdaptyUI.FlowConfiguration>, didFinishPurchase: { _, _ in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { _, _ in /* handle the error */ }, didFinishRestore: { _ in /* check access level and dismiss */ }, didFailRestore: { _ in /* handle the error */ }, didReceiveError: { _ in flowPresented = false } ) ``` Para mayor control, añade callbacks opcionales como `didPerformAction` para gestionar los toques de botón: ```swift showLineNumbers title="SwiftUI" @State var flowPresented = false // ensure that you manage this variable state and set it to `true` at the moment you want to show the flow or paywall var body: some View { Text("Hello, AdaptyUI!") .flow( isPresented: $flowPresented, flowConfiguration: <AdaptyUI.FlowConfiguration>, didPerformAction: { action in switch action { case .close: flowPresented = false default: // Handle other actions break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) } ``` Parámetros: | Parámetro | Obligatorio | Descripción | |:-----------------------|:------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | obligatorio | Un binding que gestiona si la pantalla del flow o paywall se muestra. | | **flowConfiguration** | obligatorio | Un objeto `AdaptyUI.FlowConfiguration` que contiene los detalles visuales del flow o paywall. Usa el método `AdaptyUI.getFlowConfiguration(forFlow:)`. Consulta [Obtener flows y paywalls](get-pb-paywalls) para más detalles. | | **didFinishPurchase** | obligatorio | Se invoca cuando `Adapty.makePurchase()` se completa con éxito. El flow no se cierra automáticamente — establece tu binding de presentación en `false` aquí, o no hagas nada para dejar que el flow continúe tras la compra. | | **didFailPurchase** | obligatorio | Se invoca cuando `Adapty.makePurchase()` falla. | | **didFinishRestore** | obligatorio | Se invoca cuando `Adapty.restorePurchases()` se completa con éxito. | | **didFailRestore** | obligatorio | Se invoca cuando `Adapty.restorePurchases()` falla. | | **didReceiveError** | obligatorio | Se invoca ante un error de renderizado o un error en tiempo de ejecución del script del flow (por ejemplo, una excepción JavaScript, código `AdaptyUIError` `4105`). Para errores de renderizado, [contacta con el soporte de Adapty](mailto:support@adapty.io). | | **fullScreen** | opcional | Determina si el flow o paywall aparece en modo pantalla completa o como una hoja. Por defecto es `true`. | | **didAppear** | opcional | Se invoca cuando la vista del flow o paywall fue presentada. | | **didDisappear** | opcional | Se invoca cuando la vista del flow o paywall fue cerrada. | | **didPerformAction** | opcional | Se invoca cuando el usuario pulsa un botón. Dos IDs de acción están predefinidos: `close` y `openURL`; los demás son personalizados y pueden configurarse en el builder. | | **didSelectProduct** | opcional | Se invoca cuando el usuario o el sistema selecciona un producto para su compra. | | **didStartPurchase** | opcional | Se invoca cuando el usuario inicia el proceso de compra. | | **didFinishWebPaymentNavigation** | opcional | Se invoca cuando la navegación de pago web finaliza. | | **didStartRestore** | opcional | Se invoca cuando el usuario inicia el proceso de restauración. | | **didFailLoadingProducts** | opcional | Se invoca cuando ocurren errores durante la carga de productos. Devuelve `true` para reintentar la carga. | | **didPartiallyLoadProducts** | opcional | Se invoca cuando los productos se cargan parcialmente. | | **showAlertItem** | opcional | Un binding que gestiona la visualización de elementos de alerta encima del flow o paywall. | | **showAlertBuilder** | opcional | Una función para renderizar la vista de alerta. | | **placeholderBuilder** | opcional | Una función para renderizar la vista de marcador de posición mientras el flow o paywall se carga. Por defecto es un `ProgressView`. | Consulta el tema [iOS - Gestión de eventos](ios-handling-events) para obtener más detalles sobre los parámetros. ### Presentar como una vista no modal \{#present-as-a-non-modal-view\} También puedes presentar flows y paywalls como destinos de navegación o vistas en línea dentro del flujo de navegación de tu app. Usa `AdaptyFlowView` directamente en tus vistas SwiftUI: ```swift showLineNumbers title="SwiftUI" AdaptyFlowView( flowConfiguration: <AdaptyUI.FlowConfiguration>, didFinishPurchase: { product, purchaseResult in // Dismiss the view, or do nothing to let the flow continue }, didFailPurchase: { product, error in // Handle purchase failure }, didFinishRestore: { profile in // Handle successful restore }, didFailRestore: { error in // Handle restore failure }, didReceiveError: { error in // Handle the error (rendering or JS exception from the flow script). } ) ``` ## Presenta flows y paywalls en UIKit \{#present-flows-and-paywalls-in-uikit\} Para mostrar el flow o paywall en la pantalla del dispositivo, sigue estos pasos: 1. Inicializa el flow visual que quieres mostrar utilizando el método `AdaptyUI.flowController(with:delegate:)`: ```swift showLineNumbers title="Swift" import AdaptyUI let visualFlow = try AdaptyUI.flowController( with: <AdaptyUI.FlowConfiguration>, delegate: <AdaptyFlowControllerDelegate> ) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :----------------------- | :------- | :---------- | | **flowConfiguration** | obligatorio | Un objeto `AdaptyUI.FlowConfiguration` que contiene los detalles visuales del flow o paywall. Usa el método `AdaptyUI.getFlowConfiguration(forFlow:)`. Consulta el tema [Obtener flows y paywalls](get-pb-paywalls) para más detalles. | | **delegate** | obligatorio | Un `AdaptyFlowControllerDelegate` para escuchar los eventos del flow y del paywall. Consulta el tema [Gestionar eventos de flow y paywall](ios-handling-events) para más detalles. | Devuelve: | Objeto | Descripción | | :---------------------- | :------------------------------------------------------- | | **AdaptyFlowController** | Un objeto que representa la pantalla de flow o paywall solicitada. | 2. Una vez creado el objeto correctamente, puedes mostrarlo en la pantalla del dispositivo: ```swift showLineNumbers title="Swift" present(visualFlow, animated: true) ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Consulta nuestras [apps de ejemplo](sample-apps), que demuestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: </SDKv4> <SDKv3> Si has personalizado un paywall con el Paywall Builder, no tienes que preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Este tipo de paywall contiene tanto lo que se debe mostrar en el paywall como la forma en que debe mostrarse. Para obtener el objeto `AdaptyUI.PaywallConfiguration` que se usa a continuación, consulta [Obtener paywalls del Paywall Builder y su configuración](get-pb-paywalls). ## Presenta paywalls en SwiftUI \{#present-paywalls-in-swiftui\} ### Presentar como vista modal \{#present-as-a-modal-view\} Para mostrar el paywall visual en la pantalla del dispositivo como una vista modal, usa el modificador `.paywall` en SwiftUI: ```swift showLineNumbers title="SwiftUI" @State var paywallPresented = false // ensure that you manage this variable state and set it to `true` at the moment you want to show the paywall var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywallConfiguration: <AdaptyUI.PaywallConfiguration>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, didFinishPurchase: { product, profile in paywallPresented = false }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false } ) } ``` Parámetros: | Parámetro | Requerido | Descripción | |:----------------------------------|:----------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | requerido | Un binding que controla si se muestra la pantalla del paywall. | | **paywallConfiguration** | requerido | Un objeto `AdaptyUI.PaywallConfiguration` que contiene los detalles visuales del paywall. Usa el método `AdaptyUI.paywallConfiguration(for:products:viewConfiguration:observerModeResolver:tagResolver:timerResolver:)`. Consulta el tema [Obtener paywalls del Paywall Builder y su configuración](get-pb-paywalls) para más detalles. | | **didFailPurchase** | requerido | Se invoca cuando `Adapty.makePurchase()` falla. | | **didFinishRestore** | requerido | Se invoca cuando `Adapty.restorePurchases()` se completa correctamente. | | **didFailRestore** | requerido | Se invoca cuando `Adapty.restorePurchases()` falla. | | **didFailRendering** | requerido | Se invoca si ocurre un error al renderizar la interfaz. En ese caso, [contacta con el soporte de Adapty](mailto:support@adapty.io). | | **fullScreen** | opcional | Determina si el paywall se muestra en modo pantalla completa o como modal. Por defecto es `true`. | | **didAppear** | opcional | Se invoca cuando la vista del paywall se ha presentado. | | **didDisappear** | opcional | Se invoca cuando la vista del paywall se ha cerrado. | | **didPerformAction** | opcional | Se invoca cuando el usuario pulsa un botón. Cada botón tiene un ID de acción diferente. Hay dos IDs predefinidos: `close` y `openURL`; el resto son personalizados y se pueden definir en el builder. | | **didSelectProduct** | opcional | Se invoca cuando se selecciona un producto para la compra (por el usuario o por el sistema). | | **didStartPurchase** | opcional | Se invoca cuando el usuario inicia el proceso de compra. | | **didFinishPurchase** | opcional | Se invoca cuando `Adapty.makePurchase()` se completa correctamente. | | **didFinishWebPaymentNavigation** | opcional | Se invoca cuando finaliza la navegación del pago web. | | **didStartRestore** | opcional | Se invoca cuando el usuario inicia el proceso de restauración. | | **didFailLoadingProducts** | opcional | Se invoca cuando ocurren errores durante la carga de productos. Devuelve `true` para reintentar la carga. | | **didPartiallyLoadProducts** | opcional | Se invoca cuando los productos se cargan de forma parcial. | | **showAlertItem** | opcional | Un binding que gestiona la visualización de elementos de alerta sobre el paywall. | | **showAlertBuilder** | opcional | Una función para renderizar la vista de alerta. | | **placeholderBuilder** | opcional | Una función para renderizar la vista de marcador de posición mientras se carga el paywall. | Consulta el tema [iOS - Gestión de eventos](ios-handling-events) para más detalles sobre los parámetros. ### Presentar como una vista no modal \{#present-as-a-non-modal-view\} También puedes presentar paywalls como destinos de navegación o vistas en línea dentro del flujo de navegación de tu app. Usa `AdaptyPaywallView` directamente en tus vistas SwiftUI: ```swift showLineNumbers title="SwiftUI" AdaptyPaywallView( paywallConfiguration: <AdaptyUI.PaywallConfiguration>, didFailPurchase: { product, error in // Handle purchase failure }, didFinishRestore: { profile in // Handle successful restore }, didFailRestore: { error in // Handle restore failure }, didFailRendering: { error in // Handle rendering error } ) ``` ## Presentar paywalls en UIKit \{#present-paywalls-in-uikit\} Para mostrar el paywall visual en la pantalla del dispositivo, sigue estos pasos: 1. Inicializa el paywall visual que quieres mostrar usando el método `.paywallController(for:products:viewConfiguration:delegate:)`: ```swift showLineNumbers title="Swift" import AdaptyUI let visualPaywall = AdaptyUI.paywallController( with: <paywall configuration object>, delegate: <AdaptyPaywallControllerDelegate> ) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :----------------------- | :------- | :---------- | | **paywall configuration** | requerido | Un objeto `AdaptyUI.PaywallConfiguration` que contiene los detalles visuales del paywall. Usa el método `AdaptyUI.getPaywallConfiguration(forPaywall:locale:)`. Consulta el tema [Obtener paywalls del Paywall Builder y su configuración](get-pb-paywalls) para más detalles. | | **delegate** | requerido | Un `AdaptyPaywallControllerDelegate` para escuchar los eventos del paywall. Consulta el tema [Gestión de eventos del paywall](ios-handling-events) para más detalles. | Devuelve: | Objeto | Descripción | | :---------------------- | :--------------------------------------------------- | | **AdaptyPaywallController** | Un objeto que representa la pantalla del paywall solicitada | 2. Una vez creado el objeto correctamente, puedes mostrarlo en la pantalla del dispositivo: ```swift showLineNumbers title="Swift" present(visualPaywall, animated: true) ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una aplicación móvil? Echa un vistazo a nuestras [aplicaciones de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funciones básicas. ::: </SDKv3> --- # File: handle-paywall-actions --- --- title: "Responder a acciones de flow - iOS" description: "Gestiona las acciones de botones y procesa la entrada del usuario desde flows de paywall y onboarding en tu app iOS." --- <SDKv4> Si estás construyendo flows o paywalls con el Adapty Flow Builder o Paywall Builder, es fundamental configurar los botones correctamente: 1. Añade un [botón en el Paywall Builder](paywall-buttons) y asígnale una acción ya existente o crea un ID de acción personalizado. 2. Escribe código en tu app para gestionar cada acción que hayas asignado. Esta guía explica cómo gestionar acciones personalizadas y acciones preexistentes en tu código. :::warning **Solo el cierre del flow/paywall y la apertura de URLs se gestionan automáticamente.** El resto de acciones de botones requieren una implementación adecuada en el código de la app. ::: :::note El SDK de iOS puede responder a solicitudes de permisos del sistema, como notificaciones push o acceso a la cámara, mediante un `AdaptySystemRequestsHandler`. Los flows todavía no activan estas solicitudes, por lo que no necesitas gestionarlas por ahora. ::: ## Cerrar flows y paywalls \{#close-flows-and-paywalls\} Para añadir un botón que cierre tu flow o paywall: 1. En el editor, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un manejador para la acción `close`. :::info En el SDK de iOS, la acción `close` cierra el flow o paywall por defecto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un flow podría disparar la apertura de otro. ::: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false // dismiss the flow or paywall default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` ## Abrir URLs desde flows y paywalls \{#open-urls-from-flows-and-paywalls\} :::tip Si quieres añadir un grupo de enlaces (p. ej., términos de uso y restauración de compras), añade un elemento **Link** en el builder y trátalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu flow o paywall (p. ej., **Terms of use** o **Privacy policy**): 1. En el builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieras abrir. 2. En el código de tu app, implementa un handler para la acción `openURL` que abra la URL recibida en un navegador. :::info En el SDK de iOS, la acción `openURL` abre la URL de forma predeterminada. Sin embargo, puedes modificar este comportamiento en tu código si lo necesitas. ::: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case let .openURL(url): UIApplication.shared.open(url, options: [:]) // default behavior default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` ## Manejar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que maneje cualquier otra acción: 1. En el builder, añade un botón, asígnale la acción **Custom** y dale un ID. 2. En el código de tu app, implementa un handler para el ID de acción que hayas creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro flow o paywall: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case let .custom(id): if id == "openNewPaywall" { // Display another flow or paywall } default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` </SDKv4> <SDKv3> Si estás creando paywalls con el Paywall Builder de Adapty, es fundamental configurar los botones correctamente: 1. Añade un [botón en el Paywall Builder](paywall-buttons) y asígnale una acción preexistente o crea un ID de acción personalizado. 2. Escribe código en tu app para gestionar cada acción que hayas asignado. Esta guía explica cómo gestionar acciones personalizadas y preexistentes en tu código. :::warning **Solo las compras, restauraciones, cierres del paywall y apertura de URLs se gestionan automáticamente.** Todas las demás acciones de botón requieren una implementación de respuesta adecuada en el código de la app. ::: ## Cerrar paywalls \{#close-paywalls\} Para añadir un botón que cierre tu paywall: 1. En el Paywall Builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un manejador para la acción `close` que descarte el paywall. :::info En el SDK de iOS, la acción `close` cierra el paywall de forma predeterminada. Sin embargo, puedes modificar este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un paywall podría abrir otro. ::: ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case .close: controller.dismiss(animated: true) // default behavior break } } ``` ## Abrir URLs desde paywalls \{#open-urls-from-paywalls\} :::tip Si quieres añadir un grupo de enlaces (por ejemplo, condiciones de uso y restauración de compras), añade un elemento **Link** en el Paywall Builder y gestiónalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu paywall (por ejemplo, **Terms of use** o **Privacy policy**): 1. En el Paywall Builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieres abrir. 2. En el código de tu app, implementa un handler para la acción `openUrl` que abra la URL recibida en un navegador. :::info En el SDK de iOS, la acción `openUrl` abre la URL por defecto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. ::: ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case let .openURL(url): UIApplication.shared.open(url, options: [:]) // default behavior break } } ``` ## Iniciar sesión en la app \{#log-into-the-app\} Para añadir un botón que permita a los usuarios iniciar sesión en tu app: 1. En el Paywall Builder, añade un botón y asígnale la acción **Login**. 2. En el código de tu app, implementa un handler para la acción `login` que identifique a tu usuario. ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case .login: // Show a login screen let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginViewController") controller.present(loginVC, animated: true) } } ``` ## Gestionar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el Paywall Builder, añade un botón, asígnale la acción **Custom** y asígnale un ID. 2. En el código de tu app, implementa un manejador para el ID de acción que hayas creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro paywall: ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case let .custom(id): if id == "openNewPaywall" { // Display another paywall } } break } } ``` </SDKv3> --- # File: ios-handling-events --- --- title: "Manejar eventos de flow y paywall - iOS" description: "Maneja eventos de flow y paywall en tu app de iOS." --- <SDKv4> :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de paywalls. También debes implementar el manejo de botones (cerrar el paywall, abrir enlaces, etc.). Consulta nuestra [guía sobre el manejo de acciones de botones](handle-paywall-actions) para más detalles. ::: Los flows y paywalls no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan ciertos eventos a los que tu app puede reaccionar. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selección de productos, etc.) así como notificaciones sobre acciones relacionadas con compras. Aprende a responder a estos eventos a continuación. :::tip ¿Quieres ver un ejemplo real de cómo integrar el SDK de Adapty en una app móvil? Consulta nuestras [apps de muestra](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Manejo de eventos en SwiftUI \{#handling-events-in-swiftui\} Para controlar o supervisar los procesos que ocurren en la pantalla del flow o paywall dentro de tu app, usa el modificador `.flow` en SwiftUI: ```swift showLineNumbers title="Swift" @State var flowPresented = false var body: some View { Text("Hello, AdaptyUI!") .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false case let .openURL(url): // handle opening the URL (incl. for terms and privacy) default: // handle other actions } }, didSelectProduct: { product in /* Handle the event */ }, didStartPurchase: { product in /* Handle the event */ }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didStartRestore: { /* Handle the event */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false }, didFailLoadingProducts: { error in // Return `true` to retry loading return false } ) } ``` Puedes registrar solo los parámetros del closure que necesites y omitir los que no. | Parámetro | Requerido | Descripción | |:-----------------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | requerido | Un binding que gestiona si la pantalla del flow o paywall está visible. | | **flowConfiguration** | requerido | Un objeto `AdaptyUI.FlowConfiguration` que contiene los detalles visuales del flow o paywall. Consulta [Obtener flows y paywalls](get-pb-paywalls) para más detalles. | | **didFinishPurchase** | requerido | Se invoca cuando `Adapty.makePurchase()` finaliza correctamente. El flow no se cierra de forma automática — establece tu binding de presentación a `false` aquí, o no hagas nada para que el flow continúe tras la compra. | | **didFailPurchase** | requerido | Se invoca cuando `Adapty.makePurchase()` falla. | | **didFinishRestore** | requerido | Se invoca cuando `Adapty.restorePurchases()` finaliza correctamente. | | **didFailRestore** | requerido | Se invoca cuando `Adapty.restorePurchases()` falla. | | **didReceiveError** | requerido | Se invoca cuando el flow encuentra un error de renderizado o un error en tiempo de ejecución del script del flow (por ejemplo, una excepción de JavaScript, código `AdaptyUIError` `4105`). En caso de error de renderizado, [contacta con el soporte de Adapty](mailto:support@adapty.io). | | **placeholderBuilder** | opcional | Una función para renderizar la vista de marcador de posición mientras se carga el flow o paywall. Por defecto muestra un `ProgressView`. | | **fullScreen** | opcional | Determina si el flow o paywall aparece en modo pantalla completa o como una hoja. Por defecto es `true`. | | **didAppear** | opcional | Se invoca cuando la vista del flow o paywall aparece en pantalla. | | **didDisappear** | opcional | Se invoca cuando la vista del flow o paywall fue cerrada. | | **didPerformAction** | opcional | Se invoca cuando el usuario pulsa un botón. Hay dos IDs de acción predefinidos: `close` y `openURL`; el resto son personalizados y se pueden configurar en el builder. | | **didSelectProduct** | opcional | Se invoca cuando el usuario o el sistema selecciona un producto para comprar. | | **didStartPurchase** | opcional | Se invoca cuando el usuario inicia el proceso de compra. | | **didFinishWebPaymentNavigation** | opcional | Se invoca cuando finaliza la navegación del pago web. | | **didStartRestore** | opcional | Se invoca cuando el usuario inicia el proceso de restauración. | | **didFailLoadingProducts** | opcional | Se invoca cuando se producen errores al cargar los productos. Devuelve `true` para reintentar la carga. | | **didPartiallyLoadProducts** | opcional | Se invoca cuando los productos se cargan parcialmente. | | **showAlertItem** | opcional | Un binding que gestiona la visualización de elementos de alerta sobre el flow o paywall. | | **showAlertBuilder** | opcional | Una función para renderizar la vista de alerta. | ## Gestión de eventos en UIKit \{#handling-events-in-uikit\} Para apps con UIKit, los eventos se gestionan mediante el protocolo `AdaptyFlowControllerDelegate`. Consulta [Mostrar flows y paywalls - iOS](ios-present-paywalls) para saber cómo configurar `AdaptyFlowController` con `AdaptyFlowControllerDelegate`. El protocolo declara 13 métodos. Cuatro de ellos no tienen implementación por defecto y deben implementarse al conformar: `didFinishPurchase`, `didFailPurchase`, `didFinishRestoreWith` y `didFailRestoreWith`. El resto proporcionan implementaciones no-op por defecto y pueden sobreescribirse cuando se desee un comportamiento personalizado. Los métodos se agrupan a continuación por propósito. ### Ciclo de vida \{#lifecycle\} ```swift showLineNumbers title="Swift" func flowControllerDidAppear(_ controller: AdaptyFlowController) { } func flowControllerDidDisappear(_ controller: AdaptyFlowController) { } ``` Estos se activan cuando la vista del flow o del paywall se presenta y se cierra. ### Acciones del usuario \{#user-actions\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didPerform action: AdaptyUI.Action ) { } ``` Casos de `AdaptyUI.Action`: - `.close` — el comportamiento predeterminado descarta el controlador. Sobreescríbelo si quieres mantener el controlador en pantalla o ejecutar limpieza adicional. - `.openURL(url:)` — el comportamiento predeterminado abre la URL con `UIApplication.shared.open(...)`. - `.custom(id:)` — se dispara para botones con un ID de acción personalizado definido en el builder. ### Selección de producto \{#product-selection\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didSelectProduct product: AdaptyPaywallProduct ) { } ``` Se invoca cuando el usuario o el sistema selecciona un producto para su compra. El producto incluye toda la información de la oferta (la elegibilidad se determina automáticamente en v4 — no existe un tipo `AdaptyPaywallProductWithoutDeterminingOffer` separado). ### Eventos de compra \{#purchase-events\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didStartPurchase product: AdaptyPaywallProduct ) { } func flowController( _ controller: AdaptyFlowController, didFinishPurchase product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if !purchaseResult.isPurchaseCancelled { controller.dismiss(animated: true) // or do nothing to let the flow continue } } func flowController( _ controller: AdaptyFlowController, didFailPurchase product: AdaptyPaywallProduct, error: AdaptyError ) { } ``` `didFinishPurchase` y `didFailPurchase` no tienen implementaciones por defecto y deben implementarse. El controlador no se cierra automáticamente tras una compra exitosa — llama a `controller.dismiss(animated:)` cuando sea apropiado, o no hagas nada para que un flujo de múltiples pantallas continúe tras la compra. ### Eventos de restauración \{#restore-events\} ```swift showLineNumbers title="Swift" func flowControllerDidStartRestore(_ controller: AdaptyFlowController) { } func flowController( _ controller: AdaptyFlowController, didFinishRestoreWith profile: AdaptyProfile ) { } func flowController( _ controller: AdaptyFlowController, didFailRestoreWith error: AdaptyError ) { } ``` `didFinishRestoreWith` y `didFailRestoreWith` no tienen implementaciones por defecto. Comprueba si el `AdaptyProfile` devuelto contiene el nivel de acceso que necesitas antes de cerrar el controlador. ### Errores del flow y errores de carga de productos \{#flow-errors-and-product-loading-errors\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didReceiveError error: AdaptyUIError ) { } func flowController( _ controller: AdaptyFlowController, didFailLoadingProductsWith error: AdaptyError ) -> Bool { // Return `true` to retry product loading; default returns `false`. return false } func flowController( _ controller: AdaptyFlowController, didPartiallyLoadProducts failedIds: [String] ) { } ``` `didReceiveError` se activa para errores de renderizado y para errores de ejecución del script del flow (excepciones de JavaScript, código `AdaptyUIError` `4105`). Para errores de renderizado, [contacta con el soporte de Adapty](mailto:support@adapty.io). Para errores de carga, devuelve `true` desde `didFailLoadingProductsWith` para reintentar — útil para fallos de red transitorios. ### Navegación de pago web \{#web-payment-navigation\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didFinishWebPaymentNavigation product: AdaptyPaywallProduct?, error: AdaptyError? ) { } ``` Se invoca cuando finaliza una navegación de pago web, ya sea con éxito o con error. </SDKv4> <SDKv3> :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de paywalls. También debes implementar el manejo de botones (cerrar paywall, abrir enlaces, etc.). Consulta nuestra [guía sobre el manejo de acciones de botones](handle-paywall-actions) para más detalles. ::: Los paywalls configurados con [Paywall Builder](adapty-paywall-builder) no necesitan código adicional para realizar ni restaurar compras. Sin embargo, generan algunos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el paywall. A continuación se explica cómo responder a estos eventos. Esta guía es exclusivamente para **paywalls del nuevo Paywall Builder** que requieren Adapty SDK v3.0 o posterior. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Manejo de eventos en SwiftUI \{#handling-events-in-swiftui\} Para controlar o monitorear los procesos que ocurren en la pantalla del paywall dentro de tu aplicación móvil, usa el modificador `.paywall` en SwiftUI: ```swift showLineNumbers title="Swift" @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywall: paywall, viewConfiguration: viewConfig, didPerformAction: { action in switch action { case .close: paywallPresented = false case let .openURL(url): // handle opening the URL (incl. for terms and privacy) default: // handle other actions } }, didSelectProduct: { /* Handle the event */ }, didStartPurchase: { /* Handle the event */ }, didFinishPurchase: { product, info in /* Handle the event */ }, didFailPurchase: { product, error in /* Handle the event */ }, didStartRestore: { /* Handle the event */ }, didFinishRestore: { /* Handle the event */ }, didFailRestore: { /* Handle the event */ }, didFailRendering: { error in paywallPresented = false }, didFailLoadingProducts: { error in return false } ) } ``` Puedes registrar solo los parámetros de cierre que necesites y omitir los que no. En ese caso, los parámetros de cierre no utilizados no se crearán. | Parámetro | Requerido | Descripción | |:----------------------------------|:---------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | requerido | Un binding que gestiona si la pantalla del paywall se muestra o no. | | **paywallConfiguration** | requerido | Un objeto `AdaptyUI.PaywallConfiguration` que contiene los detalles visuales del paywall. Usa el método `AdaptyUI.paywallConfiguration(for:products:viewConfiguration:observerModeResolver:tagResolver:timerResolver:)`. Consulta el tema [Obtener paywalls del Paywall Builder y su configuración](get-pb-paywalls) para más detalles. | | **didFailPurchase** | requerido | Se invoca cuando una compra falla por errores (p. ej., pago no permitido, problemas de red, producto inválido). No se invoca ante cancelaciones del usuario ni pagos pendientes. | | **didFinishRestore** | requerido | Se invoca cuando la compra se completa con éxito. | | **didFailRestore** | requerido | Se invoca cuando falla la restauración de una compra. | | **didFailRendering** | requerido | Se invoca si ocurre un error al renderizar la interfaz. En ese caso, [contacta con el soporte de Adapty](mailto:support@adapty.io). | | **fullScreen** | opcional | Determina si el paywall aparece en modo pantalla completa o como modal. El valor predeterminado es `true`. | | **didAppear** | opcional | Se invoca cuando la vista del paywall aparece en pantalla. También se invoca cuando el usuario pulsa el [botón de web paywall](web-paywall#step-2a-add-a-web-purchase-button) dentro de un paywall y se abre un web paywall en un navegador in-app. | | **didDisappear** | opcional | Se invoca cuando la vista del paywall se cierra. También se invoca cuando un [web paywall](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un paywall en un navegador in-app desaparece de la pantalla. | | **didPerformAction** | opcional | Se invoca cuando el usuario pulsa un botón. Cada botón tiene un ID de acción diferente. Hay dos IDs de acción predefinidos: `close` y `openURL`; el resto son personalizados y se pueden configurar en el builder. | | **didSelectProduct** | opcional | Se invoca cuando se selecciona un producto para la compra (ya sea por el usuario o por el sistema). | | **didStartPurchase** | opcional | Se invoca cuando el usuario inicia el proceso de compra. | | **didFinishPurchase** | opcional | Se invoca cuando la compra se completa con éxito. | | **didFinishWebPaymentNavigation** | opcional | Se invoca tras intentar abrir un [web paywall](web-paywall) para la compra, tanto si se ha completado correctamente como si ha fallado. | | **didStartRestore** | opcional | Se invoca cuando el usuario inicia el proceso de restauración. | | **didFailLoadingProducts** | opcional | Se invoca cuando ocurren errores durante la carga de productos. Devuelve `true` para reintentar la carga. | | **didPartiallyLoadProducts** | opcional | Se invoca cuando los productos se cargan parcialmente. | | **showAlertItem** | opcional | Un binding que gestiona la visualización de elementos de alerta sobre el paywall. | | **showAlertBuilder** | opcional | Una función para renderizar la vista de alerta. | | **placeholderBuilder** | opcional | Una función para renderizar la vista de marcador de posición mientras se carga el paywall. | ## Manejo de eventos en UIKit \{#handling-events-in-uikit\} Para controlar o monitorizar los procesos que ocurren en la pantalla del paywall dentro de tu app, implementa los métodos de `AdaptyPaywallControllerDelegate`. ### Eventos generados por el usuario \{#user-generated-events\} #### Selección de producto \{#product-selection\} Si un usuario selecciona un producto para comprarlo, se invocará este método: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer ) { } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### Compra iniciada \{#started-purchase\} Si un usuario inicia el proceso de compra, se invocará este método: ```swift showLineNumbers title="Swift" func paywallController(_ controller: AdaptyPaywallController, didStartPurchase product: AdaptyPaywallProduct) { } ``` <Details> <summary>Ejemplo de evento (clic para expandir)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> No se invocará en el modo Observer. Consulta el tema [iOS - Presentar paywalls de Paywall Builder en modo Observer](ios-present-paywall-builder-paywalls-in-observer-mode) para más detalles. #### Compra iniciada desde un paywall web \{#started-purchase-using-a-web-paywall\} Si un usuario inicia el proceso de compra mediante un [paywall web](web-paywall), se invocará este método: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, shouldContinueWebPaymentNavigation product: AdaptyPaywallProduct ) { } ``` <Details> <summary>Ejemplo de evento (haz clic para ampliar)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### Compra exitosa o cancelada \{#successful-or-canceled-purchase\} Si la compra se realiza con éxito, se invocará este método: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFinishPurchase product: AdaptyPaywallProductWithoutDeterminingOffer, purchaseResult: AdaptyPurchaseResult ) { } } ``` <Details> <summary>Ejemplos de eventos (haz clic para expandir)</summary> ```javascript // Successful purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } } } // Cancelled purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "cancelled" } } ``` </Details> Recomendamos cerrar la pantalla del paywall en ese caso. No se invocará en el modo Observer. Consulta el tema [iOS - Presentar paywalls de Paywall Builder en modo Observer](ios-present-paywall-builder-paywalls-in-observer-mode) para más detalles. #### Compra fallida \{#failed-purchase\} Si una compra falla por un error, se invocará este método. Esto incluye errores de StoreKit (restricciones de pago, productos no válidos, fallos de red), errores de verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones del usuario activan `didFinishPurchase` con un resultado de cancelado, y los pagos pendientes no activan este método. ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFailPurchase product: AdaptyPaywallProduct, error: AdaptyError ) { } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } ``` </Details> No se invocará en el modo Observer. Consulta el tema [iOS - Mostrar paywalls del Paywall Builder en modo Observer](ios-present-paywall-builder-paywalls-in-observer-mode) para más detalles. #### Compra fallida mediante un paywall web \{#failed-purchase-using-a-web-paywall\} Si `Adapty.openWebPaywall()` falla, se invocará este método: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFailWebPaymentNavigation product: AdaptyPaywallProduct, error: AdaptyError ) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "web_payment_failed", "message": "Web payment navigation failed", "details": { "underlyingError": "Network connection error" } } } ``` </Details> #### Restauración exitosa \{#successful-restore\} Si la restauración de una compra se realiza correctamente, se invocará este método: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFinishRestoreWith profile: AdaptyProfile ) { } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ``` </Details> Recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de la suscripción](subscription-status) para saber cómo verificarlo. #### Restauración fallida \{#failed-restore\} Si la restauración de una compra falla, se invocará este método: ```swift showLineNumbers title="Swift" public func paywallController( _ controller: AdaptyPaywallController, didFailRestoreWith error: AdaptyError ) { } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> ### Obtención de datos y renderizado \{#data-fetching-and-rendering\} #### Errores al cargar productos \{#product-loading-errors\} Si no pasas el array de productos durante la inicialización, AdaptyUI recuperará los objetos necesarios del servidor por sí solo. Si esta operación falla, AdaptyUI reportará el error llamando a este método: ```swift showLineNumbers title="Swift" public func paywallController( _ controller: AdaptyPaywallController, didFailLoadingProductsWith error: AdaptyError ) -> Bool { return true } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> Si devuelves `true`, AdaptyUI repetirá la solicitud después de 2 segundos. #### Errores de renderizado \{#rendering-errors\} Si se produce un error durante el renderizado de la interfaz, se notificará mediante este método: ```swift showLineNumbers title="Swift" public func paywallController( _ controller: AdaptyPaywallController, didFailRenderingWith error: AdaptyError ) { } ``` <Details> <summary>Ejemplo de evento (haz clic para ampliar)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ``` </Details> En una situación normal, estos errores no deberían producirse, así que si te encuentras con alguno, por favor, comunícanoslo. </SDKv3> --- # File: ios-use-fallback-paywalls --- --- title: "iOS - Usar paywalls de respaldo" description: "Gestiona los casos en que los usuarios están sin conexión o los servidores de Adapty no están disponibles" --- :::warning Los paywalls de respaldo son compatibles con el SDK de iOS v2.11 o posterior. ::: Para mantener una experiencia de usuario fluida, es importante configurar [respaldos](/fallback-paywalls) para tus flows, [paywalls](paywalls) y [onboardings](onboardings). Esta precaución amplía las capacidades de la aplicación en caso de pérdida parcial o total de la conexión a internet. * **Si la aplicación no puede acceder a los servidores de Adapty:** Podrá mostrar un flow o paywall de respaldo, y acceder a la configuración local del onboarding. * **Si la aplicación no puede acceder a internet:** Podrá mostrar un flow o paywall de respaldo. Los onboardings incluyen contenido remoto y requieren conexión a internet para funcionar. :::important Antes de seguir los pasos de esta guía, [descarga](/local-fallback-paywalls) los archivos de configuración de respaldo desde Adapty. ::: ## Configuración \{#configuration\} 1. Añade el archivo JSON de respaldo al bundle de tu proyecto: abre el menú **File** en XCode y selecciona la opción **Add Files to "YourProjectName"**. 2. Llama al método `.setFallback` **antes** de obtener el flow, paywall u onboarding de destino. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { if let urlPath = Bundle.main.url(forResource: fileName, withExtension: "json") { try await Adapty.setFallback(fileURL: urlPath) } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers if let url = Bundle.main.url(forResource: "ios_fallback", withExtension: "json") { Adapty.setFallback(fileURL: url) } ``` </TabItem> </Tabs> Parámetros: | Parámetro | Descripción | | :---------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **fileURL** | Ruta al archivo de configuración de respaldo. | --- # File: localizations-and-locale-codes --- --- title: "Usar localizaciones y códigos de idioma en el SDK de iOS" description: "Gestiona las localizaciones de la app y los códigos de idioma para llegar a una audiencia global en tu app de iOS." --- ## Por qué esto es importante \{#why-this-is-important\} Hay algunos escenarios en los que los códigos de idioma entran en juego, por ejemplo, cuando intentas obtener el paywall correcto para la localización actual de tu app. Como los códigos de idioma son complejos y pueden variar de una plataforma a otra, nos basamos en un estándar interno para todas las plataformas que soportamos. Sin embargo, precisamente por esa complejidad, es muy importante que entiendas exactamente qué estás enviando a nuestro servidor para obtener la localización correcta y qué ocurre a continuación, de modo que siempre recibas lo que esperas. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código está formado por subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués de Brasil), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de idioma \{#locale-code-matching\} Cuando Adapty recibe una llamada del SDK con el código de idioma y busca la localización correspondiente de un paywall, ocurre lo siguiente: 1. La cadena de idioma entrante se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan con guiones (`-`). 2. Buscamos la localización con el código de idioma que coincida exactamente. 3. Si no se encuentra ninguna coincidencia, tomamos la subcadena antes del primer guion (`pt` para `pt-br`) y buscamos la localización correspondiente. 4. Si tampoco se encuentra coincidencia, devolvemos la localización por defecto `en`. De este modo, un dispositivo iOS que envíe `'pt_BR'`, un dispositivo Android que envíe `pt-BR` y otro dispositivo que envíe `pt-br` obtendrán el mismo resultado. ## Implementar localizaciones: la forma recomendada \{#implementing-localizations-recommended-way\} Si te estás preguntando por las localizaciones, lo más probable es que ya estés trabajando con archivos de cadenas localizadas en tu proyecto. En ese caso, te recomendamos añadir un par clave-valor con el código de idioma de Adapty correspondiente en cada uno de tus archivos para las localizaciones respectivas, y luego extraer el valor de esa clave al llamar a nuestro SDK, como se muestra aquí: ```swift showLineNumbers // 1. Modify your Localizable.strings files /* Localizable.strings - Spanish */ adapty_paywalls_locale = "es"; /* Localizable.strings - Portuguese (Brazil) */ adapty_paywalls_locale = "pt-br"; // 2. Extract and use the locale code let locale = NSLocalizedString("adapty_paywalls_locale", comment: "") // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Así tienes el control total sobre qué localización se obtendrá para cada usuario de tu app. ## Implementar localizaciones: la otra forma \{#implementing-localizations-the-other-way\} Puedes obtener resultados similares (aunque no idénticos) sin definir explícitamente los códigos de idioma para cada localización. Esto implica extraer un código de idioma de otros objetos que proporciona tu plataforma, como en este ejemplo: ```swift showLineNumbers let locale = Locale.current.identifier // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` No recomendamos este enfoque por varios motivos: 1. En iOS, los idiomas preferidos y el idioma actual no son idénticos. Si quieres que la localización se seleccione correctamente, tendrás que apoyarte en la lógica de Apple, que funciona de manera automática si usas el enfoque recomendado con archivos de cadenas localizadas, o bien recrearla tú mismo. 2. Es difícil predecir exactamente qué recibirá el servidor de Adapty. Por ejemplo, en iOS es posible obtener un código como `ar_OM@numbers='latn'` en un dispositivo y enviarlo a nuestro servidor. Para esa llamada no obtendrás la localización `ar-om` que buscabas, sino `ar`, lo cual probablemente no es lo que esperabas. Si aun así decides utilizar este enfoque, asegúrate de haber cubierto todos los casos de uso relevantes. --- # File: ios-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del Paywall Builder en el SDK de iOS" description: "Solucionar problemas del Paywall Builder en el SDK de iOS" --- Esta guía te ayuda a resolver los problemas más comunes al usar paywalls diseñados en el Adapty Paywall Builder con el SDK de iOS. ## Falla la obtención de la configuración de un paywall \{#getting-a-paywall-configuration-fails\} **Problema**: El método `getPaywallConfiguration` no puede recuperar la configuración del paywall. **Causa**: El paywall no está habilitado para mostrarse en el dispositivo en el Paywall Builder. **Solución**: Activa el interruptor **Show on device** en el Paywall Builder. <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## El número de vistas del paywall es demasiado alto \{#the-paywall-view-number-is-too-big\} **Problema**: El recuento de vistas del paywall muestra el doble del número esperado. **Motivo**: Es posible que estés llamando a `logShowFlow` (iOS SDK v4+) / `logShowPaywall` en tu código, lo que duplica el recuento de vistas si usas el Paywall Builder o el Flow Builder. Para flows y paywalls creados con estas herramientas, las analíticas se registran automáticamente, por lo que no necesitas usar este método. **Solución**: Asegúrate de no llamar a `logShowFlow` (iOS SDK v4+) / `logShowPaywall` en tu código si usas el Paywall Builder o el Flow Builder. ## Otros problemas \{#other-issues\} **Problema**: Tienes otros problemas relacionados con el Paywall Builder que no se tratan más arriba. **Solución**: Si es necesario, migra el SDK a la última versión siguiendo las [guías de migración](ios-sdk-migration-guides). Muchos problemas se resuelven en versiones más recientes del SDK. --- # File: ios-present-paywall-builder-paywalls-in-observer-mode --- --- title: "Presentar paywalls del Paywall Builder en modo Observer en el SDK de iOS" description: "Aprende cómo presentar paywalls de PB en modo observer para obtener mejores insights." --- Si has personalizado un paywall usando el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Este tipo de paywall incluye tanto lo que se debe mostrar como la forma en que debe mostrarse. :::warning Esta sección hace referencia al [modo Observer](observer-vs-full-mode) únicamente. Si no trabajas en modo Observer, consulta [iOS - Presentar paywalls con Paywall Builder](ios-present-paywalls). ::: <SDKv4> <details> <summary>Antes de empezar a presentar flows (haz clic para expandir)</summary> 1. Configura la integración inicial de Adapty [con la App Store](initial_ios). 2. Instala y configura el SDK de Adapty. Asegúrate de establecer el parámetro `observerMode` en `true`. Consulta la [guía de instalación del SDK para iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk). 3. [Crea productos](create-product) en el Adapty Dashboard. 4. [Configura flows o paywalls en los builders](create-paywall) y asígnales productos. 5. [Crea placements y asígnales tus flows o paywalls](create-placement). 6. [Obtén los flows y su configuración](get-pb-paywalls) en el código de tu app. </details> <p> </p> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> 1. Implementa el objeto `AdaptyObserverModeResolver`. El protocolo es el mismo que en SDK v3: el modo observador en sí no cambia entre el renderizado de flows y paywalls: ```swift showLineNumbers title="Swift" func observerMode(didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // call onStartPurchase / onFinishPurchase to notify AdaptyUI about the purchase progress } func observerModeDidInitiateRestorePurchases(onStartRestore: @escaping () -> Void, onFinishRestore: @escaping () -> Void) { // call onStartRestore / onFinishRestore to notify AdaptyUI about the restore progress } ``` 2. Crea un objeto de configuración del flow, pasando tu resolver como parámetro `observerModeResolver:`: ```swift showLineNumbers title="Swift" do { let flowConfiguration = try await AdaptyUI.getFlowConfiguration( forFlow: flow, observerModeResolver: <AdaptyObserverModeResolver> ) } catch { // handle the error } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :----------------------- | :-------- | :----------------------------------------------------------------------------------------------------------------------- | | **forFlow** | requerido | Un objeto `AdaptyFlow` obtenido mediante `Adapty.getFlow(placementId:)`. Consulta [Obtener flows y paywalls](get-pb-paywalls). | | **observerModeResolver** | requerido | El `AdaptyObserverModeResolver` que implementaste anteriormente. | 3. Inicializa el controlador del flow usando `AdaptyUI.flowController(with:delegate:)`: ```swift showLineNumbers title="Swift" import AdaptyUI let visualFlow = try AdaptyUI.flowController( with: flowConfiguration, delegate: <AdaptyFlowControllerDelegate> ) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | | **flowConfiguration** | requerido | Un objeto `AdaptyUI.FlowConfiguration` que contiene los detalles visuales del flow. Consulta [Obtener flows y paywalls](get-pb-paywalls). | | **delegate** | requerido | Un `AdaptyFlowControllerDelegate` para escuchar los eventos del flow. Consulta [Gestionar eventos de flow y paywall](ios-handling-events). | Devuelve: | Objeto | Descripción | | :------------------- | :----------------------------------------------------- | | AdaptyFlowController | Un objeto que representa la pantalla del flow solicitado. | 4. Presenta el controlador: ```swift showLineNumbers title="Swift" present(visualFlow, animated: true) ``` :::warning No olvides [asociar los paywalls a las transacciones de compra](report-transactions-observer-mode). De lo contrario, Adapty no podrá determinar el paywall de origen de la compra. ::: </TabItem> <TabItem value="swiftui" label="SwiftUI" default> En SwiftUI, obtén la configuración del flow con el resolver y pásala al modificador `.flow`: ```swift showLineNumbers title="SwiftUI" @State var flowPresented = false @State var flowConfiguration: AdaptyUI.FlowConfiguration? var body: some View { Text("Hello, AdaptyUI!") .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) .task { flowConfiguration = try? await AdaptyUI.getFlowConfiguration( forFlow: flow, observerModeResolver: <AdaptyObserverModeResolver> ) } } ``` El parámetro `observerModeResolver:` en `getFlowConfiguration` es lo que hace que el flow renderizado respete tu lógica de compra personalizada — el modificador en sí utiliza los mismos callbacks que el modo completo. :::warning No olvides [asociar los paywalls a las transacciones de compra](report-transactions-observer-mode). De lo contrario, Adapty no podrá determinar el paywall de origen de la compra. ::: </TabItem> </Tabs> </SDKv4> <SDKv3> <Tabs groupId="current-os" queryString> <TabItem value="sdk3" label="Paywall Builder (SDK 3.x)" default> <details> <summary>Antes de empezar a mostrar paywalls (haz clic para ampliar)</summary> 1. Configura la integración inicial de Adapty [con Google Play](initial-android) y [con App Store](initial_ios). 2. Instala y configura el SDK de Adapty. Asegúrate de establecer el parámetro `observerMode` en `true`. Consulta nuestras instrucciones específicas para cada plataforma en [iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk). 3. [Crea productos](create-product) en el Adapty Dashboard. 4. [Configura paywalls, asígnales productos](create-paywall) y personalízalos con el Paywall Builder en el Adapty Dashboard. 5. [Crea placements y asígnales tus paywalls](create-placement) en el Adapty Dashboard. 6. [Obtén los paywalls del Paywall Builder y su configuración](get-pb-paywalls) en el código de tu app. </details> <p> </p> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> 1. Implementa el objeto `AdaptyObserverModeResolver`: ```swift showLineNumbers title="Swift" func observerMode(didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase } func observerModeDidInitiateRestorePurchases(onStartRestore: @escaping () -> Void, onFinishRestore: @escaping () -> Void) { // use the onStartRestore and onFinishRestore callbacks to notify AdaptyUI about the process of the restore } ``` El evento `observerMode(didInitiatePurchase:onStartPurchase:onFinishPurchase:)` te informará de que el usuario ha iniciado una compra. Puedes activar tu flujo de compra personalizado en respuesta a este callback. El evento `observerModeDidInitiateRestorePurchases(onStartRestore:onFinishRestore:)` te informará de que el usuario ha iniciado una restauración. Puedes activar tu flujo de restauración personalizado en respuesta a este callback. También recuerda invocar los siguientes callbacks para notificar a AdaptyUI sobre el proceso de compra o restauración. Esto es necesario para el correcto funcionamiento del paywall, como mostrar el loader, entre otras cosas: | Callback | Description | | :----------------- | :------------------------------------------------------------------------------- | | onStartPurchase() | El callback debe invocarse para notificar a AdaptyUI que la compra ha comenzado. | | onFinishPurchase() | El callback debe invocarse para notificar a AdaptyUI que la compra ha finalizado. | | onStartRestore() | El callback debe invocarse para notificar a AdaptyUI que la restauración ha comenzado. | | onFinishRestore() | El callback debe invocarse para notificar a AdaptyUI que la restauración ha finalizado. | 2. Crea un objeto de configuración de paywall: ```swift showLineNumbers title="Swift" do { let paywallConfiguration = try AdaptyUI.getPaywallConfiguration( forPaywall: <paywall object>, observerModeResolver: <AdaptyObserverModeResolver> ) } catch { // handle the error } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :----------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall** | requerido | Un objeto `AdaptyPaywall` para obtener un controlador para el paywall deseado. | | **ObserverModeResolver** | requerido | El objeto `AdaptyObserverModeResolver` que implementaste en el paso anterior. | 3. Inicializa el paywall visual que quieres mostrar usando el método `.paywallController(for:products:viewConfiguration:delegate:)`: ```swift showLineNumbers title="Swift" import AdaptyUI let visualPaywall = AdaptyUI.paywallController( with: <paywall configuration object>, delegate: <AdaptyPaywallControllerDelegate> ) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :----------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall Configuration** | requerido | Un objeto `AdaptyUI.PaywallConfiguration` que contiene los detalles visuales del paywall. Usa el método `AdaptyUI.getPaywallConfiguration(forPaywall:locale:)`. Consulta el tema [Obtener paywalls del Paywall Builder y su configuración](get-pb-paywalls) para más detalles. | | **Delegate** | requerido | Un `AdaptyPaywallControllerDelegate` para escuchar los eventos del paywall. Consulta el tema [Gestionar eventos del paywall](ios-handling-events) para más detalles. | Devuelve: | Objeto | Descripción | | :---------------------- | :--------------------------------------------------- | | AdaptyPaywallController | Un objeto que representa la pantalla del paywall solicitado | Una vez que el objeto se ha creado correctamente, puedes mostrarlo así: ```swift showLineNumbers title="Swift" present(visualPaywall, animated: true) ``` :::warning No olvides [Asociar paywalls a las transacciones de compra](report-transactions-observer-mode). De lo contrario, Adapty no podrá determinar el paywall de origen de la compra. ::: </TabItem> <TabItem value="swiftui" label="SwiftUI" default> Para mostrar el paywall visual en la pantalla del dispositivo, usa el modificador `.paywall` en SwiftUI: ```swift showLineNumbers title="SwiftUI" @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywallConfiguration: <paywall configuration object>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false } ) } ``` Request parameters: | Parámetro | Presencia | Descripción | | :----------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall Configuration** | obligatorio | Un objeto `AdaptyUI.PaywallConfiguration` que contiene los detalles visuales del paywall. Usa el método `AdaptyUI.getPaywallConfiguration(forPaywall:locale:)`. Consulta el tema [Fetch Paywall Builder paywalls and their configuration](get-pb-paywalls) para más detalles. | | **Products** | opcional | Proporciona un array de objetos `AdaptyPaywallProduct` para optimizar el momento en que se muestran los productos en pantalla. Si se pasa `nil`, AdaptyUI obtendrá automáticamente los productos necesarios. | | **TagResolver** | opcional | Define un diccionario de etiquetas personalizadas y sus valores resueltos. Las etiquetas personalizadas actúan como marcadores de posición en el contenido del paywall, y se reemplazan dinámicamente con cadenas específicas para ofrecer contenido personalizado. Consulta el tema Custom tags in Paywall Builder para más detalles. | | **ObserverModeResolver** | opcional | El objeto `AdaptyObserverModeResolver` que implementaste en el paso anterior | Parámetros del closure: | Parámetro del closure | Descripción | | :-------------------- | :----------------------------------------------------------------------------------------------- | | **didFinishRestore** | Si Adapty.restorePurchases() tiene éxito, se invocará este callback. | | **didFailRestore** | Si Adapty.restorePurchases() falla, se invocará este callback. | | **didFailRendering** | Si ocurre un error durante el renderizado de la interfaz, se invocará este callback. | Consulta el tema [iOS - Gestión de eventos](ios-handling-events) para ver otros parámetros de cierre. :::warning No olvides [asociar los paywalls a las transacciones de compra](report-transactions-observer-mode). De lo contrario, Adapty no podrá determinar el paywall de origen de la compra. ::: </TabItem> </Tabs> </TabItem> <TabItem value="sdk2" label="Legacy Paywall Builder (SDK up to 2.x)" default> <details> <summary>Antes de empezar a mostrar paywalls (haz clic para ampliar)</summary> 1. Configura la integración inicial de Adapty [con Google Play](initial-android) y [con el App Store](initial_ios). 1. Instala y configura el SDK de Adapty. Asegúrate de establecer el parámetro `observerMode` en `true`. Consulta las instrucciones específicas para cada framework: [iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk) y [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 2. [Crea productos](create-product) en el Adapty Dashboard. 3. [Configura los paywalls, asígnales productos](create-paywall) y personalízalos con el Paywall Builder en el Adapty Dashboard. 4. [Crea placements y asígnales tus paywalls](create-placement) en el Adapty Dashboard. 5. [Obtén los paywalls del Paywall Builder y su configuración](get-pb-paywalls) en el código de tu aplicación móvil. </details> <p> </p> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> 1. Implementa el objeto `AdaptyObserverModeDelegate`: ```swift showLineNumbers title="Swift" func paywallController(_ controller: AdaptyPaywallController, didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase } ``` El evento `paywallController(_:didInitiatePurchase:onStartPurchase:onFinishPurchase:)` te informará de que el usuario ha iniciado una compra. Puedes activar tu flow de compra personalizado en respuesta a este evento. Además, recuerda invocar los siguientes callbacks para notificar a AdaptyUI sobre el proceso de compra. Esto es necesario para que el paywall funcione correctamente, por ejemplo, para mostrar el loader, entre otras cosas: | Callback | Descripción | | :--------------- | :----------------------------------------------------------------------------------------------- | | onStartPurchase | El callback debe invocarse para notificar a AdaptyUI que la compra ha comenzado. | | onFinishPurchase | El callback debe invocarse para notificar a AdaptyUI que la compra ha finalizado. | 2. Inicializa el paywall visual que quieres mostrar usando el método `.paywallController(for:products:viewConfiguration:delegate:observerModeDelegate:)`: ```swift showLineNumbers title="Swift" import AdaptyUI let visualPaywall = AdaptyUI.paywallController( for: <paywall object>, products: <paywall products array>, viewConfiguration: <LocalizedViewConfiguration>, delegate: <AdaptyPaywallControllerDelegate> observerModeDelegate: <AdaptyObserverModeDelegate> ) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :----------------------- | :-------- | :----------------------------------------------------------- | | **Paywall** | requerido | Un objeto `AdaptyPaywall` para obtener un controlador del paywall deseado. | | **Products** | opcional | Proporciona un array de objetos `AdaptyPaywallProduct` para optimizar el tiempo de visualización de los productos en pantalla. Si se pasa `nil`, AdaptyUI obtendrá automáticamente los productos necesarios. | | **ViewConfiguration** | requerido | Un objeto `AdaptyUI.LocalizedViewConfiguration` que contiene los detalles visuales del paywall. Usa el método `AdaptyUI.getViewConfiguration(paywall:locale:)`. Consulta el tema [Fetch Paywall Builder paywalls and their configuration](get-pb-paywalls) para más detalles. | | **Delegate** | requerido | Un `AdaptyPaywallControllerDelegate` para escuchar los eventos del paywall. Consulta el tema [Handling paywall events](ios-handling-events) para más detalles. | | **ObserverModeDelegate** | requerido | El objeto `AdaptyObserverModeDelegate` que implementaste en el paso anterior. | | **TagResolver** | opcional | Define un diccionario de etiquetas personalizadas y sus valores resueltos. Las etiquetas personalizadas actúan como marcadores de posición en el contenido del paywall y se reemplazan dinámicamente con cadenas específicas para personalizar el contenido del paywall. Consulta el tema Custom tags in Paywall Builder para más detalles. | Devuelve: | Objeto | Descripción | | :---------------------- | :--------------------------------------------------- | | AdaptyPaywallController | Un objeto que representa la pantalla de paywall solicitada | Una vez creado el objeto correctamente, puedes mostrarlo así: ```swift showLineNumbers title="Swift" present(visualPaywall, animated: true) ``` :::warning No olvides [asociar los paywalls a las transacciones de compra](report-transactions-observer-mode). De lo contrario, Adapty no podrá determinar el paywall de origen de la compra. ::: </TabItem> <TabItem value="swiftui" label="SwiftUI" default> Para mostrar el paywall visual en la pantalla del dispositivo, usa el modificador `.paywall` en SwiftUI: ```swift showLineNumbers title="SwiftUI" @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywall: <paywall object>, configuration: <LocalizedViewConfiguration>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false }, observerModeDidInitiatePurchase: { product, onStartPurchase, onFinishPurchase in // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase }, ) } ``` Request parameters: | Parámetro | Presencia | Descripción | | :---------------- | :-------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall** | obligatorio | Un objeto `AdaptyPaywall` para obtener un controlador para el paywall deseado. | | **Product** | opcional | Proporciona un array de objetos `AdaptyPaywallProduct` para optimizar el tiempo de visualización de los productos en pantalla. Si se pasa `nil`, AdaptyUI obtendrá automáticamente los productos necesarios. | | **Configuration** | obligatorio | Un objeto `AdaptyUI.LocalizedViewConfiguration` que contiene los detalles visuales del paywall. Usa el método `AdaptyUI.getViewConfiguration(paywall:locale:)`. Consulta el tema [Obtener paywalls del Paywall Builder y su configuración](get-pb-paywalls) para más detalles. | | **TagResolver** | opcional | Define un diccionario de etiquetas personalizadas y sus valores resueltos. Las etiquetas personalizadas sirven como marcadores de posición en el contenido del paywall, y se reemplazan dinámicamente con cadenas específicas para personalizar el contenido dentro del paywall. Consulta el tema Custom tags in paywall builder para más detalles. | Parámetros de cierre: | Parámetro del closure | Descripción | | :----------------------------------- | :----------------------------------------------------------------------------------------------- | | **didFinishRestore** | Si Adapty.restorePurchases() se ejecuta correctamente, se invocará este callback. | | **didFailRestore** | Si Adapty.restorePurchases() falla, se invocará este callback. | | **didFailRendering** | Si ocurre un error durante el renderizado de la interfaz, se invocará este callback. | | **observerModeDidInitiatePurchase** | Este callback se invoca cuando un usuario inicia una compra. | Consulta el artículo [iOS - Gestión de eventos](ios-handling-events) para conocer otros parámetros de cierre. :::warning No olvides [asociar paywalls a las transacciones de compra](report-transactions-observer-mode). De lo contrario, Adapty no podrá determinar el paywall de origen de la compra. ::: </TabItem> </Tabs> </TabItem> </Tabs> </SDKv3> --- # File: ios-quickstart-manual --- --- title: "Habilitar compras en tu paywall personalizado con el SDK de iOS" description: "Integra el SDK de Adapty en tus paywalls personalizados de iOS para habilitar las compras in-app." --- Esta guía describe cómo integrar Adapty en tus paywalls personalizados. Mantén el control total sobre la implementación del paywall, mientras el SDK de Adapty obtiene los productos, gestiona las compras nuevas y restaura las anteriores. :::important **Esta guía es para desarrolladores que implementan paywalls personalizados.** Si buscas la forma más sencilla de activar compras, usa el [Adapty Flow Builder](ios-quickstart-paywalls). Con Flow Builder, creas flows en un editor visual sin código, Adapty gestiona toda la lógica de compra automáticamente y puedes probar distintos diseños sin volver a publicar tu app. ::: ## Antes de empezar \{#before-you-start\} ### Configura los productos \{#set-up-products\} Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls**](paywalls) – configuraciones que definen qué productos ofrecer. En Adapty, los paywalls son la única forma de recuperar productos, pero este diseño te permite modificar productos, precios y ofertas sin tocar el código de tu app. - [**Placements**](placements) – dónde y cuándo muestras los paywalls en tu app (como `main`, `onboarding`, `settings`). Configuras los paywalls para los placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar diferentes paywalls a distintos usuarios. Asegúrate de entender estos conceptos aunque trabajes con un paywall personalizado. Básicamente, son tu forma de gestionar los productos que vendes en tu app. Para implementar tu paywall personalizado, necesitarás crear un **paywall** y añadirlo a un **placement**. Esta configuración te permite recuperar tus productos. Para entender qué debes hacer en el dashboard, sigue la guía de inicio rápido [aquí](quickstart). ### Gestión de usuarios \{#manage-users\} Puedes trabajar con o sin autenticación backend en tu lado. Sin embargo, el SDK de Adapty gestiona los usuarios anónimos e identificados de forma diferente. Lee la [guía de inicio rápido de identificación](ios-quickstart-identify) para entender los detalles y asegurarte de que estás trabajando con los usuarios correctamente. ## Paso 1. Obtener productos \{#step-1-get-products\} Para obtener los productos de tu paywall personalizado, necesitas: 1. Obtener el objeto `flow` pasando el ID del [placement](placements) al método `getFlow`. 2. Obtener el array de productos para este flow usando el método `getPaywallProducts`. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift func loadPaywall() async { do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") let products = try await Adapty.getPaywallProducts(flow: flow) // Use products to build your custom paywall UI } catch { // Handle the error } } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift func loadPaywall() { Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): Adapty.getPaywallProducts(flow: flow) { result in switch result { case let .success(products): // Use products to build your custom paywall UI case let .failure(error): // Handle the error } } case let .failure(error): // Handle the error } } } ``` </TabItem> </Tabs> ## Paso 2. Aceptar compras \{#step-2-accept-purchases\} Cuando un usuario pulsa sobre un producto en tu paywall personalizado, llama al método `makePurchase` con el producto seleccionado. Esto gestionará el flow de compra y devolverá el perfil actualizado. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift func purchaseProduct(_ product: AdaptyPaywallProduct) async { do { let purchaseResult = try await Adapty.makePurchase(product: product) switch purchaseResult { case .userCancelled: // El usuario canceló la compra break case .pending: // La compra está pendiente (p. ej., a la espera de aprobación parental) break case let .success(profile, transaction): // Compra exitosa, perfil actualizado break } } catch { // Gestionar el error } } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift func purchaseProduct(_ product: AdaptyPaywallProduct) { Adapty.makePurchase(product: product) { result in switch result { case let .success(purchaseResult): switch purchaseResult { case .userCancelled: // User canceled the purchase break case .pending: // Purchase is pending (e.g., awaiting parental approval) break case let .success(profile, transaction): // Purchase successful, profile updated break } case let .failure(error): // Handle the error } } } ``` </TabItem> </Tabs> ## Paso 3. Restaurar compras \{#step-3-restore-purchases\} Apple exige que todas las aplicaciones con suscripciones ofrezcan una forma de restaurar las compras. Aunque las compras se restauran automáticamente cuando el usuario inicia sesión con su Apple ID, debes implementar igualmente un botón de restauración en tu app. Llama al método `restorePurchases` cuando el usuario pulse el botón de restauración. Esto sincronizará su historial de compras con Adapty y devolverá el perfil actualizado. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift func restorePurchases() async { do { let profile = try await Adapty.restorePurchases() // Restore successful, profile updated } catch { // Handle the error } } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift func restorePurchases() { Adapty.restorePurchases { result in switch result { case let .success(profile): // Restore successful, profile updated case let .failure(error): // Handle the error } } } ``` </TabItem> </Tabs> ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. [Prueba tus compras en modo sandbox](test-purchases-in-sandbox) para asegurarte de que puedes completar una compra de prueba desde el paywall. A continuación, [comprueba si los usuarios han completado su compra](ios-check-subscription-status) para determinar si mostrar el paywall o conceder acceso a las funciones de pago. --- # File: fetch-paywalls-and-products --- --- title: "Obtener paywalls y productos para paywalls de Remote Config en iOS SDK" description: "Obtén paywalls y productos en el SDK de Adapty para iOS y mejora la monetización de tus usuarios." --- <SDKv4> Antes de mostrar Remote Config y paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a Remote Config y paywalls personalizados. Para obtener orientación sobre cómo obtener flows o paywalls personalizados en el **Flow Builder** o **Paywall Builder**, consulta las <InlineTooltip tooltip="guías sobre cómo obtener flows y paywalls en tu app">[iOS](get-pb-paywalls), [Android](android-get-pb-paywalls), [React Native](react-native-get-pb-paywalls), [Flutter](flutter-get-pb-paywalls), y [Unity](unity-get-pb-paywalls)</InlineTooltip>. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener flows y productos en tu aplicación móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow o paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow o paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-ios) en tu aplicación móvil. </details> ## Obtener información del flow \{#fetch-flow-information\} En Adapty, un [producto](product) es una combinación de productos tanto de App Store como de Google Play. Estos productos multiplataforma se integran en flows y paywalls, lo que te permite mostrarlos en placements específicos de tu app móvil. Para mostrar los productos, necesitas obtener un `AdaptyFlow` de uno de tus [placements](placements) mediante el método `getFlow`. :::important **No uses IDs de producto hardcodeados.** El único ID que debes hardcodear es el ID del placement. Los flows se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un flow devuelve dos productos hoy y tres mañana, muéstralos todos sin necesidad de cambiar el código. ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") // the requested flow } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // the requested flow case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los flows y paywalls en dos capas: la caché de actualización periódica descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener flows y paywalls más rápido, y un servidor de respaldo independiente en caso de que el CDN no esté disponible.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p></p><p>Ten en cuenta que en casos excepcionales este método puede exceder ligeramente el tiempo de espera especificado en `loadTimeout`, ya que la operación puede incluir diferentes peticiones internamente.</p> | :::note En la versión 4, el parámetro `locale` ha pasado de `getFlow` a `getFlowConfiguration` (que solo se usa al renderizar con AdaptyUI). Para los paywalls personalizados, todas las configuraciones regionales disponibles se devuelven juntas en `flow.remoteConfigs` — elige la que coincida con el idioma del dispositivo del usuario o con la configuración de tu app. ::: ¡No escribas los IDs de productos en el código! Como los flows se configuran de forma remota, los productos disponibles, el número de productos y las ofertas especiales (como los períodos de prueba gratuitos) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si inicialmente obtienes 2 productos, tu app debería mostrar esos 2 productos. Sin embargo, si más adelante obtienes 3 productos, tu app debería mostrar los 3 sin necesidad de cambios en el código. Lo único que tienes que escribir directamente en el código es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Un objeto `AdaptyFlow` que contiene el placement, los identificadores (`id`, `variationId`), el nombre, un array `remoteConfigs` (una entrada por cada locale configurado) y un flag `hasViewConfiguration`. Para obtener los productos del flow, llama a `getPaywallProducts(flow:)`. | ## Obtener productos \{#fetch-products\} Una vez que tienes el flow, puedes consultar el array de productos asociado a él: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let products = try await Adapty.getPaywallProducts(flow: flow) // the requested products array } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallProducts(flow: flow) { result in switch result { case let .success(products): // the requested products array case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Lista de objetos [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct) con: identificador de producto, nombre del producto, precio, moneda, duración de la suscripción y otras propiedades. | Al implementar tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct). A continuación se muestran las propiedades más utilizadas, pero consulta el documento enlazado para obtener información completa sobre todas las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país de la store seleccionado por el usuario, no en el idioma del dispositivo. | | **Price** | Para mostrar el precio localizado, usa `product.localizedPrice`. Esta localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número con `product.price`. El valor se devuelve en la moneda local. Para obtener el símbolo de la moneda, usa `product.currencySymbol`. | | **Subscription Period** | Para mostrar el período (p. ej., semana, mes, año, etc.), usa `product.localizedSubscriptionPeriod`. Esta localización se basa en la configuración regional del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscriptionPeriod`. Desde ahí puedes acceder al enum `unit` para conocer la duración (es decir, día, semana, mes, año o desconocido). El valor `numberOfUnits` te devuelve el número de unidades del período. Por ejemplo, en una suscripción trimestral verás `.month` en la propiedad `unit` y `3` en `numberOfUnits`. | | **Introductory Offer** | Para mostrar una etiqueta u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscriptionOffer`. Este objeto contiene las siguientes propiedades útiles:<br/>• `offerType`: un enum con los valores `introductory`, `promotional` y `winBack`. Las pruebas gratuitas y las suscripciones con descuento inicial serán del tipo `introductory`.<br/>• `price`: el precio con descuento como número. En las pruebas gratuitas, el valor será `0`.<br/>• `localizedPrice`: el precio del descuento formateado según la configuración regional del usuario.<br/>• `localizedNumberOfPeriods`: una cadena localizada según la configuración regional del dispositivo que describe la duración de la oferta. Por ejemplo, una prueba de tres días mostrará `3 days` en este campo.<br/>• `subscriptionPeriod`: alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona del mismo modo que se describe en la sección anterior.<br/>• `localizedSubscriptionPeriod`: el período de suscripción del descuento formateado según la configuración regional del usuario. | :::note En la versión 4, todos los productos devueltos por `getPaywallProducts(flow:)` ya incluyen información sobre la elegibilidad de ofertas. La llamada separada `getPaywallProductsWithoutDeterminingOffer` de la versión 3 ha sido eliminada. ::: ## Acelera la carga de flows con el flow de audiencia predeterminada \{#speed-up-flow-fetching-with-default-audience-flow\} Normalmente, los flows se cargan casi de forma instantánea, por lo que no necesitas preocuparte por optimizar este proceso. Sin embargo, si tienes muchas audiencias y placements, y tus usuarios tienen una conexión a internet lenta, cargar un flow puede tardar más de lo esperado. En esos casos, puede que quieras mostrar un flow predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar nada. Para solucionar esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el flow con el método `getFlow`, tal como se detalla en la sección [Obtener información del flow](fetch-paywalls-and-products#fetch-flow-information) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar flows distintos según la versión de la app (la actual y las futuras), puedes encontrarte con dificultades. Tendrás que diseñar flows compatibles con la versión actual (legacy) o asumir que los usuarios con esa versión podrían tener problemas al no renderizarse los flows. - **Pérdida de targeting**: Todos los usuarios verán el mismo flow diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluida la segmentación por países, atribución de marketing o atributos personalizados propios). Si estás dispuesto a aceptar estas desventajas a cambio de una carga más rápida del flow, usa el método `getFlowForDefaultAudience` de la siguiente manera. De lo contrario, utiliza el método `getFlow` descrito [anteriormente](fetch-paywalls-and-products#fetch-flow-information). ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let flow = try await Adapty.getFlowForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") // the requested flow } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getFlowForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // el flow solicitado case let .failure(error): // gestiona el error } } ``` </TabItem> </Tabs> | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | </SDKv4> <SDKv3> Antes de mostrar el Remote Config y los paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema se refiere a Remote Config y paywalls personalizados. Para obtener orientación sobre cómo recuperar paywalls configurados con Paywall Builder, consulta las <InlineTooltip tooltip="guías sobre cómo obtener paywalls de Paywall Builder en tu app">[iOS](get-pb-paywalls), [Android](android-get-pb-paywalls), [React Native](react-native-get-pb-paywalls), [Flutter](flutter-get-pb-paywalls), y [Unity](unity-get-pb-paywalls)</InlineTooltip>. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener paywalls y productos en tu app (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos a tu paywall](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall al placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-ios) en tu app. </details> ## Obtener información del paywall \{#fetch-paywall-information\} En Adapty, un [producto](product) es una combinación de productos tanto de App Store como de Google Play. Estos productos multiplataforma se integran en paywalls, lo que te permite mostrarlos en placements específicos de tu aplicación móvil. Para mostrar los productos, necesitas obtener un [Paywall](paywalls) desde uno de tus [placements](placements) con el método `getPaywall`. :::important **No escribas los IDs de productos en el código.** El único ID que debes incluir directamente en el código es el del placement. Los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios dinámicamente: si hoy un paywall devuelve dos productos y mañana tres, muéstralos todos sin necesidad de modificar el código. ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID") // the requested paywall } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de lo irregular que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls en dos capas: la caché de actualización periódica descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera para este método. Si se alcanza el timeout, se devolverán los datos en caché o el fallback local.</p><p></p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede comprender varias solicitudes internamente.</p> | ¡No uses IDs de producto codificados directamente en el código! Como los paywalls se configuran de forma remota, los productos disponibles, su cantidad y las ofertas especiales (como pruebas gratuitas) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si al principio obtienes 2 productos, tu app debería mostrar esos 2 productos. Sin embargo, si más adelante obtienes 3 productos, tu app debería mostrar los 3 sin necesidad de modificar el código. Lo único que debes codificar directamente es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Un objeto [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall) con: una lista de IDs de productos, el identificador del paywall, Remote Config y varias otras propiedades. | ## Obtener productos \{#fetch-products\} Una vez que tienes el paywall, puedes consultar el array de productos que le corresponde: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let products = try await Adapty.getPaywallProducts(paywall: paywall) // the requested products array } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallProducts(paywall: paywall) { result in switch result { case let .success(products): // the requested products array case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Lista de objetos [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y otras propiedades. | Al implementar tu propio diseño de paywall, es probable que necesites acceso a estas propiedades del objeto [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct). A continuación se ilustran las propiedades más utilizadas, pero consulta el documento enlazado para ver todos los detalles de todas las propiedades disponibles. | Propiedad | Descripción | |----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país de la store seleccionado por el usuario, no en el idioma del dispositivo. | | **Price** | Para mostrar el precio en formato localizado, usa `product.localizedPrice`. Esta localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número con `product.price`. El valor se devolverá en la moneda local. Para obtener el símbolo de moneda correspondiente, usa `product.currencySymbol`. | | **Subscription Period** | Para mostrar el período (p. ej., semana, mes, año, etc.), usa `product.localizedSubscriptionPeriod`. Esta localización se basa en el idioma del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscriptionPeriod`. Desde ahí puedes acceder al enum `unit` para conocer la unidad de tiempo (es decir, día, semana, mes, año o desconocido). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral verías `.month` en la propiedad unit y `3` en numberOfUnits. | | **Introductory Offer** | Para mostrar una insignia u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscriptionOffer`. Este objeto contiene las siguientes propiedades útiles:<br/>• `offerType`: un enum con los valores `introductory`, `promotional` y `winBack`. Las pruebas gratuitas y las suscripciones con descuento inicial serán del tipo `introductory`.<br/>• `price`: el precio con descuento como número. Para las pruebas gratuitas, busca `0` aquí.<br/>• `localizedPrice`: el precio del descuento formateado según la configuración regional del usuario.<br/>• `localizedNumberOfPeriods`: una cadena localizada según el idioma del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días muestra `3 days` en este campo.<br/>• `subscriptionPeriod`: como alternativa, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma forma que se describe en la sección anterior.<br/>• `localizedSubscriptionPeriod`: el período de suscripción del descuento formateado según la configuración regional del usuario. | ## Comprobar la elegibilidad de la oferta introductoria en iOS \{#check-intro-offer-eligibility-on-ios\} De forma predeterminada, el método `getPaywallProducts` comprueba la elegibilidad para las ofertas introductorias, promocionales y de recuperación. Si necesitas mostrar los productos antes de que el SDK determine la elegibilidad de las ofertas, usa el método `getPaywallProductsWithoutDeterminingOffer` en su lugar. :::note Tras mostrar los productos iniciales, asegúrate de llamar al método `getPaywallProducts` habitual para actualizar los productos con información precisa sobre la elegibilidad de las ofertas. ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let products = try await Adapty.getPaywallProductsWithoutDeterminingOffer(paywall: paywall) // el array de productos solicitados sin subscriptionOffer } catch { // manejar el error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallProductsWithoutDeterminingOffer(paywall: paywall) { result in switch result { case let .success(products): // el array de productos solicitados sin subscriptionOffer case let .failure(error): // manejar el error } } ``` </TabItem> </Tabs> ## Acelera la carga del paywall con el paywall de audiencia predeterminada \{#speed-up-paywall-fetching-with-default-audience-paywall\} Normalmente, los paywalls se obtienen casi al instante, por lo que no necesitas preocuparte por optimizar este proceso. Sin embargo, cuando tienes muchas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, la carga de un paywall puede tardar más de lo deseado. En esas situaciones, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia fluida en lugar de no mostrar ningún paywall. Para resolver esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, tal como se detalla en la sección [Obtener información del paywall](fetch-paywalls-and-products#fetch-paywall-information) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene varios inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes paywalls para distintas versiones de la app (la actual y futuras), es posible que encuentres dificultades. Tendrás que diseñar paywalls compatibles con la versión actual (heredada) o asumir que los usuarios con esa versión podrían tener problemas al no poder renderizar los paywalls. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluyendo criterios como país, atribución de marketing o atributos personalizados propios). Si estás dispuesto a aceptar estas desventajas para beneficiarte de una carga más rápida del paywall, usa el método `getPaywallForDefaultAudience` de la siguiente manera. De lo contrario, utiliza `getPaywall` descrito [anteriormente](fetch-paywalls-and-products#fetch-paywall-information). ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID") // the requested paywall } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallForDefaultAudience(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // el paywall solicitado case let .failure(error): // gestiona el error } } ``` </TabItem> </Tabs> :::note El método `getPaywallForDefaultAudience` está disponible a partir de la versión 2.11.2 del SDK de iOS. ::: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios puede que no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo irregular que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | </SDKv3> --- # File: present-remote-config-paywalls --- --- title: "Renderizar paywall diseñado con Remote Config en el SDK de iOS" description: "Descubre cómo presentar paywalls de Remote Config en Adapty para personalizar la experiencia del usuario." --- <SDKv4> Si has personalizado un paywall con Remote Config, tendrás que implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú decides qué incluir y cómo se ve la vista del paywall. Adapty proporciona un método para obtener la configuración remota, dándote autonomía para mostrar tu paywall personalizado. No olvides [comprobar si un usuario es elegible para una oferta introductoria en iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios) y adaptar la vista del paywall para gestionar el caso en que sea elegible. ## Obtener el Remote Config de un flow y mostrarlo \{#get-flow-remote-config-and-present-it\} En v4, un flow contiene una entrada `AdaptyRemoteConfig` por cada idioma configurado en el array `remoteConfigs`. Elige el idioma que coincida con la preferencia del usuario y lee los valores que necesites. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") let config = flow.remoteConfigs.first(where: { $0.locale == "en" }) ?? flow.remoteConfigs.first let headerText = config?.dictionary?["header_text"] as? String } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in let flow = try? result.get() let config = flow?.remoteConfigs.first(where: { $0.locale == "en" }) ?? flow?.remoteConfigs.first let headerText = config?.dictionary?["header_text"] as? String } ``` </TabItem> </Tabs> En este punto, una vez que hayas recibido todos los valores necesarios, es hora de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a diferentes pantallas y orientaciones de móvil, ofreciendo una experiencia fluida y fácil de usar en distintos dispositivos. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls#track-paywall-view-events) como se describe a continuación, para que el análisis de Adapty pueda recopilar información para funnels y pruebas A/B. ::: Una vez que hayas mostrado el paywall, continúa configurando el flow de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu flow. Para más información sobre el método `.makePurchase()`, consulta [Realizar compras](making-purchases). Te recomendamos [crear un paywall de respaldo llamado paywall de respaldo](fallback-paywalls). Este respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Rastrear eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus paywalls. Aunque los datos de compras se recopilan automáticamente, el registro de las visualizaciones de paywall requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un paywall. Para registrar un evento de visualización de paywall, simplemente llama a `.logShowFlow(flow)`, y se reflejará en las métricas de tu paywall en los embudos y pruebas A/B. :::important No es necesario llamar a `.logShowFlow(flow)` si estás mostrando flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder). En esos casos, Adapty registra las vistas automáticamente. ::: ```swift showLineNumbers try await Adapty.logShowFlow(flow) ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :-------- | :------- |:-----------------------------------------------------------------------------------------| | **flow** | required | Un objeto `AdaptyFlow` obtenido mediante `Adapty.getFlow(placementId:)`. | </SDKv4> <SDKv3> Si has personalizado un paywall mediante Remote Config, deberás implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú controlas qué se incluye y cómo se ve tu paywall. Proporcionamos un método para obtener la configuración remota, dándote autonomía para mostrar tu paywall personalizado configurado a través de Remote Config. No olvides [comprobar si un usuario es elegible para una oferta introductoria en iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios) y ajustar la vista del paywall para gestionar el caso en que sea elegible. ## Obtener el Remote Config de un paywall y mostrarlo \{#get-paywall-remote-config-and-present-it\} Para obtener el Remote Config de un paywall, accede a la propiedad `remoteConfig` y extrae los valores que necesites. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID") let headerText = paywall.remoteConfig?.dictionary?["header_text"] as? String } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID") { result in let paywall = try? result.get() let headerText = paywall?.remoteConfig?.dictionary?["header_text"] as? String } ``` </TabItem> </Tabs> En este punto, una vez que hayas recibido todos los valores necesarios, es momento de renderizarlos y componerlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a distintas pantallas y orientaciones de dispositivos móviles, ofreciendo una experiencia fluida y cómoda en cualquier dispositivo. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls#track-paywall-view-events) tal como se describe a continuación, para que los análisis de Adapty puedan capturar información para funnels y pruebas A/B. ::: Una vez que hayas terminado de mostrar el paywall, continúa configurando el flow de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu paywall. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](making-purchases). Recomendamos [crear un paywall de respaldo llamado paywall de respaldo](fallback-paywalls). Este respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Registrar eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus paywalls. Aunque los datos de compras se recopilan automáticamente, registrar las visualizaciones de paywalls requiere tu intervención, ya que solo tú sabes cuándo un cliente ve un paywall. Para registrar un evento de visualización de paywall, simplemente llama a `.logShowPaywall(paywall)`, y quedará reflejado en las métricas de tu paywall en los embudos y pruebas A/B. :::important No es necesario llamar a `.logShowPaywall(paywall)` si estás mostrando paywalls creados en el [Paywall Builder](adapty-paywall-builder). ::: ```swift showLineNumbers Adapty.logShowPaywall(paywall) ``` | Parámetro | Presencia | Descripción | | :---------- | :-------- |:-----------------------------------------------------------------------------------------| | **paywall** | requerido | Un objeto [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall). | </SDKv3> --- # File: making-purchases --- --- title: "Realizar compras in-app en iOS SDK" description: "Guía para gestionar compras in-app y suscripciones con Adapty." --- Mostrar paywalls en tu aplicación móvil es un paso esencial para ofrecer a los usuarios acceso a contenido o servicios premium. Sin embargo, con solo mostrar estos paywalls es suficiente para gestionar las compras únicamente si usas el [Paywall Builder](adapty-paywall-builder) para personalizar tus paywalls. Si no usas el Paywall Builder, debes usar un método separado llamado `.makePurchase()` para completar una compra y desbloquear el contenido deseado. Este método es la puerta de entrada para que los usuarios interactúen con los paywalls y completen sus transacciones. Si tu paywall tiene una oferta promocional activa para el producto que el usuario quiere comprar, Adapty la aplicará automáticamente en el momento de la compra. :::warning Ten en cuenta que la oferta introductoria solo se aplicará automáticamente si usas los paywalls configurados con el Paywall Builder. En otros casos, tendrás que [verificar la elegibilidad del usuario para una oferta introductoria en iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Omitir este paso puede provocar que tu app sea rechazada durante la revisión. Además, podría dar lugar a que se cobre el precio completo a usuarios que son elegibles para una oferta introductoria. ::: Asegúrate de haber realizado la [configuración inicial](quickstart) sin saltarte ningún paso. Sin ella, no podemos validar las compras. ## Realizar compra \{#make-purchase\} :::note **¿Usas el [Paywall Builder](adapty-paywall-builder)?** Las compras se procesan automáticamente: puedes saltarte este paso. **¿Buscas instrucciones paso a paso?** Consulta la [guía de inicio rápido](ios-implement-paywalls-manually) para obtener instrucciones de implementación completas con todo el contexto. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let purchaseResult = try await Adapty.makePurchase(product: product) switch purchaseResult { case .userCancelled: // Handle the case where the user canceled the purchase case .pending: // Handle deferred purchases (e.g., the user will pay offline with cash) case let .success(profile, transaction): if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // Grant access to the paid features } } } catch { // Handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.makePurchase(product: product) { result in switch result { case let .success(purchaseResult): switch purchaseResult { case .userCancelled: // Handle the case where the user canceled the purchase case .pending: // Handle deferred purchases (e.g., the user will pay offline with cash) case let .success(profile, transaction): if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // Grant access to the paid features } } case let .failure(error): // Handle the error } } ``` </TabItem> </Tabs> Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | requerido | Un objeto [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct) obtenido del paywall. | Parámetros de la respuesta: | Parámetro | Descripción | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Si la solicitud se ha realizado con éxito, la respuesta contiene este objeto. Un objeto [AdaptyProfile](https://swift.adapty.io/documentation/adapty/adaptyprofile) proporciona información completa sobre los niveles de acceso, suscripciones y compras únicas de un usuario dentro de la app.</p><p>Comprueba el estado del nivel de acceso para determinar si el usuario tiene el acceso requerido a la app.</p> | :::warning **Nota:** si todavía usas una versión de StoreKit de Apple inferior a v2.0 y una versión del SDK de Adapty inferior a v2.9.0, debes proporcionar el [secreto compartido de Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret) en su lugar. Este método está actualmente obsoleto según Apple. ::: ## Compras in-app desde el App Store Cuando un usuario inicia una compra en el App Store y la transacción llega a tu app, tienes dos opciones: - **Procesar la transacción inmediatamente:** Devuelve `true` en `shouldAddStorePayment`. Esto activará la pantalla del sistema de compra de Apple de inmediato. - **Guardar el objeto de producto para procesarlo más tarde:** Devuelve `false` en `shouldAddStorePayment` y luego llama a `makePurchase` con el producto guardado más adelante. Esto puede ser útil si necesitas mostrar algo personalizado al usuario antes de iniciar una compra. Aquí tienes el fragmento completo: ```swift showLineNumbers title="Swift" final class YourAdaptyDelegateImplementation: AdaptyDelegate { nonisolated func shouldAddStorePayment(for product: AdaptyDeferredProduct) -> Bool { // 1a. // Return `true` to continue the transaction in your app. The Apple purchase system screen will show automatically. // 1b. // Store the product object and return `false` to defer or cancel the transaction. false } // 2. Continue the deferred purchase later on by passing the product to `makePurchase` when the timing is appropriate func continueDeferredPurchase() async { let storedProduct: AdaptyDeferredProduct = // get the product object from 1b. do { try await Adapty.makePurchase(product: storedProduct) } catch { // handle the error } } } ``` ## Canjear códigos de oferta en iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Sobre los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios concretos. A diferencia de las ofertas habituales, que se aplican de forma automática, los códigos de oferta se distribuyen fuera de la app — por email, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, accediendo a una URL de canje o a través de un diálogo dentro de la app. Para configurar códigos de oferta, abre una suscripción en App Store Connect y ve a su sección **Offer Codes**. Puedes crear [tres tipos](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) de códigos de oferta: - **Free** — la suscripción es gratuita durante un período determinado y la siguiente renovación se cobra al precio completo. - **Pay as you go** — el usuario paga un precio reducido en cada ciclo de facturación durante un período determinado y, después, la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único reducido por toda la duración de la oferta y, después, la suscripción se renueva al precio completo. No es necesario añadir los códigos de oferta a Adapty. Apple etiqueta cada transacción durante el período de la oferta con la categoría del código de oferta. Esto incluye el canje inicial y todas las renovaciones con descuento posteriores. Adapty detecta la etiqueta y registra cada transacción con la categoría de oferta `offer_code`. Cuando termina el período de oferta y la suscripción se renueva al precio completo, la etiqueta deja de estar presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Resolución de discrepancias en los ingresos \{#revenue-discrepancy-troubleshooting\} Si observas que una transacción con código de oferta aparece en Adapty al precio completo del producto en lugar del precio reducido, verifica lo siguiente en App Store Connect: - El código de oferta tiene los precios correctos configurados para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está configurado para el país o región específica del usuario. Apple envía el precio regional en la transacción. Si no hay ningún precio regional configurado para la oferta, Apple puede enviar el precio completo del producto. Puedes filtrar y verificar las transacciones con código de oferta en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds) mediante los filtros de tipo de oferta **Offer Code** y **Offer Discount Type**. #### Códigos promocionales heredados (obsoletos) \{#legacy-promo-codes-deprecated\} :::warning Apple dejó obsoletos los códigos promocionales para compras in-app en marzo de 2026. Los códigos de oferta los sustituyen con más funcionalidades: elegibilidad configurable, fechas de expiración y hasta 1 millón de códigos por trimestre. Si antes usabas códigos promocionales para compras in-app, migra a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app y versión) daban acceso gratuito a una suscripción. A diferencia de los códigos de oferta, Apple no incluía información de descuento en las transacciones con código promocional — enviaba el precio completo del producto en el recibo. Por ello, Adapty registraba estas transacciones al precio completo, lo que generaba discrepancias entre los análisis de Adapty y App Store Connect. Si ves transacciones históricas al precio completo que deberían haber sido gratuitas, es probable que provengan de códigos promocionales heredados. Como estos códigos ya están obsoletos, migra a los códigos de oferta para un seguimiento preciso de los ingresos. </Details> Para mostrar la pantalla de canje de códigos en tu app: ```swift showLineNumbers Adapty.presentCodeRedemptionSheet() ``` :::danger Según nuestras observaciones, la pantalla de canje de códigos de oferta puede no funcionar de forma fiable en algunas apps. Te recomendamos redirigir al usuario directamente al App Store. Para ello, debes abrir una URL con el siguiente formato: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: --- # File: restore-purchase --- --- title: "Restaurar compras en aplicaciones móviles con iOS SDK" description: "Aprende cómo restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- Restaurar compras es una función que permite a los usuarios recuperar el acceso a contenido previamente adquirido, como suscripciones o compras in-app, sin que se les cobre de nuevo. Esta función es especialmente útil para quienes hayan desinstalado y reinstalado la app, o hayan cambiado de dispositivo y quieran acceder a su contenido sin volver a pagar. :::note En los paywalls creados con [Paywall Builder](adapty-paywall-builder), las compras se restauran automáticamente sin que necesites añadir código adicional. Si es tu caso, puedes saltarte este paso. ::: Para restaurar una compra cuando no usas [Paywall Builder](adapty-paywall-builder) para personalizar el paywall, llama al método `.restorePurchases()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.restorePurchases() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // successful access restore } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.restorePurchases { [weak self] result in switch result { case let .success(profile): if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // successful access restore } case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> Parámetros de respuesta: | Parámetro | Descripción | |---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Un objeto [`AdaptyProfile`](https://swift.adapty.io/documentation/adapty/adaptyprofile). Este modelo contiene información sobre niveles de acceso, suscripciones y compras únicas.</p><p>Comprueba el **estado del nivel de acceso** para determinar si el usuario tiene acceso a la app.</p> | :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: --- # File: ios-transaction-management --- --- title: "Gestión avanzada de transacciones en el SDK de iOS" description: "Finaliza transacciones manualmente en tu app de iOS con el SDK de Adapty." --- :::note La gestión avanzada de transacciones está disponible en el SDK de Adapty para iOS a partir de la versión 3.12. ::: La gestión avanzada de transacciones en Adapty te da mayor control sobre cómo se procesan, verifican y finalizan las transacciones. Esta funcionalidad introduce tres características opcionales que trabajan en conjunto: | Característica | Propósito | |-------------------------------------------------------------|----------| | [`appAccountToken`](#assign-appaccounttoken) | Vincula las transacciones de Apple con tu ID de usuario interno | | [`jwsTransaction`](#access-the-jws-representation) | Proporciona el payload de transacción firmado por Apple para validación | | [Finalización manual](#control-transaction-finishing-behavior) | Permite finalizar transacciones solo después de que tu backend confirme el éxito | En conjunto, estas herramientas te permiten construir flujos de validación personalizados robustos mientras Adapty sigue sincronizando las transacciones con su backend. :::important La mayoría de las apps no necesitan esto. Por defecto, Adapty valida y finaliza automáticamente las transacciones de StoreKit. Usa esta guía solo si ejecutas tu propia validación en el backend o quieres controlar por completo el ciclo de vida de las compras. ::: ## Asignar `appAccountToken` \{#assign-appaccounttoken\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) es un **UUID** que te permite vincular las transacciones de la App Store con la identidad interna de tus usuarios. StoreKit asocia este token con cada transacción, de modo que tu backend puede relacionar los datos de la App Store con tus usuarios. Usa un UUID estable generado por usuario y reutilízalo para la misma cuenta en todos los dispositivos. Esto garantiza que las compras y las notificaciones de la App Store queden correctamente vinculadas. Puedes establecer el token de dos formas: durante la activación del SDK o al identificar al usuario. :::important Siempre debes pasar `appAccountToken` junto con `customerUserId`. Si solo pasas el token, no se incluirá en la transacción. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: UUID()) do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } // Or when identifying a user: do { try await Adapty.identify("YOUR_USER_ID", withAppAccountToken: UUID()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: <APP_ACCOUNT_TOKEN>) Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } // Or when identifying a user: Adapty.identify("YOUR_USER_ID", withAppAccountToken: <APP_ACCOUNT_TOKEN>) { error in if let error { // handle the error } } ``` </TabItem> </Tabs> ## Acceder a la representación JWS \{#access-the-jws-representation\} Al realizar una compra, el resultado incluye la transacción de Apple en [formato JWS Compact Serialization](https://developer.apple.com/documentation/storekit/verificationresult/jwsrepresentation-21vgo). Puedes reenviar este valor a tu backend para validación independiente o registro. ```swift let result = try await Adapty.makePurchase(product: paywallProduct) let jwsRepresentation = result.jwsTransaction ``` ## Controlar el comportamiento de finalización de transacciones \{#control-transaction-finishing-behavior\} Por defecto, Adapty finaliza automáticamente las transacciones de StoreKit tras la validación. Si necesitas retrasar la finalización hasta que tu backend confirme el éxito, establece el comportamiento de finalización en manual. En este modo: - Adapty sigue validando las compras y sincronizándolas con su backend. - Las transacciones permanecen sin finalizar hasta que llames explícitamente a `finish()`. ```swift var configBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_API_KEY") .with(transactionFinishBehavior: .manual) try await Adapty.activate(with: configBuilder.build()) ``` Al usar la finalización manual de transacciones, necesitas implementar el método delegado `onUnfinishedTransaction` para gestionar las transacciones sin finalizar: ```swift showLineNumbers title="Swift" extension YourApp: AdaptyDelegate { func onUnfinishedTransaction(_ transaction: AdaptyUnfinishedTransaction) async { // Perform your custom validation logic here // When ready, finish the transaction await transaction.finish() } } ``` Para obtener todas las transacciones sin finalizar actuales, usa el método `getUnfinishedTransactions()`: ```swift let unfinishedTransactions = try await Adapty.getUnfinishedTransactions() ``` --- # File: implement-observer-mode --- --- title: "Implementar el modo Observer en el SDK de iOS" description: "Implementa el modo Observer en Adapty para rastrear eventos de suscripción de usuarios en el SDK de iOS." --- Si ya tienes tu propia infraestructura de compras y no estás listo para migrar completamente a Adapty, puedes explorar el [modo Observer](observer-vs-full-mode). En su forma básica, el modo Observer ofrece analíticas avanzadas e integración fluida con sistemas de atribución y analíticas. Si esto cubre tus necesidades, solo tienes que: 1. Activarlo al configurar el SDK de Adapty estableciendo el parámetro `observerMode` en `true`. 2. [Reportar las transacciones](report-transactions-observer-mode) desde tu infraestructura de compras existente a Adapty. Si además necesitas paywalls y pruebas A/B, se requiere una configuración adicional, como se describe a continuación. ## Configuración del modo Observer \{#observer-mode-setup\} Activa el modo Observer si gestionas las compras y el estado de las suscripciones por tu cuenta y usas Adapty para enviar eventos de suscripción y analíticas. :::important Cuando se ejecuta en modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlo tú mismo. ::: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers @main struct YourApp: App { init() { // Configure Adapty SDK let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(observerMode: true) let config = configurationBuilder.build() // Activate Adapty SDK asynchronously Task { do { try await Adapty.activate(with: configurationBuilder) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } var body: some Scene { WindowGroup { // Your content view } } } } ``` </TabItem> <TabItem value="swift" label="UIKit" default> ```swift showLineNumbers Task { do { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(observerMode: true) let config = configurationBuilder.build() try await Adapty.activate(with: config) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } ``` </TabItem> </Tabs> Parámetros: | Parámetro | Descripción | | --------------------------- | ------------------------------------------------------------ | | observerMode | Un valor booleano que controla el [modo Observer](observer-vs-full-mode). El valor predeterminado es `false`. | ## Usar los paywalls de Adapty en el modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los paywalls y las funcionalidades de pruebas A/B de Adapty, puedes hacerlo, pero requiere una configuración adicional en el modo Observer. Esto es lo que necesitas hacer además de los pasos anteriores: 1. Muestra los paywalls de la forma habitual para los [paywalls con Remote Config](present-remote-config-paywalls). Para los paywalls con Paywall Builder, sigue las guías de configuración específicas para [iOS](ios-present-paywall-builder-paywalls-in-observer-mode). 3. [Asocia los paywalls](report-transactions-observer-mode) con las transacciones de compra. --- # File: report-transactions-observer-mode --- --- title: "Informar transacciones en Observer Mode en el SDK de iOS" description: "Informa transacciones de compra en el Observer Mode de Adapty para obtener información sobre usuarios y realizar un seguimiento de ingresos en el SDK de iOS." --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+ (current)" default> En el modo Observer, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Necesitas informar las transacciones desde tu app store. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` para informar explícitamente cada transacción y que Adapty pueda reconocerla. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `variationId` al informar una transacción. Esto vincula la compra con el paywall que la originó, garantizando un análisis preciso del paywall. ```swift showLineNumbers do { // every time when calling transasction.finish() try await Adapty.reportTransaction(transaction, withVariationId: <YOUR_PAYWALL_VARIATION_ID>) } catch { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | --------------- | --------- | ------------------------------------------------------------ | | **transaction** | requerido | <ul><li> Para StoreKit 1: SKPaymentTransaction.</li><li> Para StoreKit 2: Transaction.</li></ul> | | **variationId** | opcional | El ID único de la variante del paywall. Obtenlo desde la propiedad `variationId` del objeto [AdaptyPaywall](https://swift.adapty.io/documentation/adapty/adaptypaywall). | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x (legacy)" default> En el modo Observer, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Necesitas informar las transacciones desde tu app store o restaurarlas. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` para enviar los datos de la transacción a Adapty. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `withVariationId` al informar una transacción. Esto vincula la compra con el paywall que la originó, garantizando un análisis preciso del paywall. ```swift showLineNumbers do { // every time when calling transasction.finish() try await Adapty.reportTransaction(transaction, withVariationId: <YOUR_PAYWALL_VARIATION_ID>) } catch { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | --------------- | --------- | ------------------------------------------------------------ | | **transaction** | requerido | <ul><li> Para StoreKit 1: SKPaymentTransaction.</li><li> Para StoreKit 2: Transaction.</li></ul> | | **variationId** | opcional | El ID único de la variante del paywall. Obtenlo desde la propiedad `variationId` del objeto [AdaptyPaywall](https://swift.adapty.io/documentation/adapty/adaptypaywall). | </TabItem> <TabItem value="old2" label="Adapty SDK up to 3.2.x (legacy)" default> **Reporte de transacciones** - Las versiones hasta la 3.1.x escuchan automáticamente las transacciones en el App Store, por lo que no se requiere reporte manual. - La versión 3.2 no es compatible con el Observer Mode. **Asociar paywalls a transacciones** El SDK de Adapty no puede determinar el origen de las compras, ya que eres tú quien las procesa. Por ello, si planeas usar paywalls y/o pruebas A/B en el modo Observer, debes asociar la transacción proveniente de tu app store con el paywall correspondiente en el código de tu app. Es importante hacerlo correctamente antes de publicar tu app; de lo contrario, generará errores en los análisis. ```swift let variationId = paywall.variationId // There are two overloads: for StoreKit 1 and StoreKit 2 Adapty.setVariationId(variationId, forPurchasedTransaction: transactionId) { error in if error == nil { // successful binding } } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | variationId | requerido | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://swift.adapty.io/documentation/adapty/adaptypaywall). | | transactionId | requerido | <p>Para StoreKit 1: un objeto [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</p><p>Para StoreKit 2: objeto [Transaction](https://developer.apple.com/documentation/storekit/transaction).</p> | </TabItem> </Tabs> --- # File: ios-troubleshoot-purchases --- --- title: "Solucionar problemas de compras en el SDK de iOS" description: "Solucionar problemas de compras en el SDK de iOS" --- Esta guía te ayuda a resolver los problemas más comunes al implementar compras manualmente en el SDK de iOS. ## AdaptyError.cantMakePayments en el modo observador \{#adaptyerrorcantmakepayments-in-observer-mode\} **Problema**: Estás recibiendo `AdaptyError.cantMakePayments` al usar `makePurchase` en el modo observador. **Causa**: En el modo observador, debes gestionar las compras por tu cuenta, no usar el método `makePurchase` de Adapty. **Solución**: Si usas `makePurchase` para las compras, desactiva el modo observador. Puedes usar `makePurchase` o gestionar las compras por tu cuenta en el modo observador, pero no ambas opciones a la vez. Consulta [Implementar el modo observador](implement-observer-mode) para más detalles. ## No se encuentran makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Problema**: Estás experimentando problemas porque no se encuentran los `makePurchasesCompletionHandlers`. **Causa**: Esto suele estar relacionado con problemas en las pruebas de sandbox. **Solución**: Crea un nuevo usuario de sandbox e inténtalo de nuevo. Esto generalmente resuelve los problemas con el manejador de finalización de compras en sandbox. ## Otros problemas \{#other-issues\} **Problema**: Estás experimentando otros problemas relacionados con las compras que no se tratan más arriba. **Solución**: Si es necesario, migra el SDK a la versión más reciente siguiendo las [guías de migración](ios-sdk-migration-guides). Muchos problemas se resuelven en las versiones más nuevas del SDK. --- # File: ios-web-paywall --- --- title: "Implementar web paywalls en el SDK de iOS" description: "Configura un web paywall para cobrar sin las comisiones y revisiones de la App Store." --- :::important Antes de empezar, asegúrate de haber [configurado tu web paywall en el dashboard](web-paywall) y de tener instalada la versión 3.6.1 o posterior del SDK de Adapty. ::: ## Paywalls web abiertos \{#open-web-paywalls\} Si estás trabajando con un paywall que has desarrollado tú mismo, necesitas gestionar los paywalls web usando el método del SDK. El método `.openWebPaywall`: 1. Genera una URL única que permite a Adapty vincular un paywall específico mostrado a un usuario concreto con la página web a la que es redirigido. 2. Detecta cuándo tus usuarios regresan a la app y, a continuación, llama a `.getProfile` a intervalos cortos para determinar si los derechos de acceso del perfil han sido actualizados. De este modo, si el pago se realizó correctamente y se actualizaron los derechos de acceso, la suscripción se activa en la app casi de inmediato. ```swift showLineNumbers title="Swift" do { try await Adapty.openWebPaywall(for: product) } catch { print("Failed to open web paywall: \(error)") } ``` :::note Existen dos versiones del método `openWebPaywall`: 1. `openWebPaywall(product)`, que genera URLs a partir del paywall e incluye los datos del producto en las URLs. 2. `openWebPaywall(paywall)`, que genera URLs a partir del paywall sin incluir los datos del producto. Úsala cuando los productos de tu paywall en Adapty sean distintos a los del paywall web. ::: ## Gestión de errores \{#handle-errors\} | Error | Descripción | Acción recomendada | |-----------------------------------------|----------------------------------------------------------------------|--------------------------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | El paywall no tiene configurada una URL de compra web | Comprueba si el paywall está correctamente configurado en el Adapty Dashboard | | AdaptyError.productWithoutPurchaseUrl | El producto no tiene una URL de compra web | Verifica la configuración del producto en el Adapty Dashboard | | AdaptyError.failedOpeningWebPaywallUrl | No se pudo abrir la URL en el navegador | Revisa la configuración del dispositivo o proporciona un método de compra alternativo | | AdaptyError.failedDecodingWebPaywallUrl | No se pudieron codificar correctamente los parámetros en la URL | Verifica que los parámetros de la URL sean válidos y estén correctamente formateados | ## Ejemplo de implementación \{#implementation-example\} ```swift showLineNumbers title="Swift" class SubscriptionViewController: UIViewController { var paywall: AdaptyPaywall? @IBAction func purchaseButtonTapped(_ sender: UIButton) { guard let paywall = paywall, let product = paywall.products.first else { return } Task { await offerWebPurchase(for: product) } } func offerWebPurchase(for paywallProduct: AdaptyPaywallProduct) async { do { // Attempt to open web paywall try await Adapty.openWebPaywall(for: paywallProduct) } catch let error as AdaptyError { switch error { case .paywallWithoutPurchaseUrl, .productWithoutPurchaseUrl: showAlert(message: "Web purchase is not available for this product.") case .failedOpeningWebPaywallUrl: showAlert(message: "Could not open web browser. Please try again.") default: showAlert(message: "An error occurred: \(error.localizedDescription)") } } catch { showAlert(message: "An unexpected error occurred.") } } // Helper methods private func showAlert(message: String) { /* ... */ } } ``` :::note Cuando los usuarios vuelvan a la app, actualiza la interfaz para reflejar los cambios del perfil. `AdaptyDelegate` recibirá y procesará los eventos de actualización del perfil. ::: ## Abrir paywalls web en un navegador in-app \{#open-web-paywalls-in-an-in-app-browser\} :::important Abrir paywalls web en un navegador in-app es compatible a partir de Adapty SDK v3.15. ::: Por defecto, los paywalls web se abren en el navegador externo. Para ofrecer una experiencia de usuario fluida, puedes abrir los paywalls web en un navegador in-app. Esto muestra la página de compra web dentro de tu aplicación, permitiendo a los usuarios completar las transacciones sin cambiar de app. Para activarlo, establece el parámetro `in` en `.inAppBrowser`: ```swift showLineNumbers title="Swift" do { try await Adapty.openWebPaywall(for: product, in: .inAppBrowser) // default – .externalBrowser } catch { print("Failed to open web paywall: \(error)") } ``` --- # File: identifying-users --- --- title: "Identificar usuarios en iOS SDK" description: "Identifica usuarios en Adapty para mejorar las experiencias de suscripción personalizadas." --- Adapty crea un ID de perfil interno para cada usuario. Sin embargo, si tienes tu propio sistema de autenticación, deberías establecer tu propio Customer User ID. Puedes encontrar usuarios por su Customer User ID en la sección [Perfiles](profiles-crm) y usarlo en la [API del servidor](getting-started-with-server-side-api), que se enviará a todas las integraciones. ## Establecer el ID de usuario en la configuración \{#set-customer-user-id-on-configuration\} Si tienes un ID de usuario durante la configuración, pásalo como parámetro `customerUserId` al método `.activate()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } ``` </TabItem> </Tabs> :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Establecer el ID de usuario después de la configuración \{#set-customer-user-id-after-configuration\} Si no tienes un ID de usuario en la configuración del SDK, puedes establecerlo más adelante en cualquier momento con el método `.identify()`. Los casos más habituales para usar este método son después del registro o la autenticación, cuando el usuario pasa de ser un usuario anónimo a un usuario autenticado. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> </Tabs> Parámetros de la solicitud: - **Customer User ID** (obligatorio): un identificador de usuario en formato string. :::warning Reenvío de datos significativos del usuario En algunos casos, como cuando un usuario vuelve a iniciar sesión en su cuenta, los servidores de Adapty ya tienen información sobre ese usuario. En estos escenarios, el SDK de Adapty cambiará automáticamente para trabajar con el nuevo usuario. Si pasaste algún dato al usuario anónimo, como atributos personalizados o atribuciones de redes de terceros, debes volver a enviar esos datos para el usuario identificado. También es importante tener en cuenta que debes volver a solicitar todos los paywalls y productos después de identificar al usuario, ya que los datos del nuevo usuario pueden ser diferentes. ::: ## Cerrar sesión e iniciar sesión \{#logging-out-and-logging-in\} Puedes cerrar la sesión del usuario en cualquier momento llamando al método `.logout()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.logout() } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.logout { error in if error == nil { // successful logout } } ``` </TabItem> </Tabs> Después puedes iniciar sesión con el usuario usando el método `.identify()`. ## Configurar appAccountToken \{#set-appaccounttoken\} El [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) es un UUID que ayuda a StoreKit 2 de Apple a identificar a los usuarios entre instalaciones de la app y dispositivos. A partir del SDK de Adapty para iOS 3.10.2, puedes pasar el `appAccountToken` al configurar el SDK o al identificar a un usuario: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: UUID()) do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } // Or when identifying a user: do { try await Adapty.identify("YOUR_USER_ID", withAppAccountToken: UUID()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // Durante la configuración: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: UUID()) Adapty.activate(with: configurationBuilder.build()) { error in // maneja el error } // O al identificar a un usuario: Adapty.identify("YOUR_USER_ID", withAppAccountToken: UUID()) { error in if let error { // maneja el error } } ``` </TabItem> </Tabs> A continuación, puedes iniciar sesión del usuario con el método `.identify()`. ## Detectar usuarios en varios dispositivos \{#detect-users-across-devices\} Cuando el SDK se activa, lee automáticamente los derechos existentes del usuario desde StoreKit (iOS) o Google Play Billing (Android) y los sincroniza con el backend de Adapty. Una suscripción activa aparece en el perfil de Adapty sin que la app llame a `restorePurchases`. Lo que **no** ocurre automáticamente es reconocer que un perfil en un dispositivo nuevo pertenece al mismo usuario que el perfil en el dispositivo original. Adapty relaciona perfiles por Customer User ID, así que la continuidad de identidad depende de lo que uses como CUID. **Lo que Adapty puede detectar entre dispositivos** | Tu configuración | Lo que Adapty detecta | Lo que debes hacer | | --- | --- | --- | | Customer User ID = `device_id` (sin login en la app) | El nuevo dispositivo obtiene un CUID diferente y, por tanto, un perfil diferente. La suscripción se sincroniza con el nuevo perfil mediante un evento **Access level updated**, pero `subscription_started` no se dispara — el nuevo perfil se trata como heredero de la compra original. Los análisis basados en `subscription_started` contarán de menos a los usuarios que vuelven. | Usa un ID de cuenta estable como Customer User ID para que un usuario que regresa coincida con el perfil existente en todos los dispositivos. | | Customer User ID = ID de cuenta estable (login en cada dispositivo) | El SDK sincroniza automáticamente la suscripción en `activate()`, e `identify()` relaciona el perfil existente por CUID. | No se necesita configuración adicional — tanto la identidad como la suscripción se resuelven automáticamente. | | Heredero de Apple Family Sharing | El miembro de la familia recibe la suscripción solo a través de un evento **Access level updated** — `subscription_started` no se dispara. | Escucha el evento **Access level updated**. Consulta [Apple Family Sharing](apple-family-sharing) para ver la matriz de eventos completa. | | Misma cuenta de Apple/Google, distintos usuarios dentro de la app | El primer perfil que registra la compra se convierte en el principal. Los perfiles posteriores ven la suscripción a través de una cadena de herederos, con un evento **Access level updated**. | Exige login y elige un [modo de compartición](sharing-paid-access-between-user-accounts) que se adapte a tu modelo. | **Restaurar compras en un dispositivo nuevo** Muestra un botón "Restaurar compras" iniciado por el usuario en tu paywall. Apple App Review (directriz 3.1.1) lo exige, y actúa como alternativa cuando la sincronización automática no cubre algún caso límite. El botón debe llamar a `restorePurchases` en tu SDK. No es necesario llamar a `restorePurchases` de forma programática al primer inicio para el uso normal — el SDK ya ejecuta el equivalente en `activate()`. Reserva las llamadas programáticas para forzar una verificación de recibo actualizada, por ejemplo al depurar un acceso que falta después de que `activate()` haya completado. --- # File: setting-user-attributes --- --- title: "Definir atributos de usuario en el SDK de iOS" description: "Aprende a definir atributos de usuario en Adapty para mejorar la segmentación de audiencias." --- Puedes añadir atributos opcionales como el email, número de teléfono, etc., al usuario de tu app. Luego puedes usar esos atributos para crear [segmentos](segments) de usuarios o simplemente consultarlos en el CRM. ### Definir atributos de usuario \{#setting-user-attributes\} Para definir atributos de usuario, llama al método `.updateProfile()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(email: "email@email.com") .with(phoneNumber: "+18888888888") .with(firstName: "John") .with(lastName: "Appleseed") .with(gender: .other) .with(birthday: Date()) do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(email: "email@email.com") .with(phoneNumber: "+18888888888") .with(firstName: "John") .with(lastName: "Appleseed") .with(gender: .other) .with(birthday: Date()) Adapty.updateProfile(params: builder.build()) { error in if error != nil { // handle the error } } ``` </TabItem> </Tabs> Ten en cuenta que los atributos que hayas definido previamente con el método `updateProfile` no se restablecerán. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Lista de claves permitidas \{#the-allowed-keys-list\} Las claves `<Key>` permitidas de `AdaptyProfileParameters.Builder` y sus valores `<Value>` correspondientes son: | Clave | Valor | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | Enum, los valores permitidos son: `female`, `male`, `other` | | birthday | Date | ### Atributos de usuario personalizados \{#custom-user-attributes\} Puedes definir tus propios atributos personalizados, que normalmente están relacionados con el uso de tu app. Por ejemplo, en apps de fitness pueden ser el número de entrenamientos por semana; en apps de aprendizaje de idiomas, el nivel de conocimiento del usuario, etc. Puedes usarlos en segmentos para crear paywalls y ofertas dirigidas, y también en analíticas para identificar qué métricas de producto influyen más en los ingresos. ```swift showLineNumbers do { builder = try builder.with(customAttribute: "value1", forKey: "key1") } catch { // handle key/value validation error } ``` Para eliminar una clave existente, usa el método `.withRemoved(customAttributeForKey:)`: ```swift showLineNumbers do { builder = try builder.withRemoved(customAttributeForKey: "key2") } catch { // handle error } ``` A veces necesitas saber qué atributos personalizados ya están definidos. Para ello, usa el campo `customAttributes` del objeto `AdaptyProfile`. :::warning Ten en cuenta que el valor de `customAttributes` puede estar desactualizado, ya que los atributos de usuario pueden enviarse desde distintos dispositivos en cualquier momento, por lo que los atributos en el servidor podrían haber cambiado desde la última sincronización. ::: ### Límites \{#limits\} - Hasta 30 atributos personalizados por usuario. - Los nombres de clave tienen hasta 30 caracteres. El nombre de la clave puede incluir caracteres alfanuméricos y cualquiera de los siguientes: `_` `-` `.` - El valor puede ser una cadena de texto o un número decimal con un máximo de 50 caracteres. --- # File: subscription-status --- --- title: "Comprobar el estado de suscripción en iOS SDK" description: "Rastrea y gestiona el estado de suscripción de los usuarios en Adapty para mejorar la retención de clientes." --- Con Adapty, hacer seguimiento del estado de suscripción es muy sencillo. No tienes que insertar manualmente los IDs de producto en tu código. En su lugar, puedes confirmar fácilmente el estado de suscripción de un usuario comprobando si tiene un [nivel de acceso](access-level) activo. Antes de empezar a comprobar el estado de suscripción, configura las [Notificaciones del servidor de App Store](enable-app-store-server-notifications). ## Nivel de acceso y el objeto AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Los niveles de acceso son propiedades del objeto [AdaptyProfile](https://swift.adapty.io/documentation/adapty/adaptyprofile). Te recomendamos recuperar el perfil cuando tu app arranque, por ejemplo al [identificar a un usuario](identifying-users#set-customer-user-id-on-configuration), y actualizarlo cada vez que se produzcan cambios. Así podrás usar el objeto de perfil sin tener que solicitarlo repetidamente. Para recibir notificaciones de las actualizaciones del perfil, escucha los cambios tal como se describe en la sección [Escuchar actualizaciones del estado de suscripción](subscription-status#listening-for-subscription-status-updates) más abajo. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Obtener el nivel de acceso desde el servidor \{#retrieving-the-access-level-from-the-server\} Para obtener el nivel de acceso desde el servidor, usa el método `.getProfile()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> </Tabs> Parámetros de respuesta: | Parámetro | Descripción | | --------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Profile | <p>Un objeto [AdaptyProfile](https://swift.adapty.io/documentation/adapty/adaptyprofile). En general, solo necesitas comprobar el estado del nivel de acceso del perfil para determinar si el usuario tiene acceso premium a la app.</p><p></p><p>El método `.getProfile` devuelve el resultado más actualizado, ya que siempre intenta consultar la API. Si por algún motivo (por ejemplo, sin conexión a internet) el SDK de Adapty no puede obtener información del servidor, se devolverán los datos de la caché. También es importante destacar que el SDK de Adapty actualiza la caché de `AdaptyProfile` periódicamente para mantener esta información lo más actualizada posible.</p> | El método `.getProfile()` te proporciona el perfil del usuario a partir del cual puedes obtener el estado del nivel de acceso. Puedes tener múltiples niveles de acceso por app. Por ejemplo, si tienes una app de noticias y vendes suscripciones a diferentes temáticas de forma independiente, puedes crear los niveles de acceso "sports" y "science". Pero la mayoría de las veces solo necesitarás un nivel de acceso; en ese caso, puedes usar simplemente el nivel de acceso predeterminado "premium". A continuación tienes un ejemplo para comprobar el nivel de acceso predeterminado "premium": <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() let isPremium = profile.accessLevels["premium"]?.isActive ?? false // grant access to premium features } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get(), profile.accessLevels["premium"]?.isActive ?? false { // grant access to premium features } } ``` </TabItem> </Tabs> ### Escuchar actualizaciones del estado de suscripción \{#listening-for-subscription-status-updates\} Cada vez que cambia la suscripción del usuario, Adapty lanza un evento. Para recibir mensajes de Adapty, necesitas realizar una configuración adicional: ```swift showLineNumbers Adapty.delegate = self // To receive subscription updates, extend `AdaptyDelegate` with this method: nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { // handle any changes to subscription state } ``` Adapty también lanza un evento al inicio de la aplicación. En ese caso, se pasará el estado de suscripción almacenado en caché. ### Caché del estado de suscripción \{#subscription-status-cache\} La caché implementada en el SDK de Adapty almacena el estado de suscripción del perfil. Esto significa que, aunque el servidor no esté disponible, se puede acceder a los datos almacenados en caché para obtener información sobre el estado de suscripción del perfil. No obstante, hay que tener en cuenta que no es posible solicitar datos directamente desde la caché. El SDK consulta periódicamente el servidor cada minuto para comprobar si hay actualizaciones o cambios relacionados con el perfil. Si hay alguna modificación, como nuevas transacciones u otras actualizaciones, se enviarán a los datos almacenados en caché para mantenerlos sincronizados con el servidor. --- # File: ios-deal-with-att --- --- title: "Gestión de ATT en el SDK de iOS" description: "Comienza a usar Adapty en iOS para simplificar la configuración y gestión de suscripciones." --- Si tu aplicación utiliza el framework AppTrackingTransparency y presenta una solicitud de autorización de seguimiento al usuario, debes enviar el [estado de autorización](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) a Adapty. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(appTrackingTransparencyStatus: .authorized) do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers if #available(iOS 14, macOS 11.0, *) { let builder = AdaptyProfileParameters.Builder() .with(appTrackingTransparencyStatus: .authorized) Adapty.updateProfile(params: builder.build()) { [weak self] error in if error != nil { // handle the error } } } ``` </TabItem> </Tabs> :::warning Recomendamos encarecidamente que envíes este valor lo antes posible cuando cambie; solo así los datos se enviarán a tiempo a las integraciones que hayas configurado. ::: --- # File: kids-mode --- --- title: "Modo infantil en iOS SDK" description: "Activa fácilmente el Modo infantil para cumplir con las políticas de Apple. No se recopilan IDFA ni datos de publicidad en iOS SDK." --- <SDKv4> Si tu aplicación iOS está dirigida a niños, debes cumplir las políticas de [Apple](https://developer.apple.com/kids/). Si utilizas el SDK de Adapty, unos pocos pasos sencillos te permitirán configurarlo para cumplir con estas políticas y superar las revisiones del App Store. ## ¿Qué se necesita? \{#whats-required\} Debes configurar el SDK de Adapty para desactivar la recopilación de: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) - [Dirección IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Además, te recomendamos usar el customer user ID con cuidado. Un ID de usuario en formato `<FirstName.LastName>` se considerará definitivamente como recopilación de datos personales, al igual que el uso del correo electrónico. Para el Modo Niños, la mejor práctica es usar identificadores aleatorios o anonimizados (por ejemplo, IDs hasheados o UUIDs generados por el dispositivo) para garantizar el cumplimiento normativo. ## Activar el modo para niños \{#enabling-kids-mode\} ### Actualizaciones en el Adapty Dashboard \{#updates-in-the-adapty-dashboard\} En el Adapty Dashboard, debes desactivar la recopilación de direcciones IP. Para ello, ve a [App settings](https://app.adapty.io/settings/general) y haz clic en **Disable IP address collection** bajo **Collect users' IP address**. ### Actualizaciones en el código de tu aplicación móvil A partir del SDK 4.0, el Modo Infantil es un trait de Swift Package llamado `KidsMode`. Al activar el trait, IDFA y AdSupport se eliminan por completo del SDK — conservas los módulos habituales **Adapty** y **AdaptyUI** y los statements de importación estándar `import Adapty` / `import AdaptyUI`. :::note El trait `KidsMode` está disponible a partir de la versión 4.0 del SDK. A partir del SDK 4.0, el SDK se instala únicamente a través de Swift Package Manager — CocoaPods ya no es compatible. ::: <Tabs> <TabItem value="xcode" label="Xcode" default> 1. [Instala el SDK de Adapty](sdk-installation-ios) como de costumbre, seleccionando los módulos **Adapty** y **AdaptyUI** habituales. 2. En Xcode 26.4 o posterior, abre la configuración de tu proyecto, ve a la vista **Package Dependencies** y habilita el trait **KidsMode** para la dependencia AdaptySDK-iOS. :::note Las versiones de Xcode anteriores a la 26.4 no pueden activar traits para un proyecto de Xcode desde la UI. En ese caso, añade un paquete Swift local pequeño que dependa de Adapty con el trait `KidsMode` activado (consulta la pestaña **Package.swift**), y haz que el target de tu app dependa de ese paquete. ::: </TabItem> <TabItem value="spm" label="Package.swift"> Si añades Adapty como dependencia en `Package.swift`, activa el trait en la declaración del paquete. Los traits requieren `swift-tools-version` 6.1 o posterior. ```swift showLineNumbers title="Package.swift" .package( url: "https://github.com/adaptyteam/AdaptySDK-iOS.git", from: "4.0.0", traits: ["KidsMode"] ) ``` </TabItem> </Tabs> </SDKv4> <SDKv3> Si tu aplicación iOS está destinada a niños, debes cumplir con las políticas de [Apple](https://developer.apple.com/kids/). Si utilizas el SDK de Adapty, unos pocos pasos sencillos te ayudarán a configurarlo para cumplir con estas políticas y superar las revisiones del store. ## ¿Qué se necesita? \{#whats-required\} Debes configurar el SDK de Adapty para desactivar la recopilación de: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) - [Dirección IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Además, te recomendamos usar el customer user ID con cuidado. Un ID con el formato `<FirstName.LastName>` se considerará sin duda como recopilación de datos personales, al igual que usar un email. Para el Modo Kids, la mejor práctica es utilizar identificadores aleatorios o anonimizados (por ejemplo, IDs con hash o UUIDs generados por el dispositivo) para garantizar el cumplimiento normativo. ## Activar el modo para niños \{#enabling-kids-mode\} ### Actualizaciones en el Adapty Dashboard \{#updates-in-the-adapty-dashboard\} En el Adapty Dashboard, debes desactivar la recopilación de direcciones IP. Para ello, ve a [App settings](https://app.adapty.io/settings/general) y haz clic en **Disable IP address collection** bajo **Collect users' IP address**. ### Actualizaciones en el código de tu app para móvil \{#updates-in-your-mobile-app-code\} Para cumplir con las políticas, desactiva la recopilación del IDFA y la dirección IP del usuario. <Tabs> <TabItem value="spm" label="Swift Package Manager" default> Si usas Swift Package Manager, puedes activar el Modo Infantil seleccionando el módulo **Adapty_KidsMode** en Xcode al instalar el SDK. En Xcode, ve a **File** -> **Add Package Dependency...**. Ten en cuenta que los pasos para añadir dependencias de paquetes pueden variar según la versión de Xcode, así que consulta la documentación de Xcode si es necesario. 1. Introduce la URL del repositorio: ``` https://github.com/adaptyteam/AdaptySDK-iOS.git ``` 2. Selecciona la versión (se recomienda la última versión estable) y haz clic en **Add Package**. 3. En la ventana **Choose Package Products**, selecciona los módulos que necesites: - **Adapty_KidsMode** (módulo principal) - **AdaptyUI_KidsMode** (opcional — solo si planeas usar Paywall Builder) No necesitarás ningún otro paquete. 4. Haz clic en **Add Package** para completar la instalación. 5. En tu código, escribe `import Adapty_KidsMode` en lugar de `import Adapty`, y `import AdaptyUI_KidsMode` en lugar de `import AdaptyUI`: ```swift ``` </TabItem> <TabItem value="cocoapods" label="CocoaPods"> 1. Actualiza tu Podfile: - Si **no** tienes una sección `post_install`, añade el bloque de código completo a continuación. - Si **ya** tienes una sección `post_install`, fusiona las líneas resaltadas en ella. ```ruby showLineNumbers title="Podfile" def adapty_enable_kids_mode(installer) installer.pods_project.targets.each do |target| next unless target.name == 'Adapty' target.build_configurations.each do |config| flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)' flags = flags.join(' ') if flags.is_a?(Array) config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} -DADAPTY_KIDS_MODE" end target.frameworks_build_phase.files.dup.each do |bf| target.frameworks_build_phase.remove_build_file(bf) if bf.display_name.to_s.include?('AdSupport') end end installer.pods_project.save Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xc| File.write(xc, File.read(xc).gsub(/\s*-framework\s+"?AdSupport"?/, '')) end end post_install do |installer| # ... keep your existing post_install body (Flutter adds one automatically) ... adapty_enable_kids_mode(installer) # <-- enable Adapty Kids Mode end ``` 2. Ejecuta el siguiente comando para aplicar los cambios: ```sh showLineNumbers title="Shell" pod install ``` </TabItem> </Tabs> </SDKv3> --- # File: get-onboardings --- --- title: "Obtener onboardings y su configuración" description: "Aprende a recuperar onboardings en Adapty." --- :::tip **A partir de la versión 4 del SDK**, puedes crear [flows](get-pb-paywalls) como una alternativa más potente a los onboardings. A diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que proporciona animaciones más fluidas, un aspecto coherente con iOS, tiempos de carga más rápidos y sin dependencia del entorno WebView. Consulta [Obtener flows y paywalls](get-pb-paywalls) y [Mostrar flows y paywalls](ios-present-paywalls) para empezar. ::: Después de [diseñar la parte visual de tu onboarding](design-onboarding) con el builder en el Adapty Dashboard, puedes mostrarlo en tu aplicación móvil. El primer paso en este proceso es obtener el onboarding asociado al placement y su configuración de vista, como se describe a continuación. Antes de comenzar, asegúrate de que: 1. Tienes instalado el [SDK de Adapty para iOS, Android, React Native o Flutter](installation-of-adapty-sdks) versión 3.8.0 o superior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). ## Obtener el onboarding \{#fetch-onboarding\} Cuando creas un [onboarding](onboardings) con nuestro editor sin código, se almacena como un contenedor con la configuración que tu app necesita obtener y mostrar. Este contenedor gestiona toda la experiencia: qué contenido aparece, cómo se presenta y cómo se procesan las interacciones del usuario (como respuestas a cuestionarios o entradas de formularios). El contenedor también registra automáticamente los eventos de analítica, por lo que no necesitas implementar un seguimiento de vistas por separado. Para obtener el mejor rendimiento, obtén la configuración del onboarding con antelación para que las imágenes tengan tiempo suficiente de descargarse antes de mostrárselas a los usuarios. Para obtener un onboarding, usa el método `getOnboarding`: ```swift showLineNumbers do { let onboarding = try await Adapty.getOnboarding(placementId: "YOUR_PLACEMENT_ID") // the requested onboarding } catch { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché se mantiene al reiniciar la app y solo se borra cuando se desinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché de actualización regular descrita anteriormente y los onboardings de respaldo. También usamos CDN para cargar los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la última versión de tus onboardings y asegurar la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede consistir en diferentes peticiones internamente.</p> | Parámetros de respuesta: | Parámetro | Descripción | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Un objeto [`AdaptyOnboarding`](https://swift.adapty.io/documentation/adapty/adaptyonboarding) con: el identificador y la configuración del onboarding, Remote Config y varias otras propiedades. | ## Acelera la obtención del onboarding con el onboarding de audiencia predeterminada \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Normalmente, los onboardings se obtienen casi de forma instantánea, por lo que no necesitas preocuparte por optimizar este proceso. Sin embargo, si tienes muchas audiencias y onboardings, y tus usuarios tienen una conexión a internet lenta, la obtención de un onboarding puede tardar más de lo deseado. En esas situaciones, puede que quieras mostrar un onboarding predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ninguno. Para solucionar esto, puedes usar el método `getOnboardingForDefaultAudience`, que obtiene el onboarding del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el onboarding mediante el método `getOnboarding`, tal como se detalla en la sección [Obtener el onboarding](#fetch-onboarding) anterior. :::warning Considera usar `getOnboarding` en lugar de `getOnboardingForDefaultAudience`, ya que este último tiene limitaciones importantes: - **Problemas de compatibilidad**: Puede generar problemas al dar soporte a múltiples versiones de la app, lo que obliga a usar diseños compatibles con versiones anteriores o asumir que las versiones más antiguas podrían mostrarse incorrectamente. - **Sin personalización**: Solo muestra contenido para la audiencia "All Users", eliminando la segmentación por país, atribución o atributos personalizados. Si para tu caso de uso la obtención más rápida compensa estos inconvenientes, usa `getOnboardingForDefaultAudience` como se muestra a continuación. De lo contrario, usa `getOnboarding` como se describe [arriba](#fetch-onboarding). ::: ```swift showLineNumbers Adapty.getOnboardingForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(onboarding): // el onboarding solicitado case let .failure(error): // maneja el error } } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por una o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda, a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos utilizarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro utilizarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché de actualización periódica descrita anteriormente y los onboardings de respaldo. También usamos CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus onboardings, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.</p> | --- # File: ios-present-onboardings --- --- title: "Present onboardings in iOS SDK" description: "Discover how to present onboardings on iOS to boost conversions and revenue." --- :::tip **A partir del SDK v4**, puedes crear [flows](get-pb-paywalls) como alternativa más potente a los onboardings. A diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que te ofrece animaciones más fluidas, una apariencia coherente con iOS, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](get-pb-paywalls) y [Mostrar flows y paywalls](ios-present-paywalls) para empezar. ::: Si has personalizado un onboarding con el builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese onboarding ya incluye tanto lo que debe mostrarse como la forma en que debe mostrarse. Antes de empezar, asegúrate de: 1. Haber instalado el [SDK de Adapty para iOS](sdk-installation-ios) 3.8.0 o posterior. 2. Haber [creado un onboarding](create-onboarding). 3. Haber añadido el onboarding a un [placement](placements). ## Presentar onboardings en Swift \{#present-onboardings-in-swift\} Para mostrar el onboarding visual en la pantalla del dispositivo, sigue estos pasos: 1. Obtén la configuración de la vista del onboarding con el método `.getOnboardingConfiguration`. 2. Inicializa el onboarding visual que quieres mostrar usando el método `.onboardingController`: Parámetros de la solicitud: | Parámetro | Presencia | Descripción | |:-----------------------------|:---------|:------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onboarding configuration** | requerido | Un objeto `AdaptyUI.OnboardingConfiguration` que contiene todas las propiedades del onboarding. Usa el método `AdaptyUI.getOnboardingConfiguration` para obtenerlo. | | **delegate** | requerido | Un `AdaptyOnboardingControllerDelegate` para escuchar los eventos del onboarding. | Devuelve: | Objeto | Descripción | |:-------------------------------|:-------------------------------------------------------------| | **AdaptyOnboardingController** | Un objeto que representa la pantalla de onboarding solicitada | 3. Una vez creado correctamente el objeto, puedes mostrarlo en la pantalla del dispositivo: ```swift showLineNumbers title="Swift" import Adapty import AdaptyUI // 0. Get an onboarding if you haven't done it yet let onboarding = try await Adapty.getOnboarding(placementId: "YOUR_PLACEMENT_ID") // 1. Obtain the onboarding view configuration: let configuration = try AdaptyUI.getOnboardingConfiguration(forOnboarding: onboarding) // 2. Create Onboarding View Controller let onboardingController = try AdaptyUI.onboardingController( with: configuration, delegate: <AdaptyOnboardingControllerDelegate> ) // 3. Present it to the user present(onboardingController, animated: true) ``` ## Mostrar onboardings en SwiftUI \{#present-onboardings-in-swiftui\} Para mostrar el onboarding visual en la pantalla del dispositivo con SwiftUI: ```swift showLineNumbers title="SwiftUI" // 1. Obtain the onboarding view configuration: let configuration = try AdaptyUI.getOnboardingConfiguration(forOnboarding: onboarding) // 2. Display the Onboarding View within your view hierarchy AdaptyOnboardingView( configuration: configuration, placeholder: { Text("Your Placeholder View") }, onCloseAction: { action in // hide the onboarding view }, onError: { error in // handle the error } ) ``` ## Agregar transiciones suaves entre la pantalla de bienvenida y el onboarding \{#add-smooth-transitions-between-the-splash-screen-and-onboarding\} Por defecto, entre la pantalla de bienvenida y el onboarding verás una pantalla de carga hasta que el onboarding se haya cargado por completo. Sin embargo, si quieres que la transición sea más fluida, puedes personalizarla y prolongar la pantalla de bienvenida o mostrar otra cosa. Para ello, define un placeholder (lo que se mostrará mientras el onboarding se está cargando). Si defines un placeholder, el onboarding se cargará en segundo plano y se mostrará automáticamente cuando esté listo. <Tabs> <TabItem value="swift" label="UIKit"> ```swift showLineNumbers extension YourOnboardingManagerClass: AdaptyOnboardingControllerDelegate { func onboardingsControllerLoadingPlaceholder( _ controller: AdaptyOnboardingController ) -> UIView? { // instantiate and return the UIView which will be presented while onboarding is being loaded } } ``` </TabItem> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers AdaptyOnboardingView( configuration: configuration, placeholder: { // define your placeholder view, which will be presented while onboarding is being loaded }, // the rest of the implementation ) ``` </TabItem> </Tabs> ## Personaliza cómo se abren los enlaces en los onboardings \{#customize-how-links-open-in-onboardings\} :::important La personalización de cómo se abren los enlaces en los onboardings está disponible a partir de Adapty SDK v3.15.1. ::: Por defecto, los enlaces en los onboardings se abren en un navegador integrado en la app. Esto ofrece una experiencia fluida al mostrar las páginas web dentro de tu aplicación, sin que el usuario tenga que cambiar de app. Si prefieres abrir los enlaces en un navegador externo, puedes personalizar este comportamiento estableciendo el parámetro `externalUrlsPresentation` en `.externalBrowser`: ```swift showLineNumbers let configuration = try AdaptyUI.getOnboardingConfiguration( forOnboarding: onboarding, externalUrlsPresentation: .externalBrowser // default – .inAppBrowser ) ``` --- # File: ios-handling-onboarding-events --- --- title: "Gestionar eventos de onboarding en el SDK de iOS" description: "Gestiona eventos relacionados con onboardings en iOS usando Adapty." --- :::tip **A partir del SDK v4**, puedes crear [flows](get-pb-paywalls) como una alternativa más potente a los onboardings. A diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows renderizan de forma nativa en el dispositivo, lo que te proporciona animaciones más fluidas, una apariencia coherente con iOS, tiempos de carga más rápidos y sin dependencia del entorno WebView. Consulta [Obtener flows y paywalls](get-pb-paywalls) y [Mostrar flows y paywalls](ios-present-paywalls) para empezar. ::: Antes de comenzar, asegúrate de que: 1. Has instalado el [SDK de Adapty para iOS](sdk-installation-ios) 3.8.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). Los onboardings configurados con el builder generan eventos a los que tu app puede responder. A continuación se explica cómo hacerlo. Para controlar o supervisar los procesos que ocurren en la pantalla del onboarding dentro de tu app, implementa los métodos de `AdaptyOnboardingControllerDelegate`. ## Acciones personalizadas \{#custom-actions\} En el builder, puedes añadir una acción **personalizada** a un botón y asignarle un ID. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Luego puedes usar este ID en tu código y gestionarlo como una acción personalizada. Por ejemplo, si el usuario pulsa un botón personalizado, como **Login** o **Allow notifications**, el método delegado `onboardingController` se activará con el caso `.custom(id:)` y el parámetro `actionId` será el **Action ID** definido en el builder. Puedes crear tus propios IDs, como "allowNotifications". ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onCustomAction action: AdaptyOnboardingsCustomAction) { if action.actionId == "allowNotifications" { // Request notification permissions } } func onboardingController(_ controller: AdaptyOnboardingController, didFailWithError error: AdaptyUIError) { // Handle errors } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ## Cierre del onboarding \{#closing-onboarding\} El onboarding se considera cerrado cuando el usuario pulsa un botón con la acción **Close** asignada. <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Ten en cuenta que debes gestionar qué ocurre cuando el usuario cierra el onboarding. Por ejemplo, necesitas dejar de mostrar el onboarding. ::: Por ejemplo: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onCloseAction action: AdaptyOnboardingsCloseAction) { controller.dismiss(animated: true) } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ## Abrir un paywall \{#opening-a-paywall\} :::tip Maneja este evento para abrir un paywall dentro del onboarding. Si quieres abrirlo después de que se cierre el onboarding, hay una forma más directa de hacerlo: maneja [`AdaptyOnboardingsCloseAction`](#closing-onboarding) y abre el paywall sin depender de los datos del evento. ::: La forma más fluida de trabajar con paywalls en onboardings es que el ID de acción sea igual al ID de placement del paywall. Así, tras recibir `AdaptyOnboardingsOpenPaywallAction`, puedes usar el ID de placement para obtener y abrir el paywall directamente. Ten en cuenta que solo se puede mostrar una vista (paywall u onboarding) en pantalla a la vez. Si presentas un paywall encima de un onboarding, no podrás controlar el onboarding en segundo plano de forma programática. Si intentas cerrar el onboarding, se cerrará el paywall en su lugar, dejando el onboarding visible. Para evitar esto, cierra siempre la vista del onboarding antes de presentar el paywall. ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onPaywallAction action: AdaptyOnboardingsOpenPaywallAction) { // Dismiss onboarding before presenting the flow controller.dismiss(animated: true) { Task { do { // Get the flow using the placement ID from the action let flow = try await Adapty.getFlow(placementId: action.actionId) // Get the flow configuration let flowConfiguration = try await AdaptyUI.getFlowConfiguration( forFlow: flow ) // Create and present the flow controller let flowController = try AdaptyUI.flowController( with: flowConfiguration, delegate: self ) // Present the flow from the root view controller if let rootVC = UIApplication.shared.windows.first?.rootViewController { rootVC.present(flowController, animated: true) } } catch { // Handle any errors that occur during flow loading print("Failed to present flow: \(error)") } } } } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ## Finalización de carga del onboarding \{#finishing-loading-onboarding\} Cuando el onboarding termina de cargarse, se invocará este método: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, didFinishLoading action: OnboardingsDidFinishLoadingAction) { // Handle loading completion } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ## Seguimiento de navegación \{#tracking-navigation\} El método `onAnalyticsEvent` se llama cuando ocurren distintos eventos de análisis durante el flow de onboarding. El objeto `event` puede ser de los siguientes tipos: |Tipo | Descripción | |------------|-------------| | `onboardingStarted` | Cuando el onboarding se ha cargado | | `screenPresented` | Cuando se muestra cualquier pantalla | | `screenCompleted` | Cuando se completa una pantalla. Incluye `elementId` opcional (identificador del elemento completado) y `reply` opcional (respuesta del usuario). Se activa cuando los usuarios realizan cualquier acción para salir de la pantalla. | | `secondScreenPresented` | Cuando se muestra la segunda pantalla | | `userEmailCollected` | Se activa cuando se recoge el correo electrónico del usuario a través del campo de entrada | | `onboardingCompleted` | Se activa cuando un usuario llega a una pantalla con el ID `final`. Si necesitas este evento, [asigna el ID `final` a la última pantalla](design-onboarding). | | `unknown` | Para cualquier tipo de evento no reconocido. Incluye `name` (el nombre del evento desconocido) y `meta` (metadatos adicionales) | Cada evento incluye información `meta` con los siguientes campos: | Campo | Descripción | |------------|-------------| | `onboardingId` | Identificador único del flow de onboarding | | `screenClientId` | Identificador de la pantalla actual | | `screenIndex` | Posición de la pantalla actual en el flow | | `screensTotal` | Número total de pantallas en el flow | A continuación se muestra un ejemplo de cómo puedes usar los eventos de análisis para el seguimiento: ```swift func onboardingController(_ controller: AdaptyOnboardingController, onAnalyticsEvent event: AdaptyOnboardingsAnalyticsEvent) { switch event { case .onboardingStarted(let meta): // Track onboarding start trackEvent("onboarding_started", meta: meta) case .screenPresented(let meta): // Track screen presentation trackEvent("screen_presented", meta: meta) case .screenCompleted(let meta, let elementId, let reply): // Track screen completion with user response trackEvent("screen_completed", meta: meta, elementId: elementId, reply: reply) case .onboardingCompleted(let meta): // Track successful onboarding completion trackEvent("onboarding_completed", meta: meta) case .unknown(let meta, let name): // Handle unknown events trackEvent(name, meta: meta) // Handle other cases as needed } } ``` <Details> <summary>Ejemplos de eventos (haz clic para ampliar)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: ios-onboarding-input --- --- title: "Procesar datos de onboardings en el SDK de iOS" description: "Guarda y usa datos de onboardings en tu app de iOS con el SDK de Adapty." --- :::tip **A partir del SDK v4**, puedes crear [flows](get-pb-paywalls) como alternativa más potente a los onboardings. A diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia y experiencia consistente en iOS, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](get-pb-paywalls) y [Mostrar flows y paywalls](ios-present-paywalls) para empezar. ::: Cuando tus usuarios responden a una pregunta de un cuestionario o introducen datos en un campo de texto, se invocará el método `onStateUpdatedAction`. Puedes guardar o procesar el tipo de campo en tu código. Por ejemplo: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onStateUpdatedAction action: AdaptyOnboardingsStateUpdatedAction) { // Almacenar preferencias o respuestas del usuario switch action.params { case .select(let params): // Manejar selección única case .multiSelect(let params): // Manejar selecciones múltiples case .input(let params): // Manejar entrada de texto case .datePicker(let params): // Manejar selección de fecha } } ``` El objeto `action` contiene: | Parámetro | Descripción | |----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `elementId` | Un identificador único para el elemento de entrada. Puedes usarlo para asociar preguntas con respuestas al guardarlas. | | `params` | El objeto de datos de entrada del usuario que contiene las propiedades de tipo y valor. | | `params.type` | El tipo de elemento de entrada. Puede ser:<br/>• `"select"` - Selección única entre opciones<br/>• `"multiSelect"` - Selecciones múltiples entre opciones<br/>• `"input"` - Campo de entrada de texto<br/>• `"datePicker"` - Selección de fecha | | `params.value` | El valor o valores seleccionados o introducidos por el usuario. La estructura depende del tipo:<br/>• `select`: Objeto con `id`, `value`, `label`<br/>• `multiSelect`: Array de objetos con `id`, `value`, `label`<br/>• `input`: Objeto con `type`, `value`<br/>• `datePicker`: Objeto con `day`, `month`, `year` | <Details> <summary>Ejemplos de datos guardados (pueden diferir en tu implementación)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## Casos de uso \{#use-cases\} ### Enriquece los perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Si quieres vincular inmediatamente los datos introducidos con el perfil del usuario y evitar pedirle la misma información dos veces, necesitas [actualizar el perfil del usuario](setting-user-attributes) con esos datos al gestionar la acción. Por ejemplo, pides a los usuarios que introduzcan su nombre en el campo de texto con el ID `name`, y quieres usar ese valor como nombre de pila del usuario. También les pides que introduzcan su email en el campo `email`. En el código de tu app, podría tener este aspecto: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onStateUpdatedAction action: AdaptyOnboardingsStateUpdatedAction) { // Store user preferences or responses switch action.params { case .input(let params): // Handle text input let builder = AdaptyProfileParameters.Builder() // Map elementId to appropriate profile field switch action.elementId { case "name": builder.with(firstName: params.value.value) case "email": builder.with(email: params.value.value) default: break } // Delegate methods are synchronous; kick off the async update in a Task. Task { do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } } default: break } } ``` ### Personaliza los paywalls en función de las respuestas \{#customize-paywalls-based-on-answers\} Con los cuestionarios en los onboardings, también puedes personalizar los paywalls que muestras a los usuarios después de que completen el onboarding. Por ejemplo, puedes preguntarles sobre su experiencia con el deporte y mostrar distintos CTAs y productos a diferentes grupos de usuarios. 1. [Añade un cuestionario](onboarding-quizzes) en el constructor de onboarding y asigna IDs significativos a sus opciones. 2. Gestiona las respuestas del cuestionario según sus IDs y [establece atributos personalizados](setting-user-attributes) para los usuarios. ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onStateUpdatedAction action: AdaptyOnboardingsStateUpdatedAction) { // Handle quiz responses and set custom attributes switch action.params { case .select(let params): // Handle quiz selection let builder = AdaptyProfileParameters.Builder() // Map quiz responses to custom attributes switch action.elementId { case "experience": // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) try? builder.with(customAttribute: params.value.value, forKey: "experience") default: break } // Delegate methods are synchronous; kick off the async update in a Task. Task { do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } } default: break } } ``` 3. [Crea segmentos](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade [audiencias](audience) para cada segmento que hayas creado. 5. [Muestra un paywall](ios-paywalls) para el placement en el código de tu app. Si tu onboarding tiene un botón que abre un paywall, implementa el código del paywall como [respuesta a la acción de ese botón](ios-handling-onboarding-events#opening-a-paywall). --- # File: ios-sdk-call-order --- --- title: "Orden de llamadas en el SDK de iOS" description: "Evita perder el acceso premium, problemas con la atribución y errores intermitentes #2002 llamando a los métodos del SDK de Adapty en el orden correcto." --- `Adapty.activate()` debe completarse antes de llamar a cualquier otro método del SDK de Adapty. Hasta que se resuelva, el SDK no tiene estado. Cualquier llamada realizada antes o en paralelo con `activate()` fallará con [`#2002 notActivated`](ios-sdk-error-handling#network-errors). Si tu app autentica usuarios y recopilas un customer user ID después del lanzamiento, llama a `Adapty.identify()` en ese momento. No llames a métodos de acción del usuario hasta que `identify` se resuelva. Las llamadas que compiten con él o bien fallan con [`#3006 profileWasChanged`](ios-sdk-error-handling#general-errors), o recaen sobre el perfil anónimo creado en la activación. Cuando esto ocurre, la atribución, los IDs de MMP como `appsflyer_id` y la propiedad de la instalación no siempre se transfieren al perfil identificado. Si tu app no autentica usuarios, omite `identify` y sigue trabajando con el perfil anónimo. Los SDK de MMP y analítica (AppsFlyer, Adjust, Branch, PostHog) siguen la misma regla. Inicialízalos primero y espera a sus callbacks de UID antes de llamar a `Adapty.activate`. De lo contrario, el ID del MMP queda asociado a un perfil anónimo temporal y no siempre se transfiere al perfil identificado. Para detalles específicos de AppsFlyer, consulta [AppsFlyer](appsflyer). ## El orden correcto \{#the-correct-order\} Tu ruta depende de dos factores: cuándo conoces el customer user ID y si usas un MMP o SDK de analíticas. - **Pasos 2 y 5**: Obligatorios para todas las apps. Activa el SDK y luego llama a los métodos del SDK. - **Pasos 1 y 3**: Necesarios solo si integras un MMP o SDK de analíticas (AppsFlyer, Adjust, Branch, PostHog). - **Paso 4**: Necesario solo si tu app autentica usuarios y recoge el customer user ID después del lanzamiento. Si tienes el ID de usuario del cliente en el arranque de la app, pásalo directamente a `activate()` (paso 2a). Esta ruta nunca crea un perfil anónimo, por lo que el paso 4 no es necesario. | Paso | Llamada | Cuándo | Notas | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Inicializa tu MMP o SDK de analítica (AppsFlyer, Adjust, PostHog, Branch) | Al lanzar la app, lo primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `Adapty.activate(with: config)` con `customerUserId` configurado en el config | Al lanzar la app, tras el paso 1, si tienes el ID de usuario | Recomendado. No se crea nunca un perfil anónimo. | | 2b | `Adapty.activate(with: config)` sin `customerUserId` | Al lanzar la app, tras el paso 1, si no tienes el ID de usuario (o nunca lo recopilas) | Adapty crea un perfil anónimo. | | 3 | `Adapty.setIntegrationIdentifier(...)` para cada MMP | Tras el paso 2, antes de cualquier llamada de acción del usuario | Necesario para que los IDs del MMP queden en el perfil correcto. | | 4 | `try await Adapty.identify("YOUR_USER_ID")` | Tras el paso 3 (o el paso 2 si no hay MMP), antes del paso 5 — solo en la ruta 2b con autenticación | Siempre usa `await`. Las llamadas concurrentes durante `identify` producen `#3006 profileWasChanged`. | | 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | Tras el paso 4 si llamas a `identify`; si no, tras el paso 3 (o el paso 2 si no hay MMP) | Estas llamadas necesitan un perfil estable. | :::important Omitir estos pasos provoca pérdida de acceso premium para usuarios que regresan, ausencia de `appsflyer_id` en los perfiles y paywalls devueltos para la audiencia incorrecta. ::: ## Instalaciones web2app y de embudo web \{#web2app-and-web-funnel-installs\} Si los usuarios compran en un checkout web (Stripe, Paddle, FunnelFox) e instalan después la app nativa, el primer `activate()` del dispositivo crea un nuevo perfil anónimo. Este perfil no está vinculado al perfil web. Si puedes resolver el customer user ID antes del lanzamiento de la app (desde tu flujo de autenticación o referrer de instalación), pásalo directamente a `activate()`. De lo contrario, la compra web será invisible en el dispositivo hasta que llames a `identify("YOUR_USER_ID")` y luego a `restorePurchases`. Para los metadatos que debes enviar con cada checkout web, consulta: - [Stripe](stripe) - [Paddle](paddle) --- # File: ios-optimize-paywall-fetching --- --- title: "Optimizar la carga del paywall en el SDK de iOS" description: "Carga paywalls de Adapty de forma fiable: temporización, caché y patrones de respaldo para iOS." --- Una carga fiable del paywall en iOS hace tres cosas: renderiza rápido, devuelve el paywall orientado a la audiencia y recurre al respaldo sin problemas cuando la red es lenta. Las reglas siguientes cubren los patrones de temporización, caché y respaldo para conseguirlo. :::tip Las reglas asumen que `Adapty.activate()` y `Adapty.identify()` ya han resuelto. Consulta [Orden de llamadas en el SDK de iOS](ios-sdk-call-order). ::: ## Reglas y errores comunes \{#rules-and-pitfalls\} | Haz esto | No hagas esto | Por qué | |---|---|---| | Carga el placement que vas a mostrar. | Precarga todos los placements de forma concurrente al iniciar. | La precarga masiva bloquea el hilo principal y produce una pantalla en negro durante la ráfaga. | | Llama a `getPaywall` después de que la atribución haya tenido tiempo de resolverse — por ejemplo, 1–2 segundos después de `activate` o cuando se dispare `onProfileUpdate`. | Llama a `getPaywall` en `App.init()`. | La atribución aún no ha llegado. El paywall se resuelve contra la audiencia predeterminada y omite silenciosamente los segmentos y la personalización de ASA. | | Establece un `loadTimeout` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | Esperes a `getPaywall` indefinidamente. | Sin un tiempo límite, los usuarios con mala conectividad ven una pantalla en blanco hasta que la red responde — o cierran la app. | Consulta [Cargar paywalls y productos](fetch-paywalls-and-products) para la referencia de los parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado. ## Ajustar para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte, regiones afectadas por el enrutamiento): - Establece `fetchPolicy: .returnCacheDataElseLoad` en cada carga excepto la primera. - Configura un [paywall de respaldo](fallback-paywalls) para cada placement en el Adapty Dashboard. - Establece `loadTimeout` en 3–5 segundos y acepta el respaldo cuando se agote el tiempo. - No condicionar la visualización del paywall a `getProfile()`. Llama a `getPaywall` de forma independiente para que un perfil lento no bloquee la UI. --- # File: ios-show-aa-targeted-paywall --- --- title: "Mostrar un paywall segmentado con Apple Ads en el primer lanzamiento en iOS SDK" description: "Espera la atribución de Apple Ads antes de solicitar el paywall en iOS usando AdaptyProfile.appliedAttributionSources." --- Apple Ads (AA) la atribución llega de forma asíncrona después de `Adapty.activate()`. Si llamas a `getPaywall` demasiado pronto, la atribución aún no ha llegado y Adapty resuelve el placement contra la audiencia predeterminada, saltándose tus paywalls segmentados por AA. `AdaptyProfile.appliedAttributionSources` permite a la app detectar cuándo se ha aplicado la atribución de AA al perfil, de modo que la solicitud del paywall pueda esperar hasta que la segmentación de AA se resuelva correctamente. ## Antes de empezar \{#before-you-start\} Necesitas: - Adapty iOS SDK **3.17.1** o posterior. - Apple Ads configurado para la app en Adapty. Consulta [Apple Ads](apple-search-ads). ## Cómo funciona \{#how-it-works\} Tras `Adapty.activate()`, el SDK solicita en segundo plano la atribución de Apple Ads a Apple y reenvía el resultado al backend de Adapty. Cuando AA se convierte en la fuente de atribución activa del perfil, el SDK entrega un `AdaptyProfile` actualizado cuyo array `appliedAttributionSources` contiene `.appleAds`. Un array vacío puede significar cualquiera de estas situaciones: - La atribución de Apple Ads aún no se ha procesado para este perfil. - No ha llegado ninguna atribución. Incluso con un array vacío, `getPaywall` sigue siendo seguro de llamar — Adapty resuelve la solicitud con la audiencia que coincida con el estado actual del perfil, normalmente la audiencia por defecto. :::important La espera solo aplica en el **primer lanzamiento**. Una vez que la atribución de Apple Ads ha sido registrada, queda almacenada permanentemente en el perfil. En cada lanzamiento posterior, el perfil en caché ya incluye `.appleAds` en `appliedAttributionSources`, `didLoadLatestProfile` se dispara con ese valor de inmediato, y `getPaywall` devuelve el paywall segmentado por Apple Ads sin ningún retraso. ::: ## Implementación \{#implementation\} En el primer lanzamiento, controla la aparición de `.appleAds` en el perfil y aplica un timeout estricto: si la atribución de Apple Ads nunca llega, esos usuarios igualmente deben ver un paywall. 1. **Activa el SDK.** Consulta [Instalar y configurar el SDK de iOS](sdk-installation-ios). 2. **Suscríbete a las actualizaciones del perfil** implementando `AdaptyDelegate` y el método `didLoadLatestProfile`. Si todavía no has configurado el delegate, consulta [Escuchar actualizaciones de suscripción](ios-check-subscription-status#listen-to-subscription-updates). 3. **Observa `.appleAds` en `appliedAttributionSources`.** Cuando aparezca, solicita el paywall — Adapty devolverá la variante segmentada por AA: ```swift extension <YourAdaptyDelegateImpl>: AdaptyDelegate { nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { if profile.appliedAttributionSources.contains(where: { $0 == .appleAds }) { // load paywall via Adapty.getPaywall(placementId:) } } } ``` 4. **Inicia un temporizador de 3 a 5 segundos en paralelo con la suscripción.** Si el temporizador se dispara antes de que aparezca `.appleAds`, solicita el paywall de todas formas: Cualquiera de los dos caminos que se active primero debe cargar el paywall; el otro debe ignorarse. Usa un único indicador de estado (por ejemplo, `hasLoadedPaywall`) para deduplicar y evitar que el paywall se solicite dos veces. Configura un [paywall de respaldo](fallback-paywalls) para el placement para que el usuario nunca se quede bloqueado si la solicitud de red falla. ## Ejemplo completo \{#complete-example\} La implementación que sigue ejecuta en paralelo la espera de la atribución con un timeout y la precarga del paywall de la audiencia por defecto, devolviendo el paywall apropiado según el resultado. El código que llama solo necesita hacer `await` a una única función asíncrona: sin delegados ni flags de estado en el punto de llamada. `ProfileObserver` es un singleton reutilizable que publica actualizaciones del perfil desde `AdaptyDelegate`. `PaywallLoader.getPaywallOrDefault` ejecuta la carrera mediante un `TaskGroup` de concurrencia estructurada: - Si la atribución llega dentro del `timeout`, devuelve el paywall segmentado mediante `getPaywall(placementId:)`. - Si el `timeout` expira primero, devuelve el paywall de la audiencia predeterminada prefetchado mediante `getPaywallForDefaultAudience(placementId:)`. ```swift title="PaywallLoader.swift" /// Demuestra cómo obtener un paywall que depende de que se aplique la atribución, /// usando como respaldo el paywall de la audiencia predeterminada si la atribución /// no llega a tiempo. /// /// Sin estado y autocontenido: cada llamada inicia su propia solicitud anticipada /// para la audiencia predeterminada y la compite contra la obtención segmentada /// con atribución. enum PaywallLoader { static func getPaywallOrDefault( placementId: String, timeout: TimeInterval ) async throws -> AdaptyPaywall { struct TimedOut: Error {} // Iniciamos la solicitud de audiencia predeterminada de inmediato para que // tenga toda la ventana de `timeout` para cargarse. La cancelaremos si // hay éxito, o esperaremos su resultado si se agota el tiempo; nunca // habrá una llamada de red duplicada. let defaultPaywallTask = Task { try await Adapty.getPaywallForDefaultAudience(placementId: placementId) } do { // Competimos dos tareas secundarias: gana la que termine primero. let result = try await withThrowingTaskGroup(of: AdaptyPaywall.self) { group in // 1. Esperamos la atribución y luego pedimos a Adapty el paywall segmentado. group.addTask { await waitForAttribution() return try await Adapty.getPaywall(placementId: placementId) } // 2. Temporizador: lanza `TimedOut` tras `timeout` segundos. group.addTask { try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000)) throw TimedOut() } guard let value = try await group.next() else { throw CancellationError() } group.cancelAll() // detiene la tarea perdedora (el temporizador o la espera de atribución). return value } // Ganó el paywall segmentado: ya no necesitamos la solicitud anticipada de la audiencia predeterminada. defaultPaywallTask.cancel() return result } catch is TimedOut { // La atribución no se aplicó a tiempo: devolvemos el resultado anticipado // de la audiencia predeterminada (instantáneo si ya terminó; si no, // esperamos la solicitud en curso). return try await defaultPaywallTask.value } } /// Suspende hasta que se observa un perfil con la fuente de atribución deseada. /// `@Published.values` emite el perfil actual de inmediato al suscribirse, /// por lo que retorna en la primera iteración si la atribución ya está aplicada. @MainActor private static func waitForAttribution() async { for await profile in ProfileObserver.shared.$profile.values { if profile?.appliedAttributionSources.contains(.appleAds) == true { return } } } } @MainActor final class ProfileObserver: AdaptyDelegate { static let shared = ProfileObserver() @Published private(set) var profile: AdaptyProfile? nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { Task { @MainActor [weak self] in self?.profile = profile } } } ``` Conecta `ProfileObserver` a `AdaptyDelegate` una vez, después de que `Adapty.activate()` termine: ```swift Adapty.delegate = ProfileObserver.shared ``` Llámalo desde la pantalla de inicio: ```swift do { let paywall = try await PaywallLoader.getPaywallOrDefault( placementId: "YOUR_PLACEMENT_ID", timeout: 5 ) // present the paywall } catch { // handle the error or show a fallback paywall } ``` Si tu app ya usa un `AdaptyDelegate` para otros fines (por ejemplo, [escuchar actualizaciones de suscripción](ios-check-subscription-status#listen-to-subscription-updates)), reenvía `didLoadLatestProfile` a `ProfileObserver.shared` desde tu delegado existente en lugar de establecer `Adapty.delegate = ProfileObserver.shared`. --- # File: ios-test --- --- title: "Prueba y lanzamiento en el SDK de iOS" description: "Aprende a comprobar el estado de la suscripción en tu app de iOS con Adapty." --- Si ya has implementado el SDK de Adapty en tu app de iOS, querrás comprobar que todo está configurado correctamente y que las compras funcionan como se espera. Esto implica probar tanto la integración del SDK como las compras en sí. ## Prueba tu app \{#test-your-app\} Para realizar pruebas exhaustivas de tus compras in-app, incluyendo pruebas en sandbox y validación en TestFlight, consulta nuestra [guía de pruebas](test-purchases-in-sandbox). ## Prepárate para el lanzamiento \{#prepare-for-release\} Antes de enviar tu app al store, sigue el [checklist de lanzamiento](release-checklist) para confirmar que: - La conexión con el store y las notificaciones del servidor están configuradas - Las compras se completan y se reportan a Adapty - El acceso se desbloquea y se restaura correctamente - Se cumplen los requisitos de privacidad y revisión --- # File: InvalidProductIdentifiers --- --- title: "Solución al error Code-1000 noProductIDsFound" description: "Resuelve los errores de identificador de producto no válido al gestionar suscripciones en Adapty." --- El error con código 1000, `noProductIDsFound`, indica que ninguno de los productos que solicitaste en el paywall está disponible para su compra en el App Store, aunque aparezcan listados allí. Este error puede ir acompañado a veces de una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin error, puedes ignorarla sin problema. Si te encuentras con el error `noProductIDsFound`, sigue estos pasos para resolverlo: ## Paso 1. Verifica el bundle ID \{#step-2-check-bundle-id\} 1. Abre [App Store Connect](https://appstoreconnect.apple.com/apps). Selecciona tu app y ve a la sección **General** → **App Information**. 2. Copia el **Bundle ID** en la subsección **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Abre la pestaña [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) desde el menú superior de Adapty y pega el valor copiado en el campo **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Vuelve a la página **App information** en App Store Connect y copia el **Apple ID** desde allí. 5. En la página [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) del Adapty Dashboard, pega el ID en el campo **Apple app ID**. ## Paso 2. Verifica los productos 1. Ve a **App Store Connect** y navega a [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) en el menú de la izquierda. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripciones. Verás tus productos listados en la sección **Subscriptions**. 3. Asegúrate de que el producto que estás probando figure como **Ready to Submit**. Si no es así, sigue las instrucciones de la página [Producto en App Store](app-store-products). <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Compara el ID del producto de la tabla con el que aparece en la pestaña [**Products**](https://app.adapty.io/products) del Adapty Dashboard. Si los IDs no coinciden, copia el ID del producto de la tabla y [crea un producto](create-product) con ese ID en el Adapty Dashboard. <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 3. Verifica la disponibilidad del producto \{#step-4-check-product-availability\} 1. Vuelve a **App Store Connect** y abre la misma sección **Subscriptions**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripciones para ver tus productos. 3. Selecciona el producto que estás probando. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hasta la sección **Availability** y comprueba que todos los países y regiones necesarios están listados. <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 4. Verifica los precios del producto \{#step-5-check-product-prices\} 1. De nuevo, ve a la sección **Monetization** → **Subscriptions** en **App Store Connect**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripciones. 3. Selecciona el producto que estás probando. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hacia abajo hasta **Subscription Pricing** y expande la sección **Current Pricing for New Subscribers**. <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Asegúrate de que todos los precios necesarios están listados. <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 5. Verifica que el estado de pago de la app, la cuenta bancaria y los formularios fiscales estén activos 1. En la página de inicio de [**App Store Connect**](https://appstoreconnect.apple.com/), haz clic en **Business**. <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Selecciona el nombre de tu empresa. <img src="/assets/shared/img/business-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Desplázate hacia abajo y comprueba que tu **Paid Apps Agreement**, **Bank Account** y **Tax forms** aparecen todos como **Active**. <img src="/assets/shared/img/appstore-connect-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Siguiendo estos pasos, deberías poder resolver la advertencia `InvalidProductIdentifiers` y publicar tus productos en el store. ## Paso 6. Vuelve a crear el producto si está bloqueado Los pasos 1–5 pueden superarse correctamente — estado `Approved`, Bundle ID coincidente, API key válida — y aun así el SDK devuelve `1000 noProductIDsFound`. En ese caso, puede que el producto esté bloqueado en el registro de Apple. El registro de productos de Apple entra ocasionalmente en un estado en el que un producto existe en la interfaz de App Store Connect pero no queda expuesto en la ruta de consulta de StoreKit. Elimina el producto en App Store Connect y vuelve a crearlo con el mismo ID de producto. Espera hasta 24 horas tras la recreación para que se propague. --- # File: cantMakePayments --- --- title: "Solución para el error Code-1003 cantMakePayment" description: "Resuelve el error de realización de pagos al gestionar suscripciones en Adapty." --- El error 1003, `cantMakePayments`, indica que no es posible realizar compras in-app en este dispositivo. Si encuentras el error `cantMakePayments`, normalmente se debe a una de estas razones: - Restricciones del dispositivo: El error no está relacionado con Adapty. Consulta las soluciones más abajo. - Configuración del modo Observer: El método `makePurchase` y el modo Observer no pueden usarse al mismo tiempo. Consulta la sección más abajo. ## Problema: Restricciones del dispositivo \{#issue-device-restrictions\} | Problema | Solución | |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| | Restricciones de Screen Time | Desactiva las restricciones de compras in-app en [Screen Time](https://support.apple.com/en-us/102470) | | Cuenta suspendida | Contacta con el soporte de Apple para resolver problemas con la cuenta | | Restricciones regionales | Usa una cuenta de App Store de una región compatible | ## Problema: Usar el modo Observer y makePurchase a la vez \{#issue-using-both-observer-mode-and-makepurchase\} Si usas `makePurchases` para gestionar las compras, no necesitas el modo Observer. El [modo Observer](observer-vs-full-mode) solo es necesario si implementas la lógica de compra tú mismo. Por lo tanto, si usas `makePurchase`, puedes eliminar sin problema la activación del modo Observer del código de inicialización del SDK. --- # File: migration-to-ios-sdk-v4 --- --- title: "Migrar Adapty iOS SDK a v4.0" description: "Migra al Adapty iOS SDK v4.0 reemplazando las APIs de paywall por APIs de flow, compatibles tanto con Flow Builder como con Paywall Builder." --- Adapty iOS SDK 4.0 introduce los flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan tanto con el nuevo Flow Builder como con el Paywall Builder existente — no es necesario realizar ningún cambio de configuración en el Adapty Dashboard. ## Referencia rápida \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty.getPaywall(placementId:locale:)` | `Adapty.getFlow(placementId:)` | | `AdaptyUI.getPaywallConfiguration(forPaywall:)` | `AdaptyUI.getFlowConfiguration(forFlow:locale:)` | | `Adapty.getPaywallProducts(paywall:)` | `Adapty.getPaywallProducts(flow:)` | | `Adapty.logShowPaywall(_:)` | `Adapty.logShowFlow(_:)` | | `AdaptyPaywallController` | `AdaptyFlowController` | | `AdaptyPaywallControllerDelegate` | `AdaptyFlowControllerDelegate` | | `AdaptyUI.paywallController(with:delegate:)` | `AdaptyUI.flowController(with:delegate:)` | | `.paywall()` (modificador SwiftUI) | `.flow()` | | `AdaptyPaywallView` | `AdaptyFlowView` | | `didFailRenderingWith:` / `didFailRendering:` | `didReceiveError:` | | `didFinishPurchase` (opcional, se cierra automáticamente al completarse) | `didFinishPurchase` (obligatorio, sin cierre automático) | | Productos del paquete `Adapty_KidsMode` / `AdaptyUI_KidsMode` | Trait de paquete `KidsMode` | | `Adapty.updateAttribution(_:source:)` (`source: String`) | `Adapty.updateAttribution(_:source:)` (`source: AdaptyAttributionSource`) | | `Adapty.setIntegrationIdentifier(key:value:)` | `Adapty.setIntegrationIdentifier(_:)` (`AdaptyIntegrationIdentifier`) | ## Versión mínima de iOS \{#minimum-ios-version\} Adapty iOS SDK 4.0 eleva el objetivo mínimo de despliegue de iOS 13.0 a **iOS 15.0**. Establece el iOS Deployment Target de tu proyecto en 15.0 o superior antes de actualizar. ## Instalación: CocoaPods ya no está soportado \{#installation-cocoapods-no-longer-supported\} El SDK de Adapty para iOS 4.0 elimina el soporte de CocoaPods. Instala el SDK con [Swift Package Manager](sdk-installation-ios#install-adapty-sdk). Si tu proyecto todavía usa CocoaPods, elimina los pods `Adapty` y `AdaptyUI` de tu `Podfile`, ejecuta `pod install` para limpiarlos y luego añade el paquete en Xcode desde **File → Add Package Dependency** usando `https://github.com/adaptyteam/AdaptySDK-iOS.git`. ## Kids Mode: productos separados reemplazados por un rasgo de paquete \{#kids-mode-separate-products-replaced-by-a-package-trait\} En v3, activabas el [Kids Mode](kids-mode) seleccionando los productos de paquete separados **Adapty_KidsMode** y **AdaptyUI_KidsMode** y renombrando tus imports. En v4.0, estos productos han sido eliminados. Kids Mode es ahora un rasgo de paquete Swift llamado `KidsMode` en el paquete Adapty normal — al activarlo, se eliminan IDFA y AdSupport de todo el SDK en tiempo de compilación. Para migrar: 1. En la ventana **Choose Package Products**, selecciona los productos regulares **Adapty** y **AdaptyUI** en lugar de **Adapty_KidsMode** y **AdaptyUI_KidsMode**. 2. Habilita el trait `KidsMode`. En Xcode 26.4 o posterior, actívalo para la dependencia AdaptySDK-iOS en la vista **Package Dependencies** de tu proyecto. Si añades Adapty como dependencia en `Package.swift` (requiere `swift-tools-version` 6.1 o posterior), actívalo ahí: ```swift showLineNumbers title="Package.swift" .package( url: "https://github.com/adaptyteam/AdaptySDK-iOS.git", from: "4.0.0", traits: ["KidsMode"] ) ``` 3. Vuelve a cambiar tus imports a los módulos normales: ```diff showLineNumbers - import Adapty_KidsMode - import AdaptyUI_KidsMode + import Adapty + import AdaptyUI ``` :::note Las versiones de Xcode anteriores a la 26.4 no pueden activar traits para un proyecto de Xcode desde la interfaz. En ese caso, añade un pequeño paquete Swift local que dependa de Adapty con el trait `KidsMode` activado, y haz que el target de tu app dependa de ese paquete. ::: ## APIs eliminadas \{#removed-apis\} - **`Adapty.getPaywallProductsWithoutDeterminingOffer(paywall:)`** — eliminada. Todos los productos incluyen ahora información sobre la oferta, por lo que el paso de elegibilidad separado ya no es necesario. - **`AdaptyPaywallProductWithoutDeterminingOffer`** — eliminada. Los callbacks que antes pasaban este tipo (como `didSelectProduct`) ahora pasan `AdaptyPaywallProduct`. ## Compras in-app promocionadas en App Store temporalmente eliminadas \{#app-store-promoted-in-app-purchases-temporarily-removed\} Como parte de la migración a StoreKit 2, el SDK de Adapty para iOS 4.0 elimina el soporte para las compras in-app promocionadas en App Store. El método delegado `shouldAddStorePayment(for:)` y el tipo `AdaptyDeferredProduct` que recibe no están disponibles en la versión 4.0. :::warning Esta eliminación es temporal: el soporte para compras in-app promocionadas volverá en una versión posterior de la rama 4.x. Si tu app depende de las compras in-app promocionadas, mantente en el SDK de iOS 3.x hasta que el soporte regrese. ::: ## Obtener paywalls \{#fetching-paywalls\} ### getPaywall + getPaywallConfiguration → getFlow + getFlowConfiguration Los tipos devueltos cambian de `AdaptyPaywall` / `AdaptyUI.PaywallConfiguration` a `AdaptyFlow` / `AdaptyUI.FlowConfiguration`. El parámetro `locale` deja de estar en la llamada de obtención y pasa a `getFlowConfiguration`: ```diff showLineNumbers - let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") - let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration(forPaywall: paywall) + let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") + let flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow, locale: "en") ``` ### getPaywallProducts(paywall:) → getPaywallProducts(flow:) `getPaywallProducts` ahora recibe un `AdaptyFlow` devuelto por `Adapty.getFlow`: ```diff showLineNumbers - let products = try await Adapty.getPaywallProducts(paywall: paywall) + let products = try await Adapty.getPaywallProducts(flow: flow) ``` ## Seguimiento de vistas de paywall \{#tracking-paywall-views\} ### logShowPaywall(_:) → logShowFlow(_:) `logShowPaywall` ha sido renombrado a `logShowFlow` y ahora recibe un `AdaptyFlow` en lugar de un `AdaptyPaywall`. El evento sigue registrándose contra la misma variación, por lo que las métricas de funnel y de prueba A/B existentes seguirán funcionando sin cambios en el dashboard. ```diff showLineNumbers - try await Adapty.logShowPaywall(paywall) + try await Adapty.logShowFlow(flow) ``` Al igual que en v3, no es necesario llamar a este método al mostrar flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder) — Adapty registra esas vistas automáticamente. ## didFinishPurchase ahora es obligatorio \{#didfinishpurchase-is-now-required\} En v3, `didFinishPurchase` era opcional: si no lo implementabas, el paywall se cerraba automáticamente tras una compra exitosa. En v4.0, esta implementación de cierre automático se ha eliminado para que un flow pueda continuar después de una compra exitosa — por ejemplo, para mostrar las pantallas restantes de tu flow. Ahora tú decides qué ocurre después de una compra: cerrar la pantalla o no hacer nada para dejar que el flow continúe. - **UIKit**: los que implementen `AdaptyFlowControllerDelegate` deben implementar `didFinishPurchase` — ya no tiene una implementación por defecto. - **SwiftUI**: el closure `didFinishPurchase` de `.flow(...)` y `AdaptyFlowView(...)` ahora es obligatorio (no opcional), igual que `didFailPurchase` y `didFinishRestore`. Para mantener el comportamiento de v3, cierra la pantalla tú mismo: ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didFinishPurchase product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if !purchaseResult.isPurchaseCancelled { controller.dismiss(animated: true) } } ``` ## UIKit \{#uikit\} ### AdaptyPaywallController → AdaptyFlowController Renombra el tipo de controlador y el método factory: ```diff showLineNumbers - let controller = try AdaptyUI.paywallController( - with: paywallConfiguration, - delegate: self - ) + let controller = try AdaptyUI.flowController( + with: flowConfiguration, + delegate: self + ) ``` ### AdaptyPaywallControllerDelegate → AdaptyFlowControllerDelegate Renombra el protocolo y actualiza cada firma de método. Ten en cuenta que `didSelectProduct` ahora recibe `AdaptyPaywallProduct` en lugar del eliminado `AdaptyPaywallProductWithoutDeterminingOffer`, y `didFinishPurchase` [ahora debe implementarse](#didfinishpurchase-is-now-required) — ya no tiene una implementación predeterminada. ```diff showLineNumbers - class YourClass: AdaptyPaywallControllerDelegate { + class YourClass: AdaptyFlowControllerDelegate { - func paywallControllerDidAppear(_ controller: AdaptyPaywallController) { } + func flowControllerDidAppear(_ controller: AdaptyFlowController) { } - func paywallControllerDidDisappear(_ controller: AdaptyPaywallController) { } + func flowControllerDidDisappear(_ controller: AdaptyFlowController) { } - func paywallController(_ controller: AdaptyPaywallController, - didPerform action: AdaptyUI.Action) { } + func flowController(_ controller: AdaptyFlowController, + didPerform action: AdaptyUI.Action) { } - func paywallController(_ controller: AdaptyPaywallController, - didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer) { } + func flowController(_ controller: AdaptyFlowController, + didSelectProduct product: AdaptyPaywallProduct) { } - func paywallController(_ controller: AdaptyPaywallController, - didStartPurchase product: AdaptyPaywallProduct) { } + func flowController(_ controller: AdaptyFlowController, + didStartPurchase product: AdaptyPaywallProduct) { } - func paywallController(_ controller: AdaptyPaywallController, - didFinishPurchase product: AdaptyPaywallProduct, - purchaseResult: AdaptyPurchaseResult) { } + func flowController(_ controller: AdaptyFlowController, + didFinishPurchase product: AdaptyPaywallProduct, + purchaseResult: AdaptyPurchaseResult) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailPurchase product: AdaptyPaywallProduct, - error: AdaptyError) { } + func flowController(_ controller: AdaptyFlowController, + didFailPurchase product: AdaptyPaywallProduct, + error: AdaptyError) { } - func paywallControllerDidStartRestore(_ controller: AdaptyPaywallController) { } + func flowControllerDidStartRestore(_ controller: AdaptyFlowController) { } - func paywallController(_ controller: AdaptyPaywallController, - didFinishRestoreWith profile: AdaptyProfile) { } + func flowController(_ controller: AdaptyFlowController, + didFinishRestoreWith profile: AdaptyProfile) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailRestoreWith error: AdaptyError) { } + func flowController(_ controller: AdaptyFlowController, + didFailRestoreWith error: AdaptyError) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailRenderingWith error: AdaptyUIError) { } + func flowController(_ controller: AdaptyFlowController, + didReceiveError error: AdaptyUIError) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailLoadingProductsWith error: AdaptyError) -> Bool { } + func flowController(_ controller: AdaptyFlowController, + didFailLoadingProductsWith error: AdaptyError) -> Bool { } - func paywallController(_ controller: AdaptyPaywallController, - didPartiallyLoadProducts failedIds: [String]) { } + func flowController(_ controller: AdaptyFlowController, + didPartiallyLoadProducts failedIds: [String]) { } - func paywallController(_ controller: AdaptyPaywallController, - didFinishWebPaymentNavigation product: AdaptyPaywallProduct?, - error: AdaptyError?) { } + func flowController(_ controller: AdaptyFlowController, + didFinishWebPaymentNavigation product: AdaptyPaywallProduct?, + error: AdaptyError?) { } } ``` ## SwiftUI \{#swiftui\} ### Modificador `.paywall()` → `.flow()` \{#paywall-modifier--flow\} Renombra el modificador, actualiza el nombre del parámetro de configuración y añade el closure `didFinishPurchase` [ahora obligatorio](#didfinishpurchase-is-now-required): ```diff showLineNumbers @State var flowPresented = false // rename freely — the variable name is your choice var body: some View { Text("Hello, AdaptyUI!") - .paywall( + .flow( isPresented: $flowPresented, - paywallConfiguration: paywallConfiguration, + flowConfiguration: flowConfiguration, + didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, - didFailRendering: { error in flowPresented = false } + didReceiveError: { error in flowPresented = false } ) } ``` El callback renombrado se activa para los mismos errores de renderizado que `didFailRendering`, más los nuevos errores en tiempo de ejecución del script del flow (excepciones de JavaScript con código `AdaptyUIError` `4105` — `.jsException`). Los cuerpos de los handlers existentes no necesitan cambios en el código: solo hay que renombrar el parámetro. ### AdaptyPaywallView → AdaptyFlowView Renombra la vista, actualiza el parámetro de configuración, añade el closure `didFinishPurchase` [ahora obligatorio](#didfinishpurchase-is-now-required), y actualiza cualquier closure `didSelectProduct` — ahora recibe `AdaptyPaywallProduct` en lugar del eliminado `AdaptyPaywallProductWithoutDeterminingOffer`: ```diff showLineNumbers - AdaptyPaywallView( - paywallConfiguration: paywallConfiguration, - didSelectProduct: { product: AdaptyPaywallProductWithoutDeterminingOffer in /* handle */ }, + AdaptyFlowView( + flowConfiguration: flowConfiguration, + didSelectProduct: { product: AdaptyPaywallProduct in /* handle */ }, + didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, - didFailRendering: { error in /* handle the error */ } + didReceiveError: { error in /* handle the error */ } ) ``` ## Recursos personalizados de AdaptyUI \{#adaptyui-custom-assets\} ### AdaptyUICustomVideoAsset Hay dos cambios que afectan a todos los puntos de llamada existentes: - `.player` ahora acepta `AVPlayer` en lugar de `AVQueuePlayer`. - Todos los casos han ganado un parámetro adicional al final: `resolution: CGSize?`. Pasa `nil` para mantener el comportamiento actual, o pasa el tamaño en píxeles real para que el reproductor pueda reservar espacio de layout (relación de aspecto = `width / height`) antes de que cargue el vídeo. ```diff showLineNumbers - case file(url: URL, preview: AdaptyUICustomImageAsset?) - case remote(url: URL, preview: AdaptyUICustomImageAsset?) - case player(item: AVPlayerItem, player: AVQueuePlayer, preview: AdaptyUICustomImageAsset?) + case file(url: URL, preview: AdaptyUICustomImageAsset?, resolution: CGSize?) + case remote(url: URL, preview: AdaptyUICustomImageAsset?, resolution: CGSize?) + case player(item: AVPlayerItem, player: AVPlayer, preview: AdaptyUICustomImageAsset?, resolution: CGSize?) ``` ## Identificadores de atribución e integración \{#attribution-and-integration-identifiers\} ### updateAttribution(_:source:) El parámetro `source` cambia de `String` al nuevo tipo `AdaptyAttributionSource`, y el anteriormente anidado `AdaptyProfile.AttributionSource` se renombra al nivel superior `AdaptyAttributionSource`. Usa una de las fuentes predefinidas, o pasa un literal de cadena para cualquier otra fuente: `AdaptyAttributionSource` conforma con `ExpressibleByStringLiteral`, por lo que las llamadas con literales de cadena existentes siguen compilando. ```diff showLineNumbers - try await Adapty.updateAttribution(attribution, source: "adjust") + try await Adapty.updateAttribution(attribution, source: .adjust) ``` Fuentes predefinidas: `.appleAds`, `.adjust`, `.appsflyer`, `.branch`, `.tenjin`. Si guardas la fuente en una variable `String`, envuélvela así: `AdaptyAttributionSource(rawValue: yourSource)`. ### setIntegrationIdentifier(_:) `setIntegrationIdentifier(key:value:)` es reemplazado por un método variádico que acepta uno o más valores de tipo `AdaptyIntegrationIdentifier`. Usa los métodos de fábrica predefinidos en lugar de claves de cadena sin formato: ```diff showLineNumbers - try await Adapty.setIntegrationIdentifier(key: "appsflyer_id", value: uid) + try await Adapty.setIntegrationIdentifier(.appsflyerId(uid)) ``` Puedes establecer varios identificadores en una sola llamada: ```swift showLineNumbers try await Adapty.setIntegrationIdentifier( .appsflyerId(uid), .adjustDeviceId(adid) ) ``` Reemplaza cada cadena de clave antigua por su método de fábrica: | clave v3 | factory v4 | |---|---| | `"adjust_device_id"` | `.adjustDeviceId(_:)` | | `"airbridge_device_id"` | `.airbridgeDeviceId(_:)` | | `"amplitude_user_id"` | `.amplitudeUserId(_:)` | | `"amplitude_device_id"` | `.amplitudeDeviceId(_:)` | | `"appmetrica_device_id"` | `.appmetricaDeviceId(_:)` | | `"appmetrica_profile_id"` | `.appmetricaProfileId(_:)` | | `"appsflyer_id"` | `.appsflyerId(_:)` | | `"branch_id"` | `.branchId(_:)` | | `"facebook_anonymous_id"` | `.facebookAnonymousId(_:)` | | `"firebase_app_instance_id"` | `.firebaseAppInstanceId(_:)` | | `"mixpanel_user_id"` | `.mixpanelUserId(_:)` | | `"one_signal_subscription_id"` | `.oneSignalSubscriptionId(_:)` | | `"one_signal_player_id"` | `.oneSignalPlayerId(_:)` | | `"posthog_distinct_user_id"` | `.posthogDistinctUserId(_:)` | | `"pushwoosh_hwid"` | `.pushwooshHWID(_:)` | | `"tenjin_analytics_installation_id"` | `.tenjinAnalyticsInstallationId(_:)` | --- # File: migration-to-ios-315 --- --- title: "Migrar el SDK de Adapty para iOS a v3.15" description: "Migra al SDK de Adapty para iOS v3.15 para mejorar el rendimiento y acceder a nuevas funciones de monetización." --- Si usas [Paywall Builder](adapty-paywall-builder) en [modo Observer](observer-vs-full-mode), a partir del SDK de iOS 3.15 necesitas implementar un nuevo método `observerModeDidInitiateRestorePurchases(onStartRestore:onFinishRestore:)`. Este método te da más control sobre la lógica de restauración, permitiéndote gestionar la restauración de compras en tu propio flow. Para más detalles sobre la implementación, consulta [Mostrar paywalls de Paywall Builder en modo Observer](ios-present-paywall-builder-paywalls-in-observer-mode). ```diff showLineNumbers func observerMode(didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase } + func observerModeDidInitiateRestorePurchases(onStartRestore: @escaping () -> Void, + onFinishRestore: @escaping () -> Void) { + // use the onStartRestore and onFinishRestore callbacks to notify AdaptyUI about the process of the restore + } ``` --- # File: migration-to-ios-sdk-34 --- --- title: "Migrar el SDK de Adapty para iOS a la v. 3.4" description: "Migra al SDK de Adapty para iOS v3.4 para obtener mejor rendimiento y nuevas funciones de monetización." --- El SDK de Adapty 3.4.0 es una versión principal que introduce mejoras que requieren pasos de migración por tu parte. ## Actualizar la activación del SDK \{#update-adapty-sdk-activation\} <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```diff showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") - Adapty.activate(with: configurationBuilder) { error in + Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } ``` **Actualizar los archivos de paywall de respaldo** Actualiza los archivos de paywall de respaldo para garantizar la compatibilidad con la nueva versión del SDK: 1. [Descarga los archivos de paywall de respaldo actualizados](fallback-paywalls) desde el Adapty Dashboard. 2. [Reemplaza los paywalls de respaldo existentes en tu app](ios-use-fallback-paywalls) con los nuevos archivos. </TabItem> <TabItem value="swiftui" label="SwiftUI" default> ```diff showLineNumbers @main struct SampleApp: App { init() { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") Task { - try await Adapty.activate(with: configurationBuilder) + try await Adapty.activate(with: configurationBuilder.build()) } } var body: some Scene { WindowGroup { ContentView() } } } ``` **Actualizar los archivos de paywall de respaldo** Actualiza tus archivos de paywall de respaldo para garantizar la compatibilidad con la nueva versión del SDK: 1. [Descarga los archivos actualizados del paywall de respaldo](fallback-paywalls) desde el Adapty Dashboard. 2. [Reemplaza los paywalls de respaldo existentes en tu app](ios-use-fallback-paywalls) con los nuevos archivos. </TabItem> </Tabs> --- # File: migration-to-ios330 --- --- title: "Migrar Adapty iOS SDK a v3.3" description: "Migra al Adapty iOS SDK v3.3 para obtener mejor rendimiento y nuevas funciones de monetización." --- Adapty SDK 3.3.0 es una versión mayor que incluye algunas mejoras que, sin embargo, pueden requerir algunos pasos de migración por tu parte. 1. Renombra `Adapty.Configuration` a `AdaptyConfiguration`. 2. Renombra el método `getViewConfiguration` a `getPaywallConfiguration`. 3. Elimina los parámetros `didCancelPurchase` y `paywall` de SwiftUI, y renombra el parámetro `viewConfiguration` a `paywallConfiguration`. 4. Actualiza el procesamiento de compras in-app promocionales de la App Store eliminando el parámetro `defermentCompletion` del método `AdaptyDelegate`. 5. Elimina el método `getProductsIntroductoryOfferEligibility`. 6. Actualiza las configuraciones de integración para Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase y Google Analytics, Mixpanel, OneSignal, Pushwoosh. 7. Actualiza la implementación del modo Observer. <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/9Xs8d0lt_RY?si=xvWhUO2tlG1tKP5f" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen> </iframe> </div> ## Renombrar Adapty.Configuration a AdaptyConfiguration \{#rename-adaptyconfiguration-to-adaptyconfiguration\} Actualiza el código de activación del SDK de Adapty para iOS de la siguiente manera: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```diff showLineNumbers // In your AppDelegate class: let configurationBuilder = - Adapty.Configuration + AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) .with(customerUserId: "YOUR_USER_ID") .with(idfaCollectionDisabled: false) .with(ipAddressCollectionDisabled: false) Adapty.activate(with: configurationBuilder) { error in // handle the error } ``` </TabItem> <TabItem value="swiftui" label="SwiftUI" default> ```diff showLineNumbers @main struct SampleApp: App { init() let configurationBuilder = - Adapty.Configuration + AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) // optional .with(customerUserId: "YOUR_USER_ID") // optional .with(idfaCollectionDisabled: false) // optional .with(ipAddressCollectionDisabled: false) // optional Task { try await Adapty.activate(with: configurationBuilder) } } var body: some Scene { WindowGroup { ContentView() } } } ``` </TabItem> </Tabs> ## Renombrar el método getViewConfiguration a getPaywallConfiguration \{#rename-getviewconfiguration-method-to-getpaywallconfiguration\} Actualiza el nombre del método para obtener la `viewConfiguration` del paywall: ```diff showLineNumbers guard paywall.hasViewConfiguration else { // use your custom logic return } do { - let paywallConfiguration = try await AdaptyUI.getViewConfiguration( + let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall ) // use loaded configuration } catch { // handle the error } ``` Para más detalles sobre el método, consulta [Obtener la configuración de vista de un paywall diseñado con Paywall Builder](get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). ## Cambios de parámetros en SwiftUI \{#change-parameters-in-swiftui\} Se han realizado las siguientes actualizaciones en SwiftUI: 1. Se ha eliminado el parámetro `didCancelPurchase`. Usa `didFinishPurchase` en su lugar. 2. El método `.paywall()` ya no acepta un objeto paywall. 3. El parámetro `paywallConfiguration` ha reemplazado al parámetro `viewConfiguration`. Actualiza tu código de la siguiente manera: ```diff showLineNumbers @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, - paywall: <paywall object>, - viewConfiguration: <LocalizedViewConfiguration>, + paywallConfiguration: <AdaptyUI.PaywallConfiguration>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, - didFinishPurchase: { product, profile in paywallPresented = false }, + didFinishPurchase: { product, purchaseResult in /* handle the result*/ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false } - didCancelPurchase: { product in /* handle the result*/} ) } ``` ## Actualizar el manejo de compras in-app promocionales desde App Store \{#update-handling-of-promotional-in-app-purchases-from-app-store\} Actualiza cómo gestionas las compras in-app promocionales desde App Store eliminando el parámetro `defermentCompletion` del método `AdaptyDelegate`, tal como se muestra en el ejemplo a continuación: ```swift showLineNumbers title="Swift" final class YourAdaptyDelegateImplementation: AdaptyDelegate { nonisolated func shouldAddStorePayment(for product: AdaptyDeferredProduct) -> Bool { // 1a. // Return `true` to continue the transaction in your app. // 1b. // Store the product object and return `false` to defer or cancel the transaction. false } // 2. Continue the deferred purchase later on by passing the product to `makePurchase` func continueDeferredPurchase() async { let storedProduct: AdaptyDeferredProduct = // get the product object from the 1b. do { try await Adapty.makePurchase(product: storedProduct) } catch { // handle the error } } } ``` ## Eliminar el método getProductsIntroductoryOfferEligibility \{#remove-getproductsintroductoryoffereligibility-method\} Antes del SDK de Adapty iOS 3.3.0, el objeto de producto siempre incluía las ofertas, independientemente de si el usuario era elegible. Tenías que verificar manualmente la elegibilidad antes de usar la oferta. Ahora, el objeto de producto solo incluye una oferta si el usuario es elegible. Esto significa que ya no necesitas verificar la elegibilidad: si hay una oferta presente, el usuario es elegible. Si aún quieres ver las ofertas para usuarios que no son elegibles, consulta `sk1Product` y `sk2Product`. ## Actualizar la configuración del SDK de integraciones de terceros \{#update-third-party-integration-sdk-configuration\} A partir del SDK de Adapty iOS 3.3.0, hemos actualizado la API pública del método `updateAttribution`. Anteriormente aceptaba un diccionario `[AnyHashable: Any]`, lo que te permitía pasar objetos de atribución directamente desde varios servicios. Ahora requiere un `[String: any Sendable]`, por lo que deberás convertir los objetos de atribución antes de pasarlos. Para garantizar que las integraciones funcionen correctamente con el SDK de Adapty iOS 3.3.0 y versiones posteriores, actualiza las configuraciones de tu SDK para las siguientes integraciones tal como se describe en las secciones a continuación. ### Adjust Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Adjust](adjust#connect-your-app-to-adjust). <Tabs groupId="current-os" queryString> <TabItem value="v5" label="Adjust 5.x+" default> ```diff showLineNumbers class AdjustModuleImplementation { - func updateAdjustAttribution() { - Adjust.attribution { attribution in - guard let attributionDictionary = attribution?.dictionary()?.toSendableDict() else { return } - - Adjust.adid { adid in - guard let adid else { return } - - Adapty.updateAttribution(attributionDictionary, source: .adjust, networkUserId: adid) { error in - // handle the error - } - } - } - } + func updateAdjustAdid() { + Adjust.adid { adid in + guard let adid else { return } + + Adapty.setIntegrationIdentifier(key: "adjust_device_id", value: adid) + } + } + + func updateAdjustAttribution() { + Adjust.attribution { attribution in + guard let attribution = attribution?.dictionary() else { + return + } + + Adapty.updateAttribution(attribution, source: "adjust") + } + } } ``` </TabItem> <TabItem value="v4" label="Adjust 4.x" default> ```diff showLineNumbers class YourAdjustDelegateImplementation { // Find your implementation of AdjustDelegate // and update adjustAttributionChanged method: func adjustAttributionChanged(_ attribution: ADJAttribution?) { - if let attribution = attribution?.dictionary()?.toSendableDict() { - Adapty.updateAttribution(attribution, source: .adjust) + if let attribution = attribution?.dictionary() { + Adapty.updateAttribution(attribution, source: "adjust") } } } ``` </TabItem> </Tabs> ### AirBridge \{#airbridge\} Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo completo, consulta la [configuración del SDK para la integración con AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers import AirBridge - let builder = AdaptyProfileParameters.Builder() - .with(airbridgeDeviceId: AirBridge.deviceUUID()) - - Adapty.updateProfile(params: builder.build()) + do { + try await Adapty.setIntegrationIdentifier( + key: "airbridge_device_id", + value: AirBridge.deviceUUID() + ) + } catch { + // handle the error + } ``` ### Amplitude Actualiza el código de tu aplicación móvil como se indica a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers import Amplitude - let builder = AdaptyProfileParameters.Builder() - .with(amplitudeUserId: Amplitude.instance().userId) - .with(amplitudeDeviceId: Amplitude.instance().deviceId) - - Adapty.updateProfile(params: builder.build()) + do { + try await Adapty.setIntegrationIdentifier( + key: "amplitude_user_id", + value: Amplitude.instance().userId + ) + try await Adapty.setIntegrationIdentifier( + key: "amplitude_device_id", + value: Amplitude.instance().deviceId + ) + } catch { + // handle the error + } ``` ### AppMetrica Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers import AppMetricaCore - if let deviceID = AppMetrica.deviceID { - let builder = AdaptyProfileParameters.Builder() - .with(appmetricaDeviceId: deviceID) - .with(appmetricaProfileId: "YOUR_ADAPTY_CUSTOMER_USER_ID") - - Adapty.updateProfile(params: builder.build()) - } + if let deviceID = AppMetrica.deviceID { + do { + try await Adapty.setIntegrationIdentifier( + key: "appmetrica_device_id", + value: deviceID + ) + try await Adapty.setIntegrationIdentifier( + key: "appmetrica_profile_id", + value: "YOUR_ADAPTY_CUSTOMER_USER_ID" + ) + } catch { + // handle the error + } + } ``` ### AppsFlyer Actualiza el código de tu aplicación móvil como se indica a continuación. Para ver el ejemplo completo, consulta la [configuración del SDK para la integración con AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers class YourAppsFlyerLibDelegateImplementation { // Find your implementation of AppsFlyerLibDelegate // and update onConversionDataSuccess method: func onConversionDataSuccess(_ conversionInfo: [AnyHashable : Any]) { let uid = AppsFlyerLib.shared().getAppsFlyerUID() - Adapty.updateAttribution( - conversionInfo.toSendableDict(), - source: .appsflyer, - networkUserId: uid - ) + Adapty.setIntegrationIdentifier(key: "appsflyer_id", value: uid) + Adapty.updateAttribution(conversionInfo, source: "appsflyer") } } ``` ### Branch Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers class YourBranchImplementation { func initializeBranch() { // Pass the attribution you receive from the initializing method of Branch iOS SDK to Adapty. Branch.getInstance().initSession(launchOptions: launchOptions) { (data, error) in - if let data = data?.toSendableDict() { - Adapty.updateAttribution(data, source: .branch) - } + if let data { + Adapty.updateAttribution(data, source: "branch") + } } } } ``` ### Facebook Ads Actualiza el código de tu aplicación como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Facebook Ads](facebook-ads#connect-your-app-to-facebook-ads). ```diff showLineNumbers import FacebookCore - let builder = AdaptyProfileParameters.Builder() - .with(facebookAnonymousId: AppEvents.shared.anonymousID) - - do { - try Adapty.updateProfile(params: builder.build()) - } catch { - // handle the error - } + do { + try await Adapty.setIntegrationIdentifier( + key: "facebook_anonymous_id", + value: AppEvents.shared.anonymousID + ) + } catch { + // handle the error + } ``` ### Firebase y Google Analytics \{#firebase-and-google-analytics\} Actualiza el código de tu aplicación móvil como se indica a continuación. Para ver el ejemplo completo, consulta la [configuración del SDK para la integración con Firebase y Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers import FirebaseCore import FirebaseAnalytics FirebaseApp.configure() - if let appInstanceId = Analytics.appInstanceID() { - let builder = AdaptyProfileParameters.Builder() - .with(firebaseAppInstanceId: appInstanceId) - Adapty.updateProfile(params: builder.build()) { error in - // handle error - } - } + if let appInstanceId = Analytics.appInstanceID() { + do { + try await Adapty.setIntegrationIdentifier( + key: "firebase_app_instance_id", + value: appInstanceId + ) + } catch { + // handle the error + } + } ``` ### Mixpanel Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers import Mixpanel - let builder = AdaptyProfileParameters.Builder() - .with(mixpanelUserId: Mixpanel.mainInstance().distinctId) - - do { - try await Adapty.updateProfile(params: builder.build()) - } catch { - // handle the error - } + do { + try await Adapty.setIntegrationIdentifier( + key: "mixpanel_user_id", + value: Mixpanel.mainInstance().distinctId + ) + } catch { + // handle the error + } ``` ### OneSignal Actualiza el código de tu aplicación móvil tal como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers // PlayerID (pre-v5 OneSignal SDK) // in your OSSubscriptionObserver implementation func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges) { if let playerId = stateChanges.to.userId { - let params = AdaptyProfileParameters.Builder() - .with(oneSignalPlayerId: playerId) - .build() - - Adapty.updateProfile(params:params) { error in - // check error - } + Task { + try await Adapty.setIntegrationIdentifier( + key: "one_signal_player_id", + value: playerId + ) + } } } // SubscriptionID (v5+ OneSignal SDK) OneSignal.Notifications.requestPermission({ accepted in - let id = OneSignal.User.pushSubscription.id - - let builder = AdaptyProfileParameters.Builder() - .with(oneSignalSubscriptionId: id) - - Adapty.updateProfile(params: builder.build()) + Task { + try await Adapty.setIntegrationIdentifier( + key: "one_signal_subscription_id", + value: OneSignal.User.pushSubscription.id + ) + } }, fallbackToSettings: true) ``` ### Pushwoosh Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers - let params = AdaptyProfileParameters.Builder() - .with(pushwooshHWID: Pushwoosh.sharedInstance().getHWID()) - .build() - - Adapty.updateProfile(params: params) { error in - // handle the error - } + do { + try await Adapty.setIntegrationIdentifier( + key: "pushwoosh_hwid", + value: Pushwoosh.sharedInstance().getHWID() + ) + } catch { + // handle the error + } ``` ## Actualizar la implementación del modo Observer \{#update-observer-mode-implementation\} Actualiza cómo vinculas los paywalls a las transacciones. Antes, usabas el método `setVariationId` para asignar el `variationId`. Ahora puedes incluir el `variationId` directamente al registrar la transacción usando el nuevo método `reportTransaction`. Consulta el ejemplo de código final en [Asociar paywalls con transacciones de compra en el modo Observer](report-transactions-observer-mode). :::warning Recuerda registrar la transacción con el método `reportTransaction`. Si omites este paso, Adapty no reconocerá la transacción, no otorgará niveles de acceso, no la incluirá en los análisis ni la enviará a las integraciones. ¡Este paso es imprescindible! ::: ```diff showLineNumbers - let variationId = paywall.variationId - - // There are two overloads: for StoreKit 1 and StoreKit 2 - Adapty.setVariationId(variationId, forPurchasedTransaction: transaction) { error in - if error == nil { - // successful binding - } - } + do { + // every time when calling transaction.finish() + try await Adapty.reportTransaction(transaction, withVariationId: <YOUR_PAYWALL_VARIATION_ID>) + } catch { + // handle the error + } ``` --- # File: migration-to-ios-sdk-v3 --- --- title: "Migrar el SDK de iOS de Adapty a v3.0" description: "Migra al SDK de iOS de Adapty v3.0 para mejor rendimiento y nuevas funciones de monetización." --- Adapty SDK v3.0 incluye soporte para el nuevo y emocionante [Adapty Paywall Builder](adapty-paywall-builder), la nueva versión de la herramienta no-code fácil de usar para crear paywalls. Con su máxima flexibilidad y ricas capacidades de diseño, tus paywalls serán más efectivos y rentables. :::info Ten en cuenta que la librería AdaptyUI está obsoleta y ahora forma parte del AdaptySDK. ::: ## Reinstalar el SDK de Adapty v3.x mediante Swift Package Manager \{#reinstall-adapty-sdk-v3x-via-swift-package-manager\} 1. Elimina la dependencia del paquete AdaptyUI SDK de tu proyecto, ya no la necesitarás. 2. Aunque ya la tengas, deberás volver a añadir la dependencia del SDK de Adapty. Para ello, en Xcode, abre **File** -> **Add Package Dependency...**. Ten en cuenta que la forma de añadir dependencias de paquetes puede variar según la versión de Xcode. Consulta la documentación de Xcode si es necesario. 3. Introduce la URL del repositorio `https://github.com/adaptyteam/AdaptySDK-iOS.git` 4. Elige la versión y haz clic en el botón **Add package**. 5. Elige los módulos que necesitas: 1. **Adapty** es el módulo obligatorio 2. **AdaptyUI** es un módulo opcional que necesitas si planeas usar el [Paywall Builder de Adapty](adapty-paywall-builder). 6. Xcode añadirá la dependencia del paquete a tu proyecto y podrás importarla. Para ello, en la ventana **Choose Package Products**, haz clic en el botón **Add package** una vez más. El paquete aparecerá en la lista **Packages**. ## Reinstalar el SDK de Adapty v3.x mediante CocoaPods \{#reinstall-adapty-sdk-v3x-via-cocoapods\} 1. Añade Adapty a tu `Podfile`. Elige los módulos que necesites: 1. **Adapty** es el módulo obligatorio. 2. **AdaptyUI** es un módulo opcional que necesitas si planeas usar el [Paywall Builder de Adapty](adapty-paywall-builder). 2. ```shell showLineNumbers title="Podfile" pod 'Adapty', '~> 3.2.0' pod 'AdaptyUI', '~> 3.2.0' # optional module needed only for Paywall Builder ``` 3. Ejecuta: ```sh showLineNumbers title="Shell" pod install ``` Esto crea un archivo `.xcworkspace` para tu app. Usa este archivo para todo el desarrollo futuro de tu aplicación. Activa los módulos del SDK de Adapty y AdaptyUI. Antes de v3.0, no se activaba AdaptyUI; recuerda **añadir la activación de AdaptyUI**. Los parámetros no cambian, así que mantenlos tal cual. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .Builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) .with(customerUserId: "YOUR_USER_ID") .with(idfaCollectionDisabled: false) .with(ipAddressCollectionDisabled: false) Adapty.activate(with: configurationBuilder) { error in // handle the error } // Only if you are going to use AdaptyUI AdaptyUI.activate() ``` </TabItem> <TabItem value="swiftui" label="SwiftUI" default> ```swift title="" showLineNumbers @main struct SampleApp: App { init() let configurationBuilder = AdaptyConfiguration .Builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) // optional .with(customerUserId: "YOUR_USER_ID") // optional .with(idfaCollectionDisabled: false) // optional .with(ipAddressCollectionDisabled: false) // optional Adapty.activate(with: configurationBuilder) { error in // handle the error } // Only if you are going to use AdaptyUI AdaptyUI.activate() } var body: some Scene { WindowGroup { ContentView() } } } ``` </TabItem> </Tabs> --- # End of Documentation _Generated on: 2026-07-24T13:01:55.716Z_ _Successfully processed: 44/44 files_ # KMP - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-07-24T13:01:55.719Z Total files: 48 --- # File: kmp-sdk-overview --- --- title: "Kotlin Multiplatform SDK overview" description: "Descubre el SDK Kotlin Multiplatform de Adapty y sus principales características." --- [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-KMP.svg?style=flat&logo=kotlin)](https://github.com/adaptyteam/AdaptySDK-KMP/releases) ¡Bienvenido! Estamos aquí para que las compras in-app sean pan comido 🚀 Hemos creado el SDK Kotlin Multiplatform de Adapty para quitarte el dolor de cabeza de las compras in-app y que puedas centrarte en lo que mejor se te da: crear apps increíbles. Esto es lo que gestionamos por ti: - Gestiona compras, validación de recibos y administración de suscripciones de forma nativa - Crea y prueba paywalls sin necesidad de actualizar la app - Obtén análisis detallados de compras sin ninguna configuración: cohortes, LTV, churn y análisis de embudo incluidos - Mantén el estado de la suscripción del usuario siempre actualizado entre sesiones y dispositivos - Integra tu app con servicios de atribución de marketing y analíticas con una sola línea de código :::note Antes de sumergirte en el código, deberás integrar Adapty con Google Play Console y configurar los productos en el dashboard. Consulta nuestra [guía de inicio rápido](quickstart) para tenerlo todo configurado primero. ::: ## Primeros pasos \{#get-started\} For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. Esto es lo que cubriremos en la guía de integración: 1. [Instalar y configurar el SDK](sdk-installation-kotlin-multiplatform): Añade el SDK como dependencia a tu proyecto y actívalo en el código. 2. [Habilitar compras mediante flows](kmp-quickstart-paywalls): Configura el flujo de compra para que los usuarios puedan adquirir productos. Si prefieres crear tu propia UI, consulta [Implementar paywalls manualmente](kmp-quickstart-manual). 3. [Comprobar el estado de la suscripción](kmp-check-subscription-status): Verifica automáticamente el estado de la suscripción del usuario y controla su acceso al contenido de pago. 4. [Identificar usuarios (opcional)](kmp-quickstart-identify): Asocia a los usuarios con sus perfiles de Adapty para garantizar que sus datos se almacenen de forma consistente entre dispositivos. ### Vélo en acción \{#see-it-in-action\} ¿Quieres ver cómo encaja todo? Te tenemos cubierto: - **App de ejemplo**: Consulta nuestro [ejemplo completo](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example) que muestra la configuración completa - **Tutorial en vídeo**: Sigue la implementación paso a paso en el vídeo de abajo <iframe width="560" height="315" src="https://www.youtube.com/embed/JfwJvwnloNw?si=HskPxRk4WGkF_u9s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> ## Conceptos principales \{#main-concepts\} Antes de meterte en el código, familiarízate con los conceptos clave que hacen funcionar Adapty. Lo mejor del enfoque de Adapty es que solo los placements están codificados en tu app. Todo lo demás —productos, diseños de paywalls, precios y ofertas— se puede gestionar de forma flexible desde el Adapty Dashboard sin actualizar la app: 1. [**Producto**](product) - Cualquier cosa disponible para comprar en tu app: suscripción, producto consumible o acceso de por vida. 2. **Flow o paywall** - Productos agrupados con configuración, vinculados a un placement. Dos variantes: - **[Flow](adapty-flow-builder)** - Interfaz visual sin código creada en Flow Builder. Adapty renderiza la UI y gestiona la compra por ti. - **[Paywall](paywalls)** - Sin configuración visual; tú construyes la UI en tu propio código y llamas a `makePurchase` directamente. Consulta [Implementar paywalls manualmente](kmp-quickstart-manual). En el código del SDK, ambos se obtienen mediante el mismo método `getFlow`. 3. [**Placement**](placements) - Un punto estratégico en el recorrido del usuario donde quieres mostrar un flow o paywall. Los placements representan el "dónde" y el "cuándo" de tu estrategia de monetización. Los placements más habituales son: - `main` - Tu placement principal de paywall - `onboarding` - Mostrado durante el flow de onboarding del usuario - `settings` - Accesible desde los ajustes de tu app Empieza con los básicos como `main` u `onboarding` en tu primera integración y luego [piensa en qué otros puntos de tu app los usuarios podrían estar listos para comprar](choose-meaningful-placements). 4. [**Perfil**](profiles-crm) - Cuando los usuarios compran un producto, a su perfil se le asigna un **nivel de acceso** que usas para definir el acceso a las funciones de pago. --- # File: sdk-installation-kotlin-multiplatform --- --- title: "Instalar y configurar el SDK de Adapty para Kotlin Multiplatform" description: "Instala y configura el SDK de Adapty para aplicaciones Kotlin Multiplatform." --- El SDK de Adapty incluye dos módulos clave para una integración fluida en tu aplicación móvil: - **Core Adapty**: Este SDK esencial es necesario para que Adapty funcione correctamente en tu app. - **AdaptyUI** (`io.adapty:adapty-kmp-ui`): Este módulo es necesario si usas el [Adapty Paywall Builder](adapty-paywall-builder) con la capa de renderizado Compose Multiplatform (`view.present()`). Si tu proyecto no usa Compose Multiplatform, puedes utilizar [`createNativePaywallView`](kmp-present-paywalls#without-compose-multiplatform) y [`createNativeOnboardingView`](kmp-present-onboardings#without-compose-multiplatform) del módulo core en su lugar. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una aplicación móvil? Echa un vistazo a nuestra [aplicación de ejemplo](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example), que muestra la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: Para una guía de implementación completa, también puedes ver el vídeo: <iframe width="560" height="315" src="https://www.youtube.com/embed/JfwJvwnloNw?si=HskPxRk4WGkF_u9s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> ## Requisitos \{#requirements\} El SDK de Adapty para Kotlin Multiplatform es compatible con Xcode 16.2 y versiones posteriores. :::info Adapty es compatible con Google Play Billing Library hasta la versión 8.x. Por defecto, Adapty funciona con Google Play Billing Library v.7.0.0, pero si quieres forzar una versión posterior, puedes [añadir la dependencia](https://developer.android.com/google/play/billing/integrate#dependency) manualmente. ::: :::info Instalar el SDK es el paso 5 de la configuración de Adapty. Para que las compras funcionen en tu app, también necesitas conectar tu app a los stores, y luego crear productos, un paywall y un placement en el Adapty Dashboard. La [guía de inicio rápido](quickstart) explica todos los pasos necesarios. ::: ## Instala el SDK de Adapty mediante Gradle \{#install-adapty-sdk-via-gradle\} La instalación del SDK de Adapty con Gradle es necesaria tanto para apps de Android como de iOS. Elige tu método de configuración de dependencias: - Gradle estándar: Añade las dependencias a tu `build.gradle` **a nivel de módulo** - Si tu proyecto usa archivos `.gradle.kts`, añade las dependencias a tu `build.gradle.kts` **a nivel de módulo** - Si usas catálogos de versiones, añade las dependencias a tu archivo `libs.versions.toml` y luego referencíalas en `build.gradle.kts` :::important El SDK de Adapty para Kotlin Multiplatform 4.0 es una versión preliminar. Gradle no selecciona versiones preliminares mediante rangos de versión dinámicos (como `+` o `latest.release`), por lo que debes fijar la versión exacta — por ejemplo `io.adapty:adapty-kmp:4.0.0-beta.1`, o `adapty-kmp = "4.0.0-beta.1"` en `libs.versions.toml`. Consulta [Migrar el SDK de Adapty para Kotlin Multiplatform a v4](migration-to-kmp-sdk-v4). ::: <Tabs> <TabItem value="module-level build.gradle" label="module-level build.gradle" default> ```kotlin showLineNumbers kotlin { sourceSets { commonMain { dependencies { implementation libs.adapty.kmp } } } } ``` </TabItem> <TabItem value="module-level build.gradle.kts" label="module-level build.gradle.kts" default> ```kotlin showLineNumbers kotlin { sourceSets { val commonMain by getting { dependencies { implementation(libs.adapty.kmp) } } } } ``` </TabItem> <TabItem value="version-catalog" label="Biblioteca de versiones" default> ```toml showLineNumbers // libs.versions.toml [versions] .. adapty-kmp = "<the latest SDK version>" [libraries] .. adapty-kmp = { module = "io.adapty:adapty-kmp", version.ref = "adapty-kmp" } // build.gradle.kts kotlin { sourceSets { val commonMain by getting { dependencies { implementation(libs.adapty.kmp) } } } } ``` </TabItem> </Tabs> :::note Si recibes un error relacionado con Maven, asegúrate de tener `mavenCentral()` en tus scripts de Gradle. <details> <summary>Instrucciones para añadirlo</summary> Si tu proyecto no tiene `dependencyResolutionManagement` en tu `settings.gradle`, añade lo siguiente a tu `build.gradle` de nivel superior al final de repositories: ```groovy showLineNumbers title="top-level build.gradle" allprojects { repositories { ... mavenCentral() } } ``` De lo contrario, añade lo siguiente a tu `settings.gradle` en `repositories` de la sección `dependencyResolutionManagement`: ```groovy showLineNumbers title="settings.gradle" dependencyResolutionManagement { ... repositories { ... google() mavenCentral() } } ``` </details> ::: ## Activar el SDK de Adapty \{#activate-adapty-sdk\} ### Configuración básica \{#basic-setup\} Añade la inicialización lo antes posible, normalmente en tu código Kotlin compartido para ambas plataformas. :::note El SDK de Adapty solo necesita activarse una vez en tu app. ::: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } ``` :::important Espera a que `activate` se complete antes de llamar a cualquier otro método del SDK. Consulta [Orden de llamadas en el SDK de Kotlin Multiplatform](kmp-sdk-call-order) para ver la secuencia completa. ::: Para obtener tu **Public SDK Key**: 1. Ve al Adapty Dashboard y navega a [App settings → General](https://app.adapty.io/settings/general). 2. En la sección **Api keys**, copia la **Public SDK Key** (NO la Secret Key). 3. Reemplaza `"YOUR_PUBLIC_SDK_KEY"` en el código. :::info - Asegúrate de usar la clave SDK pública para la inicialización de Adapty; la clave secreta debe usarse únicamente para la [API del lado del servidor](getting-started-with-server-side-api). - Las claves SDK son únicas para cada app, así que si tienes varias apps asegúrate de elegir la correcta. ::: Ahora configura los paywalls en tu app: - Si usas [Adapty Paywall Builder](adapty-paywall-builder), primero [activa el módulo AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) a continuación y luego sigue la [guía de inicio rápido del Paywall Builder](kmp-quickstart-paywalls). - Si construyes tu propia interfaz de paywall, consulta la [guía de inicio rápido para paywalls personalizados](kmp-quickstart-manual). ## Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Si planeas activar el módulo **AdaptyUI** para usar el [Paywall Builder de Adapty](kmp-present-paywalls), asegúrate de establecer `.withActivateUI(true)` en tu configuración. :::info importante En tu código, debes activar el módulo principal de Adapty antes de activar AdaptyUI. ::: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withActivateUI(true) // true for activating the AdaptyUI module .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } ``` ## Configurar Proguard (Android) \{#configure-proguard-android\} Antes de lanzar tu app en producción, es posible que necesites añadir `-keep class com.adapty.** { *; }` a tu configuración de Proguard. ## Configuración opcional \{#optional-setup\} ### Registro #### Configurar el sistema de registro \{#set-up-the-logging-system\} Adapty registra errores y otra información importante para ayudarte a entender qué está pasando. Hay los siguientes niveles disponibles: | Level | Description | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------ | | `AdaptyLogLevel.ERROR` | Solo se registrarán errores. | | `AdaptyLogLevel.WARN` | Se registrarán errores y mensajes del SDK que no causan errores críticos, pero que merecen atención. | | `AdaptyLogLevel.INFO` | Se registrarán errores, advertencias y varios mensajes informativos. Valor predeterminado. | | `AdaptyLogLevel.VERBOSE` | Se registrará cualquier información adicional que pueda ser útil durante la depuración, como llamadas a funciones, consultas a la API, etc. | | `AdaptyLogLevel.DEBUG` | Se registrará la información más detallada, incluidos los datos de depuración internos. | Puedes configurar el nivel de log en tu app antes de configurar Adapty: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withLogLevel(AdaptyLogLevel.VERBOSE) // recommended for development .build() ``` ### Políticas de datos \{#data-policies\} #### Deshabilitar la recopilación y el uso compartido de direcciones IP \{#disable-ip-address-collection-and-sharing\} Al activar el módulo de Adapty, establece `ipAddressCollectionDisabled` en `true` para deshabilitar la recopilación y el uso compartido de la dirección IP del usuario. El valor predeterminado es `false`. Usa este parámetro para mejorar la privacidad del usuario, cumplir con las normativas regionales de protección de datos (como el RGPD o la CCPA), o reducir la recopilación de datos innecesaria cuando las funciones basadas en IP no son necesarias para tu aplicación. ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build() ``` #### Desactivar la recopilación y el intercambio del ID publicitario \{#disable-advertising-id-collection-and-sharing\} Al activar el módulo de Adapty, establece `appleIdfaCollectionDisabled` (iOS) o `googleAdvertisingIdCollectionDisabled` (Android) en true para desactivar la recopilación de identificadores publicitarios. El valor predeterminado es false. Utiliza este parámetro para cumplir con las políticas de App Store/Play Store, evitar que se muestre el aviso de App Tracking Transparency, o si tu app no requiere atribución publicitaria ni analítica basada en identificadores de publicidad. ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withGoogleAdvertisingIdCollectionDisabled(true) // Android only .withAppleIdfaCollectionDisabled(true) // iOS only .build() ``` #### Configurar la caché de medios para AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} Por defecto, AdaptyUI almacena en caché los archivos multimedia (como imágenes y vídeos) para mejorar el rendimiento y reducir el uso de red. Puedes personalizar la configuración de caché proporcionando una configuración personalizada. Usa `mediaCache` para sobrescribir la configuración de caché predeterminada: ```kotlin val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withMediaCacheConfiguration( AdaptyConfig.MediaCacheConfiguration( memoryStorageTotalCostLimit = 200 * 1024 * 1024, // 200 MB memoryStorageCountLimit = Int.MAX_VALUE, diskStorageSizeLimit = 200 * 1024 * 1024 // 200 MB ) ) .build() ``` ### Habilitar niveles de acceso locales (Android) \{#enable-local-access-levels-android\} Por defecto, los [niveles de acceso locales](local-access-levels) están desactivados para Android. Para habilitarlos, establece `withLocalAccessLevelAllowed` en `true`: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withGoogleLocalAccessLevelAllowed(true) .build() ``` ### Borrar datos al restaurar desde copia de seguridad \{#clear-data-on-backup-restore\} Cuando `withAppleClearDataOnBackup` se establece en `true`, el SDK detecta si la app se restauró desde una copia de seguridad de iCloud y elimina todos los datos del SDK almacenados localmente, incluyendo información de perfil en caché, detalles de productos y paywalls. A continuación, el SDK se inicializa con un estado limpio. El valor predeterminado es `false`. :::note Solo se elimina la caché local del SDK. El historial de transacciones con Apple y los datos de usuario en los servidores de Adapty permanecen sin cambios. ::: ```swift showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withAppleClearDataOnBackup(true) .build() ``` ## Resolución de problemas \{#troubleshooting\} #### Reglas de copia de seguridad de Android (configuración de Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Algunos SDKs (incluido Adapty) incluyen su propia configuración de Android Auto Backup. Si utilizas varios SDKs que definen reglas de copia de seguridad, el fusionador de manifiestos de Android puede fallar con un error relacionado con `android:fullBackupContent`, `android:dataExtractionRules` o `android:allowBackup`. Síntomas típicos del error: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Estos cambios deben realizarse en el directorio de la plataforma Android (normalmente en la carpeta `android/` de tu proyecto). ::: Para resolverlo, necesitas: - Indicar al fusionador de manifiestos que use los valores de tu app para los atributos relacionados con la copia de seguridad. - Crear archivos de reglas de copia de seguridad que combinen las reglas de Adapty con las de otros SDKs. #### 1. Añade el namespace `tools` a tu manifiesto \{#1-add-the-tools-namespace-to-your-manifest\} En tu archivo `AndroidManifest.xml`, asegúrate de que la etiqueta raíz `<manifest>` incluya tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. Sobreescribe los atributos de copia de seguridad en `<application>` \{#2-override-backup-attributes-in-application\} En el mismo archivo `AndroidManifest.xml`, actualiza la etiqueta `<application>` para que tu app proporcione los valores definitivos e indique al fusionador de manifiestos que reemplace los valores de las librerías: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` Si algún SDK también define `android:allowBackup`, inclúyelo en `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Crea los archivos de reglas de copia de seguridad combinadas \{#3-create-merged-backup-rules-files\} Crea archivos XML en el directorio `res/xml/` de tu proyecto Android que combinen las reglas de Adapty con las de otros SDKs. Android utiliza distintos formatos de reglas de copia de seguridad según la versión del sistema operativo, por lo que crear ambos archivos garantiza la compatibilidad con todas las versiones de Android que admite tu app. :::note Los ejemplos a continuación usan AppsFlyer como SDK de terceros de muestra. Reemplaza o añade reglas para cualquier otro SDK que uses en tu app. ::: **Para Android 12 y superior** (usa el nuevo formato de reglas de extracción de datos): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Para Android 11 e inferior** (usa el formato legado de contenido de copia de seguridad completa): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> :::important En un proyecto Kotlin Multiplatform, aplica estos cambios en el módulo de aplicación Android (el que genera el APK/AAB), por ejemplo, `androidApp` o `app`: - Manifiesto: `androidApp/src/main/AndroidManifest.xml` - XML de reglas de copia de seguridad: `androidApp/src/main/res/xml/` ::: #### Las compras fallan al volver desde otra app en Android \{#purchases-fail-after-returning-from-another-app-in-android\} Si la Activity que inicia el flow de compra utiliza un `launchMode` no predeterminado, Android puede recrearla o reutilizarla incorrectamente cuando el usuario regresa desde Google Play, una aplicación bancaria o un navegador. Esto puede provocar que el resultado de la compra se pierda o se trate como cancelado. Para garantizar que las compras funcionen correctamente, utiliza únicamente los modos de inicio `standard` o `singleTop` para la Activity que inicia el flow de compra, y evita cualquier otro modo. En tu `AndroidManifest.xml`, asegúrate de que la Activity que inicia el flow de compra esté configurada como `standard` o `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` --- # File: kmp-quickstart-paywalls --- --- title: "Habilitar compras con Flow Builder en el SDK de Kotlin Multiplatform" description: "Guía de inicio rápido para habilitar compras in-app con Adapty Flow Builder." --- Esta guía usa las APIs del SDK de Adapty para Kotlin Multiplatform v4 (beta). Si usas la v3, consulta la [guía de migración](migration-to-kmp-sdk-v4) para ver los nombres de métodos correspondientes. Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Products**](product) – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Flows**](adapty-flow-builder) – secuencias de pantallas que presentan productos a los usuarios, creadas en el Flow Builder sin código. El SDK las recupera mediante `getFlow`. Si prefieres construir la interfaz en tu propio código, usa un paywall en su lugar — consulta [Implementar paywalls manualmente](kmp-quickstart-manual). - [**Placements**](placements) – dónde y cuándo muestras flows en tu app (por ejemplo, `main`, `onboarding`, `settings`). Asocias flows a placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar flows diferentes a distintos usuarios. Adapty te ofrece tres formas de activar las compras en tu aplicación. Elige la que mejor se adapte a tus necesidades: | Implementación | Complejidad | Cuándo usar | |---|---|---| | Adapty Flow Builder | ✅ Fácil | [Creas un flow completo y listo para compras en el constructor no-code](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona todo el flujo de compra, la validación de recibos y la gestión de suscripciones. | | Paywalls creados manualmente | 🟡 Media | Implementas la UI de tu paywall en el código de tu app, pero sigues obteniendo el objeto flow desde Adapty para mantener flexibilidad en la oferta de productos. Consulta la [guía](kmp-quickstart-manual). | | Modo observador | 🔴 Difícil | Ya tienes tu propia infraestructura de gestión de compras y quieres seguir usándola. Ten en cuenta que el modo observador tiene sus limitaciones en Adapty. Consulta el [artículo](observer-vs-full-mode). | :::important **Los pasos a continuación muestran cómo implementar un flow creado en el Adapty Flow Builder.** Si prefieres construir la UI del paywall tú mismo, consulta [Implementar paywalls manualmente](kmp-quickstart-manual). ::: Para mostrar un flow creado en el Adapty Flow Builder, en el código de tu app solo necesitas: 1. **Obtener el flow**: Obtenlo desde Adapty. 2. **Mostrarlo y Adapty gestionará las compras por ti**: Muestra la vista en tu app. 3. **Gestionar las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abrir enlaces o cerrar el flow cuando los usuarios pulsen botones. ## Antes de empezar \{#before-you-start\} Antes de empezar, completa estos pasos: 1. Conecta tu app al [App Store](initial_ios) y/o [Google Play](initial-android) en el Adapty Dashboard. 2. [Crea tus productos](create-product) en Adapty. 3. [Crea un flow y añade productos a él](create-paywall). 4. [Crea un placement y añade tu flow a él](create-placement). 5. [Instala y activa el SDK](sdk-installation-kotlin-multiplatform) en el código de tu app. :::tip La forma más rápida de completar estos pasos es seguir la [guía de inicio rápido](quickstart) o crear flows y placements usando el [CLI para desarrolladores](developer-cli-quickstart). ::: ## 1. Obtener el flow \{#1-get-the-flow\} Tus flows están asociados a placements configurados en el dashboard. Los placements te permiten ejecutar diferentes flows para distintas audiencias o realizar [pruebas A/B](ab-tests). Para obtener un flow creado en el Adapty Flow Builder, debes: 1. Obtener el objeto `flow` por el ID del [placement](placements) usando el método `getFlow`. 2. Crear la vista del flow con el método `createFlowView`. La vista contiene los elementos de UI y los estilos necesarios para mostrar el flow. Si el flow no tiene ninguna vista configurada, `createFlowView` devuelve un error — gestiónalo en `onError`. :::important Para obtener la vista, debes activar el toggle **Show on device** en el Flow Builder. De lo contrario, `createFlowView` devolverá un error y el flow no se mostrará. ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // the flow has no view configured, or view creation failed } } .onError { error -> // handle the error } ``` ## 2. Mostrar el flow \{#display-the-flow\} Ahora que tienes el flow, basta con añadir unas pocas líneas para mostrarlo. Para mostrar el flow visual en la pantalla del dispositivo, primero debes crear la vista. Para ello, llama al método `AdaptyUI.createFlowView()`: ```kotlin showLineNumbers AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // handle the error } ``` Una vez que la vista se haya creado correctamente, puedes mostrarla en la pantalla del dispositivo. Cada vista solo puede usarse una vez: después de llamar a `dismiss()`, llama de nuevo a `createFlowView` para mostrar el flow otra vez. :::tip Para más detalles sobre cómo mostrar un flow, consulta nuestra [guía](kmp-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#handle-button-actions\} Cuando los usuarios hacen clic en los botones del flow, el SDK de Kotlin Multiplatform gestiona automáticamente las compras, la restauración, el cierre del flow y la apertura de enlaces. Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren gestionar las acciones en tu código. O bien, puede que quieras anular su comportamiento predeterminado. Por ejemplo, aquí está el comportamiento predeterminado del botón de cierre. No necesitas añadirlo en el código, pero aquí puedes ver cómo se hace si es necesario. Ten en cuenta que, por defecto, el flow permanece abierto tras una compra exitosa. Si quieres cerrarlo una vez que la compra finaliza, descarta la vista en el callback `flowViewDidFinishPurchase`. :::tip Consulta nuestras guías sobre cómo gestionar [acciones](kmp-handle-paywall-actions) y [eventos](kmp-handling-events) de botones. ::: ```kotlin showLineNumbers AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } else -> Unit } } override fun flowViewDidFinishPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) { mainUiScope.launch { view.dismiss() } } } }) ``` ## Próximos pasos \{#next-steps\} Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox del App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. Ahora necesitas [comprobar el nivel de acceso de los usuarios](kmp-check-subscription-status) para asegurarte de mostrar un paywall o dar acceso a las funciones de pago a los usuarios correctos. ## Ejemplo completo \{#full-example\} Aquí se muestra cómo integrar todos esos pasos en tu app. ```kotlin showLineNumbers // Set up the observer for handling flow events AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } else -> Unit } } override fun flowViewDidFinishPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) { mainUiScope.launch { view.dismiss() } } } }) // Get and display the flow Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // the flow has no view configured — use custom logic } } .onError { error -> // handle the error } ``` --- # File: kmp-check-subscription-status --- --- title: "Comprobar el estado de la suscripción en el SDK de Kotlin Multiplatform" description: "Aprende a comprobar el estado de la suscripción en tu app de Kotlin Multiplatform con Adapty." --- Para decidir si los usuarios pueden acceder a contenido de pago o ver un paywall, necesitas comprobar su [nivel de acceso](access-level) en el perfil. Este artículo muestra cómo acceder al estado del perfil para decidir qué deben ver los usuarios: si mostrarles un paywall o darles acceso a las funciones de pago. ## Obtener el estado de la suscripción \{#get-subscription-status\} Cuando decides si mostrar un paywall o contenido de pago a un usuario, compruebas su [nivel de acceso](access-level) en su perfil. Tienes dos opciones: - Llama a `getProfile` si necesitas los datos más recientes del perfil de inmediato (como al iniciar la app) o quieres forzar una actualización. - Configura **actualizaciones automáticas del perfil** para mantener una copia local que se refresca automáticamente cada vez que cambia el estado de la suscripción. ### Obtener el perfil \{#get-profile\} La forma más sencilla de obtener el estado de la suscripción es usar el método `getProfile` para acceder al perfil: ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access } .onError { error -> // handle the error } ``` ### Escuchar actualizaciones de la suscripción \{#listen-to-subscription-updates\} Para recibir actualizaciones del perfil automáticamente en tu app: 1. Usa `Adapty.setOnProfileUpdatedListener()` para escuchar cambios en el perfil: Adapty llamará a este método automáticamente cada vez que cambie el estado de la suscripción del usuario. 2. Almacena los datos del perfil actualizado cuando se llame a este método, para poder usarlos en toda tu app sin hacer peticiones de red adicionales. ```kotlin showLineNumbers class SubscriptionManager { private var currentProfile: AdaptyProfile? = null init { // Listen for profile updates Adapty.setOnProfileUpdatedListener { profile -> currentProfile = profile // Update UI, unlock content, etc. } } // Use stored profile instead of calling getProfile() fun hasAccess(): Boolean { return currentProfile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true } } ``` :::note Adapty llama automáticamente al listener de actualización del perfil cuando se inicia tu app, proporcionando datos de suscripción en caché incluso si el dispositivo está sin conexión. ::: ## Conectar el perfil con la lógica del paywall \{#connect-profile-with-paywall-logic\} Cuando necesitas tomar decisiones inmediatas sobre si mostrar paywalls o dar acceso a funciones de pago, puedes comprobar el perfil del usuario directamente. Este enfoque es útil en escenarios como el inicio de la app, al entrar en secciones premium o antes de mostrar contenido específico. ```kotlin showLineNumbers private fun checkAccessAndShowPaywall() { // First, check if user has access Adapty.getProfile() .onSuccess { profile -> val hasAccess = profile.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true if (!hasAccess) { // User doesn't have access, show paywall showPaywall() } else { // User has access, show premium content showPremiumContent() } } .onError { error -> // If we can't check access, show paywall as fallback showPaywall() } } private fun showPaywall() { // Get and display paywall using the KMP SDK Adapty.getPaywall("YOUR_PLACEMENT_ID") .onSuccess { paywall -> if (paywall.hasViewConfiguration) { val paywallView = AdaptyUI.createPaywallView(paywall = paywall) paywallView?.present() } else { // Handle remote config paywall or show custom UI handleRemoteConfigPaywall(paywall) } } .onError { error -> // Handle paywall loading error showError("Unable to load paywall") } } private fun showPremiumContent() { // Show your premium content here // This is where you unlock paid features } ``` ## Próximos pasos \{#next-steps\} Ahora que sabes cómo hacer seguimiento del estado de la suscripción, aprende a [trabajar con perfiles de usuario](kmp-quickstart-identify) para asegurarte de que pueden acceder a lo que han pagado. --- # File: kmp-quickstart-identify --- --- title: "Identificar usuarios en el SDK de Kotlin Multiplatform" description: "Guía de inicio rápido para configurar Adapty para la gestión de suscripciones in-app en KMP." --- :::important Esta guía es para ti si tienes tu propio sistema de autenticación. Aquí aprenderás a trabajar con perfiles de usuario en Adapty para que se alineen con tu sistema de autenticación existente. ::: La forma en que gestionas las compras de los usuarios depende del modelo de autenticación de tu app: - Si tu app no usa autenticación de backend ni almacena datos de usuario, consulta la [sección sobre usuarios anónimos](#anonymous-users). - Si tu app tiene (o tendrá) autenticación de backend, consulta la [sección sobre usuarios identificados](#identified-users). **Conceptos clave**: - Los **perfiles** son las entidades necesarias para que el SDK funcione. Adapty los crea automáticamente. - Pueden ser anónimos **(sin customer user ID)** o identificados **(con customer user ID)**. - Proporcionas el **customer user ID** para cruzar los perfiles de Adapty con tu sistema de autenticación interno. Esto es lo que difiere entre usuarios anónimos e identificados: | | Usuarios anónimos | Usuarios identificados | |-------------------------|-------------------------------------------------------|-------------------------------------------------------------------------------------| | **Gestión de compras** | Restauración de compras a nivel de store | Mantienen el historial de compras entre dispositivos mediante su customer user ID | | **Gestión de perfiles** | Nuevos perfiles en cada reinstalación | El mismo perfil entre sesiones y dispositivos | | **Persistencia de datos** | Los datos de usuarios anónimos están ligados a la instalación de la app | Los datos de usuarios identificados persisten entre instalaciones | ## Usuarios anónimos \{#anonymous-users\} Si no tienes autenticación de backend, **no necesitas gestionar la autenticación en el código de la app**: 1. Cuando el SDK se activa en el primer inicio de la app, Adapty **crea un nuevo perfil para el usuario**. 2. Cuando el usuario compra algo en la app, esa compra se **asocia a su perfil de Adapty y a su cuenta en el store**. 3. Cuando el usuario **reinstala** la app o la instala desde un **nuevo dispositivo**, Adapty **crea un nuevo perfil anónimo al activarse**. 4. Si el usuario ya había realizado compras en tu app, por defecto sus compras se sincronizan automáticamente desde el App Store al activar el SDK. Con usuarios anónimos, se crearán nuevos perfiles en cada instalación, pero eso no es un problema porque en los análisis de Adapty puedes [configurar qué se considerará una nueva instalación](general#4-installs-definition-for-analytics). Para los usuarios anónimos, debes contar las instalaciones por **ID de dispositivo**. En este caso, cada instalación de la app en un dispositivo se cuenta como una instalación, incluidas las reinstalaciones. :::note Las restauraciones de copia de seguridad se comportan de forma diferente a las reinstalaciones. Por defecto, cuando un usuario restaura desde una copia de seguridad, el SDK conserva los datos en caché y no crea un nuevo perfil. Puedes configurar este comportamiento con el parámetro `withAppleClearDataOnBackup`. [Más información](sdk-installation-kotlin-multiplatform#clear-data-on-backup-restore). ::: ## Usuarios identificados \{#identified-users\} Tienes dos opciones para identificar usuarios en la app: - [**Durante el login/registro:**](#during-loginsignup) Si los usuarios inician sesión después de que tu app arranque, llama a `identify()` con un customer user ID cuando se autentiquen. - [**Durante la activación del SDK:**](#during-the-sdk-activation) Si ya tienes un customer user ID almacenado cuando la app se inicia, envíalo al llamar a `activate()`. :::important Por defecto, cuando Adapty recibe una compra de un Customer User ID que ya está asociado a otro Customer User ID, el nivel de acceso se comparte, por lo que ambos perfiles tienen acceso de pago. Puedes configurar este ajuste para transferir el acceso de pago de un perfil a otro o deshabilitar el uso compartido completamente. Consulta el [artículo](general#6-sharing-paid-access-between-user-accounts) para más detalles. ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Durante el login/registro \{#during-loginsignup\} Si identificas a los usuarios después de que arranque la app (por ejemplo, tras iniciar sesión o registrarse), usa el método `identify` para establecer su customer user ID. - Si **no has usado este customer user ID antes**, Adapty lo vinculará automáticamente al perfil actual. - Si **ya has usado este customer user ID para identificar al usuario antes**, Adapty cambiará a trabajar con el perfil asociado a ese customer user ID. :::important Los customer user IDs deben ser únicos para cada usuario. Si hardcodeas el valor del parámetro, todos los usuarios se considerarán como uno solo. ::: Espera a que `identify` se complete (en su callback `onSuccess`) antes de llamar a otros métodos del SDK. Las llamadas concurrentes pueden acabar en el perfil anónimo. Consulta [Orden de llamadas en el SDK de Kotlin Multiplatform](kmp-sdk-call-order). ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Único para cada usuario .onSuccess { // identify exitoso } .onError { error -> // gestionar el error } ``` ### Durante la activación del SDK \{#during-the-sdk-activation\} Si ya conoces el customer user ID cuando activas el SDK, puedes enviarlo en el método `activate` en lugar de llamar a `identify` por separado. Si conoces un customer user ID pero solo lo estableces después de la activación, eso significa que, al activarse, Adapty creará un nuevo perfil anónimo y cambiará al existente solo cuando llames a `identify`. Puedes pasar un customer user ID existente (uno que hayas usado antes) o uno nuevo. Si pasas uno nuevo, el nuevo perfil creado en la activación se vinculará automáticamente al customer user ID. :::note Por defecto, la creación de perfiles anónimos no afecta a los dashboards de análisis, porque las instalaciones se cuentan en función de los ID de dispositivo. Un ID de dispositivo representa una única instalación de la app desde el store en un dispositivo y solo se regenera tras reinstalar la app. No depende de si es una primera instalación o una repetida, ni de si se usa un customer user ID existente. Crear un perfil (al activar el SDK o al cerrar sesión), iniciar sesión o actualizar la app sin reinstalarla no genera eventos de instalación adicionales. Si quieres contar las instalaciones en función de usuarios únicos en lugar de dispositivos, ve a **App settings** y configura [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Los customer user IDs deben ser únicos para cada usuario. Si hardcodeas el valor del parámetro, todos los usuarios se considerarán como uno solo. .build() ``` ### Cerrar sesión de usuarios \{#log-users-out\} Si tienes un botón para cerrar la sesión de los usuarios, usa el método `logout`. :::important Cerrar la sesión de los usuarios crea un nuevo perfil anónimo para el usuario. ::: ```kotlin showLineNumbers Adapty.logout() .onSuccess { // cierre de sesión exitoso } .onError { error -> // gestionar el error } ``` :::info Para volver a iniciar sesión en la app, usa el método `identify`. ::: ### Permitir compras sin inicio de sesión \{#allow-purchases-without-login\} Si tus usuarios pueden realizar compras tanto antes como después de iniciar sesión en tu app, debes asegurarte de que mantendrán el acceso una vez que inicien sesión: 1. Cuando un usuario sin sesión iniciada realiza una compra, Adapty la vincula a su ID de perfil anónimo. 2. Cuando el usuario inicia sesión en su cuenta, Adapty pasa a trabajar con su perfil identificado. - Si es un nuevo customer user ID (por ejemplo, la compra se realizó antes del registro), Adapty asigna el customer user ID al perfil actual, por lo que todo el historial de compras se mantiene. - Si es un customer user ID existente (el customer user ID ya está vinculado a un perfil), debes obtener el nivel de acceso real tras el cambio de perfil. Puedes llamar a [`getProfile`](kmp-check-subscription-status) justo después de la identificación, o [escuchar las actualizaciones del perfil](kmp-check-subscription-status) para que los datos se sincronicen automáticamente. ## Próximos pasos \{#next-steps\} ¡Enhorabuena! Has implementado la lógica de pago in-app en tu app. ¡Te deseamos lo mejor con la monetización de tu app! Para sacar aún más partido a Adapty, puedes explorar estos temas: - [**Pruebas**](troubleshooting-test-purchases): Asegúrate de que todo funciona correctamente - [**Integraciones**](configuration): Integra con servicios de atribución de marketing y análisis en una sola línea de código - [**Establecer atributos de perfil personalizados**](kmp-setting-user-attributes): Añade atributos personalizados a los perfiles de usuario y crea segmentos para lanzar pruebas A/B o mostrar diferentes paywalls a distintos usuarios --- # File: adapty-sdk-integration-skill-kmp --- --- title: "Integra Adapty en tu app Kotlin Multiplatform con el skill de integración del SDK" description: "Usa el skill adapty-sdk-integration para integrar el SDK de Adapty en tu app Kotlin Multiplatform de principio a fin con tu herramienta de codificación con IA." --- La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. :::important La funcionalidad está en beta. Si se detiene o se comporta de forma inesperada, sigue la [guía de integración paso a paso](adapty-cursor-kmp) — te guía a través de cada etapa con la documentación correcta. ::: La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. --- # File: adapty-cursor-kmp --- --- title: "Integra Adapty en tu app de Kotlin Multiplatform con ayuda de IA" description: "Una guía paso a paso para integrar Adapty en tu app de Kotlin Multiplatform usando Cursor, Context7, ChatGPT, Claude u otras herramientas de IA." --- Esta guía te lleva paso a paso por la integración de Adapty en tu app Kotlin Multiplatform con una herramienta de codificación con IA: le proporcionas la documentación correcta de Adapty en el orden correcto. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Antes de empezar: configuración del dashboard \{#before-you-start-dashboard-setup\} Adapty requiere algo de configuración en el dashboard antes de escribir código con el SDK. Puedes hacerlo con una skill interactiva de LLM o manualmente desde el Dashboard. ### Enfoque con skill (recomendado) \{#skill-approach-recommended\} El skill de Adapty CLI permite que tu LLM configure tu app, productos, niveles de acceso, paywalls y placements directamente, sin necesidad de abrir el Dashboard en cada paso. Solo tienes que [conectar tus stores](integrate-payments) en el Dashboard. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` Una vez añadido el skill, ejecuta `/adapty-cli` en tu agente. Te guiará paso a paso, incluyendo cuándo abrir el Dashboard para conectar tus stores. ### Enfoque desde el dashboard Si prefieres configurarlo todo de forma manual, esto es lo que necesitas antes de escribir código. Tu LLM no puede buscar los valores del dashboard por ti — tendrás que proporcionarlos tú mismo. 1. **Conecta tus app stores**: En el Adapty Dashboard, ve a **App settings → General**. Conecta tanto App Store como Google Play si tu app KMP apunta a ambas plataformas. Esto es necesario para que las compras funcionen. [Conecta los app stores](integrate-payments) 2. **Copia tu clave SDK pública**: En el Adapty Dashboard, ve a **App settings → General** y busca la sección **API keys**. En el código, es la cadena que pasas al constructor de configuración de Adapty. 3. **Crea al menos un producto**: En el Adapty Dashboard, ve a la página **Products**. No haces referencia a los productos directamente en el código — Adapty los entrega a través de paywalls. [Añadir productos](quickstart-products) 4. **Crea un paywall y un placement**: En el Adapty Dashboard, crea un paywall en la página **Paywalls** y asígnalo a un placement en la página **Placements**. En el código, el ID del placement es el string que pasas a `Adapty.getPaywall("YOUR_PLACEMENT_ID")`. [Crear un paywall](quickstart-paywalls) 5. **Configura los niveles de acceso**: En el Adapty Dashboard, configúralos por producto en la página **Products**. En el código, la cadena que se comprueba es `profile.accessLevels["premium"]?.isActive`. El nivel de acceso `premium` predeterminado funciona para la mayoría de las apps. Si los usuarios de pago tienen acceso a distintas funcionalidades según el producto (por ejemplo, un plan `basic` frente a un plan `pro`), [crea niveles de acceso adicionales](assigning-access-level-to-a-product) antes de empezar a programar. :::tip Una vez que tengas los cinco, estarás listo para escribir código. Dile a tu LLM: "Mi clave SDK pública es X, mi placement ID es Y" para que pueda generar el código correcto de inicialización y obtención de paywalls. ::: ### Configura cuando estés listo \{#set-up-when-ready\} No son obligatorios para empezar a programar, pero los necesitarás a medida que tu integración madure: - **Pruebas A/B**: Configúralas en la página **Placements**. No se necesitan cambios de código. [Pruebas A/B](ab-tests) - **Paywalls y placements adicionales**: Añade más llamadas `getPaywall` con diferentes IDs de placement. - **Integraciones de analíticas**: Configúralas en la página **Integrations**. La configuración varía según la integración. Consulta [integraciones de analíticas](analytics-integration) e [integraciones de atribución](attribution-integration). ## Proporciona la documentación de Adapty a tu LLM \{#feed-adapty-docs-to-your-llm\} ### Usa Context7 (recomendado) [Context7](https://context7.com) es un servidor MCP que da a tu LLM acceso directo a la documentación actualizada de Adapty. Tu LLM obtiene automáticamente la documentación adecuada según lo que preguntes, sin necesidad de pegar URLs manualmente. Context7 funciona con **Cursor**, **Claude Code**, **Windsurf** y otras herramientas compatibles con MCP. Para configurarlo, ejecuta: ``` npx ctx7 setup ``` Esto detecta tu editor y configura el servidor Context7. Para la configuración manual, consulta el [repositorio de Context7 en GitHub](https://github.com/upstash/context7). Una vez configurado, haz referencia a la librería de Adapty en tus prompts: ``` Use the adaptyteam/adapty-docs library to look up how to install the Kotlin Multiplatform SDK ``` :::warning Aunque Context7 elimina la necesidad de pegar links de documentación manualmente, el orden de implementación importa. Sigue el [paso a paso de implementación](#implementation-walkthrough) de principio a fin para asegurarte de que todo funciona. ::: ### Usa documentación en texto plano Puedes acceder a cualquier documento de Adapty en texto plano Markdown. Añade `.md` al final de su URL, o haz clic en **Copy for LLM** bajo el título del artículo. Por ejemplo: [adapty-cursor-kmp.md](https://adapty.io/docs/es/adapty-cursor-kmp.md). Cada etapa del [recorrido de implementación](#implementation-walkthrough) a continuación incluye un bloque "Send this to your LLM" con enlaces `.md` para pegar. Para obtener más documentación a la vez, consulta los [archivos de índice y subconjuntos por plataforma](#plain-text-doc-index-files) a continuación. ## Guía de implementación paso a paso \{#implementation-walkthrough\} El resto de esta guía recorre la integración de Adapty en orden de implementación. Cada etapa incluye la documentación que debes enviar a tu LLM, qué deberías ver al terminar y los problemas más comunes. ### Planifica tu integración \{#plan-your-integration\} Antes de escribir código, pídele a tu LLM que analice tu proyecto y elabore un plan de implementación. Si tu herramienta de IA tiene un modo de planificación (como el modo plan de Cursor o Claude Code), úsalo para que el LLM pueda leer tanto la estructura de tu proyecto como la documentación de Adapty antes de generar cualquier código. Indícale a tu LLM qué enfoque utilizas para las compras, ya que esto determina qué guías debe seguir: - [**Adapty Paywall Builder**](adapty-paywall-builder): Creas paywalls en el editor no-code de Adapty y el SDK los renderiza automáticamente. - [**Paywalls creados manualmente**](kmp-making-purchases): Construyes tu propia interfaz de paywall en código, pero sigues usando Adapty para obtener productos y gestionar compras. - [**Modo Observer**](observer-vs-full-mode): Mantienes tu infraestructura de compras existente y usas Adapty solo para analíticas e integraciones. ¿No sabes cuál elegir? Lee la [tabla comparativa en la guía de inicio rápido](kmp-quickstart-paywalls). ### Instalar y configurar el SDK \{#install-and-configure-the-sdk\} Añade la dependencia del SDK de Adapty mediante Gradle y actívalo con tu clave pública del SDK. Esta es la base: sin esto, nada más funciona. **Guía:** [Instalar y configurar el SDK de Adapty](sdk-installation-kotlin-multiplatform) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/sdk-installation-kotlin-multiplatform.md ``` :::tip[Checkpoint] - **Esperado:** La app compila y se ejecuta. Logcat (Android) o la consola de Xcode (iOS) muestra el log de activación de Adapty. - **Problema frecuente:** "Public API key is missing" → comprueba que has reemplazado el placeholder con tu clave real desde App settings. ::: ### Mostrar paywalls y gestionar compras \{#show-paywalls-and-handle-purchases\} Obtén un paywall por ID de placement, muéstralo y gestiona los eventos de compra. Las guías que necesitas dependen de cómo gestiones las compras. Prueba cada compra en el sandbox a medida que avanzas — no esperes hasta el final. Consulta [Probar compras en sandbox](test-purchases-in-sandbox) para las instrucciones de configuración. <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Paywall Builder" default> **Guías:** - [Habilitar compras usando paywalls (inicio rápido)](kmp-quickstart-paywalls) - [Obtener paywalls de Paywall Builder y su configuración](kmp-get-pb-paywalls) - [Mostrar paywalls](kmp-present-paywalls) - [Gestionar eventos de paywall](kmp-handling-events) - [Responder a acciones de botones](kmp-handle-paywall-actions) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/kmp-quickstart-paywalls.md - https://adapty.io/docs/es/kmp-get-pb-paywalls.md - https://adapty.io/docs/es/kmp-present-paywalls.md - https://adapty.io/docs/es/kmp-handling-events.md - https://adapty.io/docs/es/kmp-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Esperado:** El paywall aparece con los productos configurados. Al pulsar un producto, se activa el diálogo de compra en sandbox. - **Problema frecuente:** Paywall vacío o error en `getPaywall` → verifica que el ID del placement coincida exactamente con el dashboard y que el placement tenga una audiencia asignada. ::: </TabItem> <TabItem value="manual" label="Paywalls manuales"> **Guías:** - [Activar compras en tu paywall personalizado (quickstart)](kmp-quickstart-manual) - [Obtener paywalls y productos](fetch-paywalls-and-products-kmp) - [Renderizar paywall diseñado con Remote Config](present-remote-config-paywalls-kmp) - [Realizar compras](kmp-making-purchases) - [Restaurar compras](kmp-restore-purchase) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/kmp-quickstart-manual.md - https://adapty.io/docs/es/fetch-paywalls-and-products-kmp.md - https://adapty.io/docs/es/present-remote-config-paywalls-kmp.md - https://adapty.io/docs/es/kmp-making-purchases.md - https://adapty.io/docs/es/kmp-restore-purchase.md ``` :::tip[Checkpoint] - **Expected:** Tu paywall personalizado muestra los productos obtenidos de Adapty. Al pulsar un producto, se activa el diálogo de compra en sandbox. - **Gotcha:** Array de productos vacío → verifica que el paywall tiene productos asignados en el dashboard y que el placement tiene una audiencia. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Guías:** - [Descripción general del Observer mode](observer-vs-full-mode) - [Implementar Observer mode](implement-observer-mode-kmp) - [Reportar transacciones en Observer mode](report-transactions-observer-mode-kmp) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/observer-vs-full-mode.md - https://adapty.io/docs/es/implement-observer-mode-kmp.md - https://adapty.io/docs/es/report-transactions-observer-mode-kmp.md ``` :::tip[Punto de control] - **Esperado:** Tras una compra en sandbox usando tu flujo de compra existente, la transacción aparece en el **Event Feed** del Adapty Dashboard. - **Problema frecuente:** Si no aparecen eventos, verifica que estás reportando las transacciones a Adapty y que las notificaciones del servidor están configuradas para ambas stores. ::: </TabItem> </Tabs> ### Verificar el estado de la suscripción \{#check-subscription-status\} Tras una compra, comprueba el perfil del usuario para ver si tiene un nivel de acceso activo y así controlar el acceso al contenido premium. **Guía:** [Verificar el estado de la suscripción](kmp-check-subscription-status) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/kmp-check-subscription-status.md ``` :::tip[Punto de control] - **Esperado:** Tras una compra en sandbox, `profile.accessLevels["premium"]?.isActive` devuelve `true`. - **Problema habitual:** `accessLevels` vacío después de la compra → comprueba que el producto tenga un nivel de acceso asignado en el dashboard. ::: ### Identificar usuarios \{#identify-users\} Vincula las cuentas de usuario de tu app con los perfiles de Adapty para que las compras persistan entre dispositivos. :::important Omite este paso si tu app no tiene autenticación. ::: **Guía:** [Identificar usuarios](kmp-quickstart-identify) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/kmp-quickstart-identify.md ``` :::tip[Checkpoint] - **Esperado:** Tras llamar a `Adapty.identify("your-user-id")`, la sección **Profiles** del dashboard muestra tu ID de usuario personalizado. - **Problema frecuente:** Llama a `identify` después de la activación pero antes de obtener paywalls para evitar que la atribución quede en un perfil anónimo. ::: ### Prepararse para el lanzamiento \{#prepare-for-release\} Una vez que la integración funcione en el sandbox, revisa la lista de verificación de lanzamiento para asegurarte de que todo está listo para producción. **Guía:** [Lista de verificación de lanzamiento](release-checklist) Envía esto a tu LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/es/release-checklist.md ``` :::tip[Checkpoint] - **Esperado:** Todos los elementos del checklist confirmados: conexiones con el store, notificaciones del servidor, flujo de compra, verificaciones del nivel de acceso y requisitos de privacidad. - **Problema frecuente:** Notificaciones del servidor ausentes → configura las App Store Server Notifications en **App settings → iOS SDK** y las Google Play Real-Time Developer Notifications en **App settings → Android SDK**. ::: ## Archivos de índice de documentación en texto plano \{#plain-text-doc-index-files\} Si necesitas darle a tu LLM un contexto más amplio más allá de páginas individuales, disponemos de archivos de índice que listan o combinan toda la documentación de Adapty: - [`llms.txt`](https://adapty.io/docs/es/llms.txt): Lista todas las páginas con enlaces `.md`. Un [estándar emergente](https://llmstxt.org/) para hacer los sitios web accesibles a los LLMs. Ten en cuenta que para algunos agentes de IA (p. ej., ChatGPT) necesitarás descargar `llms.txt` y subirlo al chat como archivo. - [`llms-full.txt`](https://adapty.io/docs/es/llms-full.txt): Toda la documentación de Adapty combinada en un único archivo. Muy grande: úsalo solo cuando necesites el panorama completo. - [`kmp-llms.txt`](https://adapty.io/docs/es/kmp-llms.txt) y [`kmp-llms-full.txt`](https://adapty.io/docs/es/kmp-llms-full.txt) específicos para Kotlin Multiplatform: subconjuntos específicos de la plataforma que ahorran tokens en comparación con el sitio completo. --- # File: kmp-paywalls --- --- title: "Flows y paywalls - Kotlin Multiplatform" description: "Muestra y gestiona flows y paywalls creados con el Adapty Flow Builder o Paywall Builder en tu app de Kotlin Multiplatform." --- ## Mostrar paywalls \{#display-paywalls\} ### Adapty Flow Builder y Paywall Builder \{#adapty-flow-builder--paywall-builder\} <CustomDocCardList ids={['kmp-get-pb-paywalls', 'kmp-present-paywalls', 'kmp-handling-events', 'kmp-handle-paywall-actions']} /> :::tip Para empezar rápidamente con los paywalls del Adapty Paywall Builder, consulta nuestra [guía de inicio rápido](kmp-quickstart-paywalls). ::: ### Implementar paywalls manualmente \{#implement-paywalls-manually\} <CustomDocCardList ids={['kmp-quickstart-manual', 'fetch-paywalls-and-products-kmp', 'present-remote-config-paywalls-kmp', 'kmp-making-purchases']} /> Para más guías sobre cómo implementar paywalls y gestionar compras manualmente, consulta la [categoría](kmp-implement-paywalls-manually). ## Funciones útiles \{#useful-features\} <CustomDocCardList ids={['kmp-use-fallback-paywalls', 'kmp-web-paywalls']} /> --- # File: kmp-get-pb-paywalls --- --- title: "Obtener flows y paywalls - Kotlin Multiplatform" description: "Obtén flows y paywalls de Adapty en tu aplicación Kotlin Multiplatform." --- <SDKv4> <MethodPromo method="getFlow" /> Tras [diseñar tu flow o paywall con el Paywall Builder](adapty-paywall-builder), puedes mostrarlo en tu aplicación móvil. El primer paso es obtener el flow o paywall asociado al placement y su configuración de vista, como se describe a continuación. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a mostrar flows en tu aplicación móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow/paywall e incorpora productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow/paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-kotlin-multiplatform) en tu app móvil. </details> ## Obtener un flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall con el Flow Builder o el Paywall Builder, no tienes que preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese flow o paywall ya contiene tanto lo que debe mostrarse como la forma en que debe mostrarse. Aun así, necesitas obtener su ID a través del placement, su configuración de vista, y luego presentarlo en tu app. Para garantizar un rendimiento óptimo, es fundamental obtener el flow o paywall y su [configuración de vista](kmp-get-pb-paywalls#fetch-the-view-configuration) lo antes posible, dejando tiempo suficiente para que las imágenes se descarguen antes de mostrárselas al usuario. Para obtener un flow o paywall, usa el método `getFlow`: ```kotlin showLineNumbers Adapty.getFlow( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { flow -> // el flow/paywall solicitado }.onError { error -> // manejar el error } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.Default` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen conexiones a internet inestables, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no obtener los datos más recientes, pero tendrán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante limpieza manual.</p><p></p><p>El SDK de Adapty almacena flows y paywalls localmente en dos capas: la caché de actualización regular descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtenerlos más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente y asegurar la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo de espera especificado en `loadTimeout`, ya que la operación puede consistir en diferentes solicitudes internamente.</p><p>Para Kotlin Multiplatform: puedes crear una `Duration` con funciones de extensión como `5.seconds`, donde `.seconds` proviene de `kotlin.time.Duration.Companion.seconds`.</p> | Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :---------- | | Flow | Un objeto `AdaptyFlow` que contiene el placement, los identificadores (`instanceIdentity`, `variationId`), el nombre, las variaciones de paywall (`paywalls` — una lista de `AdaptyFlowPaywall`) y los Remote Configs (`remoteConfigs` — una lista con una entrada por idioma). Para obtener los productos reales y usarlos en precarga, UI personalizada o comprobaciones programáticas, llama a `getPaywallProducts(flow)`. | ## Obtener la configuración de la vista \{#fetch-the-view-configuration\} Una vez obtenido el flow o el paywall, carga su configuración de vista y crea la vista en un solo paso con el método `createFlowView`. No hay ningún indicador separado que comprobar: si el placement fue diseñado en el **Flow Builder** (un flow) o en el **Paywall Builder** (un paywall), `createFlowView` devuelve la vista lista para presentarse. Si el placement es un paywall personalizado sin interfaz de Builder, `createFlowView` devuelve un `AdaptyResult.Error` — [trátalo como un paywall de Remote Config](present-remote-config-paywalls-kmp). :::important Asegúrate de activar el botón **Show on device** en el Flow Builder. Si esta opción no está activada, la configuración de la vista no estará disponible para recuperarla. ::: ```kotlin showLineNumbers AdaptyUI.createFlowView( flow = flow, loadTimeout = 5.seconds, preloadProducts = true ).onSuccess { view -> // use view }.onError { error -> // the flow has no view configured, or view creation failed } ``` | Parámetro | Presencia | Descripción | | :--------------------------- | :------------- | :----------------------------------------------------------- | | **flow** | obligatorio | Un objeto `AdaptyFlow` obtenido mediante `Adapty.getFlow`. | | **loadTimeout** | opcional | Este valor limita el tiempo de espera para este método. Si se alcanza el límite, se devolverán datos en caché o el fallback local. Ten en cuenta que en casos excepcionales este método puede exceder ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede estar compuesta por diferentes solicitudes internamente. Puedes usar funciones de extensión como `5.seconds` de `kotlin.time.Duration.Companion`. | | **preloadProducts** | opcional | Establécelo en `true` para precargar productos y mejorar el rendimiento. Cuando está activado, los productos se cargan de antemano, reduciendo el tiempo necesario para mostrar el flow o el paywall. | | **productPurchaseParams** | opcional | Un mapa de [`AdaptyProductIdentifier`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-product-identifier/) a [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). Úsalo para configurar parámetros de compra específicos, como ofertas personalizadas o parámetros de actualización de suscripción para productos individuales en el flow o el paywall. | :::note Si utilizas varios idiomas, aprende cómo añadir una [localización del Builder](add-paywall-locale-in-adapty-paywall-builder). ::: Una vez cargado, [presenta el flow o el paywall](kmp-present-paywalls). ## Obtén un flow o paywall para la audiencia predeterminada y accede más rápido \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Por lo general, los flows y paywalls se obtienen casi de forma instantánea, así que no necesitas preocuparte por acelerar este proceso. Sin embargo, cuando tienes muchas audiencias y placements, y tus usuarios tienen una conexión a internet débil, obtener un flow o paywall puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un flow o paywall predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar nada. Para solucionar esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow o paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el flow o paywall mediante el método `getFlow`, tal como se detalla en la sección [Obtener flow/paywall](#fetch-flowpaywall) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar flows distintos según la versión de la app (la actual y versiones futuras), puedes encontrarte con dificultades. Tendrás que diseñar flows compatibles con la versión actual (heredada) o asumir que los usuarios con esa versión podrían tener problemas al renderizar ciertos flows. - **Pérdida de targeting**: Todos los usuarios verán el mismo flow diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluida la basada en países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas a cambio de una obtención más rápida de flows o paywalls, utiliza el método `getFlowForDefaultAudience` de la siguiente manera. De lo contrario, quédate con `getFlow` descrito [arriba](#fetch-flowpaywall). ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { flow -> // the requested flow }.onError { error -> // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | predeterminado: `AdaptyPaywallFetchPolicy.Default` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si consideras que tus usuarios tienen una conexión a internet inestable, puedes usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo intermitente que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | ## Personaliza los recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu flow o paywall, implementa los recursos personalizados. Las imágenes y vídeos hero tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizados, seleccionas estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, debes [asignar un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. Aquí tienes un ejemplo de cómo puedes proporcionar recursos personalizados a través de un mapa: :::info El SDK de Kotlin Multiplatform solo admite recursos locales. Para contenido remoto, debes descargar y almacenar en caché los recursos localmente antes de usarlos como recursos personalizados. ::: ```kotlin showLineNumbers // Import generated Res class for accessing resources viewModelScope.launch { // Get URIs for bundled resources using Res.getUri() val heroImagePath = Res.getUri("files/images/hero_image.png") val demoVideoPath = Res.getUri("files/videos/demo_video.mp4") // Or read image as byte data val imageByteData = Res.readBytes("files/images/avatar.png") // Create custom assets map val customAssets: Map<String, AdaptyCustomAsset> = mapOf( // Load image from app resources (bundled with the app) // Files should be placed in commonMain/composeResources/files/ "hero_image" to AdaptyCustomAsset.localImageResource( path = heroImagePath ), // Or use image byte data "avatar" to AdaptyCustomAsset.localImageData( data = imageByteData ), // Load video from app resources "demo_video" to AdaptyCustomAsset.localVideoResource( path = demoVideoPath ), // Or use a video file from device storage "intro_video" to AdaptyCustomAsset.localVideoFile( path = "/path/to/local/video.mp4" ), // Apply custom brand colors "brand_primary" to AdaptyCustomAsset.color( colorHex = "#FF6B35" ), // Create gradient background "card_gradient" to AdaptyCustomAsset.linearGradient( colors = listOf("#1E3A8A", "#3B82F6", "#60A5FA"), stops = listOf(0.0f, 0.5f, 1.0f) ) ) // Use custom assets when creating the flow view AdaptyUI.createFlowView( flow = flow, customAssets = customAssets ).onSuccess { view -> // Present the flow with custom assets view.present() }.onError { error -> // Handle the error - the flow will fall back to default appearance } } ``` :::note Si un recurso no se encuentra o no se puede cargar, el flow o paywall volverá a su apariencia predeterminada configurada en el Builder. ::: </SDKv4> <SDKv3> Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el nuevo Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu app. El primer paso en este proceso es obtener el paywall asociado al placement y su configuración de vista, tal como se describe a continuación. Por favor, ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Si estás implementando tus paywalls manualmente, consulta el tema [Obtener paywalls y productos para paywalls de Remote Config en tu app móvil](fetch-paywalls-and-products-kmp). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a mostrar paywalls en tu app móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-kotlin-multiplatform) en tu aplicación móvil. </details> ## Obtener un paywall diseñado con Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Si has [diseñado un paywall con Paywall Builder](adapty-paywall-builder), no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Este tipo de paywall incluye tanto lo que se muestra como la forma en que se muestra. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y, después, presentarlo en tu app. Para garantizar un rendimiento óptimo, es fundamental obtener el paywall y su [configuración de vista](kmp-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) lo antes posible, dejando tiempo suficiente para que las imágenes se descarguen antes de mostrárselas al usuario. Para obtener un paywall, usa el método `getPaywall`: ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>predeterminado: `en`</p> | <p>El identificador de la [localización del paywall](add-paywall-locale-in-adapty-paywall-builder). Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta es para el idioma y la segunda para la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | predeterminado: `AdaptyPaywallFetchPolicy.Default` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la aplicación y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché de actualización regular descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN sea inaccesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | predeterminado: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el límite, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede agotar el tiempo de espera algo más tarde de lo especificado en `loadTimeout`, ya que la operación puede consistir en distintas peticiones internamente.</p><p>Para Kotlin Multiplatform: Puedes crear `TimeInterval` con funciones de extensión (como `5.seconds`, donde `.seconds` proviene de `import com.adapty.utils.seconds`), o `TimeInterval.seconds(5)`. Para no establecer ningún límite, usa `TimeInterval.INFINITE`.</p> | Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Un objeto [`AdaptyPaywall`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall/) con una lista de IDs de productos, el identificador del paywall, el Remote Config y varias otras propiedades. | ## Obtener la configuración de vista de un paywall creado con Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Asegúrate de activar el interruptor **Show on device** en el Paywall Builder. Si esta opción no está activada, la configuración de vista no estará disponible para recuperar. ::: Tras obtener el paywall, comprueba si incluye una `ViewConfiguration`, lo que indica que fue creado con Paywall Builder. Esto te indicará cómo mostrar el paywall. Si la `ViewConfiguration` está presente, trátalo como un paywall de Paywall Builder; de lo contrario, [trátalo como un paywall de Remote Config](present-remote-config-paywalls-kmp). Usa el método `createPaywallView` para cargar la configuración de la vista. ```kotlin showLineNumbers if (paywall.hasViewConfiguration) { AdaptyUI.createPaywallView( paywall = paywall, loadTimeout = 5.seconds, preloadProducts = true ).onSuccess { paywallView -> // use paywallView }.onError { error -> // handle the error } } else { // use your custom logic } ``` | Parámetro | Presencia | Descripción | | :--------------------------- | :------------- | :----------------------------------------------------------- | | **paywall** | requerido | Un objeto `AdaptyPaywall` para obtener un controlador para el paywall deseado. | | **loadTimeout** | opcional | Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el fallback local. Ten en cuenta que en casos excepcionales este método puede agotar el tiempo de espera ligeramente después de lo especificado en `loadTimeout`, ya que la operación puede consistir en diferentes solicitudes internamente. Puedes usar funciones de extensión como `5.seconds` de `kotlin.time.Duration.Companion`. | | **preloadProducts** | opcional | Establece en `true` para precargar productos y mejorar el rendimiento. Cuando está activado, los productos se cargan de antemano, reduciendo el tiempo necesario para mostrar el paywall. | | **productPurchaseParams** | opcional | Un mapa de [`AdaptyProductIdentifier`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-product-identifier/) a [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). Úsalo para configurar parámetros de compra específicos, como ofertas personalizadas o parámetros de actualización de suscripción para productos individuales en el paywall. | :::note Si usas varios idiomas, aprende cómo añadir una [localización del Paywall Builder](add-paywall-locale-in-adapty-paywall-builder). ::: Una vez cargado, [muestra el paywall](kmp-present-paywalls). ## Obtén un paywall para la audiencia por defecto y cárgalo más rápido \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Por lo general, los paywalls se cargan casi de inmediato, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, cuando tienes numerosas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, la carga de un paywall puede tardar más de lo deseado. En esas situaciones, puede que quieras mostrar un paywall por defecto para garantizar una experiencia de usuario fluida en lugar de no mostrar ningún paywall. Para abordar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, tal como se detalla en la sección [Obtener información del paywall](#fetch-paywall-designed-with-paywall-builder) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunas desventajas importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes paywalls para distintas versiones de la aplicación (actual y futuras), puedes encontrarte con dificultades. Tendrás que diseñar paywalls compatibles con la versión actual (heredada) o asumir que los usuarios con esa versión pueden tener problemas con paywalls que no se rendericen correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluyendo por países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estos inconvenientes para beneficiarte de una obtención más rápida del paywall, utiliza el método `getPaywallForDefaultAudience` como se indica a continuación. De lo contrario, sigue usando `getPaywall` descrito [anteriormente](#fetch-paywall-designed-with-paywall-builder). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.Default` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver los datos en caché si existen. En ese caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | ## Personalizar recursos \{#customize-assets\} Para personalizar las imágenes y vídeos de tu paywall, implementa los recursos personalizados. Las imágenes y vídeos hero tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizado, apuntas a estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de previsualización local mientras se carga una imagen principal remota. - Mostrar una imagen de previsualización antes de reproducir un vídeo. :::important Para usar esta función, actualiza el SDK de Adapty a la versión 3.7.0 o superior. ::: A continuación se muestra un ejemplo de cómo puedes proporcionar recursos personalizados mediante un mapa: :::info El SDK de Kotlin Multiplatform solo admite recursos locales. Para contenido remoto, debes descargar y almacenar en caché los recursos localmente antes de usarlos como recursos personalizados. ::: ```kotlin showLineNumbers // Import generated Res class for accessing resources viewModelScope.launch { // Get URIs for bundled resources using Res.getUri() val heroImagePath = Res.getUri("files/images/hero_image.png") val demoVideoPath = Res.getUri("files/videos/demo_video.mp4") // Or read image as byte data val imageByteData = Res.readBytes("files/images/avatar.png") // Create custom assets map val customAssets: Map<String, AdaptyCustomAsset> = mapOf( // Load image from app resources (bundled with the app) // Files should be placed in commonMain/composeResources/files/ "hero_image" to AdaptyCustomAsset.localImageResource( path = heroImagePath ), // Or use image byte data "avatar" to AdaptyCustomAsset.localImageData( data = imageByteData ), // Load video from app resources "demo_video" to AdaptyCustomAsset.localVideoResource( path = demoVideoPath ), // Or use a video file from device storage "intro_video" to AdaptyCustomAsset.localVideoFile( path = "/path/to/local/video.mp4" ), // Apply custom brand colors "brand_primary" to AdaptyCustomAsset.color( colorHex = "#FF6B35" ), // Create gradient background "card_gradient" to AdaptyCustomAsset.linearGradient( colors = listOf("#1E3A8A", "#3B82F6", "#60A5FA"), stops = listOf(0.0f, 0.5f, 1.0f) ) ) // Use custom assets when creating paywall view AdaptyUI.createPaywallView( paywall = paywall, customAssets = customAssets ).onSuccess { paywallView -> // Present the paywall with custom assets paywallView.present() }.onError { error -> // Handle the error - paywall will fall back to default appearance } } ``` :::note Si un asset no se encuentra o no se carga correctamente, el paywall volverá a su apariencia predeterminada configurada en el Paywall Builder. ::: </SDKv3> --- # File: kmp-present-paywalls --- --- title: "Mostrar flows y paywalls - Kotlin Multiplatform" description: "Presenta flows y paywalls a los usuarios en tu aplicación Kotlin Multiplatform." --- <SDKv4> <MethodPromo method="getFlow" label="Display flows and paywalls" /> Si has creado un flow o un paywall, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese flow o paywall ya contiene tanto lo que debe mostrarse como la forma en que debe mostrarse. :::warning Esta guía cubre los flows y los **paywalls creados con el nuevo Paywall Builder** renderizados por Adapty. El proceso es diferente para los paywalls de Remote Config y el [modo Observer](observer-vs-full-mode). - Para presentar **paywalls de Remote Config**, consulta [Renderizar paywall diseñado con Remote Config](present-remote-config-paywalls-kmp). - Para presentar flows en **modo Observer**, consulta [Presentar flows en modo Observer](kmp-present-flows-in-observer-mode). ::: Para obtener el objeto `flow` que se usa a continuación, consulta [Obtener flows y paywalls](kmp-get-pb-paywalls). El SDK de Adapty Kotlin Multiplatform ofrece dos formas de presentar flows y paywalls: - **Con Compose Multiplatform** - **Sin Compose Multiplatform** ## Con Compose Multiplatform \{#with-compose-multiplatform\} Para mostrar un flow o paywall, usa el método `view.present()` en el `view` creado por el método [`createFlowView`](kmp-get-pb-paywalls#fetch-the-view-configuration). Cada `view` solo puede usarse una vez. Si necesitas mostrar el flow de nuevo, llama a `createFlowView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo puede provocar un error. ::: ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { AdaptyUI.createFlowView(flow = flow).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ### Mostrar diálogo \{#show-dialog\} Usa este método en lugar de los diálogos de alerta nativos cuando se muestre un flow o paywall en Android. En Android, las alertas normales aparecen detrás de la vista del flow, lo que las hace invisibles para los usuarios. Este método garantiza que el diálogo se muestre correctamente por encima del flow en todas las plataformas. ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { view.showDialog( title = "Close this screen?", content = "You will lose access to exclusive offers.", primaryActionTitle = "Stay", secondaryActionTitle = "Close" ).onSuccess { action -> if (action == AdaptyUIDialogActionType.SECONDARY) { // User confirmed - close the flow view.dismiss() } // If primary - do nothing, user stays }.onError { error -> // handle the error } } ``` ### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el flow o el paywall en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `AdaptyUIIOSPresentationStyle.FULLSCREEN` (predeterminado) o `AdaptyUIIOSPresentationStyle.PAGESHEET`. ```kotlin showLineNumbers viewModelScope.launch { val view = AdaptyUI.createFlowView(flow = flow).getOrNull() view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET) } ``` ## Sin Compose Multiplatform \{#without-compose-multiplatform\} :::note `createNativeFlowView` forma parte del módulo principal `io.adapty:adapty-kmp`. Si tu proyecto no usa Compose Multiplatform, no necesitas la dependencia `io.adapty:adapty-kmp-ui`. ::: Para incrustar un flow o paywall sin Compose Multiplatform, llama a `createNativeFlowView`. Devuelve un `AdaptyNativeFlowView` que puedes añadir a tu layout: <Tabs> <TabItem value="android" label="Android"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val nativeView = AdaptyUI.createNativeFlowView( context = context, viewModelStoreOwner = activity, flow = flow, observer = myFlowObserver, ) // Embed in your Compose layout: AndroidView( factory = { nativeView.view }, modifier = Modifier.fillMaxSize() ) ``` Por defecto, una vista embebida no aplica los rellenos de área segura — se espera que tu layout gestione los insets. Si quieres que la vista los aplique por sí misma, pasa `androidEnableSafeArea = true` a `createNativeFlowView`. Este parámetro es exclusivo de Android. </TabItem> <TabItem value="ios" label="iOS"> Debido a que los métodos predeterminados de la interfaz KMP se convierten en `@required` en Swift, no puedes implementar `AdaptyUIFlowsEventsObserver` directamente desde Swift. Primero declara una clase base abierta en `iosMain`: ```kotlin showLineNumbers title="iosMain (Kotlin)" open class BaseFlowObserver : AdaptyUIFlowsEventsObserver ``` Luego, crea una subclase en Swift sobreescribiendo solo lo que necesites: ```swift showLineNumbers title="Swift" class MyFlowObserver: BaseFlowObserver { override func flowViewDidPerformAction(view: AdaptyUIFlowView, action: any AdaptyUIAction) { if action is AdaptyUIActionCloseAction { // remove nativeView from your view hierarchy } } } let nativeView = AdaptyUI.shared.createNativeFlowView( flow: flow, observer: MyFlowObserver() ) // nativeView.viewController is a UIViewController. // Add it to your SwiftUI view or UIKit hierarchy. ``` </TabItem> </Tabs> ### Eliminar la vista \{#dispose-the-view\} Llama a `dispose()` cuando quites la vista de tu layout. Esto cancela el registro del listener de eventos y libera los recursos internos. ```kotlin showLineNumbers title="Kotlin Multiplatform" nativeView.dispose() ``` ## Etiquetas personalizadas \{#custom-tags\} Las etiquetas personalizadas te permiten evitar crear flows o paywalls separados para distintos escenarios. Imagina un único flow que se adapta dinámicamente según los datos del usuario. Por ejemplo, en lugar de un genérico "¡Hola!", podrías saludar a los usuarios de forma personal con "¡Hola, Juan!" o "¡Hola, Ana!" Estos son algunos usos de las etiquetas personalizadas: - Mostrar el nombre o el correo electrónico del usuario en el flow o paywall. - Mostrar el día de la semana actual para impulsar las ventas (por ejemplo, "Feliz jueves"). - Añadir detalles personalizados sobre los productos que vendes (como el nombre de un programa de fitness o un número de teléfono en una app de VoIP). Las etiquetas personalizadas te ayudan a crear un flow flexible que se adapta a distintas situaciones, haciendo que la interfaz de tu app sea más personalizada y atractiva. :::warning En algunos casos, tu app puede no saber con qué reemplazar una etiqueta personalizada, especialmente si los usuarios tienen una versión antigua del SDK de AdaptyUI. Para evitarlo, añade siempre un texto de respaldo que sustituya las líneas que contengan etiquetas personalizadas desconocidas. Sin esto, los usuarios podrían ver las etiquetas mostradas como código (`<USERNAME/>`). ::: Para usar etiquetas personalizadas en tu flow o paywall, pásalas al crear la vista del flow: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) AdaptyUI.createFlowView( flow = flow, customTags = customTags ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativeFlowView( context = context, viewModelStoreOwner = activity, flow = flow, observer = myFlowObserver, customTags = customTags, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativeFlowView( flow = flow, observer = myFlowObserver, customTags = customTags, ) ``` </TabItem> </Tabs> ## Temporizadores personalizados \{#custom-timers\} El temporizador es una herramienta ideal para promocionar ofertas especiales y de temporada con límite de tiempo. Sin embargo, hay que tener en cuenta que este temporizador no está vinculado a la validez de la oferta ni a la duración de la campaña. Es simplemente una cuenta regresiva independiente que parte del valor que establezcas y disminuye hasta cero. Cuando el temporizador llega a cero, no ocurre nada: simplemente se queda en cero. Puedes personalizar el texto que aparece antes y después del temporizador para crear el mensaje que desees, por ejemplo: "La oferta termina en: 10:00 seg." Para usar temporizadores personalizados en tu flow o paywall, pásalos al crear la vista del flow: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) AdaptyUI.createFlowView( flow = flow, customTimers = customTimers ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativeFlowView( context = context, viewModelStoreOwner = activity, flow = flow, observer = myFlowObserver, customTimers = customTimers, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativeFlowView( flow = flow, observer = myFlowObserver, customTimers = customTimers, ) ``` </TabItem> </Tabs> </SDKv4> <SDKv3> Si has personalizado un paywall con el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Un paywall así ya incluye tanto qué mostrar como cómo mostrarlo. :::warning Esta guía es solo para **paywalls del nuevo Paywall Builder**. El proceso para presentar paywalls es diferente para los paywalls diseñados con Remote Config y el [modo Observer](observer-vs-full-mode). Para presentar **paywalls con Remote Config**, consulta [Renderizar un paywall diseñado con Remote Config](present-remote-config-paywalls-kmp). ::: El SDK de Adapty para Kotlin Multiplatform ofrece dos formas de mostrar paywalls: - **Con Compose Multiplatform** - **Sin Compose Multiplatform** ## Con Compose Multiplatform \{#with-compose-multiplatform\} Para mostrar un paywall, usa el método `view.present()` en el `view` creado por el método [`createPaywallView`](kmp-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Cada `view` solo puede usarse una vez. Si necesitas mostrar el paywall de nuevo, llama a `createPaywallView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo puede provocar un error. ::: ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { AdaptyUI.createPaywallView(paywall = paywall).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ### Mostrar diálogo \{#show-dialog\} Usa este método en lugar de los diálogos de alerta nativos cuando se muestra una vista de paywall en Android. En Android, las alertas normales aparecen detrás de la vista del paywall, lo que las hace invisibles para los usuarios. Este método garantiza que los diálogos se muestren correctamente por encima del paywall en todas las plataformas. ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { view.showDialog( title = "Close paywall?", content = "You will lose access to exclusive offers.", primaryActionTitle = "Stay", secondaryActionTitle = "Close" ).onSuccess { action -> if (action == AdaptyUIDialogActionType.SECONDARY) { // User confirmed - close the paywall view.dismiss() } // If primary - do nothing, user stays }.onError { error -> // handle the error } } ``` ### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el paywall en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `AdaptyUIIOSPresentationStyle.FULLSCREEN` (por defecto) o `AdaptyUIIOSPresentationStyle.PAGESHEET`. ```kotlin showLineNumbers viewModelScope.launch { val view = AdaptyUI.createPaywallView(paywall = paywall).getOrNull() view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET) } ``` ## Sin Compose Multiplatform \{#without-compose-multiplatform\} :::note `createNativePaywallView` forma parte del módulo principal `io.adapty:adapty-kmp`. Si tu proyecto no usa Compose Multiplatform, no necesitas la dependencia `io.adapty:adapty-kmp-ui`. ::: Para incrustar un paywall sin Compose Multiplatform, llama a `createNativePaywallView`. Devuelve un `AdaptyNativePaywallView` que añades a tu layout: <Tabs> <TabItem value="android" label="Android"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val nativeView = AdaptyUI.createNativePaywallView( context = context, viewModelStoreOwner = activity, paywall = paywall, observer = myPaywallObserver, ) // Embed in your Compose layout: AndroidView( factory = { nativeView.view }, modifier = Modifier.fillMaxSize() ) ``` </TabItem> <TabItem value="ios" label="iOS"> Debido a que los métodos predeterminados de la interfaz KMP se convierten en `@required` en Swift, no puedes implementar `AdaptyUIPaywallsEventsObserver` directamente desde Swift. Primero declara una clase base abierta en `iosMain`: ```kotlin showLineNumbers title="iosMain (Kotlin)" open class BasePaywallObserver : AdaptyUIPaywallsEventsObserver ``` Luego, en Swift, crea una subclase y sobreescribe solo lo que necesites: ```swift showLineNumbers title="Swift" class MyPaywallObserver: BasePaywallObserver { override func paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: any AdaptyUIAction) { if action is AdaptyUIActionCloseAction { // remove nativeView from your view hierarchy } } } let nativeView = AdaptyUI.shared.createNativePaywallView( paywall: paywall, observer: MyPaywallObserver() ) // nativeView.viewController is a UIViewController. // Add it to your SwiftUI view or UIKit hierarchy. ``` </TabItem> </Tabs> ### Eliminar la vista \{#dispose-the-view\} Llama a `dispose()` cuando quieras eliminar la vista de tu layout. Esto cancela el registro del listener de eventos y libera los recursos internos. ```kotlin showLineNumbers title="Kotlin Multiplatform" nativeView.dispose() ``` ## Etiquetas personalizadas \{#custom-tags\} Las etiquetas personalizadas te permiten evitar crear paywalls separados para distintos escenarios. Imagina un único paywall que se adapte dinámicamente según los datos del usuario. Por ejemplo, en lugar de un genérico "¡Hola!", podrías saludar a los usuarios de forma personal con "¡Hola, Juan!" o "¡Hola, Ana!" Aquí tienes algunas formas de usar las etiquetas personalizadas: - Mostrar el nombre o el correo electrónico del usuario en el paywall. - Mostrar el día de la semana actual para impulsar las ventas (p. ej., "Feliz jueves"). - Añadir detalles personalizados sobre los productos que vendes (como el nombre de un programa de fitness o un número de teléfono en una app de VoIP). Las etiquetas personalizadas te ayudan a crear un paywall flexible que se adapta a distintas situaciones, haciendo que la interfaz de tu app sea más personalizada y atractiva. :::warning En algunos casos, tu app podría no saber con qué reemplazar una etiqueta personalizada, especialmente si los usuarios tienen una versión antigua del SDK de AdaptyUI. Para evitarlo, añade siempre un texto de respaldo que sustituya las líneas que contengan etiquetas desconocidas. Sin esto, los usuarios podrían ver las etiquetas mostradas como código (`<USERNAME/>`). ::: Para usar etiquetas personalizadas en tu paywall, pásalas al crear la vista del paywall: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) AdaptyUI.createPaywallView( paywall = paywall, customTags = customTags ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativePaywallView( context = context, viewModelStoreOwner = activity, paywall = paywall, observer = myPaywallObserver, customTags = customTags, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativePaywallView( paywall = paywall, observer = myPaywallObserver, customTags = customTags, ) ``` </TabItem> </Tabs> ## Temporizadores personalizados \{#custom-timers\} El temporizador de un paywall es una herramienta estupenda para promocionar ofertas especiales y de temporada con límite de tiempo. Sin embargo, ten en cuenta que este temporizador no está vinculado a la validez de la oferta ni a la duración de la campaña. Es simplemente una cuenta regresiva independiente que comienza desde el valor que establezcas y disminuye hasta cero. Cuando llega a cero, no ocurre nada: simplemente se queda en cero. Puedes personalizar el texto antes y después del temporizador para crear el mensaje que desees, por ejemplo: "La oferta termina en: 10:00 seg." Para usar temporizadores personalizados en tu paywall, pásalos al crear la vista del paywall: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) AdaptyUI.createPaywallView( paywall = paywall, customTimers = customTimers ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativePaywallView( context = context, viewModelStoreOwner = activity, paywall = paywall, observer = myPaywallObserver, customTimers = customTimers, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativePaywallView( paywall = paywall, observer = myPaywallObserver, customTimers = customTimers, ) ``` </TabItem> </Tabs> </SDKv3> --- # File: kmp-handle-paywall-actions --- --- title: "Responder a acciones de flow - Kotlin Multiplatform" description: "Gestiona las acciones de botones de flows y paywalls en tu app de Kotlin Multiplatform." --- <SDKv4> Si estás construyendo flows o paywalls con el Flow Builder o el Paywall Builder de Adapty, es fundamental configurar los botones correctamente: 1. Añade un [botón en el builder](paywall-buttons) y asígnale una acción existente o crea un ID de acción personalizado. 2. Escribe código en tu app para gestionar cada acción que hayas asignado. Esta guía muestra cómo gestionar acciones personalizadas y existentes en tu código. :::warning **Solo las compras, restauraciones, cierres de flow/paywall y apertura de enlaces se gestionan automáticamente.** El resto de acciones de botón, como las acciones personalizadas, requieren una implementación adecuada en el código de la app. ::: ## Configurar el AdaptyUIFlowsEventsObserver \{#set-up-the-adaptyuiflowseventsObserver\} Para gestionar las acciones del flow, necesitas implementar la interfaz `AdaptyUIFlowsEventsObserver` y configurarla con `AdaptyUI.setFlowsEventsObserver()`. Esto debe hacerse al inicio del ciclo de vida de tu app, normalmente en tu actividad principal o en la inicialización de la app. ```kotlin // In your app initialization AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver()) ``` Todas las acciones de los botones llegan al callback `flowViewDidPerformAction(view, action)` como una clase sellada `AdaptyUIAction`: `CloseAction`, `AndroidSystemBackAction`, `OpenUrlAction` o `CustomAction`. :::warning Sobrescribir `flowViewDidPerformAction` reemplaza el comportamiento predeterminado de **todas** las acciones, no solo la que te interesa. Mantén las ramas por defecto para `CloseAction` (cerrar el flow) y `OpenUrlAction` (abrir la URL) a menos que quieras cambiarlas, como se muestra en los ejemplos a continuación. ::: ## Cerrar flows y paywalls \{#close-flows-and-paywalls\} Para añadir un botón que cierre tu flow o paywall: 1. En el builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un handler para la acción `close` que cierre el flow. :::info En el SDK de Kotlin Multiplatform, `CloseAction` activa el cierre del flow o paywall por defecto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un flow podría desencadenar la apertura de otro. ::: ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } // default behavior is AdaptyUIAction.OpenUrlAction -> AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior else -> Unit } } } // Set up the observer AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver()) ``` Si usas [`createNativeFlowView`](kmp-present-paywalls#without-compose-multiplatform), llamar a `view.dismiss()` no tiene ningún efecto, ya que la vista está integrada en tu layout y no se presenta a través del stack de KMP. En su lugar, elimina la vista de tu layout y llama a `dispose()` sobre ella. ## Gestiona el botón Atrás del sistema Android \{#handle-the-android-system-back-button\} Al pulsar el botón Atrás del sistema Android (o usar el gesto de retroceso) se emite `AdaptyUIAction.AndroidSystemBackAction`. Por defecto, esta acción se ignora: el flow permanece abierto y el usuario lo abandona por el camino que tú definas, como un botón **Close** o una acción `on_device_back` en el builder. Si quieres que el botón Atrás del sistema cierre el flow, gestiona la acción tú mismo: ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } // default behavior is AdaptyUIAction.AndroidSystemBackAction -> mainUiScope.launch { view.dismiss() } // not handled by default is AdaptyUIAction.OpenUrlAction -> AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior else -> Unit } } } ``` ## Abrir URLs desde flows y paywalls \{#open-urls-from-flows-and-paywalls\} :::tip Si quieres añadir un grupo de enlaces (por ejemplo, términos de uso y restauración de compras), añade un elemento **Link** en el builder y trátalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu flow o paywall (por ejemplo, **Terms of use** o **Privacy policy**), en el builder añade un botón, asígnale la acción **Open URL** e introduce la URL que quieres abrir. De forma predeterminada, el SDK abre la URL recibida de forma nativa — en un navegador externo o dentro de la app, según `action.openIn` — por lo que no se necesita ningún código. Sobrescribe el handler solo si quieres una lógica personalizada, como mostrar primero un diálogo de confirmación: ```kotlin class MyAdaptyUIFlowsEventsObserver( private val uriHandler: UriHandler ) : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.OpenUrlAction -> { // Show confirmation dialog before opening URL mainUiScope.launch { val selectedAction = view.showDialog( title = "Open URL?", content = action.url, primaryActionTitle = "Cancel", secondaryActionTitle = "Open" ).getOrNull() when (selectedAction) { AdaptyUIDialogActionType.PRIMARY -> { // User cancelled } AdaptyUIDialogActionType.SECONDARY -> { // User confirmed - open URL uriHandler.openUri(action.url) } else -> Unit } } } else -> Unit } } } // Set up the observer with UriHandler AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver(uriHandler)) ``` ## Iniciar sesión en la app \{#log-into-the-app\} Para añadir un botón que permita a los usuarios iniciar sesión en tu app: 1. En el builder, añade un botón y asígnale una acción **Custom** con el ID "login". 2. En el código de tu app, implementa un manejador para la acción personalizada que identifique a tu usuario. ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { if (action.action == "login") { // Handle login action - navigate to login screen // This depends on your app's navigation system // For example, in Compose Multiplatform: // navController.navigate("login") } } else -> Unit } } } ``` ## Manejar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el builder, añade un botón, asígnale la acción **Custom** y asígnale un ID. 2. En el código de tu app, implementa un manejador para el ID de acción que hayas creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro flow o paywall: ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { when (action.action) { "openNewFlow" -> { // Display another flow or paywall } } } else -> Unit } } } // Set up the observer AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver()) ``` </SDKv4> <SDKv3> :::warning **Solo las compras y restauraciones se gestionan automáticamente.** Todas las demás acciones de los botones, como cerrar paywalls o abrir enlaces, requieren implementar las respuestas correspondientes en el código de la app. ::: Si estás construyendo paywalls con el Paywall Builder de Adapty, es fundamental configurar los botones correctamente: 1. Añade un [botón en el Paywall Builder](paywall-buttons) y asígnale una acción predefinida o crea un ID de acción personalizado. 2. Escribe código en tu app para gestionar cada acción que hayas asignado. Esta guía muestra cómo gestionar acciones personalizadas y preexistentes en tu código. ## Configura el AdaptyUIPaywallsEventsObserver \{#set-up-the-adaptyuipaywallseventsobs-erver\} Para gestionar las acciones del paywall, necesitas implementar la interfaz `AdaptyUIPaywallsEventsObserver` y configurarla con `AdaptyUI.setPaywallsEventsObserver()`. Esto debe hacerse en una etapa temprana del ciclo de vida de tu app, normalmente en tu actividad principal o en la inicialización de la app. ```kotlin // In your app initialization AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` ## Cerrar paywalls \{#close-paywalls\} Para añadir un botón que cierre tu paywall: 1. En el Paywall Builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un manejador para la acción `close` que descarte el paywall. :::info En el SDK de Kotlin Multiplatform, `CloseAction` y `AndroidSystemBackAction` activan el cierre del paywall por defecto. Sin embargo, puedes sobreescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un paywall podría desencadenar la apertura de otro. ::: ```kotlin class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver { override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { when (action) { AdaptyUIAction.CloseAction, AdaptyUIAction.AndroidSystemBackAction -> view.dismiss() } } } // Set up the observer AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` Si usas [`createNativePaywallView`](kmp-present-paywalls#without-compose-multiplatform), llamar a `view.dismiss()` no tendrá ningún efecto: la vista está integrada en tu layout, no se presenta a través del stack de KMP. En su lugar, elimina la vista de tu layout y llama a `dispose()` sobre ella. ## Abrir URLs desde paywalls \{#open-urls-from-paywalls\} :::tip Si quieres añadir un grupo de enlaces (por ejemplo, términos de uso y restauración de compras), añade un elemento **Link** en el paywall builder y trátalo de la misma manera que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu paywall (por ejemplo, **Terms of use** o **Privacy policy**): 1. En el paywall builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieres abrir. 2. En el código de tu app, implementa un manejador para la acción `openUrl` que abra la URL recibida en un navegador. :::info En el SDK de Kotlin Multiplatform, `OpenUrlAction` proporciona la URL que debe abrirse. Puedes implementar lógica personalizada para gestionar la apertura de URLs, como mostrar un diálogo de confirmación o usar el método de manejo de URLs preferido de tu app. ::: ```kotlin class MyAdaptyUIPaywallsEventsObserver( private val uriHandler: UriHandler ) : AdaptyUIPaywallsEventsObserver { override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.OpenUrlAction -> { // Show confirmation dialog before opening URL mainUiScope.launch { val selectedAction = view.showDialog( title = "Open URL?", content = action.url, primaryActionTitle = "Cancel", secondaryActionTitle = "Open" ).getOrNull() when (selectedAction) { AdaptyUIDialogActionType.PRIMARY -> { // User cancelled } AdaptyUIDialogActionType.SECONDARY -> { // User confirmed - open URL uriHandler.openUri(action.url) } else -> Unit } } } } } } // Set up the observer with UriHandler AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver(uriHandler)) ``` ## Inicia sesión en la aplicación \{#log-into-the-app\} Para añadir un botón que permita a los usuarios iniciar sesión en tu aplicación: 1. En el Paywall Builder, añade un botón y asígnale una acción **Custom** con el ID "login". 2. En el código de tu aplicación, implementa un manejador para la acción personalizada que identifique a tu usuario. ```kotlin class MyAdaptyUIObserver : AdaptyUIObserver { override fun paywallViewDidPerformAction(view: AdaptyUIView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { if (action.action == "login") { // Handle login action - navigate to login screen // This depends on your app's navigation system // For example, in Compose Multiplatform: // navController.navigate("login") } } } } } ``` ## Gestionar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el Paywall Builder, añade un botón, asígnale la acción **Custom** y asígnale un ID. 2. En el código de tu app, implementa un manejador para el ID de acción que has creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro paywall: ```kotlin class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver { override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { when (action.action) { "login" -> { // Handle login action - navigate to login screen // This depends on your app's navigation system // For example, in Compose Multiplatform: // navController.navigate("login") } } } } } } // Set up the observer AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` </SDKv3> --- # File: kmp-handling-events --- --- title: "Manejar eventos de flow y paywall - Kotlin Multiplatform" description: "Maneja eventos de flow y paywall en tu app Kotlin Multiplatform." --- <SDKv4> :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de flows. También debes implementar el manejo de botones (cerrar el flow, abrir enlaces, etc.). Consulta nuestra [guía sobre el manejo de acciones de flow](kmp-handle-paywall-actions) para más detalles. ::: Los flows y paywalls configurados con el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder) no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan algunos eventos a los que tu app puede responder. Esos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras. Aprende a responder a estos eventos a continuación. Para controlar o monitorear los procesos que ocurren en la pantalla del flow dentro de tu app móvil, implementa los métodos de la interfaz `AdaptyUIFlowsEventsObserver` y registra tu observador con `AdaptyUI.setFlowsEventsObserver()`. Algunos métodos tienen implementaciones predeterminadas que gestionan automáticamente los escenarios más comunes, así que sobrescribe solo los métodos que quieras modificar: ```kotlin showLineNumbers title="Kotlin" AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { // override only the methods you want to change }) ``` :::note Estos métodos son donde añades tu lógica personalizada para responder a los eventos del flow. Puedes usar `view.dismiss()` para cerrar el flow, o implementar cualquier otro comportamiento personalizado que necesites. Ten en cuenta que `dismiss()` es una función suspend — dentro de un callback, ejecútala en el `mainUiScope` del observer: `mainUiScope.launch { view.dismiss() }`. ::: ### Eventos generados por el usuario \{#user-generated-events\} #### Aparición y desaparición del flow \{#flow-appearance-and-disappearance\} Cuando un flow aparece o desaparece, se invocarán estos métodos: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidAppear(view: AdaptyUIFlowView) { // Handle flow appearance // You can track analytics or update UI here } override fun flowViewDidDisappear(view: AdaptyUIFlowView) { // Handle flow disappearance // You can track analytics or update UI here } ``` :::note - En iOS, `flowViewDidAppear` también se invoca cuando el usuario toca el [botón de web paywall](web-paywall#step-2a-add-a-web-purchase-button) dentro de un flow, y se abre un web paywall en un navegador in-app. - En iOS, `flowViewDidDisappear` también se invoca cuando un [web paywall](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un flow en un navegador in-app desaparece de la pantalla. ::: <Details> <summary>Ejemplos de eventos (haz clic para expandir)</summary> ```javascript // Flow appeared { // No additional data } // Flow disappeared { // No additional data } ``` </Details> #### Selección de producto \{#product-selection\} Si el usuario selecciona un producto para comprar, se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidSelectProduct(view: AdaptyUIFlowView, productId: String) { // Handle product selection // You can update UI or track analytics here } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "productId": "premium_monthly" } ``` </Details> #### Compra iniciada \{#started-purchase\} Si el usuario inicia el proceso de compra, se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidStartPurchase(view: AdaptyUIFlowView, product: AdaptyPaywallProduct) { // Handle purchase start // You can show loading indicators or track analytics here } ``` :::note En el [modo observador](kmp-present-flows-in-observer-mode), las compras iniciadas desde un flow se entregan a tu `AdaptyUIObserverModeResolver` en lugar de aquí. ::: <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### Compra exitosa, cancelada o pendiente \{#successful-canceled-or-pending-purchase\} Este método se invoca cuando finaliza una compra. Por defecto no hace nada — el flow permanece abierto después de la compra hasta que lo cierres tú, así que llama a `view.dismiss()` una vez que el usuario obtenga acceso: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFinishPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { when (purchaseResult) { is AdaptyPurchaseResult.Success -> { // Check if user has access to premium features if (purchaseResult.profile.accessLevels["premium"]?.isActive == true) { mainUiScope.launch { view.dismiss() } } } AdaptyPurchaseResult.Pending -> { // Handle pending purchase (e.g., user will pay offline with cash) } AdaptyPurchaseResult.UserCanceled -> { // Handle user cancellation } } } ``` <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```javascript // Successful purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } } } // Pending purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Pending" } } // User canceled purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "UserCanceled" } } ``` </Details> Recomendamos cerrar la pantalla del flow en caso de compra exitosa. #### Compra fallida \{#failed-purchase\} Si una compra falla debido a un error, se invocará este método. Esto incluye errores de StoreKit/Google Play Billing (restricciones de pago, productos no válidos, fallos de red), errores de verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones del usuario activan `flowViewDidFinishPurchase` con un resultado cancelado en su lugar, y los pagos pendientes no activan este método. ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFailPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, error: AdaptyError ) { // Add your purchase failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Ejemplo de evento (Clic para expandir)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } ``` </Details> #### Restauración iniciada \{#started-restore\} Si un usuario inicia el proceso de restauración, se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidStartRestore(view: AdaptyUIFlowView) { // Handle restore start // You can show loading indicators or track analytics here } ``` #### Restauración exitosa \{#successful-restore\} Si la restauración de una compra se realiza correctamente, se invocará este método. Por defecto, no hace nada: el flow permanece abierto después de la restauración hasta que lo cierras: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFinishRestore(view: AdaptyUIFlowView, profile: AdaptyProfile) { // Add your successful restore handling logic here // For example: show success message, update UI, or dismiss the flow // Check if user has access to premium features if (profile.accessLevels["premium"]?.isActive == true) { mainUiScope.launch { view.dismiss() } } } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ``` </Details> Te recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de la suscripción](subscription-status) para aprender cómo comprobarlo. #### Restauración fallida \{#failed-restore\} Si `Adapty.restorePurchases()` falla, se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFailRestore(view: AdaptyUIFlowView, error: AdaptyError) { // Add your restore failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> #### Finalización de la navegación de pago web \{#web-payment-navigation-completion\} Si un usuario inicia el proceso de compra mediante un [paywall web](web-paywall), se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFinishWebPaymentNavigation( view: AdaptyUIFlowView, product: AdaptyPaywallProduct?, error: AdaptyError? ) { if (error != null) { // Handle web payment navigation error } else { // Handle successful web payment navigation } } ``` <Details> <summary>Ejemplos de eventos (haz clic para expandir)</summary> ```javascript // Successful web payment navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed web payment navigation { "product": null, "error": { "code": "web_payment_failed", "message": "Web payment navigation failed", "details": { "underlyingError": "Network connection error" } } } ``` </Details> ### Obtención y renderizado de datos \{#data-fetching-and-rendering\} #### Errores al cargar productos \{#product-loading-errors\} Si no proporcionas los productos durante la inicialización, AdaptyUI los recuperará del servidor por sí solo. Si esta operación falla, AdaptyUI notificará el error llamando a este método: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFailLoadingProducts(view: AdaptyUIFlowView, error: AdaptyError) { // Add your product loading failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> #### Errores de renderizado y en tiempo de ejecución \{#rendering-and-runtime-errors\} Si se produce un error durante el renderizado de la interfaz o cualquier otro error en tiempo de ejecución que no sea de compra, este método lo notificará. Por defecto, el flow se cierra al producirse un error — sobreescribe el método para mantenerlo abierto o añadir tu propia gestión: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidReceiveError(view: AdaptyUIFlowView, error: AdaptyError) { // Handle the error // The default implementation dismisses the flow; // once you override this method, dismissal is up to you } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render flow interface", "details": { "underlyingError": "Invalid flow configuration" } } } ``` </Details> En circunstancias normales, estos errores no deberían producirse, por lo que si te encuentras con alguno, por favor, comunícanoslo. #### Eventos de analíticas \{#analytics-events\} El callback `flowViewDidReceiveAnalyticEvent` está reservado para eventos analíticos personalizados de un flow. Los flows aún no emiten estos eventos a tu código, por lo que no necesitas implementarlo: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String ) { // Reserved for custom analytic events from a flow } ``` ### Navegación \{#navigation\} #### Botón Atrás del sistema Android \{#android-system-back-button\} Por defecto, un flow no puede cerrarse con el botón Atrás del sistema Android ni con el gesto de retroceso — la implementación predeterminada de `flowViewDidPerformAction` solo cierra el flow al recibir `CloseAction` e ignora `AndroidSystemBackAction`, de modo que el usuario abandona el flow por la ruta que tú definas, como un botón **Close** o una acción `on_device_back` en el builder. Si quieres que el botón Atrás del sistema cierre el flow, gestiona la acción tú mismo: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } // default behavior is AdaptyUIAction.AndroidSystemBackAction -> mainUiScope.launch { view.dismiss() } // not handled by default is AdaptyUIAction.OpenUrlAction -> AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior else -> Unit } } ``` Consulta la [guía sobre cómo gestionar las acciones de flow](kmp-handle-paywall-actions) para ver la lista completa de acciones. </SDKv4> <SDKv3> Los paywalls configurados con el [Paywall Builder](adapty-paywall-builder) no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan algunos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el paywall. A continuación encontrarás cómo responder a estos eventos. :::warning Esta guía es solo para paywalls creados con el **nuevo Paywall Builder**. ::: Para controlar o supervisar los procesos que ocurren en la pantalla del paywall dentro de tu aplicación móvil, implementa los métodos de la interfaz `AdaptyUIPaywallsEventsObserver`. Algunos métodos tienen implementaciones predeterminadas que gestionan automáticamente los escenarios más comunes. :::note En estos métodos es donde añades tu lógica personalizada para responder a los eventos del paywall. Puedes usar `view.dismiss()` para cerrar el paywall, o implementar cualquier otro comportamiento personalizado que necesites. ::: ## Eventos generados por el usuario \{#user-generated-events\} ### Aparición y desaparición del paywall \{#paywall-appearance-and-disappearance\} Cuando un paywall aparece o desaparece, se invocarán estos métodos: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidAppear(view: AdaptyUIPaywallView) { // Handle paywall appearance // You can track analytics or update UI here } override fun paywallViewDidDisappear(view: AdaptyUIPaywallView) { // Handle paywall disappearance // You can track analytics or update UI here } ``` :::note - En iOS, `paywallViewDidAppear` también se invoca cuando el usuario pulsa el [botón de paywall web](web-paywall#step-2a-add-a-web-purchase-button) dentro de un paywall y se abre un paywall web en un navegador in-app. - En iOS, `paywallViewDidDisappear` también se invoca cuando un [paywall web](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un paywall en un navegador in-app desaparece de la pantalla. ::: <Details> <summary>Ejemplos de eventos (haz clic para expandir)</summary> ```javascript // Paywall appeared { // No additional data } // Paywall disappeared { // No additional data } ``` </Details> ### Selección de producto \{#product-selection\} Si un usuario selecciona un producto para comprarlo, se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) { // Handle product selection // You can update UI or track analytics here } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```javascript { "productId": "premium_monthly" } ``` </Details> ### Inicio de compra \{#started-purchase\} Si un usuario inicia el proceso de compra, se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) { // Handle purchase start // You can show loading indicators or track analytics here } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> ### Compra exitosa, cancelada o pendiente \{#successful-canceled-or-pending-purchase\} Si una compra se realiza con éxito, se invocará este método. Por defecto, cierra automáticamente el paywall a menos que el usuario haya cancelado la compra: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFinishPurchase( view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { when (purchaseResult) { is AdaptyPurchaseResult.Success -> { // Check if user has access to premium features if (purchaseResult.profile.accessLevels["premium"]?.isActive == true) { view.dismiss() } } AdaptyPurchaseResult.Pending -> { // Handle pending purchase (e.g., user will pay offline with cash) } AdaptyPurchaseResult.UserCanceled -> { // Handle user cancellation } } } ``` <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```javascript // Successful purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } } } // Pending purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Pending" } } // User canceled purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "UserCanceled" } } ``` </Details> Recomendamos cerrar la pantalla del paywall en caso de compra exitosa. ### Compra fallida \{#failed-purchase\} Si una compra falla debido a un error, se invocará este método. Esto incluye errores de StoreKit/Google Play Billing (restricciones de pago, productos no válidos, fallos de red), errores de verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones del usuario activan `paywallViewDidFinishPurchase` con un resultado de cancelación, y los pagos pendientes no activan este método. ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailPurchase( view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, error: AdaptyError ) { // Add your purchase failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Ejemplo de evento (clic para expandir)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } ``` </Details> ### Restauración iniciada \{#started-restore\} Si un usuario inicia el proceso de restauración, se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidStartRestore(view: AdaptyUIPaywallView) { // Handle restore start // You can show loading indicators or track analytics here } ``` ### Restauración exitosa \{#successful-restore\} Si la restauración de una compra se realiza correctamente, se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFinishRestore(view: AdaptyUIPaywallView, profile: AdaptyProfile) { // Add your successful restore handling logic here // For example: show success message, update UI, or dismiss paywall // Check if user has access to premium features if (profile.accessLevels["premium"]?.isActive == true) { view.dismiss() } } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ``` </Details> Te recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el artículo [Estado de suscripción](subscription-status) para saber cómo comprobarlo. ### Error al restaurar \{#failed-restore\} Si `Adapty.restorePurchases()` falla, se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailRestore(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your restore failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> ### Finalización de la navegación del pago web \{#web-payment-navigation-completion\} Si un usuario inicia el proceso de compra mediante un [paywall web](web-paywall), se invocará este método: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFinishWebPaymentNavigation( view: AdaptyUIPaywallView, product: AdaptyPaywallProduct?, error: AdaptyError? ) { if (error != null) { // Handle web payment navigation error } else { // Handle successful web payment navigation } } ``` <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```javascript // Successful web payment navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed web payment navigation { "product": null, "error": { "code": "web_payment_failed", "message": "Web payment navigation failed", "details": { "underlyingError": "Network connection error" } } } ``` </Details> ## Obtención y renderizado de datos \{#data-fetching-and-rendering\} ### Errores de carga de productos \{#product-loading-errors\} Si no pasas los productos durante la inicialización, AdaptyUI los recuperará del servidor por su cuenta. Si esta operación falla, AdaptyUI notificará el error llamando a este método: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailLoadingProducts(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your product loading failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> ### Errores de renderizado \{#rendering-errors\} Si se produce un error durante el renderizado de la interfaz, este método lo notificará: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) { // Handle rendering error // In a normal situation, such errors should not occur // If you come across one, please let us know } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ``` </Details> En una situación normal, estos errores no deberían ocurrir, así que si te encuentras con uno, por favor, comunícanoslo. </SDKv3> --- # File: kmp-use-fallback-paywalls --- --- title: "Kotlin Multiplatform - Usar paywalls de respaldo" description: "Gestiona los casos en que los usuarios están sin conexión o los servidores de Adapty no están disponibles" --- Para mantener una experiencia de usuario fluida, es importante configurar [respaldos](/fallback-paywalls) para tus flows, [paywalls](paywalls) y [onboardings](onboardings). Esta precaución amplía las capacidades de la aplicación en caso de pérdida parcial o total de la conexión a internet. * **Si la aplicación no puede acceder a los servidores de Adapty:** Podrá mostrar un flow o paywall de respaldo, y acceder a la configuración local del onboarding. * **Si la aplicación no puede acceder a internet:** Podrá mostrar un flow o paywall de respaldo. Los onboardings incluyen contenido remoto y requieren conexión a internet para funcionar. :::important Antes de seguir los pasos de esta guía, [descarga](/local-fallback-paywalls) los archivos de configuración de respaldo desde Adapty. ::: ## Configuración \{#configuration\} 1. Añade el archivo de configuración de respaldo a tu aplicación. * Si tu plataforma de destino es Android, mueve el archivo de configuración de respaldo a la carpeta `android/app/src/main/assets/`. * Si tu plataforma de destino es iOS, añade el archivo JSON de respaldo al bundle de tu proyecto. (**File** -> **Add Files to YourProjectName**) 2. Llama al método `.setFallback` **antes** de obtener el flow, paywall u onboarding de destino. 3. Establece el parámetro `assetId` en función de tu plataforma de destino. * Android: Usa la ruta del archivo relativa al directorio `assets`. * iOS: Usa el nombre completo del archivo. ```kotlin showLineNumbers Adapty.setFallback(assetId = "fallback.json") .onSuccess { // Fallback paywalls loaded successfully } .onError { error -> // Handle the error } ``` :::important `setFallback` debe ejecutarse antes de que el SDK obtenga el flow, paywall u onboarding de destino. ::: Parámetros: | Parámetro | Descripción | | :---------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **assetId** | Nombre del archivo de configuración de respaldo (iOS). <br /> Ruta del archivo de configuración de respaldo, relativa al directorio `assets` (Android). | :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: --- # File: kmp-localizations-and-locale-codes --- --- title: "Usar localizaciones y códigos de idioma en el SDK de Kotlin Multiplatform" description: "Gestiona las localizaciones de la app y los códigos de idioma para llegar a una audiencia global en tu app de Kotlin Multiplatform." --- <SDKv4> ## Por qué esto es importante \{#why-this-is-important\} Los códigos de idioma entran en juego cuando Adapty selecciona la localización para un flow y cuando lees un Remote Config para un paywall personalizado. Los códigos de idioma son complejos y pueden variar de una plataforma a otra, por lo que Adapty se basa en un estándar interno único para todas las plataformas que admite. Entender ese estándar te ayuda a predecir qué localización recibirá un usuario. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código está formado por subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués de Brasil), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de idioma \{#locale-code-matching\} Cuando Adapty busca la localización que coincide con el idioma de un usuario, ocurre lo siguiente: 1. La cadena de idioma se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. Adapty busca la localización con el código de idioma exactamente coincidente 3. Si no se encuentra ninguna coincidencia, Adapty toma la subcadena antes del primer guion (`pt` para `pt-br`) y busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, Adapty devuelve la localización predeterminada `en` De esta forma, `'pt_BR'`, `pt-BR` y `pt-br` se resuelven en la misma localización. ## Implementación de localizaciones \{#implementing-localizations\} En el SDK v4, no es necesario pasar un código de idioma al obtener un flow. - **Paywalls del Flow Builder y del Paywall Builder**: Adapty resuelve la localización automáticamente a partir del dispositivo y las localizaciones que configuraste en el builder. Renderiza el flow con `createFlowView` — no se necesita ningún código de idioma. - **Paywalls personalizados (Remote Config)**: `getFlow` devuelve todas las localizaciones configuradas en `flow.remoteConfigs`. Cada entrada es un `AdaptyRemoteConfig` con un código `locale` y un `dataMap`. Selecciona la entrada que corresponda al usuario, con tu propio fallback: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> val config = flow.remoteConfigs.firstOrNull { it.locale == "en" } ?: flow.remoteConfigs.firstOrNull() // read your values from config?.dataMap } .onError { error -> // handle the error } ``` Las reglas de coincidencia de códigos de idioma descritas anteriormente explican cómo Adapty normaliza los códigos `locale` almacenados en cada Remote Config. </SDKv4> <SDKv3> ## Por qué esto es importante \{#why-this-is-important\} Hay algunos escenarios en los que los códigos de idioma entran en juego — por ejemplo, cuando intentas obtener el paywall correcto para la localización actual de tu app. Como los códigos de idioma son complicados y pueden variar de una plataforma a otra, nos basamos en un estándar interno para todas las plataformas que soportamos. Sin embargo, precisamente por esa complejidad, es muy importante que entiendas exactamente qué le estás enviando a nuestro servidor para obtener la localización correcta y qué ocurre después — así siempre recibirás lo que esperas. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código se compone de subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués (Brasil)), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de idioma \{#locale-code-matching\} Cuando Adapty recibe una llamada del SDK con el código de idioma y empieza a buscar la localización correspondiente de un paywall, ocurre lo siguiente: 1. La cadena de idioma recibida se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. Se busca la localización cuyo código de idioma coincida exactamente 3. Si no se encuentra ninguna coincidencia, se toma la subcadena antes del primer guión (`pt` en el caso de `pt-br`) y se busca la localización correspondiente 4. Si tampoco se encuentra coincidencia, se devuelve la localización predeterminada `en` De este modo, un dispositivo iOS que envió `'pt_BR'`, un dispositivo Android que envió `pt-BR`, y otro dispositivo que envió `pt-br` obtendrán el mismo resultado. ## Implementación de localizaciones: forma recomendada \{#implementing-localizations-recommended-way\} Si te estás preguntando sobre las localizaciones, lo más probable es que ya estés gestionando recursos de cadenas localizadas en tu proyecto. Si ese es el caso, te recomendamos incluir un par clave-valor con el código de locale de Adapty correspondiente en cada uno de tus archivos de recursos para las localizaciones que uses. Luego, extrae el valor de esa clave al llamar a nuestro SDK, así: ```kotlin showLineNumbers // 1. Add the Adapty locale code to your Compose Multiplatform resources /* composeResources/values/strings.xml (default — English) */ <string name="adapty_paywalls_locale">en</string> /* composeResources/values-es/strings.xml (Spanish) */ <string name="adapty_paywalls_locale">es</string> /* composeResources/values-pt-rBR/strings.xml (Portuguese — Brazil) */ <string name="adapty_paywalls_locale">pt-br</string> // 2. Extract and use the locale code suspend fun fetchPaywall() { val locale = getString(Res.string.adapty_paywalls_locale) Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = locale ).onSuccess { paywall -> // el paywall solicitado }.onError { error -> // gestionar el error } } ``` De este modo, tienes control total sobre qué localización se recuperará para cada usuario de tu app. Si no usas recursos de Compose Multiplatform, la misma idea aplica a cualquier biblioteca de localización que uses (por ejemplo, [moko-resources](https://github.com/icerockdev/moko-resources)): guarda el código de idioma de Adapty como una cadena en el bundle de recursos de cada idioma y léelo antes de llamar al SDK. ## Otra forma de implementar las localizaciones \{#implementing-localizations-the-other-way\} Puedes obtener resultados similares (aunque no idénticos) sin definir explícitamente códigos de idioma para cada localización. Esto implica extraer el código de idioma directamente del dispositivo, lo que requiere declaraciones `expect`/`actual`, ya que no existe una API de idioma compartida en `commonMain`: ```kotlin showLineNumbers // commonMain expect fun currentLocaleTag(): String // androidMain actual fun currentLocaleTag(): String = Locale.getDefault().toLanguageTag() // iosMain actual fun currentLocaleTag(): String = NSLocale.currentLocale.localeIdentifier // commonMain — pass the locale code to Adapty suspend fun fetchPaywall() { Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = currentLocaleTag() ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } } ``` Ten en cuenta que no recomendamos este enfoque por varias razones: 1. En iOS, el idioma preferido del usuario y el locale regional del dispositivo no son idénticos. `NSLocale.currentLocale.localeIdentifier` devuelve el locale regional, que puede diferir del idioma en que los usuarios leen tu app. Las apps iOS que usan archivos de cadenas localizadas dependen de la lógica de resolución de Apple para combinar ambos, lo que funciona de forma automática con el enfoque recomendado anteriormente. 2. Es difícil predecir exactamente qué devolverá el dispositivo y si coincide con una localización de Adapty. El locale del dispositivo puede incluir extensiones o códigos regionales que no hayas configurado en Adapty; en ese caso, el SDK recurre a la coincidencia del primer subtag o, en último término, a `en`. Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: kmp-web-paywalls --- --- title: "Implementar web paywalls en el SDK de Kotlin Multiplatform" description: "Configura un web paywall para recibir pagos sin las comisiones ni las revisiones de la store." --- :::important Antes de comenzar, asegúrate de haber [configurado tu web paywall en el dashboard](web-paywall) e instalado la versión 3.15 o posterior del SDK de Adapty. ::: ## Paywalls web abiertos \{#open-web-paywalls\} Si estás trabajando con un paywall que desarrollaste tú mismo, debes gestionar los paywalls web usando el método del SDK. El método `openWebPaywall`: 1. Genera una URL única que permite a Adapty vincular un paywall específico mostrado a un usuario concreto con la página web a la que es redirigido. 2. Detecta cuando tus usuarios regresan a la app y luego solicita `getProfile` a intervalos cortos para determinar si los derechos de acceso del perfil han sido actualizados. De esta forma, si el pago se ha realizado con éxito y los derechos de acceso han sido actualizados, la suscripción se activa en la app casi de inmediato. :::note Cuando los usuarios vuelvan a la app, actualiza la interfaz para reflejar los cambios del perfil. Adapty recibirá y procesará los eventos de actualización del perfil. ::: ```kotlin showLineNumbers viewModelScope.launch { Adapty.openWebPaywall(product = product).onSuccess { // the web paywall was opened successfully }.onError { error -> // handle the error } } ``` :::note Hay dos versiones del método `openWebPaywall`: 1. `openWebPaywall(product = product)` que genera URLs por paywall y también añade los datos del producto a las URLs. 2. `openWebPaywall(paywall = paywall)` que genera URLs por paywall sin añadir los datos del producto a las URLs. Úsalo cuando los productos de tu paywall en Adapty difieran de los del paywall web. En SDK v4, el parámetro `paywall` se reemplaza por un parámetro `flowPaywall` que recibe un `AdaptyFlowPaywall` — una de las variantes del paywall en `flow.paywalls`. Consulta la [guía de migración](migration-to-kmp-sdk-v4). ::: ## Abrir paywalls web en un navegador in-app \{#open-web-paywalls-in-an-in-app-browser\} Por defecto, los paywalls web se abren en el navegador externo. Para ofrecer una experiencia de usuario fluida, puedes abrirlos en un navegador in-app. Esto muestra la página de compra web dentro de tu aplicación, permitiendo a los usuarios completar las transacciones sin cambiar de app. Para activarlo, establece el parámetro `openIn` en `AdaptyWebPresentation.IN_APP_BROWSER`: ```kotlin showLineNumbers viewModelScope.launch { Adapty.openWebPaywall( product = product, openIn = AdaptyWebPresentation.IN_APP_BROWSER // default – EXTERNAL_BROWSER ).onSuccess { // the web paywall was opened successfully }.onError { error -> // handle the error } } ``` --- # File: kmp-present-flows-in-observer-mode --- --- title: "Presentar flows en modo Observer - Kotlin Multiplatform" description: "Presenta flows y paywalls de Paywall Builder en modo Observer en tu app Kotlin Multiplatform mientras gestionas las compras con tu propio código." --- Si has personalizado un flow o paywall con el builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Un flow o paywall de este tipo contiene tanto lo que debe mostrarse como la manera en que debe mostrarse. :::warning Esta sección hace referencia únicamente al [modo Observer](observer-vs-full-mode). Si no trabajas en el modo Observer, consulta el tema [Mostrar flows y paywalls](kmp-present-paywalls). ::: :::info Esta funcionalidad requiere el SDK de Adapty para Kotlin Multiplatform 4.0 (beta) o posterior — anteriormente solo estaba disponible en los SDKs nativos de iOS y Android. Consulta la [guía de migración](migration-to-kmp-sdk-v4) para actualizar. ::: <details> <summary>Antes de empezar a mostrar flows (Haz clic para expandir)</summary> 1. Configura la integración inicial de Adapty [con App Store](initial_ios) y [con Google Play](initial-android). 2. Instala y configura el SDK de Adapty. Asegúrate de llamar a `withObserverMode(true)` en el constructor de configuración. Consulta la [guía de instalación del SDK de Kotlin Multiplatform](sdk-installation-kotlin-multiplatform#activate-adapty-sdk). 3. [Crea productos](create-product) en el Adapty Dashboard. 4. [Configura flows o paywalls en los builders](create-paywall) y asígnales productos. 5. [Crea placements y asigna tus flows o paywalls a ellos](create-placement). 6. [Obtén flows y su configuración](kmp-get-pb-paywalls) en el código de tu app. </details> En el modo Observer, el SDK no realiza compras por ti. Cuando un usuario pulsa el botón de compra o restauración en un flow o paywall renderizado por Adapty, el SDK llama a tu `AdaptyUIObserverModeResolver` en su lugar — realiza la compra o restauración con tu propio código ahí. 1. Implementa la interfaz `AdaptyUIObserverModeResolver`: ```kotlin showLineNumbers import com.adapty.kmp.AdaptyUIObserverModeResolver import com.adapty.kmp.models.AdaptyPaywallProduct import com.adapty.kmp.models.AdaptyUIFlowView class MyObserverModeResolver : AdaptyUIObserverModeResolver { override fun observerModeDidInitiatePurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, onStartPurchase: () -> Unit, onFinishPurchase: () -> Unit ) { onStartPurchase() // the view shows its loading indicator // make the purchase with your own code, // then report the transaction to Adapty and call: onFinishPurchase() // the view hides the loading indicator } override fun observerModeDidInitiateRestore( view: AdaptyUIFlowView, onStartRestore: () -> Unit, onFinishRestore: () -> Unit ) { onStartRestore() // restore purchases with your own code, then: onFinishRestore() } } ``` El método `observerModeDidInitiatePurchase` te informa de que el usuario ha iniciado una compra, y `observerModeDidInitiateRestore`, de que el usuario ha iniciado una restauración. Activa tu flow de compra o restauración personalizado en respuesta. Además, recuerda invocar los siguientes callbacks para notificar a AdaptyUI sobre el proceso de compra o restauración. Esto es necesario para un comportamiento correcto del flow, como mostrar el indicador de carga, entre otras cosas: | Callback | Descripción | | :----------------- | :------------------------------------------------------------------------------------------------------- | | onStartPurchase() | El callback debe invocarse para notificar a AdaptyUI que la compra ha comenzado. | | onFinishPurchase() | El callback debe invocarse para notificar a AdaptyUI que la compra ha finalizado. | | onStartRestore() | El callback debe invocarse para notificar a AdaptyUI que la restauración ha comenzado. | | onFinishRestore() | El callback debe invocarse para notificar a AdaptyUI que la restauración ha finalizado. | El flow permanece abierto mientras se ejecuta tu código — ciérralo tú mismo tras una compra o restauración exitosa. 2. Registra el resolver antes de mostrar cualquier pantalla: ```kotlin showLineNumbers import com.adapty.kmp.AdaptyUI AdaptyUI.setObserverModeResolver(MyObserverModeResolver()) ``` Sin un resolver registrado, la vista del flow no tiene forma de transferir la compra a tu código, y no ocurre nada cuando el usuario pulsa el botón de compra. 3. Crea y presenta la vista del flow como de costumbre: [obtén el flow y crea su vista](kmp-get-pb-paywalls) y luego [preséntala](kmp-present-paywalls). No se necesitan parámetros adicionales — una vez que el resolver esté registrado, cada flow o paywall renderizado por Adapty enruta las compras y restauraciones a través de él. :::warning No olvides [reportar la transacción y asociarla con el paywall](report-transactions-observer-mode-kmp). De lo contrario, Adapty no reconocerá la transacción y no podrá determinar el paywall de origen de la compra. ::: --- # File: kmp-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del Paywall Builder en el SDK de Kotlin Multiplatform" description: "Solucionar problemas del Paywall Builder en el SDK de Kotlin Multiplatform" --- Esta guía te ayuda a resolver problemas comunes al usar paywalls diseñados en el Adapty Paywall Builder en el SDK de Kotlin Multiplatform. ## Fallo al obtener la configuración de un paywall \{#getting-a-paywall-configuration-fails\} **Problema**: El método `createPaywallView` falla al crear una vista de paywall, o el paywall no tiene configuración de vista. **Motivo**: El paywall no está habilitado para mostrarse en el dispositivo en el Paywall Builder. **Solución**: Activa el botón **Show on device** en el Paywall Builder. También puedes comprobar si un paywall tiene configuración de vista usando la propiedad `hasViewConfiguration` del objeto `AdaptyPaywall`. <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## El número de vistas del paywall es demasiado alto \{#the-paywall-view-number-is-too-big\} **Problema**: El contador de vistas del paywall muestra el doble del número esperado. **Motivo**: Es posible que estés llamando a `logShowPaywall` en tu código, lo que duplica el contador de vistas si estás usando el Paywall Builder. Para paywalls diseñados con el Paywall Builder, las analíticas se registran automáticamente, por lo que no es necesario usar este método. **Solución**: Asegúrate de no estar llamando a `logShowPaywall` en tu código si estás usando el Paywall Builder. --- # File: kmp-implement-paywalls-manually --- --- title: "Implementar paywalls manualmente en el SDK de Kotlin Multiplatform" description: "Aprende a implementar paywalls manualmente en tu app de Kotlin Multiplatform con el SDK de Adapty." --- ## Aceptar compras \{#accept-purchases\} Si trabajas con paywalls que has implementado tú mismo, puedes delegar el manejo de compras a Adapty usando el método `makePurchase`. De esta forma, Adapty se encargará de todos los escenarios del usuario y tú solo tendrás que gestionar los resultados de la compra. :::important `makePurchase` funciona con productos creados en el Adapty Dashboard. Asegúrate de configurar los productos y las formas de recuperarlos en el dashboard siguiendo la [guía de inicio rápido](quickstart). ::: <CustomDocCardList ids={['kmp-quickstart-manual', 'fetch-paywalls-and-products-kmp', 'present-remote-config-paywalls-kmp', 'kmp-making-purchases', 'kmp-restore-purchase', 'kmp-troubleshoot-purchases']} /> ## Modo observador \{#observer-mode\} Si quieres implementar tu propia lógica de manejo de compras desde cero, pero aun así quieres aprovechar la analítica avanzada de Adapty, puedes usar el modo observador. :::important Consulta las limitaciones del modo observador [aquí](observer-vs-full-mode). ::: <CustomDocCardList ids={['implement-observer-mode-kmp', 'report-transactions-observer-mode-kmp', 'kmp-troubleshoot-purchases']} /> --- # File: kmp-quickstart-manual --- --- title: "Habilitar compras en tu paywall personalizado en Kotlin Multiplatform SDK" description: "Integra el SDK de Adapty en tus paywalls personalizados de Kotlin Multiplatform para habilitar las compras in-app." --- Esta guía describe cómo integrar Adapty en tus paywalls personalizados. Mantén el control total sobre la implementación del paywall, mientras el SDK de Adapty obtiene productos, gestiona nuevas compras y restaura las anteriores. Esta guía utiliza las APIs del SDK Kotlin Multiplatform de Adapty v4 (beta) — si estás en v3, consulta la [guía de migración](migration-to-kmp-sdk-v4) para los nombres de métodos correspondientes. :::important **Esta guía es para desarrolladores que implementan paywalls personalizados.** Si quieres la forma más sencilla de habilitar compras, usa el [Adapty Flow Builder](kmp-quickstart-paywalls). Con Flow Builder, creas flows en un editor visual sin código, Adapty gestiona toda la lógica de compras automáticamente y puedes probar diferentes diseños sin volver a publicar tu app. ::: ## Antes de empezar ### Configurar productos \{#set-up-products\} Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls**](paywalls) – configuraciones que definen qué productos ofrecer. En Adapty, los paywalls son la única forma de obtener productos, pero este diseño te permite modificar productos, precios y ofertas sin tocar el código de tu app. En SDK v4, las variaciones de paywall para un placement las lleva un objeto **flow** — obtienes un flow y consultas sus productos. - [**Placements**](placements) – dónde y cuándo mostrar los paywalls en tu app (por ejemplo, `main`, `onboarding`, `settings`). Configuras los paywalls para los placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar diferentes paywalls a distintos usuarios. Asegúrate de entender estos conceptos aunque uses un paywall personalizado. Básicamente, son tu forma de gestionar los productos que vendes en tu app. Para implementar tu paywall personalizado, tendrás que crear un **paywall** y añadirlo a un **placement**. Esta configuración te permite recuperar tus productos. Para entender qué debes hacer en el dashboard, sigue la guía de inicio rápido [aquí](quickstart). ### Gestionar usuarios \{#manage-users\} Puedes trabajar con o sin autenticación del backend en tu lado. Sin embargo, el SDK gestiona de forma diferente a los usuarios anónimos e identificados. Lee la [guía de inicio rápido de identificación](kmp-quickstart-identify) para entender las particularidades y asegurarte de trabajar correctamente con los usuarios. ## Paso 1. Obtener productos \{#step-1-get-products\} Para recuperar los productos de tu paywall personalizado, necesitas: 1. Obtener el objeto `flow` pasando el ID del [placement](placements) al método `getFlow`. 2. Obtener el array de productos para este flow usando el método `getPaywallProducts`. ```kotlin showLineNumbers fun loadPaywall() { Adapty.getFlow(placementId = "YOUR_PLACEMENT_ID") .onSuccess { flow -> Adapty.getPaywallProducts(flow = flow) .onSuccess { products -> // Use products to build your custom paywall UI } .onError { error -> // Handle the error } } .onError { error -> // Handle the error } } ``` ## Paso 2. Aceptar compras \{#step-2-accept-purchases\} Cuando un usuario toca un producto en tu paywall personalizado, llama al método `makePurchase` con el producto seleccionado. Esto gestionará el flow de compra y devolverá el perfil actualizado. ```kotlin showLineNumbers fun purchaseProduct(product: AdaptyPaywallProduct) { Adapty.makePurchase(product = product) .onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // Purchase successful, profile updated } is AdaptyPurchaseResult.UserCanceled -> { // User canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Purchase is pending (e.g., user will pay offline with cash) } } } .onError { error -> // Handle the error } } ``` ## Paso 3. Restaurar compras \{#step-3-restore-purchases\} Los stores exigen que todas las apps con suscripciones ofrezcan una forma de restaurar las compras. Llama al método `restorePurchases` cuando el usuario pulse el botón de restaurar. Esto sincronizará su historial de compras con Adapty y devolverá el perfil actualizado. ```kotlin showLineNumbers fun restorePurchases() { Adapty.restorePurchases() .onSuccess { profile -> // Restore successful, profile updated } .onError { error -> // Handle the error } } ``` ## Paso 4. Comprueba el estado de la suscripción \{#step-4-check-the-subscription-status\} Tras una compra o restauración, comprueba el [nivel de acceso](access-level) del usuario para decidir si mostrar el paywall o desbloquear las funciones de pago. Los métodos `makePurchase` y `restorePurchases` ya devuelven el perfil actualizado; cuando necesites el estado actual en otro punto de la app, usa el método `getProfile`: ```kotlin showLineNumbers fun checkPremiumAccess() { Adapty.getProfile() .onSuccess { profile -> val hasPremiumAccess = profile.accessLevels["premium"]?.isActive == true // Grant access to paid features if hasPremiumAccess is true } .onError { error -> // Handle the error } } ``` Para más formas de verificar y monitorizar el estado de la suscripción, incluida la escucha de actualizaciones en tiempo real, consulta [Verificar el estado de la suscripción](kmp-check-subscription-status). ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox de App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. Para ver cómo funciona esto en una implementación lista para producción, consulta el [AppViewModel.kt](https://github.com/adaptyteam/AdaptySDK-KMP/blob/main/example/composeMultiplatformApp/composeApp/src/commonMain/kotlin/com/adapty/exampleapp/AppViewModel.kt) en nuestra app de ejemplo, que muestra el manejo de compras con gestión de errores y estado adecuados. --- # File: fetch-paywalls-and-products-kmp --- --- title: "Obtener paywalls y productos para paywalls de Remote Config en Kotlin Multiplatform SDK" description: "Obtén paywalls y productos en el SDK de Adapty para Kotlin Multiplatform y mejora la monetización de usuarios." --- <SDKv4> Antes de mostrar el Remote Config y los paywalls personalizados, debes obtener la información sobre ellos. Ten en cuenta que este tema hace referencia al Remote Config y a los paywalls personalizados. Para obtener orientación sobre cómo obtener flows o paywalls personalizados en el **Flow Builder** o el **Paywall Builder**, consulta [Obtener flows y paywalls](kmp-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener flows y productos en tu app móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow o paywall e incorpora los productos](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow o paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-kotlin-multiplatform) en tu app móvil. </details> ## Obtener información del flow \{#fetch-flow-information\} En Adapty, un [producto](product) combina productos tanto de App Store como de Google Play. Estos productos multiplataforma se integran en flows y paywalls, lo que te permite mostrarlos en placements específicos de tu app móvil. Para mostrar los productos, debes obtener un `AdaptyFlow` desde uno de tus [placements](placements) con el método `getFlow`. :::important **No escribas los IDs de producto en el código.** El único ID que debes incluir en el código es el ID del placement. Los flows se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un flow devuelve dos productos hoy y tres mañana, muéstralos todos sin modificar el código. ::: ```kotlin showLineNumbers Adapty.getFlow( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { flow -> // the requested flow }.onError { error -> // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | predeterminado: `AdaptyPaywallFetchPolicy.Default` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no reciban los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo irregular que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los flows y paywalls en dos capas: la caché de actualización periódica descrita anteriormente y los [paywalls de respaldo](kmp-use-fallback-paywalls). También usamos CDN para obtener flows y paywalls más rápido, y un servidor de respaldo independiente en caso de que el CDN sea inaccesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus flows y paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | predeterminado: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p></p><p>Ten en cuenta que en casos excepcionales este método puede agotar el tiempo de espera un poco más tarde de lo especificado en `loadTimeout`, ya que la operación puede estar compuesta de diferentes solicitudes internamente.</p> | ¡No escribas los IDs de producto en el código! Como los flows se configuran de forma remota, los productos disponibles, el número de productos y las ofertas especiales (como las pruebas gratuitas) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si en un primer momento recuperas 2 productos, tu app debería mostrar esos 2 productos. Sin embargo, si más adelante recuperas 3 productos, tu app debería mostrar los 3 sin necesidad de modificar el código. Lo único que tienes que escribir en el código es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Un objeto `AdaptyFlow` con: el identificador del flow, las variaciones de paywall (`paywalls` — cada una con sus propios identificadores de producto), una lista `remoteConfigs` (una entrada por locale configurado) y varias otras propiedades. Para obtener los productos del flow, llama a `getPaywallProducts(flow)`. | :::note En la v4, `getFlow` no tiene el parámetro `locale`. Cuando renderizas un flow con `createFlowView`, la localización se resuelve automáticamente. Para paywalls personalizados, todos los idiomas disponibles se devuelven juntos en `flow.remoteConfigs` — elige el idioma que coincida con el dispositivo del usuario o la configuración de tu app. Consulta [Localizaciones y códigos de idioma](kmp-localizations-and-locale-codes) para más detalles. ::: ## Obtener productos \{#fetch-products\} Una vez que tengas el flow, puedes consultar el array de productos que le corresponde: ```kotlin showLineNumbers Adapty.getPaywallProducts(flow).onSuccess { products -> // the requested products }.onError { error -> // handle the error } ``` Parámetros de respuesta: | Parameter | Description | | :-------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Lista de objetos [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y otras propiedades. | Al implementar tu propio diseño de flow, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/). A continuación se ilustran las propiedades más utilizadas; consulta el documento enlazado para obtener todos los detalles sobre las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país del store seleccionado por el usuario, no en el idioma del dispositivo. | | **Price** | Para mostrar el precio localizado, usa `product.price.localizedString`. Esta localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número con `product.price.amount`. El valor se proporciona en la moneda local. Para obtener el símbolo de moneda correspondiente, usa `product.price.currencySymbol`. | | **Subscription Period** | Para mostrar el período (p. ej. semana, mes, año, etc.), usa `product.subscriptionDetails?.localizedSubscriptionPeriod`. Esta localización se basa en la configuración regional del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscriptionDetails?.subscriptionPeriod`. Desde ahí puedes acceder al enum `unit` para conocer la unidad (es decir, DAY, WEEK, MONTH, YEAR o UNKNOWN). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral verías `MONTH` en la propiedad unit y `3` en la propiedad numberOfUnits. | | **Introductory Offer** | Para mostrar un badge u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscriptionDetails?.introductoryOfferPhases`. Es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase encontrarás las siguientes propiedades útiles:<br/>• `paymentMode`: un enum con los valores `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` y `UNKNOWN`. Las pruebas gratuitas son del tipo `FREE_TRIAL`.<br/>• `price`: el precio con descuento como número. Para las pruebas gratuitas, busca `0` aquí.<br/>• `localizedNumberOfPeriods`: una cadena localizada con la configuración regional del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días muestra `3 days` en este campo.<br/>• `subscriptionPeriod`: como alternativa, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma manera para las ofertas que lo descrito en la sección anterior.<br/>• `localizedSubscriptionPeriod`: un período de suscripción formateado del descuento para la configuración regional del usuario. | ## Acelera la obtención de flows con el flow de audiencia predeterminada \{#speed-up-flow-fetching-with-default-audience-flow\} Normalmente, los flows se obtienen casi de forma instantánea, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, cuando tienes numerosas audiencias y placements, y tus usuarios tienen una conexión a internet débil, obtener un flow puede tardar más de lo deseado. En esas situaciones, puede que quieras mostrar un flow predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar nada. Para abordar esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el flow mediante el método `getFlow`, tal como se detalla en la sección [Obtener información del flow](fetch-paywalls-and-products-kmp#fetch-flow-information) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar flows distintos según la versión de la app (la actual y las futuras), puedes encontrarte con dificultades. Tendrás que diseñar flows que sean compatibles con la versión actual (legacy) o asumir que los usuarios de esa versión podrían tener problemas con flows que no se renderizan correctamente. - **Pérdida de segmentación**: Todos los usuarios verán el mismo flow diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluyendo por países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas a cambio de una obtención de flows más rápida, usa el método `getFlowForDefaultAudience` como se indica a continuación. De lo contrario, usa `getFlow` descrito [anteriormente](fetch-paywalls-and-products-kmp#fetch-flow-information). ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default ).onSuccess { flow -> // the requested flow }.onError { error -> // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.Default` | <p>Por defecto, el SDK intentará cargar datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo irregular que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la aplicación y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | </SDKv4> <SDKv3> Antes de mostrar el Remote Config y los paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia al Remote Config y a los paywalls personalizados. Para obtener orientación sobre cómo obtener paywalls personalizados con Paywall Builder, consulta [Obtener paywalls de Paywall Builder y su configuración](kmp-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener paywalls y productos en tu aplicación móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en tu paywall](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-kotlin-multiplatform) en tu aplicación móvil. </details> ## Obtén la información del paywall \{#fetch-paywall-information\} En Adapty, un [producto](product) es una combinación de productos del App Store y Google Play. Estos productos multiplataforma se integran en paywalls, lo que te permite mostrarlos en placements específicos de tu aplicación móvil. Para mostrar los productos, necesitas obtener un [Paywall](paywalls) de uno de tus [placements](placements) con el método `getPaywall`. :::important **No fijes los IDs de producto en el código.** El único ID que debes incluir en el código es el ID del placement. Los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un paywall devuelve dos productos hoy y tres mañana, muéstralos todos sin necesidad de modificar el código. ::: ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto de una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p> | | **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.Default` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no recibir los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo irregular que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls en dos capas: la caché de actualización regular descrita anteriormente y los [paywalls de respaldo](kmp-use-fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN sea inaccesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p></p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo límite especificado en `loadTimeout`, ya que la operación puede estar compuesta de distintas peticiones internamente.</p> | ¡No codifiques los IDs de producto de forma fija! Dado que los paywalls se configuran de forma remota, los productos disponibles, el número de productos y las ofertas especiales (como los períodos de prueba gratuitos) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si inicialmente obtienes 2 productos, tu app debería mostrar esos 2 productos. Sin embargo, si más adelante obtienes 3 productos, tu app debería mostrar los 3 sin necesidad de cambios en el código. Lo único que tienes que codificar de forma fija es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Un objeto [`AdaptyPaywall`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall/) con: una lista de IDs de producto, el identificador del paywall, Remote Config y varias otras propiedades. | ## Obtener productos \{#fetch-products\} Una vez que tienes el paywall, puedes consultar el array de productos que le corresponde: ```kotlin showLineNumbers Adapty.getPaywallProducts(paywall).onSuccess { products -> // the requested products }.onError { error -> // handle the error } ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Lista de objetos [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y otras propiedades. | Al implementar tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/). A continuación se ilustran las propiedades más utilizadas, pero consulta el documento enlazado para obtener detalles completos sobre todas las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país de la store seleccionado por el usuario, no en el idioma del dispositivo. | | **Price** | Para mostrar el precio en formato localizado, usa `product.price.localizedString`. Esta localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número con `product.price.amount`. El valor se proporcionará en la moneda local. Para obtener el símbolo de moneda correspondiente, usa `product.price.currencySymbol`. | | **Subscription Period** | Para mostrar el período (p. ej., semana, mes, año, etc.), usa `product.subscriptionDetails?.localizedSubscriptionPeriod`. Esta localización se basa en la configuración regional del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscriptionDetails?.subscriptionPeriod`. Desde ahí puedes acceder al enum `unit` para conocer la duración (es decir, DAY, WEEK, MONTH, YEAR o UNKNOWN). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral verás `MONTH` en la propiedad unit y `3` en la propiedad numberOfUnits. | | **Introductory Offer** | Para mostrar una insignia u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscriptionDetails?.introductoryOfferPhases`. Es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase encontrarás las siguientes propiedades útiles:<br/>• `paymentMode`: un enum con los valores `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` y `UNKNOWN`. Las pruebas gratuitas serán del tipo `FREE_TRIAL`.<br/>• `price`: el precio con descuento como número. Para las pruebas gratuitas, busca `0` aquí.<br/>• `localizedNumberOfPeriods`: una cadena localizada según el idioma del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días muestra `3 days` en este campo.<br/>• `subscriptionPeriod`: alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma manera para las ofertas que en la sección anterior.<br/>• `localizedSubscriptionPeriod`: un período de suscripción formateado del descuento para la configuración regional del usuario. | ## Acelera la obtención del paywall con el paywall de audiencia predeterminada \{#speed-up-paywall-fetching-with-default-audience-paywall\} Normalmente, los paywalls se obtienen casi al instante, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, cuando tienes numerosas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, obtener un paywall puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ningún paywall. Para solucionar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall mediante el método `getPaywall`, tal como se detalla en la sección [Obtener información del paywall](fetch-paywalls-and-products-kmp#fetch-paywall-information) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes paywalls para distintas versiones de la app (actual y futuras), puedes encontrarte con dificultades. Tendrás que diseñar paywalls compatibles con la versión actual (heredada) o asumir que los usuarios con esa versión pueden tener problemas con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que implica perder la segmentación personalizada (incluyendo por países, atribución de marketing o atributos personalizados propios). Si estás dispuesto a aceptar estas desventajas a cambio de una obtención más rápida del paywall, usa el método `getPaywallForDefaultAudience` como se describe a continuación. De lo contrario, utiliza `getPaywall` descrito [anteriormente](fetch-paywalls-and-products-kmp#fetch-paywall-information). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>predeterminado: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p> | | **fetchPolicy** | predeterminado: `AdaptyPaywallFetchPolicy.Default` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la aplicación y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | </SDKv3> --- # File: present-remote-config-paywalls-kmp --- --- title: "Mostrar paywall diseñado con Remote Config en Kotlin Multiplatform SDK" description: "Descubre cómo presentar paywalls con Remote Config en el SDK de Kotlin Multiplatform de Adapty para personalizar la experiencia del usuario." --- <SDKv4> Si has personalizado un paywall mediante Remote Config, necesitarás implementar el renderizado en el código de tu aplicación móvil para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tienes el control sobre qué se incluye y cómo se ve tu paywall. Adapty proporciona un método para obtener la configuración remota, dándote la autonomía de mostrar tu paywall personalizado. ## Obtener la configuración remota del flow y mostrarla \{#get-flow-remote-config-and-present-it\} En la versión 4, un flow incluye una entrada `AdaptyRemoteConfig` por cada idioma configurado en la lista `remoteConfigs`. Elige el idioma que coincida con la preferencia del usuario y lee los valores que necesites. ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> val config = flow.remoteConfigs.firstOrNull { it.locale == "en" } ?: flow.remoteConfigs.firstOrNull() val headerText = config?.dataMap?.get("header_text") as? String // use the remote config values } .onError { error -> // handle the error } ``` En este punto, una vez que hayas recibido todos los valores necesarios, es momento de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a las distintas pantallas y orientaciones de los dispositivos móviles, ofreciendo una experiencia fluida y fácil de usar en todos ellos. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls-kmp#track-paywall-view-events) tal como se describe a continuación, para que los análisis de Adapty puedan capturar la información necesaria para los funnels y las pruebas A/B. ::: Una vez que hayas terminado de mostrar el paywall, continúa configurando el flow de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu flow. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](kmp-making-purchases). Te recomendamos [crear un paywall de respaldo llamado paywall de respaldo](kmp-use-fallback-paywalls). Este paywall de respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Rastrear eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus flows y paywalls. Aunque los datos de compras se recopilan automáticamente, registrar las visualizaciones requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un flow. Para registrar un evento de visualización, simplemente llama a `.logShowFlow(flow)` y se reflejará en tus métricas en los embudos y las pruebas A/B. :::important No es necesario llamar a `.logShowFlow(flow)` si estás mostrando flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder). En esos casos, Adapty registra las vistas automáticamente. ::: ```kotlin showLineNumbers Adapty.logShowFlow(flow) .onSuccess { // flow view logged successfully } .onError { error -> // handle the error } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :-------- | :------- |:-----------------------------------------------------------------| | **flow** | requerido | Un objeto `AdaptyFlow` obtenido a través de `Adapty.getFlow`. | </SDKv4> <SDKv3> Si has personalizado un paywall mediante Remote Config, tendrás que implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú decides qué incluir y cómo se muestra la vista de tu paywall. Proporcionamos un método para obtener la configuración remota, dándote la autonomía de mostrar tu paywall personalizado configurado mediante Remote Config. ## Obtener el Remote Config de un paywall y mostrarlo \{#get-paywall-remote-config-and-present-it\} Para obtener el Remote Config de un paywall, accede a la propiedad `remoteConfig` y extrae los valores que necesites. ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String // use the remote config values }.onError { error -> // handle the error } ``` En este punto, una vez que hayas recibido todos los valores necesarios, es momento de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a distintos tamaños de pantalla y orientaciones de dispositivos móviles, ofreciendo una experiencia fluida y fácil de usar en diferentes dispositivos. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls-kmp#track-paywall-view-events-1) como se describe a continuación, para que Adapty Analytics pueda capturar información para los funnels y las pruebas A/B. ::: Cuando hayas terminado de mostrar el paywall, continúa con la configuración del flujo de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu paywall. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](kmp-making-purchases). Te recomendamos [crear un paywall de respaldo llamado paywall de respaldo](kmp-use-fallback-paywalls). Este paywall se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Registrar eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus paywalls. Aunque los datos de compras se recopilan automáticamente, registrar las visualizaciones de paywalls requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un paywall. Para registrar un evento de visualización de paywall, simplemente llama a `.logShowPaywall(paywall)`, y aparecerá reflejado en las métricas de tu paywall en los embudos y pruebas A/B. :::important No es necesario llamar a `.logShowPaywall(paywall)` si estás mostrando paywalls creados en el [Paywall Builder](adapty-paywall-builder). ::: ```kotlin showLineNumbers Adapty.logShowPaywall(paywall = paywall) .onSuccess { // paywall view logged successfully } .onError { error -> // handle the error } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :-------- |:-------------------------------------------------------------------------------------------------------| | **paywall** | requerido | Un objeto [`AdaptyPaywall`](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-paywall/). | </SDKv3> --- # File: kmp-making-purchases --- --- title: "Realizar compras en aplicaciones móviles con el SDK de Kotlin Multiplatform" description: "Guía para gestionar compras in-app y suscripciones con Adapty." --- Mostrar paywalls en tu app móvil es un paso esencial para ofrecer a los usuarios acceso a contenido o servicios premium. Sin embargo, con solo mostrar estos paywalls es suficiente para admitir compras únicamente si usas [Paywall Builder](adapty-paywall-builder) para personalizar tus paywalls. Si no usas el Paywall Builder, debes usar un método independiente llamado `.makePurchase()` para completar una compra y desbloquear el contenido deseado. Este método es la puerta de entrada para que los usuarios interactúen con los paywalls y realicen las transacciones que quieran. Si tu paywall tiene una oferta promocional activa para el producto que un usuario intenta comprar, Adapty la aplicará automáticamente en el momento de la compra. :::warning Ten en cuenta que la oferta introductoria solo se aplicará automáticamente si usas los paywalls configurados con el Paywall Builder. En otros casos, deberás [verificar la elegibilidad del usuario para una oferta introductoria en iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Omitir este paso puede provocar que tu app sea rechazada durante la revisión. Además, podría llevar a cobrar el precio completo a usuarios que son elegibles para una oferta introductoria. ::: Asegúrate de haber completado la [configuración inicial](quickstart) sin saltarte ningún paso. Sin ella, no podemos validar las compras. ## Realizar una compra \{#make-purchase\} :::note **¿Usas el [Paywall Builder](adapty-paywall-builder)?** Las compras se procesan automáticamente; puedes saltarte este paso. **¿Buscas una guía paso a paso?** Consulta la [guía de inicio rápido](kmp-implement-paywalls-manually) para obtener instrucciones de implementación completas con todo el contexto. ::: ```kotlin showLineNumbers Adapty.makePurchase(product = product).onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // Grant access to the paid features } } is AdaptyPurchaseResult.UserCanceled -> { // Handle the case where the user canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Handle deferred purchases (e.g., the user will pay offline with cash) } } }.onError { error -> // Handle the error } ``` | Parámetro | Presencia | Descripción | | :---------- | :-------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------| | **Product** | required | Un objeto [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) obtenido del paywall. | Parámetros de respuesta: | Parámetro | Descripción | |---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Si la solicitud se ha completado correctamente, la respuesta contiene este objeto. Un objeto [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/) proporciona información completa sobre los niveles de acceso, suscripciones y compras únicas de un usuario dentro de la app.</p><p>Comprueba el estado del nivel de acceso para determinar si el usuario tiene el acceso requerido a la app.</p> | :::warning **Nota:** si todavía usas una versión de StoreKit de Apple inferior a v2.0 y una versión del SDK de Adapty inferior a v2.9.0, debes proporcionar el [secreto compartido de Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret) en su lugar. Apple ha declarado este método como obsoleto. ::: ## Cambiar la suscripción al realizar una compra \{#change-subscription-when-making-a-purchase\} Cuando un usuario elige una nueva suscripción en lugar de renovar la actual, el funcionamiento depende del store. En Google Play, la suscripción no se actualiza automáticamente. Tendrás que gestionar el cambio en el código de tu aplicación móvil como se describe a continuación. Para reemplazar la suscripción por otra en Android, llama al método `.makePurchase()` con el parámetro adicional: ```kotlin showLineNumbers val subscriptionUpdateParams = AdaptyAndroidSubscriptionUpdateParameters( oldSubVendorProductId = "old_subscription_product_id", replacementMode = AdaptyAndroidSubscriptionUpdateReplacementMode.CHARGE_FULL_PRICE ) val purchaseParams = AdaptyPurchaseParameters.Builder() .setSubscriptionUpdateParams(subscriptionUpdateParams) .build() Adapty.makePurchase( product = product, parameters = purchaseParams ).onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // successful cross-grade } is AdaptyPurchaseResult.UserCanceled -> { // user canceled the purchase flow } is AdaptyPurchaseResult.Pending -> { // the purchase has not been finished yet, e.g. user will pay offline by cash } } }.onError { error -> // Handle the error } ``` Parámetro de solicitud adicional: | Parámetro | Presencia | Descripción | |:---------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **parameters** | opcional | un objeto [`AdaptyAndroidSubscriptionUpdateParameters`](https://kmp.adapty.io/////adapty/com.adapty.kmp.models/-adapty-android-subscription-update-parameters/) pasado a través de [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). | Puedes leer más sobre suscripciones y modos de reemplazo en la documentación de Google Developer: - [Acerca de los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Recomendaciones de Google para los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Modo de reemplazo [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Nota: este método solo está disponible para actualizaciones de suscripción. No se admiten cambios a planes inferiores. - Modo de reemplazo [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Nota: el cambio de suscripción real solo se producirá cuando finalice el período de facturación de la suscripción actual. ## Canjear códigos de oferta en iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Sobre los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios concretos. A diferencia de las ofertas habituales, que se aplican de forma automática, los códigos de oferta se distribuyen fuera de la app — por email, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, accediendo a una URL de canje o a través de un diálogo dentro de la app. Para configurar códigos de oferta, abre una suscripción en App Store Connect y ve a su sección **Offer Codes**. Puedes crear [tres tipos](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) de códigos de oferta: - **Free** — la suscripción es gratuita durante un período determinado y la siguiente renovación se cobra al precio completo. - **Pay as you go** — el usuario paga un precio reducido en cada ciclo de facturación durante un período determinado y, después, la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único reducido por toda la duración de la oferta y, después, la suscripción se renueva al precio completo. No es necesario añadir los códigos de oferta a Adapty. Apple etiqueta cada transacción durante el período de la oferta con la categoría del código de oferta. Esto incluye el canje inicial y todas las renovaciones con descuento posteriores. Adapty detecta la etiqueta y registra cada transacción con la categoría de oferta `offer_code`. Cuando termina el período de oferta y la suscripción se renueva al precio completo, la etiqueta deja de estar presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Resolución de discrepancias en los ingresos \{#revenue-discrepancy-troubleshooting\} Si observas que una transacción con código de oferta aparece en Adapty al precio completo del producto en lugar del precio reducido, verifica lo siguiente en App Store Connect: - El código de oferta tiene los precios correctos configurados para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está configurado para el país o región específica del usuario. Apple envía el precio regional en la transacción. Si no hay ningún precio regional configurado para la oferta, Apple puede enviar el precio completo del producto. Puedes filtrar y verificar las transacciones con código de oferta en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds) mediante los filtros de tipo de oferta **Offer Code** y **Offer Discount Type**. #### Códigos promocionales heredados (obsoletos) \{#legacy-promo-codes-deprecated\} :::warning Apple dejó obsoletos los códigos promocionales para compras in-app en marzo de 2026. Los códigos de oferta los sustituyen con más funcionalidades: elegibilidad configurable, fechas de expiración y hasta 1 millón de códigos por trimestre. Si antes usabas códigos promocionales para compras in-app, migra a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app y versión) daban acceso gratuito a una suscripción. A diferencia de los códigos de oferta, Apple no incluía información de descuento en las transacciones con código promocional — enviaba el precio completo del producto en el recibo. Por ello, Adapty registraba estas transacciones al precio completo, lo que generaba discrepancias entre los análisis de Adapty y App Store Connect. Si ves transacciones históricas al precio completo que deberían haber sido gratuitas, es probable que provengan de códigos promocionales heredados. Como estos códigos ya están obsoletos, migra a los códigos de oferta para un seguimiento preciso de los ingresos. </Details> Para mostrar la pantalla de canje de códigos en tu app: ```kotlin showLineNumbers Adapty.presentCodeRedemptionSheet() .onSuccess { // code redemption sheet presented successfully } .onError { error -> // handle the error } ``` :::danger Según nuestras observaciones, la pantalla de canje de códigos de oferta puede no funcionar de forma fiable en algunas apps. Recomendamos redirigir al usuario directamente a la App Store. Para hacerlo, debes abrir la URL con el siguiente formato: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ## Gestionar planes prepago (Android) \{#manage-prepaid-plans-android\} Si los usuarios de tu app pueden comprar [planes prepago](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (por ejemplo, comprar una suscripción no renovable por varios meses), puedes habilitar [transacciones pendientes](https://developer.android.com/google/play/billing/subscriptions#pending) para los planes prepago. ```kotlin showLineNumbers Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withGoogleEnablePendingPrepaidPlans(true) .build() ).onSuccess { // successful activation }.onError { error -> // handle the error } ``` --- # File: kmp-restore-purchase --- --- title: "Restaurar compras en la app móvil con el SDK de Kotlin Multiplatform" description: "Aprende cómo restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- Restaurar compras es una función que permite a los usuarios recuperar el acceso a contenido comprado anteriormente, como suscripciones o compras in-app, sin que se les vuelva a cobrar. Esta función es especialmente útil para usuarios que pueden haber desinstalado y reinstalado la app, o que han cambiado a un nuevo dispositivo y quieren acceder a su contenido comprado previamente sin pagar de nuevo. :::note En los paywalls creados con [Paywall Builder](adapty-paywall-builder), las compras se restauran automáticamente sin necesidad de código adicional. Si ese es tu caso, puedes saltarte este paso. ::: Para restaurar una compra si no usas el [Paywall Builder](adapty-paywall-builder) para personalizar el paywall, llama al método `.restorePurchases()`: ```kotlin showLineNumbers Adapty.restorePurchases().onSuccess { profile -> if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // successful access restore } }.onError { error -> // handle the error } ``` Parámetros de respuesta: | Parámetro | Descripción | |---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Un objeto [`AdaptyProfile`](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-profile/). Este modelo contiene información sobre los niveles de acceso, suscripciones y compras no relacionadas con suscripciones.</p><p>Comprueba el **estado del nivel de acceso** para determinar si el usuario tiene acceso a la app.</p> | :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: --- # File: implement-observer-mode-kmp --- --- title: "Implementar el modo Observador en el SDK de Kotlin Multiplatform" description: "Implementa el modo observador en Adapty para rastrear los eventos de suscripción de los usuarios en el SDK de Kotlin Multiplatform." --- Si ya tienes tu propia infraestructura de compras y no estás listo para migrar completamente a Adapty, puedes explorar el [modo Observer](observer-vs-full-mode). En su forma básica, el modo Observer ofrece analíticas avanzadas e integración fluida con sistemas de atribución y analíticas. Si esto satisface tus necesidades, solo tienes que: 1. Activarlo al configurar el SDK estableciendo el parámetro `observerMode` en `true`. Sigue las instrucciones de configuración para [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform). 2. [Notificar las transacciones](report-transactions-observer-mode-kmp) desde tu infraestructura de compras existente a Adapty. :::tip En el SDK v4, también puedes mostrar flows y paywalls renderizados por Adapty en modo Observer: cuando el usuario pulsa el botón de compra o restauración, el SDK cede la acción a tu código para que puedas realizar la compra o restauración tú mismo. Consulta [Presentar flows en modo Observer](kmp-present-flows-in-observer-mode). ::: ## Configuración del modo Observer \{#observer-mode-setup\} Activa el modo Observer si gestionas las compras y el estado de la suscripción por tu cuenta y utilizas Adapty para enviar eventos de suscripción y analíticas. :::important Al ejecutarse en modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlas tú mismo. ::: ```kotlin showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withObserverMode(true) // default false .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised in observer mode") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } ``` Parámetros: | Parámetro | Descripción | | --------------------------- | ------------------------------------------------------------ | | observerMode | Un valor booleano que controla el [modo Observer](observer-vs-full-mode). El valor predeterminado es `false`. | ## Usar paywalls de Adapty en el modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar las paywalls y las funciones de pruebas A/B de Adapty, puedes hacerlo, pero requiere una configuración adicional en el modo Observer. Esto es lo que necesitas hacer además de los pasos anteriores: 1. Muestra los paywalls de la forma habitual para [paywalls con Remote Config](present-remote-config-paywalls-kmp). 3. [Asocia los paywalls](report-transactions-observer-mode-kmp) con las transacciones de compra. --- # File: report-transactions-observer-mode-kmp --- --- title: "Reportar transacciones en el modo Observer en el SDK de Kotlin Multiplatform" description: "Reporta transacciones de compra en el modo Observer de Adapty para obtener información sobre usuarios y seguimiento de ingresos en el SDK de Kotlin Multiplatform." --- En el modo Observer, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Necesitas reportar las transacciones desde tu app store. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` para reportar de forma explícita cada transacción y que Adapty pueda reconocerla. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el paywall que la originó, garantizando un análisis preciso del paywall. ```kotlin showLineNumbers Adapty.reportTransaction( transactionId = "your_transaction_id", variationId = paywall.variationId ).onSuccess { profile -> // Transaction reported successfully // profile contains updated user data }.onError { error -> // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | --------------- | ---------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | obligatorio | El ID de transacción de tu compra en el app store. Normalmente es el token de compra o el identificador de transacción devuelto por el store. | | variationId | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-paywall/). | --- # File: kmp-troubleshoot-purchases --- --- title: "Solucionar problemas de compras en el SDK de Kotlin Multiplatform" description: "Solucionar problemas de compras en el SDK de Kotlin Multiplatform" --- Esta guía te ayuda a resolver problemas comunes al implementar compras manualmente en el SDK de Kotlin Multiplatform. ## makePurchase se llama correctamente, pero el perfil no se actualiza \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Problema**: El método `makePurchase` se completa correctamente, pero el perfil del usuario y el estado de la suscripción no se actualizan en Adapty. **Causa**: Esto normalmente indica una configuración incompleta de Google Play Store. **Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android). ## makePurchase se invoca dos veces \{#makepurchase-is-invoked-twice\} **Problema**: El método `makePurchase` se está llamando varias veces para la misma compra. **Causa**: Esto suele ocurrir cuando el flujo de compra se activa varias veces debido a problemas de gestión del estado de la interfaz o por interacciones rápidas del usuario. **Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android). ## AdaptyError.cantMakePayments en el modo observador \{#adaptye-rror-cantmakepayments-in-observer-mode\} **Problema**: Estás recibiendo `AdaptyError.cantMakePayments` al usar `makePurchase` en el modo observador. **Causa**: En el modo observador, debes gestionar las compras por tu cuenta, no usar el método `makePurchase` de Adapty. **Solución**: Si usas `makePurchase` para las compras, desactiva el modo observador. Tienes que elegir entre usar `makePurchase` o gestionar las compras por tu cuenta en el modo observador. Consulta [Implementar el modo observador](implement-observer-mode-kmp) para más detalles. ## Error de Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Problema**: Estás recibiendo un error de facturación no disponible de Google Play Store. **Causa**: Este error no está relacionado con Adapty. Es un error de la biblioteca Google Play Billing que indica que la facturación no está disponible en el dispositivo. **Solución**: Este error no está relacionado con Adapty. Puedes consultarlo en la documentación de Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## No se encuentran makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Problema**: Estás teniendo problemas porque no se encuentran los `makePurchasesCompletionHandlers`. **Causa**: Esto suele estar relacionado con problemas en las pruebas en sandbox. **Solución**: Crea un nuevo usuario de sandbox e inténtalo de nuevo. Esto suele resolver los problemas con los manejadores de finalización de compras en sandbox. --- # File: kmp-user --- --- title: "Usuarios y acceso en Kotlin Multiplatform SDK" description: "Aprende a gestionar usuarios y niveles de acceso en tu app de Kotlin Multiplatform con el SDK de Adapty." --- Esta página contiene todas las guías para trabajar con usuarios y niveles de acceso en tu app de Kotlin Multiplatform. Elige el tema que necesites: - **[Identificar usuarios](kmp-identifying-users)** - Aprende a identificar usuarios en tu app - **[Actualizar datos de usuario](kmp-setting-user-attributes)** - Establece atributos de usuario y datos de perfil - **[Escuchar cambios en el estado de la suscripción](kmp-listen-subscription-changes)** - Monitoriza los cambios de suscripción en tiempo real - **[Modo Niños](kids-mode-kmp)** - Implementa el Modo Niños en tu app --- # File: kmp-identifying-users --- --- title: "Identificar usuarios en Kotlin Multiplatform SDK" description: "Identifica usuarios en Adapty para mejorar las experiencias de suscripción personalizadas." --- Adapty crea un ID de perfil interno para cada usuario. Sin embargo, si tienes tu propio sistema de autenticación, deberías establecer tu propio Customer User ID. Puedes encontrar usuarios por su Customer User ID en la sección [Perfiles](profiles-crm) y usarlo en la [API del lado del servidor](getting-started-with-server-side-api), que se enviará a todas las integraciones. ### Configurar el ID de usuario del cliente durante la configuración \{#setting-customer-user-id-on-configuration\} Si tienes un ID de usuario durante la configuración, pásalo como parámetro `customerUserId` al método `.activate()`: ```kotlin showLineNumbers Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("YOUR_USER_ID") .build() ).onSuccess { // successful activation }.onError { error -> // handle the error } } ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Configuración del ID de usuario tras la inicialización \{#setting-customer-user-id-after-configuration\} Si no tienes un ID de usuario en la configuración del SDK, puedes establecerlo más adelante en cualquier momento con el método `.identify()`. Los casos más habituales para usar este método son tras el registro o la autenticación, cuando el usuario pasa de ser anónimo a estar autenticado. ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID").onSuccess { // successful identify }.onError { error -> // handle the error } ``` Parámetros de la solicitud: - **Customer User ID** (obligatorio): un identificador de usuario de tipo string. :::warning Reenvío de datos significativos del usuario En algunos casos, como cuando un usuario vuelve a iniciar sesión en su cuenta, los servidores de Adapty ya tienen información sobre ese usuario. En estos escenarios, el SDK de Adapty cambiará automáticamente para trabajar con el nuevo usuario. Si enviaste algún dato al usuario anónimo, como atributos personalizados o atribuciones de redes de terceros, deberás reenviar esos datos para el usuario identificado. También es importante tener en cuenta que debes volver a solicitar todos los paywalls y productos después de identificar al usuario, ya que los datos del nuevo usuario pueden ser diferentes. ::: ### Cerrar sesión e iniciar sesión \{#logging-out-and-logging-in\} Puedes cerrar la sesión del usuario en cualquier momento llamando al método `.logout()`: ```kotlin showLineNumbers Adapty.logout().onSuccess { // successful logout }.onError { error -> // handle the error } ``` Después puedes iniciar sesión con el método `.identify()`. ## Asignar `appAccountToken` (iOS) \{#assign-appaccounttoken-ios\} [`iosAppAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) es un **UUID** que te permite vincular las transacciones del App Store con la identidad interna de tus usuarios. StoreKit asocia este token con cada transacción, de modo que tu backend puede relacionar los datos del App Store con tus usuarios. Usa un UUID estable generado por usuario y reutilízalo para la misma cuenta en todos los dispositivos. Esto garantiza que las compras y las notificaciones del App Store queden correctamente vinculadas. Puedes establecer el token de dos formas: durante la activación del SDK o al identificar al usuario. :::important Siempre debes pasar `iosAppAccountToken` junto con `customerUserId`. Si solo pasas el token, no se incluirá en la transacción. ::: ```kotlin showLineNumbers // Durante la configuración: Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId( id = "YOUR_USER_ID", iosAppAccountToken = "YOUR_IOS_APP_ACCOUNT_TOKEN" ) .build() ).onSuccess { // activación exitosa }.onError { error -> // manejar el error } // O al identificar usuarios Adapty.identify( customerUserId = "YOUR_USER_ID", iosAppAccountToken = "YOUR_IOS_APP_ACCOUNT_TOKEN" ).onSuccess { // identificación exitosa }.onError { error -> // manejar el error } ``` ## Establecer IDs de cuenta ofuscados (Android) \{#set-obfuscated-account-ids-android\} Google Play requiere IDs de cuenta ofuscados en ciertos casos de uso para mejorar la privacidad y seguridad del usuario. Estos IDs ayudan a Google Play a identificar las compras manteniendo el anonimato de la información del usuario, algo especialmente importante para la prevención de fraudes y los análisis. Es posible que necesites configurar estos IDs si tu aplicación maneja datos sensibles de usuarios o si debes cumplir con regulaciones de privacidad específicas. Los IDs ofuscados permiten a Google Play rastrear las compras sin exponer los identificadores reales de los usuarios. :::important Siempre debes pasar `androidObfuscatedAccountId` junto con `customerUserId`. Si solo pasas el ID de cuenta ofuscado, no se incluirá en la transacción. ::: ```kotlin showLineNumbers // Durante la configuración: Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId( id = "YOUR_USER_ID", androidObfuscatedAccountId = "YOUR_OBFUSCATED_ACCOUNT_ID" ) .build() ).onSuccess { // activación correcta }.onError { error -> // gestionar el error } // O al identificar usuarios Adapty.identify( customerUserId = "YOUR_USER_ID", androidObfuscatedAccountId = "YOUR_OBFUSCATED_ACCOUNT_ID" ).onSuccess { // identificación correcta }.onError { error -> // gestionar el error } ``` ## Detectar usuarios en varios dispositivos \{#detect-users-across-devices\} Cuando el SDK se activa, lee automáticamente los derechos existentes del usuario desde StoreKit (iOS) o Google Play Billing (Android) y los sincroniza con el backend de Adapty. Una suscripción activa aparece en el perfil de Adapty sin que la app llame a `restorePurchases`. Lo que **no** ocurre automáticamente es reconocer que un perfil en un dispositivo nuevo pertenece al mismo usuario que el perfil en el dispositivo original. Adapty relaciona perfiles por Customer User ID, así que la continuidad de identidad depende de lo que uses como CUID. **Lo que Adapty puede detectar entre dispositivos** | Tu configuración | Lo que Adapty detecta | Lo que debes hacer | | --- | --- | --- | | Customer User ID = `device_id` (sin login en la app) | El nuevo dispositivo obtiene un CUID diferente y, por tanto, un perfil diferente. La suscripción se sincroniza con el nuevo perfil mediante un evento **Access level updated**, pero `subscription_started` no se dispara — el nuevo perfil se trata como heredero de la compra original. Los análisis basados en `subscription_started` contarán de menos a los usuarios que vuelven. | Usa un ID de cuenta estable como Customer User ID para que un usuario que regresa coincida con el perfil existente en todos los dispositivos. | | Customer User ID = ID de cuenta estable (login en cada dispositivo) | El SDK sincroniza automáticamente la suscripción en `activate()`, e `identify()` relaciona el perfil existente por CUID. | No se necesita configuración adicional — tanto la identidad como la suscripción se resuelven automáticamente. | | Heredero de Apple Family Sharing | El miembro de la familia recibe la suscripción solo a través de un evento **Access level updated** — `subscription_started` no se dispara. | Escucha el evento **Access level updated**. Consulta [Apple Family Sharing](apple-family-sharing) para ver la matriz de eventos completa. | | Misma cuenta de Apple/Google, distintos usuarios dentro de la app | El primer perfil que registra la compra se convierte en el principal. Los perfiles posteriores ven la suscripción a través de una cadena de herederos, con un evento **Access level updated**. | Exige login y elige un [modo de compartición](sharing-paid-access-between-user-accounts) que se adapte a tu modelo. | **Restaurar compras en un dispositivo nuevo** Muestra un botón "Restaurar compras" iniciado por el usuario en tu paywall. Apple App Review (directriz 3.1.1) lo exige, y actúa como alternativa cuando la sincronización automática no cubre algún caso límite. El botón debe llamar a `restorePurchases` en tu SDK. No es necesario llamar a `restorePurchases` de forma programática al primer inicio para el uso normal — el SDK ya ejecuta el equivalente en `activate()`. Reserva las llamadas programáticas para forzar una verificación de recibo actualizada, por ejemplo al depurar un acceso que falta después de que `activate()` haya completado. --- # File: kmp-setting-user-attributes --- --- title: "Establecer atributos de usuario en el SDK de Kotlin Multiplatform" description: "Aprende cómo establecer atributos de usuario en Adapty para mejorar la segmentación de audiencias." --- Puedes establecer atributos opcionales como el correo electrónico, el número de teléfono, etc., en el usuario de tu aplicación. Luego puedes usar estos atributos para crear [segmentos](segments) de usuarios o simplemente verlos en el CRM. ### Establecer atributos de usuario \{#setting-user-attributes\} Para establecer atributos de usuario, llama al método `.updateProfile()`: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.FEMALE) .withBirthday(AdaptyProfile.Date(1970, 1, 3)) Adapty.updateProfile(builder.build()) .onSuccess { // profile updated successfully } .onError { error -> // handle the error } ``` Ten en cuenta que los atributos que hayas establecido previamente con el método `updateProfile` no se restablecerán. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Lista de claves permitidas \{#the-allowed-keys-list\} Las claves permitidas `<Key>` de `AdaptyProfileParameters.Builder` y sus valores `<Value>` se muestran a continuación: | Clave | Valor | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | Enum, los valores permitidos son: `AdaptyProfile.Gender.FEMALE`, `AdaptyProfile.Gender.MALE`, `AdaptyProfile.Gender.OTHER` | | birthday | Date | ### Atributos personalizados de usuario \{#custom-user-attributes\} Puedes definir tus propios atributos personalizados. Estos suelen estar relacionados con el uso de tu aplicación. Por ejemplo, en aplicaciones de fitness podrían ser el número de ejercicios por semana; en aplicaciones de aprendizaje de idiomas, el nivel de conocimiento del usuario, etc. Puedes usarlos en segmentos para crear paywalls y ofertas dirigidas, y también en análisis para determinar qué métricas de producto influyen más en los ingresos. ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() builder.withCustomAttribute("key1", "value1") ``` Para eliminar una clave existente, usa el método `.withRemovedCustomAttribute()`: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() builder.withRemovedCustomAttribute("key2") ``` En ocasiones necesitas saber qué atributos personalizados ya se han establecido anteriormente. Para ello, utiliza el campo `customAttributes` del objeto `AdaptyProfile`. :::warning Ten en cuenta que el valor de `customAttributes` puede estar desactualizado, ya que los atributos de usuario pueden enviarse desde distintos dispositivos en cualquier momento, por lo que los atributos en el servidor pueden haber cambiado desde la última sincronización. ::: ### Límites \{#limits\} - Hasta 30 atributos personalizados por usuario - Los nombres de clave tienen un máximo de 30 caracteres. El nombre de la clave puede incluir caracteres alfanuméricos y cualquiera de los siguientes: `_` `-` `.` - El valor puede ser una cadena de texto o un número flotante con un máximo de 50 caracteres. --- # File: kmp-listen-subscription-changes --- --- title: "Verificar el estado de la suscripción en el SDK de Kotlin Multiplatform" description: "Rastrea y gestiona el estado de la suscripción de usuarios en Adapty para mejorar la retención de clientes en tu app de Kotlin Multiplatform." --- Con Adapty, hacer seguimiento del estado de la suscripción es muy sencillo. No tienes que insertar manualmente IDs de productos en tu código. En su lugar, puedes confirmar fácilmente el estado de la suscripción de un usuario comprobando si tiene un [nivel de acceso](access-level) activo. Antes de empezar a verificar el estado de la suscripción, configura las [notificaciones de desarrollador en tiempo real (RTDN)](enable-real-time-developer-notifications-rtdn). ## Nivel de acceso y el objeto AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Los niveles de acceso son propiedades del objeto [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/). Te recomendamos obtener el perfil cuando tu app se inicie, por ejemplo al [identificar un usuario](android-identifying-users#setting-customer-user-id-on-configuration), y actualizarlo cada vez que se produzcan cambios. Así podrás usar el objeto de perfil sin tener que solicitarlo repetidamente. Para recibir notificaciones sobre actualizaciones del perfil, escucha los cambios tal como se describe en la sección [Escuchar actualizaciones del perfil, incluidos los niveles de acceso](android-listen-subscription-changes) a continuación. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Obtener el nivel de acceso desde el servidor \{#retrieving-the-access-level-from-the-server\} Para obtener el nivel de acceso desde el servidor, usa el método `.getProfile()`: ```kotlin showLineNumbers Adapty.getProfile().onSuccess { profile -> // check the access }.onError { error -> // handle the error } ``` Parámetros de respuesta: | Parámetro | Descripción | | --------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Profile | <p>Un objeto [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/). En general, solo tienes que comprobar el estado del nivel de acceso del perfil para determinar si el usuario tiene acceso premium a la app.</p><p></p><p>El método `.getProfile` proporciona el resultado más actualizado, ya que siempre intenta consultar la API. Si por alguna razón (por ejemplo, sin conexión a internet), el SDK de Adapty no puede obtener información del servidor, se devolverán los datos de la caché. También es importante tener en cuenta que el SDK de Adapty actualiza la caché de `AdaptyProfile` periódicamente para mantener esta información lo más actualizada posible.</p> | El método `.getProfile()` te proporciona el perfil de usuario desde el que puedes obtener el estado del nivel de acceso. Puedes tener varios niveles de acceso por app. Por ejemplo, si tienes una app de noticias y vendes suscripciones a distintos temas de forma independiente, puedes crear niveles de acceso "sports" y "science". Pero la mayoría de las veces solo necesitarás un nivel de acceso; en ese caso, puedes usar simplemente el nivel de acceso predeterminado "premium". A continuación se muestra un ejemplo para comprobar el nivel de acceso predeterminado "premium": ```kotlin showLineNumbers Adapty.getProfile().onSuccess { profile -> if (profile.accessLevels["premium"]?.isActive == true) { // grant access to premium features } }.onError { error -> // handle the error } ``` ### Escuchar actualizaciones del estado de la suscripción \{#listening-for-subscription-status-updates\} Cada vez que cambia la suscripción de un usuario, Adapty lanza un evento. Para recibir mensajes de Adapty, necesitas realizar una configuración adicional: ```kotlin showLineNumbers Adapty.setOnProfileUpdatedListener { profile -> // handle any changes to subscription state } ``` Adapty también lanza un evento al inicio de la aplicación. En ese caso, se pasará el estado de la suscripción almacenado en caché. ### Caché del estado de la suscripción \{#subscription-status-cache\} La caché implementada en el SDK de Adapty almacena el estado de la suscripción del perfil. Esto significa que, aunque el servidor no esté disponible, se puede acceder a los datos en caché para obtener información sobre el estado de la suscripción del perfil. Sin embargo, es importante tener en cuenta que no es posible solicitar datos directamente desde la caché. El SDK consulta periódicamente el servidor cada minuto para comprobar si hay actualizaciones o cambios relacionados con el perfil. Si hay alguna modificación, como nuevas transacciones u otras actualizaciones, se enviarán a los datos en caché para mantenerlos sincronizados con el servidor. --- # File: kmp-deal-with-att --- --- title: "Gestionar ATT en el SDK de Kotlin Multiplatform" description: "Comienza con Adapty en Kotlin Multiplatform para simplificar la configuración y gestión de suscripciones." --- Si tu aplicación usa el framework AppTrackingTransparency y muestra al usuario una solicitud de autorización de seguimiento, debes enviar el [estado de autorización](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) a Adapty. ```kotlin showLineNumbers val profileParameters = AdaptyProfileParameters.Builder() .withAttStatus(3) // 3 = ATTrackingManagerAuthorizationStatusAuthorized .build() Adapty.updateProfile(profileParameters) .onSuccess { // ATT status updated successfully } .onError { error -> // handle AdaptyError } ``` :::warning Te recomendamos encarecidamente que envíes este valor lo antes posible cuando cambie; solo así los datos se transmitirán a tiempo a las integraciones que hayas configurado. ::: --- # File: kids-mode-kmp --- --- title: "Modo para Niños en el SDK de Kotlin Multiplatform" description: "Activa fácilmente el Modo para Niños para cumplir con las políticas de Google. Sin GAID ni datos publicitarios recopilados en el SDK de Kotlin Multiplatform." --- Si tu aplicación de Kotlin Multiplatform está destinada a niños, debes seguir las políticas de [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Si usas el SDK de Adapty, unos pocos pasos sencillos te ayudarán a configurarlo para cumplir con estas políticas y superar las revisiones de la app store. ## ¿Qué se requiere? \{#whats-required\} Necesitas configurar el SDK de Adapty para deshabilitar la recopilación de: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [Dirección IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Además, te recomendamos usar el ID de usuario del cliente con cuidado. Un ID de usuario con el formato `<Nombre.Apellido>` se considerará definitivamente como recopilación de datos personales, al igual que el uso del correo electrónico. Para el Modo para Niños, la mejor práctica es usar identificadores aleatorios o anonimizados (por ejemplo, IDs hasheados o UUIDs generados por el dispositivo) para garantizar el cumplimiento. ## Habilitar el Modo para Niños \{#enabling-kids-mode\} ### Cambios en el Adapty Dashboard \{#updates-in-the-adapty-dashboard\} En el Adapty Dashboard, debes deshabilitar la recopilación de direcciones IP. Para ello, ve a [App settings](https://app.adapty.io/settings/general) y haz clic en **Disable IP address collection** dentro de **Collect users' IP address**. ### Cambios en el código de tu aplicación móvil \{#updates-in-your-mobile-app-code\} Para cumplir con las políticas, debes deshabilitar la recopilación del Android Advertising ID (AAID/GAID) y la dirección IP al inicializar el SDK de Adapty: ```kotlin showLineNumbers override fun onCreate() { super.onCreate() val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") // highlight-start .withGoogleAdvertisingIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised with privacy settings") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } } ``` --- # File: kmp-onboardings --- --- title: "Onboardings en el SDK de Kotlin Multiplatform" description: "Aprende a trabajar con onboardings en tu app de Kotlin Multiplatform con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](kmp-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](kmp-get-pb-paywalls) y [Mostrar flows y paywalls](kmp-present-paywalls) para empezar. ::: <CustomDocCardList /> --- # File: kmp-get-onboardings --- --- title: "Obtener onboardings en el SDK de Kotlin Multiplatform" description: "Aprende cómo recuperar onboardings en Adapty para Kotlin Multiplatform." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](kmp-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que te ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](kmp-get-pb-paywalls) y [Mostrar flows y paywalls](kmp-present-paywalls) para empezar. ::: Después de [diseñar la parte visual de tu onboarding](design-onboarding) con el builder en el Adapty Dashboard, puedes mostrarlo en tu app de Kotlin Multiplatform. El primer paso en este proceso es obtener el onboarding asociado con el placement y su configuración de vista, tal como se describe a continuación. Antes de comenzar, asegúrate de que: 1. Has instalado el [SDK de Adapty para Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) en su versión 3.15.0 o superior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). ## Obtener el onboarding \{#fetch-onboarding\} Cuando creas un [onboarding](onboardings) con nuestro editor sin código, se almacena como un contenedor con la configuración que tu app necesita obtener y mostrar. Este contenedor gestiona toda la experiencia: qué contenido aparece, cómo se presenta y cómo se procesan las interacciones del usuario (como respuestas a cuestionarios o entradas de formularios). El contenedor también registra automáticamente los eventos de analítica, por lo que no necesitas implementar un seguimiento de vistas por separado. Para el mejor rendimiento, obtén la configuración del onboarding con antelación para que las imágenes tengan tiempo suficiente de descargarse antes de mostrárselas a los usuarios. Para obtener un onboarding, utiliza el método `getOnboarding`: ```kotlin showLineNumbers Adapty.getOnboarding( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>predeterminado: `en`</p> | El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.<p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p> | | **fetchPolicy** | predeterminado: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de lo intermitente que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché de actualización periódica descrita anteriormente y los onboardings de respaldo. También usamos CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para asegurarte siempre la versión más reciente de tus onboardings, garantizando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | predeterminado: 5 seg | <p>Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede incluir distintas peticiones internamente.</p> | Parámetros de respuesta: | Parámetro | Descripción | |:----------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Un objeto [`AdaptyOnboarding`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-onboarding/) con: el identificador y la configuración del onboarding, Remote Config y otras propiedades. | ## Acelera la obtención del onboarding con el onboarding de audiencia predeterminada \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Normalmente, los onboardings se obtienen casi al instante, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, cuando tienes numerosas audiencias y onboardings, y tus usuarios tienen una conexión a internet débil, obtener un onboarding puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un onboarding predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ninguno. Para solucionar esto, puedes usar el método `getOnboardingForDefaultAudience`, que obtiene el onboarding del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el onboarding mediante el método `getOnboarding`, tal como se detalla en la sección [Obtener onboarding](#fetch-onboarding) anterior. :::warning Considera usar `getOnboarding` en lugar de `getOnboardingForDefaultAudience`, ya que este último tiene limitaciones importantes: - **Problemas de compatibilidad**: Puede ocasionar problemas al dar soporte a varias versiones de la app, ya que requiere diseños retrocompatibles o aceptar que las versiones anteriores podrían mostrarse incorrectamente. - **Sin personalización**: Solo muestra contenido para la audiencia "All Users", lo que elimina la segmentación por país, atribución o atributos personalizados. Si la velocidad de carga supera estos inconvenientes en tu caso de uso, utiliza `getOnboardingForDefaultAudience` como se muestra a continuación. De lo contrario, usa `getOnboarding` como se describe [arriba](#fetch-onboarding). ::: ```kotlin showLineNumbers Adapty.getOnboardingForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { paywall -> // el onboarding solicitado }.onError { error -> // gestiona el error } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.<br/>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que los usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra al reinstalar la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché con actualizaciones periódicas descrita anteriormente y los onboardings de respaldo. También usamos una CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que la CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus onboardings, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.</p> | --- # File: kmp-present-onboardings --- --- title: "Presentar onboardings en el SDK de Kotlin Multiplatform" description: "Aprende a presentar onboardings de forma efectiva para aumentar las conversiones." --- :::warning **Los onboardings están obsoletos en SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](kmp-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](kmp-get-pb-paywalls) y [Mostrar flows y paywalls](kmp-present-paywalls) para empezar. ::: Si has personalizado un onboarding con el builder, no necesitas preocuparte por renderizarlo en el código de tu app Kotlin Multiplatform para mostrárselo al usuario. Ese onboarding incluye tanto lo que debe mostrarse como la forma en que debe mostrarse. Antes de empezar, asegúrate de que: 1. Tienes instalado el [SDK de Adapty para Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) 3.16.1 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). Adapty Kotlin Multiplatform SDK ofrece dos formas de mostrar onboardings: - **Con Compose Multiplatform** - **Sin Compose Multiplatform** ## Con Compose Multiplatform \{#with-compose-multiplatform\} Para mostrar un onboarding, usa el método `view.present()` en el `view` creado por el método `createOnboardingView`. Cada `view` solo puede usarse una vez. Si necesitas mostrar el onboarding de nuevo, llama a `createOnboardingView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo puede provocar un error. ::: ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { AdaptyUI.createOnboardingView(onboarding = onboarding).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el onboarding en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `AdaptyUIIOSPresentationStyle.FULLSCREEN` (predeterminado) o `AdaptyUIIOSPresentationStyle.PAGESHEET`. ```kotlin showLineNumbers viewModelScope.launch { val view = AdaptyUI.createOnboardingView(onboarding = onboarding).getOrNull() view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET) } ``` ### Personalizar cómo se abren los enlaces en los onboardings \{#customize-how-links-open-in-onboardings\} Por defecto, los enlaces en los onboardings se abren en un navegador integrado en la app. Esto ofrece una experiencia fluida al mostrar las páginas web dentro de tu aplicación, sin que el usuario tenga que cambiar de app. Si prefieres que los enlaces se abran en un navegador externo, puedes personalizar este comportamiento estableciendo el parámetro `externalUrlsPresentation` en `AdaptyWebPresentation.EXTERNAL_BROWSER`: ```kotlin showLineNumbers viewModelScope.launch { AdaptyUI.createOnboardingView( onboarding = onboarding, externalUrlsPresentation = AdaptyWebPresentation.EXTERNAL_BROWSER // default – IN_APP_BROWSER ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ## Sin Compose Multiplatform \{#without-compose-multiplatform\} :::note `createNativeOnboardingView` forma parte del módulo principal `io.adapty:adapty-kmp`. Si tu proyecto no usa Compose Multiplatform, no necesitas la dependencia `io.adapty:adapty-kmp-ui`. ::: Para integrar un onboarding sin Compose Multiplatform, llama a `createNativeOnboardingView`. Devuelve un `AdaptyNativeOnboardingView` que puedes añadir a tu layout: <Tabs> <TabItem value="android" label="Android"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val nativeView = AdaptyUI.createNativeOnboardingView( context = context, viewModelStoreOwner = activity, onboarding = onboarding, observer = myOnboardingObserver, ) // Embed in your Compose layout: AndroidView( factory = { nativeView.view }, modifier = Modifier.fillMaxSize() ) ``` </TabItem> <TabItem value="ios" label="iOS"> Dado que los métodos por defecto de la interfaz KMP se convierten en `@required` en Swift, no puedes implementar `AdaptyUIOnboardingsEventsObserver` directamente desde Swift. Primero declara una clase base abierta en `iosMain`: ```kotlin showLineNumbers title="iosMain (Kotlin)" open class BaseOnboardingObserver : AdaptyUIOnboardingsEventsObserver ``` Luego crea una subclase en Swift, sobreescribiendo solo lo que necesites: ```swift showLineNumbers title="Swift" class MyOnboardingObserver: BaseOnboardingObserver { override func onboardingViewOnCloseAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { // remove nativeView from your view hierarchy } } let nativeView = AdaptyUI.shared.createNativeOnboardingView( onboarding: onboarding, observer: MyOnboardingObserver() ) // nativeView.viewController is a UIViewController. // Add it to your SwiftUI view or UIKit hierarchy. ``` </TabItem> </Tabs> ### Liberar la vista \{#dispose-the-view\} Llama a `dispose()` cuando elimines la vista de tu layout. Esto cancela el registro del listener de eventos y libera los recursos internos. ```kotlin showLineNumbers title="Kotlin Multiplatform" nativeView.dispose() ``` --- # File: kmp-handling-onboarding-events --- --- title: "Gestionar eventos de onboarding en el SDK de Kotlin Multiplatform" description: "Gestiona eventos relacionados con el onboarding en Kotlin Multiplatform usando Adapty." --- :::warning **Los onboardings están obsoletos en el SDK v4 y serán eliminados en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](kmp-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que te ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](kmp-get-pb-paywalls) y [Mostrar flows y paywalls](kmp-present-paywalls) para empezar. ::: Antes de comenzar, asegúrate de que: 1. Has instalado el [SDK de Adapty para Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) 3.15.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). Los onboardings configurados con el builder generan eventos a los que tu aplicación puede responder. A continuación se explica cómo hacerlo. ## Configurar el observador de eventos del onboarding \{#set-up-the-onboarding-event-observer\} Para gestionar los eventos del onboarding, necesitas implementar la interfaz `AdaptyUIOnboardingsEventsObserver` y configurarla con `AdaptyUI.setOnboardingsEventsObserver()`. Esto debe hacerse en una etapa temprana del ciclo de vida de tu app, normalmente en tu actividad principal o en la inicialización de la app. ```kotlin // In your app initialization AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` ## Acciones personalizadas \{#custom-actions\} En el builder, puedes añadir una acción **personalizada** a un botón y asignarle un ID. Luego, puedes usar ese ID en tu código y gestionarlo como una acción personalizada. <img src={require('./img/ios-events-1.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Por ejemplo, si un usuario pulsa un botón personalizado, como **Login** o **Allow notifications**, el método delegado `onCustomAction` se activará con el ID de acción del builder. Puedes crear tus propios IDs, como "allowNotifications". ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnCustomAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { when (actionId) { "openPaywall" -> { // Display paywall from onboarding // You would typically fetch and present a new paywall here mainUiScope.launch { // Example: Get paywall by placement ID // val paywallResult = Adapty.getPaywall("your_placement_id") // paywallResult.onSuccess { paywall -> // val paywallViewResult = AdaptyUI.createPaywallView(paywall) // paywallViewResult.onSuccess { paywallView -> // paywallView.present() // } // } } } "allowNotifications" -> { // Handle notification permissions } else -> { // Handle other custom actions } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ## Cierre del onboarding \{#closing-onboarding\} El onboarding se considera cerrado cuando el usuario pulsa un botón con la acción **Close** asignada. Debes gestionar qué ocurre cuando el usuario cierra el onboarding. Por ejemplo: :::important Debes gestionar qué ocurre cuando el usuario cierra el onboarding. Por ejemplo, debes dejar de mostrar el propio onboarding. ::: Si usas [`createNativeOnboardingView`](kmp-present-onboardings#without-compose-multiplatform), `view.isStandaloneView` es `false` — la implementación por defecto no llama a `view.dismiss()`. En su lugar, elimina la vista de tu layout y llama a `dispose()` en este callback. ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnCloseAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { // Dismiss the onboarding screen mainUiScope.launch { view.dismiss() } // Additional cleanup or navigation logic can be added here // For example, navigate back or show main app content } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ## Abrir un paywall \{#opening-a-paywall\} :::tip Gestiona este evento para abrir un paywall dentro del onboarding. Si quieres abrirlo después de que el onboarding se cierre, hay una forma más directa: gestiona [`onboardingViewOnCloseAction`](#closing-onboarding) y abre el paywall sin depender de los datos del evento. ::: La forma más sencilla de trabajar con paywalls en onboardings es hacer que el ID de acción coincida con el ID de placement del paywall. Así puedes usar el ID de placement para obtener y abrir el paywall directamente: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnPaywallAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { // Get the paywall using the placement ID from the action mainUiScope.launch { val paywallResult = Adapty.getPaywall(placementId = actionId) paywallResult.onSuccess { paywall -> val paywallViewResult = AdaptyUI.createPaywallView(paywall) paywallViewResult.onSuccess { paywallView -> paywallView.present() }.onError { error -> // handle the error } }.onError { error -> // handle the error } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ## Finalizar la carga del onboarding \{#finishing-loading-onboarding\} Cuando un onboarding termina de cargarse, se invocará este método: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewDidFinishLoading( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta ) { // Handle loading completion // You can add any initialization logic here } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ## Eventos de navegación \{#navigation-events\} El método `onboardingViewOnAnalyticsEvent` se llama cuando ocurren distintos eventos de analítica durante el flow de onboarding. El objeto `event` puede ser uno de los siguientes tipos: |Tipo | Descripción | |------------|-------------| | `AdaptyOnboardingsAnalyticsEventOnboardingStarted` | Cuando el onboarding se ha cargado | | `AdaptyOnboardingsAnalyticsEventScreenPresented` | Cuando se muestra cualquier pantalla | | `AdaptyOnboardingsAnalyticsEventScreenCompleted` | Cuando se completa una pantalla. Incluye `elementId` opcional (identificador del elemento completado) y `reply` opcional (respuesta del usuario). Se dispara cuando el usuario realiza cualquier acción para salir de la pantalla. | | `AdaptyOnboardingsAnalyticsEventSecondScreenPresented` | Cuando se muestra la segunda pantalla | | `AdaptyOnboardingsAnalyticsEventUserEmailCollected` | Se dispara cuando se recoge el correo electrónico del usuario a través del campo de entrada | | `AdaptyOnboardingsAnalyticsEventOnboardingCompleted` | Se dispara cuando un usuario llega a una pantalla con el ID `final`. Si necesitas este evento, asigna el ID `final` a la última pantalla. | | `AdaptyOnboardingsAnalyticsEventUnknown` | Para cualquier tipo de evento no reconocido. Incluye `name` (el nombre del evento desconocido) y `meta` (metadatos adicionales) | Cada evento incluye información `meta` con los siguientes campos: | Campo | Descripción | |------------|-------------| | `onboardingId` | Identificador único del flow de onboarding | | `screenClientId` | Identificador de la pantalla actual | | `screenIndex` | Posición de la pantalla actual en el flow | | `screensTotal` | Número total de pantallas en el flow | A continuación, un ejemplo de cómo usar los eventos de análisis para el seguimiento: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnAnalyticsEvent( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, event: AdaptyOnboardingsAnalyticsEvent ) { when (event) { is AdaptyOnboardingsAnalyticsEventOnboardingStarted -> { // Track onboarding start trackEvent("onboarding_started", event.meta) } is AdaptyOnboardingsAnalyticsEventScreenPresented -> { // Track screen presentation trackEvent("screen_presented", event.meta) } is AdaptyOnboardingsAnalyticsEventScreenCompleted -> { // Track screen completion with user response trackEvent("screen_completed", event.meta, event.elementId, event.reply) } is AdaptyOnboardingsAnalyticsEventOnboardingCompleted -> { // Track successful onboarding completion trackEvent("onboarding_completed", event.meta) } is AdaptyOnboardingsAnalyticsEventUnknown -> { // Handle unknown events trackEvent(event.name, event.meta) } // Handle other cases as needed } } private fun trackEvent(eventName: String, meta: AdaptyUIOnboardingMeta, elementId: String? = null, reply: String? = null) { // Implement your analytics tracking here // For example, send to your analytics service } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```javascript // OnboardingStarted { "meta": { "onboardingId": "onboarding_123", "screenClientId": "welcome_screen", "screenIndex": 0, "screensTotal": 4 } } // ScreenPresented { "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 4 } } // ScreenCompleted { "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 1, "screensTotal": 4 }, "elementId": "profile_form", "reply": "success" } // SecondScreenPresented { "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 1, "screensTotal": 4 } } // UserEmailCollected { "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 1, "screensTotal": 4 } } // OnboardingCompleted { "meta": { "onboardingId": "onboarding_123", "screenClientId": "final_screen", "screenIndex": 3, "screensTotal": 4 } } ``` </Details> --- # File: kmp-onboarding-input --- --- title: "Procesar datos de onboardings en el SDK de Kotlin Multiplatform" description: "Guarda y utiliza datos de onboardings en tu aplicación Kotlin Multiplatform con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](kmp-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del entorno WebView. Consulta [Obtener flows y paywalls](kmp-get-pb-paywalls) y [Mostrar flows y paywalls](kmp-present-paywalls) para empezar. ::: Cuando tus usuarios responden a una pregunta del quiz o introducen sus datos en un campo de entrada, se invocará el método `onboardingViewOnStateUpdatedAction`. Puedes guardar o procesar el tipo de campo en tu código. Por ejemplo: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnStateUpdatedAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, elementId: String, params: AdaptyOnboardingsStateUpdatedParams ) { // Store user preferences or responses when (params) { is AdaptyOnboardingsSelectParams -> { // Handle single selection val id = params.id val value = params.value val label = params.label AppLogger.d("Selected option: $label (id: $id, value: $value)") } is AdaptyOnboardingsMultiSelectParams -> { // Handle multiple selections } is AdaptyOnboardingsInputParams -> { // Handle text input } is AdaptyOnboardingsDatePickerParams -> { // Handle date selection } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Ejemplos de datos guardados (el formato puede diferir en tu implementación)</summary> ```javascript // Example of a saved select action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "preferences_screen", "screen_index": 1, "total_screens": 3 }, "action": { "element_id": "preference_selector", "element_type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 3 }, "action": { "element_id": "interests_selector", "element_type": "multi_select", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 0, "total_screens": 3 }, "action": { "element_id": "name_input", "element_type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 0, "total_screens": 3 }, "action": { "element_id": "birthday_picker", "element_type": "date_picker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## Casos de uso \{#use-cases\} ### Enriquecer perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Si quieres vincular inmediatamente los datos de entrada con el perfil de usuario y evitar pedirles la misma información dos veces, necesitas [actualizar el perfil de usuario](kmp-setting-user-attributes) con los datos de entrada al gestionar la acción. Por ejemplo, pides a los usuarios que introduzcan su nombre en el campo de texto con el ID `name`, y quieres establecer el valor de ese campo como nombre de pila del usuario. También les pides que introduzcan su correo electrónico en el campo `email`. En el código de tu app, puede verse así: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnStateUpdatedAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, elementId: String, params: AdaptyOnboardingsStateUpdatedParams ) { // Store user preferences or responses when (params) { is AdaptyOnboardingsInputParams -> { // Handle text input val builder = AdaptyProfileParameters.Builder() // Map elementId to appropriate profile field when (elementId) { "name" -> { when (val input = params.input) { is AdaptyOnboardingsTextInput -> { builder.withFirstName(input.value) } } } "email" -> { when (val input = params.input) { is AdaptyOnboardingsEmailInput -> { builder.withEmail(input.value) } } } } // Update profile asynchronously mainUiScope.launch { val profileParams = builder.build() val result = Adapty.updateProfile(profileParams) result.onSuccess { profile -> // Profile updated successfully AppLogger.d("Profile updated: ${profile.email}") }.onError { error -> // Handle the error AppLogger.e("Failed to update profile: ${error.message}") } } } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` ### Personalizar paywalls según las respuestas \{#customize-paywalls-based-on-answers\} Con los cuestionarios en los onboardings, también puedes personalizar los paywalls que muestras a los usuarios una vez que completan el onboarding. Por ejemplo, puedes preguntarles sobre su experiencia con el deporte y mostrar distintas CTAs y productos a diferentes grupos de usuarios. 1. [Añade un cuestionario](onboarding-quizzes) en el constructor de onboarding y asigna IDs significativos a sus opciones. 2. Gestiona las respuestas del cuestionario en función de sus IDs y [configura atributos personalizados](kmp-setting-user-attributes) para los usuarios. ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnStateUpdatedAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, elementId: String, params: AdaptyOnboardingsStateUpdatedParams ) { // Handle quiz responses and set custom attributes when (params) { is AdaptyOnboardingsSelectParams -> { // Handle quiz selection val builder = AdaptyProfileParameters.Builder() // Map quiz responses to custom attributes when (elementId) { "experience" -> { // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.withCustomAttribute("experience", params.value) } } // Update profile asynchronously mainUiScope.launch { val profileParams = builder.build() val result = Adapty.updateProfile(profileParams) result.onSuccess { profile -> // Profile updated successfully AppLogger.d("Custom attribute 'experience' set to: ${params.value}") }.onError { error -> // Handle the error AppLogger.e("Failed to update profile: ${error.message}") } } } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` 3. [Crea segmentos](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade [audiencias](audience) para cada segmento que hayas creado. 5. [Muestra un paywall](kmp-paywalls) para el placement en el código de tu app. Si tu onboarding tiene un botón que abre un paywall, implementa el código del paywall como [respuesta a la acción de ese botón](kmp-handling-onboarding-events#opening-a-paywall). --- # File: kmp-best-practices --- --- title: "Mejores prácticas en el SDK de Kotlin Multiplatform" description: "Patrones de referencia para integrar el SDK de Adapty en Kotlin Multiplatform: orden de llamadas, manejo de errores y otras reglas para entornos de producción." --- <CustomDocCardList /> --- # File: kmp-sdk-call-order --- --- title: "Orden de llamadas en el SDK de Kotlin Multiplatform" description: "Evita perder el acceso premium, la atribución faltante y errores intermitentes de activación llamando a los métodos del SDK de Adapty en el orden correcto." --- `Adapty.activate()` debe completarse antes de llamar a cualquier otro método del SDK de Adapty. Hasta que finalice, el SDK no tiene estado. Cualquier llamada realizada antes o en paralelo con `activate()` fallará con un error de activación. Consulta [Gestionar errores en el SDK de Kotlin Multiplatform](kmp-handle-errors). Si tu app autentica usuarios y recopilas un customer user ID después del lanzamiento, llama a `Adapty.identify()` en ese momento. No llames a métodos de acción del usuario hasta que `identify` haya completado. Las llamadas que compiten con él devuelven un error o recaen sobre el perfil anónimo creado en la activación. Cuando esto ocurre, la atribución, los IDs de MMP como `appsflyer_id` y la propiedad de la instalación no siempre se transfieren al perfil identificado. Si tu app no autentica usuarios, omite `identify` y sigue trabajando con el perfil anónimo. Los SDKs de MMP y analítica (AppsFlyer, Adjust, Branch, PostHog) siguen la misma regla. Inicialízalos primero y espera sus callbacks de UID antes de llamar a `Adapty.activate`. De lo contrario, el ID del MMP se asigna a un perfil anónimo temporal y no siempre se transfiere al perfil identificado. Para más detalles sobre AppsFlyer, consulta [AppsFlyer](appsflyer). ## El orden correcto \{#the-correct-order\} Tu camino depende de dos cosas: cuándo conoces el ID de usuario del cliente y si usas un MMP o un SDK de analíticas. - **Pasos 2 y 5**: Obligatorios para todas las apps. Activa el SDK y luego llama a los métodos del SDK. - **Pasos 1 y 3**: Necesarios solo si integras un MMP o un SDK de analíticas (AppsFlyer, Adjust, Branch, PostHog). - **Paso 4**: Necesario solo si tu app autentica usuarios y recoge el ID de usuario del cliente después del lanzamiento. Si tienes el ID de usuario en el momento del lanzamiento de la app, pásalo al `AdaptyConfig.Builder` antes de llamar a `activate()` (paso 2a). Con esta opción nunca se crea un perfil anónimo, por lo que el paso 4 es innecesario. | Paso | Llamada | Cuándo | Notas | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Inicializa tu MMP o SDK de analíticas (AppsFlyer, Adjust, PostHog, Branch) | Al lanzar la app, en primer lugar | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `Adapty.activate(configuration = AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | Al lanzar la app, después del paso 1, si tienes el customer user ID | Recomendado. No se crea ningún perfil anónimo. | | 2b | `Adapty.activate(configuration = AdaptyConfig.Builder("KEY").build())` sin `withCustomerUserId` | Al lanzar la app, después del paso 1, si no tienes el customer user ID (o nunca lo recopilas) | Adapty crea un perfil anónimo. | | 3 | `Adapty.setIntegrationIdentifier("appsflyer_id", uid)` para cada MMP | Después del paso 2, antes de cualquier llamada de acción del usuario | Necesario para que los IDs del MMP se asocien al perfil correcto. | | 4 | `Adapty.identify("YOUR_USER_ID").onSuccess { ... }.onError { ... }` | Después del paso 3 (o el paso 2 si no hay MMP), antes del paso 5 — solo en la ruta 2b con autenticación | Espera a `onSuccess` antes de cualquier llamada de acción del usuario. Las llamadas concurrentes durante `identify` pueden asociarse al perfil anónimo. | | 5 | `getPaywall` (`getFlow` en SDK v4), `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | Después del paso 4 si llamas a `identify`; en caso contrario, después del paso 3 (o el paso 2 si no hay MMP) | Estas llamadas necesitan un perfil estable. | :::important Omitir estos pasos provoca que los usuarios que regresan pierdan el acceso premium, que falte el `appsflyer_id` en los perfiles y que se devuelvan paywalls para la audiencia incorrecta. ::: ## Instalaciones web2app y de embudo web \{#web2app-and-web-funnel-installs\} Si los usuarios compran en un checkout web (Stripe, Paddle) e instalan la app nativa después, el primer `activate()` del dispositivo crea un nuevo perfil anónimo. Este perfil no está vinculado al perfil web. Si puedes resolver el customer user ID antes del lanzamiento de la app (desde tu flujo de autenticación o el referrer de instalación), pásalo directamente al `AdaptyConfig.Builder`. De lo contrario, la compra web será invisible en el dispositivo hasta que llames a `identify("YOUR_USER_ID")` y luego a `restorePurchases`. Para los metadatos que debes enviar con cada checkout web, consulta: - [Stripe](stripe) - [Paddle](paddle) --- # File: kmp-optimize-paywall-fetching --- --- title: "Optimizar la obtención de paywalls en Kotlin Multiplatform SDK" description: "Obtén paywalls de Adapty de forma fiable: temporización, caché y patrones de respaldo para Kotlin Multiplatform." --- Una obtención de paywall fiable en Kotlin Multiplatform hace tres cosas: renderiza rápido, devuelve el paywall orientado a la audiencia y tiene un respaldo elegante cuando la red es lenta. Las reglas a continuación cubren los patrones de temporización, caché y respaldo para lograrlo. :::tip Se asume que `Adapty.activate()` y `Adapty.identify()` ya se han resuelto. Consulta [Orden de llamadas en el SDK de Kotlin Multiplatform](kmp-sdk-call-order). ::: El consejo a continuación usa los nombres de métodos de v3. En SDK v4, `getPaywall` pasa a llamarse `getFlow` (consulta la [guía de migración](migration-to-kmp-sdk-v4)) — todas las reglas se aplican sin cambios. ## Reglas y advertencias \{#rules-and-pitfalls\} | Haz esto | No hagas esto | Por qué | |---|---|---| | Obtén el placement que estás a punto de mostrar. | No hagas prefetch de todos los placements a la vez al iniciar. | El prefetch masivo bloquea el hilo principal y produce una pantalla en negro durante el pico. | | Llama a `getPaywall` después de que la atribución haya tenido oportunidad de resolverse — por ejemplo, 1–2 segundos después de `activate` o cuando se dispare `setOnProfileUpdatedListener`. | No llames a `getPaywall` al arrancar la app. | La atribución aún no ha llegado. El paywall se resuelve con la audiencia por defecto y omite silenciosamente los segmentos y la personalización de ASA. | | Establece un `loadTimeout` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | No esperes `getPaywall` indefinidamente. | Sin un timeout, los usuarios con mala conectividad ven una pantalla en blanco hasta que la red responde — o simplemente cierran la app. | Consulta [Obtener paywalls y productos](fetch-paywalls-and-products-kmp) para la referencia de los parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado. ## Ajustar para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte público, regiones afectadas por enrutamiento): - Establece `fetchPolicy = AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` en cada obtención excepto la primera. - Configura un [paywall de respaldo](fallback-paywalls) para cada placement en el Adapty Dashboard. - Establece `loadTimeout` entre 3 y 5 segundos y acepta el respaldo cuando se agote el tiempo. - No condicionales la visualización del paywall a `Adapty.getProfile()`. Llama a `getPaywall` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: kmp-test --- --- title: "Prueba y lanzamiento en Kotlin Multiplatform SDK" description: "Aprende a comprobar el estado de las suscripciones en tu app de Kotlin Multiplatform con Adapty." --- Si ya has implementado el SDK de Adapty en tu app de Kotlin Multiplatform, querrás comprobar que todo está configurado correctamente y que las compras funcionan como se espera. Esto implica probar tanto la integración del SDK como el flujo de compra real con el entorno sandbox. ## Prueba tu app \{#test-your-app\} Para realizar pruebas exhaustivas de tus compras in-app, consulta nuestras guías de pruebas específicas por plataforma: [guía de pruebas de iOS](test-purchases-in-sandbox) y [guía de pruebas de Android](testing-on-android). ## Prepárate para el lanzamiento \{#prepare-for-release\} Antes de enviar tu app al store, sigue el [checklist de lanzamiento](release-checklist) para confirmar que: - La conexión con el store y las notificaciones del servidor están configuradas - Las compras se completan y se notifican a Adapty - El acceso se desbloquea y se restaura correctamente - Se cumplen los requisitos de privacidad y revisión --- # File: kmp-reference --- --- title: "Referencia del SDK de Kotlin Multiplatform" description: "Documentación de referencia del SDK de Adapty para Kotlin Multiplatform." --- Esta página contiene la documentación de referencia del SDK de Adapty para Kotlin Multiplatform. Elige el tema que necesites: - **[Modelos del SDK](https://kmp.adapty.io/adapty/)** - Modelos de datos y estructuras utilizados por el SDK - **[Gestionar errores](kmp-handle-errors)** - Manejo de errores y solución de problemas --- # File: kmp-handle-errors --- --- title: "Gestión de errores en el SDK de Kotlin Multiplatform" description: "Aprende a gestionar errores en tu app de Kotlin Multiplatform con Adapty." --- Esta página cubre la gestión de errores en el SDK de Adapty para Kotlin Multiplatform. ## Conceptos básicos de gestión de errores \{#error-handling-basics\} Todos los métodos del SDK de Adapty devuelven resultados que pueden ser éxito o error. Gestiona siempre ambos casos: <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // Handle success } is AdaptyResult.Error -> { val error = result.error // Handle error Log.e("Adapty", "Error: ${error.message}") } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // Handle success } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle error Log.e("Adapty", "Error: " + error.getMessage()); } }); ``` </TabItem> </Tabs> ## Códigos de error más comunes \{#common-error-codes\} | Código de error | Descripción | Solución | |------------|-------------|----------| | 1000 | No se encontraron IDs de producto | Revisa la configuración de productos en el dashboard | | 1001 | Error de red | Verifica la conexión a internet | | 1002 | Clave SDK inválida | Verifica tu clave SDK | | 1003 | No se pueden realizar pagos | El dispositivo no admite pagos | | 1004 | Producto no disponible | El producto no está configurado en el store | ## Gestión de errores específicos \{#handle-specific-errors\} ### Errores de red \{#network-errors\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers Adapty.getPaywall("main") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // Use paywall } is AdaptyResult.Error -> { val error = result.error when (error.code) { 1001 -> { // Network error - show offline message showOfflineMessage() } else -> { // Other errors showErrorMessage(error.message) } } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers Adapty.getPaywall("main", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success<AdaptyPaywall>) result).getValue(); // Use paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); switch (error.getCode()) { case 1001: // Network error - show offline message showOfflineMessage(); break; default: // Other errors showErrorMessage(error.getMessage()); break; } } }); ``` </TabItem> </Tabs> ### Errores de compra \{#purchase-errors\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers product.makePurchase { result -> when (result) { is AdaptyResult.Success -> { val purchase = result.value // Purchase successful showSuccessMessage() } is AdaptyResult.Error -> { val error = result.error when (error.code) { 1003 -> { // Can't make payments showPaymentNotAvailableMessage() } 1004 -> { // Product not available showProductNotAvailableMessage() } else -> { // Other purchase errors showPurchaseErrorMessage(error.message) } } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers product.makePurchase(result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchase purchase = ((AdaptyResult.Success<AdaptyPurchase>) result).getValue(); // Purchase successful showSuccessMessage(); } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); switch (error.getCode()) { case 1003: // Can't make payments showPaymentNotAvailableMessage(); break; case 1004: // Product not available showProductNotAvailableMessage(); break; default: // Other purchase errors showPurchaseErrorMessage(error.getMessage()); break; } } }); ``` </TabItem> </Tabs> ## Estrategias de recuperación ante errores \{#error-recovery-strategies\} ### Reintentar en errores de red \{#retry-on-network-errors\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers fun getPaywallWithRetry(placementId: String, maxRetries: Int = 3) { var retryCount = 0 fun attemptGetPaywall() { Adapty.getPaywall(placementId) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // Use paywall } is AdaptyResult.Error -> { val error = result.error if (error.code == 1001 && retryCount < maxRetries) { // Network error - retry retryCount++ Handler(Looper.getMainLooper()).postDelayed({ attemptGetPaywall() }, 1000 * retryCount) // Exponential backoff } else { // Max retries reached or other error showErrorMessage(error.message) } } } } } attemptGetPaywall() } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers public void getPaywallWithRetry(String placementId, int maxRetries) { AtomicInteger retryCount = new AtomicInteger(0); Runnable attemptGetPaywall = new Runnable() { @Override public void run() { Adapty.getPaywall(placementId, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success<AdaptyPaywall>) result).getValue(); // Use paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); if (error.getCode() == 1001 && retryCount.get() < maxRetries) { // Network error - retry retryCount.incrementAndGet(); new Handler(Looper.getMainLooper()).postDelayed(this, 1000 * retryCount.get()); } else { // Max retries reached or other error showErrorMessage(error.getMessage()); } } }); } }; attemptGetPaywall.run(); } ``` </TabItem> </Tabs> ### Recurrir a datos en caché \{#fallback-to-cached-data\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers class PaywallManager { private var cachedPaywall: AdaptyPaywall? = null fun getPaywall(placementId: String) { Adapty.getPaywall(placementId) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value cachedPaywall = paywall showPaywall(paywall) } is AdaptyResult.Error -> { val error = result.error if (error.code == 1001 && cachedPaywall != null) { // Network error - use cached paywall showPaywall(cachedPaywall!!) showOfflineIndicator() } else { // No cache available or other error showErrorMessage(error.message) } } } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers public class PaywallManager { private AdaptyPaywall cachedPaywall; public void getPaywall(String placementId) { Adapty.getPaywall(placementId, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success<AdaptyPaywall>) result).getValue(); cachedPaywall = paywall; showPaywall(paywall); } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); if (error.getCode() == 1001 && cachedPaywall != null) { // Network error - use cached paywall showPaywall(cachedPaywall); showOfflineIndicator(); } else { // No cache available or other error showErrorMessage(error.getMessage()); } } }); } } ``` </TabItem> </Tabs> ## Próximos pasos \{#next-steps\} - [Solución para el error Code-1000 noProductIDsFound](InvalidProductIdentifiers-kmp) - [Solución para el error Code-1003 cantMakePayments](cantMakePayments-kmp) - [Referencia completa de la API](https://android.adapty.io) - Documentación completa del SDK --- # File: InvalidProductIdentifiers-kmp --- --- title: "Solución al error Code-1000 noProductIDsFound en el SDK de Kotlin Multiplatform" description: "Resuelve errores de identificador de producto no válido al gestionar suscripciones en Adapty." --- El error con código 1000, `noProductIDsFound`, indica que ninguno de los productos que solicitaste en el paywall está disponible para comprar en el App Store, aunque estén listados allí. A veces este error viene acompañado de una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin el error, puedes ignorarla sin problema. Si te encuentras con el error `noProductIDsFound`, sigue estos pasos para resolverlo: ## Paso 1. Comprueba el bundle ID \{#step-2-check-bundle-id\} 1. Abre [App Store Connect](https://appstoreconnect.apple.com/apps). Selecciona tu app y ve a la sección **General** → **App Information**. 2. Copia el **Bundle ID** en la subsección **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Abre la pestaña [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) desde el menú superior de Adapty y pega el valor copiado en el campo **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Vuelve a la página **App information** en App Store Connect y copia el **Apple ID** desde allí. 5. En la página [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) del Adapty Dashboard, pega el ID en el campo **Apple app ID**. ## Paso 2. Comprueba los productos \{#step-3-check-products\} 1. Ve a **App Store Connect** y navega a [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) en el menú de la izquierda. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripción. Verás tus productos listados en la sección **Subscriptions**. 3. Asegúrate de que el producto que estás probando esté marcado como **Ready to Submit**. Si no lo está, sigue las instrucciones de la página [Producto en App Store](app-store-products). <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Compara el ID del producto en la tabla con el que aparece en la pestaña [**Products**](https://app.adapty.io/products) del Adapty Dashboard. Si los IDs no coinciden, copia el ID del producto de la tabla y [crea un producto](create-product) con ese ID en el Adapty Dashboard. <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 3. Comprueba la disponibilidad del producto \{#step-4-check-product-availability\} 1. Vuelve a **App Store Connect** y abre la misma sección **Subscriptions**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripción para ver tus productos. 3. Selecciona el producto que estás probando. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hasta la sección **Availability** y comprueba que todos los países y regiones requeridos estén listados. <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 4. Comprueba los precios del producto \{#step-5-check-product-prices\} 1. De nuevo, ve a la sección **Monetization** → **Subscriptions** en **App Store Connect**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripción. 3. Selecciona el producto que estás probando. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hacia abajo hasta **Subscription Pricing** y despliega la sección **Current Pricing for New Subscribers**. <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Asegúrate de que todos los precios requeridos estén listados. <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 5. Comprueba que el estado de pago de la app, la cuenta bancaria y los formularios fiscales estén activos \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. En la página de inicio de [**App Store Connect**](https://appstoreconnect.apple.com/), haz clic en **Business**. <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Selecciona el nombre de tu empresa. <img src="/assets/shared/img/business-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Desplázate hacia abajo y comprueba que tu **Paid Apps Agreement**, **Bank Account** y **Tax forms** aparezcan como **Active**. <img src="/assets/shared/img/appstore-connect-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Siguiendo estos pasos deberías poder resolver la advertencia `InvalidProductIdentifiers` y hacer que tus productos estén disponibles en el store. ## Paso 6. Vuelve a crear el producto si está bloqueado \{#step-6-recreate-the-product-if-its-stuck\} Es posible que los pasos 1–5 pasen correctamente —estado `Approved`, Bundle ID coincidente, API key válida— y el SDK siga devolviendo `1000 noProductIDsFound`. En ese caso, puede que el producto esté bloqueado en el registro de Apple. El registro de productos de Apple puede entrar en un estado en el que el producto existe en la interfaz de App Store Connect pero no está expuesto a la ruta de búsqueda de StoreKit. Elimina el producto en App Store Connect y vuelve a crearlo con el mismo ID de producto. Espera hasta 24 horas tras la recreación para que los cambios se propaguen. --- # File: cantMakePayments-kmp --- --- title: "Solución para el error Code-1003 cantMakePayment en el SDK de Kotlin Multiplatform" description: "Resuelve el error al realizar pagos cuando gestionas suscripciones en Adapty." --- El error 1003, `cantMakePayments`, indica que no es posible realizar compras in-app en este dispositivo. Si encuentras el error `cantMakePayments`, normalmente se debe a una de estas razones: - Restricciones del dispositivo: El error no está relacionado con Adapty. Consulta las soluciones más abajo. - Configuración del modo Observer: El método `makePurchase` y el modo Observer no pueden usarse al mismo tiempo. Consulta la sección más abajo. ## Problema: Restricciones del dispositivo \{#issue-device-restrictions\} | Problema | Solución | |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| | Restricciones de Screen Time | Desactiva las restricciones de compras in-app en [Screen Time](https://support.apple.com/en-us/102470) | | Cuenta suspendida | Contacta con el soporte de Apple para resolver problemas con la cuenta | | Restricciones regionales | Usa una cuenta de App Store de una región compatible | ## Problema: Usar el modo Observer y makePurchase a la vez \{#issue-using-both-observer-mode-and-makepurchase\} Si usas `makePurchases` para gestionar las compras, no necesitas el modo Observer. El [modo Observer](observer-vs-full-mode) solo es necesario si implementas la lógica de compra tú mismo. Por lo tanto, si usas `makePurchase`, puedes eliminar sin problema la activación del modo Observer del código de inicialización del SDK. --- # File: kmp-sdk-migration-guides --- --- title: "Guías de migración del SDK de Kotlin Multiplatform" description: "Guías de migración para las versiones del SDK de Kotlin Multiplatform de Adapty." --- Esta página contiene todas las guías de migración para el SDK de Kotlin Multiplatform de Adapty. Elige la versión a la que quieres migrar para ver las instrucciones detalladas: - **[Migrar a v4.0 (beta)](migration-to-kmp-sdk-v4)** - **[Migrar a v3.15](migration-to-kmp-315)** --- # File: migration-to-kmp-sdk-v4 --- --- title: "Migrar el SDK de Adapty Kotlin Multiplatform a la versión 4.0" description: "Migra al SDK de Adapty Kotlin Multiplatform v4.0 (beta) reemplazando las APIs de paywall por APIs de flow, compatibles tanto con Flow Builder como con Paywall Builder." --- El SDK de Adapty Kotlin Multiplatform 4.0 (beta) introduce los flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan tanto con el nuevo Flow Builder como con el Paywall Builder existente — no se requieren cambios de configuración en el Adapty Dashboard. ## Referencia rápida \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty.getPaywall(placementId, locale)` | `Adapty.getFlow(placementId)` | | `Adapty.getPaywallForDefaultAudience(placementId, locale)` | `Adapty.getFlowForDefaultAudience(placementId)` | | `Adapty.getPaywallProducts(paywall)` | `Adapty.getPaywallProducts(flow)` | | `Adapty.logShowPaywall(paywall)` | `Adapty.logShowFlow(flow)` | | `AdaptyPaywall` | `AdaptyFlow` | | `AdaptyUI.createPaywallView(paywall, ...)` | `AdaptyUI.createFlowView(flow, ...)` | | `AdaptyUI.createNativePaywallView(...)` → `AdaptyNativePaywallView` | `AdaptyUI.createNativeFlowView(...)` → `AdaptyNativeFlowView` | | `AdaptyUIPaywallView` | `AdaptyUIFlowView` | | `AdaptyUI.presentPaywallView(view)` / `dismissPaywallView(view)` | `AdaptyUI.presentFlowView(view)` / `dismissFlowView(view)` | | `AdaptyUI.setPaywallsEventsObserver(observer)` | `AdaptyUI.setFlowsEventsObserver(observer)` | | `AdaptyUI.registerPaywallEventsListener` / `unregisterPaywallEventsListener` | `AdaptyUI.registerFlowEventsListener` / `unregisterFlowEventsListener` | | `AdaptyUIPaywallsEventsObserver` | `AdaptyUIFlowsEventsObserver` | | `AdaptyUIPaywallPlatformView(paywall, ...)` | `AdaptyUIFlowPlatformView(flow, ...)` | | `paywallViewDidPerformAction`, `paywallViewDidAppear` y otros callbacks `paywallView...` | `flowViewDidPerformAction`, `flowViewDidAppear` y otros callbacks `flowView...` | | `paywallViewDidFailRendering` | `flowViewDidReceiveError` | `AdaptyPaywallProduct` mantiene su nombre — los productos siguen perteneciendo a un flow, y `getPaywallProducts` también mantiene su nombre, ahora recibiendo un `AdaptyFlow`. Los métodos `getFlow` y `getFlowForDefaultAudience` ya no aceptan un parámetro `locale`. Las APIs de compra y perfil (`makePurchase`, `restorePurchases`, `getProfile`, `identify`, `updateProfile`) y los respaldos mediante `setFallback` no han cambiado. Los métodos de onboarding siguen funcionando, pero están deprecados — consulta [Deprecación de la API de Onboarding](#onboarding-api-deprecation). Algunos comportamientos predeterminados han cambiado — consulta [Cambios en el comportamiento predeterminado](#default-behavior-changes). ## Instalación \{#installation\} v4.0 es una versión previa al lanzamiento, así que fija la versión exacta — Gradle no selecciona versiones preliminares mediante rangos dinámicos: ```toml showLineNumbers title="libs.versions.toml" [versions] adapty-kmp = "4.0.0-beta.1" [libraries] adapty-kmp = { module = "io.adapty:adapty-kmp", version.ref = "adapty-kmp" } adapty-kmp-ui = { module = "io.adapty:adapty-kmp-ui", version.ref = "adapty-kmp" } ``` El módulo `adapty-kmp-ui` solo es necesario si renderizas flows y paywalls con la capa Compose Multiplatform (`view.present()`). Consulta [Instalar Adapty SDK](sdk-installation-kotlin-multiplatform) para la configuración completa. Los SDKs nativos subyacentes de Adapty se han actualizado a sus versiones 4.x en ambas plataformas y se resuelven automáticamente — no es necesario ningún cambio en la compilación. El deployment target de iOS se mantiene en **15.0**, sin cambios en esta versión. ## Obtener flows \{#fetching-flows\} ### getPaywall → getFlow El tipo devuelto cambia de `AdaptyPaywall` a `AdaptyFlow`, y el parámetro `locale` se elimina — cuando renderizas un flow, el locale se resuelve automáticamente; para los paywalls personalizados, todos los locales se devuelven en `flow.remoteConfigs`: ```diff showLineNumbers - Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") - .onSuccess { paywall -> - // use the paywall + Adapty.getFlow("YOUR_PLACEMENT_ID") + .onSuccess { flow -> + // use the flow } .onError { error -> // handle the error } ``` `getPaywallForDefaultAudience` se renombra de la misma manera: ```diff showLineNumbers - Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") + Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` mantiene su nombre pero ahora recibe un `AdaptyFlow`: ```diff showLineNumbers - Adapty.getPaywallProducts(paywall) + Adapty.getPaywallProducts(flow) .onSuccess { products -> // use the products } ``` ## Modelo de datos \{#data-model\} `getFlow` devuelve un `AdaptyFlow` en lugar de un `AdaptyPaywall`, y la estructura del objeto ha cambiado: | Propiedad v3 `AdaptyPaywall` | Propiedad v4 `AdaptyFlow` | Acción | |---|---|---| | `remoteConfig: AdaptyRemoteConfig?` (única) | `remoteConfigs: List<AdaptyRemoteConfig>` | Un flow lleva un Remote Config por idioma configurado. Lee el que corresponda al usuario: `flow.remoteConfigs.firstOrNull { it.locale == "en" }`. | | _(nueva)_ | `paywalls: List<AdaptyFlowPaywall>` | Cada entrada es una variación de paywall en el flow, con su propio `name`, `variationId` y `productIdentifiers`. Los métodos de paywall web reciben un `AdaptyFlowPaywall` — consulta [Métodos de paywall web](#web-paywall-methods). | | `productIdentifiers` | movida | Los identificadores de producto ahora están en cada variación: `flow.paywalls[i].productIdentifiers`. Para obtener productos, sigue llamando a `getPaywallProducts(flow)`. | | `hasViewConfiguration` | eliminada | Elimina cualquier comprobación de `hasViewConfiguration` de tu código — `createFlowView` devuelve un error en su lugar (consulta [Mostrar flows](#displaying-flows)). | `hasViewConfiguration` permanece en `AdaptyOnboarding` — solo el modelo de flow lo elimina. ## Métodos de Web paywall \{#web-paywall-methods\} `openWebPaywall` y `createWebPaywallUrl` mantienen sus nombres, pero el parámetro `paywall` se reemplaza por un parámetro `flowPaywall` que recibe un `AdaptyFlowPaywall` — una de las variantes en `flow.paywalls`. También puedes seguir pasando un `AdaptyPaywallProduct`: ```diff showLineNumbers - Adapty.openWebPaywall(paywall = paywall) + flow.paywalls.firstOrNull()?.let { flowPaywall -> + Adapty.openWebPaywall(flowPaywall = flowPaywall) + } ``` ## Seguimiento de visualizaciones de flows \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` ha pasado a llamarse `logShowFlow` y ahora recibe un `AdaptyFlow`. El evento se sigue registrando contra la misma variación, por lo que las métricas de embudo y las pruebas A/B siguen funcionando sin cambios en el dashboard. ```diff showLineNumbers - Adapty.logShowPaywall(paywall) + Adapty.logShowFlow(flow) ``` Al igual que en v3, no es necesario llamar a este método al mostrar flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder) — Adapty registra esas vistas automáticamente. ## Mostrando flows \{#displaying-flows\} ### createPaywallView → createFlowView Renombra el método factory y pasa el `AdaptyFlow`. El tipo de vista devuelto se renombra de `AdaptyUIPaywallView` a `AdaptyUIFlowView`, pero sus métodos (`present`, `dismiss`) y los parámetros opcionales (`loadTimeout`, `preloadProducts`, `customTags`, `customTimers`, `customAssets`, `productPurchaseParams`) no cambian: ```diff showLineNumbers - AdaptyUI.createPaywallView(paywall) + AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // handle the error } ``` Si no usas Compose Multiplatform, el método de fábrica nativo se renombra de la misma forma: ```diff showLineNumbers - AdaptyUI.createNativePaywallView(paywall) + AdaptyUI.createNativeFlowView(flow) ``` `createFlowView` devuelve un `AdaptyResult.Error` si el flow no tiene ninguna vista configurada — esto reemplaza la comprobación `hasViewConfiguration` de la v3: ```diff showLineNumbers - if (paywall.hasViewConfiguration) { - AdaptyUI.createPaywallView(paywall) - .onSuccess { view -> view.present() } - } + AdaptyUI.createFlowView(flow) + .onSuccess { view -> view.present() } + .onError { error -> + // the flow has no view configured, or view creation failed + } ``` :::note Una vista de flow es de un solo uso: después de llamar a `dismiss()`, la vista se destruye, así que llama a `createFlowView` de nuevo para mostrar el flow otra vez. ::: ## Gestión de eventos \{#handling-events\} El observador de eventos cambia de nombre de `AdaptyUIPaywallsEventsObserver` a `AdaptyUIFlowsEventsObserver`, y sus callbacks reemplazan el prefijo `paywallView` por `flowView`. El cuerpo de los handlers existentes no necesita cambios en el código — solo hay que renombrar el tipo y las sobreescrituras: ```diff showLineNumbers - AdaptyUI.setPaywallsEventsObserver(object : AdaptyUIPaywallsEventsObserver { - override fun paywallViewDidFinishPurchase( - view: AdaptyUIPaywallView, + AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { + override fun flowViewDidFinishPurchase( + view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { // custom logic after purchase } }) ``` Un callback también ha sido renombrado: `paywallViewDidFailRendering` pasa a llamarse `flowViewDidReceiveError`. Se activa para los mismos errores de renderizado que antes, además de otros errores en tiempo de ejecución no relacionados con compras: ```diff showLineNumbers - override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) {} + override fun flowViewDidReceiveError(view: AdaptyUIFlowView, error: AdaptyError) {} ``` Consulta [Gestionar eventos de flow y paywall](kmp-handling-events) para ver la lista completa de callbacks. ### Vista de plataforma de Compose \{#compose-platform-view\} Si integras vistas con el composable de Compose Multiplatform, `AdaptyUIPaywallPlatformView(paywall, ...)` pasa a llamarse `AdaptyUIFlowPlatformView(flow, ...)`. Los callbacks de eventos mantienen sus nombres `onDid...`, excepto `onDidFailRendering`, que se convierte en `onDidReceiveError`: ```diff showLineNumbers - AdaptyUIPaywallPlatformView( - paywall = paywall, + AdaptyUIFlowPlatformView( + flow = flow, onDidFinishPurchase = { view, product, result -> /* ... */ }, ) ``` Al igual que en la v3, los callbacks que pasas aquí (y cualquier observador registrado mediante `registerFlowEventsListener`) se ejecutan **además del** observador global, no en lugar de él — tu callback observa un evento, no reemplaza el comportamiento global predeterminado. Ten en cuenta los [cambios en los valores predeterminados](#default-behavior-changes): por ejemplo, el comportamiento global predeterminado ya no cierra la vista tras una compra. ### Nuevas APIs \{#new-apis\} - `AdaptyUI.setObserverModeResolver(...)` con un `AdaptyUIObserverModeResolver` — gestiona las compras y restauraciones iniciadas desde flows cuando el SDK funciona en [modo Observer](implement-observer-mode-kmp). Anteriormente esto solo estaba disponible en los SDKs nativos de iOS y Android. Consulta [Presentar flows en modo Observer](kmp-present-flows-in-observer-mode). - `AdaptyUI.setSystemRequestsHandler(...)` con un `AdaptyUISystemRequestsHandler` — reservado para solicitudes del sistema desde un flow (prompts de permisos del SO y solicitudes de reseña de la app). Los flows aún no activan estas solicitudes, así que no necesitas registrar un handler. - El nuevo callback opcional `flowViewDidReceiveAnalyticEvent` está reservado para eventos analíticos personalizados desde un flow. Los flows aún no emiten estos eventos a tu código, así que no necesitas implementarlo. - `AdaptyUI.openWebUrl(url, openIn)` y `AdaptyUI.requestAppReview()` — estos respaldan el manejo predeterminado de `OpenUrlAction` y el `handleAppReviewRequest` por defecto, de modo que las URLs y los prompts de reseña de la app se gestionan de forma nativa sin configuración adicional. Úsalos directamente solo si sobreescribes esos valores predeterminados. - `AdaptyConfig.ServerCluster.CN` — una nueva opción de clúster de servidor junto a `DEFAULT` y `EU`, para conectar tu app a los [servidores de Adapty en China](china-cluster). ## Cambios en el comportamiento predeterminado \{#default-behavior-changes\} Estos cambios no provocan errores de compilación, así que pruébalos en tiempo de ejecución: - **Finalización de compra**: En v3, el `paywallViewDidFinishPurchase` predeterminado cerraba la vista tras cualquier resultado de compra que no fuera `AdaptyPurchaseResult.UserCanceled`. En v4, el `flowViewDidFinishPurchase` predeterminado no hace nada, por lo que **un flow permanece abierto tras una compra hasta que lo cierres tú** — igual que en iOS. Si dependías de ese cierre automático, llama a `view.dismiss()` cuando finalice la compra. - **Botón atrás de Android**: En v3, el `paywallViewDidPerformAction` predeterminado cerraba la vista tanto con `CloseAction` como con `AndroidSystemBackAction`. En v4, el comportamiento predeterminado solo gestiona `CloseAction` — **el botón atrás del sistema ya no cierra un flow por sí solo**, igual que en iOS, donde un flow no puede cerrarse con un gesto del sistema. Ofrece a los usuarios una salida explícita (un botón **Close** o una acción `on_device_back`), o cierra la vista tú mismo en `flowViewDidPerformAction`. - **Errores de vista**: En v3, el `paywallViewDidFailRendering` predeterminado no hacía nada. En v4, el `flowViewDidReceiveError` predeterminado **cierra la vista** — sobreescríbelo si quieres mantenerla abierta o gestionar el error de otra manera. - **Las vistas son de un solo uso**: Tras llamar a `dismiss()`, la vista se destruye. Llama a `createFlowView` de nuevo para mostrar el flow otra vez. ## Deprecación de la API de onboarding \{#onboarding-api-deprecation\} La API de onboarding anterior está obsoleta en la versión 4.0 en favor del [Flow Builder](adapty-flow-builder). Sigue funcionando, pero se eliminará en una versión futura, así que planifica la migración de tus onboardings al Flow Builder. Símbolos obsoletos: `getOnboarding`, `getOnboardingForDefaultAudience`, `AdaptyUI.createOnboardingView`, `AdaptyUI.createNativeOnboardingView` y `AdaptyUIOnboardingsEventsObserver`. --- # File: migration-to-kmp-315 --- --- title: "Guía de migración al SDK de Adapty Kotlin Multiplatform 3.15.0" description: "Pasos de migración para el SDK de Adapty Kotlin Multiplatform 3.15.0" --- El SDK de Adapty Kotlin Multiplatform 3.15.0 es una versión mayor que trae nuevas funcionalidades y mejoras que, sin embargo, pueden requerir algunos pasos de migración por tu parte. 1. Actualiza los nombres de la clase observer y sus métodos. 2. Actualiza el nombre del método para los paywalls de respaldo. 3. Actualiza el nombre de la clase de vista en los métodos de gestión de eventos. ## Actualiza los nombres de la clase observer y sus métodos \{#update-observer-class-and-method-names\} La clase observer y su método de registro han sido renombrados: ```diff - import com.adapty.kmp.AdaptyUIObserver + import com.adapty.kmp.AdaptyUIPaywallsEventsObserver - import com.adapty.kmp.models.AdaptyUIView + import com.adapty.kmp.models.AdaptyUIPaywallView - class MyAdaptyUIObserver : AdaptyUIObserver { - override fun paywallViewDidPerformAction(view: AdaptyUIView, action: AdaptyUIAction) { + class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver { + override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { // handle actions } } // Set up the observer - AdaptyUI.setObserver(MyAdaptyUIObserver()) + AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` ## Actualiza el nombre del método para los paywalls de respaldo \{#update-fallback-paywalls-method-name\} El nombre del método para configurar los paywalls de respaldo ha cambiado: ```diff showLineNumbers - Adapty.setFallbackPaywalls(assetId = "fallback.json") + Adapty.setFallback(assetId = "fallback.json") .onSuccess { // Fallback paywalls loaded successfully } .onError { error -> // Handle the error } ``` ## Actualiza el nombre de la clase de vista en los métodos de gestión de eventos \{#update-view-class-name-in-event-handling-methods\} Todos los métodos de gestión de eventos usan ahora la nueva clase `AdaptyUIPaywallView` en lugar de `AdaptyUIView`: ```diff - override fun paywallViewDidAppear(view: AdaptyUIView) { + override fun paywallViewDidAppear(view: AdaptyUIPaywallView) { // Handle paywall appearance } - override fun paywallViewDidDisappear(view: AdaptyUIView) { + override fun paywallViewDidDisappear(view: AdaptyUIPaywallView) { // Handle paywall disappearance } - override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) { + override fun paywallViewDidSelectProduct(view: AdaptyUIView, productId: String) { // Handle product selection } - override fun paywallViewDidStartPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct) { + override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) { // Handle purchase start } - override fun paywallViewDidFinishPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) { + override fun paywallViewDidFinishPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) { // Handle purchase result } - override fun paywallViewDidFailPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct, error: AdaptyError) { + override fun paywallViewDidFailPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, error: AdaptyError) { // Add your purchase failure handling logic here } - override fun paywallViewDidFinishRestore(view: AdaptyUIView, profile: AdaptyProfile) { + override fun paywallViewDidFinishRestore(view: AdaptyUIPaywallView, profile: AdaptyProfile) { // Add your successful restore handling logic here } - override fun paywallViewDidFailRestore(view: AdaptyUIView, error: AdaptyError) { + override fun paywallViewDidFailRestore(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your restore failure handling logic here } - override fun paywallViewDidFinishWebPaymentNavigation(view: AdaptyUIView, product: AdaptyPaywallProduct?, error: AdaptyError?) { + override fun paywallViewDidFinishWebPaymentNavigation(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct?, error: AdaptyError?) { // Handle web payment navigation result } - override fun paywallViewDidFailLoadingProducts(view: AdaptyUIView, error: AdaptyError) { + override fun paywallViewDidFailLoadingProducts(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your product loading failure handling logic here } - override fun paywallViewDidFailRendering(view: AdaptyUIView, error: AdaptyError) { + override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) { // Handle rendering error } ``` --- # End of Documentation _Generated on: 2026-07-24T13:01:55.749Z_ _Successfully processed: 48/48 files_ # REACT-NATIVE - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-07-24T13:01:55.751Z Total files: 45 --- # File: sdk-installation-react-native-expo --- --- title: "Install & configure Adapty React Native SDK in an Expo project" description: "Step-by-step guide on installing Adapty React Native SDK in an Expo project for subscription-based apps." --- :::important Esta guía cubre la instalación y configuración del SDK de Adapty para React Native **en un proyecto Expo**. Si usas **React Native puro (sin Expo)**, sigue la [guía de instalación de React Native](sdk-installation-react-native-pure) en su lugar. ::: El SDK de Adapty incluye dos módulos clave para una integración fluida en tu app de React Native: - **Core Adapty**: Este módulo es necesario para que Adapty funcione correctamente en tu app. - **AdaptyUI**: Este módulo es necesario si utilizas el [Adapty Paywall Builder](adapty-paywall-builder), una herramienta visual sin código para crear paywalls multiplataforma fácilmente. AdaptyUI se activa automáticamente junto con el módulo principal. Si necesitas un tutorial completo sobre cómo implementar compras in-app en tu app de React Native, consulta [este artículo](https://adapty.io/blog/react-native-in-app-purchases-tutorial/). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app de Expo? Echa un vistazo a nuestras apps de muestra: - [Ejemplo de Expo dev build](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) para funcionalidad completa, incluyendo compras reales y Paywall Builder - [Ejemplo de Expo Go y Web](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) para pruebas en modo mock ::: Para una guía completa de implementación, también puedes ver el vídeo: <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/TtCJswpt2ms?si=FlFJGvpj-U33yoNK" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> </div> ## Requisitos \{#requirements\} El SDK de Adapty para React Native requiere iOS 15.0+. Para compilar para iOS se necesita **Swift 6.0** o posterior. El [Modo Infantil](kids-mode-react-native) requiere **Swift 6.1** o posterior. :::info A partir del SDK v3.17, Adapty SDK utiliza Google Play Billing Library v8.0.0 por defecto. ::: :::info Instalar el SDK es el paso 5 de la configuración de Adapty. Para que las compras funcionen en tu app, también necesitas conectar tu app a los stores, y luego crear productos, un paywall y un placement en el Adapty Dashboard. La [guía de inicio rápido](quickstart) explica todos los pasos necesarios. ::: ## Instalar el SDK de Adapty \{#install-adapty-sdk\} :::important A partir de la v4, el SDK de Adapty para React Native ya no admite la instalación de sus dependencias nativas mediante CocoaPods. Si necesitas la v4 o posterior (para el [Flow Builder](adapty-flow-builder)), sigue los pasos de [SDK de Adapty 4.0: activar Swift Package Manager](#adapty-sdk-40-enable-swift-package-manager) que se indican más abajo. ::: [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-React-Native.svg?style=flat&logo=react)](https://github.com/adaptyteam/AdaptySDK-React-Native/releases) :::important [Expo Dev Client](https://docs.expo.dev/versions/latest/sdk/dev-client/) (una compilación de desarrollo personalizada) es necesario para usar Adapty en un proyecto Expo. Expo Go no admite módulos nativos personalizados, por lo que solo puedes usarlo con el [**modo mock**](#set-up-mock-mode-for-expo-go--expo-web) para el desarrollo de UI/lógica (sin compras reales ni renderizado de AdaptyUI/Paywall Builder). ::: 1. Instala el SDK de Adapty (esto también instala `@adapty/core` automáticamente): ```sh npx expo install react-native-adapty npx expo prebuild ``` 2. Compila tu app para desarrollo usando EAS o una compilación local: <Tabs> <TabItem value="eas" label="EAS build" default> ```sh # For iOS eas build --profile development --platform ios # For Android eas build --profile development --platform android ``` </TabItem> <TabItem value="local" label="Local build"> ```sh # For iOS npx expo run:ios # For Android npx expo run:android ``` </TabItem> </Tabs> 3. Inicia el servidor de desarrollo: ```sh npx expo start --dev-client ``` ### SDK de Adapty 4.0: habilitar Swift Package Manager \{#adapty-sdk-40-enable-swift-package-manager\} El SDK de React Native 4.0 — que añade compatibilidad con [Flow Builder](adapty-flow-builder) — requiere **React Native 0.75 o posterior**. Instala el SDK: ```sh npx expo install react-native-adapty@^4.0.0 ``` v4 descarga los SDKs nativos de iOS (`Adapty`, `AdaptyUI`, `AdaptyPlugin`) a través de Swift Package Manager en lugar de sub-dependencias de CocoaPods ([el repositorio de specs de CocoaPods pasará a ser de solo lectura en diciembre de 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)). SPM requiere frameworks dinámicos, que en Expo se habilitan con el plugin [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/). Añádelo a `app.json` (o `app.config.js`): ```json showLineNumbers title="app.json" { "expo": { "plugins": [ [ "expo-build-properties", { "ios": { "useFrameworks": "dynamic" } } ] ] } } ``` Luego instala el plugin y regenera el proyecto nativo: ```sh npx expo install expo-build-properties npx expo prebuild --clean ``` Consulta [Migrar el SDK de Adapty para React Native a v4](migration-to-react-native-sdk-v4) para ver la migración completa. ## Activa el módulo Adapty del SDK \{#activate-adapty-module-of-adapty-sdk\} Para obtener tu **Public SDK Key**: 1. Ve al Adapty Dashboard y navega a [**App settings → General**](https://app.adapty.io/settings/general). 2. En la sección **Api keys**, copia la **Public SDK Key** (NO la Secret Key). 3. Reemplaza `"YOUR_PUBLIC_SDK_KEY"` en el código. O bien, obtenla de forma programática usando el [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` O directamente: ``` npx adapty auth login adapty apps list ``` - Asegúrate de usar la **Public SDK key** para inicializar Adapty; la **Secret key** solo debe usarse para la [API del lado del servidor](getting-started-with-server-side-api). - Las **SDK keys** son únicas para cada app, así que si tienes varias apps asegúrate de elegir la correcta. Copia el siguiente código en `App.tsx` para activar Adapty: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` :::important Espera a que `activate` se resuelva antes de llamar a cualquier otro método del SDK de Adapty. Consulta [Orden de llamadas en el SDK de React Native](react-native-sdk-call-order) para ver la secuencia completa. ::: Ahora configura los paywalls en tu app: - Si usas [Adapty Paywall Builder](adapty-paywall-builder), sigue la [guía de inicio rápido con Paywall Builder](react-native-quickstart-paywalls). - Si construyes tu propia interfaz de paywall, consulta la [guía de inicio rápido para paywalls personalizados](react-native-quickstart-manual). :::tip Para evitar errores de activación en el entorno de desarrollo, consulta los [consejos](#development-environment-tips). ::: ## Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Si planeas usar el [Paywall Builder](adapty-paywall-builder), necesitas el módulo AdaptyUI. Se activa automáticamente al activar el módulo principal; no necesitas hacer nada más. ## Configuración opcional \{#optional-setup\} ### Registro #### Configurar el sistema de registro \{#set-up-the-logging-system\} Adapty registra errores y otra información importante para ayudarte a entender qué está pasando. Hay los siguientes niveles disponibles: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | Solo se registrarán errores | | `warn` | Se registrarán errores y mensajes del SDK que no causan errores críticos, pero que conviene tener en cuenta | | `info` | Se registrarán errores, advertencias y varios mensajes informativos | | `verbose` | Se registrará cualquier información adicional que pueda ser útil durante la depuración, como llamadas a funciones, consultas a la API, etc. | Puedes establecer el nivel de log en tu app antes o durante la configuración de Adapty: ```typescript showLineNumbers title="App.tsx" // Set log level before activation // 'verbose' is recommended for development and the first production release adapty.setLogLevel('verbose'); // Or set it during configuration adapty.activate('YOUR_PUBLIC_SDK_KEY', { logLevel: 'verbose', }); ``` ### Políticas de datos \{#data-policies\} Adapty no almacena datos personales de tus usuarios a menos que los envíes explícitamente, pero puedes implementar políticas de seguridad de datos adicionales para cumplir con las directrices del store o del país. #### Deshabilitar la recopilación y el intercambio de direcciones IP \{#disable-ip-address-collection-and-sharing\} Al activar el módulo de Adapty, establece `ipAddressCollectionDisabled` en `true` para deshabilitar la recopilación y el intercambio de la dirección IP del usuario. El valor predeterminado es `false`. Usa este parámetro para mejorar la privacidad del usuario, cumplir con normativas regionales de protección de datos (como GDPR o CCPA) o reducir la recopilación innecesaria de datos cuando las funciones basadas en IP no son necesarias para tu aplicación. ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ipAddressCollectionDisabled: true, }); ``` #### Desactivar la recopilación y el uso compartido del ID publicitario \{#disable-advertising-id-collection-and-sharing\} Al activar el módulo de Adapty, establece `ios.idfaCollectionDisabled` (iOS) o `android.adIdCollectionDisabled` (Android) en `true` para desactivar la recopilación de identificadores publicitarios. El valor predeterminado es `false`. Usa este parámetro para cumplir con las políticas de App Store/Play Store, evitar que aparezca el prompt de App Tracking Transparency, o si tu app no necesita atribución publicitaria ni análisis basados en IDs publicitarios. ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { idfaCollectionDisabled: true, }, android: { adIdCollectionDisabled: true, }, }); ``` #### Configurar la caché de medios para AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} Por defecto, AdaptyUI almacena en caché los medios (como imágenes y vídeos) para mejorar el rendimiento y reducir el consumo de red. Puedes personalizar la configuración de la caché proporcionando una configuración personalizada. Usa `mediaCache` para cambiar la configuración de caché predeterminada: ```typescript adapty.activate('YOUR_PUBLIC_SDK_KEY', { mediaCache: { memoryStorageTotalCostLimit: 200 * 1024 * 1024, // Optional: memory cache size in bytes memoryStorageCountLimit: 2147483647, // Optional: max number of items in memory diskStorageSizeLimit: 200 * 1024 * 1024, // Optional: disk cache size in bytes }, }); ``` | Parámetro | Obligatorio | Descripción | |-----------|-------------|-------------| | memoryStorageTotalCostLimit | opcional | Tamaño total de la caché en memoria en bytes. El valor predeterminado depende de la plataforma. | | memoryStorageCountLimit | opcional | Límite de elementos en el almacenamiento en memoria. El valor predeterminado depende de la plataforma. | | diskStorageSizeLimit | opcional | Límite del tamaño del archivo en disco en bytes. El valor predeterminado depende de la plataforma. | ### Habilitar niveles de acceso locales (Android) \{#enable-local-access-levels-android\} Por defecto, los [niveles de acceso locales](local-access-levels) están habilitados en iOS y deshabilitados en Android. Para habilitarlos también en Android, establece `localAccessLevelAllowed` en `true`: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { android: { localAccessLevelAllowed: true, }, }); ``` ### Borrar datos al restaurar desde copia de seguridad \{#clear-data-on-backup-restore\} Cuando `clearDataOnBackup` se establece en `true`, el SDK detecta cuándo la app se restaura desde una copia de seguridad de iCloud y elimina todos los datos del SDK almacenados localmente, incluida la información de perfil en caché, los detalles de productos y los paywalls. El SDK se inicializa entonces con un estado limpio. El valor predeterminado es `false`. :::note Solo se elimina la caché local del SDK. El historial de transacciones con Apple y los datos de usuario en los servidores de Adapty no se modifican. ::: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { clearDataOnBackup: true }, }); ``` ## Consejos para el entorno de desarrollo \{#development-environment-tips\} #### Configura el modo mock para Expo Go / Expo Web \{#set-up-mock-mode-for-expo-go--expo-web\} Los entornos de Expo Go y Expo Web no tienen acceso a los módulos nativos de Adapty. Para evitar errores en tiempo de ejecución sin dejar de poder construir y probar la UI y la lógica de paywall de tu app, Adapty ofrece el **modo mock**. ::::important El modo mock **no** es una herramienta para probar compras reales: - **No abre** los flujos de compra de App Store / Google Play y **no crea** transacciones reales. - **No renderiza** paywalls/onboardings creados con **Adapty Paywall Builder (AdaptyUI)**. - Los módulos nativos de Adapty se **omiten por completo**: incluso si faltan archivos del SDK nativo en la compilación de Xcode/Android o la clave de API no es válida, no se producirán errores. Para probar compras reales y paywalls del Paywall Builder, usa un Expo Dev Client / build de producción, donde el modo mock se desactiva automáticamente. :::: **De forma predeterminada**, el SDK detecta automáticamente los entornos Expo Go y web y activa el modo simulado. No necesitas configurar nada a menos que quieras personalizar los datos simulados. Cuando el modo simulado está activo: - Todos los métodos de Adapty devuelven datos simulados sin realizar solicitudes de red a los servidores de Adapty. - Por defecto, el perfil simulado inicial no tiene suscripciones activas. - Por defecto, `makePurchase(...)` simula una compra exitosa y otorga acceso premium. Puedes personalizar los datos de prueba usando `mockConfig` durante la activación. Consulta el formato de configuración y los parámetros disponibles [aquí](https://react-native.adapty.io/interfaces/adaptymockconfig). ```typescript showLineNumbers title="App.tsx" try { await adapty.activate('YOUR_PUBLIC_SDK_KEY', { mockConfig: { // Customize the initial mock profile (optional) }, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); } ``` Si necesitas llamar a métodos del SDK antes de la activación (como `isActivated()` o `setLogLevel()`), usa `enableMock()` antes de `activate()`. Si el puente ya está inicializado, este método no hace nada. ```typescript showLineNumbers title="App.tsx" adapty.enableMock(); // Optional: pass mockConfig to customize mock data // Now you can call methods before activation await adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` #### Retrasar la activación del SDK con fines de desarrollo \{#delay-sdk-activation-for-development-purposes\} Adapty pre-carga todos los datos de usuario necesarios al activar el SDK, lo que permite un acceso más rápido a datos actualizados. Sin embargo, esto puede ser un problema en el simulador de iOS, que con frecuencia solicita autenticación durante el desarrollo. Aunque Adapty no puede controlar el flujo de autenticación de StoreKit, sí puede aplazar las solicitudes que el SDK realiza para obtener datos de usuario actualizados. Al activar la propiedad `__debugDeferActivation`, la llamada de activación queda en espera hasta que realizas la siguiente llamada al SDK de Adapty. Esto evita solicitudes de autenticación innecesarias cuando no son necesarias. Es importante tener en cuenta que **esta función está pensada únicamente para uso en desarrollo**, ya que no cubre todos los escenarios de usuario posibles. En producción, la activación no debe retrasarse, ya que los dispositivos reales suelen recordar los datos de autenticación y no solicitan las credenciales repetidamente. Este es el enfoque recomendado para su uso: ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __debugDeferActivation: isSimulator(), // 'isSimulator' from any 3rd party library }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` #### Solucionar errores de activación del SDK con Fast Refresh de React Native \{#troubleshoot-sdk-activation-errors-on-react-natives-fast-refresh\} Al desarrollar con el SDK de Adapty en React Native, es posible que encuentres el error: `Adapty can only be activated once. Ensure that the SDK activation call is not made more than once.` Esto ocurre porque la función de actualización rápida (fast refresh) de React Native activa múltiples llamadas de activación durante el desarrollo. Para evitarlo, usa la opción `__ignoreActivationOnFastRefresh` con el valor `__DEV__` (el indicador del modo de desarrollo de React Native). ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __ignoreActivationOnFastRefresh: __DEV__, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` ## Solución de problemas \{#troubleshooting\} #### Error de versión mínima de iOS \{#minimum-ios-version-error\} Al compilar para iOS, puede aparecer un error relacionado con la **versión mínima de iOS** o el deployment target. Adapty requiere **iOS 15.0+**. Como Expo genera el proyecto de iOS (incluido el `Podfile`) durante `expo prebuild`, **no debes editar el `Podfile` directamente**. En su lugar, configura el deployment target mediante el plugin de configuración `expo-build-properties`. 1. Instala el plugin: ```sh npx expo install expo-build-properties ``` 2. Actualiza tu configuración de Expo (`app.json` o `app.config.js`) para establecer el deployment target de iOS: ``` { "expo": { // ...other Expo config... "plugins": [ [ "expo-build-properties", { "ios": { // Adapty requires iOS 15.0+. "deploymentTarget": "15.0" } } ], ] } } ``` 3. Regenera el proyecto nativo de iOS y reconstrúyelo: ``` npx expo prebuild --clean npx expo run:ios # or `eas build -p ios` on your CI ``` #### Conflicto del manifiesto de Android Auto Backup \{#android-auto-backup-manifest-conflict\} Cuando usas Expo con varios SDKs que configuran Android Auto Backup (como Adapty, AppsFlyer o expo-secure-store), puede aparecer un conflicto en el fusionador de manifiestos. Un error típico tiene este aspecto: `Manifest merger failed : Attribute application@fullBackupContent value=(@xml/secure_store_backup_rules) from AndroidManifest.xml:24:248-306 is also present at [io.adapty:android-sdk:3.12.0] AndroidManifest.xml:9:18-70 value=(@xml/adapty_backup_rules).` Para resolver este conflicto, debes permitir que el plugin de Adapty gestione la configuración de copia de seguridad de Android. Si tu proyecto también usa `expo-secure-store`, desactiva su propia configuración de copia de seguridad para evitar conflictos. Así es como debes configurar tu `app.json`: ```json title="app.json" { "expo": { "plugins": [ ["react-native-adapty", { "replaceAndroidBackupConfig": true }], ["expo-secure-store", { "configureAndroidBackup": false }] ] } } ``` La opción `replaceAndroidBackupConfig` está en `false` por defecto. Cuando se activa, permite que el plugin de Adapty controle las reglas de copia de seguridad de Android. Incluye `"configureAndroidBackup": false` si usas `expo-secure-store` para evitar advertencias, ya que la configuración de copia de seguridad de SecureStore pasará a gestionarla Adapty. :::important Esta configuración solo respeta los requisitos de copia de seguridad de Adapty, AppsFlyer y expo-secure-store. Si otras bibliotecas de tu proyecto definen reglas de copia de seguridad personalizadas, tendrás que configurarlas manualmente. ::: --- # File: sdk-installation-react-native-pure --- --- title: "Install & configure Adapty SDK in a pure React Native project" description: "Step-by-step guide on installing Adapty SDK on React Native for subscription-based apps." --- :::important Esta guía aplica únicamente a **proyectos de React Native puro (sin Expo)**. Si usas **Expo**, sigue la [guía de instalación para Expo](sdk-installation-react-native-expo) en su lugar. ::: El SDK de Adapty incluye dos módulos clave para una integración fluida en tu app de React Native: - **Core Adapty**: Este módulo es necesario para que Adapty funcione correctamente en tu app. - **AdaptyUI**: Este módulo es necesario si usas el [Adapty Paywall Builder](adapty-paywall-builder), una herramienta sin código y fácil de usar para crear paywalls multiplataforma. AdaptyUI se activa automáticamente junto con el módulo principal. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funciones básicas. ::: ## Requisitos \{#requirements\} El SDK de Adapty para React Native requiere iOS 15.0+. Para compilar en iOS se necesita **Swift 6.0** o posterior. El [Modo Infantil](kids-mode-react-native) requiere **Swift 6.1** o posterior. :::info A partir del SDK v3.17, el SDK de Adapty usa Google Play Billing Library v8.0.0 por defecto. ::: :::info Instalar el SDK es el paso 5 de la configuración de Adapty. Para que las compras funcionen en tu app, también necesitas conectar tu app a los stores, y luego crear productos, un paywall y un placement en el Adapty Dashboard. La [guía de inicio rápido](quickstart) explica todos los pasos necesarios. ::: ## Instalar el SDK de Adapty \{#install-adapty-sdk\} :::important A partir de la v4, el SDK de Adapty para React Native ya no admite la instalación de sus dependencias nativas mediante CocoaPods. Si necesitas la v4 o una versión posterior (para el [Flow Builder](adapty-flow-builder)), sigue los pasos de [Adapty SDK 4.0: habilitar Swift Package Manager](#adapty-sdk-40-enable-swift-package-manager) a continuación. ::: [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-React-Native.svg?style=flat&logo=react)](https://github.com/adaptyteam/AdaptySDK-React-Native/releases) 1. Instala el SDK de Adapty (esto también instala `@adapty/core` automáticamente): ```sh showLineNumbers title="Shell" # using npm npm install react-native-adapty # or using yarn yarn add react-native-adapty ``` 2. Para iOS, instala los pods: ```sh showLineNumbers title="Shell" cd ios && pod install ``` <details> <summary>Para Android, si tu versión de React Native es anterior a 0.73.0 (haz clic para expandir)</summary> Actualiza el archivo `/android/build.gradle`. Asegúrate de que existe la dependencia `kotlin-gradle-plugin:1.8.0` o una versión más reciente: ```groovy showLineNumbers title="/android/build.gradle" ... buildscript { ... dependencies { ... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0" } } ... ``` </details> ### Adapty SDK 4.0: habilitar Swift Package Manager \{#adapty-sdk-40-enable-swift-package-manager\} React Native SDK 4.0 — que añade soporte para [Flow Builder](adapty-flow-builder) — requiere **React Native 0.75 o posterior**. Instala el SDK: ```sh showLineNumbers title="Shell" npm install react-native-adapty@^4.0.0 # or using yarn yarn add react-native-adapty@^4.0.0 ``` v4 incorpora los SDK nativos de iOS (`Adapty`, `AdaptyUI`, `AdaptyPlugin`) a través de Swift Package Manager en lugar de las sub-dependencias de CocoaPods ([el repositorio de specs de CocoaPods pasará a ser de solo lectura en diciembre de 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)). SPM requiere frameworks dinámicos — añade lo siguiente al target de tu `ios/Podfile` y luego reinstala los pods: ```ruby showLineNumbers title="ios/Podfile" use_frameworks! :linkage => :dynamic ``` ```sh showLineNumbers title="Shell" cd ios && pod install --repo-update ``` Si anteriamente tenías `Adapty`, `AdaptyUI` o `AdaptyPlugin` como sub-dependencias de CocoaPods, elimina primero cualquier línea `pod 'Adapty'`, `pod 'AdaptyUI'` o `pod 'AdaptyPlugin'` de tu `Podfile`. :::warning Cambiar del enlace estático predeterminado a frameworks dinámicos puede entrar en conflicto con bibliotecas que aún no soportan cabeceras modulares, y es incompatible con Flipper. Consulta [Migrar el SDK de Adapty React Native a v4](migration-to-react-native-sdk-v4) para más detalles. ::: ## Activa el módulo Adapty del SDK \{#activate-adapty-module-of-adapty-sdk\} Para obtener tu **Public SDK Key**: 1. Ve al Adapty Dashboard y navega a [**App settings → General**](https://app.adapty.io/settings/general). 2. En la sección **Api keys**, copia la **Public SDK Key** (NO la Secret Key). 3. Reemplaza `"YOUR_PUBLIC_SDK_KEY"` en el código. O bien, obtenla de forma programática usando el [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` O directamente: ``` npx adapty auth login adapty apps list ``` - Asegúrate de usar la **Public SDK key** para inicializar Adapty; la **Secret key** solo debe usarse para la [API del lado del servidor](getting-started-with-server-side-api). - Las **SDK keys** son únicas para cada app, así que si tienes varias apps asegúrate de elegir la correcta. Copia el siguiente código en `App.tsx` para activar Adapty: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` :::important Espera a que `activate` se resuelva antes de llamar a cualquier otro método del SDK de Adapty. Consulta [Orden de llamadas en el SDK de React Native](react-native-sdk-call-order) para ver la secuencia completa. ::: Ahora configura los paywalls en tu app: - Si usas [Adapty Paywall Builder](adapty-paywall-builder), sigue el [inicio rápido con Paywall Builder](react-native-quickstart-paywalls). - Si construyes tu propia UI de paywall, consulta el [inicio rápido para paywalls personalizados](react-native-quickstart-manual). :::tip Para evitar errores de activación en el entorno de desarrollo, consulta los [consejos](#development-environment-tips). ::: ## Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptui-module-of-adapty-sdk\} Si planeas usar [Paywall Builder](adapty-paywall-builder), necesitas el módulo AdaptyUI. Se activa automáticamente al activar el módulo principal; no es necesario hacer nada más. ## Configuración opcional \{#optional-setup\} ### Registro #### Configura el sistema de registro \{#set-up-the-logging-system\} Adapty registra errores y otra información importante para ayudarte a entender qué está ocurriendo. Hay los siguientes niveles disponibles: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | Solo se registrarán los errores | | `warn` | Se registrarán los errores y los mensajes del SDK que no causan errores críticos pero que merecen atención | | `info` | Se registrarán los errores, las advertencias y varios mensajes informativos | | `verbose` | Se registrará cualquier información adicional que pueda ser útil durante la depuración, como llamadas a funciones, consultas a la API, etc. | Puedes establecer el nivel de registro en tu app antes o durante la configuración de Adapty: ```typescript showLineNumbers title="App.tsx" // Set log level before activation // 'verbose' is recommended for development and the first production release adapty.setLogLevel('verbose'); // Or set it during configuration adapty.activate('YOUR_PUBLIC_SDK_KEY', { logLevel: 'verbose', }); ``` ### Políticas de datos \{#data-policies\} Adapty no almacena datos personales de tus usuarios a menos que los envíes explícitamente, pero puedes implementar políticas de seguridad de datos adicionales para cumplir con las directrices de la store o del país. #### Desactivar la recopilación y el uso compartido de direcciones IP \{#disable-ip-address-collection-and-sharing\} Al activar el módulo de Adapty, establece `ipAddressCollectionDisabled` en `true` para desactivar la recopilación y el uso compartido de la dirección IP del usuario. El valor predeterminado es `false`. Usa este parámetro para mejorar la privacidad del usuario, cumplir con normativas regionales de protección de datos (como GDPR o CCPA), o reducir la recopilación de datos innecesaria cuando las funciones basadas en IP no son necesarias para tu app. ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ipAddressCollectionDisabled: true, }); ``` #### Desactivar la recopilación y el uso compartido del ID publicitario \{#disable-advertising-id-collection-and-sharing\} Al activar el módulo de Adapty, establece `ios.idfaCollectionDisabled` (iOS) o `android.adIdCollectionDisabled` (Android) en `true` para deshabilitar la recopilación de identificadores publicitarios. El valor predeterminado es `false`. Usa este parámetro para cumplir con las políticas de App Store/Play Store, evitar que aparezca el aviso de App Tracking Transparency, o si tu app no necesita atribución publicitaria ni analíticas basadas en IDs de publicidad. ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { idfaCollectionDisabled: true, }, android: { adIdCollectionDisabled: true, }, }); ``` #### Configurar la caché de medios para AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} Por defecto, AdaptyUI almacena en caché los medios (como imágenes y vídeos) para mejorar el rendimiento y reducir el uso de red. Puedes personalizar la configuración de caché proporcionando una configuración personalizada. Usa `mediaCache` para sobrescribir la configuración de caché predeterminada: ```typescript adapty.activate('YOUR_PUBLIC_SDK_KEY', { mediaCache: { memoryStorageTotalCostLimit: 200 * 1024 * 1024, // Optional: memory cache size in bytes memoryStorageCountLimit: 2147483647, // Optional: max number of items in memory diskStorageSizeLimit: 200 * 1024 * 1024, // Optional: disk cache size in bytes }, }); ``` | Parámetro | Obligatorio | Descripción | |-----------|-------------|-------------| | memoryStorageTotalCostLimit | opcional | Tamaño total de la caché en memoria en bytes. El valor por defecto depende de la plataforma. | | memoryStorageCountLimit | opcional | Límite de elementos en el almacenamiento en memoria. El valor por defecto depende de la plataforma. | | diskStorageSizeLimit | opcional | Límite del tamaño de archivo en disco en bytes. El valor por defecto depende de la plataforma. | ### Habilitar niveles de acceso locales (Android) \{#enable-local-access-levels-android\} Por defecto, los [niveles de acceso locales](local-access-levels) están habilitados en iOS y deshabilitados en Android. Para habilitarlos también en Android, establece `localAccessLevelAllowed` en `true`: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { android: { localAccessLevelAllowed: true, }, }); ``` ### Borrar datos al restaurar desde copia de seguridad \{#clear-data-on-backup-restore\} Cuando `clearDataOnBackup` se establece en `true`, el SDK detecta cuándo la app se restaura desde una copia de seguridad de iCloud y elimina todos los datos del SDK almacenados localmente, incluida la información de perfil en caché, los detalles de productos y los paywalls. A continuación, el SDK se inicializa con un estado limpio. El valor predeterminado es `false`. :::note Solo se elimina la caché local del SDK. El historial de transacciones con Apple y los datos de usuario en los servidores de Adapty permanecen sin cambios. ::: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { clearDataOnBackup: true }, }); ``` ## Consejos para el entorno de desarrollo \{#development-environment-tips\} #### Retrasar la activación del SDK con fines de desarrollo \{#delay-sdk-activation-for-development-purposes\} Adapty obtiene de antemano todos los datos de usuario necesarios al activar el SDK, lo que permite un acceso más rápido a datos actualizados. Sin embargo, esto puede ser un problema en el simulador de iOS, que durante el desarrollo suele pedir autenticación con frecuencia. Aunque Adapty no puede controlar el flujo de autenticación de StoreKit, sí puede aplazar las solicitudes que realiza el SDK para obtener datos de usuario actualizados. Al activar la propiedad `__debugDeferActivation`, la llamada de activación se retiene hasta que realizas la siguiente llamada al SDK de Adapty. Esto evita solicitudes innecesarias de datos de autenticación cuando no son necesarias. Es importante tener en cuenta que **esta función está pensada solo para uso en desarrollo**, ya que no cubre todos los escenarios de usuario posibles. En producción, la activación no debe retrasarse, ya que los dispositivos reales suelen recordar los datos de autenticación y no solicitan las credenciales repetidamente. Este es el enfoque recomendado para su uso: ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __debugDeferActivation: isSimulator(), // 'isSimulator' from any 3rd party library }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` #### Solucionar errores de activación del SDK con Fast Refresh de React Native \{#troubleshoot-sdk-activation-errors-on-react-natives-fast-refresh\} Al desarrollar con el SDK de Adapty en React Native, es posible que encuentres el error: `Adapty can only be activated once. Ensure that the SDK activation call is not made more than once.` Esto ocurre porque la función de fast refresh de React Native activa múltiples llamadas de activación durante el desarrollo. Para evitarlo, usa la opción `__ignoreActivationOnFastRefresh` con el valor `__DEV__` (el indicador del modo de desarrollo de React Native). ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __ignoreActivationOnFastRefresh: __DEV__, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` #### Configura el modo mock para pruebas locales \{#set-up-mock-mode-for-local-testing\} Para el desarrollo local y las pruebas, puedes activar el modo mock para evitar necesitar cuentas de sandbox de App Store/Google Play y agilizar las iteraciones. El modo mock omite completamente los módulos nativos de Adapty y devuelve datos simulados. :::important El modo mock **no** es una herramienta para probar compras reales: - **No abre** los flujos de compra de App Store / Google Play y **no crea** transacciones reales. - **No renderiza** paywalls/onboardings creados con **Adapty Paywall Builder (AdaptyUI)**. - Los módulos nativos de Adapty se **omiten por completo**—incluso la ausencia de archivos del SDK nativo en la compilación de Xcode/Android o una API key inválida no generarán errores. - No se envían datos a los servidores de Adapty. Para probar compras reales y paywalls del Paywall Builder, desactiva el modo mock y usa cuentas sandbox. ::: Para activar el modo mock, establece `enableMock` en `true`: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { enableMock: true, }); ``` Cuando el modo mock está activo: - Todos los métodos de Adapty devuelven datos de prueba sin realizar solicitudes de red a los servidores de Adapty. - Por defecto, el perfil mock inicial no tiene suscripciones activas. - Por defecto, `makePurchase(...)` simula una compra exitosa y concede acceso premium. Puedes personalizar los datos mock usando `mockConfig` durante la activación. Consulta el formato de configuración y los parámetros disponibles [aquí](https://react-native.adapty.io/interfaces/adaptymockconfig). ```typescript showLineNumbers title="App.tsx" try { await adapty.activate('YOUR_PUBLIC_SDK_KEY', { mockConfig: { // Customize the initial mock profile (optional) }, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); } ``` Si necesitas llamar a métodos del SDK antes de la activación (como `isActivated()` o `setLogLevel()`), usa `enableMock()` antes de `activate()`. Si el puente ya está inicializado, este método no hace nada. ```typescript showLineNumbers title="App.tsx" adapty.enableMock(); // Optional: pass mockConfig to customize mock data // Now you can call methods before activation await adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` ## Solución de problemas \{#troubleshooting\} #### Error de versión mínima de iOS \{#minimum-ios-version-error\} Si obtienes un error de versión mínima de iOS, actualiza tu Podfile: ```diff -platform :ios, min_ios_version_supported +platform :ios, '15.0' ``` #### Conflicto en el manifiesto de Android Auto Backup \{#android-auto-backup-manifest-conflict\} Algunos SDKs (incluido Adapty) incluyen su propia configuración de Android Auto Backup. Si utilizas varios SDKs que definen reglas de copia de seguridad, el fusionador de manifiestos de Android puede fallar con un error relacionado con `android:fullBackupContent`, `android:dataExtractionRules` o `android:allowBackup`. Síntomas típicos del error: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Estos cambios deben realizarse en el directorio de la plataforma Android (normalmente en la carpeta `android/` de tu proyecto). ::: Para resolverlo, necesitas: - Indicar al fusionador de manifiestos que use los valores de tu app para los atributos relacionados con la copia de seguridad. - Crear archivos de reglas de copia de seguridad que combinen las reglas de Adapty con las de otros SDKs. #### 1. Añade el namespace `tools` a tu manifiesto \{#1-add-the-tools-namespace-to-your-manifest\} En tu archivo `AndroidManifest.xml`, asegúrate de que la etiqueta raíz `<manifest>` incluya tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. Sobreescribe los atributos de copia de seguridad en `<application>` \{#2-override-backup-attributes-in-application\} En el mismo archivo `AndroidManifest.xml`, actualiza la etiqueta `<application>` para que tu app proporcione los valores definitivos e indique al fusionador de manifiestos que reemplace los valores de las librerías: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` Si algún SDK también define `android:allowBackup`, inclúyelo en `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Crea los archivos de reglas de copia de seguridad combinadas \{#3-create-merged-backup-rules-files\} Crea archivos XML en el directorio `res/xml/` de tu proyecto Android que combinen las reglas de Adapty con las de otros SDKs. Android utiliza distintos formatos de reglas de copia de seguridad según la versión del sistema operativo, por lo que crear ambos archivos garantiza la compatibilidad con todas las versiones de Android que admite tu app. :::note Los ejemplos a continuación usan AppsFlyer como SDK de terceros de muestra. Reemplaza o añade reglas para cualquier otro SDK que uses en tu app. ::: **Para Android 12 y superior** (usa el nuevo formato de reglas de extracción de datos): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Para Android 11 e inferior** (usa el formato legado de contenido de copia de seguridad completa): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> #### Las compras fallan al volver desde otra app en Android \{#purchases-fail-after-returning-from-another-app-in-android\} Si la Activity que inicia el flow de compra usa un `launchMode` distinto al predeterminado, Android puede recrearla o reutilizarla de forma incorrecta cuando el usuario regresa desde Google Play, una app bancaria o un navegador. Esto puede provocar que el resultado de la compra se pierda o se trate como cancelado. Para garantizar que las compras funcionen correctamente, utiliza solo los modos de inicio `standard` o `singleTop` para la Activity que inicia el flujo de compra, y evita cualquier otro modo. En tu `AndroidManifest.xml`, asegúrate de que la Activity que inicia el flujo de compra esté configurada como `standard` o `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### Errores de compilación de Swift 6 causados por la anulación de SWIFT_VERSION en el Podfile \{#swift-6-build-errors-caused-by-podfile-swift-version-override\} Al compilar tu app de React Native para iOS, puede que veas errores de compilación de Swift 6 en los targets de los pods de Adapty. Los síntomas típicos incluyen discrepancias con `@Sendable` en `AdaptyUIBuilderLogic`, falta de conformidad con `Sendable` en tipos de Adapty, o errores de aislamiento de actores. Los pods de Adapty declaran `s.swift_version = '6.0'` y requieren Swift 6 para compilar. Tu propio código puede quedarse en Swift 5 — solo los targets de los pods de Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`, `AdaptyLogger`, `AdaptyPlugin`) necesitan compilarse con Swift 6. La causa más común es un hook `post_install` en `ios/Podfile` que sobreescribe `SWIFT_VERSION` para todos los targets del pod: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **Solución**: Excluye los targets del pod de Adapty del override: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` Luego ejecuta `pod install` desde el directorio `ios/` y vuelve a compilar. Para verificarlo, abre `ios/Pods/Pods.xcodeproj`, selecciona el target del pod `Adapty` → **Build Settings** → **Swift Language Version**. Debería mostrar **Swift 6**. --- # File: react-native-quickstart-paywalls --- --- title: "Habilitar compras con Flow Builder en React Native SDK" description: "Guía de inicio rápido para habilitar compras in-app con Adapty Flow Builder." --- Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Flows**](adapty-flow-builder) – secuencias de pantallas que presentan productos a los usuarios, creadas en el Flow Builder sin código. El SDK las recupera mediante `getFlow`. Si prefieres construir la interfaz en tu propio código, usa un paywall en su lugar — consulta [Implementar paywalls manualmente](react-native-quickstart-manual). - [**Placements**](placements) – dónde y cuándo mostrar los flows en tu app (como `main`, `onboarding`, `settings`). Asocias los flows a los placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar flows distintos a diferentes usuarios. Adapty te ofrece tres formas de habilitar compras en tu aplicación. Elige la que mejor se adapte a tus necesidades: | Implementación | Complejidad | Cuándo usar | |---|---|---| | Adapty Flow Builder | ✅ Fácil | [Creas un flow completo y listo para compras en el builder sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona todo el flujo de compra, la validación de recibos y la gestión de suscripciones en segundo plano. | | Paywalls creados manualmente | 🟡 Media | Implementas la UI de tu paywall en el código de tu app, pero obtienes el objeto flow de Adapty para mantener flexibilidad en las ofertas de productos. Consulta la [guía](react-native-quickstart-manual). | | Modo observador | 🔴 Difícil | Ya tienes tu propia infraestructura de gestión de compras y quieres seguir usándola. Ten en cuenta que el modo observador tiene sus limitaciones en Adapty. Consulta el [artículo](observer-vs-full-mode). | :::important **Los pasos a continuación muestran cómo implementar un flow creado en el Adapty Flow Builder.** Si prefieres construir la UI del paywall tú mismo, consulta [Implementar paywalls manualmente](react-native-quickstart-manual). ::: Para mostrar un flow creado en el Adapty Flow Builder, en el código de tu app solo necesitas: 1. **Obtener el flow**: Obténlo desde Adapty. 2. **Mostrarlo y Adapty gestionará las compras por ti**: Muestra la vista en tu app. 3. **Gestionar las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abrir enlaces o cerrar el flow cuando los usuarios pulsen botones. ## Antes de empezar \{#before-you-start\} Antes de empezar, completa estos pasos: 1. Conecta tu app al [App Store](initial_ios) y/o [Google Play](initial-android) en el Adapty Dashboard. 2. [Crea tus productos](create-product) en Adapty. 3. [Crea un paywall y añade productos](create-paywall). 4. [Crea un placement y añade tu paywall](create-placement). 5. [Instala y activa el SDK de Adapty](sdk-installation-reactnative) en el código de tu app. :::tip La forma más rápida de completar estos pasos es seguir la [guía de inicio rápido](quickstart) o crear paywalls y placements usando el [CLI para desarrolladores](developer-cli-quickstart). ::: ## 1. Obtén el flow \{#1-get-the-flow\} Tus flows están asociados con placements configurados en el dashboard. Los placements te permiten ejecutar distintos flows para diferentes audiencias o realizar [pruebas A/B](ab-tests). Para obtener un flow creado en el Adapty Flow Builder, obtén el objeto `flow` mediante el ID del [placement](placements) usando el método `getFlow`. El flow contiene los elementos de UI y el estilo necesarios para mostrarlo. ```typescript showLineNumbers title="React Native" try { const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); // the requested flow } catch (error) { // handle the error } ``` ## 2. Mostrar el flow \{#display-the-flow\} Ahora que tienes el flow, basta con añadir unas pocas líneas para mostrarlo. <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para insertar un flow dentro de tu árbol de componentes existente, usa el componente `AdaptyFlowView` directamente en la jerarquía de componentes de React Native: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>( (result, product) => result.type !== 'user_cancelled', [], ); return ( <AdaptyFlowView flow={flow} style={{ flex: 1 }} onPurchaseCompleted={onPurchaseCompleted} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> Para mostrar el flow como una pantalla independiente, crea un `view` con el método `createFlowView`, define sus manejadores de eventos y llama a `view.present()`. Cada `view` solo puede usarse una vez. Si necesitas mostrar el flow de nuevo, llama a `createFlowView` otra vez para crear una nueva instancia de `view`. ```typescript showLineNumbers title="React Native" try { const view = await createFlowView(flow); view.setEventHandlers({ onPurchaseCompleted(result, product) { return result.type !== 'user_cancelled'; }, }); await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> :::tip Para más detalles sobre cómo mostrar un flow, consulta nuestra [guía](react-native-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#3-handle-button-actions\} Cuando los usuarios pulsan botones en el flow, el SDK de React Native gestiona automáticamente las compras, la restauración, el cierre del flow y la apertura de URLs. Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren gestionar las acciones en tu código. O puede que quieras sobreescribir su comportamiento predeterminado. Por ejemplo, aquí tienes el comportamiento predeterminado del botón de cerrar. No necesitas añadirlo en el código, pero aquí puedes ver cómo se hace si fuera necesario. <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, gestiona las acciones directamente en el componente `AdaptyFlowView`: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>( () => true, // allow the flow to close [], ); const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>( (actionId) => false, [], ); return ( <AdaptyFlowView flow={flow} style={{ flex: 1 }} onCloseButtonPress={onCloseButtonPress} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="Presentación modal"> Para la presentación modal, implementa los manejadores de eventos usando `setEventHandlers`: ```typescript showLineNumbers title="React Native" const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; // allow the flow to close }, }); ``` </TabItem> </Tabs> :::tip Lee nuestras guías sobre cómo gestionar [acciones](react-native-handle-paywall-actions) y [eventos](react-native-handling-events-1) de botones. ::: ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox del App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. Ahora necesitas [comprobar el nivel de acceso de los usuarios](react-native-check-subscription-status) para asegurarte de mostrar un paywall o dar acceso a las funciones de pago a los usuarios correctos. ## Ejemplo completo \{#full-example\} Aquí se muestra cómo integrar todos los pasos de esta guía en tu aplicación. <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="Componente React" default> ```javascript showLineNumbers title="React Native (TSX)" export default function FlowScreen() { const [flow, setFlow] = useState(null); const loadFlow = async () => { try { const flowData = await adapty.getFlow('YOUR_PLACEMENT_ID'); setFlow(flowData); } catch (error) { console.warn('Error loading flow:', error); } }; const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>( () => true, [], ); const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>( (result, product) => result.type !== 'user_cancelled', [], ); useEffect(() => { loadFlow(); }, []); return ( <View style={{ flex: 1 }}> {flow ? ( <AdaptyFlowView flow={flow} style={{ flex: 1 }} onCloseButtonPress={onCloseButtonPress} onPurchaseCompleted={onPurchaseCompleted} /> ) : ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Load Flow" onPress={loadFlow} /> </View> )} </View> ); } ``` </TabItem> <TabItem value="standalone" label="Presentación modal"> ```javascript showLineNumbers title="React Native" export default function FlowScreen() { const showFlow = async () => { try { const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); const view = await createFlowView(flow); view.setEventHandlers({ onCloseButtonPress() { return true; }, onPurchaseCompleted(result, product) { return result.type !== 'user_cancelled'; }, }); await view.present(); } catch (error) { // handle any error that may occur during the process console.warn('Error showing flow:', error); } }; // you can add a button to manually trigger the flow for testing purposes return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Show Flow" onPress={showFlow} /> </View> ); } ``` </TabItem> </Tabs> --- # File: react-native-check-subscription-status --- --- title: "Comprobar el estado de la suscripción en el SDK de React Native" description: "Aprende a comprobar el estado de la suscripción en tu app de React Native con Adapty." --- Para decidir si los usuarios pueden acceder a contenido de pago o ver un paywall, necesitas comprobar su [nivel de acceso](access-level) en el perfil. Este artículo te muestra cómo acceder al estado del perfil para decidir qué deben ver los usuarios: si mostrarles un paywall o concederles acceso a las funciones de pago. ## Obtener el estado de la suscripción \{#get-subscription-status\} Cuando decides si mostrar un paywall o contenido de pago a un usuario, compruebas su [nivel de acceso](access-level) en su perfil. Tienes dos opciones: - Llama a `getProfile` si necesitas los datos más recientes del perfil de inmediato (por ejemplo, al arrancar la app) o quieres forzar una actualización. - Configura **actualizaciones automáticas del perfil** para mantener una copia local que se refresca automáticamente cada vez que cambia el estado de la suscripción. ### Obtener el perfil \{#get-profile\} La forma más sencilla de obtener el estado de la suscripción es usar el método `getProfile` para acceder al perfil: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); } catch (error) { // handle the error } ``` ### Escuchar actualizaciones de la suscripción \{#listen-to-subscription-updates\} Para recibir actualizaciones del perfil automáticamente en tu app: 1. Usa `adapty.addEventListener('onLatestProfileLoad')` para escuchar cambios en el perfil: Adapty llamará a este método automáticamente cada vez que cambie el estado de la suscripción del usuario. 2. Guarda los datos del perfil actualizado cuando se llame a este método, para poder usarlos en toda tu app sin hacer peticiones de red adicionales. ```javascript class SubscriptionManager { private currentProfile: any = null; constructor() { // Listen for profile updates adapty.addEventListener('onLatestProfileLoad', (profile) => { this.currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() hasAccess(): boolean { return this.currentProfile?.accessLevels?.['premium']?.isActive ?? false; } } ``` :::note Adapty llama automáticamente al listener del evento `onLatestProfileLoad` cuando arranca tu app, proporcionando datos de suscripción en caché incluso si el dispositivo está sin conexión. ::: ## Conectar el perfil con la lógica del paywall \{#connect-profile-with-paywall-logic\} Cuando necesitas tomar decisiones inmediatas sobre mostrar paywalls o conceder acceso a funciones de pago, puedes comprobar el perfil del usuario directamente. Este enfoque es útil en situaciones como el arranque de la app, al entrar en secciones premium o antes de mostrar contenido específico. ```javascript const checkAccessLevel = async () => { try { const profile = await adapty.getProfile(); return profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive === true; } catch (error) { console.warn('Error checking access level:', error); return false; // Show paywall if access check fails } }; const initializePaywall = async () => { try { await loadPaywall(); const hasAccess = await checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } catch (error) { console.warn('Error initializing paywall:', error); } }; ``` ## Próximos pasos \{#next-steps\} Ahora que sabes cómo hacer seguimiento del estado de la suscripción, aprende a [trabajar con perfiles de usuario](react-native-quickstart-identify) para asegurarte de que pueden acceder a lo que han pagado. --- # File: react-native-quickstart-identify --- --- title: "Identificar usuarios en el SDK de React Native" description: "Guía de inicio rápido para configurar Adapty para la gestión de suscripciones in-app en React Native." --- :::important Esta guía es para ti si tienes tu propio sistema de autenticación. Aquí aprenderás a trabajar con perfiles de usuario en Adapty para que se alinee con tu sistema de autenticación existente. ::: La forma en que gestionas las compras de los usuarios depende del modelo de autenticación de tu app: - Si tu app no usa autenticación de backend ni almacena datos de usuario, consulta la [sección sobre usuarios anónimos](#anonymous-users). - Si tu app tiene (o tendrá) autenticación de backend, consulta la [sección sobre usuarios identificados](#identified-users). **Conceptos clave**: - Los **perfiles** son las entidades necesarias para que el SDK funcione. Adapty los crea automáticamente. - Pueden ser anónimos **(sin customer user ID)** o identificados **(con customer user ID)**. - Proporcionas el **customer user ID** para cruzar los perfiles de Adapty con tu sistema de autenticación interno. Esto es lo que diferencia a los usuarios anónimos de los identificados: | | Usuarios anónimos | Usuarios identificados | |------------------------------|----------------------------------------------------------|------------------------------------------------------------------------------------| | **Gestión de compras** | Restauración de compras a nivel de store | Mantienen el historial de compras en todos los dispositivos mediante su customer user ID | | **Gestión de perfiles** | Nuevos perfiles en cada reinstalación | El mismo perfil en todas las sesiones y dispositivos | | **Persistencia de datos** | Los datos de usuarios anónimos están ligados a la instalación de la app | Los datos de usuarios identificados persisten entre instalaciones de la app | ## Usuarios anónimos \{#anonymous-users\} Si no tienes autenticación de backend, **no necesitas gestionar la autenticación en el código de la app**: 1. Cuando el SDK se activa en el primer inicio de la app, Adapty **crea un nuevo perfil para el usuario**. 2. Cuando el usuario realiza una compra en la app, esta compra queda **asociada a su perfil de Adapty y a su cuenta en el store**. 3. Cuando el usuario **reinstala** la app o la instala en un **nuevo dispositivo**, Adapty **crea un nuevo perfil anónimo al activarse**. 4. Si el usuario ya había realizado compras en tu app, por defecto, sus compras se sincronizan automáticamente desde el App Store al activarse el SDK. Así, con usuarios anónimos se crearán nuevos perfiles en cada instalación, pero no es un problema porque, en los análisis de Adapty, puedes [configurar qué se considerará una nueva instalación](general#4-installs-definition-for-analytics). :::note Las restauraciones desde copia de seguridad se comportan de forma diferente a las reinstalaciones. Por defecto, cuando un usuario restaura desde una copia de seguridad, el SDK conserva los datos en caché y no crea un nuevo perfil. Puedes configurar este comportamiento con el ajuste `clearDataOnBackup`. [Más información](sdk-installation-react-native-pure#clear-data-on-backup-restore). ::: Para los usuarios anónimos, debes contar las instalaciones por **IDs de dispositivo**. En este caso, cada instalación de la app en un dispositivo se cuenta como una instalación, incluidas las reinstalaciones. ## Usuarios identificados \{#identified-users\} Tienes dos opciones para identificar usuarios en la app: - [**Durante el inicio de sesión/registro:**](#during-loginsignup) Si los usuarios inician sesión después de que tu app arranque, llama a `identify()` con un customer user ID cuando se autentiquen. - [**Durante la activación del SDK:**](#during-the-sdk-activation) Si ya tienes un customer user ID almacenado cuando se inicia la app, envíalo al llamar a `activate()`. :::important Por defecto, cuando Adapty recibe una compra de un Customer User ID que está actualmente asociado a otro Customer User ID, el nivel de acceso se comparte, de modo que ambos perfiles tienen acceso de pago. Puedes configurar este ajuste para transferir el acceso de pago de un perfil a otro o desactivar el uso compartido por completo. Consulta el [artículo](general#6-sharing-paid-access-between-user-accounts) para más detalles. ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Durante el inicio de sesión/registro \{#during-loginsignup\} Si identificas a los usuarios después del inicio de la app (por ejemplo, cuando inician sesión o se registran), usa el método `identify` para establecer su customer user ID. - Si **no has usado este customer user ID antes**, Adapty lo vinculará automáticamente al perfil actual. - Si **ya has usado este customer user ID para identificar al usuario antes**, Adapty pasará a trabajar con el perfil asociado a ese customer user ID. :::important Los customer user IDs deben ser únicos para cada usuario. Si hardcodeas el valor del parámetro, todos los usuarios se considerarán como uno solo. ::: Siempre usa `await` con `identify` antes de llamar a otros métodos del SDK. Las llamadas concurrentes generan `#3006 profileWasChanged` o aterrizan en el perfil anónimo. Consulta [Orden de llamadas en el SDK de React Native](react-native-sdk-call-order). ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` ### Durante la activación del SDK \{#during-the-sdk-activation\} Si ya conoces el customer user ID cuando activas el SDK, puedes enviarlo en el método `activate` en lugar de llamar a `identify` por separado. Si conoces un customer user ID pero solo lo configuras después de la activación, eso significa que, al activarse, Adapty creará un nuevo perfil anónimo y solo pasará al existente cuando llames a `identify`. Puedes pasar un customer user ID existente (uno que ya hayas usado antes) o uno nuevo. Si pasas uno nuevo, el perfil creado al activarse se vinculará automáticamente a ese customer user ID. :::note Por defecto, la creación de perfiles anónimos no afecta a los dashboards de análisis, porque las instalaciones se cuentan por IDs de dispositivo. Un ID de dispositivo representa una única instalación de la app desde el store en un dispositivo y solo se regenera cuando la app se reinstala. No depende de si es la primera instalación o una repetida, ni de si se usa un customer user ID existente. Crear un perfil (al activar el SDK o al cerrar sesión), iniciar sesión o actualizar la app sin reinstalarla no genera eventos de instalación adicionales. Si quieres contar las instalaciones por usuarios únicos en lugar de por dispositivos, ve a **App settings** y configura [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```typescript showLineNumbers adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID" // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. }); ``` ### Cerrar sesión de usuarios \{#log-users-out\} Si tienes un botón para cerrar la sesión de los usuarios, usa el método `logout`. :::important Cerrar la sesión crea un nuevo perfil anónimo para el usuario. ::: ```typescript showLineNumbers try { await adapty.logout(); // successful logout } catch (error) { // handle the error } ``` :::info Para volver a iniciar sesión en la app, usa el método `identify`. ::: ### Permitir compras sin inicio de sesión \{#allow-purchases-without-login\} Si tus usuarios pueden realizar compras tanto antes como después de iniciar sesión en tu app, debes asegurarte de que mantengan el acceso después de iniciar sesión: 1. Cuando un usuario sin sesión iniciada realiza una compra, Adapty la vincula a su ID de perfil anónimo. 2. Cuando el usuario inicia sesión en su cuenta, Adapty pasa a trabajar con su perfil identificado. - Si es un customer user ID nuevo (por ejemplo, la compra se realizó antes del registro), Adapty asigna el customer user ID al perfil actual, por lo que se mantiene todo el historial de compras. - Si es un customer user ID existente (el customer user ID ya está vinculado a un perfil), necesitas obtener el nivel de acceso actual después del cambio de perfil. Puedes llamar a [`getProfile`](react-native-check-subscription-status) justo después de la identificación, o [escuchar las actualizaciones del perfil](react-native-check-subscription-status) para que los datos se sincronicen automáticamente. ## Próximos pasos \{#next-steps\} ¡Enhorabuena! Has implementado la lógica de pago in-app en tu app. ¡Te deseamos todo lo mejor con la monetización de tu app! Para sacar aún más partido a Adapty, puedes explorar estos temas: - [**Pruebas**](troubleshooting-test-purchases): Asegúrate de que todo funciona como se espera - [**Onboardings**](react-native-onboardings): Engancha a los usuarios con onboardings y aumenta la retención - [**Integraciones**](configuration): Integra con servicios de atribución de marketing y análisis con una sola línea de código - [**Establecer atributos de perfil personalizados**](react-native-setting-user-attributes): Añade atributos personalizados a los perfiles de usuario y crea segmentos para lanzar pruebas A/B o mostrar diferentes paywalls a distintos usuarios --- # File: adapty-sdk-integration-skill-react-native --- --- title: "Integra Adapty en tu app de React Native con la skill de integración del SDK" description: "Usa la skill adapty-sdk-integration para integrar el SDK de Adapty en tu app de React Native de principio a fin con tu herramienta de codificación con IA." --- La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. :::important La skill está en beta. Si se queda bloqueada o se comporta de forma inesperada, sigue la [guía de integración paso a paso](adapty-cursor-react-native) — te lleva a través de cada etapa con la documentación correcta. ::: La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. --- # File: adapty-cursor-react-native --- --- title: "Integra Adapty en tu app React Native con ayuda de IA" description: "Una guía paso a paso para integrar Adapty en tu app React Native usando Cursor, Context7, ChatGPT, Claude u otras herramientas de IA." --- Esta guía te lleva paso a paso por la integración de Adapty en tu app de React Native con una herramienta de codificación con IA: le proporcionas la documentación correcta de Adapty en el orden correcto. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Antes de empezar: configuración del dashboard \{#before-you-start-dashboard-setup\} Adapty necesita algo de configuración en el dashboard antes de que escribas código con el SDK. Puedes hacerlo con una skill interactiva de LLM o manualmente desde el Dashboard. ### Enfoque con skill (recomendado) \{#skill-approach-recommended\} El skill de Adapty CLI permite que tu LLM configure tu app, productos, niveles de acceso, paywalls y placements directamente — sin necesidad de abrir el Dashboard en cada paso. Solo necesitas [conectar tus stores](integrate-payments) en el Dashboard. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` Una vez añadido el skill, ejecuta `/adapty-cli` en tu agente. Te guiará paso a paso — incluyendo cuándo abrir el Dashboard para conectar tus stores. ### Enfoque desde el dashboard Si prefieres configurar todo de forma manual, esto es lo que necesitas antes de escribir código. Tu LLM no puede buscar los valores del dashboard por ti — tendrás que proporcionarlos. 1. **Conecta tus app stores**: En el Adapty Dashboard, ve a **App settings → General**. Conecta tanto App Store como Google Play si tu app tiene como destino ambas plataformas. Esto es necesario para que las compras funcionen. [Conecta los app stores](integrate-payments) 2. **Copia tu clave SDK pública**: En el Adapty Dashboard, ve a **App settings → General** y busca la sección **API keys**. En el código, es la cadena que pasas a `adapty.activate("YOUR_PUBLIC_SDK_KEY")`. 3. **Crea al menos un producto**: En el Adapty Dashboard, ve a la página **Products**. No referencias los productos directamente en el código — Adapty los entrega a través de paywalls. [Añadir productos](quickstart-products) 4. **Crea un paywall y un placement**: En el Adapty Dashboard, crea un paywall en la página **Paywalls** y luego asígnalo a un placement en la página **Placements**. En el código, el ID del placement es la cadena que pasas a `adapty.getPaywall("YOUR_PLACEMENT_ID")`. [Crear paywall](quickstart-paywalls) 5. **Configura los niveles de acceso**: En el Adapty Dashboard, configúralos por producto en la página **Products**. En el código, la cadena que se comprueba es `profile.accessLevels['premium']?.isActive`. El nivel de acceso `premium` predeterminado funciona para la mayoría de las apps. Si los usuarios de pago acceden a funciones distintas según el producto (por ejemplo, un plan `basic` frente a un plan `pro`), [crea niveles de acceso adicionales](assigning-access-level-to-a-product) antes de empezar a programar. :::tip Una vez que tengas los cinco, estás listo para escribir código. Dile a tu LLM: "Mi clave SDK pública es X, mi ID de placement es Y" para que pueda generar el código de inicialización y de obtención de paywall correctamente. ::: ### Configura cuando estés listo \{#set-up-when-ready\} Estos pasos no son necesarios para empezar a programar, pero los necesitarás a medida que tu integración madure: - **Pruebas A/B**: Configúralas en la página **Placements**. No se requieren cambios en el código. [Pruebas A/B](ab-tests) - **Paywalls y placements adicionales**: Añade más llamadas `getPaywall` con distintos IDs de placement. - **Integraciones de analíticas**: Configúralas en la página **Integrations**. La configuración varía según la integración. Consulta [integraciones de analíticas](analytics-integration) e [integraciones de atribución](attribution-integration). ## Proporciona la documentación de Adapty a tu LLM \{#feed-adapty-docs-to-your-llm\} ### Usar Context7 (recomendado) [Context7](https://context7.com) es un servidor MCP que da a tu LLM acceso directo a la documentación actualizada de Adapty. Tu LLM obtiene automáticamente la documentación correcta según lo que preguntes, sin necesidad de pegar URLs manualmente. Context7 funciona con **Cursor**, **Claude Code**, **Windsurf** y otras herramientas compatibles con MCP. Para configurarlo, ejecuta: ``` npx ctx7 setup ``` Esto detecta tu editor y configura el servidor Context7. Para la configuración manual, consulta el [repositorio de Context7 en GitHub](https://github.com/upstash/context7). Una vez configurado, haz referencia a la biblioteca de Adapty en tus prompts: ``` Use the adaptyteam/adapty-docs library to look up how to install the React Native SDK ``` :::warning Aunque Context7 elimina la necesidad de pegar enlaces de documentación manualmente, el orden de implementación es importante. Sigue el [recorrido de implementación](#implementation-walkthrough) paso a paso para asegurarte de que todo funciona correctamente. ::: ### Usa los documentos en texto plano Puedes acceder a cualquier documento de Adapty en texto plano Markdown. Añade `.md` al final de su URL, o haz clic en **Copy for LLM** debajo del título del artículo. Por ejemplo: [adapty-cursor-react-native.md](https://adapty.io/docs/es/adapty-cursor-react-native.md). Cada etapa del [recorrido de implementación](#implementation-walkthrough) a continuación incluye un bloque "Send this to your LLM" con enlaces `.md` para pegar. Para acceder a más documentación de una vez, consulta los [archivos de índice y subconjuntos por plataforma](#plain-text-doc-index-files) a continuación. ## Guía de implementación paso a paso \{#implementation-walkthrough\} El resto de esta guía recorre la integración de Adapty en orden de implementación. Cada etapa incluye la documentación que debes enviar a tu LLM, qué deberías ver al terminar y los problemas más habituales. ### Planifica tu integración \{#plan-your-integration\} Antes de escribir código, pídele a tu LLM que analice tu proyecto y cree un plan de implementación. Si tu herramienta de IA tiene un modo de planificación (como el modo plan de Cursor o Claude Code), úsalo para que el LLM pueda leer tanto la estructura de tu proyecto como la documentación de Adapty antes de generar cualquier código. Dile a tu LLM qué enfoque usas para las compras, ya que esto determina qué guías debe seguir: - [**Adapty Paywall Builder**](adapty-paywall-builder): Creas paywalls en el editor sin código de Adapty y el SDK los renderiza automáticamente. - [**Paywalls creados manualmente**](react-native-making-purchases): Construyes tu propia interfaz de paywall en código, pero sigues usando Adapty para obtener productos y gestionar compras. - [**Modo observer**](observer-vs-full-mode): Mantienes tu infraestructura de compras existente y usas Adapty solo para análisis e integraciones. ¿No sabes cuál elegir? Consulta la [tabla comparativa en la guía de inicio rápido](react-native-quickstart-paywalls). ### Instalar y configurar el SDK \{#install-and-configure-the-sdk\} Añade la dependencia del SDK de Adapty con npm (o yarn) y actívalo con tu clave SDK pública. Esta es la base: sin ella, nada más funciona. Tenemos guías de instalación independientes para proyectos Expo y React Native puro — elige la que corresponda a tu configuración. **Guías:** - [Instalar con Expo](sdk-installation-react-native-expo) - [Instalar con React Native puro](sdk-installation-react-native-pure) ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/sdk-installation-react-native-expo.md - https://adapty.io/docs/es/sdk-installation-react-native-pure.md ``` :::tip[Checkpoint] - **Esperado:** La app se compila y ejecuta tanto en iOS como en Android. Los logs de Metro bundler muestran el log de activación de Adapty. - **Error frecuente:** "Public API key is missing" → comprueba que hayas reemplazado el marcador de posición con tu clave real desde **App settings**. ::: ### Mostrar paywalls y gestionar compras \{#show-paywalls-and-handle-purchases\} Obtén un paywall por su ID de placement, muéstralo y gestiona los eventos de compra. Las guías que necesitas dependen de cómo gestiones las compras. Prueba cada compra en el sandbox a medida que avanzas — no esperes hasta el final. Consulta [Probar compras en sandbox](test-purchases-in-sandbox) para ver las instrucciones de configuración. <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Paywall Builder" default> **Guías:** - [Habilitar compras con paywalls (inicio rápido)](react-native-quickstart-paywalls) - [Obtener paywalls del Paywall Builder y su configuración](react-native-get-pb-paywalls) - [Mostrar paywalls](react-native-present-paywalls) - [Gestionar eventos de paywall](react-native-handling-events-1) - [Responder a las acciones de los botones](react-native-handle-paywall-actions) Read these Adapty docs before writing code: - https://adapty.io/docs/es/react-native-quickstart-paywalls.md - https://adapty.io/docs/es/react-native-get-pb-paywalls.md - https://adapty.io/docs/es/react-native-present-paywalls.md - https://adapty.io/docs/es/react-native-handling-events-1.md - https://adapty.io/docs/es/react-native-handle-paywall-actions.md :::tip[Checkpoint] - **Esperado:** El paywall aparece con los productos configurados. Al tocar un producto, se activa el diálogo de compra sandbox. - **Problema habitual:** Paywall vacío o error en `getPaywall` → verifica que el ID del placement coincida exactamente con el dashboard y que el placement tenga una audiencia asignada. ::: </TabItem> <TabItem value="manual" label="Paywalls manuales"> **Guías:** - [Activar compras en tu paywall personalizado (inicio rápido)](react-native-quickstart-manual) - [Obtener paywalls y productos](fetch-paywalls-and-products-react-native) - [Renderizar paywall diseñado con Remote Config](present-remote-config-paywalls-react-native) - [Realizar compras](react-native-making-purchases) - [Restaurar compras](react-native-restore-purchase) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/react-native-quickstart-manual.md - https://adapty.io/docs/es/fetch-paywalls-and-products-react-native.md - https://adapty.io/docs/es/present-remote-config-paywalls-react-native.md - https://adapty.io/docs/es/react-native-making-purchases.md - https://adapty.io/docs/es/react-native-restore-purchase.md ``` :::tip[Checkpoint] - **Expected:** Tu paywall personalizado muestra los productos obtenidos desde Adapty. Al pulsar un producto, se activa el diálogo de compra en sandbox. - **Gotcha:** Array de productos vacío → verifica que el paywall tenga productos asignados en el dashboard y que el placement tenga una audiencia. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Guías:** - [Descripción general del Observer mode](observer-vs-full-mode) - [Implementar el Observer mode](implement-observer-mode-react-native) - [Reportar transacciones en el Observer mode](report-transactions-observer-mode-react-native) Lee estos documentos de Adapty antes de escribir código: - https://adapty.io/docs/es/observer-vs-full-mode.md - https://adapty.io/docs/es/implement-observer-mode-react-native.md - https://adapty.io/docs/es/report-transactions-observer-mode-react-native.md :::tip[Punto de control] - **Resultado esperado:** Tras una compra en sandbox usando tu flow de compra existente, la transacción aparece en el **Event Feed** del Adapty Dashboard. - **Problema frecuente:** Sin eventos → verifica que estás reportando las transacciones a Adapty y que las notificaciones de servidor están configuradas para ambos stores. ::: </TabItem> </Tabs> ### Comprobar el estado de la suscripción \{#check-subscription-status\} Tras una compra, consulta el perfil del usuario para verificar si tiene un nivel de acceso activo y así controlar el acceso al contenido premium. **Guía:** [Comprobar el estado de la suscripción](react-native-check-subscription-status) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/react-native-check-subscription-status.md ``` :::tip[Checkpoint] - **Resultado esperado:** Tras una compra en sandbox, `profile.accessLevels['premium']?.isActive` devuelve `true`. - **Problema frecuente:** `accessLevels` vacío después de la compra → comprueba que el producto tenga un nivel de acceso asignado en el dashboard. ::: ### Identificar usuarios \{#identify-users\} Vincula las cuentas de usuario de tu app con los perfiles de Adapty para que las compras persistan entre dispositivos. :::important Omite este paso si tu app no tiene autenticación. ::: **Guía:** [Identificar usuarios](react-native-quickstart-identify) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/react-native-quickstart-identify.md ``` :::tip[Checkpoint] - **Esperado:** Después de llamar a `adapty.identify("your-user-id")`, la sección **Profiles** del dashboard muestra tu ID de usuario personalizado. - **Problema frecuente:** Llama a `identify` después de la activación pero antes de obtener los paywalls para evitar que la atribución quede en un perfil anónimo. ::: ### Preparativos para el lanzamiento \{#prepare-for-release\} Una vez que tu integración funcione en el sandbox, repasa el checklist de lanzamiento para asegurarte de que todo está listo para producción. **Guía:** [Checklist de lanzamiento](release-checklist) Envía esto a tu LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/es/release-checklist.md ``` :::tip[Checkpoint] - **Expected:** Todos los elementos del checklist confirmados: conexiones con el store, notificaciones del servidor, flujo de compra, comprobación de niveles de acceso y requisitos de privacidad. - **Gotcha:** Faltan notificaciones del servidor → configura las App Store Server Notifications en **App settings → iOS SDK** y las Google Play Real-Time Developer Notifications en **App settings → Android SDK**. ::: ## Archivos de índice de documentación en texto plano \{#plain-text-doc-index-files\} Si necesitas darle a tu LLM un contexto más amplio que el de páginas individuales, disponemos de archivos de índice que listan o combinan toda la documentación de Adapty: - [`llms.txt`](https://adapty.io/docs/es/llms.txt): Lista todas las páginas con enlaces `.md`. Un [estándar emergente](https://llmstxt.org/) para hacer sitios web accesibles a LLMs. Ten en cuenta que para algunos agentes de IA (p. ej., ChatGPT) tendrás que descargar `llms.txt` y subirlo al chat como archivo. - [`llms-full.txt`](https://adapty.io/docs/es/llms-full.txt): Toda la documentación de Adapty combinada en un único archivo. Muy grande — úsalo solo cuando necesites una visión completa. - [`react-native-llms.txt`](https://adapty.io/docs/es/react-native-llms.txt) y [`react-native-llms-full.txt`](https://adapty.io/docs/es/react-native-llms-full.txt) específicos de React Native: subconjuntos por plataforma que ahorran tokens en comparación con el sitio completo. --- # File: react-native-get-pb-paywalls --- --- title: "Obtener flows y paywalls - React Native" description: "Obtén flows y paywalls de Adapty en tu app de React Native." --- <SDKv4> <MethodPromo method="getFlow" /> Después de [diseñar tu flow o paywall con Paywall Builder](adapty-paywall-builder), puedes mostrarlo en tu aplicación móvil. El primer paso es obtener el flow o paywall asociado al placement y su configuración de vista, tal como se describe a continuación. Ten en cuenta que este tema hace referencia a flows y paywalls personalizados con Paywall Builder. Si estás implementando tus paywalls de forma manual, consulta el tema [Obtener paywalls y productos para paywalls con Remote Config en tu aplicación móvil](fetch-paywalls-and-products-react-native). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a mostrar flows y paywalls en tu app móvil (haz clic para ampliar)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow/paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow/paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-reactnative) en tu app móvil. </details> ## Obtener flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall con el Flow Builder o el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese flow o paywall ya contiene tanto lo que debe mostrarse como la forma en que debe presentarse. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y, después, presentarlo en tu app. Obtén el flow o paywall y crea su [vista](react-native-get-pb-paywalls#fetch-the-view-configuration) con la mayor antelación posible, idealmente mucho antes de mostrarlo. El método `createFlowView` carga la configuración de la vista y comienza a descargar y cachear sus imágenes en segundo plano. Cuanto antes lo llames, más tiempo tendrán esas descargas para completarse. Para cuando presentes el flow o paywall, su configuración e imágenes ya pueden estar cacheadas y listas para mostrarse. Para obtener un flow o paywall, usa el método `getFlow`: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlow(placementId); // el flow/paywall solicitado } catch (error) { // manejar el error } ``` Parámetros: | Parámetro | Presencia | Descripción | |-------------------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché cuando existan. En este caso, los usuarios puede que no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché de actualización periódica descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN sea inaccesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.</p> | | **loadTimeoutMs** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el límite, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo indicado en `loadTimeout`, ya que la operación puede implicar distintas solicitudes internamente.</p><p>Para Android: puedes crear un `TimeInterval` con funciones de extensión (como `5.seconds`, donde `.seconds` proviene de `import com.adapty.utils.seconds`), o con `TimeInterval.seconds(5)`. Para no establecer ningún límite, usa `TimeInterval.INFINITE`.</p> | Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | Un objeto `AdaptyFlow` con los identificadores del flow (`id`, `variationId`), el nombre, el placement, sus variantes de paywall (`paywalls`) y los Remote Configs (`remoteConfigs`). | ## Obtener la configuración de la vista \{#fetch-the-view-configuration\} :::important Asegúrate de activar el botón **Show on device** en el builder. Si esta opción no está activada, la configuración de la vista no estará disponible para recuperar. ::: Si el placement fue diseñado en el **Flow Builder** o en el **Paywall Builder**, Adapty renderiza la interfaz por ti. Crea la vista con `createFlowView` y, a continuación, [presenta el flow o el paywall](react-native-present-paywalls). Si el placement es un paywall personalizado sin interfaz del Builder, [trátalo como un paywall de Remote Config](present-remote-config-paywalls-react-native). En el SDK de React Native, llama a `createFlowView` directamente — no necesitas obtener primero la configuración de la vista. :::warning El resultado del método `createFlowView` solo puede usarse una vez. Si necesitas usarlo de nuevo, llama de nuevo al método `createFlowView`. Llamarlo dos veces sin recrearlo puede provocar el error `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers try { const view = await createFlowView(flow); } catch (error) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | :------------------- | :-------- | :----------------------------------------------------------- | | **flow** | obligatorio | Un objeto `AdaptyFlow` para obtener un controlador del flow/paywall deseado. | | **customTags** | opcional | Define un diccionario de etiquetas personalizadas y sus valores resueltos. Las etiquetas personalizadas actúan como marcadores de posición en el contenido, reemplazados dinámicamente por cadenas específicas para personalizar el contenido dentro del flow/paywall. Consulta el tema Custom tags in paywall builder para más detalles. | | **prefetchProducts** | opcional | Actívalo para optimizar el momento en que se muestran los productos en pantalla. Cuando es `true`, AdaptyUI recuperará automáticamente los productos necesarios. Valor predeterminado: `false`. | | **android.enableSafeArea** | opcional | Solo para Android (se ignora en iOS). Pásalo como objeto anidado: `android: { enableSafeArea: true }`. Cuando es `true`, la vista del flow aplica márgenes de área segura. Por defecto es `true` para la presentación modal (`createFlowView` + `present()`) y `false` para el componente `AdaptyFlowView` embebido. El valor predeterminado es adecuado para la mayoría de los casos. | :::note Si utilizas varios idiomas, aprende cómo añadir una [localización de flow](add-paywall-locale-in-adapty-paywall-builder) y cómo usar los códigos de idioma correctamente [aquí](react-native-localizations-and-locale-codes). ::: Una vez que tengas la vista, [muestra el flow/paywall](react-native-present-paywalls). ## Obtén un flow o paywall para la audiencia predeterminada y cárgalo más rápido \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Por lo general, los flows y paywalls se cargan casi al instante, así que no tendrás que preocuparte por optimizar este proceso. Sin embargo, si tienes muchas audiencias y placements, y tus usuarios tienen una conexión a internet lenta, la carga de un flow o paywall puede tardar más de lo deseado. En esos casos, puede que quieras mostrar un flow o paywall predeterminado para garantizar una experiencia fluida, en lugar de no mostrar nada. Para solucionar esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow o paywall del placement especificado para la audiencia **All Users**. Sin embargo, es importante entender que el enfoque recomendado es obtener el flow o paywall con el método `getFlow`, tal como se explica en la sección [Obtener flow/paywall](#fetch-flowpaywall) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes paywalls para distintas versiones de la app (la actual y futuras), podrías encontrarte con dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (heredada) o asumir que los usuarios con esa versión podrían tener problemas con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluyendo por país, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas para beneficiarte de una obtención más rápida del flow o paywall, usa el método `getFlowForDefaultAudience` de la siguiente manera. De lo contrario, quédate con `getFlow` descrito [arriba](#fetch-flowpaywall). ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlowForDefaultAudience(id); // the requested flow/paywall } catch (error) { // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen conexiones a internet inestables, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En ese caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de lo intermitente que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | ## Personalizar assets \{#customize-assets\} Para personalizar imágenes y vídeos en tu flow/paywall, implementa los assets personalizados. Las imágenes y vídeos hero tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de assets personalizados, seleccionas estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. :::important Para usar esta función, actualiza el SDK de React Native de Adapty a la versión 3.8.0 o superior. ::: A continuación se muestra un ejemplo de cómo puedes proporcionar recursos personalizados mediante un diccionario simple: ```javascript const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; view = await createFlowView(flow, { customAssets }) ``` :::note Si un asset no se encuentra, el flow/paywall volverá a su apariencia predeterminada. ::: </SDKv4> <SDKv3> Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el nuevo Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu app móvil. El primer paso en este proceso es obtener el paywall asociado al placement y su configuración de vista, tal como se describe a continuación. :::warning El nuevo Paywall Builder funciona con la versión 3.0 o superior del SDK de React Native. ::: Por favor, ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Si estás implementando tus paywalls manualmente, consulta el tema [Obtener paywalls y productos para paywalls con Remote Config en tu app móvil](fetch-paywalls-and-products-react-native). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a mostrar paywalls en tu app móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en ellos](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-reactnative) en tu aplicación móvil. </details> ## Obtener un paywall diseñado con Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Si has [diseñado un paywall con Paywall Builder](adapty-paywall-builder), no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Este tipo de paywall contiene tanto lo que se debe mostrar como la forma en que debe mostrarse. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y luego presentarlo en tu app. Para garantizar un rendimiento óptimo, es fundamental obtener el paywall y su [configuración de vista](react-native-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) lo antes posible, dejando tiempo suficiente para que las imágenes se descarguen antes de mostrárselas al usuario. Para obtener un paywall, usa el método `getPaywall`: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywall(placementId, locale); // the requested paywall } catch (error) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | |-------------------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-paywall-locale-in-adapty-paywall-builder). Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché de actualización periódica descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.</p> | | **loadTimeoutMs** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos poco frecuentes este método puede superar ligeramente el tiempo de espera especificado en `loadTimeout`, ya que la operación puede estar compuesta de distintas solicitudes internamente.</p><p>Para Android: puedes crear un `TimeInterval` con funciones de extensión (como `5.seconds`, donde `.seconds` proviene de `import com.adapty.utils.seconds`), o con `TimeInterval.seconds(5)`. Para no establecer ningún límite, usa `TimeInterval.INFINITE`.</p> | Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Un objeto [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) con una lista de IDs de productos, el identificador del paywall, Remote Config y otras propiedades. | ## Obtener la configuración de vista de un paywall diseñado con Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Asegúrate de activar el interruptor **Show on device** en el Paywall Builder. Si esta opción no está activada, la configuración de vista no estará disponible para recuperar. ::: Después de obtener el paywall, comprueba si incluye un `ViewConfiguration`, lo que indica que fue creado con Paywall Builder. Esto te indicará cómo mostrar el paywall. Si el `ViewConfiguration` está presente, trátalo como un paywall de Paywall Builder; si no, [trátalo como un paywall de Remote Config](present-remote-config-paywalls-react-native). En el SDK de React Native, llama directamente al método `createPaywallView` sin necesidad de obtener primero la configuración de vista manualmente. :::warning El resultado del método `createPaywallView` solo puede usarse una vez. Si necesitas volver a utilizarlo, llama de nuevo al método `createPaywallView`. Llamarlo dos veces sin recrearlo puede provocar el error `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers // for the Adapty SDK < 3.14 – import {createPaywallView} from 'react-native-adapty/dist/ui'; if (paywall.hasViewConfiguration) { try { const view = await createPaywallView(paywall); } catch (error) { // handle the error } } else { //use your custom logic } ``` Parámetros: | Parámetro | Presencia | Descripción | | :------------------- | :-------- | :----------------------------------------------------------- | | **paywall** | requerido | Un objeto `AdaptyPaywall` para obtener un controlador para el paywall deseado. | | **customTags** | opcional | Define un diccionario de etiquetas personalizadas y sus valores resueltos. Las etiquetas personalizadas actúan como marcadores de posición en el contenido del paywall, reemplazándose dinámicamente con cadenas específicas para personalizar el contenido. Consulta el tema Custom tags in paywall builder para más detalles. | | **prefetchProducts** | opcional | Actívalo para optimizar el momento en que se muestran los productos en pantalla. Cuando es `true`, AdaptyUI obtiene automáticamente los productos necesarios. Valor predeterminado: `false`. | :::note Si utilizas varios idiomas, aprende cómo añadir una [localización del Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) y cómo usar los códigos de idioma correctamente [aquí](react-native-localizations-and-locale-codes). ::: Una vez que tengas la vista, [muestra el paywall](react-native-present-paywalls). ## Obtén un paywall para la audiencia predeterminada y acelera la carga \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Por lo general, los paywalls se cargan casi al instante, así que no tendrás que preocuparte por optimizar este proceso. Sin embargo, si tienes muchas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, la carga puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un paywall predeterminado para garantizar una buena experiencia de usuario en lugar de no mostrar ninguno. Para abordar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, tal como se detalla en la sección [Obtener información del paywall](#fetch-paywall-designed-with-paywall-builder) más arriba. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes paywalls para distintas versiones de la app (actual y futuras), es posible que te encuentres con dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (heredada) o asumir que los usuarios con esa versión podrían tener problemas con paywalls que no se renderizan correctamente. - **Pérdida de segmentación**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluida la basada en países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas para beneficiarte de una obtención más rápida del paywall, usa el método `getPaywallForDefaultAudience` de la siguiente manera. De lo contrario, sigue usando `getPaywall` descrito [arriba](#fetch-paywall-designed-with-paywall-builder). ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywallForDefaultAudience(id, locale); // the requested paywall } catch (error) { // handle the error } ``` :::note El método `getPaywallForDefaultAudience` está disponible a partir de la versión 2.11.2 del SDK de React Native. ::: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués brasileño.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](react-native-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la aplicación y solo se borra cuando se desinstala la app o mediante una limpieza manual.</p> | ## Personalizar recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu paywall, implementa recursos personalizados. Las imágenes y vídeos hero tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizado, seleccionas estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el Adapty Dashboard. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. :::important Para usar esta función, actualiza el SDK de React Native de Adapty a la versión 3.8.0 o superior. ::: Este es un ejemplo de cómo puedes proporcionar recursos personalizados mediante un diccionario simple: ```javascript const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; view = await createPaywallView(paywall, { customAssets }) ``` :::note Si no se encuentra un asset, el paywall volverá a su apariencia predeterminada. ::: </SDKv3> --- # File: react-native-present-paywalls --- --- title: "Mostrar flows y paywalls - React Native" description: "Presenta flows y paywalls a los usuarios en tu app de React Native con Adapty." --- <SDKv4> <MethodPromo method="getFlow" label="Display flows and paywalls" /> Si has creado un flow o paywall en el Flow Builder, no tienes que preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. El flow incluye tanto qué mostrar como cómo mostrarlo. Antes de empezar, asegúrate de que: 1. Has [creado un flow o paywall](create-paywall). 2. Lo has añadido a un [placement](placements). 3. Has [obtenido el flow y preparado la vista](react-native-get-pb-paywalls). :::warning Esta guía es solo para **flows y paywalls creados con Paywall Builder**, que requieren SDK v4.0 o posterior. El proceso para presentar flows es diferente para los paywalls de Remote Config. - Para presentar **paywalls de Remote Config**, consulta [Renderizar un paywall diseñado con Remote Config](present-remote-config-paywalls). ::: El SDK de Adapty para React Native ofrece dos formas de presentar flows y paywalls: - **Componente React**: Un componente embebido que te permite integrarlo en la arquitectura y el sistema de navegación de tu app. - **Presentación modal** ## Componente React \{#react-component\} Para insertar un flow dentro de tu árbol de componentes existente, usa el componente `AdaptyFlowView` directamente en la jerarquía de componentes React Native. El componente embebido te permite integrarlo en la arquitectura y el sistema de navegación de tu app. :::tip El componente `AdaptyFlowView` crea su vista cuando se renderiza, lo que ocurre cuando se cargan la configuración y las imágenes. Para precargarlos, llama a [`createFlowView`](react-native-get-pb-paywalls#fetch-the-view-configuration) para el mismo flow en un momento anterior de tu app. El componente reutilizará los datos en caché y se renderizará sin esperar descargas. ::: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const flowParams = useMemo(() => ({ loadTimeoutMs: 3000, }), []); const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<FlowEventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<FlowEventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<FlowEventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<FlowEventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<FlowEventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<FlowEventHandlers['onRestoreFailed']>((error) => {}, []); const onAppeared = useCallback<FlowEventHandlers['onAppeared']>(() => {}, []); const onError = useCallback<FlowEventHandlers['onError']>((error) => {}, []); const onLoadingProductsFailed = useCallback<FlowEventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<FlowEventHandlers['onUrlPress']>((url) => {}, []); const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<FlowEventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyFlowView flow={flow} params={flowParams} style={styles.flow} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onAppeared={onAppeared} onError={onError} onLoadingProductsFailed={onLoadingProductsFailed} onCustomAction={onCustomAction} onUrlPress={onUrlPress} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` ## Presentación modal \{#modal-presentation\} Para mostrar un flow como una pantalla independiente, usa el método `view.present()` sobre el `view` creado por el método [`createFlowView`](react-native-get-pb-paywalls#fetch-the-view-configuration). Cada `view` solo puede usarse una vez. Si necesitas mostrar el flow de nuevo, llama a `createFlowView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo está prohibido. Esto resultará en un error `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers title="React Native (TSX)" const view = await createFlowView(flow); // Optional: handle flow events (close, purchase, restore, etc) // view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` :::important Llamar a `setEventHandlers` varias veces sobrescribirá los handlers que proporciones, reemplazando tanto los predeterminados como los previamente establecidos para esos eventos específicos. ::: ### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el flow en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `'full_screen'` (predeterminado) o `'page_sheet'`. ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## Usar temporizadores definidos por el desarrollador \{#use-developer-defined-timer\} Para usar temporizadores definidos por el desarrollador en tu app, utiliza el `timerId`; en este ejemplo, `CUSTOM_TIMER_NY`, el **Timer ID** del temporizador definido por el desarrollador que configuraste en el Adapty Dashboard. Esto garantiza que tu app actualice el temporizador dinámicamente con el valor correcto, como `13d 09h 03m 34s` (calculado como la fecha de finalización del temporizador, por ejemplo, Año Nuevo, menos la hora actual). <Tabs> <TabItem value="component" label="React component"> ```typescript showLineNumbers title="React Native (TSX)" const flowParams = { customTimers: { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } }; <AdaptyFlowView flow={flow} params={flowParams} // ... your event handlers /> ``` </TabItem> <TabItem value="modal" label="Modal presentation"> ```typescript showLineNumbers title="React Native (TSX)" const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createFlowView(flow, { customTimers }); ``` </TabItem> </Tabs> En este ejemplo, `CUSTOM_TIMER_NY` es el **Timer ID** del temporizador definido por el desarrollador que configuraste en el Adapty Dashboard. El `timerResolver` garantiza que tu app actualice dinámicamente el temporizador con el valor correcto, como `13d 09h 03m 34s` (calculado como la hora de fin del temporizador, por ejemplo Año Nuevo, menos la hora actual). ## Mostrar diálogo \{#show-dialog\} Usa este método en lugar de los diálogos de alerta nativos cuando hay un flow view activo en Android. En Android, las alertas normales de RN aparecen detrás del flow view, lo que las hace invisibles para los usuarios. Este método garantiza que el diálogo se muestre correctamente sobre el flow en todas las plataformas. ```typescript showLineNumbers title="React Native (TSX)" try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the flow await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## Reemplazar una suscripción por otra \{#replace-one-subscription-with-another\} Cuando un usuario intenta comprar una nueva suscripción mientras tiene otra activa en Android, puedes controlar cómo debe gestionarse esa nueva compra pasando parámetros de actualización de suscripción al crear la vista del flow. Para reemplazar la suscripción actual por la nueva, usa `productPurchaseParams` en `createFlowView` con los parámetros `oldSubVendorProductId` y `prorationMode`. ```typescript showLineNumbers title="React Native (TSX)" const productPurchaseParams = flow.paywalls .flatMap((variation) => variation.productIdentifiers) .map((productId) => { let params = {}; if (Platform.OS === 'android') { params.android = { subscriptionUpdateParams: { oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION', prorationMode: 'with_time_proration', }, }; } return { productId, params }; }); const view = await createFlowView(flow, { productPurchaseParams }); ``` </SDKv4> <SDKv3> Si has personalizado un paywall con el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese paywall contiene tanto lo que se debe mostrar como la forma en que debe mostrarse. Antes de empezar, asegúrate de que: 1. Has [creado un paywall](create-paywall). 2. Has añadido el paywall a un [placement](placements). 3. Has [obtenido el paywall y preparado la vista](react-native-get-pb-paywalls). :::warning Esta guía es exclusivamente para **paywalls creados con el nuevo Paywall Builder**, que requieren SDK v3.0 o posterior. El proceso para presentar paywalls varía según la versión del Paywall Builder utilizada y los paywalls de Remote Config. - Para presentar **paywalls de Remote Config**, consulta [Renderizar paywall diseñado con Remote Config](present-remote-config-paywalls). ::: El SDK de Adapty para React Native ofrece dos formas de presentar paywalls: - **Componente React**: Un componente embebido que puedes integrar en la arquitectura y el sistema de navegación de tu app. - **Presentación modal** ## Componente React \{#react-component\} :::note El enfoque de **React component** requiere la versión 3.14.0 o posterior del SDK. ::: Para incrustar un paywall dentro de tu árbol de componentes existente, usa el componente `AdaptyPaywallView` directamente en la jerarquía de componentes de React Native. El componente embebido te permite integrarlo en la arquitectura y el sistema de navegación de tu app. :::note En Android, si el paywall no se extiende detrás de la barra de estado, puede aparecer una superposición visual en su parte superior. Te recomendamos desactivarla para tus paywalls. Consulta [Superposición visual en la parte superior del paywall (Android)](#visual-overlay-at-the-top-of-the-paywall-android). ::: ```typescript showLineNumbers title="React Native (TSX)" function MyPaywall({ paywall }) { const paywallParams = useMemo(() => ({ loadTimeoutMs: 3000, }), []); const onCloseButtonPress = useCallback<EventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<EventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<EventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<EventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<EventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<EventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<EventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<EventHandlers['onRestoreFailed']>((error) => {}, []); const onPaywallShown = useCallback<EventHandlers['onPaywallShown']>(() => {}, []); const onRenderingFailed = useCallback<EventHandlers['onRenderingFailed']>((error) => {}, []); const onLoadingProductsFailed = useCallback<EventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<EventHandlers['onUrlPress']>((url) => {}, []); const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<EventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyPaywallView paywall={paywall} params={paywallParams} style={styles.paywall} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onPaywallShown={onPaywallShown} onRenderingFailed={onRenderingFailed} onLoadingProductsFailed={onLoadingProductsFailed} onCustomAction={onCustomAction} onUrlPress={onUrlPress} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` ## Presentación modal \{#modal-presentation\} Para mostrar un paywall como pantalla independiente, usa el método `view.present()` en el `view` creado por el método [`createPaywallView`](react-native-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Cada `view` solo puede usarse una vez. Si necesitas mostrar el paywall de nuevo, llama a `createPaywallView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo no está permitido. Producirá un error `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers title="React Native (TSX)" const view = await createPaywallView(paywall); // Optional: handle paywall events (close, purchase, restore, etc) // view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` :::important Llamar a `setEventHandlers` varias veces sobreescribirá los handlers que hayas definido, reemplazando tanto los predeterminados como los previamente configurados para esos eventos específicos. ::: ### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el paywall en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `'full_screen'` (predeterminado) o `'page_sheet'`. ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## Usar temporizadores definidos por el desarrollador \{#use-developer-defined-timer\} Para usar temporizadores definidos por el desarrollador en tu app, utiliza el `timerId`; en este ejemplo, `CUSTOM_TIMER_NY` es el **Timer ID** del temporizador definido por el desarrollador que configuraste en el Adapty dashboard. Esto garantiza que tu app actualice el temporizador dinámicamente con el valor correcto, como `13d 09h 03m 34s` (calculado como la hora de finalización del temporizador, por ejemplo, Año Nuevo, menos la hora actual). <Tabs> <TabItem value="component" label="React component"> ```typescript showLineNumbers title="React Native (TSX)" const paywallParams = { customTimers: { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } }; <AdaptyPaywallView paywall={paywall} params={paywallParams} // ... your event handlers /> ``` </TabItem> <TabItem value="modal" label="Modal presentation"> ```typescript showLineNumbers title="React Native (TSX)" const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createPaywallView(paywall, { customTimers }); ``` </TabItem> </Tabs> En este ejemplo, `CUSTOM_TIMER_NY` es el **Timer ID** del temporizador definido por el desarrollador que configuraste en el Adapty dashboard. El `timerResolver` garantiza que tu app actualice dinámicamente el temporizador con el valor correcto, como `13d 09h 03m 34s` (calculado como el tiempo de finalización del temporizador, por ejemplo el Año Nuevo, menos la hora actual). ## Mostrar diálogo \{#show-dialog\} Usa este método en lugar de los diálogos de alerta nativos cuando se muestra una vista de paywall en Android. En Android, las alertas nativas de RN aparecen detrás de la vista del paywall, lo que las hace invisibles para los usuarios. Este método garantiza que el diálogo se muestre correctamente por encima del paywall en todas las plataformas. ```typescript showLineNumbers title="React Native (TSX)" try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## Reemplazar una suscripción por otra \{#replace-one-subscription-with-another\} Cuando un usuario intenta comprar una nueva suscripción mientras tiene otra activa en Android, puedes controlar cómo se gestiona esa nueva compra pasando parámetros de actualización de suscripción al crear la vista del paywall. Para reemplazar la suscripción actual por la nueva, usa `productPurchaseParams` en `createPaywallView` con los parámetros `oldSubVendorProductId` y `prorationMode`. ```typescript showLineNumbers title="React Native (TSX)" const productPurchaseParams = paywall.productIdentifiers.map((productId) => { let params = {}; if (Platform.OS === 'android') { params.android = { subscriptionUpdateParams: { oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION', prorationMode: 'with_time_proration', }, }; } return { productId, params }; }); const view = await createPaywallView(paywall, { productPurchaseParams }); ``` ## Solución de problemas \{#troubleshooting\} ### Superposición visual en la parte superior del paywall (Android) \{#visual-overlay-at-the-top-of-the-paywall-android\} :::note Esta configuración es compatible a partir del SDK de React Native 3.15.5 y solo está disponible en proyectos bare de React Native. Si utilizas un flujo de trabajo gestionado por Expo, no puedes añadir este recurso de Android directamente. Para aplicar esta configuración, debes crear un plugin de configuración personalizado de Expo que añada el recurso de Android correspondiente y registrarlo en app.config.js. Esto es necesario porque Expo gestiona el proyecto nativo de Android por ti. ::: Si `AdaptyPaywallView` no se extiende detrás de la barra de estado, puede aparecer una superposición visual en su parte superior. Para eliminarla, añade el siguiente recurso booleano a tu app: 1. Ve a `android/app/src/main/res/values`. Si no existe el archivo `bools.xml`, créalo. 2. Añade el siguiente recurso: ```xml <resources> <bool name="adapty_paywall_enable_safe_area_paddings">false</bool> </resources> ``` Ten en cuenta que los cambios se aplican de forma global a todos los paywalls de tu app. </SDKv3> --- # File: react-native-handle-paywall-actions --- --- title: "Responder a las acciones de flow - React Native" description: "Gestiona las acciones de botones de flows y paywalls en React Native usando Adapty para una mejor monetización de la app." --- <SDKv4> Si estás creando flows o paywalls con el Adapty Flow Builder o el Paywall Builder, es fundamental configurar los botones correctamente: 1. Añade un [botón en el flow/paywall builder](paywall-buttons) y asígnale una acción predefinida o crea un ID de acción personalizado. 2. Escribe el código en tu app para gestionar cada acción que hayas asignado. Esta guía explica cómo gestionar acciones personalizadas y predefinidas en tu código. :::warning **Las compras, restauraciones, cierres de flow/paywall y la apertura de URLs se gestionan automáticamente.** Puedes configurar su comportamiento predeterminado o implementar respuestas para acciones personalizadas. ::: :::note El SDK expone un handler de flow `onRequestPermission` para solicitudes de permisos del sistema, como notificaciones push o acceso a la cámara. Los flows todavía no desencadenan estas solicitudes, por lo que no necesitas implementarlo por ahora. ::: ## Cerrar flows y paywalls \{#close-flows-and-paywalls\} Para añadir un botón que cierre tu flow o paywall: 1. En el builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un manejador para la acción `close` que descarte el flow o paywall. :::info En el SDK de React Native, la acción `close` activa el cierre del flow o paywall por defecto. Sin embargo, puedes sobreescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un flow podría desencadenar la apertura de otro. ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, gestiona la acción de cierre a través de props individuales de manejadores de eventos: ```javascript function MyPaywall({ flow }) { const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(() => { // Handle close button press - navigate away or hide component navigation.goBack(); }, [navigation]); return ( <AdaptyFlowView flow={flow} style={styles.container} onCloseButtonPress={onCloseButtonPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> Para la presentación modal, implementa el manejador de cierre: ```javascript const view = await createFlowView(flow); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; // allow flow or paywall closing } }); ``` </TabItem> </Tabs> ## Abrir URLs desde flows y paywalls \{#open-urls-from-flows-and-paywalls\} :::tip Si quieres añadir un grupo de enlaces (por ejemplo, términos de uso y restauración de compras), añade un elemento **Link** en el builder y trátalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu flow o paywall (por ejemplo, **Terms of use** o **Privacy policy**): 1. En el builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieras abrir. 2. En el código de tu app, implementa un handler para la acción `openUrl` que abra la URL recibida en un navegador. :::info En el SDK de React Native, la acción `openUrl` abre la URL de forma predeterminada. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, gestiona la apertura de URLs mediante la prop del manejador de eventos: ```javascript function MyPaywall({ flow }) { const onUrlPress = useCallback<FlowEventHandlers['onUrlPress']>((url) => { Linking.openURL(url); }, []); return ( <AdaptyFlowView flow={flow} style={styles.container} onUrlPress={onUrlPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Presentación modal"> Para la presentación modal, implementa el manejador de URL: ```javascript const view = await createFlowView(flow); const unsubscribe = view.setEventHandlers({ onUrlPress(url) { Linking.openURL(url); return false; // Keep flow or paywall open }, }); ``` </TabItem> </Tabs> ## Gestionar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el builder, añade un botón, asígnale la acción **Custom** y dale un ID. 2. En el código de tu app, implementa un handler para el ID de acción que hayas creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro flow o paywall: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, gestiona las acciones personalizadas a través del prop del manejador de eventos: ```javascript function MyPaywall({ flow }) { const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>((actionId) => { if (actionId === 'openNewPaywall') { // Display another flow or paywall } }, []); return ( <AdaptyFlowView flow={flow} style={styles.container} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="Presentación modal"> Para la presentación modal, implementa manejadores de acciones personalizadas: ```javascript const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another flow or paywall } }, }); ``` </TabItem> </Tabs> </SDKv4> <SDKv3> Si estás creando paywalls con el Paywall Builder de Adapty, es fundamental configurar correctamente los botones: 1. Añade un [botón en el Paywall Builder](paywall-buttons) y asígnale una acción ya existente o crea un ID de acción personalizado. 2. Escribe el código en tu app para gestionar cada acción que hayas asignado. Esta guía muestra cómo gestionar acciones personalizadas y existentes en tu código. :::warning **Solo las compras, restauraciones, cierres de paywall y apertura de URLs se gestionan automáticamente.** Todas las demás acciones de botón requieren una implementación adecuada en el código de la app. ::: ## Cerrar paywalls \{#close-paywalls\} Para añadir un botón que cierre tu paywall: 1. En el Paywall Builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un handler para la acción `close` que descarte el paywall. :::info En el SDK de React Native, la acción `close` activa el cierre del paywall por defecto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un paywall podría disparar la apertura de otro. ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, gestiona la acción de cierre mediante props de controladores de eventos individuales: ```javascript function MyPaywall({ paywall }) { const onCloseButtonPress = useCallback<EventHandlers['onCloseButtonPress']>(() => { // Handle close button press - navigate away or hide component navigation.goBack(); }, [navigation]); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCloseButtonPress={onCloseButtonPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> Para la presentación modal, implementa el manejador de cierre: ```javascript const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; // allow paywall closing } }); ``` </TabItem> </Tabs> ## Abrir URLs desde paywalls \{#open-urls-from-paywalls\} :::tip Si quieres añadir un grupo de enlaces (por ejemplo, términos de uso y restauración de compras), añade un elemento **Link** en el Paywall Builder y trátalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu paywall (por ejemplo, **Terms of use** o **Privacy policy**): 1. En el Paywall Builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieres abrir. 2. En el código de tu app, implementa un manejador para la acción `openUrl` que abra la URL recibida en un navegador. :::info En el SDK de React Native, la acción `openUrl` activa la apertura de la URL por defecto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, gestiona la apertura de URLs a través de la prop del manejador de eventos: ```javascript function MyPaywall({ paywall }) { const onUrlPress = useCallback<EventHandlers['onUrlPress']>((url) => { Linking.openURL(url); }, []); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onUrlPress={onUrlPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Presentación modal"> Para la presentación modal, implementa el manejador de URL: ```javascript const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onUrlPress(url) { Linking.openURL(url); return false; // Keep paywall open }, }); ``` </TabItem> </Tabs> ## Iniciar sesión en la aplicación \{#log-into-the-app\} Para añadir un botón que permita a los usuarios iniciar sesión en tu aplicación: 1. En el paywall builder, añade un botón y asígnale la acción **Login**. 2. En el código de tu aplicación, implementa un manejador para la acción `login` que identifique a tu usuario. <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, gestiona el inicio de sesión a través del prop del manejador de eventos: ```javascript function MyPaywall({ paywall }) { const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => { if (actionId === 'login') { navigation.navigate('Login'); } }, [navigation]); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="Presentación modal"> Para la presentación modal, implementa el manejador de inicio de sesión: ```javascript const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'login') { navigation.navigate('Login'); } } }); ``` </TabItem> </Tabs> ## Gestionar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el Paywall Builder, añade un botón, asígnale la acción **Custom** y asígnale un ID. 2. En el código de tu app, implementa un handler para el ID de acción que has creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro paywall: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, gestiona las acciones personalizadas a través del prop del manejador de eventos: ```javascript function MyPaywall({ paywall }) { const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => { if (actionId === 'openNewPaywall') { // Display another paywall } }, []); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="Presentación modal"> Para la presentación modal, implementa manejadores de acciones personalizadas: ```javascript const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another paywall } }, }); ``` </TabItem> </Tabs> </SDKv3> --- # File: react-native-handling-events-1 --- --- title: "Gestionar eventos de flow y paywall - React Native" description: "Gestiona eventos de flow y paywall en tu app de React Native con el SDK de Adapty." --- <SDKv4> :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de flows. También puedes configurar el manejo de botones (cerrar el flow, abrir enlaces, acciones personalizadas, etc.). Consulta nuestra [guía sobre el manejo de acciones de botones](react-native-handle-paywall-actions) para más detalles. ::: Los flows y paywalls creados con el Flow Builder no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan ciertos eventos a los que tu aplicación puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el flow. A continuación se explica cómo responder a estos eventos. Para controlar o supervisar los procesos que ocurren en la pantalla del flow dentro de tu aplicación móvil, implementa manejadores de eventos: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, los eventos se gestionan mediante props individuales de manejador de eventos en el componente `AdaptyFlowView`: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<FlowEventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<FlowEventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<FlowEventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<FlowEventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<FlowEventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<FlowEventHandlers['onRestoreFailed']>((error) => {}, []); const onAppeared = useCallback<FlowEventHandlers['onAppeared']>(() => {}, []); const onError = useCallback<FlowEventHandlers['onError']>((error) => {}, []); const onLoadingProductsFailed = useCallback<FlowEventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<FlowEventHandlers['onUrlPress']>((url, openIn) => { adapty.openWebUrl(url, openIn); return false; }, []); const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<FlowEventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyFlowView flow={flow} style={styles.container} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onAppeared={onAppeared} onError={onError} onLoadingProductsFailed={onLoadingProductsFailed} onUrlPress={onUrlPress} onCustomAction={onCustomAction} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> Para la presentación modal, implementa el método de controladores de eventos. :::important Llamar a `setEventHandlers` varias veces sobrescribirá los controladores que proporciones, reemplazando tanto los predeterminados como los establecidos anteriormente para esos eventos específicos. ::: ```javascript showLineNumbers title="React Native (TSX)" const view = await createFlowView(flow); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; }, onAndroidSystemBack() { return true; }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type !== 'user_cancelled'; }, onPurchaseStarted(product) { /***/}, onPurchaseFailed(error, product) { /***/ }, onRestoreCompleted(profile) { /***/ }, onRestoreFailed(error) { /***/ }, onProductSelected(productId) { /***/}, onError(error) { /***/ }, onLoadingProductsFailed(error) { /***/ }, onUrlPress(url, openIn) { adapty.openWebUrl(url, openIn); return false; // Keep flow open }, onAppeared() { /***/ }, onDisappeared() { /***/ }, onWebPaymentNavigationFinished() { /***/ }, }); ``` </TabItem> </Tabs> <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```javascript // onCloseButtonPress { //Record the event } // onAndroidSystemBack { //Record the event } // onUrlPress { "url": "https://example.com/terms" } // onCustomAction { "actionId": "login" } // onProductSelected { "productId": "premium_monthly" } // onPurchaseStarted { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Success { "purchaseResult": { "type": "success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Cancelled { "purchaseResult": { "type": "user_cancelled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseFailed { "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onRestoreCompleted { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } // onRestoreFailed { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } // onError { "error": { "code": "rendering_failed", "message": "Failed to render flow interface", "details": { "underlyingError": "Invalid flow configuration" } } } // onLoadingProductsFailed { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } // onAppeared { //Record the event } // onDisappeared { //Record the event } // onWebPaymentNavigationFinished { //Record the event } ``` </Details> Puedes registrar solo los manejadores de eventos que necesites y omitir los que no uses. De este modo, no se crearán listeners de eventos innecesarios. No hay manejadores de eventos obligatorios. Los manejadores de eventos devuelven un booleano. Si se devuelve `true`, el proceso de visualización se considera completado, por lo que la pantalla del flow se cierra y se eliminan los listeners de eventos para esa vista. Algunos manejadores de eventos tienen un comportamiento predeterminado que puedes sobreescribir si es necesario: - `onCloseButtonPress`: cierra el flow cuando se pulsa el botón de cierre. - `onUrlPress`: abre la URL pulsada y mantiene el flow abierto. - `onAndroidSystemBack` (solo para presentación modal): mantiene el flow abierto cuando se pulsa el botón **Back**. Devuelve `true` para cerrarlo. - `onRestoreCompleted`: mantiene el flow abierto tras una restauración exitosa. Devuelve `true` para cerrarlo. - `onPurchaseCompleted`: mantiene el flow abierto tras completarse una compra. Devuelve `true` para cerrarlo. - `onError`: cierra el flow si falla su renderizado. ### Controladores de eventos \{#event-handlers\} | Manejador de eventos | Descripción | |:------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | Se invoca cuando el usuario realiza una acción personalizada, por ejemplo, al hacer clic en un [botón personalizado](paywall-buttons). | | **onUrlPress** | Se invoca cuando el usuario hace clic en una URL dentro del flow. | | **onAndroidSystemBack** | Solo presentación modal: se invoca cuando el usuario pulsa el botón del sistema **Back** de Android. | | **onCloseButtonPress** | Se invoca cuando el botón de cerrar está visible y el usuario lo pulsa. Se recomienda cerrar la pantalla del flow en este manejador. | | **onPurchaseCompleted** | Se invoca cuando la compra finaliza, ya sea con éxito, cancelada por el usuario o pendiente de aprobación. En caso de compra exitosa, proporciona un `AdaptyProfile` actualizado. Las cancelaciones del usuario y los pagos pendientes (por ejemplo, cuando se requiere aprobación parental) activan este evento, no `onPurchaseFailed`. | | **onPurchaseStarted** | Se invoca cuando el usuario pulsa el botón de acción "Comprar" para iniciar el proceso de compra. | | **onPurchaseFailed** | Se invoca cuando una compra falla por errores (por ejemplo, restricciones de pago, productos no válidos, fallos de red o errores de verificación de transacción). No se invoca para cancelaciones del usuario ni pagos pendientes, que activan `onPurchaseCompleted` en su lugar. | | **onRestoreStarted** | Se invoca cuando el usuario inicia un proceso de restauración de compras. | | **onRestoreCompleted** | Se invoca cuando la restauración de compras se completa con éxito y proporciona un `AdaptyProfile` actualizado. Se recomienda cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de la suscripción](react-native-listen-subscription-changes) para saber cómo comprobarlo. | | **onRestoreFailed** | Se invoca cuando el proceso de restauración falla y proporciona un `AdaptyError`. | | **onProductSelected** | Se invoca cuando se selecciona cualquier producto en la vista del flow, lo que permite monitorear qué elige el usuario antes de la compra. | | **onError** | Se invoca cuando ocurre un error durante el renderizado de la vista y proporciona un `AdaptyError`. Estos errores no deberían producirse; si encuentras uno, comunícanoslo. | | **onLoadingProductsFailed** | Se invoca cuando la carga de productos falla y proporciona un `AdaptyError`. Si no has configurado `prefetchProducts: true` al crear la vista, AdaptyUI obtendrá los objetos necesarios del servidor por sí mismo. | | **onAppeared** | Se invoca cuando el flow se muestra al usuario. En iOS, también se invoca cuando el usuario pulsa el [botón de paywall web](web-paywall#step-2a-add-a-web-purchase-button) dentro de un flow y el paywall web se abre en un navegador in-app. | | **onDisappeared** | Solo presentación modal: se invoca cuando el usuario cierra el flow. En iOS, también se invoca cuando un [paywall web](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un flow en un navegador in-app desaparece de la pantalla. | | **onWebPaymentNavigationFinished** | Se invoca tras intentar abrir un [paywall web](web-paywall) para la compra, tanto si se abre con éxito como si falla. | | **onAnalytics** | Reservado para eventos analíticos personalizados de un flow. Los flows aún no emiten estos eventos a tu código, por lo que no es necesario implementarlo. | | **onRequestAppReview** | Reservado para solicitudes de valoración de la app desde un flow. Los flows aún no activan solicitudes de valoración, por lo que no es necesario implementarlo. | | **onRequestPermission** | Reservado para solicitudes de permisos del sistema (como notificaciones push o acceso a la cámara) desde un flow. Los flows aún no activan solicitudes de permisos, por lo que no es necesario implementarlo. | | **onObserverPurchaseInitiated** | Solo modo observador: se invoca cuando el usuario pulsa el botón de compra en un flow. Adapty no realiza la compra — ejecútala con tu propio código de compra y luego notifica la transacción a Adapty. Consulta [Gestionar compras en modo observador](#handle-purchases-in-observer-mode) más abajo. | | **onObserverRestoreInitiated** | Solo modo observador: se invoca cuando el usuario pulsa el botón de restaurar en un flow. Adapty no restaura — hazlo tú mismo y luego notifica las transacciones restauradas. Consulta [Gestionar compras en modo observador](#handle-purchases-in-observer-mode) más abajo. | ### Gestionar compras en modo observador \{#handle-purchases-in-observer-mode\} Si activaste el SDK en [modo observador](implement-observer-mode-react-native) (`observerMode: true`) y presentas un flow renderizado por Adapty, el SDK no realiza las compras por ti. Cuando el usuario pulsa el botón de compra o restauración, el SDK invoca `onObserverPurchaseInitiated` o `onObserverRestoreInitiated` en su lugar. Realiza la compra o restauración con tu propio código, controla el indicador de carga del flow con los callbacks proporcionados y, después, [reporta la transacción](report-transactions-observer-mode-react-native) a Adapty. ```typescript showLineNumbers const unsubscribe = view.setEventHandlers({ onObserverPurchaseInitiated(product, onStartPurchase, onFinishPurchase) { onStartPurchase(); // show the flow's loading indicator myPurchaseApi(product.vendorProductId) .then((transactionId) => adapty.reportTransaction(transactionId)) .finally(() => onFinishPurchase()); // hide the loading indicator return false; // keep the flow open; dismiss it yourself after success }, onObserverRestoreInitiated(onStartRestore, onFinishRestore) { onStartRestore(); myRestoreApi() .finally(() => onFinishRestore()); return false; }, }); ``` </SDKv4> <SDKv3> :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de paywalls. También debes implementar el manejo de botones (cerrar el paywall, abrir enlaces, etc.). Consulta nuestra [guía sobre cómo manejar las acciones de los botones](react-native-handle-paywall-actions) para más detalles. ::: Los paywalls configurados con el [Paywall Builder](adapty-paywall-builder) no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan una serie de eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selección de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el paywall. A continuación te explicamos cómo responder a estos eventos. :::warning Esta guía es exclusivamente para **paywalls del nuevo Paywall Builder** que requieren Adapty SDK v3.0 o posterior. ::: Para controlar o monitorear los procesos que ocurren en la pantalla del paywall dentro de tu app móvil, implementa manejadores de eventos: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, manejas los eventos a través de props individuales de manejadores de eventos en el componente `AdaptyPaywallView`: ```typescript showLineNumbers title="React Native (TSX)" function MyPaywall({ paywall }) { const onCloseButtonPress = useCallback<EventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<EventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<EventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<EventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<EventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<EventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<EventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<EventHandlers['onRestoreFailed']>((error) => {}, []); const onPaywallShown = useCallback<EventHandlers['onPaywallShown']>(() => {}, []); const onRenderingFailed = useCallback<EventHandlers['onRenderingFailed']>((error) => {}, []); const onLoadingProductsFailed = useCallback<EventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<EventHandlers['onUrlPress']>((url) => { Linking.openURL(url); }, []); const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<EventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onPaywallShown={onPaywallShown} onRenderingFailed={onRenderingFailed} onLoadingProductsFailed={onLoadingProductsFailed} onUrlPress={onUrlPress} onCustomAction={onCustomAction} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> Para la presentación modal, implementa el método de los manejadores de eventos. :::important Llamar a `setEventHandlers` varias veces sobreescribirá los manejadores que proporciones, reemplazando tanto los predeterminados como los configurados anteriormente para esos eventos específicos. ::: ```javascript showLineNumbers title="React Native (TSX)" const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; }, onAndroidSystemBack() { return true; }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type !== 'user_cancelled'; }, onPurchaseStarted(product) { /***/}, onPurchaseFailed(error) { /***/ }, onRestoreCompleted(profile) { /***/ }, onRestoreFailed(error) { /***/ }, onProductSelected(productId) { /***/}, onRenderingFailed(error) { /***/ }, onLoadingProductsFailed(error) { /***/ }, onUrlPress(url) { Linking.openURL(url); return false; // Keep paywall open }, onPaywallShown() { /***/ }, onPaywallClosed() { /***/ }, onWebPaymentNavigationFinished() { /***/ }, }); ``` </TabItem> </Tabs> <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```javascript // onCloseButtonPress { //Record the event } // onAndroidSystemBack { //Record the event } // onUrlPress { "url": "https://example.com/terms" } // onCustomAction { "actionId": "login" } // onProductSelected { "productId": "premium_monthly" } // onPurchaseStarted { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Success { "purchaseResult": { "type": "success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Cancelled { "purchaseResult": { "type": "user_cancelled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseFailed { "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onRestoreCompleted { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } // onRestoreFailed { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } // onRenderingFailed { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } // onLoadingProductsFailed { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } // onPaywallShown { //Record the event } // onPaywallClosed { //Record the event } // onWebPaymentNavigationFinished { //Record the event } ``` </Details> Puedes registrar solo los manejadores de eventos que necesites y omitir los que no uses. Así no se crearán listeners innecesarios. No hay ningún manejador de eventos obligatorio. Los manejadores de eventos devuelven un booleano. Si se devuelve `true`, el proceso de visualización se considera completado, por lo que la pantalla del paywall se cierra y se eliminan los listeners de eventos para esa vista. Algunos manejadores de eventos tienen un comportamiento predeterminado que puedes reemplazar si lo necesitas: - `onCloseButtonPress`: cierra el paywall cuando se pulsa el botón de cerrar. - `onUrlPress`: abre la URL pulsada y mantiene el paywall abierto. - `onAndroidSystemBack` (solo para presentación modal): cierra el paywall cuando se pulsa el botón **Back**. - `onRestoreCompleted`: cierra el paywall tras una restauración exitosa. - `onPurchaseCompleted`: cierra el paywall a menos que el usuario haya cancelado. - `onRenderingFailed`: cierra el paywall si falla su renderizado. ### Controladores de eventos \{#event-handlers\} | Manejador de eventos | Descripción | |:-----------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | Se invoca cuando el usuario realiza una acción personalizada, por ejemplo, hace clic en un [botón personalizado](paywall-buttons). | | **onUrlPress** | Se invoca cuando el usuario hace clic en una URL de tu paywall. | | **onAndroidSystemBack** | Solo en presentación modal: se invoca cuando el usuario pulsa el botón de sistema **Back** de Android. | | **onCloseButtonPress** | Se invoca cuando el botón de cierre está visible y el usuario lo pulsa. Se recomienda cerrar la pantalla del paywall en este manejador. | | **onPurchaseCompleted** | Se invoca cuando la compra finaliza, ya sea con éxito, cancelada por el usuario o pendiente de aprobación. En caso de compra exitosa, proporciona un `AdaptyProfile` actualizado. Las cancelaciones del usuario y los pagos pendientes (p. ej., se requiere aprobación parental) disparan este evento, no `onPurchaseFailed`. | | **onPurchaseStarted** | Se invoca cuando el usuario pulsa el botón de acción "Comprar" para iniciar el proceso de compra. | | **onPurchaseFailed** | Se invoca cuando una compra falla por errores (p. ej., restricciones de pago, productos no válidos, fallos de red, errores de verificación de transacción). No se invoca por cancelaciones del usuario ni pagos pendientes, que disparan `onPurchaseCompleted` en su lugar. | | **onRestoreStarted** | Se invoca cuando el usuario inicia un proceso de restauración de compras. | | **onRestoreCompleted** | Se invoca cuando la restauración de compras tiene éxito y proporciona un `AdaptyProfile` actualizado. Se recomienda cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de suscripción](react-native-listen-subscription-changes) para saber cómo comprobarlo. | | **onRestoreFailed** | Se invoca cuando el proceso de restauración falla y proporciona `AdaptyError`. | | **onProductSelected** | Se invoca cuando se selecciona cualquier producto en la vista del paywall, lo que permite monitorizar qué elige el usuario antes de la compra. | | **onRenderingFailed** | Se invoca cuando ocurre un error durante el renderizado de la vista y proporciona `AdaptyError`. Estos errores no deberían ocurrir; si te encuentras con uno, por favor comunícanoslo. | | **onLoadingProductsFailed** | Se invoca cuando la carga de productos falla y proporciona `AdaptyError`. Si no has configurado `prefetchProducts: true` al crear la vista, AdaptyUI recuperará los objetos necesarios del servidor por sí mismo. | | **onPaywallShown** | Se invoca cuando el paywall se muestra al usuario. En iOS, también se invoca cuando el usuario pulsa el [botón de web paywall](web-paywall#step-2a-add-a-web-purchase-button) dentro de un paywall y el web paywall se abre en un navegador in-app. | | **onPaywallClosed** | Solo en presentación modal: se invoca cuando el usuario cierra el paywall. En iOS, también se invoca cuando un [web paywall](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un paywall en un navegador in-app desaparece de la pantalla. | | **onWebPaymentNavigationFinished** | Se invoca tras intentar abrir un [web paywall](web-paywall) para realizar una compra, tanto si tiene éxito como si falla. | </SDKv3> --- # File: react-native-use-fallback-paywalls-expo --- --- title: "Usar paywalls de respaldo en un proyecto Expo" description: "Configura paywalls de respaldo en un proyecto Expo React Native mediante el plugin de configuración react-native-adapty." --- :::important Esta guía aplica a **proyectos Expo**. Si usas **React Native puro (sin Expo)**, consulta la [guía de paywall de respaldo para React Native puro](react-native-use-fallback-paywalls-pure) en su lugar. ::: Para mantener una experiencia de usuario fluida, es importante configurar [respaldos](/fallback-paywalls) para tus flows, [paywalls](paywalls) y [onboardings](onboardings). Esta precaución amplía las capacidades de la aplicación en caso de pérdida parcial o total de la conexión a internet. * **Si la aplicación no puede acceder a los servidores de Adapty:** Podrá mostrar un flow o paywall de respaldo, y acceder a la configuración local del onboarding. * **Si la aplicación no puede acceder a internet:** Podrá mostrar un flow o paywall de respaldo. Los onboardings incluyen contenido remoto y requieren conexión a internet para funcionar. :::important Antes de seguir los pasos de esta guía, [descarga](/local-fallback-paywalls) los archivos de configuración de respaldo desde Adapty. ::: El SDK de Adapty lee el archivo de respaldo desde el bundle **nativo**: un recurso iOS dentro del paquete `.app`, o una entrada en `android/app/src/main/assets/`. En un proyecto Expo, `npx expo prebuild --clean` regenera esos directorios en cada ejecución, por lo que no puedes añadir los archivos manualmente. El plugin de configuración de `react-native-adapty` se encarga de conectar el archivo al bundle nativo por ti. :::tip Hay una configuración completa y funcional disponible en la [app de ejemplo `FocusJournalExpo`](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo). ::: ## Configuración \{#configuration\} 1. Coloca los archivos JSON de respaldo en cualquier lugar de tu proyecto — normalmente junto a otros assets: ``` <your-project>/ └── assets/ ├── ios_fallback.json └── android_fallback.json ``` 2. Añade la opción `fallbackFile` a la entrada `react-native-adapty` en `app.json` (o `app.config.js`). Cada clave de plataforma es opcional — configura solo las plataformas que necesites: ```json title="app.json" { "expo": { "plugins": [ [ "react-native-adapty", { "fallbackFile": { "ios": "./assets/ios_fallback.json", "android": "./assets/android_fallback.json" } } ] ] } } ``` :::note Adapty exporta un JSON de respaldo diferente para cada plataforma: IDs de producto de Apple en iOS e IDs de producto de Google Play en Android. Apunta cada plataforma a su propio archivo. ::: 3. Regenera los proyectos nativos: ```sh title="Shell" npx expo prebuild ``` El plugin añade el archivo iOS a los recursos del bundle del proyecto Xcode y copia el archivo Android en `android/app/src/main/assets/`. La salida del prebuild incluye líneas como: ``` [react-native-adapty] Registered ios_fallback.json as iOS bundle resource [react-native-adapty] Copied android_fallback.json to android assets/ ``` 4. Registra el archivo con el SDK en tiempo de ejecución: ```typescript showLineNumbers title="App.tsx" import { adapty } from 'react-native-adapty'; await adapty.activate('PUBLIC_SDK_KEY'); await adapty.setFallback({ ios: { fileName: 'ios_fallback.json' }, android: { relativeAssetPath: 'android_fallback.json' }, }); ``` Los nombres de archivo que se pasan a `setFallback` deben coincidir con los nombres base de los archivos configurados en `fallbackFile`. :::important `setFallback` debe ejecutarse antes de que el SDK obtenga el flow, paywall u onboarding de destino. ::: ## Verificación \{#verification\} Después de `npx expo prebuild`, comprueba ambas plataformas: - **Android**: Lista el contenido de `android/app/src/main/assets/`. El archivo configurado en `fallbackFile.android` debe estar presente, y el nombre de archivo exclusivo de iOS no debe aparecer aquí. - **iOS**: Busca `ios/<ProjectName>.xcodeproj/project.pbxproj` el nombre de archivo exclusivo de iOS. Debe aparecer en `PBXFileReference`, el grupo `Resources` y `PBXResourcesBuildPhase`. El nombre de archivo exclusivo de Android no debe aparecer en `project.pbxproj`. --- # File: react-native-use-fallback-paywalls-pure --- --- title: "Usar paywalls de respaldo en un proyecto React Native puro" description: "Configura paywalls de respaldo en un proyecto React Native puro (sin Expo)." --- :::important Esta guía aplica a **proyectos de React Native puro (sin Expo)**. Si usas **Expo**, sigue la [guía de paywall de respaldo para Expo](react-native-use-fallback-paywalls-expo) en su lugar. ::: Para mantener una experiencia de usuario fluida, es importante configurar [respaldos](/fallback-paywalls) para tus flows, [paywalls](paywalls) y [onboardings](onboardings). Esta precaución amplía las capacidades de la aplicación en caso de pérdida parcial o total de la conexión a internet. * **Si la aplicación no puede acceder a los servidores de Adapty:** Podrá mostrar un flow o paywall de respaldo, y acceder a la configuración local del onboarding. * **Si la aplicación no puede acceder a internet:** Podrá mostrar un flow o paywall de respaldo. Los onboardings incluyen contenido remoto y requieren conexión a internet para funcionar. :::important Antes de seguir los pasos de esta guía, [descarga](/local-fallback-paywalls) los archivos de configuración de respaldo desde Adapty. ::: ## Configuración \{#configuration\} ### Android 1. Añade el archivo de configuración de respaldo a tu aplicación. Elige uno de los siguientes directorios: * **android/app/src/main/assets/** * **android/app/src/main/res/raw/** Nota: La carpeta `res/raw` tiene una convención de nomenclatura especial (los nombres deben empezar por una letra, sin mayúsculas, sin caracteres especiales excepto el guión bajo y sin espacios). 2. Actualiza la propiedad `android` de la constante `FileLocation`: * Si el archivo está en el directorio `assets`, indica la ruta del archivo relativa a ese directorio. * Si el archivo está en el directorio `res/raw`, indica el nombre del archivo sin extensión. ### iOS 1. Añade el archivo JSON de respaldo al bundle de tu proyecto: abre el menú **File** en XCode y selecciona la opción **Add Files to "YourProjectName"**. 2. Pasa el nombre de tu archivo de configuración a la propiedad `ios` de la constante `FileLocation`. ## Ejemplo \{#example\} <Tabs groupId="current-os" queryString> <TabItem value="current" label="Current (v3.8+)" default> ```typescript showLineNumbers //after v3.8 const fileLocation = { ios: { fileName: 'ios_fallback.json' }, android: { //if the file is located in 'android/app/src/main/assets/' relativeAssetPath: 'android_fallback.json' } } await adapty.setFallback(fileLocation); ``` </TabItem> <TabItem value="old" label="Legacy (before v3.8)"> ```typescript showLineNumbers //Legacy (before v3.8) const paywallsLocation = { ios: { fileName: 'ios_fallback.json' }, android: { //if the file is located in 'android/app/src/main/assets/' relativeAssetPath: 'android_fallback.json' } } await adapty.setFallbackPaywalls(paywallsLocation); ``` </TabItem> </Tabs> | Parámetro | Descripción | | :------------------- | :------------------------------------------------------- | | **fileLocation** | Objeto que representa la ubicación del archivo de configuración de respaldo. | --- # File: react-native-localizations-and-locale-codes --- --- title: "Usa localizaciones y códigos de idioma en el SDK de React Native" description: "Aprende cómo localizar paywalls en tu app de React Native con el SDK de Adapty." --- <SDKv4> ## Por qué esto es importante \{#why-this-is-important\} Los códigos de idioma entran en juego cuando Adapty selecciona la localización para un flow y cuando lees un Remote Config para un paywall personalizado. Los códigos de idioma son complejos y pueden variar de una plataforma a otra, por lo que Adapty se basa en un estándar interno único para todas las plataformas que admite. Entender ese estándar te ayuda a predecir qué localización recibirá cada usuario. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código está formado por subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués (Brasil)), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de código de configuración regional \{#locale-code-matching\} Cuando Adapty busca la localización que coincide con la configuración regional de un usuario, ocurre lo siguiente: 1. La cadena de configuración regional se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan con guiones (`-`) 2. Adapty busca la localización con el código de configuración regional que coincida exactamente 3. Si no se encuentra ninguna coincidencia, Adapty toma la subcadena antes del primer guión (`pt` para `pt-br`) y busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, Adapty devuelve la localización predeterminada `en` De esta forma, `'pt_BR'`, `pt-BR` y `pt-br` se resuelven en la misma localización. ## Implementación de localizaciones \{#implementing-localizations\} En el SDK v4, no se pasa un código de idioma al obtener un flow. - **Paywalls del Flow Builder y del Paywall Builder**: Adapty resuelve la localización automáticamente a partir del dispositivo y las localizaciones que hayas configurado en el builder. Renderiza el flow con `createFlowView` — no se necesita código de idioma. - **Paywalls personalizados (Remote Config)**: `getFlow` devuelve todas las localizaciones configuradas en `flow.remoteConfigs`. Cada entrada tiene un código `lang` y un objeto `data`. Selecciona la entrada que corresponda al usuario con tu propia lógica de respaldo: ```typescript showLineNumbers const flow = await adapty.getFlow('placement_id'); const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0]; // read your values from config?.data ``` Las reglas de coincidencia de códigos de idioma descritas anteriormente explican cómo Adapty normaliza los códigos `lang` almacenados en cada Remote Config. </SDKv4> <SDKv3> ## Por qué esto es importante \{#why-this-is-important\} Hay algunos escenarios en los que los códigos de idioma entran en juego; por ejemplo, cuando intentas obtener el paywall correcto para la localización actual de tu app. Como los códigos de idioma son complejos y pueden variar de una plataforma a otra, nos basamos en un estándar interno para todas las plataformas que soportamos. Sin embargo, precisamente por esa complejidad, es muy importante que entiendas exactamente qué estás enviando a nuestro servidor para obtener la localización correcta y qué ocurre a continuación, de modo que siempre recibas lo que esperas. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código consiste en subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués (Brasil)), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de código de idioma \{#locale-code-matching\} Cuando Adapty recibe una llamada del SDK con el código de idioma y comienza a buscar la localización correspondiente de un paywall, ocurre lo siguiente: 1. La cadena de idioma entrante se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. A continuación, se busca la localización cuyo código de idioma coincida exactamente 3. Si no se encuentra ninguna coincidencia, se toma la subcadena antes del primer guion (`pt` para `pt-br`) y se busca la localización que coincida 4. Si tampoco se encuentra ninguna coincidencia, se devuelve la localización predeterminada `en` De este modo, un dispositivo iOS que envíe `'pt_BR'`, un dispositivo Android que envíe `pt-BR` y otro dispositivo que envíe `pt-br` obtendrán el mismo resultado. ## Implementación de localizaciones: método recomendado \{#implementing-localizations-recommended-way\} Si te estás preguntando por las localizaciones, es probable que ya estés trabajando con los archivos de cadenas localizadas en tu proyecto. En ese caso, te recomendamos añadir un par clave-valor con el código de locale de Adapty correspondiente en cada uno de tus archivos de localización. Luego, extrae el valor de esa clave al llamar a nuestro SDK, así: ```javascript showLineNumbers // 1. Modify your localization files (e.g., using react-i18next) /* en.json */ { "adapty_paywalls_locale": "en" } /* es.json */ { "adapty_paywalls_locale": "es" } /* pt-BR.json */ { "adapty_paywalls_locale": "pt-br" } // 2. Extract and use the locale code const MyComponent = () => { const { t } = useTranslation(); const fetchPaywall = async () => { const locale = t('adapty_paywalls_locale'); // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; }; ``` Así te aseguras de tener control total sobre qué localización se recuperará para cada usuario de tu app. ## Implementando localizaciones: otra alternativa \{#implementing-localizations-the-other-way\} Puedes obtener resultados similares (aunque no idénticos) sin definir explícitamente los códigos de idioma para cada localización. Esto implica extraer el código de idioma del dispositivo, por ejemplo mediante [`react-native-localize`](https://github.com/zoontek/react-native-localize): ```javascript showLineNumbers const fetchPaywall = async () => { // getLocales() returns the user's preferred locales in BCP-47 format (e.g., 'en-US', 'pt-BR') const locale = RNLocalize.getLocales()[0].languageTag; // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; ``` Ten en cuenta que no recomendamos este enfoque por varias razones: 1. En iOS, los idiomas preferidos y la configuración regional actual no son idénticos. Si quieres que la localización se seleccione correctamente, tendrás que basarte en la lógica de resolución de Apple —que funciona de forma predeterminada cuando usas el enfoque recomendado con archivos de cadenas localizadas— o recrearla tú mismo. 2. La configuración regional del dispositivo puede no coincidir con ninguna localización que hayas configurado en Adapty. En ese caso, el SDK recurre a la coincidencia del primer subtag o, en última instancia, a `en` —que puede no ser el idioma que quieras mostrar por defecto a ese usuario. Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: react-native-web-paywall --- --- title: "Implementar paywalls web" description: "Aprende cómo implementar paywalls web en tu app de React Native con el SDK de Adapty." --- :::important Antes de empezar, asegúrate de haber [configurado tu paywall web en el dashboard](web-paywall) e instalado la versión 3.6.1 o posterior del SDK de Adapty. ::: ## Paywalls web abiertos \{#open-web-paywalls\} Si estás trabajando con un paywall que has desarrollado tú mismo, necesitas gestionar los paywalls web mediante el método del SDK. El método `.openWebPaywall`: 1. Genera una URL única que permite a Adapty vincular un paywall concreto mostrado a un usuario específico con la página web a la que es redirigido. 2. Detecta cuando tus usuarios regresan a la app y, a continuación, llama a `.getProfile` a intervalos cortos para determinar si los derechos de acceso del perfil se han actualizado. De este modo, si el pago se ha realizado correctamente y se han actualizado los derechos de acceso, la suscripción se activa en la app casi de inmediato. ```typescript showLineNumbers title="React Native (TSX)" try { await adapty.openWebPaywall(product); } catch (error) { console.warn('Failed to open web paywall:', error); } ``` :::note Hay dos versiones del método `openWebPaywall`: 1. `openWebPaywall(product)` que genera URLs por paywall y también añade los datos del producto a las URLs. 2. `openWebPaywall(paywall)` que genera URLs por paywall sin añadir los datos del producto a las URLs. Úsala cuando tus productos en el paywall de Adapty sean diferentes a los del web paywall. ::: #### Gestionar errores \{#handle-errors\} | Error | Descripción | Acción recomendada | |-----------------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | El paywall no tiene configurada una URL de compra web | Comprueba si el paywall está correctamente configurado en el Adapty Dashboard | | AdaptyError.productWithoutPurchaseUrl | El producto no tiene una URL de compra web | Verifica la configuración del producto en el Adapty Dashboard | | AdaptyError.failedOpeningWebPaywallUrl | No se pudo abrir la URL en el navegador | Revisa la configuración del dispositivo o proporciona un método de compra alternativo | | AdaptyError.failedDecodingWebPaywallUrl | No se pudieron codificar correctamente los parámetros en la URL | Verifica que los parámetros de la URL sean válidos y estén correctamente formateados | ## Abrir paywalls web en un navegador integrado \{#open-web-paywalls-in-an-in-app-browser\} :::important La apertura de paywalls web en un navegador integrado está disponible a partir de la versión 3.15 del SDK de Adapty. ::: Por defecto, los paywalls web se abren en el navegador externo. Para ofrecer una experiencia de usuario fluida, puedes abrirlos en un navegador integrado. Esto muestra la página de compra web dentro de tu aplicación, permitiendo a los usuarios completar las transacciones sin cambiar de app. Para activarlo, pasa `WebPresentation.BrowserInApp` como segundo argumento de `openWebPaywall`: ```typescript showLineNumbers title="React Native (TSX)" try { await adapty.openWebPaywall( product, WebPresentation.BrowserInApp, // default – WebPresentation.BrowserOutApp ); } catch (error) { console.warn('Failed to open web paywall:', error); } ``` --- # File: react-native-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del Paywall Builder en React Native SDK" description: "Solucionar problemas del Paywall Builder en React Native SDK" --- Esta guía te ayuda a resolver problemas comunes al usar paywalls diseñados en el Adapty Paywall Builder en el SDK de React Native. ## Falla al obtener la configuración de un paywall \{#getting-a-paywall-configuration-fails\} **Problema**: Falla al obtener la configuración de vista para un flow o paywall. **Causa**: El paywall no está habilitado para mostrarse en el dispositivo en el Paywall Builder. **Solución**: Activa el botón **Show on device** en el Paywall Builder. <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## El número de visualizaciones del paywall es demasiado alto \{#the-paywall-view-number-is-too-big\} **Problema**: El contador de visualizaciones del paywall muestra el doble del número esperado. **Motivo**: Es posible que estés llamando a `logShowFlow` (React Native SDK v4+) / `logShowPaywall` en tu código, lo que duplica el contador de visualizaciones si usas el Paywall Builder o el Flow Builder. Para flows y paywalls creados con estas herramientas, el seguimiento de analíticas es automático, por lo que no necesitas usar este método. **Solución**: Asegúrate de no llamar a `logShowFlow` (React Native SDK v4+) / `logShowPaywall` en tu código si usas el Paywall Builder o el Flow Builder. ## Otros problemas \{#other-issues\} **Problema**: Estás experimentando otros problemas relacionados con el Paywall Builder que no se tratan más arriba. **Solución**: Si es necesario, migra el SDK a la versión más reciente siguiendo las [guías de migración](react-native-sdk-migration-guides). Muchos problemas se resuelven en versiones más nuevas del SDK. --- # File: react-native-quickstart-manual --- --- title: "Habilitar compras en tu paywall personalizado con React Native SDK" description: "Integra el SDK de Adapty en tus paywalls personalizados de React Native para habilitar compras in-app." --- Esta guía describe cómo integrar Adapty en tus paywalls personalizados. Mantén el control total sobre la implementación del paywall, mientras el SDK de Adapty obtiene los productos, gestiona las nuevas compras y restaura las anteriores. :::important **Esta guía es para desarrolladores que implementan paywalls personalizados.** Si buscas la forma más sencilla de habilitar compras, usa el [Adapty Flow Builder](react-native-quickstart-paywalls). Con Flow Builder, creas flows en un editor visual sin código, Adapty gestiona toda la lógica de compra automáticamente y puedes probar distintos diseños sin volver a publicar tu app. ::: ## Antes de empezar \{#before-you-start\} ### Configurar productos \{#set-up-products\} Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls**](paywalls) – configuraciones que definen qué productos ofrecer. En Adapty, los paywalls son la única forma de recuperar productos, pero este diseño te permite modificar productos, precios y ofertas sin tocar el código de tu app. - [**Placements**](placements) – dónde y cuándo mostrar los paywalls en tu app (por ejemplo, `main`, `onboarding`, `settings`). Configuras los paywalls para los placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar distintos paywalls a distintos usuarios. Asegúrate de entender estos conceptos incluso si trabajas con tu propio paywall personalizado. Básicamente, son tu forma de gestionar los productos que vendes en tu app. Para implementar tu paywall personalizado, tendrás que crear un **paywall** y añadirlo a un **placement**. Esta configuración te permite recuperar tus productos. Para entender qué debes hacer en el dashboard, sigue la guía de inicio rápido [aquí](quickstart). ### Gestión de usuarios \{#manage-users\} Puedes trabajar con o sin autenticación de backend de tu parte. Sin embargo, el SDK de Adapty gestiona los usuarios anónimos e identificados de forma diferente. Lee la [guía de inicio rápido de identificación](react-native-quickstart-identify) para entender los detalles y asegurarte de que estás trabajando con los usuarios correctamente. ## Paso 1. Obtener productos \{#step-1-get-products\} Para obtener los productos de tu paywall personalizado, necesitas: 1. Obtener el objeto `flow` pasando el ID del [placement](placements) al método `getFlow`. 2. Obtener el array de productos de este flow usando el método `getPaywallProducts`. ```typescript showLineNumbers async function loadPaywall() { try { const flow: AdaptyFlow = await adapty.getFlow('YOUR_PLACEMENT_ID'); const products: AdaptyPaywallProduct[] = await adapty.getPaywallProducts(flow); // Use products to build your custom paywall UI } catch (error) { // Handle the error } } ``` ## Paso 2. Aceptar compras \{#step-2-accept-purchases\} Cuando un usuario pulsa sobre un producto en tu paywall personalizado, llama al método `makePurchase` con el producto seleccionado. Esto gestionará el flow de compra y devolverá el perfil actualizado. ```typescript showLineNumbers async function purchaseProduct(product: AdaptyPaywallProduct) { try { const purchaseResult: AdaptyPurchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': // Purchase successful, profile updated break; case 'user_cancelled': // User canceled the purchase break; case 'pending': // Purchase is pending (e.g., user will pay offline with cash) break; } } catch (error) { // Handle the error } } ``` ## Paso 3. Restaurar compras \{#step-3-restore-purchases\} Los stores requieren que todas las aplicaciones con suscripciones ofrezcan una forma de que los usuarios puedan restaurar sus compras. Llama al método `restorePurchases` cuando el usuario pulse el botón de restaurar. Esto sincronizará su historial de compras con Adapty y devolverá el perfil actualizado. ```typescript showLineNumbers async function restorePurchases() { try { const profile: AdaptyProfile = await adapty.restorePurchases(); // Restore successful, profile updated } catch (error) { // Handle the error } } ``` ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox de App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. Para ver cómo funciona esto en una implementación lista para producción, consulta [CustomPurchaseScreen.tsx](https://github.com/adaptyteam/AdaptySDK-React-Native/blob/master/examples/ExpoGoWebMock/src/CustomPurchaseScreen.tsx) en nuestra app de ejemplo, donde se muestra el manejo de compras con gestión adecuada de errores, estados de carga y estado de la interfaz. A continuación, [comprueba si los usuarios han completado su compra](react-native-check-subscription-status) para determinar si mostrar el paywall o conceder acceso a las funciones de pago. --- # File: fetch-paywalls-and-products-react-native --- --- title: "Obtener paywalls y productos para paywalls de Remote Config en el SDK de React Native" description: "Obtén paywalls y productos en el SDK de React Native de Adapty para mejorar la monetización de los usuarios." --- <SDKv4> Antes de mostrar los Remote Config y los paywalls personalizados, debes obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a Remote Config y paywalls personalizados. Para obtener orientación sobre cómo obtener flows o paywalls personalizados en el **Flow Builder** o el **Paywall Builder**, consulta [Obtener flows de Flow Builder y paywalls de Paywall Builder y su configuración](react-native-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener flows y productos en tu app (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un flow o paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu flow o paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-reactnative) en tu aplicación móvil. </details> ## Obtener información del flow \{#fetch-flow-information\} En Adapty, un [producto](product) es una combinación de productos de App Store y Google Play. Estos productos multiplataforma se integran en flows y paywalls, lo que te permite mostrarlos en placements concretos de tu app móvil. Para mostrar los productos, necesitas obtener un `AdaptyFlow` desde uno de tus [placements](placements) con el método `getFlow`. :::important **No uses IDs de producto en el código.** El único ID que debes incluir en el código es el del placement. Los flows se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un flow devuelve dos productos hoy y tres mañana, muéstralos todos sin modificar el código. ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlow(id); // the requested flow } catch (error) { // handle the error } ``` | Parámetro | Presencia | Descripción | |-------------------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no recibir los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo irregular que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los flows y paywalls en dos capas: la caché actualizada regularmente descrita anteriormente y los [paywalls de respaldo](react-native-use-fallback-paywalls). También usamos CDN para cargar flows y paywalls más rápido, y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para asegurarse de que siempre obtengas la versión más reciente de tus flows, garantizando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeoutMs** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el fallback local.</p><p></p><p>Ten en cuenta que en casos excepcionales este método puede agotar el tiempo de espera ligeramente después de lo especificado en `loadTimeout`, ya que la operación puede consistir en diferentes peticiones internamente.</p> | :::note En v4, `getFlow` ya no acepta un parámetro `locale`. En el caso de los paywalls personalizados, todos los idiomas disponibles se devuelven en el Remote Config del flow (`flow.remoteConfigs`); elige el que corresponda al idioma del dispositivo o la configuración de la app del usuario. ::: ¡No escribas los IDs de productos en el código! Dado que los flows se configuran de forma remota, los productos disponibles, su cantidad y las ofertas especiales (como las pruebas gratuitas) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si inicialmente obtienes 2 productos, la app debería mostrar esos 2 productos. Sin embargo, si más adelante obtienes 3 productos, la app debería mostrar los 3 sin necesidad de modificar el código. Lo único que debes escribir en el código es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Un objeto `AdaptyFlow` que contiene el placement, los identificadores (`id`, `variationId`), el nombre, sus variaciones de paywall (`paywalls`) y un array `remoteConfigs` (una entrada por cada locale configurado). Para obtener los productos del flow, llama a `getPaywallProducts(flow)`. | ## Obtener productos \{#fetch-products\} Una vez que tienes el flow, puedes consultar el array de productos que le corresponde: ```typescript showLineNumbers try { // ...flow const products = await adapty.getPaywallProducts(flow); // the requested products list } catch (error) { // handle the error } ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Lista de objetos [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y varias otras propiedades. | Al implementar tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct). A continuación se ilustran las propiedades más utilizadas, pero consulta el documento enlazado para obtener todos los detalles sobre las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país de la store seleccionado por el usuario, no en el idioma del dispositivo. | | **Price** | Para mostrar el precio en formato localizado, usa `product.price?.localizedString`. Esta localización se basa en la configuración de idioma del dispositivo. También puedes acceder al precio como número con `product.price?.amount`. El valor se proporcionará en la moneda local. Para obtener el símbolo de moneda correspondiente, usa `product.price?.currencySymbol`. | | **Subscription Period** | Para mostrar el período (p. ej., semana, mes, año, etc.), usa `product.subscription?.localizedSubscriptionPeriod`. Esta localización se basa en el idioma del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscription?.subscriptionPeriod`. Desde ahí puedes acceder a la propiedad `unit` para conocer la unidad de tiempo ('day', 'week', 'month', 'year' o 'unknown'). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral verás `'month'` en la propiedad unit y `3` en la propiedad numberOfUnits. | | **Introductory Offer** | Para mostrar un distintivo u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscription?.offer?.phases`. Es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase encontrarás las siguientes propiedades útiles:<br/>• `paymentMode`: una cadena con los valores `'free_trial'`, `'pay_as_you_go'`, `'pay_up_front'` y `'unknown'`. Las pruebas gratuitas serán del tipo `'free_trial'`.<br/>• `price`: el precio con descuento como número. Para pruebas gratuitas, busca el valor `0`.<br/>• `localizedNumberOfPeriods`: una cadena localizada según el idioma del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días mostrará `'3 days'` en este campo.<br/>• `subscriptionPeriod`: alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma manera para las ofertas que se describe en la sección anterior.<br/>• `localizedSubscriptionPeriod`: un período de suscripción formateado del descuento para el idioma del usuario. | ## Acelera la carga del flow con un flow de audiencia predeterminada \{#speed-up-flow-fetching-with-default-audience-flow\} Por lo general, los flows se cargan casi al instante, por lo que no tienes que preocuparte por optimizar este proceso. Sin embargo, si tienes muchas audiencias y placements, y tus usuarios tienen una conexión a internet lenta, la carga de un flow puede tardar más de lo que desearías. En ese caso, puede que quieras mostrar un flow predeterminado para garantizar una buena experiencia de usuario en lugar de no mostrar nada. Para abordar esto, puedes usar el método `getFlowForDefaultAudience`, que obtiene el flow del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el flow mediante el método `getFlow`, tal como se detalla en la sección [Obtener información del flow](fetch-paywalls-and-products-react-native#fetch-flow-information) anterior. :::warning Por qué recomendamos usar `getFlow` El método `getFlowForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar flows distintos según la versión de la app (actual y futuras), puede que te encuentres con dificultades. Tendrás que diseñar flows compatibles con la versión actual (legacy) o asumir que los usuarios con esa versión podrían tener problemas al no renderizarse los flows correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo flow diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluyendo segmentación por países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas para beneficiarte de una obtención más rápida del flow, usa el método `getFlowForDefaultAudience` de la siguiente manera. De lo contrario, quédate con el método `getFlow` descrito [anteriormente](fetch-paywalls-and-products-react-native#fetch-flow-information). ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlowForDefaultAudience(id); // the requested flow } catch (error) { // handle the error } ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | requerido | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo intermitente que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante limpieza manual.</p> | </SDKv4> <SDKv3> Antes de mostrar los Remote Config y los paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a los Remote Config y los paywalls personalizados. Para obtener orientación sobre cómo obtener paywalls personalizados con Paywall Builder, consulta [Obtener paywalls del Paywall Builder y su configuración](react-native-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener paywalls y productos en tu app (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en tu paywall](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-reactnative) en tu app para móvil. </details> ## Obtener información del paywall \{#fetch-paywall-information\} En Adapty, un [producto](product) es una combinación de productos de App Store y Google Play. Estos productos multiplataforma se integran en paywalls, lo que te permite mostrarlos en placements concretos de tu app. Para mostrar los productos, necesitas obtener un [Paywall](paywalls) desde uno de tus [placements](placements) con el método `getPaywall`. :::important **No escribas los IDs de producto en el código.** El único ID que debes incluir en el código es el ID del placement. Los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si un paywall devuelve dos productos hoy y tres mañana, muéstralos todos sin necesidad de modificar el código. ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywall(id, locale); // the requested paywall } catch (error) { // handle the error } ``` | Parámetro | Presencia | Descripción | |-------------------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](react-native-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no reciban los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando la app se reinstala o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls en dos capas: la caché de actualización regular descrita anteriormente y los [paywalls de respaldo](react-native-use-fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN sea inaccesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.</p> | | **loadTimeoutMs** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p></p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede estar compuesta de diferentes peticiones internamente.</p> | ¡No escribas los IDs de productos en el código! Dado que los paywalls se configuran de forma remota, los productos disponibles, el número de productos y las ofertas especiales (como períodos de prueba gratuitos) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si inicialmente obtienes 2 productos, tu app debería mostrar esos 2 productos. Sin embargo, si más adelante obtienes 3 productos, tu app debería mostrar los 3 sin necesidad de cambios en el código. Lo único que debes escribir directamente en el código es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Un objeto [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) con: una lista de IDs de producto, el identificador del paywall, Remote Config y varias otras propiedades. | ## Obtener productos \{#fetch-products\} Una vez que tienes el paywall, puedes consultar el array de productos que le corresponde: ```typescript showLineNumbers try { // ...paywall const products = await adapty.getPaywallProducts(paywall); // the requested products list } catch (error) { // handle the error } ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Lista de objetos [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y otras propiedades. | Al implementar tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct). A continuación se muestran las propiedades más utilizadas, pero consulta el documento enlazado para obtener todos los detalles sobre las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Título** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país del store seleccionado por el usuario, no en el idioma del dispositivo. | | **Precio** | Para mostrar el precio localizado, usa `product.price?.localizedString`. La localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número con `product.price?.amount`. El valor se devolverá en la moneda local. Para obtener el símbolo de la moneda, usa `product.price?.currencySymbol`. | | **Período de suscripción** | Para mostrar el período (p. ej., semana, mes, año, etc.), usa `product.subscription?.localizedSubscriptionPeriod`. La localización se basa en la configuración regional del dispositivo. Para obtener el período de suscripción mediante código, usa `product.subscription?.subscriptionPeriod`. Desde ahí puedes acceder a la propiedad `unit` para conocer la unidad de tiempo (es decir, `'day'`, `'week'`, `'month'`, `'year'` o `'unknown'`). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, en una suscripción trimestral verás `'month'` en la propiedad `unit` y `3` en la propiedad `numberOfUnits`. | | **Oferta introductoria** | Para mostrar una etiqueta u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscription?.offer?.phases`. Es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase encontrarás las siguientes propiedades útiles:<br/>• `paymentMode`: cadena de texto con los valores `'free_trial'`, `'pay_as_you_go'`, `'pay_up_front'` y `'unknown'`. Las pruebas gratuitas serán del tipo `'free_trial'`.<br/>• `price`: el precio con descuento como número. En las pruebas gratuitas, busca el valor `0`.<br/>• `localizedNumberOfPeriods`: cadena localizada según la configuración regional del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días mostrará `'3 days'` en este campo.<br/>• `subscriptionPeriod`: alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona igual que se describe en la sección anterior para las ofertas.<br/>• `localizedSubscriptionPeriod`: período de suscripción formateado del descuento según la configuración regional del usuario. | ## Acelera la carga de paywalls con el paywall de audiencia predeterminada \{#speed-up-paywall-fetching-with-default-audience-paywall\} Normalmente, los paywalls se cargan casi al instante, por lo que no necesitas preocuparte por optimizar este proceso. Sin embargo, cuando tienes muchas audiencias y paywalls, y tus usuarios tienen una conexión a internet lenta, la carga de un paywall puede tardar más de lo deseable. En esos casos, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia fluida en lugar de no mostrar nada. Para solucionar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es importante entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, tal como se detalla en la sección [Obtener información del paywall](fetch-paywalls-and-products-react-native#fetch-paywall-information) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar distintos paywalls para diferentes versiones de la app (la actual y las futuras), puedes encontrarte con dificultades. Tendrás que diseñar paywalls compatibles con la versión actual (heredada) o asumir que los usuarios con esa versión podrían tener problemas con paywalls que no se renderizan correctamente. - **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la personalización del targeting (incluida la segmentación por país, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas a cambio de una obtención más rápida del paywall, usa el método `getPaywallForDefaultAudience` tal como se muestra a continuación. De lo contrario, utiliza `getPaywall` descrito [anteriormente](fetch-paywalls-and-products-react-native#fetch-paywall-information). ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywallForDefaultAudience(id, locale); // the requested paywall } catch (error) { // handle the error } ``` :::note El método `getPaywallForDefaultAudience` está disponible a partir de la versión 2.11.2 del SDK de React Native. ::: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>predeterminado: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](react-native-localizations-and-locale-codes) para obtener más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | predeterminado: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si consideras que tus usuarios tienen una conexión a internet inestable, puedes usar `.returnCacheDataElseLoad` para devolver los datos en caché cuando estén disponibles. En este caso, puede que los usuarios no reciban los datos más recientes, pero experimentarán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | </SDKv3> --- # File: present-remote-config-paywalls-react-native --- --- title: "Renderizar paywall diseñado con Remote Config en React Native SDK" description: "Descubre cómo presentar paywalls de Remote Config en el SDK de Adapty para React Native y personalizar la experiencia del usuario." --- <SDKv4> Si has personalizado un flow mediante Remote Config, necesitarás implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú decides qué se incluye y cómo se ve la vista del flow. Disponemos de un método para obtener la configuración remota, dándote la autonomía de mostrar tu flow personalizado configurado a través de Remote Config. ## Obtener la Remote Config de un flow y presentarlo \{#get-flow-remote-config-and-present-it\} En la versión 4, un flow incluye una entrada `AdaptyRemoteConfig` por cada idioma configurado en el array `remoteConfigs`. Elige el idioma que coincida con las preferencias del usuario y lee los valores que necesites de su `data`. ```typescript showLineNumbers try { const flow = await adapty.getFlow("YOUR_PLACEMENT_ID"); const config = flow.remoteConfigs?.find((c) => c.lang === "en") ?? flow.remoteConfigs?.[0]; const headerText = config?.data?.["header_text"]; } catch (error) { // handle the error } ``` En este punto, una vez que hayas recibido todos los valores necesarios, es momento de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a distintos tamaños de pantalla y orientaciones de dispositivos móviles, ofreciendo una experiencia fluida y fácil de usar en todos los dispositivos. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls-react-native#track-paywall-view-events) como se describe a continuación, para que Adapty Analytics pueda capturar información para los funnels y las pruebas A/B. ::: Cuando termines de mostrar el flow, continúa con la configuración del proceso de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu flow. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](react-native-making-purchases). Te recomendamos [crear un paywall de respaldo llamado paywall de respaldo](react-native-use-fallback-paywalls). Este paywall de respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Registrar eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus flows. Aunque los datos sobre compras se recopilan automáticamente, registrar las visualizaciones del flow requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un flow. Para registrar un evento de visualización de flow, simplemente llama a `.logShowFlow(flow)`, y se reflejará en las métricas de tu paywall en los funnels y las pruebas A/B. :::important No es necesario llamar a `.logShowFlow(flow)` si estás mostrando flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder). En esos casos, Adapty registra las vistas automáticamente. ::: ```typescript showLineNumbers await adapty.logShowFlow(flow); ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :-------- | :-------- |:-------------------------------------------------------------------------------------| | **flow** | requerido | Un objeto `AdaptyFlow` obtenido mediante `adapty.getFlow(placementId)`. | </SDKv4> <SDKv3> Si has personalizado un paywall con Remote Config, tendrás que implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú tienes el control total sobre qué incluir y cómo se ve tu paywall. Te proporcionamos un método para obtener la configuración remota, dándote la autonomía de mostrar tu paywall personalizado configurado a través de Remote Config. ## Obtener la configuración remota de un paywall y mostrarlo \{#get-paywall-remote-config-and-present-it\} Para obtener la Remote Config de un paywall, accede a la propiedad `remoteConfig` y extrae los valores que necesites. ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: "YOUR_PLACEMENT_ID" }); const headerText = paywall.remoteConfig?.data?.["header_text"]; } catch (error) { // handle the error } ``` En este punto, una vez que hayas recibido todos los valores necesarios, es hora de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a distintas pantallas y orientaciones de dispositivos móviles, ofreciendo una experiencia fluida y fácil de usar en cualquier dispositivo. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls-react-native#track-paywall-view-events) como se describe a continuación, para que los análisis de Adapty capturen la información necesaria para los funnels y las pruebas A/B. ::: Una vez que hayas mostrado el paywall, continúa configurando el flujo de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu paywall. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](react-native-making-purchases). Te recomendamos [crear un paywall de respaldo llamado paywall de respaldo](react-native-use-fallback-paywalls). Este paywall de respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Registrar eventos de visualización de paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus paywalls. Aunque recopilamos los datos de compras automáticamente, registrar las visualizaciones de paywall requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un paywall. Para registrar un evento de visualización de paywall, simplemente llama a `.logShowPaywall(paywall)`, y se verá reflejado en las métricas de tu paywall en los embudos y las pruebas A/B. :::important No es necesario llamar a `.logShowPaywall(paywall)` si muestras paywalls creados con el [Paywall Builder](adapty-paywall-builder). ::: ```typescript showLineNumbers await adapty.logShowPaywall(paywall); ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :-------- |:--------------------------------------------------------------------------------------------| | **paywall** | requerido | Un objeto [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall). | </SDKv3> --- # File: react-native-making-purchases --- --- title: "Realizar compras en la app móvil con el SDK de React Native" description: "Guía para gestionar compras in-app y suscripciones con Adapty." --- Mostrar paywalls dentro de tu app móvil es un paso fundamental para ofrecer a los usuarios acceso a contenido o servicios premium. Sin embargo, con solo mostrar estos paywalls es suficiente para gestionar compras únicamente si usas el [Paywall Builder](adapty-paywall-builder) para personalizarlos. Si no usas el Paywall Builder, debes usar un método separado llamado `.makePurchase()` para completar una compra y desbloquear el contenido deseado. Este método es la puerta de entrada para que los usuarios interactúen con los paywalls y procedan con sus transacciones. Si tu paywall tiene una oferta promocional activa para el producto que el usuario quiere comprar, Adapty la aplicará automáticamente en el momento de la compra. :::warning Ten en cuenta que la oferta introductoria solo se aplicará automáticamente si usas los paywalls configurados con el Paywall Builder. En otros casos, deberás [verificar la elegibilidad del usuario para una oferta introductoria en iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Omitir este paso puede provocar que tu app sea rechazada durante la revisión. Además, podría resultar en cobrar el precio completo a usuarios que tienen derecho a una oferta introductoria. ::: Asegúrate de haber realizado la [configuración inicial](quickstart) sin saltarte ningún paso. Sin ella, no podemos validar las compras. ## Realizar una compra \{#make-purchase\} :::note **¿Usas [Paywall Builder](adapty-paywall-builder)?** Las compras se procesan automáticamente, puedes saltarte este paso. **¿Buscas instrucciones paso a paso?** Consulta la [guía de inicio rápido](react-native-implement-paywalls-manually) para ver las instrucciones de implementación completas con todo el contexto. ::: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` | Parámetro | Presencia | Descripción | | :---------- | :-------- |:-------------------------------------------------------------------------------------------------------------------------------| | **Product** | requerido | Un objeto [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) obtenido del paywall. | Parámetros de respuesta: | Parámetro | Descripción | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Si la solicitud se ha realizado correctamente, la respuesta contiene este objeto. Un objeto [AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile) proporciona información completa sobre los niveles de acceso, suscripciones y compras únicas de un usuario dentro de la app.</p><p>Comprueba el estado del nivel de acceso para determinar si el usuario tiene el acceso requerido a la app.</p> | :::warning **Nota:** si todavía usas una versión de StoreKit de Apple inferior a v2.0 y una versión del SDK de Adapty inferior a v2.9.0, debes proporcionar el [secreto compartido de Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret) en su lugar. Este método está actualmente en desuso por Apple. ::: ## Cambiar de suscripción al realizar una compra \{#change-subscription-when-making-a-purchase\} Cuando un usuario opta por una nueva suscripción en lugar de renovar la actual, el funcionamiento depende del store: - En el App Store, la suscripción se actualiza automáticamente dentro del grupo de suscripciones. Si un usuario adquiere una suscripción de un grupo mientras ya tiene activa otra de un grupo diferente, ambas suscripciones estarán activas al mismo tiempo. - En Google Play, la suscripción no se actualiza automáticamente. Debes gestionar el cambio en el código de tu app como se describe a continuación. Para reemplazar una suscripción por otra en Android, llama al método `.makePurchase()` con el parámetro adicional: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product, params); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` Parámetro de solicitud adicional: | Parámetro | Presencia | Descripción | | :--------- | :-------- | :----------------------------------------------------------- | | **params** | requerido | un objeto del tipo [`MakePurchaseParamsInput`](https://react-native.adapty.io/types/makepurchaseparamsinput). | :::info **Versión 3.8.2+**: La estructura `MakePurchaseParamsInput` ha sido actualizada. `oldSubVendorProductId` y `prorationMode` ahora están anidados bajo `subscriptionUpdateParams`, e `isOfferPersonalized` se ha movido al nivel superior. ```javascript makePurchase(product, { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } }); ``` ::: Puedes leer más sobre suscripciones y modos de reemplazo en la documentación para desarrolladores de Google: - [Acerca de los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Recomendaciones de Google para los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Modo de reemplazo [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Nota: este método solo está disponible para actualizaciones de suscripción. No se admiten cambios a un plan inferior. - Modo de reemplazo [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Nota: el cambio de suscripción real solo se producirá cuando finalice el período de facturación de la suscripción actual. ## Canjear códigos de oferta en iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Sobre los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios concretos. A diferencia de las ofertas habituales, que se aplican de forma automática, los códigos de oferta se distribuyen fuera de la app — por email, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, accediendo a una URL de canje o a través de un diálogo dentro de la app. Para configurar códigos de oferta, abre una suscripción en App Store Connect y ve a su sección **Offer Codes**. Puedes crear [tres tipos](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) de códigos de oferta: - **Free** — la suscripción es gratuita durante un período determinado y la siguiente renovación se cobra al precio completo. - **Pay as you go** — el usuario paga un precio reducido en cada ciclo de facturación durante un período determinado y, después, la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único reducido por toda la duración de la oferta y, después, la suscripción se renueva al precio completo. No es necesario añadir los códigos de oferta a Adapty. Apple etiqueta cada transacción durante el período de la oferta con la categoría del código de oferta. Esto incluye el canje inicial y todas las renovaciones con descuento posteriores. Adapty detecta la etiqueta y registra cada transacción con la categoría de oferta `offer_code`. Cuando termina el período de oferta y la suscripción se renueva al precio completo, la etiqueta deja de estar presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Resolución de discrepancias en los ingresos \{#revenue-discrepancy-troubleshooting\} Si observas que una transacción con código de oferta aparece en Adapty al precio completo del producto en lugar del precio reducido, verifica lo siguiente en App Store Connect: - El código de oferta tiene los precios correctos configurados para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está configurado para el país o región específica del usuario. Apple envía el precio regional en la transacción. Si no hay ningún precio regional configurado para la oferta, Apple puede enviar el precio completo del producto. Puedes filtrar y verificar las transacciones con código de oferta en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds) mediante los filtros de tipo de oferta **Offer Code** y **Offer Discount Type**. #### Códigos promocionales heredados (obsoletos) \{#legacy-promo-codes-deprecated\} :::warning Apple dejó obsoletos los códigos promocionales para compras in-app en marzo de 2026. Los códigos de oferta los sustituyen con más funcionalidades: elegibilidad configurable, fechas de expiración y hasta 1 millón de códigos por trimestre. Si antes usabas códigos promocionales para compras in-app, migra a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app y versión) daban acceso gratuito a una suscripción. A diferencia de los códigos de oferta, Apple no incluía información de descuento en las transacciones con código promocional — enviaba el precio completo del producto en el recibo. Por ello, Adapty registraba estas transacciones al precio completo, lo que generaba discrepancias entre los análisis de Adapty y App Store Connect. Si ves transacciones históricas al precio completo que deberían haber sido gratuitas, es probable que provengan de códigos promocionales heredados. Como estos códigos ya están obsoletos, migra a los códigos de oferta para un seguimiento preciso de los ingresos. </Details> Para mostrar la hoja de canje de códigos en tu app: ```typescript showLineNumbers adapty.presentCodeRedemptionSheet(); ``` :::danger Según nuestras observaciones, la hoja de canje de códigos de oferta en algunas apps puede no funcionar de manera fiable. Recomendamos redirigir al usuario directamente a la App Store. Para hacerlo, debes abrir la URL con el siguiente formato: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ## Gestionar planes prepagos (Android) \{#manage-prepaid-plans-android\} Si los usuarios de tu app pueden comprar [planes prepagos](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (por ejemplo, adquirir una suscripción no renovable por varios meses), puedes habilitar las [transacciones pendientes](https://developer.android.com/google/play/billing/subscriptions#pending) para planes prepagos. ```typescript showLineNumbers adapty.activate("PUBLIC_SDK_KEY", { android: { pendingPrepaidPlansEnabled: true } }); ``` --- # File: react-native-restore-purchase --- --- title: "Restaurar compras en una app móvil con React Native SDK" description: "Aprende a restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- La restauración de compras tanto en iOS como en Android es una funcionalidad que permite a los usuarios recuperar el acceso a contenido adquirido anteriormente, como suscripciones o compras in-app, sin que se les vuelva a cobrar. Esta funcionalidad es especialmente útil para usuarios que hayan desinstalado y reinstalado la app, o que hayan cambiado a un nuevo dispositivo y quieran acceder a su contenido previamente comprado sin pagar de nuevo. :::note En los paywalls creados con [Paywall Builder](adapty-paywall-builder), las compras se restauran automáticamente sin que tengas que añadir código adicional. Si es tu caso, puedes saltarte este paso. ::: Para restaurar una compra cuando no utilizas el [Paywall Builder](adapty-paywall-builder) para personalizar el paywall, llama al método `.restorePurchases()`: ```typescript showLineNumbers try { const profile = await adapty.restorePurchases(); const isSubscribed = profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // restore access } } catch (error) { // handle the error } ``` Parámetros de respuesta: | Parámetro | Descripción | |---------|-----------| | **Profile** | <p>Un objeto [`AdaptyProfile`](https://react-native.adapty.io/interfaces/adaptyprofile). Este modelo contiene información sobre niveles de acceso, suscripciones y compras únicas.</p><p>Comprueba el **estado del nivel de acceso** para determinar si el usuario tiene acceso a la app.</p> | :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: --- # File: implement-observer-mode-react-native --- --- title: "Implementar el modo Observer en React Native SDK" description: "Implementa el modo Observer en Adapty para rastrear eventos de suscripción de usuarios en React Native SDK." --- Si ya tienes tu propia infraestructura de compras y no estás listo para migrar completamente a Adapty, puedes explorar el [modo Observer](observer-vs-full-mode). En su forma básica, el modo Observer ofrece analíticas avanzadas e integración fluida con sistemas de atribución y analíticas. Si esto cubre tus necesidades, solo tienes que: 1. Activarlo al configurar el SDK de Adapty estableciendo el parámetro `observerMode` en `true`. Sigue las instrucciones de configuración para [React Native](sdk-installation-reactnative). 2. [Reportar transacciones](report-transactions-observer-mode-react-native) desde tu infraestructura de compras existente a Adapty. ### Configuración del modo Observer \{#observer-mode-setup\} Activa el modo Observer si gestionas las compras y el estado de la suscripción tú mismo y usas Adapty solo para enviar eventos de suscripción y analíticas. :::important Cuando se ejecuta en modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlas tú mismo. ::: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { observerMode: true, // Enable observer mode }); ``` Parámetros: | Parámetro | Descripción | | --------------------------- | ------------------------------------------------------------ | | observerMode | Un valor booleano que controla el [modo Observer](observer-vs-full-mode). El valor por defecto es `false`. | ## Usar los paywalls de Adapty en el modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los paywalls y las funciones de pruebas A/B de Adapty, puedes hacerlo, pero requiere una configuración adicional en el modo Observer. Esto es lo que necesitas hacer además de los pasos anteriores: 1. Muestra los paywalls de la forma habitual para los [paywalls de Remote Config](present-remote-config-paywalls-react-native). 3. [Asocia los paywalls](report-transactions-observer-mode-react-native) con las transacciones de compra. --- # File: report-transactions-observer-mode-react-native --- --- title: "Reportar transacciones en Observer Mode en el SDK de React Native" description: "Reporta transacciones de compra en el Observer Mode de Adapty para obtener información sobre usuarios y seguimiento de ingresos en el SDK de React Native." --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+ (current)" default> En Observer Mode, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Debes reportar las transacciones desde tu app store. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` para reportar explícitamente cada transacción y que Adapty pueda reconocerla. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra al paywall que la originó, garantizando análisis precisos del paywall. ```typescript showLineNumbers const variationId = paywall.variationId; try { await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | obligatorio | <ul><li> Para iOS: identificador de la transacción.</li><li> Para Android: identificador de cadena (`purchase.getOrderId`) de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación.</li></ul> | | variationId | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://react-native.adapty.io/interfaces/adaptypaywall). | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x (legacy)" default> En Observer Mode, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Debes reportar las transacciones desde tu app store o restaurarlas. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` en ambas plataformas para reportar explícitamente cada transacción, y usa `restorePurchases` en Android como paso adicional para asegurarte de que Adapty la reconozca. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a estos métodos, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra al paywall que la originó, garantizando análisis precisos del paywall. ```typescript showLineNumbers if (Platform.OS === 'android') { try { await adapty.restorePurchases(); } catch (error) { // handle the error } } ... const variationId = paywall.variationId; try { await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | obligatorio | <ul><li> Para iOS, StoreKit 1: un objeto [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</li><li> Para iOS, StoreKit 2: objeto [Transaction](https://developer.apple.com/documentation/storekit/transaction).</li><li> Para Android: identificador de cadena (`purchase.getOrderId`) de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación.</li></ul> | | variationId | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://react-native.adapty.io/interfaces/adaptypaywall). | </TabItem> <TabItem value="old2" label="Adapty SDK up to 3.2.x (legacy)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> **Reportar transacciones** - Las versiones hasta la 3.1.x escuchan automáticamente las transacciones en el App Store, por lo que no se requiere reporte manual. - La versión 3.2 no admite Observer Mode. </TabItem> <TabItem value="kotlin" label="Android and Android-based cross-platforms" default> **Reportar transacciones** Usa `restorePurchases` para reportar una transacción a Adapty en Observer Mode, tal como se explica en la página [Restaurar compras en el código móvil](react-native-restore-purchase). :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `restorePurchases`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: </TabItem> </Tabs> **Asociar paywalls a transacciones** El SDK de Adapty no puede determinar el origen de las compras, ya que eres tú quien las procesa. Por lo tanto, si planeas usar paywalls y/o pruebas A/B en Observer Mode, debes asociar la transacción proveniente de tu app store con el paywall correspondiente en el código de tu app móvil. Es importante hacerlo correctamente antes de publicar tu app; de lo contrario, generará errores en los análisis. ```typescript const variationId = paywall.variationId; try { await adapty.setVariationId('transactionId', variationId); } catch (error) { // handle the `AdaptyError` } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | obligatorio | <p>Para iOS, StoreKit 1: un objeto [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</p><p>Para iOS, StoreKit 2: objeto [Transaction](https://developer.apple.com/documentation/storekit/transaction).</p><p>Para Android: identificador de cadena (purchase.getOrderId de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación.</p> | | variationId | obligatorio | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://react-native.adapty.io/interfaces/adaptypaywall). | </TabItem> </Tabs> --- # File: react-native-troubleshoot-purchases --- --- title: "Solucionar problemas de compras en React Native SDK" description: "Solucionar problemas de compras en React Native SDK" --- Esta guía te ayuda a resolver los problemas más comunes al implementar compras manualmente en el SDK de React Native. ## makePurchase se ejecuta correctamente, pero el perfil no se actualiza \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Problema**: El método `makePurchase` se completa correctamente, pero el perfil del usuario y el estado de la suscripción no se actualizan en Adapty. **Causa**: Normalmente indica que la configuración de Google Play Store está incompleta o tiene errores. **Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android). ## makePurchase se invoca dos veces \{#makepurchase-is-invoked-twice\} **Problema**: El método `makePurchase` se está llamando varias veces para la misma compra. **Causa**: Esto suele ocurrir cuando el flujo de compra se activa varias veces por problemas de gestión del estado de la UI o interacciones rápidas del usuario. **Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android). ## AdaptyError.cantMakePayments en modo observer \{#adaptyelrorcantmakepayments-in-observer-mode\} **Problema**: Recibes `AdaptyError.cantMakePayments` al usar `makePurchase` en modo observer. **Causa**: En el modo observer, debes gestionar las compras por tu cuenta, no usar el método `makePurchase` de Adapty. **Solución**: Si usas `makePurchase` para las compras, desactiva el modo observer. Tienes que elegir entre usar `makePurchase` o gestionar las compras por tu cuenta en el modo observer. Consulta [Implementar el modo Observer](implement-observer-mode-react-native) para más detalles. ## Error de Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Problema**: Estás recibiendo un error de facturación no disponible de Google Play Store. **Causa**: Este error no está relacionado con Adapty. Es un error de la Biblioteca de Facturación de Google Play que indica que la facturación no está disponible en el dispositivo. **Solución**: Este error no está relacionado con Adapty. Puedes consultar más información en la documentación de Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## No se encuentran makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Problema**: Estás encontrando problemas porque no se encuentran los `makePurchasesCompletionHandlers`. **Causa**: Esto suele estar relacionado con problemas en las pruebas en sandbox. **Solución**: Crea un nuevo usuario sandbox e inténtalo de nuevo. Esto suele resolver los problemas del manejador de finalización de compras relacionados con el sandbox. ## Otros problemas \{#other-issues\} **Problema**: Experimentas otros problemas relacionados con las compras que no se tratan arriba. **Solución**: Migra el SDK a la última versión siguiendo las [guías de migración](react-native-sdk-migration-guides) si es necesario. Muchos problemas se resuelven en versiones más recientes del SDK. --- # File: react-native-identifying-users --- --- title: "Identificar usuarios en el SDK de React Native" description: "Aprende cómo identificar usuarios en tu app de React Native con el SDK de Adapty." --- Adapty crea un ID de perfil interno para cada usuario. Sin embargo, si tienes tu propio sistema de autenticación, debes establecer tu propio Customer User ID. Puedes encontrar usuarios por su Customer User ID en la sección [Perfiles](profiles-crm) y usarlo en la [API del lado del servidor](getting-started-with-server-side-api), que se enviará a todas las integraciones. ### Establecer el ID de usuario en la configuración \{#setting-customer-user-id-on-configuration\} Si tienes un ID de usuario durante la configuración, pásalo como parámetro `customerUserId` al método `.activate()`: ```typescript showLineNumbers adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID" }); ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Configurar el ID de usuario del cliente tras la configuración \{#setting-customer-user-id-after-configuration\} Si no tienes un ID de usuario en la configuración del SDK, puedes establecerlo más adelante en cualquier momento con el método `.identify()`. Los casos más habituales para usar este método son después del registro o la autenticación, cuando el usuario pasa de ser anónimo a estar autenticado. ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // successfully identified } catch (error) { // handle the error } ``` Parámetros de la solicitud: - **Customer User ID** (obligatorio): un identificador de usuario de tipo string. :::warning Reenvío de datos significativos del usuario En algunos casos, como cuando un usuario vuelve a iniciar sesión en su cuenta, los servidores de Adapty ya tienen información sobre ese usuario. En estos escenarios, el SDK de Adapty cambiará automáticamente para trabajar con el nuevo usuario. Si pasaste algún dato al usuario anónimo, como atributos personalizados o atribuciones de redes de terceros, deberás reenviar esos datos para el usuario identificado. También es importante tener en cuenta que debes volver a solicitar todos los paywalls y productos después de identificar al usuario, ya que los datos del nuevo usuario pueden ser diferentes. ::: ### Cerrar sesión e iniciar sesión \{#logging-out-and-logging-in\} Puedes cerrar la sesión del usuario en cualquier momento llamando al método `.logout()`: ```typescript showLineNumbers try { await adapty.logout(); // successful logout } catch (error) { // handle the error } ``` Luego puedes iniciar sesión del usuario usando el método `.identify()`. ## Asignar `appAccountToken` (iOS) \{#assign-appaccounttoken-ios\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) es un **UUID** que te permite vincular las transacciones del App Store con la identidad interna de tus usuarios. StoreKit asocia este token con cada transacción, de modo que tu backend puede relacionar los datos del App Store con tus usuarios. Usa un UUID estable generado por usuario y reutilízalo para la misma cuenta en todos los dispositivos. Así te aseguras de que las compras y las notificaciones del App Store queden correctamente vinculadas. Puedes establecer el token de dos maneras: durante la activación del SDK o al identificar al usuario. :::important Siempre debes pasar `appAccountToken` junto con `customerUserId`. Si solo pasas el token, no se incluirá en la transacción. ::: ```typescript showLineNumbers // Durante la configuración: adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID", ios: { appAccountToken: "YOUR_APP_ACCOUNT_TOKEN" }, }); // O al identificar usuarios try { await adapty.identify("YOUR_USER_ID", { ios: {appAccountToken: 'YOUR_APP_ACCOUNT_TOKEN'} }); // identificado correctamente } catch (error) { // gestiona el error } ``` ### Establecer IDs de cuenta ofuscados (Android) \{#set-obfuscated-account-ids-android\} Google Play requiere IDs de cuenta ofuscados en ciertos casos de uso para mejorar la privacidad y seguridad del usuario. Estos IDs ayudan a Google Play a identificar las compras manteniendo el anonimato de la información del usuario, lo que resulta especialmente importante para la prevención del fraude y el análisis de datos. Es posible que necesites establecer estos IDs si tu aplicación maneja datos de usuario sensibles o si debes cumplir con normativas de privacidad específicas. Los IDs ofuscados permiten a Google Play hacer seguimiento de las compras sin exponer los identificadores reales de los usuarios. ```typescript showLineNumbers // Durante la configuración: adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID", android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' } }); // O al identificar usuarios try { await adapty.identify("YOUR_USER_ID", { android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' } }); // identificado correctamente } catch (error) { // gestionar el error } ``` ## Detectar usuarios en varios dispositivos \{#detect-users-across-devices\} Cuando el SDK se activa, lee automáticamente los derechos existentes del usuario desde StoreKit (iOS) o Google Play Billing (Android) y los sincroniza con el backend de Adapty. Una suscripción activa aparece en el perfil de Adapty sin que la app llame a `restorePurchases`. Lo que **no** ocurre automáticamente es reconocer que un perfil en un dispositivo nuevo pertenece al mismo usuario que el perfil en el dispositivo original. Adapty relaciona perfiles por Customer User ID, así que la continuidad de identidad depende de lo que uses como CUID. **Lo que Adapty puede detectar entre dispositivos** | Tu configuración | Lo que Adapty detecta | Lo que debes hacer | | --- | --- | --- | | Customer User ID = `device_id` (sin login en la app) | El nuevo dispositivo obtiene un CUID diferente y, por tanto, un perfil diferente. La suscripción se sincroniza con el nuevo perfil mediante un evento **Access level updated**, pero `subscription_started` no se dispara — el nuevo perfil se trata como heredero de la compra original. Los análisis basados en `subscription_started` contarán de menos a los usuarios que vuelven. | Usa un ID de cuenta estable como Customer User ID para que un usuario que regresa coincida con el perfil existente en todos los dispositivos. | | Customer User ID = ID de cuenta estable (login en cada dispositivo) | El SDK sincroniza automáticamente la suscripción en `activate()`, e `identify()` relaciona el perfil existente por CUID. | No se necesita configuración adicional — tanto la identidad como la suscripción se resuelven automáticamente. | | Heredero de Apple Family Sharing | El miembro de la familia recibe la suscripción solo a través de un evento **Access level updated** — `subscription_started` no se dispara. | Escucha el evento **Access level updated**. Consulta [Apple Family Sharing](apple-family-sharing) para ver la matriz de eventos completa. | | Misma cuenta de Apple/Google, distintos usuarios dentro de la app | El primer perfil que registra la compra se convierte en el principal. Los perfiles posteriores ven la suscripción a través de una cadena de herederos, con un evento **Access level updated**. | Exige login y elige un [modo de compartición](sharing-paid-access-between-user-accounts) que se adapte a tu modelo. | **Restaurar compras en un dispositivo nuevo** Muestra un botón "Restaurar compras" iniciado por el usuario en tu paywall. Apple App Review (directriz 3.1.1) lo exige, y actúa como alternativa cuando la sincronización automática no cubre algún caso límite. El botón debe llamar a `restorePurchases` en tu SDK. No es necesario llamar a `restorePurchases` de forma programática al primer inicio para el uso normal — el SDK ya ejecuta el equivalente en `activate()`. Reserva las llamadas programáticas para forzar una verificación de recibo actualizada, por ejemplo al depurar un acceso que falta después de que `activate()` haya completado. --- # File: react-native-setting-user-attributes --- --- title: "Establecer atributos de usuario en el SDK de React Native" description: "Aprende cómo actualizar atributos de usuario y datos de perfil en tu app de React Native con el SDK de Adapty." --- Puedes establecer atributos opcionales como correo electrónico, número de teléfono, etc., para los usuarios de tu app. Luego puedes usar esos atributos para crear [segmentos](segments) de usuarios o simplemente verlos en el CRM. ### Establecer atributos de usuario \{#setting-user-attributes\} Para establecer atributos de usuario, llama al método `.updateProfile()`: ```typescript showLineNumbers // Only for TypeScript validation const params: AdaptyProfileParameters = { email: 'email@email.com', phoneNumber: '+18888888888', firstName: 'John', lastName: 'Appleseed', gender: 'other', birthday: new Date().toISOString(), }; try { await adapty.updateProfile(params); } catch (error) { // handle `AdaptyError` } ``` Ten en cuenta que los atributos que hayas establecido previamente con el método `updateProfile` no se restablecerán. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Lista de claves permitidas \{#the-allowed-keys-list\} Las claves permitidas `<Key>` de `AdaptyProfileParameters.Builder` y los valores `<Value>` correspondientes se indican a continuación: | Clave | Valor | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | Enum, los valores permitidos son: `female`, `male`, `other` | | birthday | Date | ### Atributos de usuario personalizados \{#custom-user-attributes\} Puedes definir tus propios atributos personalizados. Estos suelen estar relacionados con el uso de tu app. Por ejemplo, en aplicaciones de fitness pueden ser el número de ejercicios por semana; en apps de aprendizaje de idiomas, el nivel de conocimiento del usuario, etc. Puedes usarlos en segmentos para crear paywalls y ofertas dirigidas, y también en analíticas para determinar qué métricas de producto influyen más en los ingresos. ```typescript showLineNumbers try { await adapty.updateProfile({ codableCustomAttributes: { key_1: 'value_1', key_2: 2, }, }); } catch (error) { // handle `AdaptyError` } ``` Para eliminar una clave existente, usa el método `.withRemoved(customAttributeForKey:)`: ```typescript showLineNumbers try { // to remove a key, pass null as its value await adapty.updateProfile({ codableCustomAttributes: { key_1: null, key_2: null, }, }); } catch (error) { // handle `AdaptyError` } ``` A veces necesitas saber qué atributos personalizados ya están configurados. Para ello, usa el campo `customAttributes` del objeto `AdaptyProfile`. :::warning Ten en cuenta que el valor de `customAttributes` puede estar desactualizado, ya que los atributos de usuario pueden enviarse desde distintos dispositivos en cualquier momento, por lo que los atributos en el servidor podrían haber cambiado después de la última sincronización. ::: ### Límites \{#limits\} - Hasta 30 atributos personalizados por usuario - Los nombres de clave pueden tener hasta 30 caracteres. El nombre de la clave puede incluir caracteres alfanuméricos y cualquiera de los siguientes: `_` `-` `.` - El valor puede ser una cadena de texto o un número flotante con un máximo de 50 caracteres. --- # File: react-native-listen-subscription-changes --- --- title: "Comprobar el estado de suscripción en el SDK de React Native" description: "Haz seguimiento y gestión del estado de suscripción del usuario en Adapty para mejorar la retención de clientes en tu app de React Native." --- Con Adapty, hacer seguimiento del estado de suscripción es muy sencillo. No tienes que insertar manualmente los IDs de producto en tu código. En su lugar, puedes comprobar fácilmente el estado de suscripción de un usuario verificando si tiene un [nivel de acceso](access-level) activo. <details> <summary>Antes de empezar a comprobar el estado de suscripción (haz clic para ampliar)</summary> - Para iOS, configura las [notificaciones del servidor de App Store](enable-app-store-server-notifications) - Para Android, configura las [notificaciones en tiempo real para desarrolladores (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## Nivel de acceso y el objeto AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Los niveles de acceso son propiedades del objeto [AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile). Te recomendamos recuperar el perfil cuando arranque tu app, por ejemplo cuando [identificas a un usuario](react-native-identifying-users#setting-customer-user-id-on-configuration), y actualizarlo cada vez que se produzcan cambios. Así podrás usar el objeto de perfil sin tener que solicitarlo repetidamente. Para recibir notificaciones de las actualizaciones del perfil, escucha los cambios de perfil tal como se describe en la sección [Escuchar actualizaciones del perfil, incluidos los niveles de acceso](react-native-listen-subscription-changes) que encontrarás más abajo. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Obtener el nivel de acceso desde el servidor \{#retrieving-the-access-level-from-the-server\} Para obtener el nivel de acceso desde el servidor, usa el método `.getProfile()`: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); } catch (error) { // handle the error } ``` Parámetros de respuesta: | Parámetro | Descripción | | --------- | ------------------------------------------------------------ | | Profile | <p>Un objeto [AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile). Por lo general, solo necesitas comprobar el estado del nivel de acceso del perfil para determinar si el usuario tiene acceso premium a la app.</p><p></p><p>El método `.getProfile` devuelve el resultado más actualizado, ya que siempre intenta consultar la API. Si por algún motivo (por ejemplo, sin conexión a internet) el SDK de Adapty no puede recuperar información del servidor, se devolverán los datos de la caché. También es importante tener en cuenta que el SDK de Adapty actualiza la caché de `AdaptyProfile` de forma periódica para mantener esta información lo más actualizada posible.</p> | El método `.getProfile()` te proporciona el perfil de usuario desde el que puedes obtener el estado del nivel de acceso. Puedes tener múltiples niveles de acceso por app. Por ejemplo, si tienes una app de noticias y vendes suscripciones a diferentes temáticas de forma independiente, puedes crear niveles de acceso "sports" y "science". Pero la mayoría de las veces solo necesitarás un nivel de acceso; en ese caso, puedes usar simplemente el nivel de acceso predeterminado "premium". Aquí tienes un ejemplo para comprobar el nivel de acceso "premium" predeterminado: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); const isActive = profile.accessLevels?.["premium"]?.isActive; if (isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` ### Escuchar actualizaciones del estado de suscripción \{#listening-for-subscription-status-updates\} Cada vez que cambia la suscripción del usuario, Adapty lanza un evento. Para recibir mensajes de Adapty, necesitas realizar algunas configuraciones adicionales: ```typescript showLineNumbers // Create an "onLatestProfileLoad" event listener adapty.addEventListener('onLatestProfileLoad', profile => { // handle any changes to subscription state }); ``` Adapty también lanza un evento al iniciar la aplicación. En ese caso, se pasará el estado de suscripción almacenado en caché. ### Caché del estado de suscripción \{#subscription-status-cache\} La caché implementada en el SDK de Adapty almacena el estado de suscripción del perfil. Esto significa que, aunque el servidor no esté disponible, se puede acceder a los datos en caché para obtener información sobre el estado de suscripción del perfil. No obstante, hay que tener en cuenta que no es posible solicitar datos directamente desde la caché. El SDK consulta el servidor periódicamente cada minuto para comprobar si hay actualizaciones o cambios relacionados con el perfil. Si hay alguna modificación, como nuevas transacciones u otras actualizaciones, se enviarán a los datos en caché para mantenerlos sincronizados con el servidor. --- # File: react-native-deal-with-att --- --- title: "Gestionar ATT en el SDK de React Native" description: "Empieza con Adapty en React Native para simplificar la configuración y gestión de suscripciones." --- Si tu aplicación utiliza el framework AppTrackingTransparency y muestra al usuario una solicitud de autorización de seguimiento de la app, debes enviar el [estado de autorización](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) a Adapty. ```typescript showLineNumbers try { await adapty.updateProfile({ // you can also pass a string value (validated via tsc) if you prefer appTrackingTransparencyStatus: AppTrackingTransparencyStatus.Authorized, }); } catch (error) { // handle `AdaptyError` } ``` :::warning Te recomendamos encarecidamente que envíes este valor lo antes posible cuando cambie; solo así los datos se enviarán a tiempo a las integraciones que hayas configurado. ::: --- # File: kids-mode-react-native --- --- title: "Modo Kids en React Native SDK" description: "Activa fácilmente el Modo Kids para cumplir con las políticas de Apple y Google. Sin IDFA, GAID ni datos publicitarios en React Native SDK." --- Si tu aplicación React Native está destinada a niños, debes seguir las políticas de [Apple](https://developer.apple.com/kids/) y [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Si usas el SDK de Adapty, unos pocos pasos sencillos te ayudarán a configurarlo para cumplir con estas políticas y superar las revisiones de las tiendas. :::important En iOS, el modo Kids se activa mediante el trait de Swift Package `KidsMode`, que elimina en compilación todo el código relacionado con IDFA, AdSupport y AppTrackingTransparency. Requiere el SDK v4 (que instala el SDK nativo de iOS a través de Swift Package Manager) y **Xcode 26** o posterior. Consulta [Actualizaciones en tu iOS Podfile](#updates-in-your-ios-podfile) a continuación. ::: ## ¿Qué se necesita? \{#whats-required\} Tienes que configurar el SDK para desactivar la recopilación de: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [Dirección IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Además, te recomendamos usar el customer user ID con cuidado. Un ID de usuario con formato `<FirstName.LastName>` se considera sin duda como recopilación de datos personales, al igual que el uso del correo electrónico. Para el modo Kids, la mejor práctica es usar identificadores aleatorios o anonimizados (por ejemplo, IDs con hash o UUIDs generados por el dispositivo) para garantizar el cumplimiento normativo. ## Activación del modo infantil \{#enabling-kids-mode\} ### Actualizaciones en el Adapty Dashboard En el Adapty Dashboard, debes desactivar la recopilación de direcciones IP. Para ello, ve a [App settings](https://app.adapty.io/settings/general) y haz clic en **Disable IP address collection** bajo **Collect users' IP address**. ### Actualizaciones en el código de tu aplicación móvil \{#updates-in-your-mobile-app-code\} Para cumplir con las políticas, desactiva la recopilación del IDFA del usuario (iOS), GAID/AAID (Android) y la dirección IP al activar el SDK de Adapty: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { // Disable IP address collection ipAddressCollectionDisabled: true, // Disable IDFA collection on iOS ios: { idfaCollectionDisabled: true, }, // Disable Google Advertising ID collection on Android android: { adIdCollectionDisabled: true, }, }); ``` ### Actualizaciones en tu Podfile de iOS \{#updates-in-your-ios-podfile\} Para la categoría Niños del App Store (o cumplimiento con COPPA), el SDK nativo de iOS debe compilarse con el trait de paquete Swift `KidsMode`, que elimina del código todo lo relacionado con IDFA, AdSupport y AppTrackingTransparency. React Native instala el SDK nativo a través de Swift Package Manager, que no puede reenviar traits de paquete, por lo que el SDK incluye un helper para Podfile que aplica el trait por ti. Este paso requiere **Xcode 26** o posterior. En `ios/Podfile`, añade el helper y llámalo **después** de `react_native_post_install`: ```ruby showLineNumbers title="ios/Podfile" require Pod::Executable.execute_command('node', ['-p', 'require.resolve( "react-native-adapty/ios/adapty_kids_mode.rb", {paths: [process.argv[1]]}, )', __dir__]).strip # ... post_install do |installer| react_native_post_install( installer, config[:reactNativePath], :mac_catalyst_enabled => false ) adapty_enable_kids_mode(installer) end ``` Luego ejecuta `pod install`: ```sh showLineNumbers title="Shell" cd ios && pod install ``` Para confirmar que Kids Mode está activo, comprueba que la línea de log `adapty.activate(...)` muestra `kids_mode_enabled: true`. Mantén la llamada al helper en `post_install` de forma permanente: React Native regenera las referencias del paquete Swift en cada `pod install`, y el helper vuelve a aplicar el trait cada vez. ### Actualizaciones en tu manifiesto de Android \{#updates-in-your-android-manifest\} :::note Si tu app está dirigida **exclusivamente** a niños y compila contra Android 13 (API 33) o superior, Google Play exige que no solicites el permiso `AD_ID`. Otro SDK de tu app (de analítica, atribución o publicidad) puede añadir este permiso mediante la fusión de manifiestos. Establecer `adIdCollectionDisabled` impide que Adapty recopile el ID, pero no elimina un permiso que haya declarado otro SDK. ::: Para eliminar el permiso, añade lo siguiente dentro del elemento `<manifest>` de `android/app/src/main/AndroidManifest.xml`. El elemento `<manifest>` debe declarar `xmlns:tools="http://schemas.android.com/tools"`. ```xml showLineNumbers title="AndroidManifest.xml" <uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" /> ``` --- # File: react-native-get-onboardings --- --- title: "Obtener onboardings en el SDK de React Native" description: "Aprende cómo recuperar onboardings en Adapty para React Native." --- :::warning **Los onboardings están obsoletos en SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](react-native-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](react-native-get-pb-paywalls) y [Mostrar flows y paywalls](react-native-present-paywalls) para empezar. ::: Después de [diseñar la parte visual de tu onboarding](design-onboarding) con el editor en el Adapty Dashboard, puedes mostrarlo en tu app de React Native. El primer paso es obtener el onboarding asociado al placement y su configuración de vista, tal como se describe a continuación. Antes de empezar, asegúrate de que: 1. Tienes instalado el [SDK de Adapty para React Native](sdk-installation-reactnative) versión 3.8.0 o superior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). ## Obtener el onboarding \{#fetch-onboarding\} Cuando creas un [onboarding](onboardings) con nuestro editor sin código, se almacena como un contenedor con la configuración que tu aplicación necesita obtener y mostrar. Este contenedor gestiona toda la experiencia: qué contenido aparece, cómo se presenta y cómo se procesan las interacciones del usuario (como respuestas a cuestionarios o entradas de formularios). El contenedor también registra automáticamente los eventos de análisis, por lo que no necesitas implementar un seguimiento de vistas por separado. Para un mejor rendimiento, obtén la configuración del onboarding con antelación para que las imágenes tengan tiempo suficiente de descargarse antes de mostrárselas a los usuarios. Para obtener un onboarding, usa el método `getOnboarding`: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const onboarding = await adapty.getOnboarding(placementId, locale); // the requested onboarding } catch (error) { // handle the error } ``` Luego, llama al método `createOnboardingView` para crear una instancia de la vista. :::warning El resultado del método `createOnboardingView` solo puede usarse una vez. Si necesitas usarlo de nuevo, llama al método `createOnboardingView` otra vez. Llamarlo dos veces sin recrearlo puede provocar el error `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers // for the Adapty SDK < 3.14 – import {createOnboardingView} from 'react-native-adapty/dist/ui'; if (onboarding.hasViewConfiguration) { try { const view = await createOnboardingView(onboarding); } catch (error) { // handle the error } } else { //use your custom logic } ``` Parámetros: | Parámetro | Presencia | Descripción | |-------------------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto de una o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda, a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués brasileño.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo irregular que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se borra cuando se desinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché de actualización periódica descrita anteriormente y los onboardings de respaldo. También usamos CDN para cargar los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus onboardings, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeoutMs** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.</p><p>Ten en cuenta que en casos excepcionales este método puede agotarse ligeramente después del tiempo especificado en `loadTimeout`, ya que la operación puede estar compuesta de diferentes solicitudes internamente.</p> | Parámetros de respuesta: | Parámetro | Descripción | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Un objeto [`AdaptyOnboarding`](https://react-native.adapty.io/interfaces/adaptyonboarding) con: el identificador y la configuración del onboarding, Remote Config y varias otras propiedades. | ## Acelera la obtención del onboarding con el onboarding de audiencia predeterminada \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Por lo general, los onboardings se obtienen casi de forma instantánea, así que no tienes que preocuparte por acelerar este proceso. Sin embargo, cuando tienes muchas audiencias y onboardings, y tus usuarios tienen una conexión a internet débil, obtener un onboarding puede tardar más de lo deseado. En esos casos, puede que quieras mostrar un onboarding predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ningún onboarding. Para solucionar esto, puedes usar el método `getOnboardingForDefaultAudience`, que obtiene el onboarding del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el onboarding con el método `getOnboarding`, tal como se detalla en la sección [Obtener onboarding](#fetch-onboarding) anterior. :::warning Considera usar `getOnboarding` en lugar de `getOnboardingForDefaultAudience`, ya que este último tiene limitaciones importantes: - **Problemas de compatibilidad**: Puede generar problemas al dar soporte a varias versiones de la app, lo que obliga a diseñar con compatibilidad hacia atrás o asumir que las versiones antiguas podrían mostrarse incorrectamente. - **Sin personalización**: Solo muestra contenido para la audiencia "All Users", eliminando la segmentación por país, atribución o atributos personalizados. Si la obtención más rápida compensa estos inconvenientes en tu caso de uso, utiliza `getOnboardingForDefaultAudience` como se muestra a continuación. De lo contrario, usa `getOnboarding` como se describe [arriba](#fetch-onboarding). ::: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const onboarding = await adapty.getOnboardingForDefaultAudience(placementId, locale); // el onboarding solicitado } catch (error) { // manejar el error } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | requerido | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo irregular que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché de actualización periódica descrita anteriormente y los onboardings de respaldo. También usamos CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus onboardings y al mismo tiempo asegurar la fiabilidad incluso cuando la conexión a internet es escasa.</p> | --- # File: react-native-present-onboardings --- --- title: "Presentar onboardings en el SDK de React Native" description: "Descubre cómo presentar onboardings en React Native para aumentar conversiones e ingresos." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](react-native-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que ofrece animaciones más fluidas, una apariencia nativa coherente, tiempos de carga más rápidos y sin dependencia del entorno WebView. Consulta [Obtener flows y paywalls](react-native-get-pb-paywalls) y [Mostrar flows y paywalls](react-native-present-paywalls) para empezar. ::: Si has personalizado un onboarding usando el builder, no necesitas preocuparte por renderizarlo en el código de tu aplicación móvil para mostrárselo al usuario. Ese onboarding incluye tanto lo que debe mostrarse como la forma en que debe mostrarse. Antes de empezar, asegúrate de que: 1. Tienes instalado el [SDK de Adapty para React Native](sdk-installation-reactnative) 3.8.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). El SDK de Adapty para React Native ofrece dos formas de presentar onboardings: - **Componente React**: El componente embebido te permite integrarlo en la arquitectura y el sistema de navegación de tu app. - **Presentación modal** ## Componente React \{#react-component\} Para insertar un onboarding dentro de tu árbol de componentes existente, usa el componente `AdaptyOnboardingView` directamente en la jerarquía de componentes de React Native. El componente embebido te permite integrarlo en la arquitectura y el sistema de navegación de tu app. :::note En Android, recomendamos configuración adicional para `AdaptyOnboardingView` para evitar un artefacto visual de renderizado. Consulta [La interfaz del sistema se superpone al contenido del onboarding en Android](#system-ui-overlaps-onboarding-content-on-android). ::: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK versión 3.14 o posterior" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onAnalytics={onAnalytics} onClose={onClose} onCustom={onCustom} onPaywall={onPaywall} onStateUpdated={onStateUpdated} onFinishedLoading={onFinishedLoading} onError={onError} /> ); } ``` </TabItem> <TabItem value="old" label="Versión de SDK < 3.14" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { return ( <AdaptyOnboardingView onboarding={onboarding} style={{ flex: 1 }} eventHandlers={{ onAnalytics(event, meta) { // Handle analytics events }, onClose(actionId, meta) { // Handle close actions }, onCustom(actionId, meta) { // Handle custom actions }, onPaywall(actionId, meta) { // Handle paywall actions }, onStateUpdated(action, meta) { // Handle state updates }, onFinishedLoading(meta) { // Handle when onboarding finishes loading }, onError(error) { // Handle errors }, }} /> ); } ``` </TabItem> </Tabs> ## Presentación modal \{#modal-presentation\} Para mostrar un onboarding como pantalla independiente que los usuarios puedan cerrar, usa el método `view.present()` en el `view` creado por el método `createOnboardingView`. Cada `view` solo puede usarse una vez. Si necesitas mostrar el onboarding de nuevo, llama a `createOnboardingView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo está prohibido. Provocará un error `AdaptyUIError.viewAlreadyPresented`. ::: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK version 3.14 or later" default> ```typescript showLineNumbers title="React Native (TSX)" const view = await createOnboardingView(onboarding); // Optional: handle onboarding events (close, custom actions, etc) // view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> <TabItem value="old" label="SDK version < 3.14" default> ```typescript showLineNumbers title="React Native (TSX)" const view = await createOnboardingView(onboarding); view.setEventHandlers(); // handle close press, etc try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> ### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el onboarding en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `'full_screen'` (predeterminado) o `'page_sheet'`. ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## Loader durante el onboarding \{#loader-during-onboarding\} Al mostrar un onboarding en React Native, puede que notes un breve destello blanco o pantalla de carga antes de que aparezca el onboarding. Esto ocurre mientras se inicializa la vista nativa subyacente. Puedes manejarlo de distintas formas según tus necesidades y tu flujo de trabajo. #### Controla la splash screen usando onFinishedLoading \{#control-splash-screen-using-onfinishedloading\} :::note Este enfoque solo está disponible al usar el componente React. No está disponible para la presentación modal. ::: La forma recomendada en React Native es mantener visible tu pantalla de splash o un overlay personalizado hasta que el onboarding se haya cargado completamente, y luego ocultarlo manualmente. Al usar el componente React (`AdaptyOnboardingView`), espera el evento `onFinishedLoading` antes de ocultar tu pantalla de splash o overlay: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK versión 3.14 o posterior" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const [isLoading, setIsLoading] = useState(true); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => { // Hide your splash screen or custom overlay here setIsLoading(false); }, []); return ( <> <AdaptyOnboardingView onboarding={onboarding} onFinishedLoading={onFinishedLoading} // ... other callbacks /> {isLoading && <YourCustomLoadingOverlay />} </> ); } ``` </TabItem> <TabItem value="old" label="Versión SDK < 3.14"> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const [isLoading, setIsLoading] = useState(true); return ( <> <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onFinishedLoading(meta) { // Hide your splash screen or custom overlay here setIsLoading(false); }, // ... other handlers }} /> {isLoading && <YourCustomLoadingOverlay />} </> ); } ``` </TabItem> </Tabs> #### Personalizar el loader nativo \{#customize-native-loader\} :::important El flujo gestionado de Expo no admite la colocación de layouts nativos personalizados (p. ej., `res/layout` en Android). Para aplicaciones Expo, la única solución viable es controlar la splash screen o usar un overlay de React Native. ::: Puedes reemplazar el loader nativo usando layouts específicos de plataforma en Android e iOS. Si usas presentación modal, esta es tu única opción. Sin embargo, este enfoque suele ser menos cómodo para aplicaciones React Native: - Requiere implementaciones separadas para Android e iOS - No es compatible con el flujo gestionado de Expo Define un placeholder para cada plataforma: - **iOS**: Añade `AdaptyOnboardingPlaceholderView.xib` a tu proyecto Xcode. [Más información](ios-present-onboardings#add-smooth-transitions-between-the-splash-screen-and-onboarding). - **Android**: Crea `adapty_onboarding_placeholder_view.xml` en `res/layout` y define allí el placeholder. [Más información](android-present-onboardings#add-smooth-transitions-between-the-splash-screen-and-onboarding). ## Personalizar cómo se abren los enlaces en los onboardings \{#customize-how-links-open-in-onboardings\} :::important La personalización de cómo se abren los enlaces en los onboardings está disponible a partir de Adapty SDK v3.15.1. ::: Por defecto, los enlaces en los onboardings se abren en un navegador integrado en la app. Esto proporciona una experiencia fluida al mostrar las páginas web dentro de tu aplicación, sin que el usuario tenga que cambiar de app. Si prefieres abrir los enlaces en un navegador externo, puedes personalizar este comportamiento estableciendo el parámetro `externalUrlsPresentation` en `WebPresentation.BrowserOutApp`: <Tabs groupId="rn-onboarding-views" queryString> <TabItem value="component" label="Componente de React" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} externalUrlsPresentation={WebPresentation.BrowserOutApp} // default – BrowserInApp onAnalytics={onAnalytics} onClose={onClose} onCustom={onCustom} onPaywall={onPaywall} onStateUpdated={onStateUpdated} onFinishedLoading={onFinishedLoading} onError={onError} /> ); } ``` </TabItem> <TabItem value="modal" label="Presentación modal"> ```typescript showLineNumbers title="React Native (TSX)" const view = await createOnboardingView( onboarding, { externalUrlsPresentation: WebPresentation.BrowserOutApp } // default – BrowserInApp ); try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## Solución de problemas \{#troubleshooting\} ### La interfaz de sistema se superpone al contenido del onboarding en Android \{#system-ui-overlaps-onboarding-content-on-android\} :::note Esta configuración solo está disponible en proyectos de React Native sin Expo (bare). Si usas el flujo de trabajo gestionado de Expo (managed workflow), no puedes añadir este recurso de Android directamente. Para aplicar esta configuración, debes crear un plugin de configuración personalizado de Expo que añada el recurso de Android correspondiente y registrarlo en `app.config.js`. Esto es necesario porque Expo gestiona el proyecto nativo de Android por ti. ::: Al usar `AdaptyOnboardingView` en Android, los elementos de la interfaz del sistema como la barra de estado y la barra de navegación pueden aparecer sobre el contenido del paywall. Para evitarlo, añade el siguiente recurso booleano a tu app: 1. Ve a `android/app/src/main/res/values`. Si no existe el archivo `bools.xml`, créalo. 2. Añade el siguiente recurso: ```xml <resources> <bool name="adapty_onboarding_enable_safe_area_paddings">false</bool> </resources> ``` Ten en cuenta que los cambios se aplican globalmente a todos los onboardings de tu app. ## Siguientes pasos \{#next-steps\} Una vez que hayas presentado tu onboarding, querrás [gestionar las interacciones y eventos del usuario](react-native-handling-onboarding-events). Aprende a manejar los eventos del onboarding para responder a las acciones del usuario y realizar un seguimiento de la analítica. --- # File: react-native-handling-onboarding-events --- --- title: "Gestionar eventos de onboarding en el SDK de React Native" description: "Gestiona eventos relacionados con el onboarding en React Native usando Adapty." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](react-native-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que te ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](react-native-get-pb-paywalls) y [Mostrar flows y paywalls](react-native-present-paywalls) para empezar. ::: Los onboardings configurados con el builder generan eventos a los que tu app puede responder. La forma de gestionar estos eventos depende del enfoque de presentación que estés usando: - **Presentación modal**: Requiere configurar manejadores de eventos que gestionen los eventos de todas las vistas de onboarding - **Componente React**: Gestiona los eventos mediante parámetros de callback directamente en el widget Antes de empezar, asegúrate de que: 1. Has instalado [el SDK de Adapty para React Native](sdk-installation-reactnative) 3.8.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). Para controlar o monitorizar los procesos que ocurren en la pantalla de onboarding dentro de tu app, implementa los manejadores de eventos: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK version 3.14 or later" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, los eventos se gestionan mediante props individuales de manejadores de eventos en el componente `AdaptyOnboardingView`: ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onAnalytics={onAnalytics} onClose={onClose} onCustom={onCustom} onPaywall={onPaywall} onStateUpdated={onStateUpdated} onFinishedLoading={onFinishedLoading} onError={onError} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> Para la presentación modal, implementa el método de controladores de eventos. :::important Llamar a `setEventHandlers` varias veces sobreescribirá los controladores que proporciones, reemplazando tanto los predeterminados como los configurados previamente para esos eventos específicos. ::: ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onAnalytics(event, meta) { // Track analytics events }, onClose(actionId, meta) { // Handle close action view.dismiss(); return true; }, onCustom(actionId, meta) { // Handle custom actions }, onPaywall(actionId, meta) { // Handle paywall actions }, onStateUpdated(action, meta) { // Handle user input updates }, onFinishedLoading(meta) { // Onboarding finished loading }, onError(error) { // Handle loading errors }, }); try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> Para la versión del SDK < 3.14, solo se admite la presentación modal: ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onAnalytics(event, meta) { // Track analytics events }, onClose(actionId, meta) { // Handle close action view.dismiss(); return true; }, onCustom(actionId, meta) { // Handle custom actions }, onPaywall(actionId, meta) { // Handle paywall actions }, onStateUpdated(action, meta) { // Handle user input updates }, onFinishedLoading(meta) { // Onboarding finished loading }, onError(error) { // Handle loading errors }, }); try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## Tipos de eventos \{#event-types\} Las siguientes secciones describen los distintos tipos de eventos que puedes manejar, independientemente del enfoque de presentación que estés usando. ### Gestionar acciones personalizadas \{#handle-custom-actions\} En el builder, puedes añadir una acción **custom** a un botón y asignarle un ID. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Luego, puedes usar este ID en tu código y gestionarlo como una acción personalizada. Por ejemplo, si el usuario pulsa un botón personalizado, como **Login** o **Allow notifications**, el manejador de eventos se activará con el parámetro `actionId` que coincide con el **Action ID** del builder. Puedes crear tus propios IDs, como "allowNotifications". <Tabs groupId="version" queryString> <TabItem value="new" label="SDK version 3.14 or later" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => { switch (actionId) { case 'login': login(); break; case 'allow_notifications': allowNotifications(); break; } }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onCustom={onCustom} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onCustom(actionId, meta) { switch (actionId) { case 'login': login(); break; case 'allow_notifications': allowNotifications(); break; } }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onCustom(actionId, meta) { switch (actionId) { case 'login': login(); break; case 'allow_notifications': allowNotifications(); break; } }, }); ``` </TabItem> </Tabs> <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "actionId": "allow_notifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ### Finalizar la carga del onboarding \{#finishing-loading-onboarding\} Cuando un onboarding termina de cargarse, se dispara este evento: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK versión 3.14 o posterior" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="Componente React" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => { console.log('Onboarding loaded:', meta.onboardingId); }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onFinishedLoading={onFinishedLoading} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onFinishedLoading(meta) { console.log('Onboarding loaded:', meta.onboardingId); }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onFinishedLoading(meta) { console.log('Onboarding loaded:', meta.onboardingId); }, }); ``` </TabItem> </Tabs> <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ### Cerrar el onboarding \{#closing-onboarding\} El onboarding se considera cerrado cuando el usuario pulsa un botón con la acción **Close** asignada. <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Ten en cuenta que debes gestionar qué ocurre cuando el usuario cierra el onboarding. Por ejemplo, debes dejar de mostrar el propio onboarding. ::: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK versión 3.14 o posterior" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="Componente React" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding, navigation }) { const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => { navigation.goBack(); }, [navigation]); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onClose={onClose} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onClose(actionId, meta) { await view.dismiss(); return true; }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onClose(actionId, meta) { await view.dismiss(); return true; }, }); ``` </TabItem> </Tabs> <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ### Abrir un paywall \{#opening-a-paywall\} :::tip Maneja este evento para abrir un paywall si quieres abrirlo dentro del onboarding. Si quieres abrir un paywall después de cerrarlo, hay una forma más directa de hacerlo: maneja la acción de cierre y abre el paywall sin depender de los datos del evento. ::: La manera más sencilla de trabajar con paywalls en onboardings es hacer que el ID de la acción sea igual al ID del placement del paywall. <Tabs groupId="version" queryString> <TabItem value="new" label="SDK versión 3.14 o posterior" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => { openPaywall(actionId); }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onPaywall={onPaywall} /> ); } const openPaywall = async (placementId) => { // Implement your paywall opening logic here }; ``` </TabItem> <TabItem value="standalone" label="Presentación modal"> Ten en cuenta que, en iOS, solo se puede mostrar una vista (paywall u onboarding) en pantalla a la vez. Si presentas un paywall encima de un onboarding, no podrás controlar programáticamente el onboarding en segundo plano. Si intentas cerrar el onboarding, se cerrará el paywall en su lugar, dejando el onboarding visible. Para evitarlo, cierra siempre la vista del onboarding antes de presentar el paywall. ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onPaywall(actionId, meta) { view.dismiss().then(() => { openPaywall(actionId); }); }, }); const openPaywall = async (placementId) => { // Implement your paywall opening logic here }; ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> Ten en cuenta que, en iOS, solo se puede mostrar una vista (paywall u onboarding) en pantalla a la vez. Si presentas un paywall encima de un onboarding, no podrás controlar el onboarding que queda en segundo plano de forma programática. Si intentas cerrar el onboarding, se cerrará el paywall en su lugar, dejando el onboarding visible. Para evitar esto, cierra siempre la vista del onboarding antes de presentar el paywall. ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onPaywall(actionId, meta) { view.dismiss().then(() => { openPaywall(actionId); }); }, }); const openPaywall = async (placementId) => { // Implement your paywall opening logic here }; ``` </TabItem> </Tabs> <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ### Seguimiento de la navegación \{#tracking-navigation\} Recibirás un evento de análisis cuando se produzcan distintos eventos relacionados con la navegación durante el flow de onboarding: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK versión 3.14 o posterior" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="Componente React" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => { trackEvent(event.name, meta.onboardingId); }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onAnalytics={onAnalytics} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onAnalytics(event, meta) { trackEvent(event.name, meta.onboardingId); }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onAnalytics(event, meta) { trackEvent(event.name, meta.onboardingId); }, }); ``` </TabItem> </Tabs> El objeto `event` puede ser uno de los siguientes tipos: | Tipo | Descripción | |------------|-------------| | `onboardingStarted` | Cuando el onboarding se ha cargado | | `screenPresented` | Cuando se muestra cualquier pantalla | | `screenCompleted` | Cuando se completa una pantalla. Incluye un `elementId` opcional (identificador del elemento completado) y un `reply` opcional (respuesta del usuario). Se activa cuando el usuario realiza cualquier acción para salir de la pantalla. | | `secondScreenPresented` | Cuando se muestra la segunda pantalla | | `userEmailCollected` | Se activa cuando se recoge el email del usuario mediante el campo de entrada | | `onboardingCompleted` | Se activa cuando un usuario llega a una pantalla con el ID `final`. Si necesitas este evento, [asigna el ID `final` a la última pantalla](design-onboarding). | | `unknown` | Para cualquier tipo de evento no reconocido. Incluye `name` (el nombre del evento desconocido) y `meta` (metadatos adicionales) | Cada evento incluye información `meta` con los siguientes campos: | Campo | Descripción | |------------|-------------| | `onboardingId` | Identificador único del flow de onboarding | | `screenClientId` | Identificador de la pantalla actual | | `screenIndex` | Posición de la pantalla actual en el flow | | `screensTotal` | Número total de pantallas en el flow | <Details> <summary>Ejemplos de eventos (haz clic para expandir)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: react-native-onboarding-input --- --- title: "Procesar datos de onboardings en el SDK de React Native" description: "Guarda y usa datos de onboardings en tu app de React Native con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos en el SDK v4 y se eliminarán en una versión futura.** Ya no reciben correcciones ni mejoras. Usa [flows](react-native-get-pb-paywalls) en su lugar: a diferencia de los onboardings, que se ejecutan dentro de un WebView, los flows se renderizan de forma nativa en el dispositivo, lo que te ofrece animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. Consulta [Obtener flows y paywalls](react-native-get-pb-paywalls) y [Mostrar flows y paywalls](react-native-present-paywalls) para empezar. ::: Cuando tus usuarios responden a una pregunta de un quiz o introducen sus datos en un campo de entrada, se invocará el método `onStateUpdatedAction`. Puedes guardar o procesar el tipo de campo en tu código. Por ejemplo: ```javascript // Full-screen presentation const unsubscribe = view.setEventHandlers({ onStateUpdated(action, meta) { // Process data }, }); // Embedded widget <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onStateUpdated(action, meta) { // Process data }, }} /> ``` Consulta el formato de acción [aquí](https://react-native.adapty.io/types/onboardingstateupdatedaction). <Details> <summary>Ejemplos de datos guardados (el formato puede variar según tu implementación)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "elementType": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" }, "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "totalScreens": 3 } } // Example of a saved multi-select action { "elementId": "interests_selector", "elementType": "multi_select", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ], "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "totalScreens": 3 } } // Example of a saved input action { "elementId": "name_input", "elementType": "input", "value": { "type": "text", "value": "John Doe" }, "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "totalScreens": 3 } } // Example of a saved date picker action { "elementId": "birthday_picker", "elementType": "date_picker", "value": { "day": 15, "month": 6, "year": 1990 }, "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "totalScreens": 3 } } ``` </Details> ## Casos de uso \{#use-cases\} ### Enriquecer perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Si quieres vincular inmediatamente los datos introducidos con el perfil del usuario y evitar pedirle la misma información dos veces, necesitas [actualizar el perfil del usuario](react-native-setting-user-attributes) con los datos introducidos al gestionar la acción. Por ejemplo, pides a los usuarios que introduzcan su nombre en el campo de texto con el ID `name`, y quieres establecer el valor de ese campo como el nombre del usuario. También les pides que introduzcan su correo electrónico en el campo `email`. En el código de tu app, podría verse así: ```javascript showLineNumbers // Full-screen presentation const unsubscribe = view.setEventHandlers({ onStateUpdated(action, meta) { // Store user preferences or responses if (action.elementType === 'input') { const profileParams = {}; // Map elementId to appropriate profile field switch (action.elementId) { case 'name': if (action.value.type === 'text') { profileParams.firstName = action.value.value; } break; case 'email': if (action.value.type === 'email') { profileParams.email = action.value.value; } break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }); // Embedded widget <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onStateUpdated(action, meta) { // Store user preferences or responses if (action.elementType === 'input') { const profileParams = {}; // Map elementId to appropriate profile field switch (action.elementId) { case 'name': if (action.value.type === 'text') { profileParams.firstName = action.value.value; } break; case 'email': if (action.value.type === 'email') { profileParams.email = action.value.value; } break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }} /> ``` ### Personaliza los paywalls según las respuestas \{#customize-paywalls-based-on-answers\} Con los cuestionarios en onboardings, también puedes personalizar los paywalls que muestras a los usuarios después de que completen el onboarding. Por ejemplo, puedes preguntarles sobre su experiencia con el deporte y mostrar diferentes CTAs y productos a distintos grupos de usuarios. 1. [Añade un cuestionario](onboarding-quizzes) en el editor de onboarding y asigna IDs significativos a sus opciones. 2. Gestiona las respuestas del cuestionario según sus IDs y [establece atributos personalizados](react-native-setting-user-attributes) para los usuarios. ```javascript showLineNumbers // Full-screen presentation const unsubscribe = view.setEventHandlers({ onStateUpdated(action, meta) { // Handle quiz responses and set custom attributes if (action.elementType === 'select') { const profileParams = {}; // Map quiz responses to custom attributes switch (action.elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) profileParams.codableCustomAttributes = { experience: action.value.value }; break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }); // Embedded widget <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onStateUpdated(action, meta) { // Handle quiz responses and set custom attributes if (action.elementType === 'select') { const profileParams = {}; // Map quiz responses to custom attributes switch (action.elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) profileParams.codableCustomAttributes = { experience: action.value.value }; break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }} /> ``` 3. [Crea segmentos](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade [audiencias](audience) para cada segmento que hayas creado. 5. [Muestra un paywall](react-native-paywalls) para el placement en el código de tu app. Si tu onboarding tiene un botón que abre un paywall, implementa el código del paywall como [respuesta a la acción de ese botón](react-native-handling-onboarding-events#opening-a-paywall). --- # File: react-native-sdk-call-order --- --- title: "Orden de llamadas en el SDK de React Native" description: "Evita la pérdida de acceso premium, la atribución incompleta y los errores intermitentes #2002 llamando a los métodos del SDK de Adapty en el orden correcto." --- `adapty.activate()` debe completarse antes de llamar a cualquier otro método del SDK de Adapty. Hasta que se resuelva, el SDK no tiene estado. Cualquier llamada realizada antes o en paralelo con `activate()` fallará con [`#2002 notActivated`](react-native-handle-errors#custom-network-codes). Si tu app autentica usuarios y obtienes un customer user ID después del lanzamiento, llama a `adapty.identify()` en ese momento. No llames a métodos de acción del usuario hasta que `identify` se resuelva. Las llamadas que compiten con él fallan con [`#3006 profileWasChanged`](react-native-handle-errors#custom-network-codes) o aterrizan en el perfil anónimo creado durante la activación. Cuando esto ocurre, la atribución, los IDs de MMP como `appsflyer_id` y la propiedad de la instalación no siempre se transfieren al perfil identificado. Si tu app no autentica usuarios, omite `identify` y sigue trabajando con el perfil anónimo. Los SDK de MMP y análisis (AppsFlyer, Adjust, Branch, PostHog) siguen la misma regla. Inicialízalos primero y espera sus callbacks de UID antes de llamar a `adapty.activate`. De lo contrario, el ID del MMP aterriza en un perfil anónimo temporal y no siempre se transfiere al identificado. Para más detalles sobre AppsFlyer, consulta [AppsFlyer](appsflyer). ## El orden correcto \{#the-correct-order\} Tu ruta depende de dos cosas: cuándo conoces el customer user ID y si usas un SDK de MMP o análisis. - **Pasos 2 y 5**: Obligatorios para todas las apps. Activa el SDK y luego llama a los métodos del SDK. - **Pasos 1 y 3**: Requeridos solo si integras un SDK de MMP o análisis (AppsFlyer, Adjust, Branch, PostHog). - **Paso 4**: Requerido solo si tu app autentica usuarios y recopila el customer user ID después del lanzamiento. Si tienes el customer user ID al lanzar la app, pásalo directamente a `activate()` (paso 2a). Esta ruta nunca crea un perfil anónimo, por lo que el paso 4 no es necesario. | Paso | Llamada | Cuándo | Notas | |------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Inicializa tu SDK de MMP o análisis (AppsFlyer, Adjust, PostHog, Branch) | Al lanzar la app, primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `adapty.activate('YOUR_PUBLIC_SDK_KEY', { customerUserId: 'YOUR_USER_ID' })` | Al lanzar la app, después del paso 1, si tienes el customer user ID | Recomendado. Nunca se crea un perfil anónimo. | | 2b | `adapty.activate('YOUR_PUBLIC_SDK_KEY')` sin `customerUserId` | Al lanzar la app, después del paso 1, si no tienes el customer user ID (o nunca lo recopilas) | Adapty crea un perfil anónimo. | | 3 | `adapty.updateAttribution(data, source, networkUserId)` para cada MMP | Después del paso 2, antes de cualquier llamada de acción del usuario | Necesario para que los IDs de MMP lleguen al perfil correcto. | | 4 | `await adapty.identify('YOUR_USER_ID')` | Después del paso 3 (o del paso 2 si no hay MMP), antes del paso 5 — solo en la ruta 2b con autenticación | Siempre usa `await`. Las llamadas concurrentes durante `identify` producen `#3006 profileWasChanged`. | | 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | Después del paso 4 si llamas a `identify`; de lo contrario, después del paso 3 (o del paso 2 si no hay MMP) | Estas llamadas necesitan un perfil estable. | :::important Saltarse estos pasos provoca pérdida de acceso premium para usuarios que regresan, `appsflyer_id` ausente en los perfiles y paywalls devueltos para la audiencia incorrecta. ::: ## Instalaciones de web2app y web funnel \{#web2app-and-web-funnel-installs\} Si los usuarios compran en un checkout web (Stripe, Paddle) y luego instalan la app nativa, el primer `activate()` del dispositivo crea un nuevo perfil anónimo. Este perfil no está vinculado al perfil web. Si puedes resolver el customer user ID antes del lanzamiento de la app (desde tu flujo de autenticación o el referrer de instalación), pásalo directamente a `activate()`. De lo contrario, la compra web será invisible en el dispositivo hasta que llames a `identify('YOUR_USER_ID')` y luego a `restorePurchases`. Para los metadatos que debes enviar con cada checkout web, consulta: - [Stripe](stripe) - [Paddle](paddle) --- # File: react-native-optimize-paywall-fetching --- --- title: "Optimizar la carga de paywalls en React Native SDK" description: "Carga paywalls de Adapty de forma fiable: temporización, caché y patrones de respaldo para React Native." --- Una carga de paywall fiable en React Native hace tres cosas: renderiza rápido, devuelve el paywall dirigido a la audiencia correcta y activa el respaldo de forma elegante cuando la red va lenta. Las reglas a continuación cubren los patrones de temporización, caché y respaldo para lograrlo. :::tip Las reglas asumen que `adapty.activate()` y `adapty.identify()` ya se han resuelto. Consulta [Orden de llamadas en React Native SDK](react-native-sdk-call-order). ::: ## Reglas y errores comunes \{#rules-and-pitfalls\} | Haz esto | No hagas esto | Por qué | |---|---|---| | Carga el placement que estás a punto de mostrar. | Precargues todos los placements de forma concurrente al inicio. | La precarga masiva bloquea el hilo JS y produce una pantalla en negro durante la ráfaga. | | Llama a `getPaywall` después de que la atribución haya tenido oportunidad de resolverse — por ejemplo, 1–2 segundos después de `activate` o cuando se dispare `onProfileUpdate`. | Llames a `getPaywall` en el montaje del componente raíz. | La atribución aún no ha llegado. El paywall se resuelve contra la audiencia por defecto y omite silenciosamente los segmentos y la personalización de ASA. | | Configura un `loadTimeoutMs` y un [paywall de respaldo](fallback-paywalls) para cada placement. | Esperes indefinidamente a `getPaywall`. | Sin tiempo de espera, los usuarios con mala conectividad ven una pantalla en blanco hasta que la red responde — o cierran la app. | Consulta [Obtener paywalls y productos](fetch-paywalls-and-products-react-native) para la referencia de los parámetros `fetchPolicy` y `loadTimeoutMs`, y [Placements](placements) para elegir el placement adecuado. ## Ajustar para mala conectividad \{#tune-for-poor-connectivity\} Para mercados con conectividad constantemente deficiente (zonas rurales, transporte, regiones afectadas por enrutamiento): - Establece `fetchPolicy: .returnCacheDataElseLoad` en cada carga excepto la primera. - Configura un [paywall de respaldo](fallback-paywalls) para cada placement en el Adapty Dashboard. - Establece `loadTimeoutMs` entre 3 y 5 segundos y acepta el respaldo cuando se agote el tiempo. - No condicionar la visualización del paywall a `getProfile()`. Llama a `getPaywall` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: react-native-show-aa-targeted-paywall --- --- title: "Mostrar un paywall dirigido por AA en el primer lanzamiento con React Native SDK" description: "Muestra un paywall de inmediato y actualízalo para usuarios de Apple Ads una vez que se aplica la atribución en React Native, usando AdaptyProfile.appliedAttributionSources." --- La atribución de Apple Ads (AA) llega de forma asíncrona después de `adapty.activate()`. En el primer arranque normalmente aún no ha llegado, por lo que `getPaywall` resuelve contra la audiencia predeterminada y los usuarios de Apple Ads no ven el paywall segmentado por AA. En lugar de retrasar el paywall hasta que llegue la atribución, muestra uno inmediatamente y refrésalo una vez que se aplique la atribución de AA — así los usuarios de Apple Ads reciben la variante segmentada y los demás ven un paywall sin espera. `AdaptyProfile.appliedAttributionSources` te indica cuándo se ha aplicado la atribución de AA. ## Antes de empezar \{#before-you-start\} Necesitas: - Adapty React Native SDK **3.17.1** o posterior. - Apple Ads configurado para la app en Adapty. Consulta [Apple Ads](apple-search-ads). ## Cómo funciona \{#how-it-works\} Tras llamar a `adapty.activate()`, el SDK solicita en segundo plano la atribución de Apple Ads a Apple y reenvía el resultado al backend de Adapty. Cuando AA se convierte en la fuente de atribución activa del perfil, el SDK entrega un `AdaptyProfile` actualizado a tu listener `onLatestProfileLoad`, con `'apple_search_ads'` en su array `appliedAttributionSources`. Esto te permite cargar el paywall en dos pasos: 1. Llama a `getPaywall` de inmediato. Como todavía no hay atribución aplicada, Adapty resuelve la solicitud contra la audiencia predeterminada, por lo que el usuario ve un paywall enseguida. 2. Cuando aparezca `'apple_search_ads'`, llama a `getPaywall` de nuevo. Adapty ahora resuelve la solicitud contra la audiencia de Apple Ads y devuelve el paywall segmentado, que reemplaza al primero. `appliedAttributionSources` puede estar vacío o ausente. Eso significa que: - La atribución de Apple Ads aún no se ha procesado para este perfil, o - no ha llegado ninguna atribución. En cualquier caso, el paso 1 es seguro — Adapty resuelve la solicitud según la audiencia que coincida con el estado actual del perfil, normalmente la audiencia predeterminada. El paso 2 solo se ejecuta una vez que `'apple_search_ads'` aparece. :::important En cada lanzamiento posterior, el perfil en caché ya incluye `'apple_search_ads'` en `appliedAttributionSources`, por lo que el primer `getPaywall` ya devuelve el paywall segmentado por Apple Ads — no hay una segunda llamada ni ningún cambio visible. El flujo de dos pasos solo importa en el primer lanzamiento, mientras la atribución aún está en curso. ::: ## Implementación \{#implementation\} Muestra el paywall de inmediato y luego escucha el evento `'apple_search_ads'` para actualizar el paywall cuando llegue. 1. **Activa el SDK.** Consulta [Instala y configura el SDK de React Native](sdk-installation-reactnative). 2. **Carga y muestra un paywall** con `getPaywall` como de costumbre — no bloquees la ejecución esperando la atribución. 3. **Suscríbete a las actualizaciones del perfil** con `adapty.addEventListener('onLatestProfileLoad', …)` y espera `'apple_search_ads'`. Cuando aparezca, vuelve a cargar el paywall y muestra el actualizado. Si aún no has configurado el listener, consulta [Escucha las actualizaciones de suscripción](react-native-check-subscription-status#listen-to-subscription-updates): ```typescript const subscription = adapty.addEventListener('onLatestProfileLoad', async profile => { if (!profile.appliedAttributionSources?.includes('apple_search_ads')) return; const targeted = await adapty.getPaywall(placementId); // present the targeted paywall in place of the first one }); // Call subscription.remove() after the upgrade, or after a timeout (see below). ``` 4. **Detén la escucha tras un tiempo de espera.** La mayoría de los usuarios nunca reciben atribución de Apple Ads, así que elimina el listener después de un tiempo en lugar de mantenerlo abierto toda la sesión. Configura un [paywall de respaldo](react-native-use-fallback-paywalls) para el placement para que el usuario siempre vea algo si una solicitud falla. ## Ejemplo completo \{#complete-example\} `onAppleAdsAttribution` se resuelve una vez que se aplica la atribución de Apple Ads, o se rechaza tras `timeoutMs`. El siguiente ejemplo carga un paywall de inmediato y lo vuelve a obtener cuando llega la atribución; los usuarios de Apple Ads reciben el paywall personalizado y, si la atribución nunca llega, el primer paywall permanece activo: ```typescript const APPLE_ADS_SOURCE = 'apple_search_ads'; const placementId = 'YOUR_PLACEMENT_ID'; function hasAppleAdsAttribution(profile: AdaptyProfile): boolean { return profile.appliedAttributionSources?.includes(APPLE_ADS_SOURCE) ?? false; } /** * Resolves once Apple Ads attribution is applied to the profile. * Rejects with a timeout error if attribution never arrives within `timeoutMs`. * Call after `adapty.activate()`. */ export function onAppleAdsAttribution(timeoutMs: number): Promise<void> { return new Promise((resolve, reject) => { let timer: ReturnType<typeof setTimeout> | undefined; let subscription: { remove: () => void } | undefined; const stop = () => { clearTimeout(timer); subscription?.remove(); }; subscription = adapty.addEventListener('onLatestProfileLoad', profile => { if (!hasAppleAdsAttribution(profile)) return; stop(); resolve(); }); timer = setTimeout(() => { stop(); reject(new Error(`Apple Ads attribution timed out after ${timeoutMs}ms`)); }, timeoutMs); }); } let paywall = await adapty.getPaywall(placementId); onAppleAdsAttribution(30_000) .then(() => adapty.getPaywall(placementId)) .then(updated => { paywall = updated; }) .catch(() => { console.log('Apple Ads attribution or loading failed'); }); ``` Al primer lanzamiento, un usuario de Apple Ads ve brevemente el paywall predeterminado antes de que sea reemplazado. Si presentas paywalls con el Paywall Builder, decide si volver a presentarlo es aceptable, o aplica la actualización solo antes de mostrar el paywall. Ajusta `timeoutMs` según cuánto tiempo estés dispuesto a esperar — la atribución que está en camino suele llegar a los pocos segundos del lanzamiento. Si tu app ya escucha `onLatestProfileLoad` para otros fines (por ejemplo, [comprobar el estado de la suscripción](react-native-check-subscription-status#listen-to-subscription-updates)), no necesitas cambiarlo. `adapty.addEventListener` admite múltiples listeners independientes, así que este añade el suyo sin afectar a los demás. --- # File: react-native-test --- --- title: "Prueba y lanzamiento en React Native SDK" description: "Aprende cómo probar y lanzar tu aplicación React Native con el SDK de Adapty." --- Si ya has implementado el SDK de Adapty en tu aplicación React Native, querrás comprobar que todo está configurado correctamente y que las compras funcionan según lo esperado tanto en iOS como en Android. Esto implica probar tanto la integración del SDK como el flujo de compra real con el entorno sandbox de Apple y el entorno de pruebas de Google Play. ## Prueba tu aplicación \{#test-your-app\} Para hacer pruebas exhaustivas de tus compras in-app, consulta nuestras guías de pruebas por plataforma: [guía de pruebas para iOS](test-purchases-in-sandbox) y [guía de pruebas para Android](testing-on-android). ## Prepárate para el lanzamiento \{#prepare-for-release\} Antes de enviar tu aplicación a la store, sigue la [lista de verificación para el lanzamiento](release-checklist) para confirmar que: - La conexión con la store y las notificaciones del servidor están configuradas - Las compras se completan y se registran en Adapty - El acceso se desbloquea y se restaura correctamente - Se cumplen los requisitos de privacidad y revisión --- # File: InvalidProductIdentifiers-react-native --- --- title: "Solución para el error Code-1000 noProductIDsFound en el SDK de React Native" description: "Resuelve errores de identificador de producto no válido al gestionar suscripciones en Adapty." --- El error con código 1000, `noProductIDsFound`, indica que ninguno de los productos que solicitaste en el paywall está disponible para su compra en el App Store, aunque aparezcan listados en él. Este error a veces viene acompañado de una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin error, puedes ignorarla con tranquilidad. Si estás encontrando el error `noProductIDsFound`, sigue estos pasos para resolverlo: ## Paso 1. Verifica el bundle ID \{#step-2-check-bundle-id\} 1. Abre [App Store Connect](https://appstoreconnect.apple.com/apps). Selecciona tu app y ve a la sección **General** → **App Information**. 2. Copia el **Bundle ID** en la subsección **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Abre la pestaña [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) desde el menú superior de Adapty y pega el valor copiado en el campo **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Vuelve a la página **App information** en App Store Connect y copia el **Apple ID** desde allí. 5. En la página [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) del Adapty Dashboard, pega el ID en el campo **Apple app ID**. ## Paso 2. Verifica los productos \{#step-3-check-products\} 1. Ve a **App Store Connect** y navega a [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) en el menú de la izquierda. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripción. Verás tus productos listados en la sección **Subscriptions**. 3. Asegúrate de que el producto que estás probando esté marcado como **Ready to Submit**. <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Compara el ID de producto de la tabla con el que aparece en la pestaña [**Products**](https://app.adapty.io/products) del Adapty Dashboard. Si los IDs no coinciden, copia el ID del producto de la tabla y [crea un producto](create-product) con ese ID en el Adapty Dashboard. <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 3. Verifica la disponibilidad del producto \{#step-4-check-product-availability\} 1. Vuelve a **App Store Connect** y abre la misma sección **Subscriptions**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripción para ver tus productos. 3. Selecciona el producto que estás probando. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hasta la sección **Availability** y comprueba que todos los países y regiones necesarios aparecen en la lista. <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 4. Verifica los precios del producto \{#step-5-check-product-prices\} 1. De nuevo, ve a la sección **Monetization** → **Subscriptions** en **App Store Connect**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripción. 3. Selecciona el producto que estás probando. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hacia abajo hasta **Subscription Pricing** y despliega la sección **Current Pricing for New Subscribers**. <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Asegúrate de que todos los precios necesarios aparecen en la lista. <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 5. Verifica que el estado de pago de la app, la cuenta bancaria y los formularios fiscales estén activos \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. En la página de inicio de [**App Store Connect**](https://appstoreconnect.apple.com/), haz clic en **Business**. <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Selecciona el nombre de tu empresa. <img src="/assets/shared/img/business-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Desplázate hacia abajo y comprueba que tu **Paid Apps Agreement**, **Bank Account** y **Tax forms** muestran el estado **Active**. <img src="/assets/shared/img/appstore-connect-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Siguiendo estos pasos deberías poder resolver la advertencia `InvalidProductIdentifiers` y publicar tus productos en el store. ## Paso 6. Recrea el producto si está bloqueado \{#step-6-recreate-the-product-if-its-stuck\} Los pasos 1 a 5 pueden superarse correctamente —estado `Approved`, Bundle ID coincidente, API key válida— y aun así el SDK devuelve `1000 noProductIDsFound`. En ese caso, es posible que el producto esté bloqueado en el registro de Apple. El registro de productos de Apple puede entrar ocasionalmente en un estado en el que un producto existe en la interfaz de App Store Connect pero no está expuesto en la ruta de búsqueda de StoreKit. Elimina el producto en App Store Connect y vuelve a crearlo con el mismo ID de producto. Espera hasta 24 horas tras la recreación para que los cambios se propaguen. --- # File: cantMakePayments-react-native --- --- title: "Solución para el error Code-1003 cantMakePayment en el SDK de React Native" description: "Resuelve el error de pago al gestionar suscripciones en Adapty." --- El error 1003, `cantMakePayments`, indica que no es posible realizar compras in-app en este dispositivo. Si encuentras el error `cantMakePayments`, normalmente se debe a una de estas razones: - Restricciones del dispositivo: El error no está relacionado con Adapty. Consulta las soluciones más abajo. - Configuración del modo Observer: El método `makePurchase` y el modo Observer no pueden usarse al mismo tiempo. Consulta la sección más abajo. ## Problema: Restricciones del dispositivo \{#issue-device-restrictions\} | Problema | Solución | |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| | Restricciones de Screen Time | Desactiva las restricciones de compras in-app en [Screen Time](https://support.apple.com/en-us/102470) | | Cuenta suspendida | Contacta con el soporte de Apple para resolver problemas con la cuenta | | Restricciones regionales | Usa una cuenta de App Store de una región compatible | ## Problema: Usar el modo Observer y makePurchase a la vez \{#issue-using-both-observer-mode-and-makepurchase\} Si usas `makePurchases` para gestionar las compras, no necesitas el modo Observer. El [modo Observer](observer-vs-full-mode) solo es necesario si implementas la lógica de compra tú mismo. Por lo tanto, si usas `makePurchase`, puedes eliminar sin problema la activación del modo Observer del código de inicialización del SDK. --- # File: migration-to-react-native-sdk-v4 --- --- title: "Migrar el SDK de React Native de Adapty a la versión 4.0" description: "Migra al SDK de React Native de Adapty v4.0 (beta) reemplazando las APIs de paywall por APIs de flow, compatibles tanto con Flow Builder como con Paywall Builder." --- El SDK de React Native de Adapty 4.0 (beta) introduce los flows y cambia el nombre de las APIs de paywall en consecuencia. Las nuevas APIs funcionan tanto con el nuevo Flow Builder como con el Paywall Builder existente — no se requieren cambios de configuración en el Adapty Dashboard. ## Referencia rápida \{#quick-reference\} | v3 | v4 | |---|---| | `adapty.getPaywall(placementId, locale?, params?)` | `adapty.getFlow(placementId, params?)` | | `adapty.getPaywallForDefaultAudience(placementId, locale?, params?)` | `adapty.getFlowForDefaultAudience(placementId, params?)` | | `adapty.getPaywallProducts(paywall)` | `adapty.getPaywallProducts(flow)` | | `adapty.logShowPaywall(paywall)` | `adapty.logShowFlow(flow)` | | `AdaptyPaywall` (tipo) | `AdaptyFlow` | | `createPaywallView(paywall)` | `createFlowView(flow)` | | `AdaptyPaywallView` (componente) | `AdaptyFlowView` | | `EventHandlers` (tipo) | `FlowEventHandlers` | | `onPaywallShown` | `onAppeared` | | `onPaywallClosed` | `onDisappeared` | | `onRenderingFailed` | `onError` | `AdaptyPaywallProduct` mantiene su nombre — los productos siguen perteneciendo a un flow, y `getPaywallProducts` ahora acepta un `AdaptyFlow`. Los métodos `getFlow` y `getFlowForDefaultAudience` ya no reciben un parámetro `locale`. Los métodos de vista `present`, `dismiss`, `setEventHandlers` y `showDialog`, y los manejadores de eventos `onCloseButtonPress`, `onUrlPress`, `onCustomAction`, `onProductSelected`, `onPurchaseStarted`, `onPurchaseCompleted`, `onPurchaseFailed`, `onRestoreStarted`, `onRestoreCompleted`, `onRestoreFailed`, `onLoadingProductsFailed`, `onWebPaymentNavigationFinished` y `onAndroidSystemBack` conservan los mismos nombres que en v3. Algunos comportamientos predeterminados han cambiado — consulta [Cambios en el comportamiento predeterminado](#default-behavior-changes). ## Versión mínima de iOS \{#minimum-ios-version\} Adapty React Native SDK 4.0 eleva el deployment target mínimo de iOS 13.0 a **iOS 15.0**. Establece tu deployment target de iOS en 15.0 o superior antes de actualizar. ## Instalación \{#installation\} ### Actualizar el paquete \{#update-the-package\} v4.0 es una versión previa al lanzamiento, así que fija la versión exacta: npm no selecciona versiones pre-release mediante rangos con caret/tilde: ```bash showLineNumbers npm install react-native-adapty@4.0.0 # or yarn add react-native-adapty@4.0.0 ``` ### iOS: los SDKs nativos ahora se obtienen a través de Swift Package Manager \{#ios-native-sdks-now-come-through-swift-package-manager\} [El repositorio de specs de CocoaPods pasará a ser de solo lectura en diciembre de 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/), por lo que a partir de la v4 los SDKs nativos `Adapty`, `AdaptyUI` y `AdaptyPlugin` **ya no se descargan como sub-dependencias de CocoaPods** — el podspec los obtiene a través de **Swift Package Manager** (mediante el helper `spm_dependency`). Esto requiere dos cosas: - **React Native 0.75 o posterior** — necesario para el helper `spm_dependency` del podspec. Con una versión anterior, `pod install` falla con un error explícito; actualiza React Native primero, o quédate con `react-native-adapty` 3.x. - **Frameworks dinámicos** — las dependencias SPM requieren enlace dinámico. La forma de habilitarlo difiere entre Expo y React Native sin configuración adicional. #### Expo Añade el plugin de configuración [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/) y establece los frameworks de iOS como dinámicos en `app.json` (o `app.config.js`): ```json showLineNumbers title="app.json" { "expo": { "plugins": [ [ "expo-build-properties", { "ios": { "useFrameworks": "dynamic" } } ] ] } } ``` Luego instala el plugin y regenera el proyecto nativo: ```bash showLineNumbers npx expo install expo-build-properties npx expo prebuild --clean ``` #### Bare React Native Añade los frameworks dinámicos a tu target de iOS y reinstala los pods: ```ruby showLineNumbers title="ios/Podfile" use_frameworks! :linkage => :dynamic ``` ```bash showLineNumbers cd ios && pod install --repo-update ``` Si anteriormente incluiste `Adapty`, `AdaptyUI` o `AdaptyPlugin` como sub-dependencias de CocoaPods, elimina primero cualquier línea `pod 'Adapty'`, `pod 'AdaptyUI'` o `pod 'AdaptyPlugin'` de tu `Podfile`. :::warning Cambiar del enlace estático predeterminado a frameworks dinámicos puede entrar en conflicto con bibliotecas que aún no admiten encabezados modulares y es incompatible con Flipper. Si encuentras problemas de compilación, consulta este [artículo sobre cómo integrar Swift Package Manager con bibliotecas de React Native](https://www.callstack.com/blog/integrating-swift-package-manager-with-react-native-libraries). ::: Consulta [Instalar el SDK de Adapty](sdk-installation-reactnative) para ver la configuración completa. ## Obtención de flows \{#fetching-flows\} ### getPaywall → getFlow El tipo devuelto cambia de `AdaptyPaywall` a `AdaptyFlow`, y se elimina el parámetro `locale` — cuando renderizas un flow, el idioma se resuelve automáticamente; para paywalls personalizados, todos los idiomas están disponibles en `flow.remoteConfigs`: ```diff showLineNumbers - const paywall = await adapty.getPaywall('YOUR_PLACEMENT_ID', 'en'); + const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); ``` `getPaywallForDefaultAudience` se renombra de la misma forma: ```diff showLineNumbers - const paywall = await adapty.getPaywallForDefaultAudience('YOUR_PLACEMENT_ID', 'en'); + const flow = await adapty.getFlowForDefaultAudience('YOUR_PLACEMENT_ID'); ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` mantiene su nombre pero ahora recibe un `AdaptyFlow`: ```diff showLineNumbers - const products = await adapty.getPaywallProducts(paywall); + const products = await adapty.getPaywallProducts(flow); ``` ## Modelo de datos \{#data-model\} `getFlow` devuelve un `AdaptyFlow` en lugar de un `AdaptyPaywall`, y la forma del objeto ha cambiado: | Campo de v3 `AdaptyPaywall` | Campo de v4 `AdaptyFlow` | Acción | |---|---|---| | `remoteConfig?` (único) | `remoteConfigs?: AdaptyRemoteConfig[]` (array) | Un flow lleva un Remote Config por idioma configurado. Lee el que corresponde al usuario: `flow.remoteConfigs?.find((c) => c.lang === 'en')`. | | `products` | `flow.paywalls[i].productIdentifiers` | Los identificadores de producto ahora están en cada variación del flow, no en el flow en sí. | | `webPurchaseUrl?` | `flow.paywalls[i].webPurchaseUrl` | Se ha movido del flow a cada variación de paywall. | | `version?: number` | `flowVersionId?: string` | Renombrado, y el tipo ha cambiado de `number` a `string`. | | `hasViewConfiguration` | eliminado | Elimina cualquier comprobación de `hasViewConfiguration` de tu código. | | `requestLocale` | eliminado | La configuración regional ya no forma parte del modelo. | | _(nuevo)_ | `paywalls: AdaptyFlowPaywall[]` | Cada entrada es una variación de paywall dentro del flow. | | _(nuevo)_ | `responseCreatedAt: number` | Marca de tiempo de la respuesta del servidor, en milisegundos. | Product identifiers moved from the flow to each variation: ```diff showLineNumbers - const ids = paywall.products; + const ids = flow.paywalls[0].productIdentifiers; ``` ## Métodos de paywall web \{#web-paywall-methods\} `openWebPaywall` y `createWebPaywallUrl` mantienen sus nombres, pero el primer argumento es ahora un `AdaptyFlowPaywall` (una variante de flow) en lugar de un `AdaptyPaywall`. Puedes seguir pasando un `AdaptyPaywallProduct`. ```diff showLineNumbers const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); - await adapty.openWebPaywall(paywall); + await adapty.openWebPaywall(flow.paywalls[0]); ``` ## Seguimiento de visualizaciones de flows \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` ha pasado a llamarse `logShowFlow` y ahora recibe un `AdaptyFlow`. El evento se sigue registrando contra la misma variación, por lo que las métricas de embudo y las pruebas A/B existentes siguen funcionando sin cambios en el dashboard. ```diff showLineNumbers - await adapty.logShowPaywall(paywall); + await adapty.logShowFlow(flow); ``` Al igual que en v3, no es necesario llamar a este método cuando se muestran flows o paywalls renderizados por el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-paywall-builder) — Adapty registra esas vistas automáticamente. ## Mostrar flows \{#displaying-flows\} ### createPaywallView → createFlowView Renombra la función de fábrica y pasa el `AdaptyFlow`. Los métodos del controlador devuelto (`present`, `dismiss`, `setEventHandlers`, `showDialog`) no cambian: ```diff showLineNumbers - import { createPaywallView } from 'react-native-adapty'; + import { createFlowView } from 'react-native-adapty'; - const view = await createPaywallView(paywall); + const view = await createFlowView(flow); await view.present(); ``` ### AdaptyPaywallView → AdaptyFlowView Si renderizas con el componente React, renómbralo y pasa el prop `flow`: ```diff showLineNumbers - import { AdaptyPaywallView } from 'react-native-adapty'; + import { AdaptyFlowView } from 'react-native-adapty'; - <AdaptyPaywallView paywall={paywall} /* … */ /> + <AdaptyFlowView flow={flow} /* … */ /> ``` :::note Una vista de flow creada con `createFlowView` es de un solo uso: después de llamar a `dismiss()`, la vista se destruye, por lo que debes llamar a `createFlowView` de nuevo para presentar el flow otra vez. Un `AdaptyFlowView` embebido se descarta desmontándolo — devolver `true` desde un manejador no cierra una vista embebida, así que cambia tu propio estado en su lugar, por ejemplo en `onCloseButtonPress`. ::: ## Gestión de eventos \{#handling-events\} La interfaz de gestión de eventos se renombra de `EventHandlers` a `FlowEventHandlers`, y tres callbacks también cambian de nombre. No es necesario modificar el código interno de los handlers — solo renómbralos: ```diff showLineNumbers - onPaywallShown: () => { /* … */ }, + onAppeared: () => { /* … */ }, - onPaywallClosed: () => { /* … */ }, + onDisappeared: () => { /* … */ }, - onRenderingFailed: (error) => { /* … */ }, + onError: (error) => { /* … */ }, ``` Todos los demás manejadores de eventos conservan sus nombres. Dos también reciben un segundo argumento: `onPurchaseCompleted` pasa a ser `(purchaseResult, product)` y `onPurchaseFailed` pasa a ser `(error, product)`, donde `product` es el `AdaptyPaywallProduct` involucrado. Consulta [Gestionar eventos de flow y paywall](react-native-handling-events-1) para ver la lista completa. :::note `onDisappeared` solo se activa en un flow presentado de forma modal con `createFlowView().present()`. El componente `AdaptyFlowView` no lo expone como prop; para cerrar una vista embebida, desmóntala. ::: La v4 también añade algunas funciones opcionales a las que puedes suscribirte: - Los métodos `adapty.openWebUrl(url, openIn?)` y `adapty.requestAppReview()` respaldan los handlers predeterminados `onUrlPress` y `onRequestAppReview`, de modo que las URLs y las solicitudes de reseña de la app se gestionan de forma nativa sin configuración adicional. Llámalos directamente solo si sobreescribes esos handlers. - Gestión de compras en modo observador dentro de flows mediante los nuevos handlers `onObserverPurchaseInitiated` / `onObserverRestoreInitiated`. Consulta [Gestionar compras en modo observador](react-native-handling-events-1#handle-purchases-in-observer-mode). ## APIs eliminadas y obsoletas \{#removed-and-deprecated-apis\} ### setFallbackPaywalls → setFallback `setFallbackPaywalls` ha sido eliminado. Usa `setFallback`, que acepta el mismo argumento: ```diff showLineNumbers - await adapty.setFallbackPaywalls(fileLocation); + await adapty.setFallback(fileLocation); ``` ### Exportaciones eliminadas \{#removed-exports\} Estos símbolos ya no se exportan desde `react-native-adapty`. Elimina sus importaciones: - **`AdaptyPaywall`**: Usa `AdaptyFlow` en su lugar. - **`ProductReference`**: Usa `AdaptyProductIdentifier`, léelo desde `flow.paywalls[i].productIdentifiers`. - **`AdaptyPaywallBuilder`**: Eliminado. Los flows y paywalls se renderizan de forma nativa. - **`AdaptyAndroidSubscriptionUpdateParameters`**: Usa la forma anidada `subscriptionUpdateParams` (ver más abajo). ### activate: lockMethodsUntilReady `lockMethodsUntilReady` se ha eliminado y este comportamiento ahora está siempre activo. Elimínalo de tu llamada a `activate` — mantenerlo ya no compila: ```diff showLineNumbers - await adapty.activate('PUBLIC_SDK_KEY', { lockMethodsUntilReady: true }); + await adapty.activate('PUBLIC_SDK_KEY'); ``` ### makePurchase: actualización de suscripción en Android \{#makepurchase-android-subscription-update\} Se elimina la estructura plana de actualización de suscripción en Android. Mueve `oldSubVendorProductId` y `prorationMode` a un objeto anidado `subscriptionUpdateParams`, y mantén `isOfferPersonalized` en el nivel superior. Consulta [Realizar compras](react-native-making-purchases) para ver el ejemplo completo. ### Android: relleno de áreas seguras \{#android-safe-area-paddings\} El recurso booleano de Android `<bool name="adapty_paywall_enable_safe_area_paddings">…</bool>` ha sido eliminado. Bórralo de `res/values/bools.xml` y controla los rellenos de áreas seguras en tiempo de ejecución con el parámetro `enableSafeArea` al crear el flow view. Su valor predeterminado es `true` para la presentación modal y `false` para el componente embebido. ### Modo mock \{#mock-mode\} Si ejecutas el SDK en modo mock (Expo Go o vista previa web), renombra la clave de configuración mock `paywalls` a `flows`. ## Cambios en el comportamiento por defecto \{#default-behavior-changes\} Estos cambios no provocan errores de compilación, así que pruébalos en tiempo de ejecución: - **`onAndroidSystemBack`**: El comportamiento por defecto cambió de cerrar la vista a mantenerla abierta. Para restaurar el comportamiento anterior, devuelve `true` desde el handler. - **`onPurchaseCompleted`**: El comportamiento por defecto cambió de cerrar la vista (salvo que el usuario cancelara la compra) a mantenerla siempre abierta. Para restaurar el comportamiento anterior, devuelve `purchaseResult.type !== 'user_cancelled'` desde el handler. - **`onRestoreCompleted`**: El comportamiento por defecto cambió de cerrar la vista tras una restauración exitosa a mantenerla abierta. Para restaurar el comportamiento anterior, devuelve `true` desde el handler. - **`onUrlPress`**: El comportamiento por defecto ahora abre la URL a través de la capa nativa, respetando la configuración de navegador integrado o externo del dashboard. Sobreescribe el handler para gestionar la apertura de URLs tú mismo. ## Desuso de la API de onboarding \{#onboarding-api-deprecation\} La API de onboarding heredada está marcada como obsoleta en v4.0 a favor del [Flow Builder](adapty-flow-builder). Sigue funcionando, y tu IDE señala los símbolos obsoletos a través de sus anotaciones `@deprecated` — no hay advertencias en tiempo de ejecución. Estos símbolos se eliminarán en una versión futura, así que planifica la migración de tus onboardings al Flow Builder. Símbolos obsoletos: `getOnboarding`, `getOnboardingForDefaultAudience`, `createOnboardingView` y `AdaptyOnboardingView`. --- # File: migration-react-native-314 --- --- title: "Migrar el SDK de Adapty React Native a v3.14" description: "Migra al SDK de Adapty React Native v3.14 para obtener mejor rendimiento y nuevas funciones de monetización." --- El SDK de Adapty React Native 3.14.0 es una versión mayor que introduce mejoras que requieren pasos de migración por tu parte: - El método `registerEventHandlers` ha sido reemplazado por el método `setEventHandlers`. - En `AdaptyOnboardingView`, los manejadores de eventos ahora se pasan como props individuales en lugar de un objeto `eventHandlers` - Se ha introducido un nuevo estilo de importación simplificado para los componentes de UI - El método `logShowOnboarding` ha sido eliminado - La versión mínima de React Native se ha actualizado a 0.73.0 - El estilo de presentación por defecto en iOS para paywalls y onboardings ha cambiado de page sheet a pantalla completa ## Reemplaza `registerEventHandlers` por `setEventHandlers` \{#replace-registereventhandlers-with-seteventhandlers\} El método `registerEventHandlers`, utilizado para trabajar con el Adapty Paywall Builder y el Adapty Onboarding Builder, ha sido reemplazado por el método `setEventHandlers`. Si usas el Adapty Paywall Builder y/o el Adapty Onboarding Builder, busca `registerEventHandlers` en el código de tu app y sustitúyelo por `setEventHandlers`. Este cambio se ha introducido para que el comportamiento del método sea más claro: los handlers funcionan de uno en uno porque cada uno devuelve `true`/`false`, y tener múltiples handlers para un mismo evento hacía que el comportamiento resultante fuera difícil de predecir. Ten en cuenta que al usar componentes React como `AdaptyOnboardingView` o `AdaptyPaywallView`, no necesitas devolver `true`/`false` desde los manejadores de eventos, ya que controlas la visibilidad del componente mediante tu propio estado. Los valores de retorno solo son necesarios cuando se presenta una pantalla modal donde el SDK gestiona el ciclo de vida de la vista. :::important Llamar a `setEventHandlers` varias veces sobreescribirá los manejadores que hayas definido, reemplazando tanto los predeterminados como los configurados previamente para esos eventos específicos. ::: ```diff showLineNumbers - const unsubscribe = view.registerEventHandlers({ - // your event handlers - }) const unsubscribe = view.setEventHandlers({ // your event handlers }) ``` ## Actualiza las rutas de importación para componentes de UI \{#update-import-paths-for-ui-components\} El SDK de Adapty 3.14.0 introduce un estilo de importación simplificado para los componentes de UI. En lugar de importar desde `react-native-adapty/dist/ui`, ahora puedes importar directamente desde `react-native-adapty`. El nuevo estilo de importación es más coherente con las prácticas estándar de React Native y hace que las instrucciones de importación sean más limpias. Si utilizas componentes de UI como `AdaptyPaywallView` o `AdaptyOnboardingView`, actualiza tus importaciones como se muestra a continuación: ```diff showLineNumbers - import { AdaptyPaywallView } from 'react-native-adapty/dist/ui'; + import { AdaptyPaywallView } from 'react-native-adapty'; - import { AdaptyOnboardingView } from 'react-native-adapty/dist/ui'; + import { AdaptyOnboardingView } from 'react-native-adapty'; - import { createPaywallView } from 'react-native-adapty/dist/ui'; + import { createPaywallView } from 'react-native-adapty'; - import { createOnboardingView } from 'react-native-adapty/dist/ui'; + import { createOnboardingView } from 'react-native-adapty'; ``` :::note Por compatibilidad con versiones anteriores, el antiguo estilo de importación (`react-native-adapty/dist/ui`) sigue siendo compatible. Sin embargo, recomendamos usar el nuevo estilo de importación para mayor coherencia y claridad. ::: ## Actualizar los manejadores de eventos del onboarding en el componente React \{#update-onboarding-event-handlers-in-the-react-component\} Los manejadores de eventos para onboardings se han movido fuera del objeto `eventHandlers` en `AdaptyOnboardingView`. Si estás mostrando onboardings con `AdaptyOnboardingView`, actualiza la estructura de manejo de eventos. :::important Presta atención a la forma en que recomendamos implementar los manejadores de eventos. Para evitar recrear objetos en cada renderizado, usa `useCallback` para las funciones que gestionan eventos. ::: ```diff showLineNumbers import React, { useCallback } from 'react'; - import { AdaptyOnboardingView } from 'react-native-adapty/dist/ui'; + import { AdaptyOnboardingView } from 'react-native-adapty'; + import type { OnboardingEventHandlers } from 'react-native-adapty'; + + function MyOnboarding({ onboarding }) { + const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); + const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); + const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); + const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); + const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); + const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); + const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); + return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} - eventHandlers={{ - onAnalytics(event, meta) { /* ... */ }, - onClose(actionId, meta) { /* ... */ }, - onCustom(actionId, meta) { /* ... */ }, - onPaywall(actionId, meta) { /* ... */ }, - onStateUpdated(action, meta) { /* ... */ }, - onFinishedLoading(meta) { /* ... */ }, - onError(error) { /* ... */ }, - }} + onAnalytics={onAnalytics} + onClose={onClose} + onCustom={onCustom} + onPaywall={onPaywall} + onStateUpdated={onStateUpdated} + onFinishedLoading={onFinishedLoading} + onError={onError} /> ); + } ``` :::note Por compatibilidad con versiones anteriores, la prop `eventHandlers` sigue siendo compatible, pero está obsoleta. Recomendamos migrar a las props de manejadores de eventos individuales tal como se muestra arriba. ::: ## Elimina `logShowOnboarding` \{#delete-logshowonboarding\} En el SDK de Adapty 3.14.0, hemos eliminado el método `logShowOnboarding` del SDK. Si has estado usando este método, no estará disponible cuando actualices el SDK a la versión 3.14 o posterior. En su lugar, puedes [crear onboardings en el constructor de onboardings sin código de Adapty](onboardings). Las analíticas de estos onboardings se registran automáticamente y tienes muchas opciones de personalización. ## Actualiza React Native \{#update-react-native\} A partir del SDK de Adapty 3.14.0, la versión mínima compatible de React Native es 0.73.0. Si estás usando una versión anterior, actualiza React Native a la versión 0.73.0 o posterior para que tu experiencia con el SDK de Adapty sea consistente y fiable. ## Actualiza el estilo de presentación de iOS para paywalls y onboardings modales \{#update-ios-presentation-style-for-modal-paywalls-and-onboardings\} En el SDK de Adapty 3.14.0, el estilo de presentación predeterminado de iOS para paywalls y onboardings mostrados con el método `view.present()` ha cambiado de hoja de página a pantalla completa. Si quieres mantener el estilo de presentación de hoja de página anterior, pasa el parámetro `iosPresentationStyle` al método `present()`: ```typescript showLineNumbers title="React Native (TSX)" try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` --- # File: react-native-migration-guide-380 --- --- title: "Migrar el SDK de React Native de Adapty a v3.8" description: "Migra al SDK de React Native de Adapty v3.8 para obtener mejor rendimiento y nuevas funciones de monetización." --- Adapty SDK 3.8.0 es una versión mayor que incorpora algunas mejoras que, sin embargo, pueden requerir ciertos pasos de migración por tu parte. ## Actualizar el tipo de entrada para obtener los parámetros del placement \{#update-input-type-for-getting-placement-params\} `GetPaywallParamsInput` ha sido renombrado a `GetPlacementParamsInput`: ```diff showLineNumbers - type GetPaywallParamsInput = { + type GetPlacementParamsInput = { placementId: string; locale?: string; fetchPolicy?: AdaptyPlacementFetchPolicy; loadTimeoutMs?: number; } ``` ## Actualizar el método de respaldo \{#update-fallback-method\} El método para establecer respaldos ha sido actualizado, y el tipo para especificar las ubicaciones de respaldo ha sido renombrado: ```diff showLineNumbers - adapty.setFallbackPaywalls(paywallsLocation: Input.FallbackPaywallsLocation); + adapty.setFallback(fileLocation: Input.FileLocation); ``` ## Actualización del acceso a propiedades del paywall \{#update-paywall-property-access\} Las siguientes propiedades se han movido de `AdaptyPaywall` a `AdaptyPlacement`: ```diff showLineNumbers - paywall.abTestName - paywall.audienceName - paywall.revision - paywall.placementId + paywall.placement.abTestName + paywall.placement.audienceName + paywall.placement.revision + paywall.placement.id ``` --- # File: migration-to-react-native-sdk-34 --- --- title: "Migrar el SDK de Adapty para React Native a v. 3.4" description: "Migra al SDK de Adapty para React Native v3.4 para mejorar el rendimiento y acceder a nuevas funciones de monetización." --- El SDK de Adapty 3.4.0 es una versión mayor que introduce mejoras que requieren pasos de migración por tu parte. ## Actualizar los archivos del paywall de respaldo \{#update-fallback-paywall-files\} Actualiza los archivos del paywall de respaldo para garantizar la compatibilidad con la nueva versión del SDK: 1. [Descarga los archivos actualizados del paywall de respaldo](fallback-paywalls) desde el Adapty Dashboard. 2. [Reemplaza los paywalls de respaldo existentes en tu aplicación móvil](react-native-use-fallback-paywalls) con los nuevos archivos. ## Actualizar la implementación del Modo Observador \{#update-implementation-of-observer-mode\} Si usas el Modo Observador, asegúrate de actualizar su implementación. Antes se utilizaban métodos distintos para reportar transacciones a Adapty. En la nueva versión, el método `reportTransaction` debe usarse de forma consistente tanto en Android como en iOS. Este método reporta explícitamente cada transacción a Adapty para garantizar que se reconozca. Si se usó un paywall, pasa el ID de variación para vincular la transacción a él. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: ```diff showLineNumbers - if (Platform.OS === 'android') { - try { - await adapty.restorePurchases(); - } catch (error) { - // handle the error - } - } const variationId = paywall.variationId; try { await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` --- # File: migration-to-react-native330 --- --- title: "Migrar el SDK de React Native de Adapty a v3.3" description: "Migra al SDK de React Native de Adapty v3.3 para mejorar el rendimiento y acceder a nuevas funciones de monetización." --- El SDK de Adapty 3.3.1 es una versión mayor que incluye mejoras que pueden requerir algunos pasos de migración por tu parte. 1. Actualiza al SDK de Adapty v3.3.x. 2. Actualiza los modelos. 3. Elimina el método `getProductsIntroductoryOfferEligibility`. 4. Actualiza el proceso de compra. 5. Actualiza la presentación del paywall en Paywall Builder. 6. Revisa la implementación del temporizador definido por el desarrollador. 7. Actualiza el manejo de eventos de compra en Paywall Builder. 8. Actualiza el manejo de eventos de acción personalizada en Paywall Builder. 9. Modifica el callback `onProductSelected`. 10. Elimina los parámetros de integración de terceros del método `updateProfile`. 11. Actualiza las configuraciones de integración para Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase y Google Analytics, Mixpanel, OneSignal y Pushwoosh. 12. Actualiza la implementación del modo Observer. ## Actualiza el SDK de React Native de Adapty a 3.3.x \{#upgrade-adapty-react-native-sdk-to-33x\} Antes de la versión 3.3.1, el SDK `react-native-adapty` era el núcleo obligatorio para que Adapty funcionara correctamente en tu app. El SDK `@adapty/react-native-ui` era opcional y solo necesario si usabas el Paywall Builder de Adapty. A partir de la versión 3.3.1, el SDK `@adapty/react-native-ui` queda obsoleto y su funcionalidad se ha integrado en el SDK `react-native-adapty`. Para actualizar a la versión 3.3.1, sigue estos pasos: 1. Actualiza el paquete `react-native-adapty` a la versión 3.3.1. 2. Elimina el paquete `@adapty/react-native-ui` de las dependencias de tu proyecto. 3. Sincroniza las dependencias del proyecto para aplicar los cambios. ## Cambios en los modelos \{#changes-in-models\} ### Nuevos modelos \{#new-models\} 1. [AdaptySubscriptionOffer](https://react-native.adapty.io/interfaces/adaptysubscriptionoffer): ```typescript showLineNumbers export interface AdaptySubscriptionOffer { readonly identifier: AdaptySubscriptionOfferId; phases: AdaptyDiscountPhase[]; android?: { offerTags?: string[]; }; } ``` 2. [AdaptySubscriptionOfferId](https://react-native.adapty.io/types/adaptysubscriptionofferid): ```typescript showLineNumbers export type AdaptySubscriptionOfferId = | { id?: string; type: 'introductory'; } | { id: string; type: 'promotional' | 'win_back'; }; ``` ### Modelos modificados 1. [AdaptyPaywallProduct](https://react-native.adapty.io/interfaces/adaptypaywallproduct): - Se ha renombrado la propiedad `subscriptionDetails` a `subscription`. <p> </p> ```diff showLineNumbers - subscriptionDetails?: AdaptySubscriptionDetails; + subscription?: AdaptySubscriptionDetails; ``` 2. [AdaptySubscriptionDetails](https://react-native.adapty.io/interfaces/adaptysubscriptiondetails): - `promotionalOffer` ha sido eliminado. Ahora la oferta promocional se entrega dentro de la propiedad `offer` solo si está disponible. En ese caso, `offer?.identifier?.type` será `'promotional'`. - `introductoryOfferEligibility` ha sido eliminado (las ofertas solo se devuelven si el usuario es elegible). - `offerId` ha sido eliminado. El ID de la oferta ahora se almacena en `AdaptySubscriptionOffer.identifier`. - `offerTags` se ha movido a `AdaptySubscriptionOffer.android`. <p> </p> ```diff showLineNumbers - introductoryOffers?: AdaptyDiscountPhase[]; + offer?: AdaptySubscriptionOffer; ios?: { - promotionalOffer?: AdaptyDiscountPhase; subscriptionGroupIdentifier?: string; }; android?: { - offerId?: string; basePlanId: string; - introductoryOfferEligibility: OfferEligibility; - offerTags?: string[]; renewalType?: 'prepaid' | 'autorenewable'; }; } ``` 3. [AdaptyDiscountPhase](https://react-native.adapty.io/interfaces/adaptydiscountphase): - El campo `identifier` se ha eliminado del modelo `AdaptyDiscountPhase`. El identificador de oferta ahora se almacena en `AdaptySubscriptionOffer.identifier`. <p> </p> ```diff showLineNumbers - ios?: { - readonly identifier?: string; - }; ``` ### Modelos eliminados \{#remove-models\} 1. `AttributionSource`: - Ahora se usa un string en los lugares donde antes se usaba `AttributionSource`. 2. `OfferEligibility`: - Este modelo se ha eliminado porque ya no es necesario. Ahora, una oferta solo se devuelve si el usuario es elegible. ## Elimina el método `getProductsIntroductoryOfferEligibility` \{#remove-getproductsintroductoryoffereligibility-method\} Antes del SDK de Adapty 3.3.1, los objetos de producto siempre incluían las ofertas, incluso si el usuario no era elegible. Esto requería verificar la elegibilidad manualmente antes de usar la oferta. A partir de la versión 3.3.1, el objeto de producto incluye ofertas solo si el usuario es elegible. Esto simplifica el proceso, ya que puedes asumir que el usuario es elegible si hay una oferta presente. ## Actualizar la realización de compras \{#update-making-purchase\} En versiones anteriores, las compras canceladas y pendientes se trataban como errores y devolvían los códigos `2: 'paymentCancelled'` y `25: 'pendingPurchase'`, respectivamente. A partir de la versión 3.3.1, las compras canceladas y pendientes se consideran resultados exitosos y deben gestionarse en consecuencia: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` ## Actualiza la presentación de paywalls del Paywall Builder \{#update-paywall-builder-paywall-presentation\} Para ver ejemplos actualizados, consulta la documentación [Presentar paywalls nuevos del Paywall Builder en React Native](react-native-present-paywalls). ```diff showLineNumbers - import { createPaywallView } from '@adapty/react-native-ui'; + import { createPaywallView } from 'react-native-adapty/dist/ui'; const view = await createPaywallView(paywall); view.registerEventHandlers(); // handle close press, etc try { await view.present(); } catch (error) { // handle the error } ``` ## Actualiza la implementación del temporizador definido por el desarrollador \{#update-developer-defined-timer-implementation\} Renombra el parámetro `timerInfo` a `customTimers`: ```diff showLineNumbers - let timerInfo = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } + let customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } //and then you can pass it to createPaywallView as follows: - view = await createPaywallView(paywall, { timerInfo }) + view = await createPaywallView(paywall, { customTimers }) ``` ## Modificar eventos de compra del Paywall Builder \{#modify-paywall-builder-purchase-events\} Anteriormente: - Las compras canceladas activaban el callback `onPurchaseCancelled`. - Las compras pendientes devolvían el código de error `25: 'pendingPurchase'`. Ahora: - Ambos casos se gestionan mediante el callback `onPurchaseCompleted`. #### Pasos para migrar: \{#steps-to-migrate\} 1. Elimina el callback `onPurchaseCancelled`. 2. Elimina el manejo del código de error `25: 'pendingPurchase'`. 3. Actualiza el callback `onPurchaseCompleted`: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.registerEventHandlers({ // ... other optional callbacks onPurchaseCompleted(purchaseResult, product) { switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; // highlight-start case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; // highlight-end } // highlight-start return purchaseResult.type !== 'user_cancelled'; // highlight-end }, }); ``` ## Modifica los eventos de acción personalizada del Paywall Builder \{#modify-paywall-builder-custom-action-events\} Callbacks eliminados: - `onAction` - `onCustomEvent` Callback añadido: - Nuevo callback `onCustomAction(actionId)`. Úsalo para acciones personalizadas. ## Modifica el callback `onProductSelected` \{#modify-onproductselected-callback\} Antes, `onProductSelected` requería el objeto `product`. Ahora requiere `productId` como string. ## Elimina los parámetros de integración de terceros del método `updateProfile` \{#remove-third-party-integration-parameters-from-updateprofile-method\} Los identificadores de integración de terceros ahora se configuran con el método `setIntegrationIdentifier`. El método `updateProfile` ya no los acepta. ## Actualiza la configuración del SDK de integraciones de terceros \{#update-third-party-integration-sdk-configuration\} Para garantizar que las integraciones funcionen correctamente con el SDK de Adapty React Native 3.3.1 y versiones posteriores, actualiza las configuraciones de tu SDK para las siguientes integraciones según se describe en las secciones a continuación. Además, si usabas `AttributionSource` para obtener el identificador de atribución, cambia tu código para proporcionar el identificador requerido como string. ### Adjust Actualiza el código de tu app para móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers import { Adjust, AdjustConfig } from "react-native-adjust"; import { adapty } from "react-native-adapty"; var adjustConfig = new AdjustConfig(appToken, environment); // Before submiting Adjust config... adjustConfig.setAttributionCallbackListener(attribution => { // Make sure Adapty SDK is activated at this point // You may want to lock this thread awaiting of `activate` adapty.updateAttribution(attribution, "adjust"); }); // ... Adjust.create(adjustConfig); + Adjust.getAdid((adid) => { + if (adid) + adapty.setIntegrationIdentifier("adjust_device_id", adid); + }); ``` ### AirBridge \{#airbridge\} Actualiza el código de tu app móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers import Airbridge from 'airbridge-react-native-sdk'; import { adapty } from 'react-native-adapty'; try { const deviceId = await Airbridge.state.deviceUUID(); - await adapty.updateProfile({ - airbridgeDeviceId: deviceId, - }); + await adapty.setIntegrationIdentifier("airbridge_device_id", deviceId); } catch (error) { // handle `AdaptyError` } ``` ### Amplitude \{#amplitude\} Actualiza el código de tu app móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; try { - await adapty.updateProfile({ - amplitudeDeviceId: deviceId, - amplitudeUserId: userId, - }); + await adapty.setIntegrationIdentifier("amplitude_device_id", deviceId); + await adapty.setIntegrationIdentifier("amplitude_user_id", userId); } catch (error) { // handle `AdaptyError` } ``` ### AppMetrica Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import AppMetrica, { DEVICE_ID_KEY, StartupParams, StartupParamsReason } from '@appmetrica/react-native-analytics'; // ... const startupParamsCallback = async ( params?: StartupParams, reason?: StartupParamsReason ) => { const deviceId = params?.deviceId if (deviceId) { try { - await adapty.updateProfile({ - appmetricaProfileId: 'YOUR_ADAPTY_CUSTOMER_USER_ID', - appmetricaDeviceId: deviceId, - }); + await adapty.setIntegrationIdentifier("appmetrica_profile_id", 'YOUR_ADAPTY_CUSTOMER_USER_ID'); + await adapty.setIntegrationIdentifier("appmetrica_device_id", deviceId); } catch (error) { // handle `AdaptyError` } } } AppMetrica.requestStartupParams(startupParamsCallback, [DEVICE_ID_KEY]) ``` ### AppsFlyer Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import appsFlyer from 'react-native-appsflyer'; appsFlyer.onInstallConversionData(installData => { try { - const networkUserId = appsFlyer.getAppsFlyerUID(); - adapty.updateAttribution(installData, AttributionSource.AppsFlyer, networkUserId); + const uid = appsFlyer.getAppsFlyerUID(); + adapty.setIntegrationIdentifier("appsflyer_id", uid); + adapty.updateAttribution(installData, "appsflyer"); } catch (error) { // handle the error } }); // ... appsFlyer.initSdk(/*...*/); ``` ### Branch \{#branch\} Actualiza el código de tu app móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import branch from 'react-native-branch'; branch.subscribe({ enComplete: ({ params, }) => { - adapty.updateAttribution(params, AttributionSource.Branch); + adapty.updateAttribution(params, "branch"); }, }); ``` ### Facebook Ads Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Facebook Ads](facebook-ads#connect-your-app-to-facebook-ads). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import { AppEventsLogger } from 'react-native-fbsdk-next'; try { const anonymousId = await AppEventsLogger.getAnonymousID(); - await adapty.updateProfile({ - facebookAnonymousId: anonymousId, - }); + await adapty.setIntegrationIdentifier("facebook_anonymous_id", anonymousId); } catch (error) { // handle `AdaptyError` } ``` ### Firebase y Google Analytics \{#firebase-and-google-analytics\} Actualiza el código de tu aplicación móvil como se indica a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Firebase y Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers import analytics from '@react-native-firebase/analytics'; import { adapty } from 'react-native-adapty'; try { const appInstanceId = await analytics().getAppInstanceId(); - await adapty.updateProfile({ - firebaseAppInstanceId: appInstanceId, - }); + await adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId); } catch (error) { // handle `AdaptyError` } ``` ### Mixpanel \{#mixpanel\} Actualiza el código de tu app móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import { Mixpanel } from 'mixpanel-react-native'; // ... try { - await adapty.updateProfile({ - mixpanelUserId: mixpanelUserId, - }); + await adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelUserId); } catch (error) { // handle `AdaptyError` } ``` ### OneSignal Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con OneSignal](onesignal#sdk-configuration). <Tabs groupId="current-os" queryString> <TabItem value="v5+" label="OneSignal SDK v5+ (actual)" default> ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import OneSignal from 'react-native-onesignal'; OneSignal.User.pushSubscription.addEventListener('change', (subscription) => { const subscriptionId = subscription.current.id; if (subscriptionId) { - adapty.updateProfile({ - oneSignalSubscriptionId: subscriptionId, - }); + adapty.setIntegrationIdentifier("one_signal_subscription_id", subscriptionId); } }); ``` </TabItem> <TabItem value="pre-v5" label="OneSignal SDK v. up to 4.x (legacy)" default> ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import OneSignal from 'react-native-onesignal'; OneSignal.addSubscriptionObserver(event => { const playerId = event.to.userId; - adapty.updateProfile({ - oneSignalPlayerId: playerId, - }); + adapty.setIntegrationIdentifier("one_signal_player_id", playerId); }); ``` </TabItem> </Tabs> ### Pushwoosh \{#pushwoosh\} Actualiza el código de tu app móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import Pushwoosh from 'pushwoosh-react-native-plugin'; // ... try { - await adapty.updateProfile({ - pushwooshHWID: hwid, - }); + await adapty.setIntegrationIdentifier("pushwoosh_hwid", hwid); } catch (error) { // handle `AdaptyError` } ``` ## Actualizar la implementación del modo Observer \{#update-observer-mode-implementation\} Actualiza cómo vinculas los paywalls a las transacciones. Antes usabas el método `setVariationId` para asignar el `variationId`. Ahora puedes incluir el `variationId` directamente al registrar la transacción con el nuevo método `reportTransaction`. Consulta el ejemplo de código final en [Asociar paywalls con transacciones de compra en modo Observer](report-transactions-observer-mode-react-native). :::warning No olvides registrar la transacción con el método `reportTransaction`. Si omites este paso, Adapty no reconocerá la transacción, no otorgará niveles de acceso, no la incluirá en los análisis ni la enviará a las integraciones. ¡Este paso es imprescindible! ::: :::note Ten en cuenta que el orden de los parámetros del método `reportTransaction` es diferente al del método `setVariationId`. ::: ```diff showLineNumbers const variationId = paywall.variationId; try { - await adapty.setVariationId(variationId, transactionId); + await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` --- # File: migration-to-react-native-sdk-v3 --- --- title: "Migrar el SDK de Adapty React Native a v3.0" description: "Migra al SDK de Adapty React Native v3.0 para mejor rendimiento y nuevas funcionalidades de monetización." --- El SDK de Adapty v3.0 incluye soporte para el nuevo y emocionante [Adapty Paywall Builder](adapty-paywall-builder), la nueva versión de la herramienta no-code y fácil de usar para crear paywalls. Con su máxima flexibilidad y ricas capacidades de diseño, tus paywalls serán más efectivos y rentables. ## Actualizar a la versión 3.0.1 \{#upgrade-to-version-301\} 1. Actualiza a la versión 3.0.1 como de costumbre. 2. Reemplaza los archivos de paywall de respaldo: 1. [Descarga la versión más reciente](fallback-paywalls) desde el Adapty Dashboard. 2. Guárdalos en el dispositivo del usuario y pásalos al método `.setFallbackPaywalls` tal como se describe [aquí](react-native-use-fallback-paywalls). --- # End of Documentation _Generated on: 2026-07-24T13:01:55.780Z_ _Successfully processed: 45/45 files_ # TUTORIAL - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-07-24T13:01:55.783Z Total files: 277 --- # File: is-adapty-right-for-me --- --- title: "¿Es Adapty la opción adecuada para mí?" description: "Descubre cómo encaja Adapty en tu caso de uso. Tanto si estás lanzando una nueva app, optimizando ingresos o migrando desde otra herramienta — aquí es por donde empezar." --- Adapty es una plataforma de compras in-app para apps móviles. Gestiona suscripciones, compras únicas y consumibles — desde el procesamiento de compras y la validación de recibos hasta analíticas, pruebas A/B e integraciones. Aquí te explicamos cómo funciona Adapty en distintos escenarios. ## Voy a lanzar una nueva app con compras in-app \{#im-launching-a-new-app-with-in-app-purchases\} Tanto si vas a vender suscripciones, compras únicas o consumibles, Adapty lo cubre todo: - **SDKs para 7 plataformas**: iOS, Android, React Native, Flutter, Unity, Kotlin Multiplatform y Capacitor. - **Gestión de compras**: Suscripciones con renovaciones y lógica de reintentos, compras únicas, consumibles y validación de recibos — todo gestionado por nosotros. - **Paywall Builder sin código**: Diseña y lanza paywalls sin escribir código de interfaz. - **Analíticas desde el primer día**: Rastrea ingresos, pruebas, conversiones y más desde que lleguen tus primeros usuarios. ¿Listo para empezar? Sigue la [guía de inicio rápido](quickstart). ## Quiero pruebas A/B, analíticas e integraciones \{#i-want-ab-tests-analytics-and-integrations\} Adapty te ayuda a optimizar lo que ya funciona: - **Pruebas A/B**: Prueba diferentes precios, diseños de paywall, duraciones de prueba y ofertas promocionales para descubrir qué convierte mejor. Usa [AI Growth Advisor](autopilot) para obtener recomendaciones de pruebas A/B adaptadas a tu app, basadas en datos de más de 20.000 apps de suscripción. - **Gráficos de analítica**: Rastrea MRR, LTV, churn, retención y docenas de otras métricas. - **Segmentación de audiencia**: Dirige paywalls y ofertas personalizadas a grupos de usuarios específicos. - **Configuración remota de paywalls**: Itera en tus paywalls sin lanzar una nueva versión de la app. - **Integraciones con terceros**: Envía eventos de compra a Amplitude, AppsFlyer, Adjust, Mixpanel y otras herramientas que tu equipo ya utiliza. Explora las [pruebas A/B](ab-tests), [Analytics](analytics), las [integraciones de servicios de analytics](analytics-integration) o las [integraciones de servicios de atribución](attribution-integration). ## Quiero implementar compras in-app con un LLM \{#i-want-to-implement-in-app-purchases-with-an-llm\} La documentación de Adapty está optimizada para su uso con asistentes de código con IA como Cursor, Claude, ChatGPT y otros. Cada página está disponible en Markdown plano, y ofrecemos guías de implementación asistidas por LLM paso a paso para cada plataforma: - **Guías listas para copiar y pegar**: Envía la guía a tu LLM y deja que te acompañe en cada etapa de la implementación. - **Acceso en Markdown**: Añade `.md` a cualquier URL de la documentación o haz clic en **Copy for LLM** para obtener una versión en texto limpio. - **Compatibilidad con Context7 MCP**: Conecta la documentación de Adapty directamente a tu IDE con IA. Elige tu plataforma y empieza: [Integra Adapty con ayuda de IA](adapty-cursor). ## Quiero ejecutar y optimizar campañas de Apple Ads \{#i-want-to-run-and-optimize-apple-ads-campaigns\} Si utilizas Apple Search Ads, Adapty Ads Manager conecta el rendimiento de tus campañas directamente con las métricas de ingresos, sin necesidad de ningún MMP: - **Datos de rendimiento en tiempo real**: Rastrea campañas, grupos de anuncios y palabras clave. - **Seguimiento de ingresos de extremo a extremo**: Sigue la cadena desde la búsqueda hasta la instalación, el periodo de prueba, la suscripción y el LTV. - **Predicciones y recomendaciones con IA**: Prevé el retorno y obtén sugerencias de escalado. - **Agente de IA**: Formula preguntas en lenguaje natural sobre tu cuenta y obtén respuestas y recomendaciones para todo el funnel. - **Automatizaciones basadas en reglas**: Mantén estables tus objetivos de CPA y ROAS. Empieza con [Adapty Ads Manager](adapty-ads-manager). ## Quiero saber de dónde vienen mis usuarios \{#i-want-to-track-where-my-users-come-from\} Adapty Attribution es una solución de atribución integrada que conecta el gasto publicitario con las instalaciones de la app y los ingresos por suscripción: - **Dashboard de marketing unificado**: Ve ROAS, instalaciones e ingresos de todos tus canales en un solo lugar. - **Atribución integrada**: Conecta campañas de publicidad con instalaciones e ingresos sin depender de MMPs externos. - **Links de seguimiento**: Genera links en Adapty y añádelos a tus campañas para una atribución precisa. - **Deeplinks diferidos**: Lleva a los usuarios al contenido correcto tras la instalación, aunque no tuvieran la app cuando hicieron clic. - **Análisis de cohortes**: Analiza el rendimiento de adquisición y el comportamiento de los usuarios a lo largo del tiempo. Más información sobre [Atribución de Adapty](adapty-user-acquisition). ## Quiero convertir usuarios en prueba y recuperar suscriptores perdidos por email \{#i-want-to-convert-trial-users-and-recover-churned-subscribers-via-email\} Adapty Mail convierte los datos de usuarios de Adapty en campañas de email generadas por IA que se dirigen a usuarios en prueba, suscriptores perdidos y otros eventos del ciclo de vida: - **Campañas generadas por IA**: Adapty genera el texto y el diseño de cada campaña a partir de tu perfil de marca. - **Disparadores de ciclo de vida**: Envía campañas automáticamente basándote en eventos clave del ciclo de vida. - **Checkout en paywall web**: Enlaces de checkout personalizados para cada destinatario, con las compras atribuidas al email que las generó. - **Envía desde tu propio dominio**: Todos los emails se envían desde tu dominio verificado, sin necesidad de una plataforma de email independiente. Más información sobre [Adapty Mail](adapty-mail). ## Quiero iterar rápido sin publicar nuevas versiones de la app \{#i-want-to-iterate-fast-without-app-releases\} Una vez integrado Adapty, la mayor parte del trabajo diario ocurre en el dashboard — sin necesidad de nuevas versiones de la app: - **Flows**: Diseña paywalls y onboardings en un editor visual y publica cambios al instante. - **Pruebas A/B desde el dashboard**: Lanza experimentos, ajusta precios y cambia ofertas sin tocar el código. - **Analíticas del dashboard**: Monitorea ingresos, cancelaciones, trials y conversiones en tiempo real. - **Informes por Slack y correo electrónico**: Recibe actualizaciones automáticas sobre las métricas que importan a tu equipo. Explora los [Flows](adapty-flow-builder) o consulta [Analytics](charts). ## Vendo en la web y necesito una app móvil \{#i-sell-on-the-web-and-need-a-mobile-app\} Si tus usuarios ya pagan a través de un sitio web y estás añadiendo una app móvil, Adapty sincroniza las compras entre plataformas: - **Integración con Stripe y Paddle**: Sincroniza las compras web en Adapty automáticamente. - **Sincronización web-a-móvil**: Los usuarios que pagaron en la web obtienen acceso en tu app, y viceversa. - **Analíticas unificadas entre plataformas**: Consulta los ingresos web y móvil en un mismo dashboard. Configura la [integración con Stripe](stripe), la [integración con Paddle](paddle), o aprende a [sincronizar suscriptores web y móvil](sync-subscribers-from-web). ## Estoy migrando desde otra herramienta \{#im-migrating-from-another-tool\} Adapty facilita la migración desde otras plataformas de suscripciones: - **Guías de migración**: Instrucciones paso a paso para migrar desde otras plataformas de suscripciones. - **Modo Observer**: Mantén tu código de facturación actual y adopta Adapty de forma incremental con el [modo Observer](observer-vs-full-mode) — empieza con analíticas y pruebas A/B, y amplía cuando estés listo. - **Importación de datos históricos**: Importa tu historial de transacciones existente a Adapty para que tus analíticas estén completas. Aprende sobre [migrar a Adapty](migrate-to-adapty-from-another-solutions) e [importar datos históricos](importing-historical-data-to-adapty). --- ¿Aún explorando? La [guía de inicio rápido](quickstart) es siempre un buen punto de partida. --- # File: integrate-payments --- --- title: "Integrar con stores o plataformas de pago" description: "Integra Adapty con App Store, Google Play, stores personalizados, Stripe y Paddle." --- Para empezar con Adapty, primero integra con los stores donde tus usuarios compran productos. Adapty se conecta a varios app stores y proveedores de pago web, centralizando todas tus compras in-app y análisis en un solo lugar. ## Integrar con stores y pagos web \{#integrate-with-stores-and-web-payments\} Elige tu store a continuación para ver los pasos de integración detallados: - [App Store](initial_ios) - [Google Play](initial-android) - Pagos web: - [Stripe](stripe) - [Paddle](paddle) - [Otros stores](custom-store) ## Próximos pasos \{#next-steps\} Una vez que hayas conectado tu store o plataforma de pago, puedes continuar con [agregar productos](quickstart-products). --- # File: quickstart-products --- --- title: "Añadir productos" description: "Añade productos in-app o suscripciones a Adapty y vincúlalos a tus listados de App Store, Google Play, Stripe, Paddle o tiendas personalizadas." --- :::tip ¿Configurando Adapty de forma programática? Puedes completar este paso usando el [CLI para desarrolladores](developer-cli-quickstart). ::: Antes de poder usar las funciones principales de Adapty, necesitas añadir cada producto que vendes y vincularlo a cada store o plataforma de pago que utilices. Esta configuración te permite entregar productos a los dispositivos de los usuarios y hacer seguimiento de ellos en el análisis posterior. En Adapty, cualquier cosa que tu app venda es un **producto**. Si el mismo artículo existe en el App Store, Google Play o Stripe, puedes agruparlos en un único producto en Adapty. Configúralo una vez y gestiona todo desde un solo lugar. Vamos a añadir tu primer producto. <Tabs groupId="products" queryString> <TabItem value="no-products" label="No products in stores yet" default> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/qUpC2XG-r5E?si=7Komyv4_PUQ4FaEH" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> </TabItem> <TabItem value="products-in-stores" label="Products in stores already"> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/nlkdKCF0SwY?si=VVigzHcpv3waKJmI" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> </TabItem> </Tabs> ## Añade tu primer producto \{#add-your-first-product\} :::tip Este inicio rápido cubre los aspectos básicos para crear un producto. Para más detalles, consulta la guía sobre [cómo crear productos](create-product). ::: Supongamos que quieres añadir una suscripción mensual como producto. 1. Ve a [Products](https://app.adapty.io/products) desde el menú principal de Adapty. 2. Haz clic en **Create product** en la parte superior derecha. <img src={require('./img/products-tab.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important **Los siguientes pasos dependen de si ya tienes productos en el App Store y/o Google Play:** ::: <Tabs groupId="products" queryString> <TabItem value="no-products" label="No products in stores yet" default> :::important Antes de empezar, asegúrate de haber configurado la integración con [App Store](initial_ios) y/o [Google Play](initial-android). Para el App Store, asegúrate de haber [añadido la clave API de App Store Connect](app-store-connection-configuration#step-6-add-app-store-connect-api-key) para que Adapty pueda enviar productos. ::: 3. Selecciona **Create a new product and push to stores**. 4. Añade los detalles del producto: - **Product name**: El nombre visible solo para ti en el Adapty Dashboard. - **Access Level**: El identificador único que determina qué funciones se desbloquean tras la compra. Si todos los usuarios de pago en tu app acceden a las mismas funciones, puedes usar el nivel de acceso predeterminado: `premium`. Para configuraciones más complejas, crea [niveles de acceso](access-level) adicionales. - **Subscription duration**: Selecciona la duración de la suscripción de la lista. - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**: La duración de la suscripción. - **Lifetime**: Usa un período vitalicio para los productos que desbloquean las funciones premium de la app para siempre. - **Non-Subscriptions**: Para los productos que no son suscripciones y por tanto no tienen duración, usa non-subscriptions. Se pueden usar para desbloquear funciones adicionales, productos consumibles, etc. - **Consumables**: Los artículos consumibles se pueden comprar varias veces. Se consumen durante el ciclo de vida de la aplicación. Ejemplos son la moneda del juego y los extras. Ten en cuenta que los productos consumibles no afectan a los niveles de acceso. - **Price (USD)**: El precio del producto en USD. Este precio se usará como base para calcular y establecer automáticamente los precios en todos los países. Podrás [personalizar el precio para diferentes países y regiones](edit-product#set-country-specific-prices) más adelante. <img src={require('./img/create-product-push.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en **Save & Continue** y cambia a la pestaña **App Store** o **Google Play** para rellenar los detalles del producto para la store. <Tabs> <TabItem value="App Store" label="App Store" default> - **Product ID**: Crea un ID único y permanente para el producto. - **Product group**: Selecciona un grupo de productos existente que hayas creado en App Store Connect o haz clic en **Create new Product Group** y establece su nombre e ID. Una vez que Adapty lo cree, podrás seleccionarlo en el desplegable. - **Screenshot**: Sube una captura de pantalla de la compra in-app que muestre claramente el artículo o servicio ofrecido. Esta captura se usa únicamente para la revisión del App Store y no se muestra en la tienda. Consulta los requisitos de tamaño y formato de la captura [aquí](https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications/). :::warning Si es tu primer producto para esta app, debes enviarlo manualmente para revisión en App Store Connect. Esto no será necesario más adelante. Una vez finalizada la revisión, el estado del producto en Adapty se actualizará automáticamente. ::: </TabItem> <TabItem value="Google Play" label="Google Play" default> - **Base Product ID**: Crea un ID único y permanente para el producto. - **Subscription**: Selecciona un grupo de suscripción existente que hayas creado en Google Play Console o haz clic en **Create new Product Group** y establece su nombre e ID. Una vez que Adapty lo cree, podrás seleccionarlo en el desplegable. </TabItem> </Tabs> 6. Para iOS, configura la oferta introductoria — prueba gratuita — seleccionando su **Free duration** en el desplegable. Para esta configuración inicial, puedes añadir una prueba gratuita introductoria. Una vez que el producto principal sea aprobado por las stores, podrás [añadir más ofertas](offers) (p. ej., promocionales, de recuperación) vinculando sus IDs existentes desde la consola de tu store. :::important Las ofertas introductorias no se sincronizan automáticamente con Google Play. A diferencia del App Store, Google Play no tiene un tipo de "oferta introductoria" independiente: las pruebas gratuitas y las ofertas con descuento se configuran como **ofertas** en un plan base. [Crea la oferta en Google Play Console y vincúlala a tu producto de Adapty](google-play-offers). ::: </TabItem> <TabItem value="products-in-stores" label="Products in stores already"> 3. Selecciona **Connect an existing store product**. 4. Añade los detalles del producto: - **Product name**: El nombre visible solo para ti en el Adapty Dashboard. - **Access level ID**: El identificador único que determina qué funciones se desbloquean tras la compra. Si todos los usuarios de pago en tu app acceden a las mismas funciones, puedes usar el nivel de acceso predeterminado: `premium`. Para configuraciones más complejas, crea [niveles de acceso](access-level) adicionales. - **Subscription duration**: Selecciona la duración de la suscripción de la lista. - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**: La duración de la suscripción. - **Lifetime**: Usa un período vitalicio para los productos que desbloquean las funciones premium de la app para siempre. - **Non-Subscriptions**: Para los productos que no son suscripciones y por tanto no tienen duración, usa non-subscriptions. Se pueden usar para desbloquear funciones adicionales, productos consumibles, etc. - **Consumables**: Los artículos consumibles se pueden comprar varias veces. Se consumen durante el ciclo de vida de la aplicación. Ejemplos son la moneda del juego y los extras. Ten en cuenta que los productos consumibles no afectan a los niveles de acceso. - **Price (USD)**: El precio del producto en USD. Si tu producto ya está en la store, este valor no afectará a su precio real en la tienda; puedes seleccionar cualquier valor de la lista. Más adelante, puedes [personalizar los precios para diferentes regiones](edit-product#set-country-specific-prices) directamente en el Adapty Dashboard. <img src={require('./img/product-info.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <br /> 5. Añade los detalles de la store. Elige tu store: <Tabs> <TabItem value="App Store" label="App Store" default> - **App Store Product ID**: El identificador único utilizado para acceder a tu producto en los dispositivos. Si no puedes encontrarlo, asegúrate de que el ID sea correcto y pertenezca a la app correcta. </TabItem> <TabItem value="Google Play" label="Google Play" default> - **Google Play Product ID**: El identificador del producto en la Play Store. Selecciónalo de la lista de IDs de producto existentes. Si no puedes encontrarlo, asegúrate de que el ID sea correcto y pertenezca a la app correcta. - **Base plan ID**: El ID que define el plan base del producto en la Play Store. - **Legacy fallback product**: Un producto de respaldo que se utiliza exclusivamente para apps que usan versiones antiguas del SDK de Adapty (versiones 2.5 e inferiores). Especifica el valor en el siguiente formato: `<subscription_id>:<base_plan_id>`. :::important Las ofertas introductorias no se sincronizan automáticamente con Google Play. A diferencia del App Store, Google Play no tiene un tipo de "oferta introductoria" independiente: las pruebas gratuitas y las ofertas con descuento se configuran como **ofertas** en un plan base. [Crea la oferta en Google Play Console y vincúlala a tu producto de Adapty](google-play-offers). ::: <details> <summary>Haz clic aquí para saber dónde encontrar el Product ID y el Base plan ID de Google Play.</summary> 1. Ve a **Monetize with Play > Products > Subscriptions** en tu cuenta de [Google Play Console](https://play.google.com/console/developers/android/app). 2. Abre la **Subscription** correspondiente a la compra. 3. Verás el Product ID en la sección **Subscription details** y el Base plan ID en la columna **ID and duration** de la sección **Base plans and offers**. <img src={require('./img/play-store-id.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </details> </TabItem> <TabItem value="Stripe" label="Stripe" default> - **Stripe Product ID**: El identificador único del producto en Stripe. - **Stripe Price ID**: El identificador único de Stripe para el precio asociado al producto. <details> <summary>Haz clic aquí para saber dónde encontrar el Product ID y el Price ID de Stripe.</summary> 1. Ve a tu [catálogo de productos](https://dashboard.stripe.com/products?active=true) en Stripe. 2. Abre el producto que necesitas. 3. Verás: - El Stripe Product ID (con formato `prod_...`) en la esquina superior derecha. - El Stripe Price ID (con formato `price_...`) en la columna **API ID** de la sección **Pricing**. <img src={require('./img/product-stripe.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </details> </TabItem> <TabItem value="Paddle" label="Paddle" default> - **Paddle Product ID**: El identificador único del producto en Paddle. - **Paddle Price ID**: El identificador único de Paddle para el precio asociado al producto. <details> <summary>Haz clic aquí para saber dónde encontrar el Product ID y el Price ID de Paddle.</summary> 1. Ve a tu [catálogo de productos](https://vendors.paddle.com/products-v2) en Paddle. 2. Abre el producto que necesitas. 3. Verás: - El Paddle Product ID (con formato `pro_...`) en la sección **Additional details**. - El Paddle Price ID (con formato `pri_...`) en la columna **ID** de la sección **Prices**. <img src={require('./img/paddle-product-price.webp').default} style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </details> </TabItem> <TabItem value="Custom" label="Custom store" default> Puedes seleccionar una tienda personalizada existente o añadir una nueva y asociarle un producto. Ten en cuenta que Adapty solo hace seguimiento de transacciones del App Store, Google Play y Stripe. Para tiendas personalizadas, deberás enviar las transacciones mediante la API server-side de Adapty con el método [Set transaction](api-adapty/operations/setTransaction). </TabItem> </Tabs> 6. Si lo necesitas, puedes [crear ofertas](create-offer) para el producto. Para añadir ofertas, haz clic en **Yes, add offers**. De lo contrario, haz clic en **No, thanks**. Tu producto aparecerá en la lista de productos. <img src={require('./img/created-product.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </TabItem> </Tabs> ## Próximos pasos \{#next-steps\} Una vez que hayas añadido tus productos a Adapty, puedes pasar a [configurar paywalls](quickstart-paywalls), ya que es la única forma de empezar a venderlos. --- # File: quickstart-paywalls --- --- title: "Activar compras" description: "Añade un flow o paywall en Adapty para mostrar tus productos y luego asócialo a un placement." --- :::info Para continuar con esta guía, asegúrate de haber completado la [integración del store](integrate-payments) y de haber creado al menos un producto, tal como se describe en la guía anterior sobre [cómo agregar productos](quickstart-products). ::: Ahora que tienes productos, necesitas una forma de mostrárselos a los usuarios. Adapty te ofrece tres opciones: - **Flow Builder (recomendado)**: Editor visual sin código para todo el proceso de compra. El SDK de Adapty renderiza el resultado de forma nativa, por lo que no hay que escribir código de UI. - **Paywall manual**: Tú creas un paywall, le asignas productos y renderizas la UI en el código de la app. - **Adapty Paywall Builder (Legacy)**: Editor de paywalls sin código. Ambas opciones terminan igual: lo que hayas creado lo asocias a un [placement](placements). El placement es lo que tu app llama en tiempo de ejecución para obtener el contenido adecuado para cada usuario. <Tabs groupId="purchase-setup" queryString> <TabItem value="flow-builder" label="Use the Flow Builder" default> :::important El Flow Builder actualmente es compatible con iOS, Android, React Native, Flutter y Capacitor SDK v4 o superior. Próximamente estará disponible para otras plataformas. ::: Un flow es una o más pantallas con productos integrados directamente. Lo diseñas en el [Flow Builder](adapty-flow-builder) — sin necesidad de código. El SDK de Adapty renderiza los flows de forma nativa en cada plataforma. Tu app llama a `getFlow`, y el SDK presenta las pantallas, gestiona las compras e informa de los eventos. Sin código de interfaz adicional, sin ningún paywall que mantener por separado. ## 1. Crea el flow \{#1-build-the-flow\} 1. Ve a [**Flows**](https://app.adapty.io/flows) en el menú principal de Adapty. 2. Haz clic en **Create flow** y diseña tu flow. Aprende más sobre el [Adapty Flow Builder](adapty-flow-builder). Las guías de plantillas a continuación recorren los patrones más comunes paso a paso: <CustomDocCardList ids={['basic-paywall-screen', 'show-plans-bottom-sheet', 'paywall-with-tabs', 'paywall-features-per-product', 'onboarding-flow-tutorial']} /> Una vez que tu flow esté guardado y publicado, pasa a conectarlo a un placement. :::warning ¡No olvides publicar el flow! Si no lo publicas, no podrás añadirlo a un placement. ::: ### 2. Añade el flow a un placement Crea un <InlineTooltip tooltip="placement">Un placement es un punto concreto de tu app donde muestras un flow, paywall, onboarding o prueba A/B. Los placements te permiten dirigirte a [audiencias](audience) específicas con tu contenido. Más información sobre [placements](placements).</InlineTooltip> para que tu app pueda solicitar el flow en tiempo de ejecución. Empecemos por el más esencial: el placement de onboarding. Más adelante, puedes añadir más [placements con significado](choose-meaningful-placements) a lo largo del recorrido del usuario. 1. Ve a [**Placements**](https://app.adapty.io/placements) en el menú principal de Adapty y cambia a la pestaña **Flows**. 2. Haz clic en **Create placement**. 3. Introduce un **Placement name** (por ejemplo, `main` u `onboarding`). Este es un identificador interno en el Adapty Dashboard. 4. Introduce un **Placement ID**. Usarás este ID en el SDK de Adapty para cargar el flow del placement. 5. Haz clic en **Run flow** y elige el flow que acabas de crear. 6. Haz clic en **Save & publish**. En el código de tu app solo tienes que escribir los IDs de placement. Todo lo demás — qué flow se ejecuta, qué productos vende, cómo se ve — se configura en el Adapty Dashboard y puede cambiarse en cualquier momento sin actualizar la app. :::tip Adapty te permite mostrar diferentes flows a distintos grupos de usuarios y analizar el rendimiento. Más información sobre [audiencias](audience) y [pruebas A/B](ab-tests). ::: </TabItem> <TabItem value="manual-paywall" label="Implement paywall manually"> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/e4o7Z2tUGL8?si=ipwbW3VVN0fIg0R0" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Un paywall es un contenedor configurado remotamente para uno o más productos. Adapty sirve la lista de productos y un payload JSON de [Remote Config](customize-paywall-with-remote-config) opcional — tu código de app los lee y dibuja la interfaz. :::tip ¿Configurando Adapty de forma programática? Puedes completar este paso usando el [CLI para desarrolladores](developer-cli-quickstart). ::: ### 1. Crea un paywall \{#1-create-a-paywall\} 1. Ve a [**Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty. 2. Haz clic en **Create paywall**. 3. Introduce un **Paywall name**. Es el identificador interno en el Adapty Dashboard. 4. Haz clic en **Add product** y elige los productos que quieres mostrar en el paywall. 5. (Opcional) Abre la pestaña **Remote config** y añade el payload JSON que necesite tu app (títulos, textos, feature flags). Consulta [Diseña un paywall con Remote Config](customize-paywall-with-remote-config) para más detalles. 6. Haz clic en **Create as a draft** y publícalo cuando esté listo. <img src="/assets/shared/img/quickstart-paywall.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Renderizarás este paywall en el código de tu app. <InlineTooltip tooltip="implementar paywalls manualmente">Sigue la guía para tu plataforma: [iOS](ios-implement-paywalls-manually), [Android](android-implement-paywalls-manually), [React Native](react-native-implement-paywalls-manually), [Flutter](flutter-implement-paywalls-manually), [Unity](unity-implement-paywalls-manually).</InlineTooltip> ### 2. Añade el paywall a un placement Crea un <InlineTooltip tooltip="placement">Un placement es un punto específico de tu app donde muestras un flow, paywall, onboarding o prueba A/B. Los placements te permiten dirigir contenido a [audiencias](audience) concretas. Más información sobre [placements](placements).</InlineTooltip> para que tu app pueda solicitar el paywall en tiempo de ejecución. Empecemos con el más básico: el placement de onboarding. Más adelante podrás añadir más [placements con significado](choose-meaningful-placements) a lo largo del journey del usuario. 1. Ve a [**Placements**](https://app.adapty.io/placements) en el menú principal de Adapty y cambia a la pestaña **Paywalls**. 2. Haz clic en **Create placement**. 3. Introduce un **Placement name** (por ejemplo, `main` u `onboarding`). Es un identificador interno en el Adapty Dashboard. 4. Introduce un **Placement ID**. Usarás este ID en el SDK de Adapty para cargar el paywall del placement. 5. Haz clic en **Run paywall** y elige el paywall que acabas de crear. 6. Haz clic en **Save & publish**. <img src="/assets/shared/img/add-placement.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> En el código de tu app solo tienes que escribir los IDs de los placements. Todo lo demás — qué paywall se muestra, qué productos vende, el Remote Config — se configura en el Adapty Dashboard y puede cambiarse en cualquier momento sin actualizar la app. :::tip Adapty te permite mostrar diferentes paywalls a distintos grupos de usuarios y analizar el rendimiento. Más información sobre [audiencias](audience) y [pruebas A/B](ab-tests). ::: </TabItem> <TabItem value="paywall-builder" label="Adapty Paywall Builder (Legacy)"> Un paywall creado en el [Paywall Builder](adapty-paywall-builder) es una pantalla sin código con productos integrados directamente. El SDK de Adapty lo renderiza de forma nativa, por lo que no hay que escribir código de UI. :::warning El Paywall Builder sigue siendo funcional, pero Adapty ya no añade funciones ni publica actualizaciones para él. Para nuevos proyectos, usa el [Flow Builder](adapty-flow-builder) en su lugar. ::: ### 1. Construye el paywall \{#build-the-paywall\} 1. Ve a [**Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty. 2. Haz clic en **Create paywall**. 3. Introduce un **Paywall name**. Este es un identificador interno en el Adapty Dashboard. 4. Haz clic en **Add product** y elige los productos que quieres mostrar en el paywall. 5. Abre la pestaña **Builder & Generator**. Crea un paywall desde una plantilla o genéralo usando IA. 6. Activa el interruptor **Show on device** para que el SDK pueda renderizarlo. ### 2. Añade el paywall a un placement Crea un <InlineTooltip tooltip="placement">Un placement es un punto específico de tu app donde se muestra un flow, paywall, onboarding o prueba A/B. Los placements te permiten dirigirte a [audiencias](audience) concretas con tu contenido. Más información sobre los [placements](placements).</InlineTooltip> para que tu app pueda solicitar el paywall en tiempo de ejecución. 1. Ve a [**Placements**](https://app.adapty.io/placements) en el menú principal de Adapty y cambia a la pestaña **Paywalls**. 2. Haz clic en **Create placement**. 3. Introduce un **Placement name** (por ejemplo, `main` u `onboarding`). Este es un identificador interno en el Adapty Dashboard. 4. Introduce un **Placement ID**. Usarás este ID en el SDK de Adapty para cargar el paywall del placement. 5. Haz clic en **Run paywall** y elige el paywall que creaste. 6. Haz clic en **Save & publish**. En el código de tu app solo necesitas hardcodear los IDs de placement. Todo lo demás — qué paywall se ejecuta, qué productos vende, cómo se ve — se configura en el Adapty Dashboard y puede cambiarse en cualquier momento sin actualizar la app. </TabItem> </Tabs> ## Próximos pasos \{#next-steps\} Ya tienes algo que el SDK puede entregar. A continuación, [integra el SDK](quickstart-sdk) en tu app y empieza a obtener el placement. --- # File: quickstart-sdk --- --- title: "Integra el SDK de Adapty en el código de tu app" description: "Integra Adapty con App Store, Google Play, stores personalizadas, Stripe y Paddle." --- Integra el SDK de Adapty en tu app para: - Gestionar compras, validación de recibos y suscripciones sin configuración adicional - Crear y probar paywalls sin actualizar la app - Obtener análisis detallados de compras sin ninguna configuración: cohortes, LTV, churn y análisis de embudo incluidos - Mantener el estado de la suscripción del usuario siempre actualizado entre sesiones y dispositivos - Integrar tu app con servicios de atribución de marketing y análisis con una sola línea de código ## Cómo funciona \{#how-does-it-work\} Para una implementación básica del SDK de Adapty, solo necesitas ocuparte de tres cosas: 1. Instalar e inicializar el SDK. 2. Delegar el manejo de las compras in-app a Adapty. 3. Monitorear el estado de la suscripción en el perfil. Adapty determina el estado, el tipo y la fecha de vencimiento de la suscripción; el SDK simplemente consume esa información. El orden y los detalles pueden variar de una app a otra, pero básicamente eso es todo. ## Empezar \{#get-started\} Elige tu plataforma y comienza: **iOS** - **[Inicio rápido del SDK](ios-sdk-overview)** - **[Apps de ejemplo](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples)** **Android** - **[Inicio rápido del SDK](android-sdk-overview)** - **[App de ejemplo](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app)** **React Native** - **[Inicio rápido del SDK](react-native-sdk-overview)** - **[Apps de ejemplo](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/)** **Flutter** - **[Inicio rápido del SDK](flutter-sdk-overview)** - **[App de ejemplo](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example)** **Unity** - **[Inicio rápido del SDK](unity-sdk-overview)** - **[App de ejemplo](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets)** **Capacitor** - **[Inicio rápido del SDK](capacitor-sdk-overview)** - **[Apps de ejemplo](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples)** **Kotlin Multiplatform**: - **[Inicio rápido del SDK](kmp-sdk-overview)** - **[App de ejemplo](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example)** ## Próximos pasos \{#next-steps\} Una vez que hayas configurado el SDK de Adapty en el código de la app, puedes pasar a [probar la implementación](quickstart-test). --- # File: quickstart-test --- --- title: "Prueba tu integración con Adapty" description: "Verifica rápidamente tu integración con Adapty probando la activación del SDK, la obtención de paywalls y las compras in-app en App Store, Google Play, Stripe y Paddle." --- ¡Todo listo! Ahora asegúrate de que tu integración funciona como se espera y de que puedes ver tus compras en el Adapty Dashboard. Realizar una compra de prueba es la mejor forma de verificar que tu integración funciona de extremo a extremo. Empieza con una compra in-app y luego valida los resultados. ## 1. Prueba las compras in-app \{#1-test-in-app-purchases\} Sigue la guía según tu store o plataforma de pago. ### App Store \{#app-store\} Te recomendamos usar una cuenta de prueba (Sandbox Apple ID) y realizar las pruebas en un dispositivo real. Para conocer todos los pasos de prueba en detalle, consulta el artículo sobre [pruebas en el Sandbox de App Store](test-purchases-in-sandbox). :::warning Realiza las pruebas en un dispositivo real para obtener los resultados más fiables. Opcionalmente puedes usar el simulador, pero no lo recomendamos ya que es menos confiable. ::: ### Google Play Store \{#google-play-store\} Crea un usuario de prueba y prueba tu app en un dispositivo real. Para conocer todos los pasos de prueba en detalle, consulta el artículo sobre [pruebas en Google Play Store](testing-on-android). :::note Google [recomienda](https://support.google.com/googleplay/android-developer/answer/14316361) usar un dispositivo real para las pruebas. Si decides usar un emulador, asegúrate de que tenga Google Play instalado para garantizar que tu app funciona correctamente. ::: ### Stripe \{#stripe\} Para probar compras en Stripe, necesitas conectar Stripe a Adapty usando la clave API del modo de prueba de Stripe. Las transacciones que realices desde el modo de prueba de Stripe se considerarán Sandbox en Adapty. Para conocer todos los pasos de conexión, consulta el [artículo de integración con Stripe](stripe#6-test-your-integration). ### Paddle \{#paddle\} Para probar compras en Paddle, necesitas conectar Paddle a Adapty usando la clave API del entorno de prueba de Paddle. Las transacciones que realices desde el entorno de prueba de Paddle se considerarán Test en Adapty. Para conocer todos los pasos de conexión, consulta el [artículo de integración con Paddle](paddle#4-test-your-integration). ## 2. Valida las compras de prueba \{#2-validate-test-purchases\} Después de realizar una compra de prueba, comprueba si aparece la transacción correspondiente en el [**Event Feed**](https://app.adapty.io/event-feed) del Adapty Dashboard. Si la compra no aparece en el **Event Feed**, significa que Adapty no la está registrando. Más información en la guía detallada sobre [validación de compras de prueba](validate-test-purchases). <img src="/assets/shared/img/test-event-feed.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Próximos pasos \{#next-steps\} ¡Felicidades por completar el onboarding de Adapty! Ya estás listo para hacer crecer tus compras in-app. Prepárate para el lanzamiento en producción: <Button id="release-checklist"> Lista de verificación para el lanzamiento </Button> O puedes continuar con lo siguiente: - **[Pruebas A/B](ab-tests)**: Experimenta con distintos precios, duraciones de suscripción, períodos de prueba y elementos visuales para identificar las combinaciones más efectivas. - **[Analíticas](how-adapty-analytics-works)**: Explora métricas de monetización detalladas para entender el comportamiento de los usuarios y optimizar el rendimiento de los ingresos. - **Integraciones**: Adapty envía [eventos de suscripción](events) a herramientas de analítica y atribución de terceros, como [Amplitude](amplitude), [AppsFlyer](appsflyer), [Adjust](adjust), [Branch](branch), [Mixpanel](mixpanel), [Facebook Ads](facebook-ads), [AppMetrica](appmetrica) y un [Webhook](webhook) personalizado. :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: --- # File: release-checklist --- --- title: "Lista de verificación para el lanzamiento" description: "Sigue la lista de verificación de Adapty para garantizar un proceso de actualización de tu app sin problemas." --- ¡Nos alegra que hayas elegido Adapty! Esperamos que la implementación haya ido bien. Esta guía te llevará paso a paso para asegurarte de que tu app esté lista para publicarse en los stores y de que el flujo de monetización funcione correctamente. ## Elementos esenciales antes del lanzamiento \{#pre-flight-essentials\} Lo que necesitas antes de empezar la validación: - Un dispositivo real con una cuenta sandbox - Acceso al Adapty Dashboard - Acceso a App Store Connect / Google Play Console :::note Aunque las compras sandbox pueden ejecutarse en simuladores, necesitas dispositivos reales para probar todos los flujos, incluidos los diálogos de pago y las solicitudes biométricas. ::: <Button id="test-purchases-in-sandbox"> Guía de pruebas para App Store </Button> <Button id="testing-on-android"> Guía de pruebas para Google Play </Button> ## Validaciones universales \{#universal-validations\} - [ ] **Conexión con el store**: Asegúrate de haber conectado Adapty a App Store y/o Google Play: - [ ] [App Store](initial_ios) - [ ] [Google Play](initial-android) - [ ] **Entrega de eventos de suscripción**: Confirma que las notificaciones del servidor están configuradas: - [ ] [Notificaciones del servidor de App Store](enable-app-store-server-notifications) - [ ] [Notificaciones en tiempo real para desarrolladores (RTDN)](enable-real-time-developer-notifications-rtdn) - [ ] **Identificación de perfiles**: Valida la lógica de identificación de usuarios y asegúrate de que las compras se asocien al perfil correcto: - [ ] [Comprueba que la lógica de identificación en el código de tu app coincide con tu caso de uso](ios-quickstart-identify) - [ ] [Asegúrate de entender la lógica de padre/heredero para compartir el acceso de pago entre perfiles de usuario](sharing-paid-access-between-user-accounts) - [ ] **Ofertas**: Si tienes ofertas promocionales de App Store en la app, asegúrate de haber [añadido tu clave de compra in-app](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers) tanto en el campo principal como en la sección **App Store promotional offers**. - [ ] **Recopilación de datos**: Garantiza el cumplimiento de la privacidad: - [ ] Si necesitas cumplir con normativas de privacidad como GDPR o CCPA, o tu app está destinada a niños, controla si [habilitas la recopilación y el uso compartido del IDFA e IP](sdk-installation-ios#data-policies). - [ ] Si tu app usa AppTrackingTransparency, asegúrate de [enviar el estado de autorización a Adapty](ios-deal-with-att). - [ ] **Etiquetas de privacidad**: [Más información](apple-app-privacy) sobre los datos que recopila Adapty y qué indicadores tendrás que configurar para la revisión. ## Validación de compras \{#purchase-validations\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Antes de publicar tu app, asegúrate de que las compras funcionan correctamente y de que tu paywall está listo para la revisión de la store. La forma de validar las compras in-app depende de cómo las hayas implementado: - Muestras un paywall creado en el Adapty Paywall Builder - Has implementado tu propio paywall y usas el método `makePurchase` dentro de él para gestionar las compras - Usas Adapty en modo observador (ya sea con el Adapty Paywall Builder o con tu paywall personalizado) <Tabs groupId="paywall" queryString> <TabItem value="builder" label="Adapty Paywall Builder" default> **Objetivo**: Adapty renderiza el paywall, los usuarios pueden comprar productos, el acceso se desbloquea y el flow de restauración funciona. - [ ] Tu app [muestra el paywall](ios-present-paywalls) desde el mismo placement que vas a publicar. - [ ] El paywall se muestra en pantalla. Si la carga tarda demasiado (por ejemplo, si tú o tus usuarios tenéis una conexión inestable), considera [ajustar tu fetch policy](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder). - [ ] El paywall coincide con la variante esperada (audiencia/idioma si aplica). Puedes [cambiar la prioridad de la audiencia](change-audience-priority) si es necesario. - [ ] Los productos y precios aparecen en el paywall. Ten en cuenta que la API de Apple puede proporcionar precios incorrectos durante las pruebas (especialmente con configuraciones de distintas regiones), así que prioriza probar el flujo de compra en sí sobre la exactitud de los precios, ya que Adapty no afecta a los precios del Store. - [ ] La compra en sandbox se completa correctamente. Se recibe el callback de compra exitosa. - [ ] El acceso se desbloquea y se mantiene. Comprueba que [el acceso de pago se concede según el perfil de Adapty actual](ios-check-subscription-status#connect-profile-with-paywall-logic). - [ ] Tras la compra, el perfil de Adapty tiene un nivel de acceso activo. - [ ] Las funciones de pago se desbloquean cuando el perfil contiene ese nivel de acceso (no solo en el callback de compra). - [ ] La restauración de compras funciona. Cuando reinstales la app o la instales en un dispositivo nuevo, la restauración automática de compras funciona según la configuración de [Compartir acceso de pago](sharing-paid-access-between-user-accounts). Si no tienes autenticación de backend, las compras se restauran automáticamente independientemente de la configuración. En otros casos, asegúrate de que los usuarios puedan restaurar sus compras tras reinstalar la app. - [ ] Requisitos de revisión del Store: - [ ] El botón **Restore purchases** está en el paywall. Puedes añadirlo en el Paywall Builder y procesará las restauraciones de compras automáticamente al pulsarlo. - [ ] Los Términos de Uso y la Política de Privacidad son accesibles desde la pantalla del paywall, y al hacer clic en estos enlaces se abren en un navegador. </TabItem> <TabItem value="makepurchase" label="Paywall personalizado (makePurchase)" default> **Objetivo**: Tú renderizas la UI; Adapty gestiona las compras, actualizaciones de perfil y restauraciones. - [ ] Los IDs de productos no están hardcodeados en el código de tu app. Solo hardcodeas IDs de [placement](placements). - [ ] Tu app [obtiene los productos](fetch-paywalls-and-products) desde el mismo placement que vas a publicar. - [ ] La lista de productos carga correctamente. Si la carga tarda demasiado (por ejemplo, si tú o tus usuarios tenéis una conexión inestable), considera [ajustar tu fetch policy](fetch-paywalls-and-products#fetch-paywall-information). - [ ] Los productos obtenidos coinciden con la variante esperada (audiencia/idioma, si aplica). Puedes [cambiar la prioridad de la audiencia](change-audience-priority) si es necesario. - [ ] Los productos y precios aparecen en el paywall. Ten en cuenta que la API de Apple puede mostrar precios incorrectos durante las pruebas (especialmente con distintas configuraciones de región), así que prioriza probar el flujo de compra en sí y no la exactitud de los precios, ya que Adapty no influye en los precios del Store. - [ ] La compra en sandbox con [makePurchase](making-purchases) se completa correctamente: - [ ] El resultado de compra exitosa se gestiona correctamente. - [ ] Los resultados pendientes, fallidos o cancelados se manejan sin errores. - [ ] Si [usas un Remote Config](present-remote-config-paywalls), sus valores se cargan correctamente en tu paywall. - [ ] Cuando se muestra un paywall, se llama al método [`logShowFlow` (iOS SDK v4+) / `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events). - [ ] La compra en sandbox se completa correctamente. Se recibe el callback de compra exitosa. - [ ] El acceso se desbloquea y persiste. Confirma que [el acceso de pago se concede según el perfil de Adapty actual](ios-check-subscription-status#connect-profile-with-paywall-logic). - [ ] Tras la compra, el perfil de Adapty tiene un nivel de acceso activo. - [ ] Las funciones de pago se desbloquean cuando el perfil contiene ese nivel de acceso (no solo en el callback de compra). - [ ] La restauración de compras funciona. Al reinstalar la app o instalarla en un nuevo dispositivo, la restauración automática de compras funciona según la configuración de [Compartir acceso de pago](sharing-paid-access-between-user-accounts). Si no tienes autenticación en el backend, las compras se restauran automáticamente independientemente de la configuración. En otros casos, asegúrate de que los usuarios puedan restaurar sus compras después de reinstalar la app. - [ ] Requisitos para la revisión del Store: - [ ] El botón **Restore purchases** es accesible y [gestiona las restauraciones](restore-purchase). - [ ] Los Términos de uso y la Política de privacidad son accesibles desde la pantalla del paywall, y al hacer clic en esos enlaces se abren en el navegador. </TabItem> <TabItem value="observer" label="Modo observador"> **Objetivo**: Tú gestionas las compras, actualizaciones de perfil y restauraciones; Adapty recibe el reporte de transacciones. - [ ] **Tu app completa las compras usando tu propio flujo de compra** (StoreKit / BillingClient / backend): - [ ] La compra en sandbox se completa correctamente en la interfaz de la store. - [ ] Los resultados pendientes, fallidos o cancelados se gestionan correctamente en tu app. - [ ] **Las transacciones se reportan a Adapty**. - [ ] El modo observador está [habilitado en el código de tu app](implement-observer-mode). - [ ] La compra aparece en el Event Feed de Adapty. - [ ] Las renovaciones, cancelaciones y reembolsos se reflejan con el tiempo (según corresponda). - [ ] **Se registran las vistas de paywall**. El método [`logShowFlow` (iOS SDK v4+) / `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events) se llama cuando se muestra un paywall. - [ ] **La restauración de compras funciona con tu implementación**. Al reinstalar la app o cambiar de dispositivo, el acceso se restaura correctamente. - [ ] **Requisitos de revisión de la store**: - [ ] La acción **Restaurar compras** es accesible y activa tu flujo de restauración. - [ ] Los Términos de uso y la Política de privacidad son accesibles desde el paywall o la pantalla de compra y se abren en un navegador. </TabItem> </Tabs> Si tienes alguna pregunta sobre la integración del SDK de Adapty, usa el chatbot de IA en la parte inferior derecha o contáctanos en [support@adapty.io](mailto:support@adapty.io). --- # File: observer-vs-full-mode --- --- title: "Modo Observer" description: "Compara el modo Observer y el modo completo en Adapty para suscripciones." --- Adapty es una plataforma de compras in-app potente y flexible diseñada para impulsar tus ingresos y tu base de suscriptores. Con funciones como paywalls personalizables adaptados a segmentos de usuarios específicos, pruebas A/B de precios, duración, períodos de prueba y elementos visuales, además de herramientas analíticas completas para la monetización de apps e integraciones con terceros, Adapty potencia tu estrategia de crecimiento. Sin embargo, si ya tienes tu propia infraestructura de compras y no estás preparado para migrar al sistema de Adapty, puedes explorar el modo Observer de Adapty. Este modo limitado prescinde del uso de paywalls de Adapty, de su segmentación por audiencias de usuarios, de la gestión de suscripciones (incluidas renovaciones y reintentos de cobro), y se centra únicamente en la analítica. A pesar de sus limitaciones, el modo Observer sigue ofreciendo sólidas capacidades analíticas, como integración con sistemas de atribución, analítica avanzada, mensajería y perfiles CRM. Ambos modos tienen el mismo precio y requieren que actualices tu app móvil, por lo que la elección se reduce básicamente a migrar a la infraestructura de Adapty para obtener toda la funcionalidad, o mantener tu infraestructura actual y obtener únicamente integraciones con terceros y capacidades analíticas. | Funcionalidad | Modo Observer | Modo completo | |-------------|-------------|---------| | **Analítica completa** | ✅ | ✅ | | **Integraciones con terceros** | ✅ | ✅ | | **Respuesta a eventos de compra para dar/restringir acceso de pago a tus usuarios** | ❌ | ✅ | | **Responsable del mantenimiento de la infraestructura de compras** | Tú | Adapty | | **Pruebas A/B** | <p>Viable, pero requiere una cantidad significativa de código y configuración adicionales, más que en el modo completo.</p> | ✅ | | **Tiempo de implementación** | <p>Para analítica e integraciones: menos de una hora</p><p>Con pruebas A/B: hasta una semana con pruebas exhaustivas</p> | Varias horas | ## Cómo funciona el modo Observer \{#how-observer-mode-works\} En el modo Observer, tú reportas las nuevas transacciones de Apple/Google al SDK de Adapty, y el SDK las reenvía al backend de Adapty. Eres responsable de gestionar el acceso al contenido de pago en tu app, completar las transacciones, gestionar las renovaciones, resolver los problemas de facturación, etc. ## Cómo configurar el modo Observer \{#how-to-set-up-observer-mode\} 1. Configura la integración inicial de Adapty [con Google Play](initial-android) y [con App Store](initial_ios). 2. Actívalo al configurar el SDK de Adapty estableciendo el parámetro `observerMode` en `true`. Sigue las instrucciones de configuración para [iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk), [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform#activate-adapty-sdk) y [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 3. [Reporta las transacciones](report-transactions-observer-mode) desde tu infraestructura de compras existente a Adapty para iOS y frameworks multiplataforma basados en iOS. 4. (opcional) Si quieres usar integraciones con terceros, configúralas como se describe en el artículo [Configurar integraciones con terceros](configuration). :::warning Al operar en modo Observer, el SDK de Adapty no finaliza las transacciones, así que asegúrate de gestionar este aspecto tú mismo. ::: ## Cómo usar paywalls y pruebas A/B en el modo Observer \{#how-to-use-paywalls-and-ab-tests-in-observer-mode\} En el modo Observer, el SDK de Adapty no puede determinar el origen de las compras, ya que estas se realizan en tu propia infraestructura. Por lo tanto, si pretendes usar paywalls y/o pruebas A/B en el modo Observer, debes asociar la transacción procedente de tu store con el paywall correspondiente en el código de tu app móvil al reportar una transacción. Además, los paywalls diseñados con el Paywall Builder deben mostrarse de una forma especial cuando se usa el modo Observer: - Muestra paywalls en el modo Observer para [iOS](implement-observer-mode) o [Android](android-present-paywall-builder-paywalls-in-observer-mode). - [Asocia paywalls a transacciones de compra](report-transactions-observer-mode) al reportar transacciones en el modo Observer. --- # File: migration-from-revenuecat --- --- title: "Migración desde RevenueCat" description: "Migra de RevenueCat a Adapty con nuestra guía paso a paso." --- Tu plan de migración tiene 5 pasos lógicos y dura una media de 2 horas. El 90 % de todas las migraciones se completan en menos de un día laborable. 1. Aprende las diferencias clave y crea y prepara una cuenta de Adapty _(5 minutos)_; 2. Instala el SDK de Adapty para tu plataforma ([iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity)) en lugar del SDK de RevenueCat _(1 hora)_; 3. Configura las [notificaciones de servidor de Apple App Store](enable-app-store-server-notifications) para Adapty y (opcionalmente) el [reenvío de eventos sin procesar](enable-app-store-server-notifications#raw-events-forwarding) _(5 minutos)_; 4. Prueba y publica la actualización de tu app _(30 minutos)_; 5. (Opcional) Solicita al soporte de RevenueCat los datos históricos en formato CSV _(5 minutos)_; 6. (Opcional) Importa los datos históricos a través del soporte de Adapty _(30 minutos)_. :::info Tus suscriptores migrarán automáticamente Todos los usuarios que alguna vez hayan activado una suscripción pasarán a Adapty automáticamente en cuanto abran la nueva versión de tu app con el SDK de Adapty. La validación del estado de la suscripción y el acceso premium se restaurarán de forma automática. ::: Antes de publicar una nueva versión de tu app con el SDK de Adapty, asegúrate de revisar nuestra [lista de verificación para el lanzamiento](release-checklist). ## Aprende las diferencias clave y crea y prepara una cuenta de Adapty \{#learn-the-core-differences-create-and-prepare-an-adapty-account\} Los SDKs de Adapty y RevenueCat tienen un diseño similar. La mayor diferencia está en el uso de red y la velocidad: el SDK de Adapty está diseñado para proporcionarte información lo más rápido posible cuando la solicitas. Por ejemplo, al pedir un paywall, primero recibes el [Remote Config](customize-paywall-with-remote-config) para precomponer tu onboarding o paywall, y luego solicitas los productos en una petición separada. Los nombres son ligeramente distintos: | RevenueCat | Adapty | | :---------- | :-------------- | | Package | Product | | Offering | Paywall | | Paywall | Paywall Builder | | Entitlement | Access level | Adapty tiene el concepto de [placement](placements). Es un lugar lógico dentro de tu app donde el usuario puede realizar una compra. En la mayoría de los casos, tendrás uno o dos placements: - Onboarding (ya que el 80 % de todas las compras se realizan ahí); - General (se muestra en los ajustes o dentro de la app después del onboarding). <img src="/assets/shared/img/2406d97-image.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Instala el SDK de Adapty y reemplaza el SDK de RevenueCat \{#install-adapty-sdk-and-replace-revenuecat-sdk\} Instala el SDK de Adapty para tu plataforma ([iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity)) en tu app. Necesitas reemplazar algunos métodos del SDK en el lado de la app. Veamos las funciones más comunes y cómo sustituirlas por las del SDK de Adapty. ### Activación del SDK \{#sdk-activation\} Reemplaza `Purchases.configure` con `Adapty.activate`. ### Obtener paywalls (offerings) \{#getting-paywalls-offerings\} Reemplaza `Purchases.shared.getOfferings` con [`Adapty.getPaywall`](fetch-paywalls-and-products#fetch-paywall-information). En Adapty, siempre solicitas el paywall mediante el [placement id](placements). En la práctica, solo obtienes 1 o 2 paywalls como máximo, así que lo hemos diseñado así a propósito para acelerar el SDK y reducir el uso de red. ### Obtener un usuario (perfil del cliente) \{#getting-a-user-customer-profile\} Reemplaza `Purchases.shared.getCustomerInfo` con `Adapty.getProfile`. ### Obtener productos \{#getting-products\} En RevenueCat, usas la siguiente estructura: `Purchases.shared.getOfferings` y luego `self.offering?.availablePackages`. En Adapty, primero solicitas un paywall (ver arriba) para acceder de inmediato al [Remote Config](customize-paywall-with-remote-config) de Adapty, y luego obtienes los productos con [`Adapty.getPaywallProducts`](fetch-paywalls-and-products#fetch-products). ### Realizar una compra \{#making-a-purchase\} Reemplaza `Purchases.shared.purchase` con [`Adapty.makePurchase`](making-purchases#make-purchase). ### Verificar el nivel de acceso (entitlement) \{#checking-access-level-entitlement\} Obtén el perfil del cliente (lee el apartado anterior primero) y luego reemplaza `customerInfo?.entitlements["premium"]?.isActive == true` con [`profile.accessLevels["premium"]?.isActive == true`](subscription-status#retrieving-the-access-level-from-the-server). ### Restaurar una compra \{#restore-purchase\} Reemplaza `Purchases.shared.restorePurchases` con [`Adapty.restorePurchases`](restore-purchase). ### Comprobar si el usuario ha iniciado sesión \{#check-if-the-user-is-logged-in\} Reemplaza `Purchases.shared.isAnonymous` con `if profile.customerUserId == nil`. ### Iniciar sesión con un usuario \{#log-in-user\} Reemplaza `Purchases.shared.logIn` con [`Adapty.identify`](identifying-users#set-customer-user-id-after-configuration). ### Cerrar sesión de un usuario \{#log-out-user\} Reemplaza `Purchases.shared.logOut` con [`Adapty.logout`](identifying-users#logging-out-and-logging-in). ## Cambia las notificaciones del servidor de App Store a Adapty \{#switch-app-store-server-side-notifications-to-adapty\} Lee cómo hacerlo [aquí](migrate-to-adapty-from-another-solutions#changing-apple-server-notifications). ## Prueba y publica una nueva versión de tu app \{#test-and-release-a-new-version-of-your-app\} Si estás leyendo esto, ya has: - [x] Configurado el Adapty Dashboard - [x] Instalado el SDK de Adapty - [x] Reemplazado la lógica del SDK con las funciones de Adapty - [x] Cambiado las notificaciones del servidor de App Store a Adapty y, opcionalmente, activado el reenvío de eventos sin procesar a RevenueCat - [ ] Realizado una compra en sandbox - [ ] Publicado una nueva versión de la app Si has marcado los puntos anteriores, haz una compra de prueba en el Sandbox y luego publica la app. :::info Repasa la [lista de verificación para el lanzamiento](release-checklist). Haz la revisión final con nuestra lista para validar la integración existente o añadir funciones adicionales como integraciones de [atribución](attribution-integration) o [análisis](analytics-integration). ::: ## (Opcional) Exporta tus datos históricos de RevenueCat en formato CSV \{#optional-export-your-revenuecat-historical-data-in-csv-format\} :::warning No te precipites con la importación de datos históricos Deberías esperar al menos una semana después de publicar la versión con el SDK antes de importar los datos históricos. Durante ese tiempo recopilaremos toda la información sobre los precios de compra desde el SDK, por lo que los datos que importes serán más relevantes. ::: Exporta tus datos históricos de RevenueCat en formato CSV siguiendo las instrucciones de la [documentación oficial de RevenueCat](https://www.revenuecat.com/docs/integrations/scheduled-data-exports). ## (Opcional) Solicita al soporte de RevenueCat los tokens de compra de Google \{#optional-ask-revenuecat-support-for-google-purchase-tokens\} Si necesitas importar transacciones de Google Play, contacta con el soporte de RevenueCat para obtener un archivo CSV con los Google Purchase Tokens a través de su [página de soporte](https://app.revenuecat.com/settings/support). El Google Purchase Token es un identificador único que proporciona Google Play para cada transacción, imprescindible para rastrear y verificar las compras con precisión en Adapty. Esta información no se incluye en el archivo de exportación estándar. El archivo contiene las siguientes tres columnas: - `user_id` - `google_purchase_token` - `google_product_id` ## Escríbenos para importar tus datos históricos \{#write-us-to-import-your-historical-data\} Contáctanos a través del chat del sitio web o envíanos un correo a [support@adapty.io](mailto:support@adapty.io) con tus archivos CSV. 1. Envía el archivo CSV que exportaste de RevenueCat directamente a nuestro equipo de soporte. 2. Si vas a importar transacciones de Google Play, incluye el archivo CSV con los Google Purchase Tokens que recibiste del soporte de RevenueCat. 3. Indícanos qué ID de usuario debe usarse como Customer User ID (el identificador principal de usuario en Adapty): `rc_original_app_user_id` o `rc_last_seen_app_user_id_alias`. Nuestro equipo de soporte importará tus transacciones a Adapty. Se importarán los siguientes datos para cada transacción: | Parámetro | Descripción | | ----------------------------- | ------------------------------------------------------------ | | user_id | Customer User ID, el identificador principal de tu usuario en Adapty y en tu sistema. | | apple_original_transaction_id | Para cadenas de suscripciones, esta es la fecha de compra de la transacción original, vinculada por `store_original_transaction_id`. | | google_product_id | El ID del producto en Google Play Store. | | google_purchase_token | Un identificador único proporcionado por Google Play para cada transacción, necesario para la validación. | | country | El país del usuario. | | created_at | La fecha y hora de creación del usuario. | | subscription_expiration_date | La fecha y hora en que expira la suscripción. | | email | El correo electrónico del usuario final. | | phone_number | El número de teléfono del usuario final. | | idfa | El Identificador para Anunciantes (IDFA), asignado por Apple al dispositivo de un usuario. | | idfv | El Identificador para Proveedores (IDFV), un código asignado a todas las apps de un mismo desarrollador y compartido entre esas apps en un dispositivo. | | advertising_id | Un identificador único proporcionado por el sistema operativo Android que los anunciantes pueden usar para el seguimiento. | | attribution_channel | El nombre del canal de marketing. | | attribution_campaign | El nombre de la campaña de marketing. | | attribution_ad_group | El grupo de anuncios de atribución. | | attribution_ad_set | El conjunto de anuncios de atribución. | | attribution_creative | La palabra clave creativa de atribución. | Además, se importarán los identificadores de integración para las siguientes integraciones: Amplitude, Mixpanel, AppsFlyer, Adjust y FacebookAds. ## Preguntas frecuentes \{#faq\} ### Instalé el SDK de Adapty correctamente y publiqué una nueva versión de la app. ¿Qué pasará con mis suscriptores existentes que no actualicen a la versión con el SDK de Adapty? \{#i-successfully-installed-adapty-sdk-and-released-a-new-app-version-with-it-what-will-happen-to-my-legacy-subscribers-who-did-not-update-to-a-version-with-adapty-sdk\} La mayoría de los usuarios cargan sus teléfonos por la noche, que es cuando App Store suele actualizar automáticamente todas sus apps, por lo que no debería ser un problema. Puede que quede un pequeño número de suscriptores de pago que no hayan actualizado, pero seguirán teniendo acceso al contenido premium. No tienes que preocuparte por ello ni forzarlos a actualizar. ### ¿Necesito exportar mis datos históricos de RevenueCat lo antes posible o los perderé? \{#do-i-need-to-export-my-historical-data-from-revenuecat-as-quickly-as-possible-or-will-i-lose-it\} No hace falta hacerlo con prisa; primero publica la versión con el SDK de Adapty y luego compártenos tus datos históricos. Restauraremos el historial de pagos de tus usuarios y completaremos los [perfiles](profiles-crm) y los [gráficos](charts). ### Uso MMP (AppsFlyer, Adjust, etc.) y herramientas de análisis (Mixpanel, Amplitude, etc.). ¿Cómo me aseguro de que todo funcionará correctamente? \{#i-use-mmp-appsflyer-adjust-etc-and-analytics-mixpanel-amplitude-etc-how-do-i-make-sure-that-everything-will-work\} Primero tienes que pasarnos los IDs de esos servicios de terceros a través de nuestro SDK para que podamos enviarles datos. Lee la guía de [integración de atribución](attribution-integration) y de [integración de análisis](analytics-integration). Para los datos históricos y los usuarios existentes, **asegúrate de pasarnos esos IDs a partir de los datos que exportaste de RevenueCat.** --- # File: migration-from-superwall --- --- title: "Migración desde Superwall" description: "Migra de Superwall a Adapty con una guía paso a paso que mapea cada llamada al SDK y cada concepto." --- La mayoría de las migraciones desde Superwall a Adapty llevan unas dos horas. Cambias el SDK, apuntas las notificaciones del servidor de la store a Adapty y publicas una nueva versión de la app. Tus suscriptores de pago conservan su acceso — Adapty lo restaura desde los recibos de App Store y Google Play en el primer arranque. :::info Tus suscriptores migrarán automáticamente Todos los usuarios que alguna vez hayan activado una suscripción pasan a Adapty en cuanto abran una nueva versión de tu app con el SDK de Adapty. La validación del estado de la suscripción y el acceso premium se restauran automáticamente. ::: ## Cómo está organizada esta guía \{#how-this-guide-is-organized\} La migración tiene seis pasos: 1. [Mapea los conceptos de Superwall a Adapty](#map-your-superwall-concepts-to-adapty) 2. [Instala el SDK de Adapty](#install-the-adapty-sdk) 3. [Reemplaza las llamadas al SDK](#replace-sdk-calls) 4. [Cambia las notificaciones del servidor de App Store y Google Play](#switch-app-store-and-google-play-server-notifications) 5. [Prueba y publica](#test-and-release) 6. [(Opcional) Importa datos históricos](#optional-import-historical-data) ## Mapea los conceptos de Superwall a Adapty \{#map-your-superwall-concepts-to-adapty\} La mayoría de los conceptos de Superwall tienen un equivalente directo en Adapty: | Superwall | Adapty | Qué cambia | | :------------------- | :------------------------------------------------ | :--------------------------------------------------------------------------- | | Campaign | [Placement](placements) + [Audience](audience) | La lógica de la campaña se divide en un placement (la ubicación) y una audiencia (la regla). | | Placement | [Placement](placements) | Mismo concepto, mismo nombre. | | Audience filter | [Audience](audience) | Los conjuntos de reglas viven dentro de un placement. | | Entitlement | [Nivel de acceso](access-level) | Identificador con nombre (por ejemplo, `premium`). | | WebView paywall | [Paywall de Paywall Builder](adapty-paywall-builder) | Renderizado por el SDK de Adapty de forma nativa en lugar de un `WKWebView`. | | `PurchaseController` | Integrado | No hay protocolo que implementar — Adapty gestiona las compras. | | Feature gating | Comprobación de [nivel de acceso](access-level) | Comprueba `profile.accessLevels["premium"]?.isActive`. | Hay dos cambios conceptuales que vale la pena tener en cuenta antes de tocar el código: - **Obtener y presentar son pasos separados**: El método `register` de Superwall obtiene el paywall, evalúa la campaña y presenta la interfaz en una sola llamada. Adapty divide estos pasos — obtienes el paywall, su configuración de vista y luego lo presentas. Esto añade unas pocas líneas, pero te permite precargar configuraciones, mostrar un estado de carga personalizado o cancelar la presentación según tu propia lógica. - **El estado de la suscripción es por nivel de acceso**: Superwall expone una sola propiedad publicada `subscriptionStatus`. Adapty devuelve un [`AdaptyProfile`](https://swift.adapty.io/documentation/adapty/adaptyprofile) con niveles de acceso con nombre, de modo que un usuario puede tener los niveles de acceso `sports` y `science` de forma independiente. Para lecturas síncronas, guarda en caché el perfil del `AdaptyDelegate` en lugar de llamar a `getProfile()` en cada carga de vista. ## Instala el SDK de Adapty \{#install-the-adapty-sdk\} Instala el SDK de Adapty para tu plataforma — [iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity) o [Capacitor](sdk-installation-capacitor) — y elimina SuperwallKit de tu proyecto al mismo tiempo. ## Reemplaza las llamadas al SDK \{#replace-sdk-calls\} Revisa cada área de tu integración y sustituye la llamada de Superwall por su equivalente en Adapty. Los enlaces al final de cada subsección cubren los siete SDKs de plataforma — sigue el que corresponda a tu app. ### Inicializa el SDK \{#initialize-the-sdk\} Reemplaza `Superwall.configure` con `Adapty.activate`. Consulta la guía de instalación para tu plataforma — [iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity) o [Capacitor](sdk-installation-capacitor). ### Identifica y desconecta usuarios \{#identify-and-log-out-users\} Reemplaza `Superwall.shared.identify` con `Adapty.identify` y `Superwall.shared.reset` con `Adapty.logout`. Ambos SDKs generan un perfil anónimo en el primer arranque, por lo que estas llamadas solo son necesarias cuando un usuario inicia o cierra sesión. Vuelve a obtener los paywalls después de identificar — el nuevo usuario puede resolverse a una audiencia diferente. Consulta la guía de identificación para tu plataforma — [iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Kotlin Multiplatform](kmp-identifying-users), [Unity](unity-identifying-users) o [Capacitor](capacitor-identifying-users). ### Obtén y presenta un paywall \{#fetch-and-present-a-paywall\} Reemplaza `Superwall.shared.register` con un flujo de dos pasos: obtén el paywall con `Adapty.getPaywall`, carga su configuración de vista con `AdaptyUI.getPaywallConfiguration` y luego preséntalo. Dos diferencias a destacar: - **El feature gating reemplaza el closure `feature:`**: Después de que se cierre el paywall, comprueba el nivel de acceso activo en el perfil devuelto (o en `Adapty.getProfile`) y ramifica desde ahí. - **Los paywalls son renderizados por el SDK**: Superwall renderiza los paywalls dentro de un `WKWebView`. Adapty renderiza los paywalls del Paywall Builder de forma nativa — las fuentes, la información del producto y los botones los dibuja el SDK. Consulta la guía de inicio rápido de paywalls para tu plataforma — [iOS](ios-quickstart-paywalls), [Android](android-quickstart-paywalls), [React Native](react-native-quickstart-paywalls), [Flutter](flutter-quickstart-paywalls), [Kotlin Multiplatform](kmp-quickstart-paywalls), [Unity](unity-quickstart-paywalls) o [Capacitor](capacitor-quickstart-paywalls). ### Comprueba el estado de la suscripción \{#check-subscription-status\} Reemplaza `Superwall.shared.subscriptionStatus` con una comprobación del nivel de acceso con nombre en el perfil: `profile.accessLevels["premium"]?.isActive`. Observa los cambios mediante `AdaptyDelegate.didLoadLatestProfile(_:)` en lugar del patrón de propiedad `@Published`, y guarda el perfil en caché en tu lado para lecturas síncronas. Consulta la guía de estado de suscripción para tu plataforma — [iOS](ios-check-subscription-status), [Android](android-check-subscription-status), [React Native](react-native-check-subscription-status), [Flutter](flutter-check-subscription-status), [Kotlin Multiplatform](kmp-check-subscription-status), [Unity](unity-check-subscription-status) o [Capacitor](capacitor-check-subscription-status). ### Gestiona compras y restauraciones \{#handle-purchases-and-restores\} Con el Paywall Builder, ambos SDKs procesan las compras automáticamente dentro de la interfaz del paywall — **puedes saltarte este paso**. Para paywalls personalizados, Superwall requiere una implementación de `PurchaseController`. Adapty no: reemplaza `PurchaseController.purchase` con `Adapty.makePurchase` y `PurchaseController.restorePurchases` con `Adapty.restorePurchases`. El SDK gestiona la validación por su cuenta. Consulta la guía de inicio rápido de paywall personalizado para tu plataforma — [iOS](ios-quickstart-manual), [Android](android-quickstart-manual), [React Native](react-native-quickstart-manual), [Flutter](flutter-quickstart-manual), [Kotlin Multiplatform](kmp-quickstart-manual), [Unity](unity-quickstart-manual) o [Capacitor](capacitor-quickstart-manual). ### Configura atributos de usuario \{#set-user-attributes\} Reemplaza `Superwall.shared.setUserAttributes` con `Adapty.updateProfile`. Consulta la guía de atributos de usuario para tu plataforma — [iOS](setting-user-attributes), [Android](android-setting-user-attributes), [React Native](react-native-setting-user-attributes), [Flutter](flutter-setting-user-attributes), [Kotlin Multiplatform](kmp-setting-user-attributes), [Unity](unity-setting-user-attributes) o [Capacitor](capacitor-setting-user-attributes). ## Cambia las notificaciones del servidor de App Store y Google Play \{#switch-app-store-and-google-play-server-notifications\} Apunta las notificaciones del servidor de la store a Adapty. Adapty funciona sin ellas, pero las analíticas, las integraciones de terceros y las métricas de pruebas A/B dependen de ellas: - **App Store**: Sigue [Habilitar notificaciones del servidor de App Store](enable-app-store-server-notifications). - **Google Play**: Sigue [Habilitar notificaciones en tiempo real para desarrolladores](enable-real-time-developer-notifications-rtdn). Si quieres ejecutar Superwall y Adapty en paralelo durante el lanzamiento, usa el [reenvío de eventos sin procesar](enable-app-store-server-notifications#raw-events-forwarding) — Adapty reenvía los eventos de la store a Superwall mientras verificas la nueva integración. ## Prueba y publica \{#test-and-release\} Antes de publicar, comprueba cada elemento: - [x] Configurado el Adapty Dashboard (productos, paywalls, placements, niveles de acceso) - [x] Instalado el SDK de Adapty - [x] Reemplazadas las llamadas al SDK de Superwall por sus equivalentes en Adapty - [x] Apuntadas las notificaciones del servidor de App Store y Google Play a Adapty - [ ] Realizada una compra en sandbox - [ ] Enviada una nueva versión de la app Revisa el [checklist de lanzamiento](release-checklist) para una validación final. ## (Opcional) Importa datos históricos \{#optional-import-historical-data\} Superwall no es dueño de tu estado de suscripción — lo son App Store y Google Play. Adapty valida los recibos en el primer arranque, por lo que los usuarios de pago conservan su acceso sin necesidad de ninguna importación. Si quieres que las transacciones históricas queden registradas en las analíticas de Adapty, sigue [Importar datos históricos a Adapty](importing-historical-data-to-adapty). Espera al menos una semana después del lanzamiento del SDK para que tenga tiempo de recopilar precios de compra actualizados. ## Preguntas frecuentes \{#faq\} ### ¿Qué pasa con los suscriptores que no actualizan la app? \{#what-happens-to-subscribers-who-dont-update-the-app\} La mayoría de los usuarios actualizan sus apps automáticamente durante la noche, por lo que la proporción de usuarios en la versión anterior disminuye rápidamente. Los suscriptores en la versión antigua conservan su acceso directamente a través de App Store o Google Play — no es necesario forzar una actualización. ### ¿Las audiencias de mis campañas de Superwall se migran? \{#do-my-superwall-campaign-audiences-carry-over\} No. Los filtros de audiencia de Superwall y las audiencias de Adapty se configuran en dashboards diferentes y usan identificadores distintos. Recrea tu segmentación como [audiencias](audience) dentro de los [placements](placements) de Adapty. La mayoría de las apps tienen uno o dos placements (onboarding y un trigger general dentro de la app), por lo que la reconstrucción suele ser rápida. ### ¿Tiene Adapty un equivalente a `getPresentationResult`? \{#does-adapty-have-an-equivalent-to-getpresentationresult\} No como una sola llamada. Para comprobar si un placement mostraría un paywall, llama a `Adapty.getPaywall(placementId:)` y ramifica según el resultado. Si la llamada tiene éxito, hay un paywall asignado para la audiencia de ese usuario. Si falla porque no hay ningún paywall configurado, omite la presentación y ejecuta tu lógica de respaldo. --- # File: importing-historical-data-to-adapty --- --- title: "Importar datos históricos en Adapty" description: "Importa datos históricos en Adapty para obtener analíticas detalladas." --- Después de instalar el SDK de Adapty y publicar tu app, puedes acceder a tus usuarios y suscriptores en la sección [Profiles](profiles-crm). Pero ¿qué pasa si tienes una infraestructura legacy y necesitas migrar a Adapty, o simplemente quieres ver tus datos existentes en Adapty? :::note La importación de datos no es obligatoria Adapty otorgará automáticamente niveles de acceso a los usuarios históricos y restaurará sus eventos de compra en cuanto abran la app con el SDK de Adapty integrado. Para este caso de uso, importar datos históricos no es necesario. Sin embargo, importar los datos garantiza unas analíticas precisas si tienes un volumen significativo de transacciones históricas, aunque en general no es un requisito para la migración. ::: Para importar datos en Adapty: 1. Exporta tus transacciones a un archivo CSV (se deben proporcionar archivos separados para iOS, Android y Stripe). Consulta la sección [Formato del archivo de importación](importing-historical-data-to-adapty#import-file-format) más abajo para conocer los requisitos detallados. 2. Si algún archivo supera 1 GB, prepara una muestra de datos con aproximadamente 100 líneas. 3. Sube todos los archivos a Google Drive (puedes comprimirlos, pero mantenlos separados). 4. Para las transacciones de iOS, asegúrate de que la sección **In-app purchase API** en [**App settings**](https://app.adapty.io/settings/ios-sdk) esté completada con el **Issuer ID**, **Key ID** y la **Private key** (archivo .P8), incluso si usas StoreKit 1. Consulta las secciones [Provide Issuer ID and Key ID](app-store-connection-configuration#step-2-provide-issuer-id-and-key-id) y [Upload In-App Purchase Key file](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file) para obtener instrucciones detalladas. 5. Comparte los enlaces con nuestro equipo a través de [correo electrónico](mailto:support@adapty.io) o del chat en línea en el Adapty Dashboard. No te preocupes: importar datos históricos no creará duplicados, aunque esos datos se solapen con entradas ya existentes en Adapty. ## Limitaciones conocidas para Android \{#known-limitations-for-android\} 1. Solo se restaurarán las suscripciones activas; las transacciones expiradas no se restaurarán. 2. Solo se restaurarán las renovaciones más recientes de una suscripción; no se restaurará toda la cadena de compras. 3. Si el precio del producto ha cambiado desde la compra, se utilizará el precio actual, lo que puede dar lugar a precios incorrectos. :::note Si tienes un gran volumen de transacciones de Android, es posible que necesites [solicitar un aumento de cuota de la Google Play Developer API](google-play-quota-increase) antes de comenzar la importación para evitar superar el límite predeterminado de la API. ::: ## Formato del archivo de importación \{#import-file-format\} :::tip Si estás migrando desde RevenueCat, puedes enviar el archivo de exportación de RevenueCat directamente, sin necesidad de convertirlo. Consulta la [documentación de RevenueCat](https://www.revenuecat.com/docs/integrations/scheduled-data-exports) para obtener instrucciones de exportación. ::: Prepara tus datos en uno o varios archivos que cumplan las siguientes reglas: - [ ] El formato del archivo es .CSV. - [ ] Archivos separados para importaciones de Android, iOS y Stripe. - [ ] Cada archivo de importación contiene todas las [columnas requeridas](importing-historical-data-to-adapty#required-fields). - [ ] Las columnas de los archivos de importación tienen encabezados. - [ ] Los encabezados de columna coinciden exactamente con los de la columna **Column name** de la tabla de abajo. Comprueba que no haya errores tipográficos. - [ ] Las columnas que no son obligatorias pueden estar ausentes del archivo. No añadas columnas vacías para datos que no tengas. - [ ] Los archivos de importación no deben tener columnas adicionales que no se mencionen en la tabla. Si las hay, elimínalas. - [ ] Los valores están separados por comas. - [ ] Los valores no están entre comillas. - [ ] Si hay varios **apple_original_transaction_id** para un mismo usuario, añádelos todos como líneas separadas para cada **apple_original_transaction_id**. De lo contrario, es posible que no podamos restaurar las compras consumibles. Usa los siguientes archivos como ejemplos para [iOS](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/adapty_import_ios_sample.csv) y [Android](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/adapty_import_android_sample.csv). ### Columnas disponibles en el archivo de importación \{#available-import-file-columns\} | Nombre de columna | Presencia | Descripción | |-----------|--------|-----------| | **user_id** | obligatorio | ID de tu usuario | | **apple_original_transaction_id** | obligatorio para iOS | <p>El ID de transacción original u OTID ([más información](https://developer.apple.com/documentation/appstoreserverapi/originaltransactionid)), utilizado en el mecanismo de importación de StoreKit 2. Como un usuario puede tener varios OTID, basta con proporcionar al menos uno para una importación exitosa.</p><p></p><p>**Nota:** Para esta importación es necesario que las credenciales de la In-app purchase API estén configuradas en tu Adapty Dashboard. Aprende cómo hacerlo [aquí](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file).</p> | | **google_product_id** | obligatorio para Google | ID del producto en la Google Play Store. | | **google_purchase_token** | obligatorio para Google | Identificador único que representa al usuario y el ID del producto de la compra in-app que realizó | | **google_is_subscription** | obligatorio para Google | Los valores posibles son `1` \| `0` | | **stripe_token** | obligatorio para Stripe | Token de un objeto de Stripe que representa una compra única. Puede ser el token de una Suscripción de Stripe (`sub_...`) o de un Payment Intent (`pi_...`). | | **subscription_expiration_date** | opcional | La fecha de expiración de la suscripción, es decir, la próxima fecha de cobro, con fecha y hora con zona horaria (2020-12-31T23:59:59-06:00) | | **created_at** | opcional | Fecha y hora de creación del perfil (2019-12-31 23:59:59-06:00) | | **birthday** | opcional | La fecha de nacimiento del usuario en formato 2000-12-31 | | **email** | opcional | El correo electrónico de tu usuario | | **gender** | opcional | El género del usuario | | **phone_number** | opcional | El número de teléfono de tu usuario | | **country** | opcional | formato [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) | | **first_name** | opcional | El nombre de tu usuario | | **last_name** | opcional | El apellido de tu usuario | | **last_seen** | opcional | La fecha y hora con zona horaria (2020-12-31T23:59:59-06:00) | | **idfa** | opcional | El identificador para anunciantes (IDFA) es un identificador de dispositivo aleatorio que Apple asigna al dispositivo del usuario. Solo aplicable a apps de iOS | | **idfv** | opcional | El identificador para proveedores (IDFV) es un código único asignado a todas las apps desarrolladas por un mismo desarrollador, en este caso las tuyas. Solo aplicable a apps de iOS | | **advertising_id** | opcional | El Advertising ID es un código único asignado por el sistema operativo Android que los anunciantes pueden usar para identificar de forma única el dispositivo de un usuario | | **amplitude_user_id** | opcional | El ID de usuario de Amplitude | | **amplitude_device_id** | opcional | El ID de dispositivo de Amplitude | | **mixpanel_user_id** | opcional | ID de usuario de Mixpanel | | **appmetrica_profile_id** | opcional | ID de perfil de usuario de AppMetrica | | **appmetrica_device_id** | opcional | El ID de dispositivo de AppMetrica | | **appsflyer_id** | opcional | Identificador único de AppsFlyer | | **adjust_device_id** | opcional | El ID de dispositivo de Adjust | | **facebook_anonymous_id** | opcional | Identificador único generado por Facebook para usuarios que interactúan con tu app o sitio web de forma anónima, es decir, sin haber iniciado sesión en Facebook | | **branch_id** | opcional | Identificador único de Branch | | **attribution_source** | opcional | La integración de origen de la atribución, por ejemplo, appsflyer | | **attribution_status** | opcional | organic | | **attribution_channel** | opcional | El canal de atribución que trajo la transacción | | **attribution_campaign** | opcional | La campaña de atribución que trajo la transacción | | **attribution_ad_group** | opcional | El grupo de anuncios de atribución que trajo la transacción | | **attribution_ad_set** | opcional | El conjunto de anuncios de atribución que trajo la transacción | | **attribution_creative** | opcional | Elementos visuales o textuales específicos utilizados en un anuncio o campaña de marketing que se rastrean para determinar su efectividad a la hora de generar acciones deseadas, como clics, conversiones o instalaciones | | **custom_attributes** | opcional | Define hasta 30 atributos personalizados como un diccionario JSON en formato clave-valor: <ul><li>**key**: (string) El nombre del atributo personalizado</li><li> **value**: (string, entero, float o booleano) El valor del atributo personalizado.</li></ul><p> Formato: `"{'string_value': 'some_value', 'float_value': 123.0, 'int_value': 456}"`.</p><p>Ten en cuenta el uso de comillas dobles y simples en el formato. Los valores booleanos y enteros se convertirán a float.</p> | ### Campos obligatorios \{#required-fields\} Hay 2 grupos de campos obligatorios para cada plataforma: **user_id** y los datos que identifican las compras específicas de la plataforma correspondiente. Consulta la tabla a continuación para conocer los campos obligatorios por plataforma. | Plataforma | Campos obligatorios | |--------|---------------| | iOS | <p>user_id</p><p>apple_original_transaction_id</p> | | Android | <p>user_id</p><p>google_product_id</p><p>google_purchase_token</p><p>google_is_subscription</p> | | Stripe | <p>user_id</p><p>stripe_token</p> | Sin estos campos, Adapty no podrá obtener las transacciones. Para unas analíticas de cohorte precisas, especifica `created_at`. Si no se proporciona, asumiremos que la fecha de instalación coincide con la fecha de la primera compra. ### Importar datos en Adapty \{#import-data-to-adapty\} Ponte en contacto con nosotros y comparte tus archivos de importación a través de [support@adapty.io](mailto:support@adapty.io) o del chat en línea en el [Adapty Dashboard](https://app.adapty.io/overview). --- # File: migrate-integrations-to-adapty --- --- title: "Migrar integraciones a Adapty" description: "Cambia las integraciones de analytics y atribución de una solución legacy a Adapty sin duplicar eventos ni interrumpir campañas." --- Migrar a Adapty requiere algo más que cambiar el SDK. Tus integraciones de analytics y atribución con terceros — herramientas como Amplitude y Adjust — también necesitan una transición coordinada. Si se hace con cuidado, el cambio genera muy pocos eventos duplicados o perdidos y no interrumpe tus campañas. ## Mapea tus eventos \{#map-your-events\} Los nombres de eventos son personalizables en la mayoría de las integraciones de Adapty. Puedes configurarlos para que coincidan con los nombres que ya usas en tus dashboards y campañas. Tanto tus informes de analytics como los de campañas seguirán funcionando con los mismos nombres de eventos tras el cambio. Para ver la lista completa de eventos disponibles en Adapty, consulta [Eventos](events). En el caso de Adjust, la integración utiliza IDs de eventos en lugar de nombres de eventos personalizados. Transfiere tus IDs de eventos existentes desde el dashboard de Adjust a la configuración de la integración de Adapty. Consulta la [guía de integración de Adjust](adjust) para más detalles. ## Cómo crea Adapty los eventos de integración \{#how-adapty-creates-integration-events\} Para enviar un evento a una integración, Adapty necesita tener un perfil de usuario. Un perfil se crea de una de estas dos formas: - **Importación histórica**: el perfil se crea cuando [importas datos históricos de transacciones](importing-historical-data-to-adapty) antes de que el SDK entre en funcionamiento. - **Interacción con el SDK**: el perfil se crea automáticamente cuando el usuario abre la app con el SDK de Adapty por primera vez. Adapty se entera de las compras realizadas en el sistema legacy en tiempo real. Sin embargo, solo puede enviar un evento de integración una vez que el perfil del comprador existe. Ese perfil se crea cuando el usuario abre la app con el SDK de Adapty. Los usuarios que no actualicen a la nueva versión no generarán eventos de integración. ## Prepárate antes del día de la migración \{#prepare-before-migration-day\} ### Excluye los eventos históricos \{#exclude-historical-events\} Activa **Exclude Historical Events** en los [ajustes de tu integración](configuration). Esto impide que los eventos anteriores a la primera sesión del usuario con el SDK de Adapty se envíen a la integración. Esta configuración es especialmente importante durante la [importación histórica](importing-historical-data-to-adapty), cuando Adapty procesa un gran volumen de transacciones pasadas de una vez. Sin ella, esas transacciones generarán un gran volumen de eventos en tu herramienta de analytics. ### Configura la integración con antelación \{#set-up-the-integration-in-advance\} Adapty te permite configurar y probar una integración mientras la mantienes desactivada. Puedes establecer credenciales, mapeo de eventos y filtros sin activar la integración hasta que estés listo. La configuración se guarda cuando la activas, así que no se pierde nada por mantenerla desactivada hasta el día de la migración. Para encontrar tu integración, consulta [Integraciones de atribución](attribution-integration), [Integraciones de analytics](analytics-integration), [Integraciones de servicios de mensajería](messaging) o [Integraciones de Webhook y ETL](webhook-and-etl). ## Realiza el cambio el día de la migración \{#switch-on-migration-day\} Desactiva la integración en tu solución legacy y actívala en Adapty al mismo tiempo. Ejecutar ambas simultáneamente generará eventos duplicados. Pausa las campañas de adquisición grandes el día de la migración. Esto reduce el riesgo de errores en la optimización de campañas causados por eventos en la ventana de solapamiento. ## Qué esperar \{#what-to-expect\} Algunos eventos de integración perdidos o duplicados durante la migración son inevitables. Cuando el cambio se realiza correctamente, el número de eventos afectados es insignificante. La principal fuente de huecos es el momento descrito anteriormente: Adapty solo puede enviar eventos de integración para una compra después de que exista el perfil del usuario. Las compras realizadas en el sistema legacy no generan eventos de integración en Adapty hasta que el comprador abre la app con el SDK de Adapty. ## Integraciones frente a notificaciones server-to-server \{#integrations-vs-server-to-server-notifications\} Adapty recomienda usar integraciones en lugar de reenviar las notificaciones server-to-server sin procesar del store directamente a tus herramientas de analytics o atribución. Con las integraciones: - **Formato unificado**: los eventos de todos los stores — App Store, Google Play, Stripe — utilizan el mismo formato de evento. - **Datos enriquecidos**: los eventos incluyen datos que Adapty recopila, como el estado de la suscripción y los atributos del usuario. Las notificaciones sin procesar no incluyen esto. --- # File: whats-new --- --- title: "Novedades" description: "Mantente al día con las últimas funciones y mejoras de Adapty" --- Descubre las últimas funciones, mejoras, actualizaciones del SDK y mejoras en la documentación que te ayudan a optimizar la estrategia de monetización de tu app. Esta página destaca los lanzamientos más importantes de cada mes. :::note ¿Tienes comentarios sobre las nuevas funciones? ¡Nos encantaría escucharte! Contáctanos a través del [tablón de comentarios sobre el producto](https://adapty.featurebase.app/en?b=69831ba5e82e7a3391632ec2). ::: ## Julio 2026 \{#july-2026\} - **Monedas virtuales**: Define monedas dentro de la app como tokens, monedas o gemas, asigna y rastrea el saldo de cada usuario, y consulta esos saldos desde tu servidor a través de la API server-side. [Más información](virtual-currencies) - **Agente de IA en Apple Ads Manager**: Consulta a un agente de chat sobre el rendimiento de tus Apple Ads y obtén respuestas basadas en los datos de tus campañas, sin necesidad de crear informes manualmente. [Más información](ads-manager-ai-agent) - **Nuevas automatizaciones en Apple Ads Manager**: Automatiza cambios a nivel de campaña y grupo de anuncios con dos nuevos tipos de reglas, junto a las automatizaciones existentes de palabras clave y términos de búsqueda. [Reglas de campaña](ads-manager-automations-campaign-rules) | [Reglas de grupo de anuncios](ads-manager-automations-ad-group-rules) - **Perfiles en Adapty Mail**: Una vista por suscriptor que muestra el recorrido de cada usuario, el estado actual de la suscripción y el estado de cancelación de suscripción en un solo lugar. [Más información](mail-profiles) - **SDK v4 para React Native, Flutter, Capacitor y Kotlin Multiplatform**: Los SDKs v4 con soporte para Flows ya están disponibles. React Native, Flutter y Capacitor alcanzaron disponibilidad general, y Kotlin Multiplatform v4 se ha lanzado — cada uno con su propia guía de migración. [React Native](migration-to-react-native-sdk-v4) | [Flutter](migration-to-flutter-sdk-v4) | [Capacitor](migration-to-capacitor-sdk-v4) | [Kotlin Multiplatform](migration-to-kmp-sdk-v4) - **Nuevos campos en webhooks**: Los payloads de los webhooks ahora incluyen el precio original y el descuento de cada transacción, para que puedas rastrear las ofertas promocionales e introductorias. Estos campos están disponibles únicamente en webhooks. [Más información](webhook-event-types-and-fields) - **Precios tachados en flows**: Muestra el precio original tachado junto al precio con descuento, con una insignia de descuento, directamente en el Flow Builder. [Más información](strikethrough-price) - **Galería de plantillas de flow**: Empieza un nuevo flow desde una plantilla diseñada por profesionales en lugar de un lienzo en blanco y personalízala para que encaje con tu app. [Más información](paywall-builder-templates) - **Botón Install tools**: Ahora todos los artículos de documentación tienen un botón **Install tools** en la cabecera. Al pulsarlo, se abre un modal con comandos listos para copiar e instalar la skill de integración del SDK de Adapty en Claude Code, Copilot CLI, Gemini CLI, Codex y otros asistentes de programación con IA. [Más información](adapty-sdk-integration-skill) - **Nuevo método de instalación del SDK de Unity**: Ahora puedes instalar el SDK de Unity a través de Swift Package Manager, con orientación adicional para solucionar problemas comunes de configuración. [Más información](sdk-installation-unity) - **Contenedor de pie de página en el Flow Builder**: Un panel inferior fijo que permanece anclado mientras el resto de la pantalla se desplaza — ideal para botones de CTA, texto legal y enlaces. [Más información](builder-containers#footer) - **Nuevos tutoriales en vídeo del Flow Builder**: Una lista de reproducción de YouTube en crecimiento con guías paso a paso para construir flows, ahora integrada en las guías del Flow Builder. [Más información](adapty-flow-builder) ## Junio 2026 \{#june-2026\} - **Los flows ahora funcionan en Android**: El editor visual sin código para paywalls y onboardings ya está disponible en Android SDK v4 y superiores, además de iOS. Las pantallas se renderizan de forma nativa, sin web views. [Más información](adapty-flow-builder) - **Pruebas A/B de CPP en Apple Ads Manager**: Compara páginas de producto personalizadas entre sí dentro de Apple Ads. Elige entre 2 y 4 páginas — incluida tu página predeterminada actual — y Apple Ads distribuye el tráfico entre ellas e informa sobre cuál convierte mejor. [Más información](ads-manager-cpp-ab-tests) - **Adapty Mail API**: Envía perfiles de usuario y transacciones a Adapty Mail directamente desde tu servidor, sin pasar los datos por el SDK. Úsala para crear una base de suscriptores, reutilizar suscriptores de otras apps o mantener tu backend como fuente de verdad. [Más información](mail-send-data-via-api) - **Mostrar un paywall dirigido por Apple Ads en el primer lanzamiento**: la atribución de Apple Ads llega después de que el SDK se activa, por lo que un paywall solicitado demasiado pronto no alcanza tu audiencia de Apple Ads. Usa `AdaptyProfile.appliedAttributionSources` para mostrar el paywall dirigido por Apple Ads en cuanto lleguen los datos de atribución. [iOS](ios-show-aa-targeted-paywall) | [React Native](react-native-show-aa-targeted-paywall) | [Capacitor](capacitor-show-aa-targeted-paywall) - **Autoguardado en el Flow Builder**: El Flow Builder ahora guarda tu progreso automáticamente cada minuto, por lo que ya no perderás trabajo sin guardar al salir de la página. También puedes guardar un borrador manualmente con **Cmd/Ctrl + S**. [Más información](builder-save-publish) - **Nuevos tutoriales en vídeo del Flow Builder**: Dos nuevos recorridos explican cómo crear navegación entre pantallas del flow y cómo diseñar estados de elementos como seleccionado, activo y desactivado. [Navegación en flows](onboarding-navigation-branching) | [Estados de elementos](builder-element-states) - **Documentación en japonés y vietnamita**: La documentación de Adapty ya está disponible en japonés (日本語) y vietnamita (Tiếng Việt). Cambia de idioma con el selector de idioma en la navegación superior. ## Mayo 2026 \{#may-2026\} - **Flows (Beta)**: Crea secuencias de pantallas completas en un editor visual sin código — paywalls de una sola pantalla, onboardings de varios pasos y todo lo que hay entre medias, todo en un flow. Las pantallas se renderizan de forma nativa sin web views, y puedes actualizar textos, diseño y lógica sin publicar una nueva versión de la app. Actualmente es compatible con iOS, Android, React Native, Flutter y Capacitor SDK v4 en adelante. [Más información](adapty-flow-builder) - **Autopilot ahora se adapta a tus resultados**: Actuando como un gestor de crecimiento con IA, actualiza el plan de crecimiento tras cada ronda completada. La siguiente hipótesis se construye a partir de los experimentos que has ejecutado, cuáles ganaron y qué direcciones siguen valiendo la pena explorar, en lugar de seguir una secuencia fija. [Más información](autopilot-how-it-works#how-ai-growth-advisor-decides-what-to-recommend) - **ARPU de activación en Autopilot Market Insights**: Un nuevo gráfico compara el ingreso promedio por instalación nueva de tu app con la media de la categoría. Combínalo con el embudo de conversión: una conversión alta junto con un ARPU de activación bajo puede indicar que las ofertas tienen un precio demasiado bajo. [Más información](autopilot-analysis#activation-arpu) - **Analytics en Adapty Mail**: Compara métricas de entrega e ingresos atribuidos al email para cada campaña en una sola vista. Agrupa, desglosa y filtra por campaña, segmento, variante de prueba A/B, mensaje o trigger, y profundiza en cualquier fila. [Más información](mail-analytics) - **Perfil de marca en Adapty Mail**: Un perfil centralizado que define el contenido de los correos, el tono, los elementos visuales y el contenido del paywall web. Adapty lo genera a partir de la ficha de tu app en el store, la página de aterrizaje, las páginas legales y los perfiles sociales, y puedes revisar o ajustar cada sección directamente. [Más información](mail-brand) - **Predicciones en Adapty UA**: Ingresos previstos, ROAS, beneficio publicitario, ARPU y ARPPU para cada cohorte, para que puedas comparar campañas antes de que maduren. Las predicciones se construyen a partir de los datos históricos de cohortes de tu propia app, se actualizan diariamente y están disponibles para períodos de cohorte desde D0 hasta D360 o un día personalizado. [Más información](ua-predicted-metrics) - **Nuevos campos en la exportación personalizada S3 de Adapty UA**: La exportación personalizada a S3 ahora incluye `bundle_id`, `device_brand`, `device_model`, `os_version`, `app_version` y `sdk_version`. Segmenta y combina datos de atribución por dispositivo y versión de app en tus sistemas downstream. [Más información](ua-custom-s3) - **Audiencias de placement en la CLI**: Los comandos `adapty placements create` y `adapty placements update` ahora aceptan el flag `--audiences` — un array JSON de entradas `{segment_ids, paywall_id, priority}` — para que puedas dirigir distintos paywalls a distintos segmentos desde el terminal. El nuevo comando `adapty paywalls placements` lista todos los placements que usan un paywall concreto, para que puedas previsualizar el impacto antes de cambiarlo. [Más información](developer-cli-reference#placements) - **Documentación en español**: La documentación de Adapty ya está disponible en español. Cambia de idioma usando el selector de idioma en la navegación superior. ## Abril 2026 \{#april-2026\} - **Adapty Mail**: Campañas de email generadas por IA que convierten a usuarios en prueba en suscriptores de pago. Crea, envía y atribuye campañas desde tu proyecto de Adapty sin necesidad de ninguna plataforma de email independiente. [Más información](adapty-mail) - **Diagnóstico de paywall en Autopilot**: Descubre qué mejorar en tu paywall antes de crear una prueba. Sube una captura de pantalla y Autopilot te devuelve recomendaciones basadas en benchmarks de las apps con mejor rendimiento en tu categoría, además de sugerencias de diseño y texto generadas por IA. Las recomendaciones con benchmark se convierten en rondas de prueba A/B en tu plan de crecimiento. [Más información](autopilot-analysis#paywall-analysis) - **Orientación más clara para cada sugerencia de Autopilot**: Cada hipótesis ahora explica por qué es importante (una explicación basada en datos sobre cómo tu paywall se desvía de los patrones establecidos), qué cambiar y cómo configurar la prueba A/B, y qué métricas vigilar en una nueva sección "Cómo interpretar tus resultados". [Más información](autopilot-execute-plan#step-1-view-the-hypothesis) - **Mantén actualizado tu plan de crecimiento Autopilot**: Actualiza el análisis para obtener los datos de mercado más recientes y nuevas sugerencias, y revisa las sugerencias anteriores en el historial de versiones si las nuevas no se ajustan a lo que buscas. Las hipótesis se agrupan en las pestañas Top priority, All, Pricing, Visual, Geo-pricing y Archived. [Más información](autopilot-growth-plan) - **Distribución de ingresos por duración en Autopilot**: Comprueba si tus ingresos están sobreconcentrados en una duración de suscripción. Un nuevo gráfico de Market Insights muestra la composición de tus ingresos por duración junto con la media del sector para tu categoría y país. [Más información](autopilot-analysis#revenue-distribution-by-duration) - **LTV y predicciones de ingresos actualizadas**: El LTV predicho y los ingresos ahora utilizan los datos de retención de cohortes de tu propia app cuando hay suficiente historial, y promedios entre apps en caso contrario — así incluso las apps más nuevas obtienen predicciones utilizables en análisis y pruebas A/B. [Más información](predicted-ltv-and-revenue) - **Enviar todos los eventos en Adapty UA**: Dale a Meta y TikTok una visión más completa de las conversiones para un modelado de audiencias más preciso. Adapty ahora permite reenviar instalaciones y transacciones de usuarios orgánicos y no atribuidos a tu píxel, no solo de usuarios vinculados a una campaña. [Meta](ua-facebook#send-all-events) | [TikTok](ua-tiktok#send-all-events) - **Documentación en ruso y turco**: La documentación de Adapty ya está disponible en ruso (Русский) y turco (Türkçe). Cambia de idioma usando el selector de idioma en la navegación superior. ## Marzo 2026 \{#march-2026\} - **CLI para desarrolladores**: Gestiona tu cuenta de Adapty desde la terminal sin abrir el Dashboard. El CLI te permite crear apps, definir niveles de acceso, configurar productos, crear paywalls y configurar placements — todo scriptable para entornos automatizados. También hay disponible una [skill de Adapty CLI](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) para ayudar a los asistentes de programación con IA a trabajar con el CLI. [Más información](developer-cli) - **Página general en Apple Ads Manager**: Consulta todas las métricas clave de Apple Ads en un solo lugar, cada una con un gráfico de tendencias. Filtra por app mediante el desplegable del encabezado, personaliza qué métricas se muestran y ajusta el tipo de gráfico y la visualización de ingresos. [Más información](ads-manager-overview) - **Inteligencia de mercado en Apple Ads Manager**: Descubre en qué palabras clave publican anuncios tus competidores en más de 50 países y añade las palabras clave de mejor rendimiento directamente a tus campañas. [Más información](ads-manager-market-intelligence) - **Automatizaciones de palabras clave de ciclo completo en Apple Ads Manager**: Ajusta pujas automáticamente, pausa o activa palabras clave y muévelas entre grupos de anuncios según las reglas de rendimiento que definas. [Más información](ads-manager-automations-keyword-rules) - **Historial de pujas en Apple Ads Manager**: Consulta el registro completo de cambios de la puja CPT de cualquier palabra clave: cuándo ocurrió cada cambio, los valores anterior y nuevo, y qué regla de automatización lo desencadenó. [Más información](ads-manager-manage-keywords#bid-history) - **Rondas visuales en Autopilot**: Las sugerencias de diseño de paywall ahora son rondas de primera clase en tu plan de crecimiento — aparecen en la barra lateral junto a las rondas de monetización. Cada ronda visual incluye un mockup de diseño, una descripción de cuándo funciona mejor ese patrón y las métricas clave que tiene como objetivo. [Más información](autopilot-growth-plan#view-the-growth-plan) - **Añade tu propia hipótesis a Autopilot**: Amplía tu plan de crecimiento con rondas personalizadas. Añade un título, descripción, tipo de ronda (monetización o visual), métricas objetivo y — en el caso de rondas de monetización — los productos involucrados. [Más información](autopilot-growth-plan#add-your-own-hypothesis) - **Reordena las rondas de Autopilot**: Arrastra y reordena las etapas de tu plan de crecimiento para ejecutar los experimentos en el orden que mejor se adapte a tu estrategia. [Más información](autopilot) - **Precios geográficos en Autopilot**: Prueba cambios de precio por país como un nuevo tipo de ronda en tu plan de crecimiento. A partir de los datos de Market Insights, Autopilot recomienda si aumentar, reducir o mantener los precios en cada país. Añade una recomendación como ronda de precios geográficos para ejecutarla como prueba A/B — se pueden ejecutar hasta 5 simultáneamente. [Más información](autopilot-growth-plan#geo-pricing-hypotheses) - **Automatizaciones de términos de búsqueda en Apple Ads Manager**: Promociona automáticamente los términos de búsqueda ganadores a palabras clave de coincidencia exacta y niégalos en el origen, sin necesidad de descargar informes manualmente. Las reglas se pueden crear desde plantillas o construir desde cero con condiciones y programaciones personalizadas. [Más información](ads-manager-automations-search-terms) - **Puja de Maximizar Conversiones en Apple Ads Manager**: Al crear campañas, ahora puedes seleccionar Maximizar Conversiones como estrategia de puja. El algoritmo de Apple maximiza las descargas dentro de tu presupuesto, guiado por un CPA objetivo opcional. [Más información](ads-manager-create-campaign) - **Integración con FunnelFox en Adapty UA**: Ya está disponible la nueva integración con FunnelFox en Adapty UA. [FunnelFox](ua-funnelfox) - **Documentación en chino**: La documentación de Adapty ya está disponible en chino (中文). Cambia de idioma con el selector de idioma en la navegación superior. ## Febrero de 2026 \{#february-2026\} - **Precios de productos por país**: Establece precios distintos por país directamente en el Adapty Dashboard — Adapty sincroniza los cambios con App Store Connect y Google Play de forma automática. Cada actualización de precios queda registrada en el registro de auditoría, sin que ningún cambio pase desapercibido. [Más información](edit-product) - **Precios de la competencia por país en Autopilot**: Compara los precios de tu suscripción con los de la competencia en tus mercados principales. [Más información](autopilot-analysis#market-and-competitor-analysis) - **Control de versiones de onboarding**: Lleva un seguimiento y gestiona las versiones de tus onboardings con un historial completo de versiones. Revisa los cambios y haz rollback cuando lo necesites. - **Gráficos de conversión de paywall en análisis**: Dos nuevos gráficos de conversión — Paywall view → Trial y Paywall view → Paid — muestran cómo tus paywalls convierten visitantes en suscriptores. [Más información](analytics-conversion) - **Segmentos duplicados**: Copia un segmento existente con todos sus filtros en lugar de reconstruir uno similar desde cero. Útil cuando se gestionan varias campañas o pruebas A/B con audiencias superpuestas. [Más información](segments#duplicate-segments) - **Notificaciones push en la app móvil de Adapty**: Configura notificaciones push para 14 tipos de eventos directamente en la app de Adapty para iOS y mantente al tanto de la actividad de suscripciones sin abrir el dashboard. [Más información](push-notifications) - **Kotlin Multiplatform SDK 3.15**: Añade soporte para onboardings, paywalls web y mejoras en la API. [Más información](migration-to-kmp-315) - **Capacitor SDK 3.16**: Añade soporte para Capacitor 8. Los proyectos que usen Capacitor 7 deben quedarse en el SDK v3.15. [Más información](migration-to-capacitor-316) - **Guías de integración del SDK asistidas por LLM**: Guías paso a paso para integrar Adapty con la ayuda de asistentes de código IA. Cada guía lleva a tu LLM por toda la implementación, desde la configuración del dashboard hasta las compras. [iOS](adapty-cursor) | [Android](adapty-cursor-android) | [React Native](adapty-cursor-react-native) | [Flutter](adapty-cursor-flutter) | [Unity](adapty-cursor-unity) | [Kotlin Multiplatform](adapty-cursor-kmp) | [Capacitor](adapty-cursor-capacitor). Para un flow automatizado en un solo comando, prueba el nuevo **adapty-sdk-integration skill** (beta): [iOS](adapty-sdk-integration-skill) | [Android](adapty-sdk-integration-skill-android) | [React Native](adapty-sdk-integration-skill-react-native) | [Flutter](adapty-sdk-integration-skill-flutter) | [Unity](adapty-sdk-integration-skill-unity) | [Kotlin Multiplatform](adapty-sdk-integration-skill-kmp) | [Capacitor](adapty-sdk-integration-skill-capacitor) ## Enero de 2026 \{#january-2026\} - **SDK de Capacitor publicado oficialmente**: El SDK de Capacitor ya está listo para producción tras un exhaustivo proceso de pruebas. Crea apps de suscripción para iOS y Android con Capacitor y soporte completo de integración con Adapty. [Más información](capacitor-sdk-overview) - **Autopilot para apps nuevas**: El análisis de Autopilot ya está disponible aunque tu app no tenga un historial extenso de transacciones. Obtén recomendaciones de optimización de precios basadas en datos y crea tu plan de crecimiento desde el primer día. [Más información](autopilot) - **Oportunidades de precios globales en Autopilot**: Identifica el potencial de ingresos en tus mercados más rentables con recomendaciones de precios por país. Autopilot analiza las tasas de conversión y el poder adquisitivo de tus 5 principales países, y te ofrece información basada en datos sobre si conviene subir, bajar o mantener los precios según el Índice de Precios de Adapty. [Más información](autopilot) - **Métricas de conversión de recuperación de facturación**: Nuevos gráficos de análisis rastrean los ingresos recuperados por problemas de facturación y períodos de gracia. Monitoriza "Billing issue converted", "Billing issue converted revenue", "Grace period converted" y "Grace period converted revenue" para medir tus esfuerzos de retención y recuperación. - **Gestión de anuncios directa en Apple Ads Manager**: Crea y gestiona tus campañas de Apple Ads directamente desde Adapty sin cambiar de plataforma. [Más información](ads-manager-manage-ads) - **Análisis de Apple Ads Manager**: Accede a métricas de rendimiento detalladas a nivel de anuncio y datos de atribución dentro de Adapty. Consulta el rendimiento de campañas, análisis de grupos de anuncios e información de atribución en un dashboard unificado. [Más información](adapty-ads-manager-analytics) - **Gráficos de atribución de Apple Ads**: Combina múltiples métricas de atribución en gráficos personalizables para analizar el rendimiento de tus Apple Ads junto con los datos de suscripción. [Más información](adapty-ads-manager-analytics#charts) - **Segmentos de atribución de Apple Ads**: Crea segmentos de usuarios basados en datos de atribución de Apple Ads con un flujo de trabajo simplificado de dos clics. Dirige tus campañas a usuarios por campaña, grupo de anuncios o palabra clave para análisis y experimentos más precisos. [Más información](ads-manager-create-segments) - **Nueva plataforma de documentación**: El sitio de documentación ha migrado a una nueva plataforma, lo que permite actualizaciones de funciones más rápidas y una experiencia de usuario mejorada con búsqueda, navegación y organización de contenido optimizadas. ## Diciembre 2025 \{#december-2025\} - **Documentación de Apple Ads Manager**: Combina los datos de tus campañas de Apple Search Ads con métricas de ingresos en un único dashboard de análisis. La nueva documentación cubre la creación de campañas, la gestión de grupos de anuncios y las formas de hacer seguimiento del ROI de tu inversión publicitaria junto al rendimiento de las suscripciones. [Más información](ads-manager) - **Paywalls web in-app**: Muestra paywalls basados en web dentro de tu app usando un navegador in-app, ofreciendo una experiencia fluida sin redirecciones externas. [iOS](ios-web-paywall#open-web-paywalls-in-an-in-app-browser) | [Android](android-web-paywall#open-web-paywalls-in-an-in-app-browser) | [React Native](react-native-web-paywall#open-web-paywalls-in-an-in-app-browser) | [Flutter](flutter-web-paywall#open-web-paywalls-in-an-in-app-browser) - **Segmentos dinámicos**: Crea segmentos de audiencia dinámicos que se actualizan automáticamente en función de ventanas de tiempo móviles. Por ejemplo, crea un segmento de "usuarios que instalaron la app en los últimos 7 días" que se refresca continuamente para mostrar siempre a tus clientes más recientes. [Más información](segments#available-attributes) - **Guías de configuración de campañas en Meta y TikTok**: Documentación paso a paso para crear y rastrear campañas en Meta (Facebook e Instagram) y TikTok, con seguimiento de conversiones e integración de analíticas. [Meta](meta-create-campaign) | [TikTok](tiktok-create-campaign) - **Guías de inicio rápido para implementación manual de paywalls**: Implementa compras in-app más rápido con guías paso a paso que muestran cómo integrar el SDK de Adapty en tu UI de paywall personalizada. [iOS](ios-implement-paywalls-manually) | [Android](android-implement-paywalls-manually) | [React Native](react-native-implement-paywalls-manually) | [Flutter](flutter-implement-paywalls-manually) | [Unity](unity-implement-paywalls-manually) | [Kotlin Multiplatform](kmp-quickstart-manual) | [Capacitor](capacitor-quickstart-manual) - **Navegador integrado para los enlaces del onboarding**: Los enlaces externos en los onboardings se abren ahora por defecto en un navegador integrado, manteniendo a los usuarios dentro de la app. Puedes personalizar este comportamiento para usar navegadores externos si lo necesitas. [iOS](ios-present-onboardings#customize-how-links-open-in-onboardings) | [Android](android-present-onboardings#customize-how-links-open-in-onboardings) | [React Native](react-native-present-onboardings#customize-how-links-open-in-onboardings) - **Sugerencias mejoradas de Autopilot**: Autopilot ahora ofrece mejores recomendaciones de optimización de precios basadas en un análisis más detallado de los datos de tu suscripción. [Prueba Autopilot](autopilot) - **Modo oscuro en la documentación**: La documentación ahora es compatible con el modo oscuro, con detección automática de las preferencias del sistema o activación manual desde la esquina superior derecha. --- # File: adapty-ecosystem --- --- title: "El ecosistema de Adapty" description: "Adapty es una plataforma de compras in-app para aplicaciones móviles. Conoce qué hace cada producto y cómo se conectan." --- Adapty es una plataforma de compras in-app para aplicaciones móviles, creada con una sola misión: hacer que las apps sean rentables. Te da todo lo que necesitas para aumentar los ingresos: captar usuarios, convertirlos, mantenerlos suscritos y recuperar a los que se van. Con un solo registro tienes acceso a todo el ecosistema de Adapty desde el primer día. Haz clic en el logo de Adapty para cambiar entre productos: - **Core** — procesa compras sin tocar StoreKit ni Google Play Billing, diseña paywalls sin código y haz seguimiento de ingresos en tiempo real. El resto de productos se construyen sobre esta base. - **Adapty Ads Manager** — ejecuta y optimiza Apple Ads, medidos frente a ingresos reales de suscripciones. - **Adapty Attribution** — descubre qué canales de publicidad generan ingresos de verdad, sin necesidad de MMP. - **Adapty Mail** — convierte trials y recupera usuarios que se dieron de baja con emails automatizados. Otros dos productos complementan los cuatro principales: **FunnelFox** (funnels de web a app y checkout alojado) y **Adapty Finance** (anticipos sobre ingresos futuros por suscripciones). ## Cómo encajan los productos entre sí \{#how-the-products-fit-together\} Cada producto interviene en un punto distinto del ciclo de vida del cliente. Pasa el cursor sobre cualquier funcionalidad enlazada para ver una definición rápida, o haz clic para ir a su documentación. <ProductMap /> :::link Ver también: [¿Es Adapty la opción adecuada para mí?](is-adapty-right-for-me) ::: ## Diseñado para flujos de trabajo con IA \{#built-for-ai-workflows\} Ejecuta Adapty desde tu agente de codificación con IA: integra, gestiona y consulta todo sin salir de tu editor. Apunta al agente al [skill de integración del SDK](adapty-sdk-integration-skill) de tu plataforma y completará toda la configuración con un solo comando, o sigue una [guía LLM paso a paso](adapty-cursor) para revisar cada paso tú mismo. Tu herramienta de IA puede integrar la documentación de la forma que mejor se adapte. Copia cualquier página en Markdown con el botón **Copy for LLM**, o apúntala a [`llms.txt`](https://adapty.io/docs/es/llms.txt) — un mapa completo de la documentación. Para acceso en tiempo real, el servidor MCP de [Context7](https://context7.com/adaptyteam/adapty-docs) muestra los fragmentos de código más relevantes de la documentación en Cursor, Claude Code y otros IDEs. Consulta [Gestiona Adapty con IA](manage-adapty-with-ai) para ver todos los puntos de entrada. ## Core \{#core\} Core es la plataforma base de Adapty. Muestra tus paywalls, gestiona las compras y mide todo lo que ocurre a continuación. ### SDKs y stores \{#sdks-and-stores\} Olvídate de la fontanería de facturación. Adapty gestiona las compras, la validación de recibos y las renovaciones por ti, y mantiene el estado de cada suscriptor actualizado en tiempo real, para que siempre sepas quién tiene acceso y por qué. Dentro de tu app, los [SDK](installation-of-adapty-sdks) gestionan todo el flujo de compra de extremo a extremo, o [observan tu sistema de facturación existente](observer-vs-full-mode) si ya tienes uno. Son compatibles con 7 plataformas: [iOS](ios-sdk-overview), [Android](android-sdk-overview), [React Native](react-native-sdk-overview), [Flutter](flutter-sdk-overview), [Unity](unity-sdk-overview), [Kotlin Multiplatform](kmp-sdk-overview) y [Capacitor](capacitor-sdk-overview). Del lado del servidor, Adapty se conecta directamente a los stores, de modo que cada renovación, reembolso e incidencia de facturación te llega en tiempo real, incluso cuando la app está cerrada. Compatible con: [App Store](initial_ios), [Google Play](initial-android), [Stripe](stripe) y [Paddle](paddle), además de una [integración personalizada](custom-store) para cualquier otro proveedor. ### Productos, ofertas y niveles de acceso \{#products-offers-and-access-levels\} Adapty separa lo que vendes de lo que los usuarios desbloquean. Gracias a esa separación, puedes cambiar precios, intercambiar productos o lanzar ofertas sin publicar una actualización de la app. Todo funciona con tres elementos: - **[Productos](product)** — un producto unifica tus SKUs de App Store, Play Store y web — suscripciones, compras únicas o consumibles — para que gestiones el catálogo en un solo lugar. - **[Ofertas](offers)** — los mecanismos que mejoran la conversión: descuentos introductorios, promocionales y de recuperación. - **[Niveles de acceso](access-level)** — te permiten desacoplar los privilegios de acceso de los productos individuales. ### Flows y placements \{#flows-and-placements\} Crea las pantallas que generan ingresos — paywalls, onboardings, cuestionarios — sin escribir código. Los [flows](adapty-flow-builder) se renderizan en el dispositivo a través del SDK, por lo que puedes cambiar textos, diseño y precios en cualquier momento sin publicar una nueva versión de la app. Empieza desde una [plantilla](paywall-builder-templates) o desde cero. Asocia cada flow a un [placement](placements) y dirige cada uno a distintas [audiencias](audience) creadas a partir de [segmentos](segments). ### Perfiles y segmentos \{#profiles-and-segments\} Consulta el historial completo de cualquier suscriptor — los [perfiles](profiles-crm) muestran la línea de tiempo de eventos, el estado de suscripción, los ingresos y los atributos personalizados de cada usuario. Divide tu base de usuarios en [segmentos](segments) por cualquier atributo para personalizar lo que ven, filtrar análisis y acotar pruebas A/B. El [feed de eventos](event-feed) transmite cada evento de suscripción en tiempo real. ### Pruebas A/B y AI Growth Advisor \{#ab-tests-and-ai-growth-advisor\} Aumenta los ingresos encontrando qué es lo que mejor convierte: - **[Pruebas A/B](ab-tests)** — prueba distintos precios, duraciones de prueba y diseños de flow. - **[AI Growth Advisor](autopilot)** — te dice exactamente qué probar en tu próxima prueba A/B. Compara tu paywall con más de 20.000 apps de suscripción y clasifica los experimentos por el aumento de ingresos esperado. Descubre [cómo funciona](autopilot-how-it-works). ### Análisis y predicciones \{#analytics-and-predictions\} [Analytics](analytics) convierte los datos de la store, el SDK y la atribución en un dashboard de ingresos en tiempo real con [docenas de métricas](metric-comparison-table) — muchas más de las que muestran por sí solos App Store y Google Play. Profundiza con análisis de [cohortes](analytics-cohorts), [embudos](analytics-funnels), [retención](analytics-retention) y [conversión](analytics-conversion). [Predictions](predicted-ltv-and-revenue) predice el LTV de cada cohorte con meses de antelación e identifica [ganadores de pruebas A/B](predictions-in-ab-tests) antes de que alcancen significación estadística. Los [informes](reports) programados llegan directamente a tu bandeja de entrada. ### CLI para desarrolladores \{#developer-cli\} El [CLI para desarrolladores de Adapty](developer-cli-quickstart) permite configurar productos, placements y niveles de acceso desde la línea de comandos — una alternativa al dashboard para desarrolladores que prefieren la terminal. ## Adapty Ads Manager \{#adapty-ads-manager\} [Adapty Ads Manager](adapty-ads-manager) es una plataforma de Apple Ads. Sustituye la consola nativa de Apple Ads con optimización basada en IA, atribución de ingresos en tiempo real e inteligencia competitiva. Como Core ya registra cada instalación, prueba, suscripción y renovación, Ads Manager conecta directamente el gasto en publicidad con el LTV. Sin necesidad de MMP. Características principales: - **[Campañas y palabras clave](ads-manager)** — créalas y gestiónalas, junto con grupos de anuncios y pujas, desde el Adapty Dashboard. - **[Agente de IA](ads-manager-ai-agent)** — consultas de embudo completo y recomendaciones en lenguaje natural. - **[Inteligencia de mercado](ads-manager-market-intelligence)** — estrategias de palabras clave de la competencia en más de 50 países. - **[Pruebas A/B de CPP](ads-manager-cpp-ab-tests)** — páginas de producto personalizadas comparadas directamente. - **[Automatizaciones](ads-manager-automations)** — tus campañas se optimizan solas. Las pujas, palabras clave y términos de búsqueda se ajustan automáticamente cuando tus métricas superan los umbrales definidos. ## Atribución de Adapty \{#adapty-attribution\} [Adapty Attribution](adapty-user-acquisition) relaciona las instalaciones de la app y los ingresos por suscripción con las campañas publicitarias que los generaron. Combina el gasto en plataformas de anuncios, los clics en enlaces de seguimiento y los eventos de instalación del SDK en vistas de ROAS, LTV y cohorte para todos tus canales de pago. No necesitas ningún MMP externo. Características principales: - **[Integraciones con plataformas publicitarias](ua-integrations)** — Meta Ads, TikTok for Business, FunnelFox y pipes de S3/GCS. - **[Enlaces de seguimiento](ua-tracking-links)** — generados en Adapty, añadidos a tus campañas y vinculados a instalaciones en el primer lanzamiento. - **[Deeplinks diferidos](ua-deferred-data)** — lleva a los nuevos usuarios al contenido correcto de la app en el primer lanzamiento, aunque hayan hecho clic antes de instalar. - **[Datos de atribución](ua-attribution-data)** — recibe el payload de atribución en tu app para lógica personalizada. ## Adapty Mail \{#adapty-mail\} [Adapty Mail](adapty-mail) convierte los datos de usuario en campañas de email generadas por IA. Construye un [perfil de marca](mail-brand) a partir del listado de tu app en el store, la landing page y los perfiles sociales, y genera una secuencia completa de emails en minutos. Los emails se envían desde tu dominio verificado y cada compra se atribuye al email que la originó. No necesitas ninguna plataforma de email adicional. Características principales: - **[Campañas](mail-email-campaigns)** — una secuencia completa de varios correos, generada en un solo paso. Empieza a enviarse en cuanto la vinculas a un flow. - **[Flows](mail-flows)** — vincula una campaña a un segmento y a un evento de suscripción como *nunca ha comprado* o *problema de facturación*, para que se envíe automáticamente. - **[Web paywall](mail-checkout)** — páginas de pago personalizadas, una por destinatario, para que las compras se atribuyan al correo. - **[Segmentos](mail-segments)** y **[perfiles](mail-profiles)** — llega a los usuarios con más probabilidades de convertir. Crea un segmento por estado de compra, país o ingresos, y dispara una campaña o flow a partir de él. Los datos provienen de Adapty Core y se limitan a perfiles identificados que tengan un correo electrónico. ## Conecta Adapty con tu stack existente \{#connect-adapty-to-your-existing-stack\} Las [integraciones con terceros](configuration) reenvían los [eventos](events) de suscripción a las plataformas de analítica, atribución y mensajería que ya usa tu equipo: - **Analytics**: [Amplitude](amplitude), [Mixpanel](mixpanel), [PostHog](posthog), [Firebase / Google Analytics](firebase-and-google-analytics), [AppMetrica](appmetrica), [SplitMetrics Acquire](splitmetrics). - **Atribución**: [AppsFlyer](appsflyer), [Adjust](adjust), [Branch](branch), [Airbridge](airbridge), [Apple Ads](apple-search-ads), [Singular](singular), [Tenjin](tenjin), [Asapty](asapty), [Facebook Ads](facebook-ads). - **Mensajería**: [Braze](braze), [OneSignal](onesignal), [Pushwoosh](pushwoosh), [Slack](slack). - **Webhook y ETL**: [webhooks](webhook) personalizados, [Amazon S3](s3-exports), [Google Cloud Storage](google-cloud-storage). ## El ecosistema más amplio \{#the-wider-ecosystem\} Otros dos productos se conectan a tus datos de Adapty, pero cubren necesidades fuera del ciclo de vida principal. ### FunnelFox [FunnelFox](https://funnelfox.com/docs/integrations/subscription-management/adapty) es un creador de funnels web-to-app. Crea landing pages y cuestionarios que llevan a los usuarios a tu app. Su motor de [facturación](https://funnelfox.com/docs/billing/integration-billing-funnelfox) recoge sus pagos en la web. Conecta FunnelFox a Adapty para el seguimiento de suscripciones y la atribución de ingresos. ### Adapty Finance [Adapty Finance](https://adapty.io/blog/introducing-adapty-finance/) adelanta tus ingresos futuros por suscripciones, para que no tengas que esperar los pagos de la store. ## Próximos pasos \{#next-steps\} - **[¿Es Adapty lo que necesito?](is-adapty-right-for-me)** — un recorrido por la plataforma centrado en casos de uso. - **[Guía de inicio rápido](quickstart)** — conecta un store, añade productos e integra el SDK. - **[Gestiona Adapty con IA](manage-adapty-with-ai)** — todos los puntos de entrada para usar Adapty con una herramienta de desarrollo asistida por IA. --- # File: generate-in-app-purchase-key --- --- title: "Generar la In-App Purchase Key en App Store Connect" description: "Genera una clave de compra in-app para transacciones seguras." --- La **In-App Purchase Key** es una clave de API especializada que se crea en App Store Connect para validar las compras confirmando su autenticidad. :::note Para generar claves de API para la App Store Server API, debes tener el rol de Admin o de Account Holder en App Store Connect. También puedes consultar cómo generar claves de API en la [Documentación para Desarrolladores de Apple](https://developer.apple.com/documentation/appstoreserverapi/creating-api-keys-to-authorize-api-requests). ::: 1. Abre **App Store Connect**. Ve a la sección [**Users and Access** → **Integrations** → **In-App Purchase**](https://appstoreconnect.apple.com/access/integrations/api/subs). 2. Haz clic en el botón de añadir **(+)** junto al título **Active**. <img src="/assets/shared/img/6d737db-generate_in-app_key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la ventana **Generate In-App Purchase Key** que se abre, introduce el nombre de la clave para tu referencia futura. No se utilizará en Adapty. 4. Haz clic en el botón **Generate**. Una vez que se cierre la ventana **Generate in-App Purchase Key**, verás la clave creada en la lista **Active**. <img src="/assets/shared/img/fac066b-download_inapp_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Una vez generada tu clave de API, haz clic en el botón **Download In-App Purchase Key** para obtener la clave como archivo. <img src="/assets/shared/img/d59faff-download_in-app_purchase_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. En la ventana **Download in-App Purchase Key**, haz clic en el botón **Download**. El archivo se guardará en tu ordenador. Es fundamental mantener este archivo seguro para subirlo al Adapty Dashboard en el futuro. Ten en cuenta que el archivo generado solo se puede descargar una vez, así que asegúrate de guardarlo en un lugar seguro hasta que lo subas. La clave .p8 generada desde la sección **In-App Purchase** se utilizará al [configurar la integración inicial de Adapty con el App Store](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file). **Próximos pasos:** - [Configurar la integración con App Store](app-store-connection-configuration) --- # File: app-store-connection-configuration --- --- title: "Configurar la integración con App Store" description: "Configura tu conexión con App Store para un seguimiento de suscripciones sin interrupciones." --- <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/VJQbzoTCkqs?si=l7BPX9mIu6GVGZ0Z" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Esta sección explica cómo establecer la conexión entre App Store y Adapty para tu app de iOS. Es necesaria para que podamos mostrar analíticas de suscripciones y validar compras. Puedes completar la integración durante el onboarding inicial o más tarde en **App Settings** dentro del Adapty Dashboard. Aunque puede que hayas configurado inicialmente la integración de tu app móvil con Adapty durante el onboarding, puedes modificar estos ajustes más adelante en **App settings**. :::danger Los cambios de configuración se pueden realizar de forma segura durante la fase Sandbox, hasta que tu aplicación móvil esté publicada con el SDK de Adapty instalado. Los cambios realizados después del lanzamiento pueden romper el flujo de compra en tu app. ::: ## Paso 1. Proporciona el Bundle ID y el Apple app ID \{#step-1-provide-bundle-id-and-apple-app-id\} El Bundle ID es el identificador único de tu app en el App Store. Es necesario para el funcionamiento básico de Adapty, como el procesamiento de suscripciones. 1. Abre [App Store Connect](https://appstoreconnect.apple.com/apps). Selecciona tu app y ve a la sección **General** → **App Information**. 2. Copia el **Bundle ID** en la subsección **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Abre la pestaña [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) desde el menú superior de Adapty y pega el valor copiado en el campo **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Vuelve a la página **App information** en App Store Connect y copia el **Apple ID** desde allí. 5. En la página [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) del Adapty Dashboard, pega el ID en el campo **Apple app ID**. ## Paso 2. Proporciona el Issuer ID y el Key ID \{#step-2-provide-issuer-id-and-key-id\} El **In-app purchase Issuer ID**, denominado **Issuer ID** en App Store Connect, es un ID especial que identifica al emisor que creó el token de autenticación. El **In-App Purchase Key ID**, denominado **Key ID** en App Store Connect, es un identificador único asociado a una clave criptográfica que generaste en la sección [Generar la clave de compra in-app en App Store Connect](generate-in-app-purchase-key). 1. Abre **App Store Connect**. Ve a la sección [**Users and Access** → **Integrations** → **In-App Purchase**](https://appstoreconnect.apple.com/access/integrations/api/subs). 2. En la lista **Active**, busca la clave que creaste en la sección [Generar la clave de compra in-app en App Store Connect](generate-in-app-purchase-key). <img src="/assets/shared/img/19a2868-issuer_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Copia el **Issuer ID** y pégalo en el campo **In-app purchase Issuer ID** del Adapty Dashboard. <img src="/assets/shared/img/c2b42e7-issuer_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Copia el **Key ID** y pégalo en el campo **In-app purchase Key ID** del Adapty Dashboard. ## Paso 3. Sube el archivo de clave de compra in-app \{#step-3-upload-in-app-purchase-key-file\} Sube el archivo de **In-App Purchase Key** que descargaste en la sección [Generar la clave de compra in-app en App Store Connect](generate-in-app-purchase-key) <img src="/assets/shared/img/88cdfff-download_inapp_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> en el campo **Private key (.p8 file)** del Adapty Dashboard. <img src="/assets/shared/img/253b840-in-app_file_upload.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 4. Para pruebas y ofertas especiales: configura las ofertas promocionales \{#step-4-for-trials-and-special-offers--set-up-promotional-offers\} :::important Este paso es obligatorio si tu app tiene [pruebas u otras ofertas promocionales](offers). ::: 1. Copia el mismo ID de clave que usaste en el [Paso 2](#step-2-provide-issuer-id-and-key-id) en el campo **Subscription key ID** de la sección **App Store promotional offers**. 2. Sube el mismo archivo **In-App Purchase Key** que usaste en el [Paso 3](#step-3-upload-in-app-purchase-key-file) al área **Subscription key (.p8 file)** de la sección **App Store promotional offers**. <img src="/assets/shared/img/promo-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 5. Introduce el App Store shared secret \{#step-5-enter-app-store-shared-secret\} El **App Store shared secret**, también conocido como App Store Connect Shared Secret, es una cadena hexadecimal de 32 caracteres que se utiliza para la validación de recibos de compras in-app y suscripciones. 1. Abre [App Store Connect](https://appstoreconnect.apple.com/apps). Selecciona tu app y ve a la sección **General** → **App Information**. 2. Desplázate hasta la subsección **App-Specific Shared Secret**. <img src="/assets/shared/img/2bd112a-shared_secret_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::info Si la subsección **App-Specific Shared Secret** no aparece, asegúrate de tener el rol de Account Holder o Admin. Si tienes el rol de Admin y aun así no ves la subsección **App-Specific Shared Secret**, pide al Account Holder de la app (la persona que creó la aplicación en App Store Connect) que genere el shared secret de la app. Después de eso, la subsección también será visible para los Admins. ::: 3. Haz clic en el botón **Manage**. <img src="/assets/shared/img/2d8b4c0-shared_secret_apple_copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. En la ventana **App-Specific Shared Secret** que se abre, copia el **Shared Secret**. Si no ves ningún shared secret, haz clic primero en el botón **Manage** o **Generate** (el que esté disponible) y luego copia el **Shared Secret**. 5. Pega el **Shared Secret** copiado en el campo **App Store shared secret** del Adapty Dashboard. <img src="/assets/shared/img/4f9624d-shared_secret.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Haz clic en el botón **Save** del Adapty Dashboard para confirmar los cambios. ## Paso 6. Añade la clave de API de App Store Connect \{#step-6-add-app-store-connect-api-key\} Genera una clave de API de App Store Connect y añádela a Adapty para poder [gestionar tus productos en el App Store desde el Adapty Dashboard](create-product#create-product-and-push-to-store): 1. En App Store Connect, ve a [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api) y haz clic en **+**. <img src="/assets/shared/img/app-store-connect-api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. En la ventana **Generate API key window**, introduce un nombre para la clave y otórgale acceso **Admin**. <img src="/assets/shared/img/generate-api-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Download** junto a tu clave. Ten en cuenta que solo puedes descargarla una vez. <img src="/assets/shared/img/download-api-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. En el Adapty Dashboard, ve a [**App settings > iOS SDK**](https://app.adapty.io/settings/ios-sdk) y haz clic en **Connect API key**. <img src="/assets/shared/img/connect-api-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Rellena los campos en la ventana: - **Issuer ID**: Cópialo desde [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api). Está encima de la tabla **API keys**. <img src="/assets/shared/img/issuer-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **Key ID**: Cópialo desde [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api). Está en la tabla **API keys** junto a tu clave. <img src="/assets/shared/img/key-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **API key**: Sube el archivo de clave API que has descargado desde App Store Connect. <img src="/assets/shared/img/app-store-connect-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Haz clic en **Connect**. **Qué hacer a continuación** - [Activar las notificaciones del servidor de App Store](enable-app-store-server-notifications) --- # File: enable-app-store-server-notifications --- --- title: "Activar notificaciones de servidor de App Store" description: "Activa las notificaciones de servidor de App Store para rastrear eventos de suscripción en tiempo real." --- Configurar las notificaciones de servidor de App Store es fundamental para garantizar la precisión de los datos, ya que te permite recibir actualizaciones en tiempo real desde App Store, incluyendo información sobre reembolsos y otros eventos. :::important Se requiere Adapty iOS SDK 2.10.0 o posterior para soporte completo de App Store Server Notifications V2. ::: 1. Copia la **URL for App Store server notification** en el Adapty Dashboard. <img src="/assets/shared/img/2901185-app_server_notifications.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Abre [App Store Connect](https://appstoreconnect.apple.com/apps). Selecciona tu aplicación y ve a la sección **General** → **App Information**, subsección **App Store Server Notifications**. 3. Pega la **URL for App Store server notification** copiada en los campos **Production Server URL** y **Sandbox Server URL**. <img src="/assets/shared/img/86fb3d2-app_server_notifications_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Reenvío de eventos sin procesar \{#raw-events-forwarding\} En algunos casos, puede que quieras seguir recibiendo eventos S2S sin procesar desde Apple. Para continuar recibiéndolos mientras usas Adapty, simplemente añade tu endpoint al campo **URL for forwarding raw Apple events** y te enviaremos los eventos tal como los recibimos de Apple. <img src="/assets/shared/img/e9f4bba-CleanShot_2021-03-16_at_19.30.272x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **Siguientes pasos** Configura el SDK de Adapty para: - [iOS](sdk-installation-ios) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) --- # File: troubleshoot-app-store-integration --- --- title: "Solucionar problemas de integración con App Store" description: "Resuelve los problemas más comunes de configuración con la App Store de Apple: acuerdos pendientes, retrasos en notificaciones del servidor y discrepancias de precios." --- Este artículo cubre los problemas más comunes de integración con App Store. Cada sección describe los síntomas, la causa raíz y la solución. ## Los productos no aparecen \{#products-dont-appear\} Dos síntomas distintos apuntan a la misma causa raíz: - La clave API de App Store Connect está configurada correctamente, pero Adapty no puede obtener los productos. - Los productos existen en App Store Connect pero no aparecen en Adapty, o aparecen menos de los esperados. El SDK reporta "Product Id not found" al intentar realizar una compra. La causa más común es que los **acuerdos de Apple estén sin firmar** — el acuerdo de pago, los formularios fiscales o los formularios bancarios en estado pendiente o sin firmar. Cuando los acuerdos están pendientes, la API de App Store Connect devuelve silenciosamente un 403 en los endpoints relacionados con productos. No se muestra ningún error claro en Adapty; los productos simplemente se descartan sin aviso. Ve a **App Store Connect → Agreements, Tax, and Banking** y firma todos los acuerdos pendientes. Luego vuelve a sincronizar en **App settings → iOS SDK** de Adapty. ## Las notificaciones del servidor de App Store muestran "Delayed" \{#app-store-server-notifications-show-delayed\} En App Store Connect, el estado de las App Store Server Notifications puede aparecer como **Delayed**. Esto significa que Apple tiene retraso en el envío de notificaciones de eventos de suscripción: renovaciones, cancelaciones y problemas de facturación se acumulan en cola y llegan tarde. Las estadísticas de instalaciones no se ven afectadas. Adapty contabiliza las instalaciones desde el primer lanzamiento de la app, no a partir de notificaciones del servidor. Si los datos de renovación o cancelación van por detrás, el estado Delayed es la causa más probable. El estado suele desaparecer automáticamente a medida que Apple procesa el backlog. ## Los precios en Adapty no coinciden con App Store \{#prices-in-adapty-dont-match-app-store\} El campo **price** en la página de edición de productos de Adapty se comporta de forma distinta según cómo se haya añadido el producto. Si creas un producto en Adapty y lo publicas en la store desde el dashboard, este precio se utiliza como precio inicial en la store. Si añades un producto que ya existe en la store, este precio es un marcador de posición. Las analíticas, integraciones y SDK de Adapty utilizan los precios reales obtenidos de App Store, independientemente de este valor. Los cambios en los precios de App Store no se sincronizan para actualizar el marcador de posición, y por ahora no es posible editar ese marcador desde el dashboard. ## La exportación de precios en CSV está vacía \{#csv-price-export-is-empty\} Si tu exportación de precios en CSV solo devolvió las cabeceras de columna, significa que la clave API de App Store Connect no está completamente configurada. Consulta [Paso 6 — Añadir clave API de App Store Connect](app-store-connection-configuration#step-6-add-app-store-connect-api-key). ## No se pueden publicar nuevos productos en App Store \{#cant-push-new-products-to-app-store\} Adapty puede publicar nuevos productos en App Store Connect cuando los creas en el dashboard. La opción de publicar queda bloqueada si la integración con App Store no está completamente configurada. Se requieren dos ajustes: - **Apple app ID**: Configúralo en [Paso 1 — Proporcionar Bundle ID y Apple app ID](app-store-connection-configuration#step-1-provide-bundle-id-and-apple-app-id). - **App Store Connect API key**: Configúrala en [Paso 6 — Añadir clave API de App Store Connect](app-store-connection-configuration#step-6-add-app-store-connect-api-key). --- # File: enabling-of-devepoler-api --- --- title: "Habilitar las APIs de desarrollador en Google Play Console" description: "Habilita la API de desarrollador de Adapty para automatizar y simplificar la gestión de suscripciones en tu app." --- <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/7dN50n5bcLc?si=c2znttIb--4VcrRO" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Si tu app móvil está disponible en Play Store, activar las APIs de desarrollador es fundamental para integrarla con Adapty. Este paso garantiza una comunicación fluida entre tu app y nuestra plataforma, facilitando procesos automatizados y análisis de datos en tiempo real para optimizar tu modelo de suscripciones. Las siguientes APIs deben estar habilitadas: - [Google Play Android Developer API](https://console.cloud.google.com/apis/library/androidpublisher.googleapis.com) - [Google Play Developer Reporting API](https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com) - [Cloud Pub/Sub API](https://console.cloud.google.com/marketplace/product/google/pubsub.googleapis.com) Si tu app no se distribuye a través de Play Store, puedes omitir este paso. Sin embargo, si vendes a través de Play Store, puedes posponerlo por ahora, aunque es esencial para el funcionamiento básico de Adapty. Una vez completado el proceso de onboarding, puedes configurar los ajustes del store en la sección **App settings**. Así se habilitan las APIs de desarrollador en Google Play Console: 1. Abre la [Google Cloud Console](https://console.cloud.google.com/). 2. En la esquina superior izquierda de la ventana de Google Cloud, selecciona el proyecto que deseas usar o crea uno nuevo. Asegúrate de usar el mismo proyecto de Google Cloud hasta que hayas subido el archivo de clave de cuenta de servicio a Adapty. <img src="/assets/shared/img/fd66a11-google_cloud_project.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Abre la página de la [**Google Play Android Developer API**](https://console.cloud.google.com/apis/library/androidpublisher.googleapis.com). <img src="/assets/shared/img/f754f72-google_play_api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Haz clic en el botón **Enable** y espera a que aparezca el estado **Enabled**. Esto indica que la Google Android Developer API está habilitada. <img src="/assets/shared/img/d47ed14-google_play_api_create_credentials.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Abre la página de la [**Google Play Developer Reporting API**](https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com). <img src="/assets/shared/img/966cf73-Google_play_developer_reporting_api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Haz clic en el botón **Enable** y espera a que aparezca el estado **Enabled**. <img src="/assets/shared/img/e776d77-Google_play_developer_reporting_api_enabled.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Abre la página de la [**Cloud Pub/Sub API**](https://console.cloud.google.com/marketplace/product/google/pubsub.googleapis.com). <img src="/assets/shared/img/b13f609-enable_Cloud_Pub_Sub_API.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. Haz clic en el botón **Enable** y espera a que aparezca el estado **Enabled**. <img src="/assets/shared/img/3f45602-Cloud_Pub_Sub_API_enabled.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Las APIs de desarrollador están habilitadas. Puedes verificarlo en la página [**APIs & Services**](https://console.cloud.google.com/apis/dashboard) de la Google Cloud Console. Desplázate hacia abajo por la página y comprueba que la tabla al final contiene las 3 APIs: - Google Play Android Developer API - Google Play Developer Reporting API - Cloud Pub/Sub API <img src="/assets/shared/img/b81d174-google_enabled_api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **Siguiente paso** - [Crear una cuenta de servicio en la Google Cloud Console](create-service-account) --- # File: create-service-account --- --- title: "Crear una cuenta de servicio en Google Cloud Console" description: "Aprende a crear una cuenta de servicio para el acceso seguro a la API en Adapty." --- Para que Adapty pueda automatizar el acceso a los datos, es necesario crear una cuenta de servicio en Google Play Console. 1. Abre la sección [**IAM & Admin** -> **Service accounts**](https://console.cloud.google.com/iam-admin/serviceaccounts) de Google Cloud Console. Asegúrate de estar usando el proyecto correcto. <img src="/assets/shared/img/17bbf45-google_cloud_create_service_account.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. En la ventana **Service accounts**, haz clic en el botón **Create service account**. <img src="/assets/shared/img/b93eec1-service_account_details.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la subsección **Service account details** de la ventana **Create service account**, introduce el **Service Account Name** que quieras. Te recomendamos incluir "Adapty" en el nombre para indicar el propósito de esta cuenta. El **Service account ID** se generará automáticamente. 4. Copia la dirección de correo electrónico de la cuenta de servicio y guárdala para usarla más adelante. 5. Haz clic en el botón **Create and continue**. <img src="/assets/shared/img/e69d713-grant_access_to_project.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. En la lista desplegable **Select a role** de la subsección **Grant this service account access to project**, selecciona **Pub/Sub -> Pub/Sub Admin**. Este rol es necesario para habilitar las notificaciones en tiempo real para desarrolladores. <img src="/assets/shared/img/976299c-service_account_role.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Haz clic en el botón **Add another role**. 8. En la nueva lista desplegable **Role**, selecciona **Monitoring -> Monitoring Viewer**. Este rol es necesario para permitir la monitorización de la cola de notificaciones. 9. Haz clic en el botón **Continue**. <img src="/assets/shared/img/ffe8d82-grant_user_access.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 10. Haz clic en el botón **Done** sin realizar ningún cambio. Se abrirá la ventana **Service accounts**. **Siguiente paso** - [Conceder permisos a la cuenta de servicio en Google Play Console](grant-permissions-to-service-account) --- # File: grant-permissions-to-service-account --- --- title: "Conceder permisos a la cuenta de servicio en la Google Play Console" description: "Concede permisos a las cuentas de servicio para un acceso seguro y eficiente a la API." --- Concede los permisos necesarios a la cuenta de servicio que Adapty utilizará para gestionar suscripciones y validar compras. 1. Abre la página [**Users and permissions**](https://play.google.com/console/u/0/developers/8970033217728091060/users-and-permissions) en la Google Play Console y haz clic en el botón **Invite new users**. <img src="/assets/shared/img/7b0e614-users_and_permissions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. En la página **Invite user**, introduce el correo electrónico de los usuarios de servicio que has creado. <img src="/assets/shared/img/3afd002-invite_user.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Cambia a la pestaña **Account permissions**. <img src="/assets/shared/img/4e2717b-account_permissions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Selecciona los siguientes permisos: - View app information and download bulk reports (read-only) - View financial data, orders, and cancellation survey responses - Manage orders and subscriptions - Manage store presence 5. Haz clic en el botón **Invite user**. 6. En la ventana **Send invite?**, haz clic en el botón **Send invite**. La cuenta de servicio aparecerá en la lista de usuarios. **Siguientes pasos** - [Genera el archivo de clave de la cuenta de servicio en la Google Play Console](create-service-account-key-file) --- # File: create-service-account-key-file --- --- title: "Generar el archivo de clave de cuenta de servicio en Google Play Console" description: "Aprende a crear un archivo de clave de cuenta de servicio para una integración fluida con Adapty." --- Para vincular tu app móvil en Play Store con Adapty, necesitarás generar archivos especiales de clave de cuenta de servicio en Google Play Console y subirlos a Adapty. Estos archivos ayudan a proteger tu app y evitan accesos no autorizados. :::warning Por lo general, la nueva cuenta de servicio tarda al menos 24 horas en activarse. Sin embargo, existe un [truco](https://stackoverflow.com/a/60691844). Tras crear la cuenta de servicio en [Google Play Console](https://play.google.com/apps/publish/), abre cualquier aplicación y ve a **Monetize** -> **Products** -> **Subscriptions/In-app products**. Edita la descripción de cualquier producto y guarda los cambios. Esto debería activar la cuenta de servicio de inmediato, y luego puedes revertir los cambios. ::: 1. Abre la sección [**Service accounts**](https://console.cloud.google.com/iam-admin/serviceaccounts) en Google Play Console. Asegúrate de haber seleccionado el proyecto correcto. <img src="/assets/shared/img/c3156cb-action_manage_keys.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. En la ventana que se abre, haz clic en **Add key** y elige **Create new key** en el menú desplegable. <img src="/assets/shared/img/44b30ee-create_new_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la ventana **Create private key for [Your_project_name]**, haz clic en **Create**. Tu clave privada se guardará en tu ordenador como un archivo JSON. Puedes encontrarlo usando el nombre de archivo que aparece en la ventana **Private key saved to your computer**. <img src="/assets/shared/img/e7b8101-cretae_private_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. En la ventana **Create private key for Your_project_name**, haz clic en el botón **Create**. Esta acción guardará tu clave privada en tu ordenador como un archivo JSON. Puedes usar el nombre del archivo que aparece en la ventana **Private key saved to your computer** para localizarlo si lo necesitas. <img src="/assets/shared/img/187ddc6-Private_key_saved.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Necesitarás este archivo cuando [configures la integración con Google Play Store](google-play-store-connection-configuration). :::warning Por lo general, la nueva cuenta de servicio tarda al menos 24 horas en activarse. Sin embargo, existe un [truco](https://stackoverflow.com/a/60691844). Tras crear la cuenta de servicio en [Google Play Console](https://play.google.com/apps/publish/), abre cualquier aplicación y ve a **Monetize** -> **Products** -> **Subscriptions/In-app products**. Edita la descripción de cualquier producto y guarda los cambios. Esto debería activar la cuenta de servicio de inmediato, y luego puedes revertir los cambios. ::: **Siguientes pasos** - [Configurar la integración con Google Play Store](google-play-store-connection-configuration) --- # File: google-play-store-connection-configuration --- --- title: "Configurar la integración con Google Play Store" description: "Configura la conexión con Google Play Store en Adapty para gestionar las compras in-app sin problemas." --- Esta sección describe el proceso de integración de tu aplicación móvil distribuida a través de Google Play con Adapty. Tendrás que introducir los datos de configuración de tu app desde la Play Store en el Adapty Dashboard. Este paso es fundamental para validar las compras y recibir actualizaciones de suscripciones desde la Play Store dentro de Adapty. Puedes completar este proceso durante el onboarding inicial o realizar cambios posteriormente en los **App Settings** del Adapty Dashboard. :::danger Los cambios de configuración solo son válidos antes de publicar tu aplicación móvil con los paywalls de Adapty integrados. Modificar la configuración tras el lanzamiento romperá la integración y los paywalls dejarán de mostrarse en tu aplicación. ::: ## Paso 1. Proporciona el Package name \{#step-1-provide-package-name\} El Package name es el identificador único de tu app en Google Play Store. Es necesario para el funcionamiento básico de Adapty, como el procesamiento de suscripciones. 1. Abre la [Google Play Developer Console](https://play.google.com/console/u/0/developers). 2. Selecciona la app cuyo ID necesitas. Se abrirá la ventana **Dashboard**. <img src="/assets/shared/img/7889edb-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Busca el ID del producto bajo el nombre de la aplicación y cópialo. 4. Abre los [**App settings**](https://app.adapty.io/settings/android-sdk) desde el menú superior de Adapty. <img src="/assets/shared/img/b00066c-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. En la pestaña **Android SDK** de la ventana **App settings**, pega el **Package name** copiado. ## Paso 2. Sube el archivo de clave de cuenta \{#step-2-upload-the-account-key-file\} 1. Sube el archivo de clave privada de cuenta de servicio en formato JSON que creaste en el paso [Crear archivo de clave de cuenta de servicio](create-service-account) en el área **Service account key file**. <img src="/assets/shared/img/20fdba1-service_key_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> No olvides hacer clic en el botón **Save** para confirmar los cambios. **Próximos pasos** - [Activar las notificaciones en tiempo real para desarrolladores (RTDN) en la Google Play Console](enable-real-time-developer-notifications-rtdn) --- # File: enable-real-time-developer-notifications-rtdn --- --- title: "Habilitar notificaciones en tiempo real para desarrolladores (RTDN) en Google Play Console" description: "Mantente informado sobre eventos críticos y garantiza la exactitud de los datos habilitando las Notificaciones en Tiempo Real para Desarrolladores (RTDN) en Google Play Console para Adapty. Aprende a configurar RTDN para recibir actualizaciones instantáneas sobre reembolsos y otros eventos importantes de la Play Store" --- Configurar las notificaciones en tiempo real para desarrolladores (RTDN) es fundamental para garantizar la exactitud de los datos, ya que te permite recibir actualizaciones al instante desde la Play Store, incluyendo información sobre reembolsos y otros eventos. ## Habilitar notificaciones \{#enable-notifications\} 1. Asegúrate de tener **Google Cloud Pub/Sub** habilitado. Abre [este enlace](https://console.cloud.google.com/flows/enableapi?apiid=pubsub) y selecciona el proyecto de tu app. Si todavía no has habilitado **Google Cloud Pub/Sub**, debes hacerlo aquí. <img src="/assets/shared/img/pubsub.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Ve a [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) desde el menú superior de Adapty y copia el contenido del campo **Enable Pub/Sub API** que aparece junto al título **Google Play RTDN topic name**. <img src="/assets/shared/img/a72ff2d-copy_topic.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note Si el contenido del campo **Enable Pub/Sub API** tiene un formato incorrecto (el formato correcto empieza por `projects/...`), consulta la sección [Corregir el formato incorrecto en el campo Enable Pub/Sub API](enable-real-time-developer-notifications-rtdn#fixing-incorrect-format-in-enable-pubsub-api-field) para obtener ayuda. ::: 3. Abre la [Google Play Console](https://play.google.com/console/), elige tu app y ve a **Monetize with Play** -> **Monetization setup**. En la sección **Google Play Billing**, marca la casilla **Enable real-time notifications**. 4. Pega el contenido del campo **Enable Pub/Sub API** que copiaste en los **App Settings** de Adapty en el campo **Topic name**. 5. Haz clic en **Save changes** en la Google Play Console. <img src="/assets/shared/img/e55ba0e-paste_topic_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Probar las notificaciones \{#test-notifications\} Para comprobar si te has suscrito correctamente a las notificaciones en tiempo real para desarrolladores: 1. Guarda los cambios en la configuración de Google Play Console. 2. Debajo del campo **Topic name** en Google Play Console, haz clic en **Send test notification**. <img src="/assets/shared/img/rtdn-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Ve a [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) en Adapty. Si se ha enviado una notificación de prueba, verás su estado encima del nombre del topic. <img src="/assets/shared/img/rtdn-adapty-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Corregir el formato incorrecto en el campo Enable Pub/Sub API \{#fixing-incorrect-format-in-enable-pubsub-api-field\} Si el contenido del campo **Enable Pub/Sub API** tiene un formato incorrecto (el formato correcto empieza por `projects/...`), sigue estos pasos para solucionar el problema: ### 1. Verificar la habilitación de la API y los permisos \{#1-verify-api-enablement-and-permissions\} Comprueba detenidamente que todas las APIs necesarias estén habilitadas y que los permisos estén correctamente concedidos a la cuenta de servicio. Aunque ya hayas completado estos pasos, es importante revisarlos de nuevo para asegurarte de que no se omitió ninguno. Repite los pasos de las siguientes secciones: 1. [Habilitar las APIs de desarrollador en Google Play Console](enabling-of-devepoler-api) 2. [Crear una cuenta de servicio en Google Cloud Console](create-service-account) 3. [Conceder permisos a la cuenta de servicio en Google Play Console](grant-permissions-to-service-account) 4. [Generar el archivo de clave de la cuenta de servicio en Google Play Console](create-service-account-key-file) 5. [Configurar la integración con Google Play Store](google-play-store-connection-configuration) ### 2. Ajustar las políticas de dominio \{#2-adjust-domain-policies\} Cambia las políticas **Domain restricted contacts** y **Domain restricted sharing**: 1. Abre la [Google Cloud Console](https://console.cloud.google.com/) y selecciona el proyecto donde creaste la cuenta de servicio para gestionar tu app. 2. En la sección **Quick Access**, elige **IAM & Admin**. <img src="/assets/shared/img/google-cloud-IAM-and-Admin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En el panel izquierdo, elige **Organization Policies**. 4. Busca la política **Domain restricted contacts**. <img src="/assets/shared/img/google-cloud-policy-action.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en el botón de puntos suspensivos en la columna **Actions** y elige **Edit policy**. 6. En la ventana de edición de la política: 1. En **Policy source**, selecciona el botón de opción **Override parent's policy**. 2. En **Policy enforcement**, selecciona el botón de opción **Replace**. 3. En **Rules**, haz clic en el botón **ADD A RULE**. <img src="/assets/shared/img/google-cloud-edit-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. En **New rule** -> **Policy values**, elige **Allow All**. <img src="/assets/shared/img/google-cloud-allow-all-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en **SET POLICY**. 7. Repite los pasos 4-6 para la política **Domain restricted sharing**. Por último, vuelve a generar el contenido del campo **Enable Pub/Sub API** situado junto al título **Google Play RTDN topic name**. El campo tendrá ahora el formato correcto. Asegúrate de cambiar **Policy source** de vuelta a **Inherit parent's policy** para las políticas actualizadas una vez que hayas habilitado correctamente las Notificaciones en Tiempo Real para Desarrolladores (RTDN). ## Reenvío de eventos sin procesar \{#raw-events-forwarding\} En algunos casos, puede que quieras seguir recibiendo eventos S2S sin procesar de Google. Para continuar recibiéndolos mientras usas Adapty, simplemente añade tu endpoint en el campo **URL for forwarding raw Google events** y enviaremos los eventos tal cual los recibimos de Google. <img src="/assets/shared/img/e388892-001774-September-22-GhkjOFbT.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- **Próximos pasos** Configura el SDK de Adapty para: - [Android](sdk-installation-android) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) --- # File: stripe --- --- title: "Integración inicial con Stripe" description: "Integra Stripe con Adapty para procesar pagos de suscripciones sin problemas." --- Adapty admite flujos de suscripción web2app mediante el seguimiento de pagos y suscripciones realizados a través de [Stripe](https://stripe.com/). Esta integración cubre compras iniciadas desde la web (Stripe Checkout, páginas de pago alojadas o flujos web personalizados) y las sincroniza con el acceso a la app móvil y la analítica. Es útil en los siguientes escenarios: - Proporcionar acceso automático a funciones de pago para usuarios que compraron en la web pero luego instalaron la app e iniciaron sesión en su cuenta - Tener toda la analítica de suscripciones en un único Adapty Dashboard (incluyendo cohortes, predicciones y el resto de herramientas de analítica) Aunque las compras web son cada vez más populares para las apps, el App Store de Apple permite un sistema diferente al de las compras in-app para productos digitales únicamente en EE. UU. Asegúrate de no promocionar tus suscripciones web dentro de tu app para otros países. De lo contrario, tu app podría ser rechazada o vetada. Los pasos a continuación describen cómo configurar la integración con Stripe. :::important Esta integración se centra en el seguimiento y sincronización de compras web de Stripe. Si necesitas enviar usuarios desde la app a un checkout web, consulta [Web paywalls](web-paywall). ::: ## 1\. Conecta Stripe a Adapty \{#1-connect-stripe-to-adapty\} Esta integración se basa principalmente en que Adapty obtenga datos de suscripción de Stripe a través del webhook. Por lo tanto, necesitas conectar tu cuenta de Adapty a tu cuenta de Stripe proporcionando las claves API y usando la URL del webhook de Adapty en Stripe. Para automatizar la configuración del webhook, instala la app de Adapty en Stripe: :::note Los pasos a continuación son los mismos para los modos de Producción y Prueba de Stripe, pero necesitarás usar claves API diferentes para cada uno. ::: 0. Determina si vas a conectar Stripe en modo de prueba o en modo en vivo. Si lo haces inicialmente en modo de prueba, tendrás que repetir los pasos a continuación para el modo en vivo también. 1. Ve al [Stripe App Marketplace](https://marketplace.stripe.com/apps/adapty) e instala la app de Adapty. Ten en cuenta que el modo sandbox no admite la instalación de apps. Solo puedes hacerlo en modo de producción o de prueba. <img src="/assets/shared/img/stripe1.png"/> 2. Otorga los permisos necesarios a la app. Esto permitirá que Adapty acceda a los datos e historial de suscripciones. Luego, haz clic en **Continue to app settings** para continuar. En la parte inferior del pop-up de permisos, puedes seleccionar si instalar la app en modo en vivo o de prueba. <img src="/assets/shared/img/stripe2.png"/> 3. En el pop-up, genera una nueva clave restringida. Tendrás que verificar tu identidad mediante tu correo electrónico, Touch ID o clave de seguridad. Una vez que generes una clave, no podrás volver a verla, así que guárdala de forma segura en un gestor de contraseñas o un almacén de secretos. <img src="/assets/shared/img/stripe4.png"/> 4. Copia la clave generada del pop-up y ve a [App Settings → Stripe](https://app.adapty.io/settings/stripe) en Adapty. Pega la clave en la sección **Stripe App Restricted API Key** según tu modo. Ten en cuenta que debes generar claves diferentes para los modos de prueba y en vivo. <img src="/assets/shared/img/Stripe3.png"/> ¡Todo listo! A continuación, crea tus productos en Stripe y añádelos a Adapty. <Details> <summary>Flujo de instalación obsoleto</summary> 1. Ve a [Developers → API Keys](https://dashboard.stripe.com/apikeys) en Stripe: <img src="/assets/shared/img/6549602-CleanShot_2023-12-06_at_17.29.122x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el botón **Reveal live (test) key** junto al título **Secret key**, cópiala y ve a [App Settings → Stripe](https://app.adapty.io/settings/stripe) en Adapty. Pega la clave aquí: <img src="/assets/shared/img/2989508-CleanShot_2023-12-07_at_14.59.122x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. A continuación, copia la URL del webhook que aparece en la parte inferior de la misma página en Adapty. Ve a [**Developers** → **Webhooks**](https://dashboard.stripe.com/webhooks) en Stripe y haz clic en el botón **Add endpoint**: <img src="/assets/shared/img/e7149f5-CleanShot_2023-12-07_at_17.31.392x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Pega la URL del webhook de Adapty en el campo **Endpoint URL**. Luego elige **Latest API version** en el campo **Version** del webhook. A continuación, selecciona los siguientes eventos: - charge.refunded - customer.subscription.created - customer.subscription.deleted - customer.subscription.paused - customer.subscription.resumed - customer.subscription.updated - invoice.created - invoice.updated - payment_intent.succeeded <img src="/assets/shared/img/cbc5404-CleanShot_2023-12-07_at_17.36.232x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Pulsa "Add endpoint" y luego pulsa "Reveal" bajo "Signing secret". Esta es la clave que se usa para decodificar los datos del webhook en el lado de Adapty; cópiala después de revelarla: <img src="/assets/shared/img/0460cbb-CleanShot_2023-12-07_at_17.52.582x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Por último, pega esta clave en App Settings → Stripe de Adapty, bajo "Stripe Webhook Secret": <img src="/assets/shared/img/055db20-CleanShot_2023-12-07_at_14.56.212x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Details> ## 2\. Crea productos en Stripe \{#2-create-products-on-stripe\} :::note Si estás configurando esto en modo de prueba, asegúrate de que Stripe también esté cambiado a modo de prueba antes de continuar con este paso. ::: Ve al [Catálogo de productos](https://dashboard.stripe.com/products?active=true) de Stripe y crea los productos que deseas vender junto con sus planes de precios. Ten en cuenta que Stripe permite tener múltiples planes de precios por producto, lo cual es útil para adaptar tu oferta sin necesidad de crear productos adicionales. <img src="/assets/shared/img/b202e2e-CleanShot_2023-12-06_at_15.06.262x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning Por el momento, Adapty solo admite precios de **Tarifa plana** ($9,99/mes) o **Precio por paquete** ($9,99/10 unidades), ya que se comportan de manera similar a las tiendas de apps. Las opciones **Precio escalonado**, **Tarifa basada en uso** y **El cliente elige el precio** no están disponibles. ::: ## 3\. Añade productos de Stripe a Adapty \{#3-add-stripe-products-to-adapty\} :::warning ¡Los productos son obligatorios! Asegúrate de crear tus productos de Stripe en el Adapty Dashboard. Adapty solo realiza el seguimiento de eventos para transacciones vinculadas a estos productos, así que no omitas este paso; de lo contrario, no se crearán eventos de transacción. ::: Tratamos Stripe igual que el App Store y Google Play: es simplemente otra store donde vendes tus productos digitales. Por eso se configura de forma similar: simplemente añade productos de Stripe (concretamente su `product_id` y `price_id`) a la sección de Productos de Adapty: <img src="/assets/shared/img/stripe-add-product.webp" style={{ border: 'none', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Los IDs de producto en Stripe tienen el formato `prod_...` y los IDs de precio tienen el formato `price_...`. Son fáciles de encontrar para cada producto en el [Catálogo de productos](https://dashboard.stripe.com/products?active=true) de Stripe, una vez que abres cualquier producto: <img src="/assets/shared/img/14a72d7-CleanShot_2023-12-06_at_17.32.512x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Una vez que hayas añadido todos los productos necesarios, el siguiente paso es informar a Stripe sobre qué usuario está realizando la compra, para que Adapty pueda identificarlo. ## 4\. Enriquece las compras web con tu ID de usuario \{#4-enrich-purchases-made-on-the-web-with-your-user-id\} Adapty depende de los webhooks de Stripe para proporcionar y actualizar los niveles de acceso de los usuarios como única fuente de información. Pero debes proporcionar información adicional desde tu lado cuando trabajas con Stripe para que esta integración funcione correctamente. Para que los niveles de acceso sean consistentes entre plataformas (web o móvil), debes asegurarte de que haya un único ID de usuario en el que confiar y que Adapty pueda reconocer a partir de los webhooks. Podría ser el correo electrónico del usuario, su número de teléfono o cualquier otro ID del sistema de autorización que estés utilizando. Determina qué ID deseas usar para identificar a tus usuarios. Luego, accede a la parte de tu código que inicializa el pago a través de Stripe y añade este ID de usuario al objeto `metadata` de [Stripe Subscription](https://docs.stripe.com/api/subscriptions/object#subscription_object-metadata) (`sub_...`) o del objeto [Checkout Session](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-metadata) (`ses_...`) como `customer_user_id`, así: ```json showLineNumbers title="Stripe Metadata contents" {'customer_user_id': "YOUR_USER_ID"} ``` Esta sencilla adición es lo único que tienes que hacer en tu código. Después de eso, Adapty analizará todos los webhooks que reciba de Stripe, extraerá estos `metadata` y asociará correctamente las suscripciones con tus clientes. :::warning El ID de usuario es obligatorio De lo contrario, no tenemos forma de identificar a este usuario y proporcionarle el nivel de acceso en el móvil. Si no proporcionas `customer_user_id` en los `metadata`, tendrás la opción de hacer que Adapty busque `customer_user_id` en otros lugares: bien en el `email` del objeto Customer de Stripe, bien en el `client_reference_id` de la Session de Stripe. Obtén más información sobre cómo configurar el comportamiento de creación de perfiles [a continuación](stripe#profile-creation-behavior) ::: :::note El Customer de Stripe también es obligatorio Si estás usando Checkout Sessions, [asegúrate de crear un Customer de Stripe](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-customer_creation) estableciendo `customer_creation` en `always`. ::: ## 5\. Proporciona acceso a los usuarios en el móvil \{#5-provide-access-to-users-on-the-mobile\} Para asegurarte de que los usuarios móviles que llegan desde la web puedan acceder a las funciones de pago, simplemente llama a `Adapty.activate()` o `Adapty.identify()` con el mismo `customer_user_id` que proporcionaste en el paso anterior (consulta <InlineTooltip tooltip="Identificación de usuarios">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) y [Unity](unity-identifying-users)</InlineTooltip> para más información). ## 6\. Prueba tu integración \{#6-test-your-integration\} Asegúrate de haber completado los pasos anteriores tanto para Sandbox como para Producción. Las transacciones que realices desde el modo de prueba de Stripe se considerarán Sandbox en Adapty. :::info ¡Eso es todo! Tus usuarios ya pueden completar compras en la web y acceder a las funciones de pago en tu app. Y también puedes ver toda la analítica de suscripciones en un único lugar. ::: ## Comportamiento de creación de perfiles \{#profile-creation-behavior\} Adapty debe vincular una compra a un [perfil de cliente](profiles-crm) para que esté disponible en el móvil; por eso, de forma predeterminada, crea perfiles al recibir webhooks de Stripe. Puedes elegir qué usar como ID de usuario del cliente en Adapty: 1. **Predeterminado y recomendado:** el `customer_user_id` que proporcionaste en los metadatos en el [paso 4 anterior](stripe#4-enrich-purchases-made-on-the-web-with-your-user-id) 2. `email` en el objeto Customer de Stripe (consulta la [documentación de Stripe](https://docs.stripe.com/api/customers/object#customer_object-email)) 3. `client_reference_id` en el objeto Session de Stripe (consulta la [documentación de Stripe](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-client_reference_id)) Puedes configurar qué ID deseas usar en [App Settings → Stripe](https://app.adapty.io/settings/stripe). :::warning **Nota:** si una transacción concreta de Stripe no contiene el ID especificado, no crearemos un perfil en absoluto. Esta transacción permanecerá anónima hasta que algún perfil la recoja (por ejemplo, si usas [S2S validate](api-adapty/operations/validateStripePurchase) después e informas manualmente sobre esta transacción). Aparecerá en Analytics, pero no en las secciones que dependen del recuento de perfiles (LTV, Cohortes, Conversiones, etc.) y no podrás verla en el Event feed. ::: También tienes una cuarta opción para no crear perfiles en absoluto, pero no se recomienda debido a las limitaciones de Analytics mencionadas anteriormente. ## Limitaciones actuales \{#current-limitations\} ### Actualizaciones, degradaciones y prorrateo \{#upgrading-downgrading-and-proration\} Los cambios de suscripción, como actualizaciones o degradaciones, pueden dar lugar a cargos prorrateados. Adapty no tendrá en cuenta estos cargos en los cálculos de ingresos. Lo mejor es deshabilitar estas opciones manualmente desde el dashboard de Stripe. También puedes desactivarlas estableciendo el valor del atributo `proration_behaviour` en `none` a través de la API de Stripe. ### Cancelaciones \{#cancellations\} Stripe tiene dos opciones de cancelación de suscripción: 1. Cancelación inmediata: La suscripción se cancela de inmediato con o sin opciones de prorrateo 2. Cancelación al final del período: La suscripción se cancela al final del período de facturación actual (similar a las suscripciones in-app en las tiendas de apps). Adapty admite ambas opciones, pero el cálculo de ingresos para la cancelación inmediata no tendrá en cuenta la opción de prorrateo. ### Problemas de facturación y período de gracia \{#billing-issues-and-grace-period\} Cuando un cliente tiene un problema con su pago, Adapty generará un evento de problema de facturación y se revocará el acceso. Aún no admitimos el período de gracia de Stripe; esto formará parte de futuras versiones. ### Reembolsos \{#refunds\} Adapty solo realiza el seguimiento de reembolsos totales. Los reembolsos prorrateados o parciales no están disponibles actualmente. ### Unicidad del ID de transacción \{#transaction-id-uniqueness\} Adapty empareja perfiles y transacciones usando `store_transaction_id` y `store_original_transaction_id`. Estos **deben ser únicos** entre los entornos de prueba y producción. #### Por qué importa \{#why-this-matters\} Si el mismo ID de transacción existe en ambos entornos, Adapty los trata como una sola transacción, lo que provoca: - Que las compras de producción hereden los niveles de acceso y los IDs de producto de prueba - IDs de producto y entornos incorrectos en las respuestas de la API - Vinculación de perfiles y eventos de suscripción interrumpidos #### Cómo garantizar la unicidad \{#how-to-ensure-uniqueness\} Los IDs de factura de Stripe pueden solaparse entre los entornos de prueba y en vivo. Para evitar colisiones entre entornos, elige una opción: #### Opción 1: Numeración a nivel de cuenta con prefijos de entorno \{#option-1-account-level-numbering-with-environment-prefixes\} Configura prefijos por separado para cada entorno: 1. En el dashboard de Stripe, cambia al modo de prueba. 2. Ve a [Settings → Billing → Invoices](https://dashboard.stripe.com/settings/account/?support_details=true). 3. Establece **Invoice numbering** en **Sequentially across your account**. 4. Establece **Invoice prefix** en TEST- (u otro prefijo específico para el entorno de prueba). 5. Cambia al modo en vivo y repite los pasos 2-4, usando LIVE- (u otro prefijo específico para el entorno en vivo) como prefijo. #### Opción 2: Numeración a nivel de cliente \{#option-2-customer-level-numbering\} Establece **Invoice numbering** en la pestaña [**Stripe settings** -> **Billing** -> **Invoices**](https://dashboard.stripe.com/settings/account/?support_details=true) como **Sequentially for each customer (customer-level)**. Incluso con la configuración anterior, si eliminas una factura, Stripe puede reutilizar ese ID para nuevas facturas del mismo cliente. Lo mejor es evitar eliminar facturas siempre que sea posible. ### Compras únicas a través de Stripe Checkout o Payment Links \{#one-time-purchases-via-stripe-checkout-or-payment-links\} Adapty realiza el seguimiento de compras únicas (no de suscripción) realizadas a través de Stripe Checkout (`mode=payment`) o Payment Links solo si Stripe genera una factura para la compra. De forma predeterminada, Stripe no crea una factura para compras únicas de Checkout. En este caso, `payment_intent.succeeded` llega sin datos de factura, lo cual no es suficiente para que Adapty registre la transacción. Para realizar el seguimiento de compras únicas de Checkout en Adapty, [activa la creación de facturas](https://docs.stripe.com/payments/checkout/receipts?payment-ui=stripe-hosted#paid-invoices-hosted) cuando crees la sesión. Stripe generará entonces una factura y emitirá los eventos `invoice.created` e `invoice.updated` relacionados, que Adapty procesa para registrar la transacción. ## Aprovecha al máximo tus datos de Stripe \{#get-more-from-your-stripe-data\} Una vez que te integres con Stripe, Adapty está listo para proporcionar insights de inmediato. Para sacar el máximo partido a tus datos de Stripe, puedes configurar integraciones adicionales de Adapty para reenviar eventos de Stripe, reuniendo toda la analítica de suscripciones en un único Adapty Dashboard. :::tip Para una analítica mejorada, puedes incluir un `variation_id` en tus metadatos de Stripe para atribuir las compras a instancias específicas de paywall. Esto es especialmente útil cuando implementas paywalls web propios y quieres saber qué paywall concreto condujo a la conversión. Ten en cuenta que `variation_id` solo se lee de los metadatos en los objetos Stripe Subscription (`sub_...`) y Checkout Session (`ses_...`): ```json showLineNumbers title="Stripe Metadata with variation_id" { 'customer_user_id': "YOUR_USER_ID", 'variation_id': "YOUR_VARIATION_ID" } ``` ::: Integraciones que puedes usar para reenviar y analizar tus eventos de Stripe: - [Amplitude](amplitude/) - [Webhook](webhook) - [Firebase](firebase-and-google-analytics) - [Mixpanel](mixpanel) - [Posthog](posthog) ### Eventos de Stripe compatibles \{#supported-stripe-events\} Adapty admite los siguientes eventos de Stripe: - charge.refunded - customer.subscription.created - customer.subscription.deleted - customer.subscription.paused - customer.subscription.resumed - customer.subscription.updated - invoice.created - invoice.updated - payment_intent.succeeded --- # File: paddle --- --- title: "Integración inicial con Paddle" description: "Integra Paddle con Adapty para gestionar pagos de suscripciones sin complicaciones." --- Adapty admite flujos de suscripción web2app rastreando pagos y suscripciones web realizados a través de [Paddle](https://www.paddle.com/). Esta integración cubre las compras iniciadas desde la web y las sincroniza con el acceso a la app móvil y los análisis, junto con las compras in-app de los stores. Es útil en los siguientes escenarios: - Recopilar datos de suscripción tanto de compras in-app como de compras en el sitio web en un único sistema - Conceder acceso a funciones de pago en tu aplicación móvil a usuarios que compraron en tu sitio web - Ver análisis y datos de suscripción de todos los canales de venta en un solo dashboard :::note Apple ahora permite que las aplicaciones de la App Store de EE. UU. incluyan enlaces a sistemas de pago externos, aunque es posible que las aplicaciones aún necesiten ofrecer compras in-app junto con las opciones externas. Consulta las directrices actuales de la App Store para tu región y categoría de aplicación. ::: :::note Esta integración se centra en el seguimiento y la sincronización de compras web de Paddle. Si necesitas enviar usuarios desde la app a un checkout web, usa los [paywalls web](web-paywall) de Adapty. ::: Para configurar la integración con Paddle, sigue estos pasos: ## 1\. Conectar Paddle con Adapty \{#1-connect-paddle-to-adapty\} La integración utiliza webhooks para enviar datos de suscripción desde Paddle a Adapty. Para conectar tus cuentas de Adapty y Paddle, necesitarás: 1. Proporcionar tus claves API de Paddle. 2. Añadir la URL del webhook de Adapty a Paddle. :::note Los pasos a continuación se aplican tanto a Producción como a Test. Puedes configurar ambos simultáneamente. Los enlaces proporcionados corresponden al entorno de Producción — para obtener los enlaces del entorno de Test, simplemente añade `sandbox-` al principio de cada URL. Por ejemplo, usa `https://sandbox-vendors.paddle.com/authentication-v2` en lugar de `https://vendors.paddle.com/authentication-v2`. ::: ### 1.1. Obtén y añade las claves API de Paddle \{#get-and-add-paddle-api-keys\} 1. En Paddle, ve a [Developer Tools → Authentication](https://vendors.paddle.com/authentication-v2) y haz clic en **New API key**. <img src="/assets/shared/img/paddle-new-key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Dale un nombre a la clave y establece la fecha de expiración. Para que la clave API funcione con Adapty, necesitas concederle el permiso **Read** para todas las entidades. Haz clic en **Save**. <img src="/assets/shared/img/paddle-key.webp" style={{ border: 'none', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Copy key**. <img src="/assets/shared/img/copy-paddle-key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. En Adapty, ve a [App Settings → Paddle](https://app.adapty.io/settings/paddle) y pega la clave en la sección **Paddle API key**. :::warning Si estableciste una fecha de vencimiento para tu clave de API de Paddle, debes generar manualmente una nueva clave y actualizarla en Adapty antes de que expire. La integración dejará de funcionar sin previo aviso cuando la clave caduque, y los usuarios no podrán realizar compras. ::: <img src="/assets/shared/img/paddle-api-keys-adapty.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 1.2. Añadir eventos que se enviarán a Adapty \{#add-events-that-will-be-sent-to-adapty\} 1. Copia la **Webhook URL** de la misma página de **Paddle** en Adapty. 2. En Paddle, ve a [**Developer Tools → Notifications**](https://vendors.paddle.com/notifications-v2) y haz clic en **New destination** para añadir un webhook. <img src="/assets/shared/img/paddle-webhook.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Introduce un nombre descriptivo para el webhook. Te recomendamos incluir "Adapty" en él para que puedas encontrarlo fácilmente cuando lo necesites. 4. Pega la **Webhook URL** de Adapty en el campo **URL**. Asegúrate de usar el webhook para el entorno correcto. 5. Establece **Notification type** en **Webhook**. <img src="/assets/shared/img/paddle-create-webhook.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Selecciona los siguientes eventos: - `subscription.created` - `subscription.updated` - `transaction.created` - `transaction.updated` - `adjustment.created` - `adjustment.updated` <img src="/assets/shared/img/paddle_events.png" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Haz clic en **Save destination** para finalizar la configuración del webhook. ### 1.3. Obtener y añadir la clave secreta del webhook \{#retrieve-and-add-the-webhook-secret-key\} 1. En la ventana **Notifications**, haz clic en los tres puntos junto al webhook que acabas de crear y selecciona **Edit destination**. 2. Aparecerá un nuevo campo llamado **Secret key** en el panel **Edit destination**. Cópialo. <img src="/assets/shared/img/paddle-webhook-secret-key-copy.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En Adapty, ve a [App Settings → Paddle](https://app.adapty.io/settings/paddle) y pega la clave en el campo **Notification secret key**. Esta clave se usa para verificar los datos del webhook en Adapty. <img src="/assets/shared/img/paddle-webhook-secret-key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 1.4. Vincula los clientes de Paddle con los perfiles de Adapty \{#14-match-paddle-customers-with-adapty-profiles\} Adapty necesita vincular cada compra a un [perfil de cliente](profiles-crm) para que pueda usarse en tu app. De forma predeterminada, los perfiles se crean automáticamente cuando Adapty recibe webhooks de Paddle. Puedes elegir qué valor usar como `customer_user_id` en Adapty: 1. **Predeterminado y recomendado:** El `customer_user_id` que pasas en el campo `custom_data` (ver [documentación de Paddle](https://developer.paddle.com/build/transactions/custom-data)) 2. El `email` del objeto Paddle Customer (ver [documentación de Paddle](https://developer.paddle.com/paddle-js/methods/paddle-checkout-open/#parameters)) 3. El ID de cliente de Paddle en formato `ctm-...` (ver [documentación de Paddle](https://developer.paddle.com/paddle-js/methods/paddle-checkout-open/#parameters)) 4. No crear perfiles. Elige esta opción si quieres tener mayor control sobre los perfiles de tus clientes y gestionarlos tú mismo. Puedes configurar qué valor usar en el campo **Profile creation behavior** en [App Settings → Paddle](https://app.adapty.io/settings/paddle). <img src="/assets/shared/img/paddle-users.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 2. Añade productos de Paddle a Adapty \{#2-add-paddle-products-to-adapty\} :::warning Asegúrate de añadir tus productos de Paddle al Adapty Dashboard o de agregar un ID de producto de Paddle a tus productos existentes. Adapty solo registra eventos para transacciones vinculadas a estos productos. Si omites este paso, no se crearán eventos de transacción. ::: Paddle funciona en Adapty igual que App Store y Google Play: es otra plataforma donde vendes productos digitales. Para configurarlo, añade los valores de `product_id` y `price_id` correspondientes de Paddle en la sección [Products](https://app.adapty.io/products) de Adapty. En Paddle, los IDs de producto tienen el formato `pro_...` y los IDs de precio `pri_...`. Los encontrarás en tu [catálogo de productos de Paddle](https://vendors.paddle.com/products-v2) una vez que abras un producto específico: <img src="/assets/shared/img/paddle-product-price.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Una vez añadidos tus productos, el siguiente paso es asegurarte de que Adapty pueda vincular la compra al usuario correcto. ## 3\. Dar acceso a los usuarios en el móvil \{#provide-access-to-users-on-the-mobile\} Para asegurarte de que los usuarios que compran en la web obtengan acceso en el móvil, llama a `Adapty.activate()` o `Adapty.identify()` usando el mismo `customer_user_id` que pasaste al realizar la compra. Consulta [Identificar usuarios](identifying-users) para más detalles. ## 4\. Probar tu integración \{#test-your-integration\} Una vez que todo esté configurado, puedes probar tu integración. Las transacciones realizadas en el entorno de Test de Paddle aparecerán como **Test** en Adapty. Las transacciones del entorno de Producción aparecerán como **Production**. Tu integración ya está completa. Los usuarios pueden comprar suscripciones en tu sitio web y acceder automáticamente a las funciones premium en tu aplicación móvil, mientras tú haces seguimiento de todos los análisis de suscripciones desde tu Adapty Dashboard unificado. ## Consideraciones importantes \{#important-considerations\} - En las analíticas de Adapty, los importes de las transacciones incluyen impuestos y comisiones de Paddle, lo que difiere del dashboard de Paddle, donde los importes se muestran después de impuestos y comisiones. Esto significa que las cifras que ves en Adapty serán más altas que las de tu dashboard de Paddle. - A diferencia de otros stores, los reembolsos en Paddle solo afectan a la transacción específica que se reembolsa y no cancelan automáticamente la suscripción. La suscripción seguirá activa a menos que se cancele de forma explícita. - También puedes incluir `variation_id` en el campo `custom_data` para atribuir compras a instancias de paywall específicas. Adapty procesará estos datos desde los webhooks y los incluirá en las analíticas. ### Períodos de prueba de pago \{#paid-trials\} Al trabajar con períodos de prueba de pago en Paddle, necesitas crear dos productos en Adapty: 1. Crea un producto que no sea suscripción y vincúlalo al precio de Paddle que cobra por el período de prueba. 2. Luego crea un producto de suscripción (mensual, semanal, etc.) y vincúlalo al precio de Paddle que tiene el componente de prueba gratuita. Desde el punto de vista de Paddle, se trata de un único producto con dos precios en una sola transacción: un precio para el cargo del período de prueba (p. ej., $0,99) y otro precio para la prueba gratuita ($0,00). Desde la perspectiva de Adapty, esto genera dos eventos separados: una compra única por el pago de la prueba y un evento de inicio de prueba para el producto de suscripción. Por ejemplo, cuando un usuario inicia una prueba de pago de $0,99 para una suscripción de $9,99/mes, Paddle crea una sola transacción con ambos precios, mientras que Adapty lo procesa como una compra única de $0,99 (pago inmediato) y un evento de inicio de prueba a $0,00 (suscripción futura a $9,99/mes). :::note Cuando los usuarios cancelan una prueba de pago, recibirás los eventos **Trial expired** y **Trial renewal canceled**. ::: ## Saca más partido a tus datos de Paddle \{#get-more-from-your-paddle-data\} :::important Para que tus eventos de Paddle funcionen con las integraciones, tus usuarios deben haber iniciado sesión en la app con su cuenta de App Store/Google Play al menos una vez. ::: Una vez que te integres con Paddle, Adapty está listo para ofrecer información de inmediato. Para aprovechar al máximo tus datos de Paddle, puedes configurar integraciones adicionales de Adapty para reenviar eventos de Paddle, centralizando todos tus análisis de suscripciones en un único Adapty Dashboard. Integraciones que puedes usar para reenviar y analizar tus eventos de Paddle: - [AppsFlyer](appsflyer) - [Webhook](webhook) - [Posthog](posthog) ## Limitaciones actuales \{#current-limitations\} - **Cancelaciones**: Paddle tiene dos opciones de cancelación de suscripción: 1. Cancelación inmediata: La suscripción se cancela de inmediato. 2. Cancelación al final del período: La suscripción se cancela al final del período de facturación actual (similar a las suscripciones in-app en los stores). - **Reembolsos**: Adapty registra los reembolsos totales y parciales. - **Período de gracia**: Por defecto, Paddle aplica un período de gracia fijo de 30 días para problemas de facturación, durante el cual la suscripción permanece activa. Puedes [personalizar la duración del período de gracia y la acción al final del mismo (pausar o cancelar la suscripción)](https://developer.paddle.com/build/retain/configure-payment-recovery-dunning#prerequisites). **Pruebas**: Si el cobro falla al finalizar una prueba, el estado de la suscripción cambia a `past_due`. En producción, Paddle Retain aplica una ventana de gestión de impagos para intentar recuperar el pago antes de cancelar o pausar la suscripción. En sandbox, Retain no está disponible, por lo que no se realizan reintentos de pago y la suscripción permanece en `past_due` indefinidamente. --- **Ver también:** - [Validar una compra en Paddle, obtener un nivel de acceso e importar el historial de transacciones desde Paddle con la API del lado del servidor](api-adapty/operations/validatePaddlePurchase) --- # File: custom-store --- --- title: "Integración inicial con otras stores" description: "Integración inicial de Adapty con App Store: Guía rápida" --- ¡Nos alegra mucho tenerte con nosotros en Adapty! Nuestra prioridad es ayudarte a ponerte en marcha cuanto antes y obtener los mejores resultados posibles para tu app. La integración inicial solo es necesaria para [App Store](initial_ios), [Google Play](initial-android), [Stripe](stripe) y [Paddle](paddle), ya que Adapty verifica tus apps, productos y ofertas con estas stores. Adapty no valida datos con otras app stores ni procesa las compras realizadas a través de ellas. Sin embargo, puedes marcar los productos vendidos en otras stores para que Adapty conceda acceso al contenido de pago tras una compra exitosa, refleje las transacciones en tus analíticas y las comparta mediante integraciones. <img src="/assets/shared/img/Adapty-Communication-Scheme.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::important Asegúrate de que tu backend procese la compra y envíe la transacción a Adapty mediante la [API server-side de Adapty](getting-started-with-server-side-api). Adapty solo concederá acceso, disparará un evento de transacción, lo enviará a las integraciones y lo reflejará en las analíticas una vez que se reciba la transacción. ::: Para marcar un producto como vendido a través de una app store personalizada, selecciona la app store al crear el producto. Si la store que necesitas no aparece en la lista, así es como puedes crearla: 1. En la página **Products**, abre el producto que quieres vender a través de una app store personalizada. 2. Elige la app store a través de la que quieres vender. Si no aparece en la lista, haz clic en el botón **Create Custom Store**. <img src="/assets/shared/img/create_custom-appstore.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Introduce el **Title** y el **Store ID** de la store. 4. Haz clic en el botón **Create store**. Si tu backend está configurado correctamente, Adapty recibirá las transacciones de productos de esta store personalizada, las reflejará en las analíticas, en el [**Event Feed**](event-feed) y en las [integraciones](https://app.adapty.io/integrations), y concederá acceso según corresponda. ## Saca más partido a los datos de tu store personalizada \{#get-more-from-your-custom-store-data\} :::important Para que los eventos de tu store personalizada funcionen con las integraciones, tus usuarios deben haber iniciado sesión en la app con su cuenta de App Store/Google Play al menos una vez. ::: Una vez que configures la integración con tu store personalizada, Adapty está listo para ofrecerte información de inmediato. Para aprovechar al máximo tus datos, puedes configurar integraciones adicionales de Adapty para reenviar los eventos de la store personalizada y reunir todas tus analíticas de suscripciones en un único Adapty Dashboard. Integraciones que puedes usar para reenviar y analizar los eventos de tu store personalizada: - [AppsFlyer](appsflyer) - [Webhook](webhook) - [Posthog](posthog) --- # File: transfer-apps --- --- title: "Transferir tu app a una cuenta diferente" description: "Cambia el propietario de la app en Adapty" --- Transfiere tu app a un propietario diferente cuando tu empresa sea adquirida, estés vendiendo tu app o reorganizando entidades empresariales. El proceso de transferencia implica coordinar cambios en Adapty, App Store Connect y Google Play Console para garantizar la continuidad del servicio. ## Transferir la propiedad de la app \{#transfer-app-ownership\} Completa primero la transferencia en la store y luego transfiere la app en Adapty. Este orden garantiza que las compras sigan funcionando durante toda la transición. :::note No elimines ni vuelvas a crear productos durante el proceso de transferencia. No cambies los IDs de los productos hasta después de verificar que la transferencia se completó correctamente. ::: ### Transferencia en App Store (iOS) \{#app-store-ios-transfer\} :::important Las claves API de App Store Connect (Issuer ID, Key ID, archivo .p8) tienen alcance de cuenta, no de app. Tras la transferencia, debes generar nuevas claves API desde la cuenta del nuevo propietario y actualizarlas en Adapty. El secreto compartido específico de la app sigue validando recibos durante el periodo de transferencia, pero el nuevo propietario también debe regenerarlo y actualizarlo en Adapty una vez completada la transferencia. ::: 1. **Nuevo propietario:** Crea una cuenta de Adapty en [app.adapty.io](https://app.adapty.io) si aún no tienes una. 2. **Propietario anterior:** Inicia la transferencia de la app en App Store Connect siguiendo la [guía de transferencia](https://developer.apple.com/help/app-store-connect/transfer-an-app/overview-of-app-transfer) de Apple. 3. **Nuevo propietario:** Acepta la transferencia en App Store Connect. 4. **Propietario anterior:** Envía un correo a [support@adapty.io](mailto:support@adapty.io) para transferir la app en Adapty. Incluye el nombre de la app y la dirección de correo electrónico del nuevo propietario. 5. **Nuevo propietario:** Tras recibir la app en Adapty, completa la [guía de integración con App Store](initial_ios) para generar y configurar todas las credenciales en tu cuenta. ### Transferencia de Google Play (Android) \{#google-play-android-transfer\} 1. **Nuevo propietario:** Crea una cuenta en Adapty en [app.adapty.io](https://app.adapty.io) si aún no tienes una. 2. **Ambos propietarios:** Asegúrate de que ambas cuentas de Google Play Developer estén completamente registradas. 3. **Propietario anterior:** Envía una solicitud de transferencia a través de Google Play Console o el soporte de Google Play Developer. Google puede solicitar documentación adicional, como números DUNS, contratos o justificantes de venta. 4. **Nuevo propietario:** Revisa y aprueba la solicitud de transferencia. 5. **Google:** El equipo de soporte de Google procesa la transferencia, normalmente en unos pocos días hábiles, aunque puede tardar más según la verificación de la cuenta, la complejidad de las suscripciones y la configuración de pagos. 6. **Propietario anterior:** Cuando Google complete la transferencia, envía un correo a [support@adapty.io](mailto:support@adapty.io) para transferir la app en Adapty. Indica el nombre de la app y la dirección de correo del nuevo propietario. 7. **Nuevo propietario:** Una vez recibida la app en Adapty, completa la [guía de integración de Google Play](initial-android) para generar y configurar todas las credenciales bajo tu cuenta. La transferencia incluye usuarios, suscripciones, estadísticas, valoraciones y la ficha de la store. La continuidad de facturación se mantiene para los suscriptores existentes, pero los pagos pasan a la cuenta del nuevo propietario solo después de que se complete la transferencia. Los informes de pagos y pedidos anteriores a la transferencia permanecen en la cuenta original. Consulta la [guía de transferencia](https://support.google.com/googleplay/android-developer/answer/6230247) de Google para conocer los requisitos detallados. ## Mitigación de riesgos y tiempos \{#risk-mitigation-and-timing\} **Qué sigue funcionando durante la transferencia:** - Las compras y renovaciones (el shared secret específico de la app sigue validando los recibos durante el periodo de transferencia) - El acceso de los suscriptores existentes - El SDK sigue funcionando **Qué deja de funcionar temporalmente:** - Las llamadas a la API de App Store Connect (hasta que se configuren las nuevas claves) - Las notificaciones del servidor (hasta que se reconfigure el endpoint) - Es posible que los datos de análisis tengan lagunas durante la transición de credenciales **Momento recomendado:** - Completa las transferencias durante los períodos de poco tráfico (3:00–6:00 en la zona horaria principal de tus usuarios) - Ten al nuevo propietario listo para configurar las credenciales justo después de aceptar la transferencia en el store - Reserva entre 15 y 30 minutos entre la aceptación de la transferencia y la finalización de la integración con Adapty **Tras completar la transferencia:** - Prueba la validación de recibos de inmediato - Supervisa las tasas de éxito de renovación automática durante 48 horas - Verifica que las notificaciones del servidor están llegando a tus sistemas - Comprueba que las nuevas compras se están registrando correctamente ## Verificar que la transferencia se completó correctamente \{#verify-transfer-completed-successfully\} Tras completar tanto la transferencia en Adapty como en la store: 1. **Comprobar el acceso al Dashboard**: El nuevo propietario debe ver la app en su Adapty Dashboard. 2. **Verificar la conexión de la clave de API**: Comprueba que la nueva clave de API de App Store Connect o la cuenta de servicio de Google Play se conecta correctamente en Adapty. 3. **Probar la conexión del SDK**: Ejecuta tu app y verifica que el SDK de Adapty se inicializa sin errores. --- # File: installation-of-adapty-sdks --- --- title: "Instalación del SDK de Adapty" description: "Instala los SDKs de Adapty para iOS, Android y apps multiplataforma." --- Tienes tres formas de empezar según tus preferencias: - **Sigue las guías de inicio rápido por plataforma**: Las guías incluyen fragmentos de código listos para producción, así que la implementación no lleva mucho tiempo. - [iOS](ios-sdk-overview) - [Android](android-sdk-overview) - [React Native](react-native-sdk-overview) - [Flutter](flutter-sdk-overview) - [Unity](unity-sdk-overview) - [Kotlin Multiplatform](kmp-sdk-overview) - [Capacitor](capacitor-sdk-overview) - **Usa LLMs**: Nuestra documentación es compatible con LLMs. Lee nuestra [guía](adapty-cursor) sobre cómo sacar el máximo partido a los LLMs con la documentación de Adapty. - **Explora las apps de ejemplo**: - [iOS (Swift)](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples) - [Android (Kotlin)](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app) - [React Native (Ejemplo básico en RN puro)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/BasicExample) - [React Native (Ejemplo avanzado – útil para desarrollo, ya que permite trabajar con casos más complejos)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/AdaptyDevtools) - [React Native (Build de desarrollo con Expo)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) - [React Native (Expo Go y Web)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) - [Flutter (Dart)](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example) - [Unity (C#)](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets) - [Kotlin Multiplatform](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example) - [Capacitor](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples) --- # File: sample-apps --- --- title: "Apps de ejemplo" description: "" --- Para ayudarte a empezar con el SDK de Adapty, hemos preparado apps de ejemplo que muestran cómo integrar y usar sus funciones principales. Estas apps incluyen implementaciones listas para usar de paywalls, compras y seguimiento de analíticas. <img src="/assets/shared/img/adapty-scheme.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## ¿Por qué usar las apps de ejemplo? \{#why-use-sample-apps\} - **Integración rápida:** Descubre cómo funciona el SDK de Adapty en una app real. - **Buenas prácticas:** Sigue los patrones de implementación recomendados. - **Depuración y pruebas:** Usa las apps de ejemplo para resolver problemas y experimentar antes de integrar Adapty en tu propio proyecto. ## Apps de ejemplo disponibles \{#available-sample-apps\} - [iOS (Swift)](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples) - [Android (Kotlin)](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app) - [React Native (Ejemplo básico en RN puro)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/BasicExample) - [React Native (Ejemplo avanzado — útil para desarrollo, ya que permite trabajar con casos más complejos)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/AdaptyDevtools) - [React Native (Expo dev build)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) - [React Native (Expo Go & Web)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) - [Flutter (Dart)](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example) - [Unity (C#)](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets) - [Kotlin Multiplatform](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example) - [Capacitor (React)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-react-example) - [Capacitor (Vue.js)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-vue-example) - [Capacitor (Angular)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-angular-example) - [Capacitor (Herramientas de desarrollo avanzadas)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/adapty-devtools) --- # File: paywall-builder-templates --- --- title: "Crear un flow" description: "Comienza un nuevo flow a partir de una plantilla de galería personalizada o un inicio mínimo." --- Puedes crear un flow a partir de una plantilla o desde cero. :::link ¿Quieres aprender más sobre cómo crear flows? Mira tutoriales en vídeo paso a paso en nuestra [lista de reproducción de YouTube](https://www.youtube.com/playlist?list=PLMksWqaZiWtM). ::: ## Crear un flow \{#create-flow\} 1. Abre la página **Flows**. 2. Haz clic en **Create flow**. 3. Elige una opción: - **Browse templates** (abre la biblioteca de plantillas) - **Start from scratch** (crea un flow vacío) 4. Cambia el nombre del flow en el editor. Haz clic en el nombre del flow en la cabecera e introduce uno nuevo. :::warning Adapty permite nombres de flow duplicados. Renombra cada nuevo flow, o acabarás creando varios flows **Untitled** difíciles de distinguir. ::: ### Usar una plantilla La biblioteca de plantillas contiene varias plantillas que sirven como punto de partida para tu flow. Cada una es un flow completo con múltiples pantallas, elementos interactivos y navegación funcional. Puedes editar cualquier elemento para personalizarlo. Para aplicar una plantilla: 1. En la biblioteca de plantillas, navega por las tarjetas de plantillas. Cada tarjeta muestra capturas de pantalla previas de un flow. 2. Haz clic en **Use as template** en la tarjeta que quieras. La plantilla se carga en el builder. Desde aquí puedes cambiar cualquier elemento, pantalla o propiedad. ### Comenzar desde cero \{#start-from-scratch\} Comenzar desde cero crea un flow con una sola pantalla en blanco. Diseña la pantalla con elementos de la [biblioteca de elementos](builder-elements). ## Cambiar la plantilla \{#change-the-template\} Puedes cambiar de plantilla desde el propio editor. Abre el panel Screens y haz clic en el botón **Templates** Templates para volver a abrir la biblioteca de plantillas y elegir una nueva. :::warning Aplicar una nueva plantilla reemplaza el borrador actual de tu flow. Adapty te pedirá que confirmes: haz clic en **Use template** para continuar, o en **Cancel** para conservar tu borrador. Una vez confirmado, el borrador anterior no se puede recuperar. El flow publicado sigue activo y no se ve afectado. ::: ## Fuentes personalizadas en plantillas \{#custom-fonts-in-templates\} :::link Artículo principal: [Fuentes personalizadas en el Flow Builder](using-custom-fonts-in-flow-builder) ::: Las plantillas marcadas con el chip **Custom font** utilizan fuentes personalizadas. Estas fuentes no se incluyen con el SDK. Pasa el cursor sobre el chip para ver qué fuentes usa la plantilla. Para ver la tipografía prevista en el dispositivo, añade los archivos de fuente al bundle de tu app. Las versiones anteriores de la app que no incluyan la fuente recurrirán a una fuente del sistema. Para cambiar una fuente sin afectar versiones anteriores, duplica el flow, cambia la fuente en la copia y restringe esa copia a [usuarios con versiones de la app que incluyan la fuente](segments). --- # File: builder-ui --- --- title: "Interfaz del Flow Builder" description: "Descripción general de la interfaz y el espacio de trabajo del Flow Builder." --- La interfaz principal del Flow Builder incluye todas las herramientas necesarias para añadir elementos visuales, editar sus propiedades y modificar la lógica del flow. Este artículo cubre cada área de la interfaz: qué hace y dónde encontrarla. :::link ¿Quieres aprender más sobre cómo crear flows? Mira tutoriales en vídeo paso a paso en nuestra [lista de reproducción de YouTube](https://www.youtube.com/playlist?list=PLMksWqaZiWtM). ::: ## Controles del proyecto y atajos útiles (barra de herramientas superior) \{#project-controls-and-useful-shortcuts-top-toolbar\} * **Close** Close: Salir del editor de flow y volver a la página de flows. * **App name** App: Identifica la app a la que pertenece el flow. * **All flows** Flows: Abre la lista de todos los flows de esta app. * **Flow status**: El icono a la izquierda del nombre del flow indica el [estado actual del flow](builder-save-publish#flow-status): - **Draft** Draft - **Publishing** (indicador giratorio) - **Failed** Failed - o **Live** Live. * **Rename the flow**: Haz clic en el nombre del flow para cambiarlo. Varios flows pueden tener el mismo nombre: [dale a cada nuevo flow un nombre único](paywall-builder-templates#create-flow). * **View mode toggle**: Cambia entre la vista de diseño Cursor y la [vista de Remote Config](customize-flow-with-remote-config)Remote Config. * **Undo/Redo**: Haz clic en los iconos de flecha para deshacer Undo o rehacer Redo los cambios del flow. También puedes usar ⌘Z / Ctrl+Z para deshacer. * **Save draft / Publish**: Haz clic en **Save draft** para guardar el progreso sin publicar (⌘ / Ctrl+S). Abre el desplegable Open dropdown para acceder al botón [**Publish**](builder-save-publish). Solo podrás añadir el flow a un [placement](create-placement) después de publicarlo. ## Área de vista previa (centro) \{#preview-area-center\} El área central del espacio de trabajo simula cómo se verá tu flow en un dispositivo móvil. * Para seleccionar un elemento y editar sus propiedades, haz clic en él. Para seleccionar un elemento hijo dentro de un contenedor, primero haz clic en el contenedor y luego en el elemento hijo. * Para editar las propiedades de la pantalla en sí, haz clic fuera de cualquier elemento o selecciona la pantalla en el panel Screens and Layers. * Para cambiar el orden de un elemento, arrastra su entrada hacia arriba o hacia abajo en el panel Screens and Layers. :::warning El editor de flows está diseñado para crear layouts adaptables. Por eso, **no puedes cambiar manualmente la posición de los elementos** — solo puedes cambiar su orden. La configuración de layout de cada contenedor determina cómo se distribuyen los elementos dentro de él. ::: ### Barra de pantalla activa (encima de la previsualización del dispositivo) \{#active-screen-bar-above-the-device-preview\} - **Screen name** — una etiqueta con el nombre de la pantalla actual. - **Toggle animations** Toggle animations — activa o desactiva las vistas previas de animaciones de los elementos; se reproducen de forma continua hasta que se desactivan. Solo aparece cuando la pantalla activa contiene al menos una [animación](builder-styling#animation). No afecta a la visibilidad de las animaciones en el dispositivo real. - **Add element** Plus — abre la [biblioteca de elementos](builder-elements) en la pantalla actual. Es equivalente al **+** situado en la parte superior del panel Screens and Layers, y resulta útil cuando dicho panel está contraído. ### Controles de visualización (barra de herramientas inferior) \{#view-controls-bottom-toolbar\} Las herramientas de la barra inferior te permiten controlar la vista previa. * **Device**: Selecciona uno de los modelos de iPhone o Android disponibles para cambiar las dimensiones del viewport y el contorno del dispositivo. * **Screen orientation**: Alterna entre los modos vertical Portrait y horizontal Landscape para previsualizar tu flow en distintas orientaciones. * **Color scheme**: Cambia entre los modos claro Light mode y oscuro Dark mode para ver cómo se adapta tu diseño a cada tema. * **Locale**: Selecciona un idioma para previsualizar tu flow con el contenido localizado. * **View options**: Activa o desactiva el bisel del dispositivo y las guías de área segura. ## Propiedades de pantalla y elemento (panel derecho) \{#screen-and-element-properties-right-panel\} ### Ajustes y disposición de pantalla \{#screen-settings-and-layout\} :::link Artículo principal: [Pantallas y capas](paywall-layout-and-products) ::: Cuando no hay ningún elemento seleccionado, el panel derecho te permite ajustar las propiedades de la pantalla del [flow](paywall-layout-and-products) activa, entre ellas: * Interacciones con la interfaz del sistema (por ejemplo, si la barra de estado es visible) * Reglas de disposición automática * Fondo (color, imagen o vídeo) * Tamaño del relleno (padding) * Comportamiento de desplazamiento vertical Si la pantalla contiene ciertos elementos, como [cuestionarios interactivos](onboarding-quizzes), esta lista se ampliará con las propiedades correspondientes. ### Propiedades del elemento \{#element-properties\} Cuando seleccionas un elemento, el panel derecho te permite cambiar sus propiedades de estilo e interacción. #### Propiedades de diseño \{#design-properties\} :::link Más información: [Diseño y posicionamiento](manage-paywall-ui-elements), [Estilos y apariencia](builder-styling) ::: La pestaña **Design** te permite configurar la apariencia visual y el diseño del elemento seleccionado: * **Visibility**: Muestra u oculta el elemento. Activa la visibilidad **Conditional** para establecer reglas que determinen cuándo debe ser visible el elemento. * **Position**: Elige entre posicionamiento Relative, Absolute o Fixed. * **Content** (solo elementos de texto): Edita el contenido de texto del elemento, inserta [variables](#variables) y gestiona las localizaciones. * **Typography** (solo elementos de texto): Configura la fuente, el peso, el tamaño, el color, la alineación, la decoración y el truncamiento. * **Spacing**: Establece el margen y el relleno del elemento. * **Effects**: Añade sombras externas, sombras internas, desenfoque de fondo o desenfoque de capa. * **Animation**: Añade efectos animados (p. ej., Pulse) y configura su temporización e intensidad. * **Appearance**: Ajusta la opacidad y la rotación. * **Layout**: Elige una dirección de diseño (vertical u horizontal) y determina cómo se distribuyen los elementos secundarios. #### Propiedades de interacciones \{#interactions-properties\} :::link Más información: [Acciones](onboarding-actions), [Navegación e interacción](onboarding-navigation-branching) ::: La pestaña **Interactions** te permite definir qué ocurre cuando el usuario interactúa con el elemento seleccionado. Cada interacción consta de un **trigger** y una o más **acciones**: * **Los disparadores** definen *cuándo* ocurre algo — por ejemplo, **On Tap** (el usuario toca el elemento). * **Las acciones** definen *qué* ocurre — por ejemplo, navegar a otra pantalla o cambiar el valor de una variable. Añade varias acciones a un mismo disparador para encadenarlas en secuencia. Puedes añadir varios disparadores al mismo elemento para ejecutar varias acciones en orden. ## Panel izquierdo \{#left-panel\} El panel izquierdo cambia su funcionalidad según el botón que esté activo. Puedes elegir entre: * [Pantallas y capas](#screens-and-layers) * [Añadir elemento](#element-selection) * [Productos](#products) * [Estilos](#saved-styles) * [Variables](#variables) * [Localización](#localization) ### Pantallas y Capas \{#screens-and-layers\} :::link Artículo principal: [Pantallas y Capas](paywall-layout-and-products) ::: El botón de capas Layers abre Pantallas y Capas (que se muestra por defecto al abrir el flow builder). Muestra cada pantalla como un árbol de capas. Cada elemento de una pantalla es una capa, y los contenedores tienen sus elementos hijos anidados dentro. Puedes arrastrar y soltar capas para reordenarlas. ### Selección de elementos \{#element-selection\} :::link Artículo principal: [Elementos](builder-elements) ::: Si haces clic en el botón más Plus, el panel izquierdo muestra la lista de elementos de interfaz disponibles y sus variaciones. Haz clic en un elemento para añadirlo a la pantalla actual como una nueva capa. ### Productos :::link Artículo principal: [Productos](paywall-product-block) ::: El botón de productos Products abre la lista de productos. Muestra qué productos están asignados a cada pantalla de tu flow. Esta lista es de solo lectura. Para asignar productos a una pantalla, añade un elemento Producto y configúralo en el panel derecho. Para crear o editar productos, utiliza la página **Products** en el Adapty Dashboard. ### Estilos guardados \{#saved-styles\} :::info Más información: - [Estilos y apariencia](builder-styling) - [Contenido de texto](onboarding-text) - [Modo oscuro](paywall-dark-mode) ::: El botón de estilos Styles abre los Estilos guardados. Aquí puedes editar y gestionar los estilos globales. Si varios elementos de tu flow usan la misma tipografía o color, guarda esos datos como estilo global. Después podrás reutilizarlos con un solo clic. Actualmente, Flow Builder admite dos tipos de estilos globales: estilos de fuente y estilos de color. Cada estilo de color puede tener, opcionalmente, un valor distinto para el modo oscuro. ### Variables :::link Artículo principal: [Variables](onboarding-variables) ::: El botón de corchetes Variables abre Variables. Aquí puedes crear y gestionar variables para tu flow. En tiempo de ejecución, el SDK reemplaza los marcadores de posición de las variables con valores reales: atributos de usuario, precios de productos, cadenas localizadas y más. Las variables se agrupan en dos pestañas: * **Custom**: Variables que creas y controlas mediante acciones. * **Elements**: Valores determinados por la interacción del usuario, como respuestas de quiz, estados de toggles o selección de pestañas. Las variables de producto — precio, nombre y otros datos del producto — no aparecen en este panel. Referenciarlas directamente al editar un elemento de texto. Usa las variables para: * **Vincular texto**: Muestra contenido dinámico en lugar de cadenas estáticas. * **Controlar la visibilidad**: Muestra u oculta elementos según condiciones (por ejemplo, ocultar un botón de actualización para usuarios premium). * **Interactuar con el usuario**: Accede a los datos de los campos de entrada del usuario, como formularios o cuestionarios. ### Localización \{#localization\} :::link Artículo principal: [Localización](add-flow-remote-config-locale) ::: La vista de Localización te permite gestionar todo el contenido traducible de tu flow. Muestra una tabla con cada cadena de texto e imagen, organizada por pantalla, con columnas para cada idioma. Desde esta vista puedes: * Añadir nuevos idiomas y editar las cadenas localizadas directamente. * Hacer seguimiento del estado de traducción — cada fila aparece marcada como **Done** o **Missing**. * Filtrar por pantalla o mostrar solo las traducciones que faltan. * Usar **AI Translate** para traducir el contenido automáticamente, o **Import/Export** para gestionar las traducciones de forma masiva. --- # File: flow-builder-recipes --- --- title: "Recetas comunes de flows" description: "Guías paso a paso para construir las plantillas de pantalla más comunes en el Flow Builder." --- Esta sección explica cómo construir las plantillas de pantalla más comunes en el Flow Builder — elemento por elemento, desde las opciones de diseño hasta las interacciones. Cada guía es independiente y utiliza los elementos estándar del Flow Builder. <CustomDocCardList /> Sigue este vídeo de inicio rápido para crear un flow personalizado básico: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/aa-m459VIuY?si=zN_Co6B6qB88UPZP" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> :::link ¿Quieres aprender más sobre cómo crear flows? Mira tutoriales en vídeo paso a paso en nuestra [lista de reproducción de YouTube](https://www.youtube.com/playlist?list=PLMksWqaZiWtM). ::: --- # File: basic-paywall-screen --- --- title: "Crear una pantalla básica de paywall" description: "Guía paso a paso para construir una pantalla de paywall estándar en el Flow Builder." --- Esta es la plantilla de paywall más habitual. Úsala como pantalla independiente o colócala al final de un [flow](adapty-flow-builder) de varias pantallas. Una pantalla de paywall estándar contiene un encabezado, una descripción del valor, una lista de características, una lista de productos, un botón de compra y enlaces en el pie de página para restaurar compras, términos de uso y política de privacidad. ## Antes de empezar \{#before-you-start\} - [Crea productos](create-product) en el Adapty Dashboard. - [Conecta Adapty con la App Store y Google Play](integrate-payments). ## 1. Configura los estilos reutilizables \{#set-up-reusable-styles\} Los estilos reutilizables te permiten aplicar la misma tipografía y colores en todas las pantallas con un solo clic. Cada nuevo flow incluye un conjunto de estilos de texto predeterminados (H1, Body, Button Label, etc.): ajústalos para que coincidan con tu diseño antes de empezar a añadir elementos. Añade estilos de color para los colores de marca que usarás en toda la pantalla. Para ver las instrucciones completas, consulta [Estilos y apariencia — Estilos reutilizables](builder-styling#reusable-styles). Para configurar los estilos: 1. En el panel izquierdo, abre el panel **Styles** Styles. 2. En la pestaña **Text**, haz clic en un estilo existente para editar su fuente, grosor, tamaño y color. Añade nuevos estilos solo si los predeterminados no cubren tus necesidades. 3. En la pestaña **Colors**, haz clic en **Plus Create style** y añade los colores que vayas a reutilizar en la pantalla. ## 2. Configura el diseño de la pantalla \{#2-set-up-the-screen-layout\} La propia pantalla actúa como contenedor de todo lo que añades. Configura primero su diseño, fondo y relleno para que los elementos que añadas después se distribuyan correctamente. Para ver la lista completa de propiedades de pantalla, consulta [Pantallas y capas — Configuración de pantalla](paywall-layout-and-products#screen-settings). Para configurar la pantalla: 1. Haz clic en un área vacía del lienzo para seleccionar la pantalla. El panel derecho cambia a la configuración de pantalla. 2. En **System UI**, desactiva **Safe area** para que el contenido se extienda hasta los bordes de la pantalla. 3. En **Layout**, establece la dirección en **Vertical** Vertical y la distribución en **Space evenly**. 4. En **Fill**, elige un tipo de fondo — color sólido, degradado o imagen. Este ejemplo usa un **Gradient** Gradient con dos paradas de color. ## 3. Añade el botón de cierre \{#add-the-close-button\} El botón de cierre descarta el paywall. El preset **Close** viene preconfigurado — no es necesario configurar ninguna acción. 1. En el canvas, haz clic en **+**. 2. Selecciona **Buttons** > **Close**. ## 4. Añadir el título y agruparlo con el botón de cierre \{#add-the-title-and-pair-it-with-the-close-button\} El H1 se coloca junto al botón de cierre en la parte superior de la pantalla. Para alinearlos horizontalmente, envuélvelos en un contenedor horizontal. Para añadir el título: 1. Haz clic en **+** > **Text** > **H1**. 2. Con el H1 seleccionado, abre la pestaña **Design** en el panel derecho y edita el texto en el campo **Content**. Para agrupar el título con el botón de cierre: 1. En el panel **Layers**, haz clic en el menú de tres puntos Context menu en la capa del botón de cierre y elige **Wrap** > **Wrap in Horizontal Container**. 2. Arrastra la capa H1 al nuevo contenedor horizontal. Para alinear los dos elementos: 1. Ajusta el tamaño del botón de cierre y el tamaño de fuente del H1 para que quepan cómodamente en la misma línea. 2. Con el contenedor horizontal seleccionado, configura la alineación y distribución en el panel derecho para que los elementos queden bien alineados. ## 5. Añade la descripción del valor \{#add-the-value-description\} Una línea de texto breve bajo el título explica qué obtiene el usuario con la suscripción. 1. Haz clic en **+** > **Text** > **Body**. 2. Con el elemento body seleccionado, edita el texto en el campo **Content** de la pestaña **Design**. ## 6. Añade la lista de características \{#add-the-feature-list\} La lista de características destaca lo que incluye desbloquear la suscripción. Cada fila tiene un icono, un título de característica y una breve descripción. Para ver el conjunto completo de presets de lista, consulta [Elements — List](builder-elements#list). Para añadir la lista de características: 1. Haz clic en **+** > **List** y elige un preset de lista. Icon List es el más habitual para paywalls. 2. Con cada fila seleccionada, edita el título y la descripción en el campo **Content**. 3. Para añadir o eliminar filas, selecciona la lista y usa los controles de fila en el panel **Layers**. ## 7. Añadir la lista de productos \{#add-the-product-list\} La lista de productos muestra las opciones de suscripción entre las que el usuario puede elegir. El elemento Products renderiza una tarjeta por cada producto asignado a la pantalla, y una de ellas se marca automáticamente como predeterminada. Para saber más sobre la gestión de productos, consulta [Configurar compras](paywall-product-block). Para añadir y configurar productos: 1. Haz clic en **+** > **Products** y elige una plantilla de diseño. La lista vertical es la más habitual. 2. Selecciona cada tarjeta de producto en el lienzo y elige un producto del menú desplegable en la pestaña **Design**. El desplegable muestra todos los productos configurados en el Adapty Dashboard. 3. Para cambiar la selección predeterminada, selecciona la tarjeta que quieras y activa **Set as default product** en la pestaña **Design**. 4. Para personalizar la insignia de descuento, despliega una tarjeta de producto en el panel **Layers**, selecciona la capa de la insignia y edita su texto en el campo **Content**. Oculta la insignia en las demás tarjetas activando el icono de ojo Show junto a cada capa de insignia. ## 8. Añade el botón de compra \{#add-the-purchase-button\} El botón de compra inicia la compra in-app del producto que el usuario haya seleccionado. La variable `products.selectedProduct` siempre se resuelve en el producto actualmente seleccionado en la pantalla. Para añadir el botón de compra: 1. Haz clic en **+** > **Buttons** y elige un preset de botón. 2. Con el botón seleccionado, abre la pestaña **Interactions** en el panel derecho. 3. Haz clic en **Add trigger** > **On tap** y luego en **Add action**. 4. Establece **Action** en **Purchase** y **Product** en `products.selectedProduct`. ## 9. Añadir enlaces al pie de página \{#add-footer-links\} El pie de página contiene enlaces a los términos de uso y a la política de privacidad (requeridos por los stores) y un botón para restaurar compras anteriores. Para añadir los enlaces al pie de página: 1. Haz clic en **+** > **Buttons** > **Links**. Esto añade una fila con Restore Purchases, Terms of Use y Privacy Policy. 2. En el panel **Layers**, selecciona el botón **Terms of Use**. Abre la pestaña **Interactions** — la acción **Open URL** ya está asignada. Haz clic en la acción e introduce la URL de destino. 3. Repite el proceso para el botón **Privacy Policy** con tu URL de privacidad. 4. Deja el botón **Restore Purchases** tal como está. Su acción ya está preconfigurada. :::tip Si el posicionamiento de algún elemento te parece demasiado alto o bajo, o si quieres añadir más espacio en algún punto, ajusta los márgenes y el padding del elemento. ::: ## Pasos siguientes \{#next-steps\} - [Guarda y publica tu flow](builder-save-publish). - [Añade el flow a un placement](create-placement) para empezar a mostrárselo a los usuarios. --- # File: show-plans-bottom-sheet --- --- title: "Mostrar todos los planes en un bottom sheet" description: "Crea un paywall principal con un único CTA, un enlace 'Mostrar todos los planes' y un bottom sheet que revela la lista completa de productos." --- Esta plantilla muestra primero una única oferta destacada, con un enlace discreto a la lista completa de planes. Al pulsar **Show all plans** se desliza hacia arriba un bottom sheet que contiene los demás productos, un botón de compra y los enlaces del pie de página. Use it when one plan converts disproportionately well — the bottom sheet keeps the alternatives one tap away without crowding the main screen. ## Antes de empezar \{#before-you-start\} - [Crea productos](create-product) en el Adapty Dashboard. - [Conecta Adapty con App Store y Google Play](integrate-payments). ## 1. Configura el diseño de la pantalla \{#1-set-up-the-screen-layout\} Usa la imagen principal como fondo de pantalla y agrupa el resto del contenido en la parte inferior, de modo que la imagen ocupe la parte superior. Para consultar la lista completa de propiedades de pantalla, consulta [Pantallas y capas — Configuración de pantalla](paywall-layout-and-products#screen-settings). Para configurar la pantalla: 1. Haz clic en un área vacía del lienzo para seleccionar la pantalla. 2. En **System UI**, desactiva **Safe area** para que la imagen principal se extienda hasta los bordes de la pantalla. 3. En **Fill**, elige **Image** Image y sube tu imagen principal. 4. En **Layout**, configura la dirección, el espaciado y la alineación para anclar el contenido donde quieras. En esta plantilla, una dirección **Vertical** Vertical con un espaciado pequeño y alineación **bottom-middle** agrupa el encabezado y los botones en la parte inferior de la pantalla. ## 2. Añade el encabezado del CTA \{#add-the-cta-heading\} El encabezado se sitúa en la parte inferior de la pantalla, justo encima del botón de suscripción. La imagen hero ocupa el área superior. 1. Haz clic en **+** > **Text** > **H1**. 2. Con el H1 seleccionado, abre la pestaña **Design** y edita el texto en el campo **Content**. ## 3. Añadir el bottom sheet y su título \{#add-the-bottom-sheet-and-its-title\} El bottom sheet es un contenedor de diseño que se desliza hacia arriba desde la parte inferior de la pantalla. Por ahora añádelo visible — lo rellenarás en los próximos pasos y lo ocultarás una vez que el contenido esté en su lugar. Los elementos ocultos no se pueden editar, así que el sheet debe permanecer visible hasta que termines de completarlo. Para más información sobre bottom sheets y otros contenedores de diseño, consulta [Elementos — Layout](builder-elements#layout). Para añadir el bottom sheet y su título: 1. Haz clic en **+** > **Layout** > **Bottom Sheet**. 2. En el panel **Layers**, despliega el bottom sheet, selecciona la capa **Title** y edita el campo **Content** en la pestaña **Design** — por ejemplo, `Choose your plan`. ## 4. Agrega la lista de productos dentro del bottom sheet \{#4-add-the-product-list-inside-the-bottom-sheet\} Coloca todos los productos dentro del bottom sheet. Uno de ellos también determinará el precio que se muestra en el botón CTA principal. Para más información sobre la gestión de productos, consulta [Configurar compras](paywall-product-block). Para agregar y configurar productos: 1. Haz clic en **+** > **Products** y elige un preset de diseño. Vertical List funciona bien en la mayoría de los casos. El elemento aparece en la pantalla, fuera del bottom sheet. 2. En el panel **Layers**, arrastra la capa Products al contenedor **Content** dentro del bottom sheet. 3. Selecciona cada tarjeta de producto en el lienzo y elige un producto del desplegable en la pestaña **Design**. ## 5. Añade el botón de compra dentro del bottom sheet \{#add-the-purchase-button-inside-the-bottom-sheet\} El bottom sheet necesita su propio botón de compra para adquirir el plan que el usuario seleccione de la lista. 1. Haz clic en **+** > **Buttons** y elige un preset de botón. 2. En el panel **Layers**, arrastra el nuevo botón al contenedor **Content** dentro del bottom sheet. 3. Con el botón seleccionado, abre la pestaña **Interactions** en el panel derecho. 4. Haz clic en **Add trigger** > **On tap** y luego en **Add action**. 5. Establece **Action** en **Purchase** y **Product** en `products.selectedProduct`. ## 6. Añade los enlaces del pie de página dentro del bottom sheet \{#add-the-footer-links-inside-the-bottom-sheet\} :::important No uses [enlaces en línea](onboarding-text#inline-link) para texto dentro de botones. En su lugar, configura la acción **Open URL** directamente en el botón. ::: Los términos de uso, la política de privacidad y la restauración de compras se colocan al final del sheet, para que la pantalla principal quede despejada. 1. Haz clic en **+** > **Buttons** > **Links**. Esto añade una fila con Restore Purchases, Terms of Use y Privacy Policy. 2. En el panel **Layers**, arrastra la fila Links al contenedor **Content** dentro del bottom sheet. 3. En el panel **Layers**, selecciona el botón **Terms of Use**. Abre la pestaña **Interactions** y pega la URL de tus términos en el campo **Open URL**. 4. Repite el mismo proceso para el botón **Privacy Policy** con tu URL de privacidad. 5. Deja el enlace **Restore Purchases** tal como está. Su acción ya viene preconfigurada. ## 7. Ocultar la hoja inferior \{#hide-the-bottom-sheet\} Con el contenido de la hoja ya listo, ocúltala para que no aparezca en pantalla por defecto. Los usuarios la verán al pulsar **Show all plans** en el último paso. En el panel **Layers**, selecciona la hoja inferior y establece su estado en **Hide** Hide. La hoja permanece en el árbol de capas, pero deja de renderizarse en el canvas. ## 8. Añade el botón principal de suscripción \{#add-the-main-subscribe-button\} El botón principal de la pantalla suscribe al usuario al plan mensual con un solo toque. Su etiqueta usa la variable de precio del producto mensual para que el botón siempre esté sincronizado con el producto. 1. En el panel **Layers**, haz clic en la pantalla para que los nuevos elementos se añadan a la raíz y no dentro del bottom sheet. 2. Haz clic en **+** > **Buttons** y elige un preset de botón. 3. Con el botón seleccionado, abre la pestaña **Design** y sitúa el cursor en el campo **Content**. Haz clic en Variable icon y elige la variable de precio del producto principal. Añade el resto de la etiqueta alrededor — por ejemplo, `Subscribe for {price}/month`. 4. Cambia a la pestaña **Interactions** y haz clic en **Add trigger** > **On tap** > **Add action**. 5. Establece **Action** en **Purchase** y **Product** en el producto que necesitas. A diferencia del botón del bottom sheet, este apunta a un producto específico en lugar de `products.selectedProduct`. ## 9. Añade el enlace 'Mostrar todos los planes' \{#add-the-show-all-plans-link\} Un enlace de texto bajo el botón de suscripción muestra el bottom sheet al pulsarlo. Añadirlo como elemento de texto con el estilo **Button Label** mantiene un aspecto minimalista sin renunciar a la posibilidad de asignarle una acción. Para más información sobre la acción Mostrar/Ocultar, consulta [Acciones — Mostrar/ocultar elementos](onboarding-actions#showhide-elements). Para añadir el enlace: 1. Con la pantalla seleccionada en el panel **Layers**, haz clic en **+** > **Text** > **Button Label**. 2. Con el elemento de texto seleccionado, edita el campo **Content** para que muestre `Show all plans`. 3. Abre la pestaña **Interactions** y haz clic en **Add trigger** > **On tap** > **Add action**. 4. Establece **Action** en **Show** y selecciona el elemento bottom sheet en el desplegable. ## Próximos pasos \{#next-steps\} - [Guarda y publica tu flow](builder-save-publish). - [Añade el flow a un placement](create-placement) para empezar a mostrárselo a los usuarios. --- # File: paywall-with-tabs --- --- title: "Crear un paywall con pestañas" description: "Construye una pantalla de paywall con dos pestañas que alternan entre diferentes listas de características, grupos de productos y acciones de compra." --- Esta plantilla usa pestañas para alternar entre dos variantes de la misma oferta en una sola pantalla. Cada pestaña contiene su propia lista de características, lista de productos y botón de compra. Al tocar una pestaña, el contenido visible cambia sin salir de la pantalla — ideal para separar planes por nivel, período de facturación o segmento de audiencia. ## Antes de empezar \{#before-you-start\} - [Crea productos](create-product) en el Adapty Dashboard. - [Conecta Adapty con App Store y Google Play](integrate-payments). ## 1. Configura el diseño de la pantalla \{#1-set-up-the-screen-layout\} La pantalla actúa como contenedor del botón de cierre, el encabezado, las pestañas y el contenido de cada pestaña. En este ejemplo, el fondo es una imagen, aunque un color sólido o un degradado funcionan igual. Para ver la lista completa de propiedades de pantalla, consulta [Pantallas y capas — Configuración de pantalla](paywall-layout-and-products#screen-settings). Para configurar la pantalla: 1. Haz clic en un área vacía del lienzo para seleccionar la pantalla. 2. En **System UI**, desactiva **Safe area** para que el fondo se extienda hasta los bordes de la pantalla. 3. En **Fill**, elige un tipo de fondo y configúralo. En este ejemplo se usa una **Image** Image, pero un color sólido o un degradado funcionan igual. 4. En **Layout**, establece la dirección en **Vertical** Vertical y configura el espacio y la alineación para que los elementos se apilen desde arriba y el contenido de las pestañas ocupe el espacio restante. ## 2. Añade el botón de cierre \{#add-the-close-button\} El botón de cierre descarta el paywall. El preset **Close** viene preconfigurado — no es necesario configurar ninguna acción. 1. En el canvas, haz clic en **+**. 2. Selecciona **Buttons** > **Close**. ## 3. Añade el título y emparéjalo con el botón de cierre \{#add-the-title-and-pair-it-with-the-close-button\} El título se sitúa junto al botón de cierre en la parte superior de la pantalla. Para alinearlos horizontalmente, envuélvelos en un contenedor horizontal. Para añadir el título: 1. Haz clic en **+** > **Text** > **H1**. 2. Con el H1 seleccionado, abre la pestaña **Design** y edita el texto en el campo **Content**. Para agrupar el título con el botón de cierre: 1. En el panel **Layers**, haz clic en el menú de tres puntos Context menu en la capa del botón de cerrar y elige **Wrap** > **Wrap in Horizontal Container**. 2. Arrastra la capa H1 al nuevo contenedor horizontal. Para alinear los dos elementos: 1. Ajusta el tamaño del botón de cierre y el tamaño de fuente del H1 para que quepan cómodamente en la misma línea. 2. Con el contenedor horizontal seleccionado, configura la alineación y distribución en el panel derecho para que los elementos queden correctamente alineados. ## 4. Añadir las pestañas y configurar sus etiquetas \{#4-add-the-tabs-and-configure-their-labels\} El elemento Tabs divide una sección de la pantalla en paneles de contenido intercambiables. Cada pestaña tiene su propio contenedor de contenido que aparece cuando el usuario la selecciona. Para más información sobre el elemento Tabs, consulta [Elementos — Tabs](builder-elements#tabs). Para más información sobre los grupos seleccionables, consulta [Elementos y grupos seleccionables](flow-selectable-elements). Para añadir las pestañas: 1. Haz clic en **+** > **Tabs** y elige un preset: Segment control, Button Tabs o Underline. 2. Con el nombre de cada pestaña seleccionado en el lienzo o en el panel **Layers**, edita el campo **Content** en la pestaña **Design** para cambiar la etiqueta; por ejemplo, `Premium` y `Pro`. ## 5. Añade una lista de características a la primera pestaña \{#5-add-a-feature-list-to-the-first-tab\} Una lista de características breve y compacta dentro de la primera pestaña indica a los usuarios qué incluye ese plan. Para ver el conjunto completo de ajustes de lista, consulta [Elementos — Lista](builder-elements#list). Para añadir la lista de características: 1. Haz clic en **+** > **List** y elige un ajuste de lista. Icon List es el más compacto para paywalls. El elemento aparece al final del árbol de capas. 2. Con cada fila seleccionada, edita el título en el campo **Content**. 3. En el panel **Layers**, arrastra la lista al contenedor **Content** de la primera pestaña. ## 6. Añade la lista de productos al primer tab \{#add-the-product-list-to-the-first-tab\} La lista de productos muestra las opciones de suscripción del primer tab. El elemento Products renderiza una tarjeta por cada producto asignado a la pantalla y crea su propio grupo seleccionable. Para más información sobre cómo gestionar productos, consulta [Configurar compras](paywall-product-block). Para añadir y configurar productos: 1. Haz clic en **+** > **Products** y elige un preset de diseño. Vertical List funciona bien para planes apilados. El elemento aparece al final del árbol de capas. 2. Selecciona cada tarjeta de producto en el canvas y elige un producto del desplegable en la pestaña **Design**. 3. En el panel **Layers**, arrastra la capa Products al contenedor **Content** de la primera pestaña. ## 7. Añadir el botón de compra a la primera pestaña \{#add-the-purchase-button-to-the-first-tab\} El botón de compra inicia la compra in-app del producto que el usuario haya seleccionado en la primera pestaña. Su etiqueta muestra el precio del producto seleccionado para mantenerse sincronizada con la elección del usuario. Para más información sobre la acción de compra, consulta [Acciones — Compra](onboarding-actions#purchase). Para añadir y configurar el botón de compra: 1. Haz clic en **+** > **Buttons** y elige un preset de botón. El elemento aparece al final del árbol de capas. 2. Con el botón seleccionado, abre la pestaña **Design** y coloca el cursor en el campo **Content**. Haz clic en el icono de variable Variable icon, selecciona `products.selectedProduct` y luego el atributo `prod_price` — la variable completa se resuelve como `products.selectedProduct.prod_price`. Rodéala con el resto de la etiqueta — por ejemplo, `Subscribe for {prod_price}`. 3. Cambia a la pestaña **Interactions** y haz clic en **Add trigger** > **On tap** > **Add action**. 4. Establece **Action** en **Purchase** y **Product** en `products.selectedProduct`. 5. En el panel **Layers**, arrastra el botón al contenedor **Content** de la primera pestaña. ## 8. Copia el contenido del primer tab en el segundo tab \{#8-copy-the-first-tabs-content-into-the-second-tab\} En lugar de reconstruir la misma estructura desde cero, copia la lista de características, la lista de productos y el botón de compra del primer tab al segundo. Solo tendrás que actualizar los valores después. Para copiar el contenido: 1. En el panel **Layers**, expande el contenedor **Content** de la primera pestaña. 2. Selecciona cada elemento que haya dentro (lista de funciones, productos, botón de compra), cópialo con ⌘C / Ctrl+C y pégalo con ⌘V / Ctrl+V. Las copias aparecerán al final del árbol de capas. 3. Arrastra cada elemento copiado al contenedor **Content** de la segunda pestaña. ## 9. Actualiza el contenido de la segunda pestaña \{#update-the-second-tabs-content\} La segunda pestaña ahora es un espejo de la primera. Actualiza cada elemento para que refleje el segundo plan. Para actualizar la segunda pestaña: 1. Edita la lista de características dentro de la segunda pestaña para que las filas coincidan con las características del segundo plan. 2. Selecciona cada tarjeta de producto en el elemento Products de la segunda pestaña y asigna los productos del segundo plan desde el desplegable. Este elemento Products se convierte automáticamente en un grupo seleccionable separado (`products2`). 3. Selecciona el botón de compra en la segunda pestaña. En el campo **Content** de la pestaña **Design**, cambia la variable de precio de `products.selectedProduct.prod_price` a `products2.selectedProduct.prod_price`. 4. Cambia a la pestaña **Interactions** y actualiza el **Product** de la acción **Purchase** de `products.selectedProduct` a `products2.selectedProduct`. ## 10. Añadir los enlaces del pie de página compartidos \{#add-the-shared-footer-links\} Los enlaces de términos de uso, política de privacidad y restaurar compras son visibles independientemente de la pestaña activa. Añádelos a nivel de pantalla —fuera de los dos contenedores de contenido de pestañas— para que sean compartidos entre las pestañas. Para añadir los enlaces del pie de página: 1. Haz clic en **+** > **Buttons** > **Links**. Esto añade una fila con Restore Purchases, Terms of Use y Privacy Policy al final del árbol de capas, que es justo donde la necesitas: en la raíz de la pantalla, no anidada dentro de una pestaña. 2. En el panel **Layers**, selecciona el botón **Terms of Use**. Abre la pestaña **Interactions** y pega la URL de tus términos en el campo **Open URL**. 3. Repite el proceso con el botón **Privacy Policy** usando tu URL de privacidad. 4. Deja el enlace **Restore Purchases** tal como está. Su acción ya está preconfigurada. ## Próximos pasos \{#next-steps\} - [Guarda y publica tu flow](builder-save-publish). - [Añade el flow a un placement](create-placement) para empezar a mostrárselo a los usuarios. --- # File: paywall-features-per-product --- --- title: "Mostrar diferentes características por producto" description: "Muestra una lista de características distinta según el producto que seleccione el usuario, usando visibilidad condicional." --- Esta plantilla usa visibilidad condicional para destacar distintas listas de características según el plan. La pantalla muestra dos productos —por ejemplo, Pro y Pro+— y la lista de características cambia dependiendo del producto que el usuario haya seleccionado. Un producto está marcado como predeterminado, por lo que su lista de características es visible cuando se carga la pantalla por primera vez. ## Antes de empezar \{#before-you-start\} - [Crea productos](create-product) en el Adapty Dashboard. - [Conecta Adapty con App Store y Google Play](integrate-payments). ## 1. Configura el diseño de la pantalla \{#1-set-up-the-screen-layout\} La pantalla actúa como contenedor de todo lo que añades. En este ejemplo, el fondo es una imagen, pero un color sólido o un degradado funcionan igual. Para ver la lista completa de propiedades de la pantalla, consulta [Pantallas y capas — Configuración de pantalla](paywall-layout-and-products#screen-settings). Para configurar la pantalla: 1. Haz clic en un área vacía del canvas para seleccionar la pantalla. 2. En **System UI**, desactiva **Safe area** para que el fondo se extienda hasta los bordes de la pantalla. 3. En **Fill**, elige un tipo de fondo y configúralo. Este ejemplo usa una **Image** Image, pero un color sólido o un degradado funcionan igual. 4. En **Layout**, establece la dirección en **Vertical** Vertical y configura el espaciado y la alineación para que los elementos se apilen desde arriba con el contenido ocupando el espacio restante. ## 2. Añade el botón de cierre \{#2-add-the-close-button\} El botón de cierre descarta el paywall. El preset **Close** viene preconfigurado — no es necesario configurar ninguna acción. 1. En el canvas, haz clic en **+**. 2. Selecciona **Buttons** > **Close**. ## 3. Añade el título y combínalo con el botón de cierre \{#add-the-title-and-pair-it-with-the-close-button\} El encabezado aparece junto al botón de cierre en la parte superior de la pantalla. Para alinearlos horizontalmente, envuelve ambos en un contenedor horizontal. Para añadir el título: 1. Haz clic en **+** > **Text** > **H1**. 2. Con el H1 seleccionado, abre la pestaña **Design** y edita el texto en el campo **Content**. Para agrupar el título con el botón de cierre: 1. En el panel **Layers**, haz clic en el menú de tres puntos Context menu sobre la capa del botón de cierre y elige **Wrap** > **Wrap in Horizontal Container**. 2. Arrastra la capa H1 al nuevo contenedor horizontal. Para alinear los dos elementos: 1. Ajusta el tamaño del botón de cierre y el tamaño de fuente del H1 para que quepan cómodamente en la misma línea. 2. Con el contenedor horizontal seleccionado, configura la alineación y la distribución en el panel derecho para que los elementos queden bien colocados. ## 4. Añade la lista de productos \{#add-the-product-list\} Añade los productos entre los que el usuario puede elegir. Marca uno como predeterminado para que la pantalla tenga un estado con sentido cuando se cargue por primera vez. Para más información sobre cómo gestionar productos, consulta [Configurar compras](paywall-product-block). Para añadir y configurar productos: 1. Haz clic en **+** > **Products** y elige un diseño predefinido. La Lista Vertical funciona bien para esta plantilla. 2. Selecciona cada tarjeta de producto en el lienzo y elige un producto en el menú desplegable de la pestaña **Design**. 3. Selecciona la tarjeta que quieres que esté seleccionada por defecto —por ejemplo, Pro+— y activa **Set as default product** en la pestaña **Design**. ## 5. Añade la lista de características del primer producto \{#add-the-feature-list-for-the-first-product\} La primera lista de características describe el producto por defecto. Solo es visible cuando el usuario tiene seleccionado el primer producto. Para más información sobre la visibilidad condicional, consulta [Visibilidad condicional](onboarding-element-visibility). :::tip En lugar de dos listas separadas, puedes añadir una única lista y hacer que los elementos de texto dentro de ella sean condicionales, de modo que una sola lista se adapte al producto seleccionado. Consulta [Añadir texto condicional](onboarding-text#add-conditional-text). ::: Para añadir y configurar la lista de características: 1. Haz clic en **+** > **List** y elige un preset de lista compacta. Icon List funciona bien para los paywalls. 2. Con cada fila seleccionada, edita el título en el campo **Content** para describir las características del primer producto. 3. Con la lista aún seleccionada, abre la pestaña **Design**. En **Visibility**, selecciona **Conditional** Conditional. 4. Configura la condición para que la lista se muestre solo cuando el primer producto sea el seleccionado en ese momento. Compara con la variable `products.selectedProduct.prod_title`. En **Value**, haz clic en el icono de variable `{}`, selecciona la tarjeta del primer producto y luego su atributo `prod_title` — la comparación se resuelve con el título de ese producto. ## 6. Añade la lista de características del segundo producto \{#add-the-feature-list-for-the-second-product\} Repite el mismo proceso para el segundo producto. Las dos listas son mutuamente excluyentes: solo una es visible a la vez, según el producto seleccionado. Para añadir la segunda lista de características: 1. Haz clic en **+** > **List** y elige el mismo preset compacto para mantener coherencia visual. 2. Edita cada fila para describir las características del segundo producto. 3. En **Visibility**, selecciona **Conditional** Conditional y configura la misma condición que en el paso 5, pero apunta el selector de variables **Value** al `prod_title` de la segunda tarjeta de producto. ## 7. Añadir el botón de compra \{#add-the-purchase-button\} El botón de compra inicia la compra in-app del producto que el usuario haya seleccionado. Su etiqueta muestra el precio del producto seleccionado, por lo que se actualiza cuando el usuario cambia de plan. Para más información sobre la acción de compra, consulta [Acciones — Purchase](onboarding-actions#purchase). Para añadir y configurar el botón de compra: 1. Haz clic en **+** > **Buttons** y elige un preset de botón. 2. Con el botón seleccionado, abre la pestaña **Design** y coloca el cursor en el campo **Content**. Haz clic en el icono de variable Variable icon, selecciona `products.selectedProduct` y luego el atributo `prod_price` — la variable completa se resuelve como `products.selectedProduct.prod_price`. Rodéala con el resto de la etiqueta — por ejemplo, `Subscribe for {prod_price}`. 3. Cambia a la pestaña **Interactions** y haz clic en **Add trigger** > **On tap** > **Add action**. 4. Establece **Action** en **Purchase** y **Product** en `products.selectedProduct`. ## 8. Añadir los enlaces del pie de página \{#add-the-footer-links\} Los enlaces de términos de uso, política de privacidad y restaurar compras van debajo del contenido principal. Para añadir los enlaces del pie de página: 1. Haz clic en **+** > **Buttons** > **Links**. Esto añade una fila con Restore Purchases, Terms of Use y Privacy Policy al final del árbol de capas. 2. En el panel **Layers**, selecciona el botón **Terms of Use**. Abre la pestaña **Interactions** y pega la URL de tus términos en el campo **Open URL**. 3. Repite el proceso para el botón **Privacy Policy** con tu URL de privacidad. 4. Deja el enlace **Restore Purchases** tal como está. Su acción ya está preconfigurada. ## Próximos pasos \{#next-steps\} - [Guarda y publica tu flow](builder-save-publish). - [Añade el flow a un placement](create-placement) para empezar a mostrárselo a los usuarios. --- # File: show-offer-on-close --- --- title: "Mostrar una oferta cuando los usuarios pulsan cerrar" description: "Intercepta el primer toque en cerrar para mostrar una oferta de última oportunidad antes de que los usuarios abandonen el paywall." --- Cuando un usuario pulsa el botón de cerrar, está a punto de irse sin pagar. Esta receta intercepta el primer toque en cerrar: en lugar de cerrar el paywall, muestra un overlay con una oferta de última oportunidad. Cuando el usuario descarta la oferta, el botón de cerrar funciona con normalidad y cierra el flow. La lógica utiliza una variable booleana personalizada y una acción condicional: - `close_tapped` empieza como `False`. - El primer toque en cerrar muestra el overlay de la oferta y establece `close_tapped` en `True`. - Cualquier toque posterior en cerrar cierra el flow. ## Antes de empezar \{#before-you-start\} - Crea una pantalla de paywall con un botón de cierre — por ejemplo, sigue [Crea una pantalla de paywall básica](basic-paywall-screen). - [Crea un producto](create-product) con una [oferta](offers) para promocionar en el overlay. ## 1. Crea la variable \{#1-create-the-variable\} Para más información sobre las variables personalizadas, consulta [Variables](onboarding-variables#custom-variables). 1. En el panel izquierdo, haz clic en el icono **{ }** para abrir **Variables**. 2. En la pestaña **Custom**, haz clic en **+**. 3. Nombra la variable `close_tapped` y establece **Value Type** en **Boolean**. Deja **Initial Value** en **False**. 4. Haz clic en **Create variable**. ## 2. Construye el overlay de oferta \{#2-build-the-offer-overlay\} El overlay es un contenedor que se fija a la pantalla del dispositivo y se superpone al paywall. Constrúyelo visible — los elementos ocultos no se pueden editar, así que lo ocultarás en el paso 4, una vez que el contenido esté en su lugar. 1. Haz clic en **+** > **Layout** > **Vertical Container**. 2. Con el contenedor seleccionado, abre la pestaña **Design** y establece **Position** en **Fixed**. Configura la alineación horizontal en **Left & Right** y la vertical en **Top**. Como no hay opción de centrado vertical, introduce un desplazamiento superior (por ejemplo, `300`) para mover el overlay hacia el centro de la pantalla. 3. En **Fill**, define un fondo: un color sólido o una imagen. El contenedor es transparente por defecto, así que sin relleno el paywall seguirá visible debajo del overlay. 4. Añade el contenido de la oferta. Con el contenedor seleccionado en el panel **Layers**, haz clic en **+** > **Text** > **H2**. Para mostrar el precio con descuento, inserta [variables de oferta](onboarding-variables#product-variables) como `offer_price` en el campo **Content**. 5. Haz clic en **+** > **Products**, elige un preset de diseño y arrástralo al overlay. Selecciona la tarjeta de producto en el lienzo y elige el producto y la oferta en la pestaña **Design**. 6. Haz clic en **+** > **Buttons**, elige un preset de botón y arrástralo al overlay. En la pestaña **Interactions**, haz clic en **Add trigger** > **On tap** > **Add action**, y establece **Action** en **Purchase** y **Product** en tu producto de oferta. ## 3. Añadir el botón de cierre al overlay \{#3-add-the-dismiss-button-to-the-overlay\} El overlay necesita su propio botón de cierre. Su acción debe ocultar el overlay, no cerrar el flow. 1. Con el overlay seleccionado, haz clic en **+** > **Buttons** > **Close flow**. 2. Con el botón seleccionado, abre la pestaña **Interactions**. El preset incluye una acción **Close Flow** preconfigurada. Haz clic en la acción y cambia su tipo a **Hide element**. Establece el destino como el contenedor del overlay. :::important No dejes la acción **Close Flow** preconfigurada en el botón de cierre del overlay — cerraría el flow completo en lugar de ocultar el overlay. ::: ## 4. Ocultar el overlay \{#hide-the-overlay\} El overlay debe permanecer invisible hasta el primer toque de cierre. En el panel **Layers**, selecciona el contenedor del overlay y establece su estado en **Hide** Hide. El overlay permanece en el árbol de capas pero ya no se renderiza en el canvas. ## 5. Configura el botón de cierre \{#5-set-up-the-close-button\} Reemplaza la acción predeterminada del botón de cierre por una acción condicional que se ramifique en `close_tapped`. Para más información sobre las acciones condicionales, consulta [Acciones — Acciones condicionales](onboarding-actions#conditional-actions). 1. Selecciona el botón de cerrar del paywall — el que aparece en la pantalla, no el botón de cierre del overlay. 2. Abre la pestaña **Interactions**, haz clic en la acción **Close Flow** preconfigurada y cambia su tipo a **Conditional Action**. 3. En el bloque **if**, haz clic en **Add condition** y configura: `close_tapped` **Equals** **False**. 4. En el bloque **then**, añade dos acciones: - **Set Variable**: establece `close_tapped` en **True**. - **Show element**: Establece el objetivo en el overlay de la oferta. 5. En el bloque **else**, añade una acción **Close Flow**. Ahora el primer toque en cerrar muestra la oferta. Después de que el usuario la descarte, volver a tocar cerrar coincide con la rama **else** y cierra el flow. ## Próximos pasos \{#next-steps\} - [Guarda y publica tu flow](builder-save-publish). - [Añade el flow a un placement](create-placement) para empezar a mostrárselo a los usuarios. --- # File: strikethrough-price --- --- title: "Mostrar un precio tachado con una insignia de descuento" description: "Tacha el precio del plan mensual junto al precio mensual efectivo del plan anual para que el descuento sea visible de un vistazo." --- Un precio tachado junto al precio real hace que el descuento sea visible de un vistazo. Esta receta transforma una tarjeta de producto anual para mostrar lo que costaría la suscripción por mes con el plan mensual — tachado — junto al precio mensual efectivo del plan anual, coronado con una insignia de descuento. El formato de tachado se aplica a un elemento de texto completo: no puedes tachar una variable dentro de un texto más largo. Por eso, el precio tachado vive en su propio elemento de texto, junto al precio con descuento, dentro de un contenedor horizontal. :::tip Si con mostrar un precio "antes" inflado para el mismo producto es suficiente —por ejemplo, el doble del precio actual, tachado—, añade directamente el elemento [Old Price](onboarding-text#add-an-old-price). Esta guía cubre el caso en que el precio tachado es el precio real de otro producto. ::: ## Antes de empezar \{#before-you-start\} - Crea una pantalla de paywall con productos; por ejemplo, sigue la guía [Crea una pantalla de paywall básica](basic-paywall-screen). Esta receta asume que el paywall ofrece un producto anual y uno mensual. ## 1. Añadir filas de precio apiladas \{#1-stack-the-price-rows\} La tarjeta anual comienza con una sola línea de precio: la variable `prod_price` del producto anual seguida de `/year`, que se muestra, por ejemplo, como `$29.99/year`. Añade una segunda línea encima para el precio mensual. 1. En el panel **Layers**, selecciona el texto del precio dentro de la tarjeta del producto anual. Ya está dentro del contenedor vertical de la tarjeta, así que la copia se apilará debajo. 2. Haz clic en el menú de tres puntos Context menu de la capa y elige **Duplicate**. La copia aparece debajo del original. En el siguiente paso, el texto superior se convierte en la fila del precio mensual; el inferior mantiene el precio anual. ## 2. Divide la fila mensual en dos precios \{#2-split-the-monthly-row-into-two-prices\} La fila mensual contiene dos elementos de texto: el precio tachado del plan mensual y el precio por mes del plan anual. :::important El selector de variables solo ofrece los precios de los productos presentes en una pantalla del flow. Para referenciar un producto que no está en el flow, crea una pantalla vacía, añade un elemento **Products** y asígnale el producto. Asegúrate de que ninguna acción de navegación conduzca a esa pantalla. ::: 1. Selecciona el elemento de texto superior, haz clic en su menú de tres puntos y elige **Wrap** > **Wrap in Horizontal Container**. 2. Haz clic en el menú de tres puntos de la capa de texto dentro del nuevo contenedor y elige **Duplicate**. 3. Selecciona el primer elemento de texto y borra su campo **Content**. Haz clic en el icono de variable Variable icon, elige el producto mensual y luego su atributo `prod_price_per_month`. Escribe `/month` después de la variable. 4. En la pestaña **Design**, en **Typography**, establece **Decoration** en **Strikethrough** Strikethrough (consulta [Estilos y apariencia — Decoration](builder-styling#decoration)). 5. Selecciona el segundo elemento de texto y reemplaza su contenido de la misma forma, pero elige el atributo `prod_price_per_month` del producto anual. Escribe `/month` después de la variable. ## 3. Atenúa el precio anual \{#tone-down-the-yearly-price\} Los precios mensuales duplicados heredaron el estilo del precio original, así que los tres precios comparten el mismo tamaño y grosor. Haz el precio anual más discreto para que la fila mensual destaque. 1. En el canvas, selecciona el elemento de texto inferior: el precio anual. 2. En la barra de herramientas sobre el elemento, abre el desplegable de estilo de texto y elige un estilo más sutil, por ejemplo, **Caption**. O bien, selecciona un estilo diferente en **Typography** en la pestaña **Design**. ## 4. Añade el distintivo de descuento \{#add-the-discount-badge\} El distintivo resalta el ahorro junto a los precios. 1. Haz clic en **+** > **Badge**. 2. En el panel **Layers**, arrastra el distintivo dentro del producto anual, entre el contenedor vertical con los precios y el botón de opción. 3. Selecciona la capa de texto del distintivo y edita el campo **Content** — por ejemplo, `Save 75%`. No hay ninguna variable para el porcentaje de descuento — calcúlalo a partir de tus precios e introdúcelo como texto estático. Ahora la tarjeta anual ancla su precio al plan mensual: el precio mensual tachado, el precio mensual efectivo del plan anual y la insignia de descuento aparecen en una misma línea, con el precio anual completo debajo. ## Próximos pasos \{#next-steps\} - [Guarda y publica tu flow](builder-save-publish). - [Añade el flow a un placement](create-placement) para empezar a mostrárselo a los usuarios. --- # File: onboarding-flow-tutorial --- --- title: "Crea un flow de onboarding personalizado" description: "Recorre el proceso completo para crear un flow de onboarding multipantalla — pantallas, contenido, navegación y ramas condicionales — con un ejemplo guiado." --- Un flow multipantalla en el Flow Builder es una secuencia de pantallas conectadas mediante acciones de navegación. El flow puede ser lineal o ramificarse según la entrada del usuario recogida en una pantalla anterior. Este tutorial recorre el proceso de principio a fin — crear pantallas, construir su contenido, configurar la navegación y añadir ramificaciones condicionales — usando un flow de onboarding de cuatro pantallas como ejemplo práctico. El ejemplo usa: - Un **campo de nombre** que expone el nombre del usuario como variable para personalización. - Un **cuestionario de opción única** cuya respuesta determina qué pantalla ve el usuario a continuación. - **Dos rutas de bifurcación** con contenido adaptado para cada segmento de audiencia. - Un **paywall** como pantalla final. El mismo patrón se aplica a cualquier flow que personalice el contenido según la información del usuario. ¿Prefieres el formato en vídeo? Este tutorial de inicio rápido recorre el mismo proceso de principio a fin: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/aa-m459VIuY?si=zN_Co6B6qB88UPZP" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Antes de comenzar \{#before-you-start\} - [Crea productos](create-product) en el Adapty Dashboard. El ejemplo del flow usa dos: una suscripción Anual y una Mensual. - [Conecta Adapty con App Store y Google Play](integrate-payments). ## 1. Configura estilos reutilizables \{#1-set-up-reusable-styles\} Los estilos reutilizables te permiten aplicar tipografía y colores consistentes en todas las pantallas con un solo clic. Los estilos de color incluyen una variante clara y una oscura, por lo que el flow admite ambos temas automáticamente. Para ver las instrucciones completas, consulta [Estilos y apariencia — Estilos reutilizables](builder-styling#reusable-styles). Para configurar los estilos: 1. En el panel izquierdo, abre el panel **Styles** Styles. 2. En la pestaña **Colors**, haz clic en **Plus Create style** y añade los colores que vayas a reutilizar. Para cada color, elige un valor Light, cambia a la pestaña Dark y elige un valor Dark. 3. En la pestaña **Text**, haz clic en un estilo existente para editar su tipografía, grosor y tamaño, o haz clic en **Plus Create style** para añadir presets personalizados. ## 2. Crea las pantallas \{#2-create-the-screens\} Un flow es una secuencia de pantallas. Configura la primera pantalla con la base compartida — diseño, fondo y área segura — y luego duplícala para el resto. Así, todas las pantallas comparten la misma base y solo tienes que configurarla una vez. Para más información sobre cómo gestionar pantallas, consulta [Pantallas y capas — Gestionar pantallas](paywall-layout-and-products#manage-screens). Para configurar las pantallas: 1. Haz clic en un área vacía del lienzo en la primera pantalla para abrir la configuración de pantalla. 2. En **System UI**, desactiva **Safe area** para que los fondos y los elementos alineados con los bordes puedan extenderse hasta los extremos de la pantalla. 3. En **Fill**, elige un tipo de fondo y configúralo — por ejemplo, una **Image** Image que aparezca detrás de todas las pantallas del flow. 4. En **Layout**, establece la dirección en **Vertical** Vertical y elige una distribución que se ajuste a tu diseño. 5. En la sección **Screens** del panel izquierdo, haz clic en el menú de tres puntos Context menu en la primera pantalla y elige **Duplicate**. Repite el proceso hasta tener cuatro pantallas en total — la segunda ruta de bifurcación se añadirá más adelante duplicando la primera. 6. Renombra cada pantalla según su función — en nuestro ejemplo: `Welcome`, `Quiz`, `Rock path` y `Paywall`. ## 3. Construye la pantalla de introducción \{#build-the-introduction-screen\} La primera pantalla suele marcar el tono: un titular, una lista de características y una llamada a la acción que abre el resto del flow. En nuestro ejemplo, es la pantalla de bienvenida. Haz clic en la pantalla **Welcome** en el panel **Screens** y añade los elementos: 1. Añade la imagen principal. Haz clic en **+** > **Media** > **Image**, sube tu imagen y ajusta los márgenes si es necesario. 2. Añade un título: haz clic en **+** > **Text**, elige un estilo de encabezado de tus estilos de texto guardados y edita el campo **Content**. 3. Añade la lista de características. Haz clic en **+** > **List** > **Icon Cards**, luego edita el icono y la etiqueta de cada tarjeta. 4. Añade un botón de navegación principal en la parte inferior. La acción se configurará en el paso de navegación. ## 4. Crear la pantalla de entrada y cuestionario \{#4-build-the-input-and-quiz-screen\} La segunda pantalla recopila información del usuario. En nuestro ejemplo, solicita un nombre y una respuesta de opción única que determina qué camino verá el usuario a continuación. Para más información sobre entradas y cuestionarios, consulta [Entradas y formularios](builder-inputs-and-forms) y [Encuestas y cuestionarios](onboarding-quizzes). Haz clic en la pantalla **Quiz** en el panel **Screens** y añade los elementos. Cada grupo de la pantalla — introducción, pregunta + entrada, pregunta + cuestionario — se coloca en su propio Contenedor Vertical para que los elementos relacionados permanezcan juntos visualmente. 1. Añade el titular y el cuerpo de la introducción. Haz clic en **+** > **Text** > **H1** para el titular y en **+** > **Text** > **Body** para el texto de apoyo. 2. Agrupa la introducción. Haz clic en **+** > **Layout** > **Vertical Container**, arrastra el nuevo contenedor a la parte superior del árbol de capas y luego arrastra el H1 y el cuerpo dentro de él. 3. Añade la primera pregunta e input. Haz clic en **+** > **Text** para el título de la pregunta, luego haz clic en **+** > **Inputs** > **Text** para el campo. 4. Establece el **Element ID** del input en la pestaña **Design** — en nuestro ejemplo, `name`. Esto expone el valor como una variable a la que otras pantallas pueden hacer referencia. 5. Agrupa el título y el campo en un Contenedor Vertical de la misma forma que la introducción. 6. Añade la segunda pregunta y el quiz. Haz clic en **+** > **Text** para el título, luego haz clic en **+** > **Quiz** y elige un preset de diseño como Icon Options. Configura las opciones — en nuestro ejemplo, `Rock` y `Hip hop`. 7. Agrupa el título y el quiz en un Contenedor Vertical de la misma forma. 8. Establece los IDs de las opciones. Selecciona cada opción del quiz, abre la pestaña **Interactions** y establece su **Element ID**. Estos IDs se utilizan en la navegación condicional más adelante. 9. Cambia el quiz a selección única: haz clic en un área vacía del canvas para abrir **Screen settings**, desplázate hacia abajo hasta **Selectable Groups**, haz clic en el nombre del grupo del quiz y establece el tipo en **Single choice**. 10. Añade un botón principal en la parte inferior — es el botón Siguiente que activa la ramificación. ## 5. Crear el primer camino de bifurcación \{#5-build-the-first-branching-path\} Cada pantalla de camino adapta el contenido para un segmento de audiencia. En nuestro ejemplo, el camino Rock muestra contenido centrado en el rock: playlists, artistas y recomendaciones. Para más información sobre las variables, consulta [Variables](onboarding-variables). Para crear la pantalla: 1. En el panel **Screens**, haz clic en la pantalla **Rock path**. 2. Añade un titular. Coloca el cursor en el campo **Content** donde deba aparecer la personalización, haz clic en el icono de variable Variable icon y abre la pestaña **Elements**. Selecciona la pantalla donde se encuentra el input — en nuestro ejemplo, **Quiz** — y luego selecciona la variable de valor del input. El selector la resuelve como `<elementId>.value` — en nuestro ejemplo, `name.value`. En tiempo de ejecución, el titular se actualiza con lo que haya escrito el usuario. 3. Añade el cuerpo del texto como elementos de texto adicionales, adaptados al segmento de audiencia de esta ruta. 4. Añade un botón principal en la parte inferior. ## 6. Construye el segundo camino de ramificación \{#build-the-second-branching-path\} Las pantallas de cada camino suelen compartir la misma estructura: solo cambia el texto. Duplica la primera pantalla del camino y actualiza el contenido. Para duplicar y actualizar: 1. En el panel **Screens**, selecciona la primera pantalla del camino y pulsa ⌘D / Ctrl+D para duplicarla. La copia aparece al final de la lista de pantallas. 2. Renombra la copia — en nuestro ejemplo, `Hip hop path` — y arrástrala al lugar correcto en la lista de pantallas, de forma que quede junto a la pantalla de la que fue duplicada. 3. Actualiza el texto del cuerpo para el otro segmento de audiencia. El titular personalizado sigue funcionando: la variable se mantiene. ## 7. Crea el paywall \{#7-build-the-paywall\} La pantalla final es el paywall, donde el usuario puede suscribirse. Para una guía más completa sobre la mecánica de los paywalls, consulta [Crea una pantalla de paywall básica](basic-paywall-screen). La versión a continuación resume esa guía. Haz clic en la pantalla **Paywall** en el panel **Screens** y añade los elementos: 1. Añade un **Horizontal Container** en la parte superior e inserta dentro un botón **Close**. El preset Close viene preconfigurado. 2. Añade la imagen principal, el título (con la misma variable de personalización que en las pantallas de ruta) y un subtítulo como texto de apoyo. 3. Añade los productos: haz clic en **+** > **Products** y selecciona **Vertical List**. Asigna a cada tarjeta un producto desde el desplegable en la pestaña **Design**. 4. Haz clic en la tarjeta del producto predeterminado y activa **Set as default product** para que esté preseleccionado cuando se cargue la pantalla. 5. Añade el botón de compra. Haz clic en **+** > **Buttons** y elige un preset. En la pestaña **Interactions**, haz clic en **Add trigger** > **On tap** > **Add action** y establece **Action** como **Purchase** con **Product** como `products.selectedProduct`. 6. Añade la plantilla **Button** > **Links** a la pantalla. Incluye tres enlaces en el pie: Restore Purchases, Terms of Use y Privacy Policy. El enlace Restore ya está preconfigurado. Para configurar los demás enlaces, selecciona el elemento de botón, abre la pestaña **Interactions** y establece el destino de la acción **Open URL**. ## 8. Conecta la navegación entre pantallas \{#wire-navigation-between-the-screens\} Las pantallas no se conectan automáticamente entre sí. Usa los activadores **On tap** y las acciones **Navigate to** para vincular el botón principal de cada pantalla con la siguiente. Si una pantalla ramifica el flujo según la entrada del usuario, usa una **Conditional action** en su lugar. Para más información sobre navegación y acciones condicionales, consulta [Navegación e interacción](onboarding-navigation-branching) y [Acciones — Acciones condicionales](onboarding-actions#conditional-actions). Para conectar la navegación del flow de ejemplo: 1. **Navegación estática desde la pantalla de introducción.** Abre la pantalla de bienvenida, selecciona el botón principal y cambia a la pestaña **Interactions**. Haz clic en **Add trigger** > **On tap** > **Add action**, establece **Action** en **Navigate to** y selecciona la siguiente pantalla — en nuestro ejemplo, la pantalla de Quiz. 2. **Navegación condicional desde el quiz.** Abre la pantalla Quiz, selecciona el botón Next y añade un disparador **On tap** con una **Conditional action**. Configura la regla IF/ELSE: - En el selector de variables, abre la pestaña **Elements**, elige la pantalla **Quiz** y selecciona `quiz.selectedOptionId`. - Usa el operador **Equals** y compara con el ID de una de las opciones — en nuestro ejemplo, la opción Rock. - **IF** la comparación coincide, activa **Navigate to** y elige la primera pantalla del recorrido. - **ELSE**, activa **Navigate to** y elige la segunda pantalla del recorrido. 3. **Navegación estática desde cada rama hacia el paywall.** Repite el patrón del paso 1 en cada pantalla de ruta, con el paywall como destino. ## Próximos pasos \{#next-steps\} - [Guarda y publica tu flow](builder-save-publish). - [Añade el flow a un placement](create-placement) para empezar a mostrárselo a los usuarios. - Para flows específicos por audiencia (en lugar de ramificaciones dentro del flow), crea segmentos de audiencia y asigna distintos flows en la página del Placement. :::link ¿Quieres aprender más sobre cómo crear flows? Mira tutoriales en vídeo paso a paso en nuestra [lista de reproducción de YouTube](https://www.youtube.com/playlist?list=PLMksWqaZiWtM). ::: --- # File: migrate-to-flows --- --- title: "Migrar a flows" description: "Mueve tu onboarding y paywall por separado a un único flow de Adapty: qué cambia y cómo desplegarlo sin interrumpir a los usuarios en versiones antiguas de la app." --- En Adapty, un *flow* combina un onboarding y un paywall en una única entidad dentro de un placement. Un flow reemplaza el onboarding y el paywall por separado que hoy construyes y sirves de forma independiente. Esta guía explica qué cambia al migrar a flows y cómo implementar el cambio sin interrumpir a los usuarios en versiones anteriores de la app. :::important Los flows están disponibles actualmente en iOS, Android, React Native, Flutter y Capacitor SDK v4 en adelante. Próximamente se añadirá soporte para otras plataformas y frameworks. ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/8Cby6lVGI0o?si=rYA1HtdayyF1ffWd" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Flows vs. onboardings y paywalls \{#flows-vs-onboardings-and-paywalls\} Con onboardings y paywalls por separado, gestionas dos builders y dos placements. Además, tienes que gestionar en tu propio código el paso del usuario del onboarding al paywall. Un flow reemplaza ambos con una sola experiencia — pantallas de introducción, un quiz y la pantalla de compra — construida en un único editor y servida desde un único placement. La tabla de abajo compara lo que ofrece cada opción: | | Flow | Paywall de Paywall Builder | Onboarding | |---|---|---|---| | Múltiples pantallas | Sí | No: una sola pantalla | Sí | | Renderizado | Nativo | Nativo | WebView | | Productos y placement | Un placement; añades productos directamente al flow | Un placement; añades productos directamente al paywall | Un placement, pero sin productos propios: para vender, creas un paywall independiente y lo sirves desde su propio placement | ## ¿Deberías migrar? \{#should-you-migrate\} Tus onboardings y paywalls existentes siguen funcionando, y Adapty continuará dando soporte. Sin embargo, las nuevas funcionalidades ahora se incorporan en los flows en lugar de en los editores independientes de onboarding y paywall. **Si estás construyendo a largo plazo, los flows son la mejor base**: migra a ellos cuando encaje en tu calendario de lanzamientos. ## Cómo migrar \{#how-to-migrate\} La migración tiene cuatro pasos. La mayor parte del trabajo es una actualización puntual del SDK — crear y previsualizar el flow no requiere código. 1. **[Crea tu flow](#build-your-flow)**: Crea un flow en el editor visual sin código; no necesitas desarrolladores. 2. **[Previsualiza en el dispositivo](#preview-on-device)**: Comprueba el flow en un dispositivo real a través de la app móvil de Adapty; no necesitas compilar la app. 3. **[Crea un nuevo placement para tu flow](#create-a-new-placement-for-your-flow)**: Crea un nuevo placement de flow con su propio ID único y decide cómo coexiste con tus placements actuales. 4. **[Actualiza el SDK](#update-the-sdk)**: Actualiza a iOS, Android, React Native o Capacitor SDK v4, obtén el flow desde su placement y verifica una compra en sandbox. Esta es la tarea principal del desarrollador. ### Crea tu flow \{#build-your-flow\} En la página **Flows**, haz clic en **Create flow** para empezar a construir tu onboarding y paywall como una sola experiencia. Para saber más sobre el builder: - **[Documentación de flows](adapty-flow-builder)**: Te guía por el builder y lo que puedes crear. - **[Recetas de flows habituales](flow-builder-recipes)**: Guías paso a paso para las pantallas más comunes. - **Pregunta a la IA**: Usa el chat en cualquier página de la documentación cuando te quedes atascado. :::note Crear un flow a partir de una plantilla lista o generarlo con IA todavía no está disponible — ambas opciones llegarán próximamente. Por ahora, cada nuevo flow comienza con varias pantallas de uso común que puedes editar y personalizar según tus necesidades. ::: ### Vista previa en el dispositivo \{#preview-on-device\} Puedes previsualizar el flow en un dispositivo real sin tocar la app. Descarga la [app de Adapty](https://apps.apple.com/us/app/adapty/id6739359219) desde el App Store. Luego, en el flow builder, haz clic en **Test on device**, elige un idioma y escanea el código QR con tu dispositivo. Así verás las pantallas reales, las ramificaciones, los textos y el diseño. :::note En el modo de vista previa, Adapty no puede acceder a tus productos en los stores, así que los precios que se muestran no son reales. Las compras reales se verifican más adelante, en la build v4 con una cuenta sandbox — consulta [Actualizar el SDK](#update-the-sdk). ::: ### Crea un nuevo placement para tu flow \{#create-a-new-placement-for-your-flow\} Un placement sirve para un único tipo de contenido: un flow, un paywall o un onboarding. No puedes convertir un placement de onboarding o paywall existente en un placement de flow (consulta los [tipos de placement](create-placement)). Un flow necesita su propio placement nuevo. **Dale al nuevo placement de flow un ID de placement completamente nuevo y único.** No puede coincidir ni reutilizar el ID de un placement de paywall o onboarding existente. :::warning Mantén tus placements antiguos activos durante la transición Los usuarios con versiones antiguas de la app tienen los IDs de placement de onboarding y paywall compilados en la app. Seguirán llamando a los métodos de onboarding y paywall y verán tu onboarding y paywall existentes hasta que actualicen. Retira los placements antiguos solo cuando la adopción del SDK v4 sea suficientemente alta. ::: No es necesario migrar todos los placements a flows de una vez. En SDK v4, el método `getFlow` obtiene datos tanto de placements de flow como de placements de paywall, por lo que tu app llama al mismo método en todos lados. Mantén los paywalls del Paywall Builder en los placements donde los quieras, y usa flows en el resto. Durante la transición, cada tipo de placement registra sus propias métricas. Mientras convivan versiones antiguas y nuevas de la app, los datos se dividen entre dos conjuntos de placements. Los placements de onboarding y paywall antiguos cubren las versiones anteriores; el nuevo placement de flow cubre el SDK v4+. Compáralos como cohortes separadas y espera que la proporción del placement de flow crezca a medida que los usuarios actualicen. Puedes seguir haciendo pruebas A/B con flows: ejecuta una [prueba A/B normal](ab-tests) entre variantes de flow en un placement de flow. Las pruebas A/B entre placements solo están disponibles para paywalls por ahora, así que aún no puedes ejecutar una entre placements de flow. Comparar un flow nuevo con tu paywall antiguo es una comparación de cohortes, no una prueba única, ya que viven en tipos de placement distintos. ### Actualizar el SDK \{#update-the-sdk\} Con tu placement de flow listo, apunta la app hacia él. Los flows solo se renderizan con el SDK de Adapty v4 o posterior. Actualiza el SDK y obtén el flow desde tu nuevo placement con `getFlow`. Consulta la guía de migración a v4 para tu plataforma — [iOS](migration-to-ios-sdk-v4), [Android](migration-to-android-sdk-v4), [React Native](migration-to-react-native-sdk-v4) o [Capacitor](migration-to-capacitor-sdk-v4) — para conocer los pasos específicos de actualización. Una vez configurado el flow, verifícalo como cualquier otro flujo de compra: ejecútalo en un dispositivo o simulador y realiza una compra en sandbox ([iOS](ios-test) / [Android](testing-on-android)) para confirmar que los productos, la compra y el nivel de acceso funcionan correctamente. :::note Los usuarios solo ven los flows después de instalar la app compilada con el SDK v4+. Quienes tengan una versión anterior de la app seguirán viendo el onboarding y el paywall que ya tenían, por eso los placements antiguos permanecen activos durante la transición. Lo mismo aplica en las plataformas que aún no son compatibles con flows. ::: --- # File: paywall-layout-and-products --- --- title: Pantallas y capas description: "Gestiona las pantallas y la jerarquía de elementos dentro de cada pantalla en el Flow Builder." --- Un flow está compuesto por una o más pantallas. Cada pantalla representa un paso en el recorrido del usuario — por ejemplo, un paywall, un cuestionario o una diapositiva con información sobre el producto. Los elementos de cada pantalla están organizados en una jerarquía de capas. Para gestionar tus pantallas, capas y elementos, abre la vista predeterminada **Screens and Layers**. Aquí verás la secuencia de pantallas y la estructura de capas de cada una. ## Gestionar pantallas \{#manage-screens\} La sección superior del panel izquierdo muestra todas las pantallas del flow. Cada entrada incluye una etiqueta numerada y una vista previa en miniatura. * **Seleccionar una pantalla**: Haz clic en una entrada de pantalla para activarla. El editor visual muestra la pantalla seleccionada y la sección Layers se actualiza para mostrar su jerarquía de capas. * **Añadir una pantalla**: Haz clic en el botón Plus en la parte superior de la sección Screens para añadir una nueva pantalla vacía al flow. * **Abrir la biblioteca de plantillas**: Haz clic en el botón Templates en la parte superior de la sección Screens para explorar y aplicar [plantillas de flow](paywall-builder-templates). * **Reordenar pantallas**: Arrastra y suelta las entradas de pantalla para cambiar su orden en el flow. :::important Si tu flow tiene pantallas vacías sin usar, no podrás publicarlo. Elimina cualquier pantalla borrador antes de publicar. ::: ### Acciones de pantalla \{#screen-actions\} Haz clic en el icono de tres puntos Context en una entrada de pantalla para abrir el menú contextual. | Acción | Atajo | Descripción | |--------|-------|-------------| | **Play Animation** | | Previsualiza las animaciones configuradas en esta pantalla | | **Copy** | ⌘C / Ctrl+C | Copia la pantalla al portapapeles | | **Paste here** | ⌘V / Ctrl+V | Pega una pantalla copiada anteriormente | | **Duplicate** | ⌘D / Ctrl+D | Crea una copia de la pantalla y la añade al flow | | **Rename** | | Cambia el nombre de la pantalla | | **Delete** | ⌘⌫ / Ctrl+Del | Elimina la pantalla del flow | :::tip El portapapeles persiste entre flows. Copia una pantalla o elemento de un flow, abre otro y pégalo. ::: :::warning Cuando eliminas una pantalla, cualquier acción [Navegar a pantalla](onboarding-navigation-branching) que apuntaba a ella **pierde su destino**, pero la acción en sí **no se elimina**. Asigna un nuevo destino o elimina la acción; de lo contrario, no podrás [previsualizar ni publicar el flow](builder-save-publish#publish-a-flow). ::: ## Navegar entre pantallas \{#navigate-between-screens\} :::link Artículo principal: [Navegación e interacción](onboarding-navigation-branching) ::: El orden de las pantallas en la lista no determina la navegación por sí solo. Para conectar pantallas, usa las interacciones de los elementos: configura un botón para llevar al usuario a otra pantalla. ## Configuración de la pantalla \{#screen-settings\} Para ver las propiedades y ajustes de la pantalla activa, haz clic en un área vacía de la vista previa de la pantalla. El panel derecho cambiará a la vista de configuración de la pantalla. ### IU del sistema \{#system-ui\} Controla cómo interactúa la pantalla con el hardware del dispositivo. * **Safe area** añade relleno para que el contenido no quede tapado por el notch ni las barras del sistema. * **Status bar** muestra u oculta la barra de estado del sistema (hora, batería, iconos de señal). ### Incluir pantalla en el indicador de progreso \{#include-screen-in-progress-indicator\} Si añades un elemento [Indicador de progreso](builder-loaders-and-progress-bars#progress-indicators) a tu flow, Adapty lo mostrará en todas las pantallas. Desmarca **Include screen in progress indicator** para quitar el indicador de progreso de una pantalla concreta. Úsalo para limpiar las pantallas de bienvenida, el paywall final o cualquier paso que no quieras registrar como progreso. ### Diseño de pantalla \{#screen-layout\} :::link Artículo completo: [Diseño y posicionamiento](manage-paywall-ui-elements) ::: La sección **Layout** determina cómo la pantalla distribuye sus elementos hijos. Estas propiedades están disponibles en cualquier elemento contenedor. * **Free**: Los elementos hijos se posicionan de forma independiente. * **Vertical**: Los elementos se organizan de arriba a abajo, como una columna flexbox. * **Horizontal**: Los elementos se organizan de izquierda a derecha, como una fila flexbox. Para los diseños verticales y horizontales, también puedes configurar el espaciado y la alineación. * **Alignment**: Posición del elemento en el eje transversal. * **Gap**: Espacio entre elementos adyacentes. * **Distribution**: Distribución del espacio entre los elementos hijos y alrededor de ellos. #### Diseño RTL \{#rtl-layout\} Activa la casilla **Mirror for RTL** para invertir el diseño en sistemas de escritura que van de derecha a izquierda. El orden de los elementos en los contenedores horizontales se invertirá. ### Fondo de pantalla \{#screen-background\} :::link Artículo principal: [Fondos](paywall-head-picture) ::: **Fill** establece el [fondo de pantalla](paywall-head-picture) como un color sólido, degradado, imagen o vídeo. El fondo cubre todo el viewport del dispositivo, incluidas las áreas detrás del notch y las barras del sistema, incluso cuando **Safe area** está activada. #### Reproducir el vídeo de fondo en bucle \{#loop-background-video\} Activa el interruptor **Loop** para reproducir el vídeo de fondo de forma continua. #### Asignar un ID de medio personalizado \{#assign-a-custom-media-id\} Al igual que con [cualquier imagen o vídeo](custom-media), puedes asignar al fondo de pantalla un ID de medios personalizado para referenciarlo en tu SDK. ### Espaciado de pantalla \{#screen-spacing\} Ajusta el relleno de pantalla para cada lado (superior, derecho, inferior, izquierdo). ### Scroll \{#scroll\} Controla el comportamiento del desbordamiento. Activa **Vertical scroll** para permitir que el contenido de la pantalla se desplace cuando supere la altura del viewport. ### Grupos seleccionables \{#selectable-groups\} :::link Artículo principal: [Elementos y grupos seleccionables](flow-selectable-elements) ::: La sección **Selectable groups** muestra todos los grupos seleccionables de la pantalla actual: desde [cuestionarios](onboarding-quizzes), [productos](paywall-product-block), [pestañas](builder-tabs), [controles de prueba](builder-toggles) o cualquier [elemento seleccionable personalizado](flow-selectable-elements#make-an-element-selectable). Haz clic en una entrada de grupo para renombrarla, cambiar su tipo, ver las variables que expone o eliminarla. ## Gestionar capas \{#manage-layers\} Cada elemento de una pantalla se representa como una capa. La sección Layers muestra el orden de los elementos en la pantalla activa. :::important Las capas de un flow no se superponen como las capas en un software de diseño gráfico. En su lugar, representan componentes individuales de la pantalla. Los elementos se superponen *solo* si utilizan [posicionamiento absoluto o fijo](manage-paywall-ui-elements). Su orden de apilamiento está determinado por la propiedad `z-index`, no por su posición en el árbol de capas. ::: La estructura en árbol refleja las relaciones padre-hijo. Haz clic en la flecha de cualquier capa padre para expandir o contraer sus hijos. No puedes crear capas directamente. Cada elemento que añades desde la vista [Añadir elemento](builder-elements) aparece como una nueva capa en el árbol. * **Seleccionar una capa**: Haz clic en una capa para seleccionarla. El editor visual resalta el elemento correspondiente en el lienzo, y el panel derecho muestra sus propiedades de [diseño](builder-styling) e [interacción](onboarding-navigation-branching). * **Reordenar capas**: Arrastra y suelta capas dentro del árbol para cambiar su orden dentro del contenedor padre. El orden en el árbol coincide con el orden visual en pantalla. * **Mostrar u ocultar una capa**: Pasa el cursor sobre una capa para que aparezca el icono de ojo Eye a su derecha. Haz clic en él para alternar la visibilidad de la capa. Las capas ocultas permanecen en el árbol pero no aparecen en el editor visual ni en el dispositivo. Para controlar la visibilidad con lógica en tiempo de ejecución, usa la [visibilidad condicional](onboarding-element-visibility). * **Contraer todas las capas**: Haz clic en el botón de contraer Collapse en la esquina superior derecha de la sección Layers para plegar el árbol completo. ### Acciones de capa \{#layer-actions\} Haz clic en el icono de tres puntos Context para abrir el menú contextual. | Acción | Atajo | Descripción | |--------|-------|-------------| | **Copy** | ⌘C / Ctrl+C | Copiar la capa al portapapeles | | **Paste here** | ⌘V / Ctrl+V | Pegar una capa copiada anteriormente como elemento hijo | | **Duplicate** | ⌘D / Ctrl+D | Crear una copia de la capa en el mismo contenedor | | **Rename** | | Cambiar el nombre de visualización de la capa. Por defecto, las capas usan su contenido o tipo de componente como nombre | | **Delete** | ⌘⌫ / Ctrl+Del | Eliminar la capa y todos sus elementos hijos | | **Wrap** | | Envolver la capa en un nuevo contenedor: **Wrap in Horizontal Container** o **Wrap in Vertical Container** | | **Unwrap / Ungroup** | | Eliminar el contenedor envolvente y mover sus elementos hijos un nivel hacia arriba | | **Move up** | ↑ | Mover la capa una posición hacia arriba en su contenedor padre | | **Move down** | ↓ | Mover la capa una posición hacia abajo en su contenedor padre | --- # File: manage-paywall-ui-elements --- --- title: "Layout y posicionamiento" description: "Organiza elementos en pantalla con layout, modo de posición, tamaño y espaciado." --- El Flow Builder crea layouts responsivos. No arrastras elementos a coordenadas exactas; en cambio, los anidas dentro de **contenedores** que organizan sus elementos hijos automáticamente. El contenedor define la dirección de los elementos (vertical u horizontal), la alineación y el espaciado. Cada elemento puede ajustar individualmente su tamaño y márgenes o, cuando sea necesario, salir del flujo normal con posicionamiento absoluto o fijo. :::link Para las propiedades visuales como relleno, bordes y efectos, consulta [Estilos y apariencia](builder-styling). ::: <Tabs groupId="video"> <TabItem value="align" label="Alineación y posicionamiento"> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/aRS4Bzb6W4I?si=qH7B6t3kMab70gBi" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ``` </TabItem> <TabItem value="layout" label="Diseño, tamaño y espaciado"> ``` <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/WQ9fpxrndok?si=ROMdIPvJ32tSwUX6" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> </TabItem> </Tabs> ## Diseño \{#layout\} El diseño es la herramienta principal para organizar los elementos en pantalla. Cada contenedor distribuye automáticamente sus elementos secundarios según un conjunto de reglas: dirección, alineación y espaciado. Los elementos de diseño disponibles en el builder son: * **[Contenedor vertical](builder-containers#containers)**: Organiza los elementos hijo de arriba a abajo * **[Contenedor horizontal](builder-containers#containers)**: Organiza los elementos hijo de izquierda a derecha * **[Divisor](builder-containers#dividers)**: Un separador visual entre elementos * **[Carrusel](builder-containers#carousel)**: Un conjunto de diapositivas con desplazamiento horizontal * **[Bottom Sheet](builder-containers#bottom-sheet)**: Un panel deslizante que muestra contenido adicional cuando el usuario pulsa un botón Los contenedores son los bloques principales de una pantalla. Puedes anidarlos entre sí para construir layouts complejos. Cada contenedor tiene una sección **Layout** en el panel derecho que controla cómo se distribuyen sus elementos hijos. Para agrupar elementos en un nuevo contenedor, usa la acción de capa **Wrap** [layer action](paywall-layout-and-products#layer-actions). Para eliminar un contenedor y ascender sus hijos, usa **Unwrap**. :::link Para más detalles sobre la jerarquía de pantallas y capas, consulta [Pantallas y capas](paywall-layout-and-products). ::: ### Dirección \{#direction\} * Free **Free**: Sin layout automático. Los elementos hijos se posicionan de forma independiente (útil cuando usan posicionamiento absoluto) * Vertical **Vertical**: Los elementos hijos se apilan de arriba hacia abajo, como filas en una columna * Horizontal **Horizontal**: Los elementos hijos se distribuyen de izquierda a derecha, como ítems en una fila ### Orden de los elementos \{#element-order\} Los elementos secundarios se renderizan en el orden en que aparecen en el panel **Layers**. En un contenedor vertical, el elemento situado más arriba en la lista aparece en la parte superior de la pantalla. En un contenedor horizontal, el elemento más arriba aparece a la izquierda. Arrastra los elementos en el panel Layers para reordenarlos, o usa **Move Up** y **Move Down** en las [acciones de capa](paywall-layout-and-products#layer-actions). ### Alineación \{#alignment\} La cuadrícula de alineación controla dónde se colocan los elementos hijos a lo largo del eje transversal del contenedor. En un contenedor vertical, la alineación controla la posición horizontal de los hijos (izquierda, centro o derecha). En un contenedor horizontal, controla su posición vertical (arriba, al medio o abajo). ### Distribución \{#distribution\} La distribución determina cómo se divide el espacio entre los elementos hijos a lo largo del eje principal: * **Gap** Gap (predeterminado): Un valor fijo en píxeles entre elementos hijos adyacentes * **Space Between**: Los hijos se extienden hasta los bordes; aparecen espacios iguales entre ellos * **Space Around**: Un espacio igual rodea a cada hijo, con espacios de la mitad de tamaño en los bordes * **Space Evenly**: Espacio igual antes, entre y después de todos los hijos ### Recortar contenido \{#clip-content\} Recorta visualmente el contenido que sobresale más allá de los límites del contenedor. Desactívalo para permitir el desbordamiento (por ejemplo, una insignia que intencionalmente sobresale del borde de la tarjeta). ## Posición \{#position\} Por defecto, la posición de cada elemento la determina automáticamente el layout de su contenedor. El toggle **Position** te permite sacarlo del flujo normal y posicionarlo manualmente. ### Relativo (predeterminado) \{#relative-default\} El elemento permanece en el flujo normal del diseño. Su posición la determina automáticamente el contenedor padre según sus reglas de maquetación: no se puede arrastrar libremente. Usa **Margin** para ajustar el espacio alrededor de un elemento relativo. Usa el posicionamiento relativo para la gran mayoría del contenido: bloques de texto, imágenes, tarjetas, botones y elementos de lista. ### Absoluta \{#absolute\} El elemento se extrae del flujo normal y se superpone sobre el resto del contenido. Ya no afecta al diseño de los elementos vecinos. Al seleccionar **Absolute**, aparecen controles adicionales: * **Campos de desplazamiento** (T, L, R, B): Establece la distancia en píxeles desde el elemento hasta cada borde de su contenedor padre * **Cuadrícula de anclaje**: Haz clic en un punto de la cuadrícula 3×3 para elegir a qué esquina, borde o centro del padre se ancla el elemento * **Anclaje horizontal** Horizontal positioning (Left / Center / Right) y **Anclaje vertical** Vertical positioning (Top / Center / Bottom): Menús desplegables que controlan el mismo punto de anclaje que la cuadrícula * **Z-index**: Campo numérico que controla el [orden de apilamiento](#stacking-order) del elemento respecto a sus hermanos. Los elementos con valores más altos aparecen encima Usa posicionamiento absoluto para superposiciones decorativas, insignias, botones de cierre e iconos colocados sobre imágenes. :::tip Para que un elemento absoluto ocupe todo el ancho de su contenedor, establece el anclaje horizontal en **Left**, luego añade un desplazamiento **Right** de 0. El elemento quedará anclado a ambos bordes. ::: ### Fijo \{#fixed\} El elemento ignora completamente su contenedor padre y se ancla a la pantalla. Permanece visible mientras el usuario hace scroll: el contenido de la página se mueve por debajo de él. El posicionamiento fijo usa los mismos controles que el posicionamiento absoluto (desplazamientos, cuadrícula de anclaje, índice Z). Todos los desplazamientos son relativos al área segura de la pantalla en lugar del elemento padre. Por ejemplo, un desplazamiento de 0 desde la parte inferior mantiene el elemento por encima del indicador de inicio. Para medir los desplazamientos desde los bordes físicos de la pantalla, activa [Ignorar área segura](#ignore-safe-area). Usa la posición fija para los elementos que deben flotar sobre el contenido que se desplaza sin reservar espacio, como botones flotantes de cerrar o restaurar, banners superiores persistentes, controles de volver arriba y barras de navegación. Para un área de acción dedicada en la parte inferior, usa un [Footer](builder-containers#footer) en su lugar. ### Ignorar el área segura \{#ignore-safe-area\} El área segura es la parte de la pantalla que queda libre del notch, la barra de estado y el indicador de inicio. Por defecto, los elementos posicionados se mantienen dentro de ella. Marca la casilla **Ignore safe area** que aparece debajo del selector de tipo de posición para medir los desplazamientos del elemento desde los bordes físicos de la pantalla. De este modo, el elemento puede extenderse detrás del notch y del indicador de inicio. Usa esto para medios a sangre completa: posiciona una imagen o vídeo como **Fixed**, establece los cuatro offsets a 0 y selecciona **Ignore safe area**. El contenido ocupa toda la pantalla de borde a borde. La casilla solo funciona con posicionamiento absoluto y fijo. Para elementos relativos está deshabilitada, y si vuelves un elemento a **Relative**, la configuración se borra. ## Tamaño \{#sizing\} Cada elemento tiene controles de **Width** y **Height**. Haz clic en el desplegable para elegir el modo de dimensionado: * **Fill**: El elemento se expande para ocupar todo el espacio disponible en su contenedor. El valor en píxeles que se muestra es el resultado calculado. * **Hug**: El elemento se encoge para ajustarse a su contenido. El valor en píxeles que se muestra es el resultado calculado. * **Fixed**: El elemento usa exactamente el valor en píxeles que especifiques, independientemente del tamaño del contenedor o del contenido. Es el único modo disponible para elementos con posicionamiento absoluto o fijo. ## Espaciado \{#spacing\} Ajusta los valores de espaciado de forma independiente para cada lado del elemento. * **Margin**: El espacio entre el elemento y sus vecinos. No se extiende más allá de los límites del contenedor padre, independientemente de su valor. * **Padding**: El espacio entre el borde del elemento y su contenido. Los elementos de texto solo tienen margin. Las pantallas solo tienen padding. Ambos están disponibles para contenedores y otros elementos con contenido hijo. ## Orden de apilamiento \{#stacking-order\} Los elementos relativos nunca se superponen entre sí: cada contenedor coloca sus hijos en secuencia. La superposición solo ocurre cuando un elemento sale del flujo normal con posicionamiento **Absolute** o **Fixed**. Cuando los elementos se superponen, los hermanos posteriores en el panel **Layers** se renderizan por encima de los anteriores, incluso cuando el hermano posterior es relativo y el anterior es absoluto. Los elementos **Absolute** y **Fixed** tienen un campo **Z-index** para un control más preciso: los valores más altos tienen prioridad. Los elementos Relative no tienen Z-index — solo el orden de capas determina su posición en la pila. Usa las [acciones de capa](paywall-layout-and-products#layer-actions) **Move up** y **Move down** para cambiar el orden de los elementos. --- # File: builder-styling --- --- title: "Estilos y apariencia" description: "Configura la apariencia visual de los elementos: relleno, bordes, efectos, tipografía, estados y estilos globales del proyecto." --- La pestaña **Design** del panel derecho controla el aspecto visual de cada elemento. Las propiedades disponibles dependen del tipo de elemento, aunque la mayoría comparte opciones de estilo comunes. :::link Para tamaño, espaciado y posicionamiento, consulta [Diseño y posicionamiento](manage-paywall-ui-elements). ::: ## Visibilidad \{#visibility\} El botón **Visibility** determina si el elemento aparece en pantalla. * Show **Show** (predeterminado): El elemento siempre es visible. * Conditional **Conditional**: El elemento solo es visible cuando se cumplen condiciones específicas. Consulta [Visibilidad condicional](onboarding-element-visibility) para más información. * Hide **Hide**: El elemento siempre está oculto. Úsalo para quitar temporalmente un elemento del flow sin eliminarlo. <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/3w3YSOmI3tQ?si=vPhoQGt44SI285Ru" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Relleno \{#fill\} La sección **Fill** controla el fondo del elemento. Hay cuatro tipos de relleno disponibles: color sólido, degradado, imagen y vídeo. Usa esta propiedad para establecer la imagen o vídeo principal de toda la pantalla. * **Color sólido** Solid color. Usa el selector de color, introduce un valor hexadecimal o asigna un [estilo de color del proyecto](#color-styles). Ajusta la **opacidad** para que el fondo sea semitransparente. * **Degradado** Gradient. Añade un relleno degradado con dos o más paradas de color. Arrastra las paradas para ajustar la transición y cambia el ángulo del degradado para controlar su dirección. * **Image** Image o **Video** Video. Establece una [imagen / vídeo](custom-media) como fondo del elemento. ## Borde \{#border\} Los bordes están desactivados por defecto. Haz clic en Plus junto a **Border** en el panel derecho para añadir uno. Para eliminar un borde, haz clic en Close junto al encabezado **Border**. Cuando hay un borde, configura: * **Color**: Usa el selector de color, introduce un valor hexadecimal o asigna un [estilo de color del proyecto](#color-styles). Ajusta la **opacidad** para que el borde sea semitransparente. * **Width**: El grosor del borde en píxeles. ## Esquinas \{#corners\} La sección **Corners** controla el radio del borde (esquinas redondeadas). * **Control deslizante de radio**: Establece el mismo radio para las cuatro esquinas * **Activar por esquina** Por esquina: Actívalo para definir un radio distinto en cada esquina individual ## Efectos \{#effects\} Haz clic en el botón más Plus junto a **Effects** para añadir uno o más efectos visuales: * **Drop shadow**: Una sombra detrás del elemento * **Inner shadow**: Una sombra dentro de los límites del elemento * **Background blur**: Desenfoca el fondo * **Layer blur**: Desenfoca el elemento y sus hijos Puedes apilar varios efectos en el mismo elemento. Activa o desactiva su visibilidad Show para desactivar un efecto temporalmente. ## Animación \{#animation\} Haz clic en el botón Plus junto a **Animation** para añadir un efecto animado. Por ahora, **Pulse** es la única animación disponible: el elemento se escala hacia arriba y hacia abajo rítmicamente para captar la atención. Configura la animación Pulse con los siguientes parámetros: | Parámetro | Descripción | |-----------|-------------| | Scale amount (%) | Cuánto crece el elemento respecto a su tamaño original | | Duration (ms) | Duración de un ciclo de animación | | Delay between loops (ms) | Pausa entre repeticiones | | Shadow color | Color del efecto de sombra pulsante | | Shadow size (px) | Tamaño de la sombra pulsante | ### Vista previa de la animación \{#preview-the-animation\} El builder muestra pantallas estáticas por defecto: las animaciones permanecen quietas hasta que las actives. Hay dos formas de hacerlo: - Haz clic en el botón **Toggle animations** Toggle animations que aparece encima de la vista previa del dispositivo. Activa o desactiva las animaciones de la pantalla; una vez activadas, se reproducen de forma continua hasta que vuelvas a hacer clic. El botón solo aparece cuando la pantalla activa contiene al menos una animación. - Abre el [menú contextual](paywall-layout-and-products#screen-actions) de la pantalla (el icono de tres puntos junto a la capa de la pantalla) y elige **Play Animation**. ## Apariencia \{#appearance\} * **Opacity**: Va del 0% (transparente) al 100% (opaco) * **Rotation**: Introduce un valor en grados para rotar el elemento ## Propiedades de tipografía (elementos de texto) \{#typography-properties-text-elements\} Los elementos de texto muestran una sección de **Typography** con los siguientes controles: ### Fuente \{#font\} :::link Ver también: [Fuentes personalizadas](using-custom-fonts-in-flow-builder) ::: Haz clic en el desplegable de fuente Font select para abrir el selector de fuentes. Tiene dos pestañas: * **Styles**: Muestra los [estilos de texto](#text-styles) guardados en tu proyecto. Selecciona un estilo para aplicar toda su configuración tipográfica de una vez. * **Fonts**: Muestra todas las familias de fuentes disponibles. Busca o desplázate para encontrar la que necesitas. Las fuentes integradas pueden **mostrarse de forma diferente según el dispositivo** — para un renderizado uniforme, sube una [fuente personalizada](using-custom-fonts-in-flow-builder). ### Tamaño y peso \{#size-and-weight\} :::warning Para las [fuentes personalizadas](using-custom-fonts-in-flow-builder), los controles **Weight**, **Bold** e **Italic** solo afectan a la vista previa integrada del editor. Para mostrar distintos pesos y estilos, sube cada variación de la fuente como un archivo independiente. ::: * **Weight**: Selecciona un peso de fuente en el desplegable * **Size**: Selecciona un tamaño en el desplegable o escribe un valor personalizado ### Color \{#color\} Haz clic en la muestra de color para abrir el selector de color. Escribe un valor hexadecimal, usa la paleta o selecciona uno de los [estilos reutilizables](#reusable-styles). Ajusta el control deslizante de opacidad para hacer el texto semitransparente. ### Alineación \{#alignment\} Dos grupos de controles de alineación: * **Horizontal**: Izquierda Align left, Centro Align center, o Derecha Align right * **Vertical**: Arriba Align top, Centro Align middle, o Abajo Align bottom ### Decoración \{#decoration\} * **None** None: Sin decoración (predeterminado) * **Underline** Underline: Añade un subrayado al texto * **Strikethrough** Strikethrough: Añade una línea tachada ### Truncación \{#truncation\} Activa la truncación para cortar el texto que supere el ajuste de **Máx. líneas**. Esto es útil cuando trabajas con varios idiomas: si una cadena traducida es más larga que el original, la truncación evita que rompa el diseño. :::note Cuando seleccionas un elemento de texto, aparece también una **barra de herramientas inline** encima de él en el canvas. Esta barra ofrece acceso rápido a la fuente, el peso, el tamaño y la alineación sin necesidad de desplazarte por el panel derecho. ::: ## Configuración por estado (elementos interactivos) \{#state-specific-settings-interactive-elements\} Los elementos interactivos admiten varios estados visuales. Al seleccionar uno de estos elementos, aparece una sección **States** en el panel derecho. Cambia entre estados para configurar distintas propiedades visuales en cada uno. Cada estado puede sobrescribir cualquier propiedad visual: relleno, borde, color de tipografía, opacidad y más. ### Estados seleccionables \{#selectable-states\} :::link Artículo principal: [Elementos seleccionables](flow-selectable-elements) ::: Los elementos que pertenecen a un grupo seleccionable (opciones de quiz, productos, pestañas, toggles de prueba) ofrecen dos estados por defecto: * **Default**: La apariencia normal del elemento * **Selected**: La apariencia cuando el usuario ha seleccionado esta opción. Sobreescribe propiedades como relleno, color de borde y color de texto para resaltar la opción activa Para aplicar estilos a un elemento seleccionable cuando no es interactivo, añade un tercer estado manualmente. Abre **States settings** Settings y añade un **Disabled state**. El estado **Disabled** está basado en condiciones. Selecciónalo y haz clic en **Set conditions** set conditions para definir cuándo el elemento queda deshabilitado en tiempo de ejecución, por ejemplo cuando un campo obligatorio está vacío. ### Estados de input \{#input-states\} Los campos de input ofrecen estados adicionales: * **Default**: Apariencia normal, sin foco * **Active**: El campo tiene el foco y está listo para recibir texto * **Invalid**: El valor introducido no supera la validación * **Disabled**: El campo no es interactivo ### Otros elementos con estados Algunos elementos exponen estilos específicos de estado fuera del patrón estándar **Default / Selected / Disabled**: - **[Pasos del indicador de progreso](builder-loaders-and-progress-bars#step-states)** — tres estados por paso: **Completed**, **Current** y **Upcoming**. - **[Puntos del carrusel](builder-containers#dots)** — dos variantes de color: **Color** para los puntos inactivos y **Active Color** para el punto de la diapositiva actual. ## Estilos reutilizables \{#reusable-styles\} El panel **Styles** Styles en la barra lateral izquierda te permite definir estilos reutilizables que se aplican en todo tu flow. Hay dos tipos de estilos disponibles: estilos de texto y estilos de color. Necesitas usar estilos de color para activar el soporte del modo oscuro. ### Estilos de texto \{#text-styles\} :::link Artículo principal: [Contenido de texto](onboarding-text) ::: Los estilos de texto almacenan un conjunto completo de ajustes tipográficos: familia de fuente, peso, tamaño, interlineado, alineación y decoración. Cada plantilla de flow incluye presets predeterminados, y puedes crear estilos personalizados. Para crear un estilo de texto: 1. Abre el panel **Styles** Styles y selecciona la pestaña **Text**. 2. Haz clic en **Plus Create style**. 3. Escribe un nombre y configura los ajustes de tipografía. 4. Haz clic en **Create**. Para aplicar un estilo de texto, selecciona un elemento de texto y elige el estilo en el desplegable de fuente de la sección **Typography**. ### Estilos de color \{#color-styles\} Los estilos de color son colores con nombre que puedes referenciar en todo tu flow. Cada estilo de color tiene un nombre (como "Primary text" o "Brand"), un valor hexadecimal y un contador de uso que muestra cuántos elementos lo referencian. Para crear un estilo de color: 1. Abre el panel **Styles** Styles y selecciona la pestaña **Colors**. 2. Haz clic en **Plus Create style**. 3. Escribe un nombre y elige un color. Cuando actualizas un estilo de color, todos los elementos que lo referencian se actualizan automáticamente. ### Modo oscuro \{#dark-mode\} :::link Artículo principal: [Modo oscuro](paywall-dark-mode) ::: Si es necesario, puedes añadir dos variantes a cada estilo de color: una para el modo claro Light mode y otra para el modo oscuro Dark mode. El SDK aplica automáticamente la variante correcta según el esquema de colores actual del dispositivo. Para previsualizar el modo oscuro en el builder, usa el **selector de tema** Dark mode en la [barra de herramientas inferior](builder-ui#view-controls-bottom-toolbar). --- # File: paywall-product-block --- --- title: "Configurar compras" description: "Asigna productos a pantallas, añade elementos de producto y conecta un botón de compra en el Flow Builder." --- Para configurar compras en una pantalla, añade un botón de compra y configura su acción **Purchase**. La acción puede apuntar a un producto específico o al producto que el usuario seleccione en un elemento Products de la pantalla. <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/LLIZCd94PlE?si=t_8BitA1FBpbd8ue" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Añadir productos \{#add-products\} Un elemento de producto es una tarjeta visual que muestra un producto en el canvas. Para añadir un elemento de producto: 1. En el canvas, haz clic en **+** en la pantalla de destino. 2. Selecciona **Products**. 3. Elige un diseño predefinido: lista vertical, lista horizontal, carrusel de características, tarjetas de características, lista de banner o hoja inferior. 4. Selecciona cada tarjeta de producto y asígnale un producto en el desplegable del panel **Design**. :::important Un elemento de producto sin un producto asignado [bloquea la previsualización y la publicación](builder-save-publish#troubleshooting). Asigna un producto o elimina el elemento. ::: Para mostrar un precio de referencia tachado en una tarjeta, añade un [elemento Old Price](onboarding-text#add-an-old-price) dentro de ella. :::note También puedes asignar una acción de **Purchase** directamente a la interacción **On tap** de una tarjeta de producto. Al pulsar la tarjeta se iniciará la compra sin necesidad de un botón de compra independiente. ::: :::important Si eliminas un grupo de productos y lo reemplazas por uno nuevo, comprueba que todas las acciones y variables apunten al nuevo grupo. Las referencias que sigan apuntando al grupo eliminado [bloquean la vista previa y la publicación](builder-save-publish#troubleshooting). ::: ## Añadir un botón de compra \{#add-a-purchase-button\} Un botón de compra desencadena la acción **Purchase** cuando el usuario lo pulsa. Para añadir un botón de compra: 1. En el lienzo, haz clic en **+** sobre la pantalla. 2. Selecciona **Button** y elige un preset de botón. 3. Con el botón seleccionado, abre la pestaña **Interactions** en el panel derecho. 4. Haz clic en **Add trigger** > **On tap** y luego en **Add action**. 5. Establece **Action** en **Purchase** y luego **Product** en una de estas opciones: - `products.selectedProduct`: Compra el producto que el usuario haya seleccionado en un elemento Products de la pantalla. - Un producto específico: Siempre compra ese producto, independientemente de cualquier selección en la pantalla. ### Mostrar el precio en el botón \{#show-the-price-on-the-button\} Para insertar el precio del producto seleccionado en la etiqueta del botón, usa una variable: 1. Con el botón seleccionado, abre la pestaña **Design** en el panel derecho. 2. En el campo **Content**, coloca el cursor donde deba aparecer el precio. 3. Haz clic en el icono de variable, selecciona `products.selectedProduct` y luego el atributo `prod_price`. La variable completa se resuelve como `products.selectedProduct.prod_price`. 4. Añade texto estático alrededor de la variable; por ejemplo, `Subscribe for {prod_price}`. La etiqueta se actualiza a medida que el usuario selecciona diferentes productos. ## Restaurar compras \{#restore-purchases\} Para que los usuarios puedan restaurar compras anteriores, añade un botón o enlace de restauración a la pantalla. Para añadir un elemento de restaurar compras: 1. En el canvas, haz clic en **+** en la pantalla. 2. Selecciona **Button**, luego elige **Links** para un enlace de texto o cualquier otro tipo de botón para un botón con estilo. 3. Con el elemento seleccionado, abre la pestaña **Interactions** en el panel derecho y haz clic en **Add trigger**. 4. Selecciona **On tap** y haz clic en **Add action**. 5. En el menú desplegable **Action**, selecciona **Restore purchases**. ## Mostrar elementos adicionales según el producto seleccionado \{#display-additional-elements-based-on-the-selected-product\} Si una pantalla tiene productos, puedes mostrar u ocultar otros elementos según el producto que seleccione el usuario. Para configurar la visibilidad condicional: 1. En el elemento **Products**, selecciona una tarjeta de producto. 2. Abre la pestaña **Interactions** en el panel derecho y haz clic en **Add trigger**. 3. Selecciona **On tap** y haz clic en **Add action**. 4. En el desplegable **Action**, selecciona **Show** o **Hide**. 5. Selecciona el elemento que quieres mostrar u ocultar cuando se seleccione ese producto. ## Revisar productos en el flow \{#review-products-in-flow\} El panel **Products** de la barra lateral izquierda relaciona los productos existentes con cada pantalla del flow. Cada pantalla tiene dos secciones: - **Default** — un producto preseleccionado cuando se carga la pantalla. - **Other** — productos adicionales disponibles en la misma pantalla. --- # File: flow-selectable-elements --- --- title: "Elementos seleccionables y grupos" description: "Haz que los elementos sean seleccionables, organízalos en grupos y usa su estado en condiciones a lo largo del flow." --- Los elementos seleccionables son elementos del flow que los usuarios pueden pulsar para seleccionar o deseleccionar. Su estado puede controlar la navegación, la visibilidad y otra lógica a lo largo del flow. Esto es lo que puedes hacer: - [Usar elementos seleccionables por defecto](#default-selectable-elements) — las opciones de quiz, productos, pestañas y toggles de prueba son seleccionables de serie - [Hacer cualquier elemento seleccionable](#make-an-element-selectable) — convierte cualquier elemento en seleccionable y asígnalo a un grupo - [Crear y gestionar grupos](#create-a-group) — organiza los elementos seleccionables en grupos de selección única, selección múltiple o toggle - [Usar el estado seleccionado en condiciones](#use-selectable-state-in-conditions) — referencia los valores de grupo en condiciones de cualquier pantalla del flow ## Elementos seleccionables por defecto \{#default-selectable-elements\} Algunos tipos de elementos son seleccionables por defecto: ya pertenecen a grupos creados automáticamente y no necesitan configuración adicional: - **Opciones de quiz**: Cada respuesta de un quiz es un elemento seleccionable dentro del grupo del quiz. Consulta [Quizzes](onboarding-quizzes). - **Productos**: Tarjetas de producto dentro de un grupo de productos. Consulta [Bloque de producto](paywall-product-block). - **Pestañas**: Elementos de pestaña dentro de un grupo de pestañas. Consulta [Pestañas](builder-tabs). - **Toggles de prueba**: Un contenedor que pertenece a un grupo y adquiere un estado seleccionado. Consulta [Toggles](builder-toggles). ## Hacer un elemento seleccionable \{#make-an-element-selectable\} <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/btpZPOm9VRY?si=1P959iwNfIJ1ZP7N" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> En algunos casos, puede que quieras hacer que elementos adicionales sean seleccionables. Por ejemplo, puedes añadir una casilla **No volver a preguntar** que funcione como un elemento dentro de un grupo de quiz. Para hacer un elemento seleccionable: 1. Selecciona el elemento en la pantalla o en el panel **Layers**. 2. A la derecha, cambia al panel **Interactions**. 3. Selecciona **Turn into selectable element**. 4. En el desplegable **Group**, selecciona un grupo existente o [crea uno nuevo](#create-a-group). 5. Establece el **Element ID** — un identificador único para este elemento dentro del grupo. 6. Si quieres que este elemento esté seleccionado por defecto, marca la casilla **Set as default in group**. ## Crear un grupo \{#create-a-group\} Los grupos organizan los elementos seleccionables de una pantalla y definen cómo funciona la selección: si los usuarios pueden elegir una opción, varias opciones o activar/desactivar. Para crear un grupo: 1. Selecciona un elemento y [hazlo seleccionable](#make-an-element-selectable). 2. En el desplegable **Group**, selecciona **Create group**. 3. Introduce un **Group name**. 4. Selecciona el [tipo de grupo](#group-types). El grupo ya estará disponible en el desplegable **Group** para otros elementos seleccionables de la misma pantalla. ## Tipos de grupo \{#group-types\} :::important La mayoría de los [presets de quiz](onboarding-quizzes) son de **opción múltiple** por defecto. Cambia el [tipo de grupo](#manage-groups) para permitir solo una respuesta. ::: - **Opción única**: Solo se puede seleccionar un elemento del grupo a la vez. Al seleccionar uno nuevo, se deselecciona el anterior. - **Opción múltiple**: Se pueden seleccionar varios elementos al mismo tiempo. - **Toggle**: Cada elemento alterna entre seleccionado y deseleccionado en cada toque, de forma independiente a los demás. ## Gestionar grupos \{#manage-groups\} Para ver y editar grupos, abre el panel **Screen settings** y busca la sección **Selectable groups**. Aquí aparecen todos los grupos de la pantalla actual. Haz clic en el ID de un grupo para: - Cambiar el ID del grupo - Cambiar el [tipo de grupo](#group-types) - Ver cómo se referencian los elementos del grupo en las condiciones ## Usar el estado de selección en condiciones \{#use-selectable-state-in-conditions\} Puedes referenciar el estado de selección de un grupo en condiciones de cualquier pantalla del flow, no solo en la pantalla donde está definido el grupo. Por ejemplo: `IF quiz.photo is selected, THEN navigate to the Photo screen`. :::important Todos los elementos de un grupo deben estar en la misma pantalla. No puedes añadir elementos de diferentes pantallas a un mismo grupo. Sin embargo, puedes referenciar los valores del grupo en condiciones de cualquier pantalla del flow. ::: Usa el estado de selección con: - **[Acciones condicionales](onboarding-actions#conditional-actions)**: Redirige a los usuarios a distintas pantallas o activa diferentes acciones según los elementos seleccionados. - **[Navegación dinámica](onboarding-navigation-branching)**: Ramifica el flow según las respuestas a un quiz, los estados de los toggles u otras selecciones. - **[Visibilidad condicional](onboarding-element-visibility)**: Muestra u oculta elementos según lo que los usuarios hayan seleccionado en pantallas anteriores. --- # File: builder-element-states --- --- title: "Estados de los elementos" description: "Da estilo a los elementos según su estado y usa una condición para desactivar un elemento en tiempo de ejecución." --- Los elementos interactivos del flow cambian su apariencia según las acciones del usuario: una opción de quiz seleccionada pasa a estado **Selected**, un campo enfocado pasa a estado **Active**. Algunos estados dependen de condiciones — por ejemplo, puedes **desactivar** un botón. Dale estilo a cada estado por separado para ofrecer retroalimentación visual sin necesidad de código en la app. <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/gdsNfHpKAqQ?si=VY5mqZgH1j0RB6fE" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Estados disponibles por tipo de elemento \{#available-states-by-element-kind\} | Tipo de elemento | Estados integrados | Estados añadibles | |---|---|---| | [Elementos seleccionables](#selectable-element-states) | **Default**, **Selected** | **Disabled** | | [Inputs](#input-states) | **Default**, **Active**, **Invalid** | **Disabled** | | [Cualquier elemento con interacción de toque](#condition-driven-disabled-state) — botones, imágenes, iconos, contenedores, etc. | **Default** | **Disabled** | | [Pasos del indicador de progreso](#step-states-for-progress-indicators) | **Completed**, **Current**, **Upcoming** | — | Los estados **Addable** no aparecen por defecto — abre **States settings** Settings para añadirlos. Son [**controlados por condición**](#condition-driven-disabled-state): tú defines cuándo se activan. ## Cómo aplicar estilos a un estado \{#how-to-style-a-state\} 1. Selecciona un elemento. La sección **States** del panel derecho muestra los estados que admite ese elemento. 2. En la sección **States**, activa el estado que quieras. Añade el [estado Disabled basado en condiciones](#condition-driven-disabled-state) si lo necesitas. 3. Cambia cualquier propiedad — relleno, borde, tipografía, etc. El cambio se aplica únicamente a ese estado. Nested elements become stateful alongside the parent. Any change to a child is scoped to the parent's active state. 4. El Builder aplica el estilo correspondiente en tiempo de ejecución. ## Estados de los elementos seleccionables \{#selectable-element-states\} Los elementos seleccionables — opciones de quiz, productos, pestañas, toggles de prueba y cualquier [elemento seleccionable personalizado](flow-selectable-elements#make-an-element-selectable) — tienen dos estados por defecto: - **Default**: La apariencia en reposo del elemento. - **Selected**: Se aplica cuando el usuario toca el elemento. El Builder vuelve a Default cuando el usuario lo deselecciona. En un grupo de selección única, elegir un elemento deselecciona los demás. Los grupos de selección múltiple permiten que varios estén seleccionados al mismo tiempo. Los toggles son independientes: seleccionar uno no afecta a sus hermanos. Consulta [tipos de grupo](flow-selectable-elements#group-types). :::tip ¿Necesitas aplicar el mismo estilo de estado a varios elementos (por ejemplo, opciones de un quiz)? Estiliza primero un elemento y luego duplícalo. El estilo de estado no se transfiere entre elementos hermanos — la duplicación es el método alternativo por ahora. ::: ## Estados del input \{#input-states\} - **Default**: El aspecto predeterminado del input en reposo. - **Active**: Se aplica mientras el input está enfocado. - **Invalid**: Se aplica cuando el contenido del input no supera la validación. Por ejemplo, cuando un campo de email no contiene `@`. Consulta [Validación de inputs](builder-inputs-and-forms#input-validation). - **Disabled**: El input no es interactivo. Añade este estado manualmente; consulta [Estado Disabled por condición](#condition-driven-disabled-state). Estiliza cada estado igual que un elemento seleccionable: activa el estado objetivo y cambia las propiedades. ## Estado Desactivado basado en condiciones \{#condition-driven-disabled-state\} El estado Desactivado impide que el usuario interactúe con un elemento. A diferencia de Default, Selected, Active o Invalid, el estado Disabled no se activa por sí solo — requiere una condición definida por el usuario. Disabled está disponible en: - **Entradas**: Cualquier [campo de entrada](builder-inputs-and-forms) — texto, correo electrónico, contraseña, número, teléfono, fecha y/o hora. - **Elementos seleccionables**: Opciones de quiz, productos, pestañas, interruptores de prueba y cualquier [elemento seleccionable personalizado](flow-selectable-elements#make-an-element-selectable). - **Cualquier elemento con interacción de toque**: Por ejemplo, un botón, imagen o icono que activa una acción de navegación. ### Añadir el estado Disabled \{#add-the-disabled-state\} Para añadir y configurar un estado Disabled: 1. Selecciona el elemento de destino. 2. En la sección **States**, haz clic en **Settings** Settings. 3. Elige **Add Disabled state**. El estado Disabled aparece en la sección **States**. 4. Junto al nuevo estado Disabled, haz clic en **Edit conditional state** Edit conditional state. 5. Añade una condición. Si quieres desactivar el botón **submit** a menos que el input supere la validación, compara la variable `isValid` del input con `false`. 6. Dale estilo al estado Disabled para comunicar visualmente la restricción (por ejemplo, reduce la opacidad). { } El SDK de Adapty evalúa la condición en tiempo de ejecución y aplica el estado Disabled cuando corresponde, sin necesidad de código adicional en la app. ## Estados de paso para indicadores de progreso \{#step-states-for-progress-indicators\} :::link Artículo principal: [Indicadores de progreso](builder-loaders-and-progress-bars#step-states) ::: Los indicadores de progreso muestran a los usuarios hasta dónde han llegado en un flow de onboarding. Cada paso tiene tres estados: - **Completado**: Los pasos que el usuario ya ha superado. - **Actual**: El paso en el que se encuentra el usuario en ese momento. - **Próximo**: Los pasos a los que el usuario aún no ha llegado. --- # File: builder-containers --- --- title: "Elementos de maquetación: contenedores, carruseles y hojas inferiores" description: "Agrupa elementos en contenedores, carruseles y hojas inferiores en el Flow Builder." --- Los elementos de maquetación agrupan otros elementos y controlan cómo se distribuyen en la pantalla. :El Flow Builder incluye cinco tipos de elementos de maquetación: - **Containers**: organizan los elementos hijos a lo largo de un eje — vertical u horizontal - **Carousel**: un contenedor deslizable que muestra una diapositiva a la vez - **Bottom Sheet**: un panel que se desliza desde la parte inferior de la pantalla y se renderiza sobre el contenido subyacente - **Footer**: un panel fijado en la parte inferior de la pantalla, fuera del área de desplazamiento - **Dividers**: líneas finas que separan filas o columnas :::link **Tabs** también forma parte de este grupo, pero tiene su propio artículo. Consulta [Tabs](builder-tabs) para más detalles. ::: ## Contenedores \{#containers\} :::link Artículo principal: [Posicionamiento de elementos](manage-paywall-ui-elements) ::: Los contenedores agrupan elementos de forma vertical u horizontal. El **Vertical Container** organiza los elementos en filas; el **Horizontal Container** los organiza en columnas. :::tip Anida contenedores dentro de otros para crear diseños más complejos. ::: ### Cambiar la dirección del contenedor \{#change-container-direction\} La dirección de un contenedor no es fija. Cambia entre **Vertical**, **Horizontal** y **Free** en la sección **Layout** del panel derecho en cualquier momento, sin necesidad de eliminar y volver a crear el contenedor. Configura el espaciado, la alineación y la distribución en esa misma sección **Layout**. Los elementos secundarios se renderizan en el orden en que aparecen en el panel **Layers** — arrastra para reordenarlos. ### Convertir en contenedor y deshacer el contenedor \{#wrap-and-unwrap\} Para convertir un elemento existente en un contenedor, selecciónalo y usa la acción de capa **Wrap** desde [acciones de capa](paywall-layout-and-products#layer-actions). Arrastra elementos adicionales al nuevo contenedor desde el panel **Layers**. Para eliminar un contenedor y subir sus hijos un nivel, usa **Unwrap**. ## Carrusel \{#carousel\} Un **Carrusel** es un contenedor deslizable que muestra una diapositiva a la vez. El usuario desliza horizontalmente para ver la siguiente diapositiva, o el carrusel avanza automáticamente con un temporizador. Un Carrusel contiene un conjunto de capas **Slide**. Cuando una diapositiva está activa, los elementos de esa capa aparecen en pantalla. A diferencia de las pestañas, la diapositiva activa del carrusel no se expone como un [grupo seleccionable](flow-selectable-elements) — las diapositivas no pueden usarse en condiciones ni en texto dinámico. Usa un Carrusel para rotación visual, no para ramificaciones basadas en el usuario. ### Cambiar la diapositiva activa \{#change-active-slide\} Al seleccionar el carrusel, el builder muestra una barra de control emergente con un desplegable **Slide** y un botón **+ Add Slide**. - Haz clic en **+ Add Slide** para añadir una nueva diapositiva vacía. - Usa el desplegable **Slide** para cambiar qué diapositiva está activa en el lienzo, o haz clic en la capa Slide correspondiente en el panel **Layers**. Para reordenar las diapositivas, arrástralas dentro del Carrusel en el panel Layers. {/* TODO: on-device GIF */} ### Propiedades \{#properties\} #### Desplazamiento automático \{#auto-scroll\} El desplazamiento automático hace que las diapositivas pasen solas, sin que el usuario tenga que deslizar para ver todo el contenido. Dos controles de tiempo determinan su comportamiento: - **Delay** — cuánto tiempo permanece visible cada diapositiva (ms). - **Duration** — cuánto dura la transición entre diapositivas (ms). #### Tamaño del carrusel \{#carousel-sizing\} Los controles dedicados determinan el tamaño del carrusel y el espacio entre diapositivas adyacentes. Establece **Height** en **Fixed** para que el layout no cambie mientras el usuario desliza entre diapositivas con contenidos de diferente longitud. #### Tamaño de la diapositiva \{#slide-sizing\} **Width** y **Height** por diapositiva. Por defecto es Fill, de modo que cada diapositiva se ajusta a las dimensiones del carrusel. Establece un ancho fijo para crear un efecto de vista previa donde las diapositivas adyacentes se muestran parcialmente. #### Puntos \{#dots\} El indicador de página en la parte inferior del carrusel. Muestra al usuario cuántas diapositivas hay y cuál está activa. Desactiva el interruptor **Show dots** para ocultar el indicador de diapositivas. Cuando los puntos son visibles, las siguientes propiedades controlan su apariencia: - **Color** — color de relleno de un punto inactivo. - **Active Color** — color de relleno del punto correspondiente a la diapositiva visible actualmente. - **Size** — diámetro de cada punto, en píxeles. - **Gap** — espacio entre puntos adyacentes. - **Padding** — espacio entre la fila de puntos y el contenido del carrusel situado encima. ## Hoja inferior \{#bottom-sheet\} :::link Guía: [Mostrar todos los planes en una hoja inferior](show-plans-bottom-sheet) ::: Una **hoja inferior** es un panel de diseño que se desliza hacia arriba desde la parte inferior de la pantalla, por encima del contenido subyacente. La hoja siempre desenfoca todo lo que hay detrás; ese desenfoque no se puede desactivar. Actívala al tocar — por ejemplo, detrás de un enlace **Mostrar todos los planes** — en lugar de al cargar la pantalla. ### Estructura \{#structure\} Un Bottom Sheet incluye dos capas principales: - **Heading** — un contenedor en la parte superior del sheet, con una capa de texto **Title** y un **Close button** Close ya añadidos. Edítalos o elimínalos según necesites. - **Content** — el contenedor principal. Añade productos, botones, enlaces o cualquier otro elemento dentro de él. {/* TODO: on-device GIF */} ### Visibilidad inicial \{#initial-visibility\} Por defecto, una bottom sheet aparece en cuanto se renderiza la pantalla. Para abrirla bajo demanda: 1. **Primero termina el contenido de la sheet** — las capas ocultas no se pueden editar, así que la sheet debe permanecer visible hasta que hayas terminado de rellenarla. 2. En el panel **Layers**, selecciona la bottom sheet. 3. Establece **Visibility** en **Hide** Hide. La sheet permanece en el árbol de capas pero deja de renderizarse en la pantalla. ### Activar el panel inferior \{#triggering-the-bottom-sheet\} Para abrir un panel inferior oculto, añade una acción **Show** a otro elemento: 1. Selecciona el elemento que actuará como disparador (por ejemplo, un botón o un enlace de texto). 2. Abre la pestaña **Interactions** en el panel derecho. 3. Haz clic en **Add trigger** > **On tap** y luego en **Add action**. 4. Establece **Action** en **Show** y elige el panel inferior en el desplegable. ## Pie de página \{#footer\} Un **Footer** es un contenedor fijo que ocupa la parte inferior de la pantalla. Puede tener cualquier altura y contener desde un único botón hasta varias filas de texto. Úsalo para el contenido que debe permanecer en su lugar mientras el resto de la pantalla se desplaza: botones CTA, textos legales, enlaces. A diferencia de los elementos habituales, el footer se extiende hasta el área segura inferior del dispositivo: su fondo llega hasta el borde mismo de la pantalla. Solo se permite un pie de página por pantalla. No puedes duplicar un pie de página existente ni añadir uno nuevo. ### Footer vs. un elemento fijo normal \{#footer-vs-a-regular-fixed-element\} Ambos permanecen en pantalla mientras el contenido se desplaza. Elige la opción que mejor se adapte a lo que necesitas: - **Usa un Footer** para la barra inferior principal de la pantalla (botón de CTA, texto legal, enlaces). Reserva su propia altura, así que el contenido siempre puede desplazarse sin quedar oculto detrás de él, y cubre automáticamente la zona segura inferior. - **Usa un [elemento fijo](manage-paywall-ui-elements)** para algo que deba flotar sobre el contenido desplazable en lugar de reservar espacio para sí mismo, o que se ancle a un borde distinto del inferior — un botón flotante de cerrar/restaurar, un banner persistente en la parte superior, un control para volver al inicio. Tú mismo gestionas el espaciado de la zona segura. ## Divisores \{#dividers\} El **Divisor Horizontal** y el **Divisor Vertical** son líneas delgadas que separan contenido. Usa el Divisor Horizontal para separar filas y el Divisor Vertical para separar columnas dentro de un contenedor horizontal. Ajusta el grosor, el color y la longitud desde el panel derecho. --- # File: using-custom-fonts-in-flow-builder --- --- title: "Fuentes personalizadas en el Flow Builder" description: "Sube y utiliza fuentes personalizadas en el Flow Builder." --- Al crear flows, puede que quieras usar una fuente personalizada para que coincida con el resto de tu app. Aquí te explicamos cómo añadir fuentes personalizadas y usarlas en tus flows. :::tip [Configura las fuentes](onboarding-text) en el panel **Styles** antes de empezar a diseñar el flow. Así, cualquier cambio que hagas se aplicará de forma global. ::: ## Fuentes integradas \{#built-in-fonts\} Cuando creas un flow en el Builder, Adapty usa la fuente del sistema por defecto. Normalmente esto significa SF Pro en iOS y Roboto en Android, aunque puede variar según el dispositivo. También puedes elegir entre fuentes de uso común como Arial, Times New Roman, Courier New, Georgia y Helvetica. Cada una de estas fuentes incluye varias opciones de estilo. Estas fuentes no se incluyen en el SDK de Adapty y solo se usan con fines de previsualización. No podemos garantizar que funcionen perfectamente en todos los dispositivos. Sin embargo, en nuestras pruebas, la mayoría de los dispositivos las reconocen sin necesidad de ninguna configuración adicional. También puedes [consultar qué fuentes están disponibles por defecto en iOS](https://developer.apple.com/fonts/system-fonts/). ## Añadir una fuente personalizada \{#add-a-custom-font\} :::warning El archivo que subes es **solo para la vista previa del editor** — Adapty no lo envía a los dispositivos de los usuarios. Para renderizar la fuente en el dispositivo, [incluye el archivo en el bundle de tu app](#add-the-font-files-to-your-apps-bundle). Sin él, el SDK usará SF Pro (iOS) o Roboto (Android) en tiempo de ejecución. ::: Si necesitas usar fuentes distintas a la predeterminada del sistema, puedes añadir una fuente personalizada. Para añadir una fuente personalizada: 0. Si la fuente es variable, divídela en archivos de un solo estilo con nombres únicos. Los controles de peso, negrita e itálica no se aplican a las fuentes personalizadas. Adapty solo registra un estilo por archivo de fuente personalizada. Para [aplicar estilo al texto](onboarding-text), cambia la fuente a la variación correcta. 1. Selecciona **Upload new font** en cualquiera de los desplegables de fuente. 2. En la ventana **Add custom font**, rellena los siguientes campos: :::warning El **nombre de la fuente en Builder**, el **nombre de fuente para iOS** y el **nombre de fuente para Android** deben ser únicos entre todos los archivos de fuentes personalizadas de la app. ::: - **Font name in Builder**: Introduce un nombre de visualización para la fuente. Este nombre aparecerá en los menús desplegables de fuentes del Builder. - **iOS font name**: Introduce el nombre PostScript de la fuente. Puedes encontrarlo en Font Book → PostScript name, o a través de la [API `UIFont`](https://developer.apple.com/documentation/uikit/uifont). - **Android font name**: Introduce el nombre del archivo de `res/font/`. Usa solo letras minúsculas, números y guiones bajos. - **Font file**: Arrastra y suelta el archivo de fuente o haz clic en **Select files**. Formatos compatibles: `.ttf`, `.otf`, `.woff`, `.woff2`. 3. Haz clic en **Save font**. Al subir el archivo de fuente a Adapty, confirmas que tienes derecho a usarla en tu aplicación. ### Eliminar una fuente personalizada \{#delete-a-custom-font\} Eliminar una fuente personalizada desde el dashboard reescribe automáticamente todas las referencias a esa fuente por la fuente del sistema en todos los flows, tanto en borrador como publicados. No hay ningún aviso previo y la acción no se puede deshacer. Antes de eliminarla, asegúrate de que ningún flow activo la esté usando. ## Fuentes personalizadas en plantillas de flow \{#custom-fonts-in-flow-templates\} La [biblioteca de plantillas de flow](paywall-builder-templates) incluye plantillas con fuentes personalizadas. Pasa el cursor sobre el chip **Custom font** de una tarjeta de plantilla para ver qué fuentes utiliza. Adapty no incluye estas fuentes. Tienes que obtenerlas por tu cuenta, asegurándote de que los nombres coincidan con los que aparecen en el chip. Algunas fuentes pueden requerir una licencia comercial. Una vez que tengas los archivos de fuentes, sigue los pasos de integración que se indican a continuación. ## Agrega los archivos de fuentes al bundle de tu app \{#add-the-font-files-to-your-apps-bundle\} Si ya usas una fuente personalizada en otra parte de tu app, solo tienes que añadir las fuentes del paywall de la misma manera. Si no es así, asegúrate de incluir el archivo de fuentes en el proyecto y el bundle de tu app. Aquí te explicamos cómo hacerlo: - En iOS: [En la documentación oficial de Apple](https://developer.apple.com/documentation/uikit/adding-a-custom-font-to-your-app) - En Android: [En la documentación oficial de Android](https://developer.android.com/develop/ui/views/text-and-emoji/fonts-in-xml) --- # File: paywall-head-picture --- --- title: "Fondos" description: "Rellena una pantalla con un color sólido, degradado, imagen o vídeo de fondo en el Flow Builder." --- Establece un fondo en cualquier pantalla desde el panel **Fill** en [**Screen settings**](paywall-layout-and-products#screen-settings). Elige entre cuatro tipos de fondo: color sólido, degradado, imagen o vídeo. ## Imagen \{#image\} Sube un archivo `.JPG`, `.PNG`, `.GIF` o `.WEBP` de hasta 20 MB. La imagen se escala para cubrir todo el fondo. :::note Las imágenes y vídeos de fondo ocupan todo el viewport, incluidas las áreas detrás de la muesca y las barras del sistema, incluso cuando **Safe area** está activada. Mantén el contenido importante alejado de los bordes para evitar recortes. ::: Para cambiar la imagen de fondo en tiempo de ejecución desde el código de tu app, activa un [ID de media personalizado](custom-media#custom-media-id). ## Vídeo \{#video\} Sube un archivo `.MP4` o `.WEBM` de hasta 50 MB. La vista previa muestra un fotograma estático, pero el vídeo se reproduce en el dispositivo en tiempo de ejecución. Activa **Loop** para reproducir el vídeo en bucle de forma continua. Para cambiar el vídeo de fondo en tiempo de ejecución, activa un [ID de media personalizado](custom-media#custom-media-id). ## Color sólido \{#solid-color\} Introduce un valor hexadecimal y ajusta la opacidad del 0 al 100%. Elige un [estilo de color](builder-styling) guardado desde la muestra para aplicar el color de tu marca; el fondo sigue automáticamente tus temas claro y oscuro. ## Degradado \{#gradient\} Crea un degradado lineal con múltiples paradas: - **Direction** — rota el degradado de 0 a 360°. - **Stops** — arrastra a lo largo de la barra para reposicionar. Haz clic en una parada para editar su valor hexadecimal y opacidad. --- # File: custom-media --- --- title: "Imágenes, vídeos e iconos" description: "Añade elementos de imagen, vídeo e icono a una pantalla en el Flow Builder, y cambia los medios en tiempo de ejecución con IDs de medios personalizados." --- El Flow Builder incluye tres tipos de elementos multimedia en la categoría **Media**: Image, Video e Icon. :::tip Para que una imagen o vídeo cubra toda la pantalla, incluidas las zonas detrás de la muesca y el indicador de inicio, colócalo como fijo con todos los desplazamientos en 0 y selecciona **Ignore safe area**. Consulta [Diseño y posicionamiento](manage-paywall-ui-elements#ignore-safe-area). ::: ## Imagen \{#image\} Sube un archivo `.JPG`, `.PNG` o `.GIF` de hasta 20 MB. - **Aspect** — controla cómo encaja la imagen en su contenedor: - **Fit** — escala la imagen para que quepa dentro del contenedor sin recortarla. - **Fill** — estira la imagen para rellenar el contenedor. - **Cover** — escala la imagen para cubrir el contenedor, recortándola si es necesario. Valor predeterminado. - **Use custom media ID** — consulta [Custom media ID](#custom-media-id) más abajo. ## Video \{#video\} Sube un archivo `.MP4` o `.WEBM` de hasta 50 MB y no más de 30 segundos. La resolución mínima del vídeo es de 640x640 píxeles. - **Aspect** — Fit, Fill o Cover. El valor predeterminado es Fill. - **Loop** — reproduce el vídeo en bucle de forma continua. Activado por defecto. - **Use custom media ID** — consulta [Custom media ID](#custom-media-id) más abajo. Los vídeos no se reproducen en la vista previa del editor: el lienzo muestra un fotograma estático. En el dispositivo en tiempo de ejecución, el vídeo se reproduce sin sonido de forma predeterminada. Con Loop activado, se repite indefinidamente. ### Desencadenar una acción cuando el vídeo termina \{#trigger-an-action-when-the-video-ends\} :::link Artículo principal: [Acciones](onboarding-actions) ::: El elemento Video admite un trigger **On playback finished** que se activa cuando el vídeo llega al final. Configúralo en el panel **Interactions** para navegar a otra pantalla, mostrar un CTA o ejecutar cualquier otra acción. ## Icono \{#icon\} Elige entre la biblioteca integrada [Tabler Icons](https://tabler.io/icons), con miles de iconos en dos estilos visuales: - **Stroke** — solo contorno. - **Filled** — relleno sólido. Busca en el selector por palabra clave para encontrar un icono. Configura el color del icono en el selector **Color** — elige un [estilo de color](builder-styling) guardado o establece un color personalizado. ## ID de medio personalizado \{#custom-media-id\} :::important También puedes establecer un ID de medio personalizado para una imagen y un vídeo de [fondo](paywall-head-picture). ::: Etiqueta un elemento de imagen o vídeo con un ID de medio personalizado para reemplazarlo en tiempo de ejecución desde el código de tu app. Úsalo para [imágenes personalizadas](get-pb-paywalls#customize-assets) — por ejemplo, para mostrar el avatar elegido por el usuario. El medio que subas en el Flow Builder sirve como respaldo. Si tu código no proporciona ningún medio para ese ID en tiempo de ejecución, se muestra el respaldo. Para habilitar un ID de medio personalizado en un elemento de imagen o vídeo: 1. Selecciona la casilla **Use custom media ID** bajo el área de carga. 2. Introduce un ID de medio. 3. Sube una imagen o vídeo de respaldo. En el código de tu app, obtén los medios por su ID — consulta [Personalizar assets](get-pb-paywalls#customize-assets) para ver la API del SDK. --- # File: paywall-buttons --- --- title: "Botones en el Flow Builder" description: "Añade y configura botones de acción en el Flow Builder." --- :::info Esta sección describe el nuevo Flow Builder, que funciona con las versiones 4.0 o superiores de los SDKs de Adapty. ::: Los botones son los elementos interactivos del Flow Builder que responden a los toques del usuario. Úsalos para: - CTAs de compra que se conectan a productos y procesan transacciones automáticamente - Navegación — mueve a los usuarios entre pantallas (Siguiente, Atrás, Cerrar, Omitir) - Enlaces de utilidad — Restaurar compras, Términos de servicio y Política de privacidad :::tip Coloca los CTAs de compra, los enlaces de restauración y los enlaces legales dentro de un [Footer](builder-containers#footer) — está anclado en la parte inferior de la pantalla y queda por encima de los elementos que se desplazan debajo. ::: ## Añadir botones \{#add-buttons\} Para añadir cualquier botón: 1. Haz clic en **+** y selecciona **Button**. 2. Selecciona un tipo de botón. <img src="/assets/shared/img/button-type.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Los botones de compra, los enlaces y los botones de cierre vienen con acciones preconfiguradas. Para los enlaces, [configura las URLs a las que se dirigirá a los usuarios](#links). Para los demás tipos de botón, ve al panel **Interactions**. Allí, en la sección **Button triggers**, configura las [acciones](onboarding-actions) que debe realizar el botón. <img src="/assets/shared/img/button-action.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Configura el [diseño del botón](builder-styling) en el panel **Design**. ## Tipos de botones \{#button-types\} ### Botones de compra \{#purchase-buttons\} :::link Para que los botones de compra funcionen, vincula productos a las pantallas y añade el elemento **Products**. Consulta la [guía](paywall-product-block). ::: Un botón de compra inicia la compra in-app del producto que el usuario haya seleccionado en la pantalla. El SDK procesa la transacción automáticamente, por lo que no necesitas gestionar las compras en el código de la app. Para añadir un botón de compra: 1. Haz clic en **+** y selecciona **Button**, luego elige un preset de botón. 2. Con el botón seleccionado, abre la pestaña **Interactions** en el panel derecho. 3. Haz clic en **Add trigger** > **On tap**, luego haz clic en **Add action**. 4. Establece **Action** en **Purchase** y **Product** en `products.selectedProduct`. La variable `products.selectedProduct` siempre se resuelve al producto actualmente seleccionado en la pantalla. :::tip Puedes atraer más atención hacia los botones de compra animándolos. El Paywall Builder admite actualmente el tipo de animación **Pulse**. Configura el estilo de animación en el panel **Design**. ::: <img src="/assets/shared/img/purchase-button.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Links \{#links\} :::important Los botones **Terms of Use** y **Privacy Policy** tienen una acción **Open URL** integrada. Establece la URL de destino allí. Las URLs vacías en Open URL y los [enlaces en línea](onboarding-text#inline-link) bloquean la vista previa y la publicación. ::: Para cumplir con algunos requisitos del store, puedes añadir enlaces a: - Términos de servicio - Política de privacidad - Restauración de compras Para añadir enlaces: 1. Haz clic en **+** y selecciona **Button > Links**. Esto añadirá una fila de botones en línea con acciones predefinidas: restaurar compras o abrir una URL. Si no necesitas todos los botones incluidos, elimina los que no necesites en el panel de capas. <img src="/assets/shared/img/add-links.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Ahora, configura las acciones de los botones: - El botón **Restore purchases** ya gestiona la restauración de compras. - Para cada enlace restante: 1. Haz clic en el botón para seleccionarlo y cambia a la pestaña **Interactions** en el panel derecho. 2. Pega la URL en el campo. 3. Por defecto, la URL se abre en un navegador integrado en la app para una experiencia de usuario fluida. Si quieres que los usuarios naveguen a un navegador externo, marca la casilla **Open in external browser**. <img src="/assets/shared/img/pb-links.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Cerrar el flow \{#close-flow\} El botón **Close** cierra el flow automáticamente. Para añadir un botón de cierre, haz clic en **+** y selecciona **Button > Close flow**. <img src="/assets/shared/img/close-flow.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::tip Usa la posición **Absolute** para colocar el botón de cierre en la esquina de la pantalla. ::: También puedes configurar cualquier otro botón para cerrar el flow usando [acciones](onboarding-actions). ### Botones personalizados \{#custom-buttons\} Cualquier botón que añadas puede configurarse para realizar una acción al pulsarlo: - Navegar a la siguiente pantalla - Mostrar una alerta - Establecer una [variable](onboarding-variables) - [Mostrar u ocultar elementos de la pantalla](onboarding-element-visibility) - Abrir URLs - Restaurar compras - Ejecutar acciones condicionales --- # File: builder-tabs --- --- title: "Tabs" description: "Añade navegación por pestañas que intercambia paneles de contenido en un flow." --- **Tabs** divide una sección de pantalla en paneles de contenido intercambiables: el usuario toca un encabezado de pestaña y el panel inferior se actualiza para coincidir. {/* TODO: on-device GIF */} ## Añadir, eliminar y seleccionar pestañas \{#add-remove-and-select-tabs\} Cada pestaña tiene dos partes: - **Encabezado de pestaña** — la etiqueta en la que se hace clic (Tab 1, Tab 2, etc.). - **Contenido de pestaña** — un contenedor por pestaña. Lo que pongas en un contenedor de contenido aparece cuando se selecciona esa pestaña. Haz clic en **Add tab** para añadir una nueva pestaña. Cada nueva pestaña genera su propio contenedor de contenido. Para que una pestaña concreta esté activa al cargar la pantalla, activa **Selected by default**. ## Dar estilo a las pestañas \{#style-the-tabs\} ### Plantillas \{#templates\} El Flow Builder ofrece tres plantillas de pestañas listas para usar: - **Segment control** — un selector con forma de píldora y esquinas redondeadas alrededor de la pestaña seleccionada. - **Button Tabs** — pestañas con estilo de botón independientes. - **Underline** — etiquetas de texto con un subrayado que marca la pestaña seleccionada. ### Estados de las pestañas \{#tab-states\} Cada pestaña tiene un selector de estado (**Default / Selected**) para aplicar estilos distintos al estado activo e inactivo: tipografía, colores, relleno y borde por estado. ## Grupo seleccionable \{#selectable-group\} Las pestañas son un **grupo seleccionable de elección única** — exactamente una pestaña está activa a la vez. Gestiona el grupo desde el panel **Screen settings**, en la sección [Selectable groups](paywall-layout-and-products#selectable-groups). El grupo expone dos variables: - `tabs.selectedOptionId` — el ID de la pestaña seleccionada. Úsalo en condiciones. - `tabs.selectedOptionTitle` — la etiqueta de la pestaña seleccionada. Úsalo en texto dinámico. Reemplaza `tabs` con tu **Group ID** personalizado si renombraste el grupo. Consulta [Selectable elements and groups](flow-selectable-elements) para ver el panorama completo. --- # File: builder-toggles --- --- title: "Toggles" description: "Añade interruptores de activación a tus flows de pago." --- :::warning Apple puede rechazar apps que usen un toggle de prueba preseleccionado. Un toggle configurado en "activado" por defecto puede marcarse como un patrón oscuro manipulador según las directrices de revisión de la App Store, ya que implica el consentimiento del usuario a una prueba gratuita sin una elección explícita. Para evitar el rechazo, configura el toggle en **desactivado** por defecto y deja que los usuarios activen la prueba por su cuenta. ::: Un trial toggle es un interruptor binario que permite a los usuarios elegir entre productos estándar y productos con periodo de prueba en un paywall. Cuando el usuario cambia su estado, puede desencadenar una acción de forma instantánea, como intercambiar grupos de productos, actualizar variables o mostrar y ocultar elementos. Para añadir un trial toggle, haz clic en **+** en la pantalla de destino y selecciona **Trial toggle**. Cada trial toggle es un elemento seleccionable de tipo **Toggle**. Cada elemento seleccionable tiene una variable asignada para reflejar su estado; por ejemplo, un toggle llamado `trial` obtiene una variable `trial.is_selected` con un valor `True` o `False`. Para hacer que otros elementos dependan del estado del toggle, establece una [acción](onboarding-actions) condicional o una [visibilidad condicional](onboarding-element-visibility) basada en esta variable. --- # File: builder-reviews-and-testimonials --- --- title: "Reseñas y testimonios" description: "Añade reseñas, valoraciones y prueba social a un paywall." --- La categoría de elementos **User Engagement** ofrece cuatro plantillas para mostrar reseñas, valoraciones y prueba social en un paywall. Cada plantilla es una composición totalmente editable: reemplaza el texto de marcador de posición y aplica tus [estilos de color](builder-styling) y [tipografía](onboarding-text) para que encaje con el resto del flow. ## Reseña \{#review\} Una tarjeta con una valoración, una cita y el nombre del autor. Úsala para destacar una cita memorable de un usuario. ## Valoración \{#rating\} Una fila con recuento y estrellas, como "17000+ valoraciones". Úsala para destacar el volumen de valoraciones. ## Valoración de la app \{#app-rating\} Una puntuación destacada con el tamaño de la muestra, como "4.9 / Basado en más de 1000 reseñas". Úsala para resaltar una puntuación global sólida. ## Prueba social \{#social-proof\} Un grupo de avatares con un contador de miembros, como "Únete a más de 50.000 usuarios". Úsalo para destacar el tamaño de la comunidad. --- # File: flow-timer --- --- title: "Temporizador de cuenta regresiva" description: "Añade un temporizador de cuenta regresiva a un paywall." --- El **Temporizador de cuenta regresiva** cuenta hacia atrás desde una duración fija hasta cero — una vez que llega a cero, la pantalla se congela. ## Plantillas \{#templates\} La categoría ofrece cuatro variantes visuales: - **Blocks** — Días, horas, minutos y segundos en celdas separadas con etiquetas. - **Inline Units** — Texto en una sola línea con sufijos de unidad. - **Inline** — Solo dígitos. - **Badge** — Visualización de dígitos en forma de píldora. ## Configuración \{#settings\} ### Establecer la duración \{#set-the-duration\} En la sección **Countdown** del panel derecho, introduce la duración inicial en días, horas, minutos y segundos. ### Configurar el comportamiento \{#configure-the-behavior\} El desplegable **Behavior** controla cuándo empieza el temporizador: - **Every appear** — Se reinicia cada vez que el usuario abre la pantalla. Es el valor predeterminado. - **First appear** — Arranca en la primera vez que el usuario ve la pantalla durante la sesión actual. Sigue contando si vuelve a ella en la misma sesión; se reinicia al abrir la app de nuevo. - **First appear (persisted)** — Arranca la primera vez que el usuario abre la pantalla y sigue contando aunque cierre y vuelva a abrir la app. ### Desencadenar una acción cuando el temporizador termina \{#trigger-an-action-when-the-timer-ends\} :::link Artículo principal: [Acciones](onboarding-actions) ::: Añade un disparador **On timer end** para ejecutar una acción cuando la cuenta atrás llega a cero — por ejemplo, navegar a otra pantalla u ocultar una insignia de descuento. --- # File: onboarding-quizzes --- --- title: "Cuestionarios en flows" description: "Añade cuestionarios interactivos a tus flows de Adapty para recopilar preferencias de usuario y crear flows personalizados, sin código." --- Usa los cuestionarios para presentar a los usuarios opciones predefinidas. A diferencia de los campos de entrada, los cuestionarios no tienen campos de texto libre: los usuarios eligen entre las opciones que tú defines. Úsalos para recopilar preferencias, segmentar usuarios o ramificar el flow según sus respuestas. ### Agregar un cuestionario \{#add-a-quiz\} 1. Haz clic en **+** en la parte superior izquierda. 2. Selecciona **Quiz**. 3. Elige el tipo de cuestionario: - **Icon/image/emoji options:** Una lista vertical de opciones seleccionables, cada una con un icono, imagen o emoji junto a una etiqueta de texto. - **Icon/image/emoji grid:** Una cuadrícula de opciones seleccionables, cada una con un icono, imagen o emoji. - **Rating:** Una escala para que los usuarios expresen una valoración — numérica o basada en estrellas. ### Configurar la navegación condicional \{#set-up-conditional-navigation\} Para redirigir a los usuarios de forma diferente según su selección, configura una acción condicional en el **botón de navegación**, no en la opción del quiz: 1. Selecciona el botón de navegación. 2. En el panel **Interactions**, añade un trigger **On Tap** con una acción **Conditional**. 3. En el diálogo **Edit Action**, configura la fila **if**: - A la izquierda, haz clic en `{}` y selecciona **Elements → Screen → `<quizElementId>.selectedOptionId`** para referenciar la selección del usuario. - Deja el operador como `=`. - A la derecha, introduce el elementId que quieres comparar — por ejemplo, `rock`. 4. En **then**, establece la acción como **Navigate to** y elige la pantalla de destino. 5. En **else**, define un destino alternativo con **Navigate to**, o haz clic en **+ Add else/if** para añadir más condiciones para otras opciones. :::link Consulta las guías relevantes para entender cómo usar las respuestas de los cuestionarios: - [Navegación condicional](onboarding-navigation-branching) - [Variables](onboarding-variables) - [Acciones](onboarding-actions) ::: ### Cambiar el tipo de cuestionario \{#change-quiz-type\} Por defecto, un cuestionario es de **selección múltiple** — los usuarios pueden elegir varias opciones a la vez. Cámbialo a **selección única** si quieres que solo puedan elegir una opción. 1. Selecciona la pantalla que contiene el cuestionario. 2. En **Screen settings**, desplázate hasta **Selectable groups** y haz clic en tu cuestionario. 3. En el diálogo **Edit group**, abre **Group type** y elige: - **Single choice** — solo se puede seleccionar una opción a la vez. - **Multi choice** — los usuarios pueden seleccionar varias opciones. 4. Haz clic en **Save**. --- # File: builder-inputs-and-forms --- --- title: "Inputs y formularios en el Flow Builder" description: "Añade elementos de formulario interactivos como campos de texto y casillas de verificación." --- Usa inputs para recopilar datos introducidos por los usuarios, como un nombre, una dirección de correo electrónico o una fecha de nacimiento. Guarda las respuestas y referencíalas en cualquier otra parte del flow, por ejemplo, para dirigirte al usuario por su nombre en una pantalla posterior. ## Añadir un campo de entrada \{#add-an-input\} 1. Haz clic en **+** en la parte superior izquierda. 2. Selecciona **Input**. 3. Elige el tipo de campo de entrada: - **Text:** Cualquier texto corto. - **Email:** Direcciones de correo electrónico, con validación de formato opcional. - **Password:** Entrada de texto seguro, con requisitos configurables. - **Number:** Valores numéricos, con formato configurable. - **Phone number:** Números de teléfono. - **Date:** Abre un selector de fecha. - **Time:** Abre un selector de hora. - **Date and time:** Abre un selector combinado. ## Configurar un campo de entrada \{#configure-an-input\} :::link Para más detalles sobre los ajustes visuales —diseño, estilo y visibilidad— consulta [Estilos y apariencia](builder-styling). ::: Para todos los tipos de campos de entrada, puedes configurar lo siguiente en la pestaña **Design**: - **Type:** Cambia el tipo de entrada (Text, Email, Password, Number, Phone number, Date, Time o Date and time). - **Element ID:** Identificador para referenciar el valor del campo en otras partes del flow. Consulta [Usar valores de entrada](#use-input-values) más abajo. - **Placeholder:** Texto de ayuda que se muestra dentro del campo vacío. - **State:** Define el aspecto del campo en distintas situaciones. Alterna entre **Default**, **Active**, **Invalid** y **Disabled** y aplica diferentes estilos visuales a cada uno. - **Typography:** Estilo del texto del valor mostrado en el campo. - **Leading and trailing icons:** Añade iconos dentro del campo de entrada. Algunos ajustes son específicos de determinados tipos de entrada: | Ajuste | Tipos de entrada | |---------------------------------|-------------------------------| | Botón para borrar | Texto, Correo electrónico | | Validar formato de correo | Correo electrónico | | Icono de mostrar contraseña | Contraseña | | Editar requisitos de contraseña | Contraseña | | Formato de número | Número | | Formato de fecha/hora | Fecha, Hora, Fecha y hora | | Fecha mínima y máxima | Fecha, Fecha y hora | ## Usar los valores de los campos de entrada \{#use-input-values\} Cada campo de entrada está disponible automáticamente como variable, sin necesidad de configuración ni de una acción **On Submit**. El valor se referencia mediante el **Element ID** del campo, que defines en **Input Settings**. Para usar el valor de un campo en otro punto del flow (por ejemplo, para personalizar texto, rellenar otro campo o controlar la navegación condicional), inserta una variable y elige: **Element > Screen > `<elementId>.value`** :::link Consulta las guías correspondientes para entender cómo usar los valores de entrada guardados: - [Navegación condicional](onboarding-navigation-branching) - [Variables](onboarding-variables) ::: ## Validación de inputs \{#input-validation\} El comportamiento de validación depende del tipo de input. Cada input expone una variable booleana de solo lectura, `<elementId>.isValid`, que indica si el valor introducido supera las reglas de validación del input. Úsala en acciones condicionales o visibilidad condicional — por ejemplo, para ocultar un botón Siguiente hasta que el formato de un correo electrónico sea válido. :::note - La variable `isValid` es de solo lectura — no se puede modificar. - Un input vacío siempre se considera válido. - Los inputs de texto no tienen reglas de validación. `textInput.isValid` siempre devuelve `True`. ::: | Tipo de entrada | Comportamiento de validación | |---|---| | Texto | Sin reglas de validación integradas. | | Correo electrónico | Opcional. Activa **Validate email format** en el panel **Design** para comprobar que el valor introducido tiene formato de correo electrónico. | | Número de teléfono | Verificación de formato de número de teléfono integrada. No es configurable en el Builder: la regla se evalúa en tiempo de ejecución. | | Contraseña | Configurable. Consulta [Requisitos de contraseña](#password-requirements) a continuación. | | Número | Basado en formato. El valor introducido debe coincidir con el formato de número seleccionado. Consulta [Formato de número](#number-format) a continuación. | | Fecha, Hora, Fecha y hora | Integrado. El selector solo acepta valores de fecha u hora válidos. | El [estado visual](builder-styling#input-states) **Invalid** se activa cuando el usuario envía el formulario, por ejemplo, al pulsar Intro o «Listo» en el teclado. Hasta ese momento, el campo muestra el estado **Active** o **Default**. ### Requisitos de contraseña \{#password-requirements\} Los campos de contraseña admiten reglas de validación configurables. Haz clic en **Edit password requirements** en el panel **Design** para abrir el editor de reglas. Las reglas activadas se muestran como una lista de verificación en tiempo real debajo del campo: cada elemento recibe una marca de verificación en el momento en que se cumple su regla. Reglas disponibles: - **Min length** — número mínimo de caracteres. Por defecto: 8. - **Max length** — número máximo de caracteres. Por defecto: 32. - **Uppercase letter** — al menos una letra de A a Z. - **Lowercase letter** — al menos una letra de a a z. - **Number** — al menos un dígito. - **Special character** — al menos un carácter no alfanumérico (por ejemplo, `!@#$%`). La contraseña es válida únicamente cuando se cumplen todas las reglas habilitadas. ### Formato de número \{#number-format\} El menú desplegable **Format** en la configuración de entrada **Number** controla cómo se interpreta el valor introducido: - **Integer** — solo números enteros (por ejemplo, `4`). - **Decimal (Point)** — decimales con punto como separador (por ejemplo, `4.89`). - **Decimal (Comma)** — decimales con coma como separador (por ejemplo, `4,89`). Los valores que no coincidan con el formato seleccionado se consideran inválidos. ## Activar acciones ante eventos de entrada \{#trigger-actions-on-input-events\} :::link Artículo principal: [Acciones](onboarding-actions) ::: Puedes ejecutar acciones en respuesta a la interacción del usuario mediante el panel **Interactions**: - **On changed** — se activa cuando el usuario cambia el valor del campo. Disponible en todos los tipos de entrada. - **On submit** — se activa cuando el usuario envía un campo de texto pulsando Intro o Listo en el teclado. Los selectores de fecha y hora no tienen este disparador. --- # File: onboarding-navigation-branching --- --- title: "Navegación y bifurcaciones" description: "Guía a los usuarios por las pantallas usando rutas estáticas y bifurcaciones dinámicas." --- La navegación y las bifurcaciones te permiten guiar a los usuarios a través de cada paso de tu flow: usa rutas estáticas para enviar a todos a las pantallas principales, y navegación dinámica para adaptar el flow según las elecciones del usuario. :::link La navegación es un tipo de acción. Para saber más sobre las acciones, consulta [Acciones](onboarding-actions). ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/OLl-WziDMhU?si=_eUtsmbEuFAaLj1r" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Navegar entre pantallas \{#navigate-between-screens\} Puedes configurar la navegación estática y dinámica usando distintos elementos del flow. ### Navegación estática \{#static-navigation\} La navegación estática dirige a todos los usuarios a la misma pantalla de destino. Para configurarla: 1. Selecciona cualquier elemento que los usuarios puedan pulsar: un botón, una respuesta de quiz o un toggle. 2. Abre el panel **Interactions** a la derecha y haz clic en **Add trigger**. Para navegar a los usuarios de inmediato cuando pulsan una opción del quiz —sin necesidad de un toque adicional en un botón— selecciona aquí el elemento de opción del quiz en lugar de un botón. 3. Configura el trigger **On tap**: - **Action**: Selecciona **Navigate to screen**. - **Destination**: Elige la pantalla de destino. ### Navegación dinámica \{#dynamic-navigation\} La navegación dinámica enruta a los usuarios según sus respuestas en el quiz, el estado de los elementos toggle y los atributos personalizados. Cualquier [elemento seleccionable](flow-selectable-elements) puede ser una condición para la navegación dinámica. Para configurarla: 1. Selecciona el elemento que navegará a los usuarios. 2. Abre el panel **Interactions** a la derecha y haz clic en **Add trigger**. Para navegar a los usuarios de inmediato cuando pulsan una opción del quiz —sin necesidad de un toque adicional en un botón— selecciona aquí el elemento de opción del quiz en lugar de un botón. 3. Configura el trigger **On tap**: - **Action**: Selecciona **Conditional**. - **Conditions**: Define las acciones de navegación condicional. Más información [aquí](onboarding-actions#conditional-actions). ## Cerrar el flow \{#close-flow\} Si el recorrido del usuario requiere cerrar el flow, puedes configurarlo usando botones o quizzes de respuesta única: 1. Añade y selecciona el elemento que debe cerrar el flow al pulsarlo. 2. Abre el panel **Interactions** a la derecha y haz clic en **Add trigger**. 3. Configura el trigger **On tap**: - **Action**: Selecciona **Close flow**. --- # File: onboarding-actions --- --- title: "Acciones" description: "Define acciones activadas por interacciones del usuario en el builder." --- El panel **Interactions** te permite definir cómo responden los elementos del flow a distintos eventos, como toques, apariciones de elementos y envíos de formularios. Para cada evento, asignas una o más acciones: navegar entre pantallas, mostrar u ocultar elementos, abrir URLs, establecer variables y más. Usa condiciones para personalizar el flow según los datos del usuario. Cada interacción sigue una cadena de tres partes: 1. **Element**: El componente de la pantalla que inicia la interacción: un botón, una respuesta de quiz, un campo de texto o cualquier otro elemento. 2. **Trigger**: El evento que activa la lógica, como un toque, la aparición de un elemento o el envío de un formulario. 3. **Action**: La tarea que ejecuta el flow en respuesta. Un solo trigger puede ejecutar varias acciones en secuencia. ## Configurar interacciones \{#set-up-interactions\} Para configurar una interacción: 1. Selecciona un elemento en la pantalla o en el panel **Layers**. 2. A la derecha, cambia al panel **Interactions** y haz clic en **Add trigger**. 3. En la sección **Button triggers**, selecciona el [tipo de disparador](#trigger-types). 4. Haz clic en **Add action**, haz clic en el nombre de la acción y selecciona un [tipo de acción](#action-types) en el desplegable de la ventana **Edit action**. 5. Configura las propiedades de la acción según el [tipo de acción](#action-types) que hayas seleccionado. 6. Si es necesario, haz clic en **Add action** para añadir más acciones al mismo activador. ## Tipos de disparadores \{#trigger-types\} Los disparadores se activan en respuesta al comportamiento del usuario, cambios de estado de los elementos o la carga de la pantalla. **On screen appear** es universal; los demás son específicos de cada elemento. | Disparador | Se activa cuando... | Compatible con | |---|---|---| | **On screen appear** | La pantalla se carga | Todos los elementos | | **On tap** | El usuario pulsa el elemento | [Botones](paywall-buttons), [opciones de quiz](onboarding-quizzes), [toggles](builder-toggles), [contadores regresivos](flow-timer), [vídeos](custom-media) | | **On changed** | El usuario cambia el valor del campo (escribiendo, seleccionando una fecha u hora) | Todos los [elementos de entrada](builder-inputs-and-forms) | | **On submit** | El usuario envía un campo de texto pulsando Intro o Hecho en el teclado | [Entradas de texto](builder-inputs-and-forms) | | **On timer end** | Un elemento [Countdown](flow-timer) llega a cero | [Countdown](flow-timer) | | **On playback finished** | Un [vídeo](custom-media) llega al final | [Video](custom-media) | Para elementos sin interacciones integradas (como [Loader](builder-loaders-and-progress-bars)), **On screen appear** es el único disparador disponible. ## Tipos de acción \{#action-types\} :::important **Cualquier acción de navegación** que lleve al usuario a otra pantalla debe ser siempre la última acción de la lista. Las acciones colocadas después (como "Set Variable") pueden no ejecutarse porque la app ya habrá cambiado de pantalla. ::: ### Navegar a la pantalla \{#navigate-to-screen\} Esta es la acción principal para mover a los usuarios entre pantallas. Lleva al usuario a una pantalla de destino específica. Para esta acción, solo necesitas configurar la pantalla de destino. Si quieres habilitar la navegación dinámica, consulta [Navegación y ramificación](onboarding-navigation-branching) o la sección [Acciones condicionales](#conditional-actions). ### Avanzar a la siguiente pantalla \{#navigate-next\} Avanza al usuario a la siguiente pantalla en el orden del flow. Úsalo en flows lineales, donde el orden de las pantallas en el editor coincide con el orden en que quieres que los usuarios las vean. ### Navegar hacia atrás \{#navigate-back\} Devuelve al usuario a la pantalla anterior en su historial de navegación, en lugar de la pantalla anterior en la secuencia. ### Abrir URL \{#open-url\} :::tip Usa [enlaces en línea](onboarding-text#inline-link) para insertar enlaces en texto continuo. ::: Abre una dirección web específica. Úsalo para redirigir a los usuarios a páginas web, artículos o perfiles en redes sociales fuera de las pantallas nativas de tu app. Para esta acción, puedes configurar dos ajustes: - **URL address**: Establece una dirección URL. Además, puedes hacerla dinámica, por ejemplo, para dirigir a los usuarios a diferentes páginas según su respuesta en un cuestionario o con los datos que han enviado. Para ello, haz clic en Variable icon y selecciona la variable que quieras usar. - **Open in external browser**: Define dónde quieres abrir los enlaces externos. Por defecto, se abren en un navegador integrado en la app para mantener a los usuarios dentro de ella. Marca la casilla **Open in external browser** si prefieres abrirlos en un navegador externo. ### Cerrar flow \{#close-flow\} Cierra el flow actual. ### Mostrar/ocultar elementos \{#showhide-elements\} Muestra u oculta un elemento específico en la pantalla. Esta acción anula el estado inicial definido en **Visibility** del panel **Design**. Si **Visibility** está configurado como **Hide**, la acción **Show** hará que el elemento aparezca. :::important Una acción **Show** o **Hide** sin elemento de destino [bloquea la previsualización y la publicación](builder-save-publish#troubleshooting). Selecciona un destino o elimina la acción. ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/3w3YSOmI3tQ?si=vPhoQGt44SI285Ru" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ### Mostrar alerta \{#show-alert\} Muestra una ventana emergente nativa del sistema. Los usuarios deben pulsar **Ok** para continuar. En las alertas debes configurar su **Title** y **Message**. En ambos campos puedes usar variables para que el contenido sea dinámico. Para ello, haz clic en Variable icon y selecciona la variable que quieras usar. :::important Una acción **Show alert** con una configuración vacía o incompleta [bloquea la vista previa y la publicación](builder-save-publish#troubleshooting). Rellena ambos campos o elimina la acción. ::: ### Establecer variable \{#set-variable\} Actualiza el valor de una variable en el flow. Antes de añadir esta acción, crea las variables en el panel **Variables** de la izquierda (consulta [Variables](onboarding-variables)). Haz clic en **Add variable** y define tantas variables y valores como necesites. :::important Una acción **Set variable** sin ninguna asignación [bloquea la vista previa y la publicación](builder-save-publish#troubleshooting). Configura al menos una asignación o elimina la acción. ::: ### Comprar \{#purchase\} Desencadena un flow de compra directamente desde un botón o interacción en tu onboarding. Úsalo para que los usuarios se suscriban o compren un producto sin salir del flow. Puedes configurar dos comportamientos para esta acción: - **In-app store**: Inicia una compra nativa. Establece **Product** en un producto específico, o en `products.selectedProduct` para la selección actual del usuario en la pantalla. - **Web payment**: Envía al usuario a un [paywall web](web-paywall) en lugar de activar una compra nativa. Úsalo cuando quieras gestionar la transacción fuera de la app, como para ofertas de suscripción basadas en web. :::important Una acción **Purchase** sin un **Product** o **Web Paywall URL** de destino [bloquea la previsualización y la publicación](builder-save-publish#troubleshooting). Asigna un destino o elimina la acción. ::: ### Restaurar compras \{#restore-purchases\} Activa el flow de restauración de compras en el dispositivo. Los usuarios lo utilizan cuando ya han adquirido una suscripción en otro dispositivo o tras reinstalar la app, y necesitan recuperar el acceso a sus derechos. No hay nada que configurar para esta acción: Adapty gestiona la restauración a través del flow nativo del store. La acción **Restore purchases** también está preconfigurada en el enlace **Restore** del preset de botón **Links** (consulta [Set up purchases](paywall-product-block#restore-purchases)). ## Acciones personalizadas \{#custom-actions\} Una acción personalizada dispara un **ID de acción** con nombre que tu propio código de la app gestiona. Úsala cuando los tipos de acción integrados no cubren lo que necesitas. Adapty proporciona el disparador; tu app implementa el comportamiento: 1. En el builder, asignas un **ID de acción** a la interacción de un elemento. 2. Cuando el usuario activa la interacción, el flow pasa el ID a tu app. 3. Tu app identifica el ID y ejecuta tu código. ### Configurar una acción personalizada \{#set-up-a-custom-action\} 1. En la ventana **Edit action**, asigna un **Action ID** — una cadena que tu app reconocerá (por ejemplo, `show_discount`). 2. En el código de tu app, implementa un manejador para este Action ID. Consulta [Gestionar acciones del paywall](handle-paywall-actions) para ver los detalles de implementación y ejemplos de código. :::important Una acción **Custom** sin un **Action ID** [bloquea la previsualización y la publicación](builder-save-publish#troubleshooting). Asigna un Action ID o elimina la acción. ::: ### Qué puedes hacer con las acciones personalizadas \{#what-you-can-do-with-custom-actions\} Una acción personalizada no hace nada por sí sola. Tú asignas un ID de acción estático en el builder, y el código de tu app se encarga de lo que ocurre cuando recibe ese ID. Todos los casos de uso que se describen a continuación siguen el mismo patrón: asigna un ID en el flow y luego gestiónalo en tu código. - **Activar un evento in-app**: Dispara un ID como `viewed_special_offer` y registra el evento en tu sistema de analítica cuando tu app lo reciba. - **Solicitar un permiso del sistema**: Dispara un ID como `request_location` y llama al prompt de permisos del SO desde tu app. Para permisos que el prompt no puede conceder, abre los ajustes del sistema del teléfono. Adapty no muestra el prompt — lo hace tu app. - **Iniciar autenticación nativa**: Dispara un ID como `login_google` y muestra tu propia pantalla de inicio de sesión. El flow no puede autenticar al usuario. - **Aplicar lógica de negocio**: Dispara un ID como `apply_discount` y desbloquea contenido o cambia el estado de la app por tu cuenta. - **Pasar una respuesta de quiz a tu app**: Asigna un Action ID distinto a cada opción (por ejemplo, `goal_weight_loss` y `goal_muscle`) y lee el ID en tu código. Úsalo para establecer un [atributo de usuario personalizado](setting-user-attributes#custom-user-attributes) sobre el que puedas segmentar más adelante. Como la acción solo lleva un ID fijo, esta es la única forma de reportar la elección — el flow no puede enviar el valor seleccionado. :::important Una acción personalizada se dispara en el momento en que el usuario selecciona una opción. Si el usuario cambia su respuesta, el flow también dispara el nuevo Action ID. Tu app recibirá ambos en orden — por ejemplo, `goal_weight_loss` y luego `goal_muscle`. Haz que tu manejador sea idempotente para que prevalezca la señal más reciente. ::: ### Qué no pueden hacer las acciones personalizadas \{#what-custom-actions-cant-do\} Las acciones personalizadas son estáticas. El ID de acción se fija cuando construyes el flow — no puede leer [variables](onboarding-variables) ni [entradas del usuario](builder-inputs-and-forms). Cuando se dispara la acción, tu app recibe únicamente ese ID, nunca el email, número de teléfono u otra entrada que el usuario haya introducido. Los campos de entrada permanecen dentro del flow como variables para ramificación y personalización. Para usar esos valores en tu app, recógelos a través de tu propia interfaz o API. Las acciones personalizadas también son unidireccionales. Tu app no puede devolver un resultado al flow, y el flow no espera a que tu código termine. Si una acción **Navigate next** sigue a la acción personalizada, el usuario avanza a la siguiente pantalla aunque tu código falle — por ejemplo, cuando el usuario cierra tu pantalla de inicio de sesión sin haber iniciado sesión. Combinado con el Action ID estático, esto impide validar la entrada del usuario en tu app — por ejemplo, verificar un código SMS que el usuario introdujo y ramificarse según el resultado. Si el resto del flow depende de lo que haya hecho tu código, [divide el flow entre dos placements](#continue-the-flow-based-on-the-result). ### Continuar el flow según el resultado \{#continue-the-flow-based-on-the-result\} Si algunas pantallas deben aparecer solo después de que una acción personalizada tenga éxito — por ejemplo, pantallas que siguen a un inicio de sesión — divide el flow entre dos [placements](placements) y deja que tu app decida cuándo mostrar la segunda parte: 1. En el primer placement, crea un flow que termine con una acción personalizada (por ejemplo, `login`). 2. En tu app, gestiona el Action ID: muestra tu pantalla de inicio de sesión y comprueba si el usuario ha iniciado sesión. 3. Si el usuario ha iniciado sesión, muestra el flow del segundo placement con las pantallas de seguimiento. De esta forma, tu app controla la transición basándose en el resultado real, en lugar de que el flow navegue hacia adelante sin importar el resultado. ## Acciones condicionales \{#conditional-actions\} <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/xmWSEPxnI0s?si=mazHQHE89qEDxvPA" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Usa acciones condicionales para dividir el flow en distintos caminos según los datos del usuario. Algunos casos de uso habituales son: - Tienes un cuestionario en la pantalla y quieres llevar a los usuarios a diferentes pantallas según sus respuestas. En ese caso, añade una acción condicional a un botón. - Quieres ofrecer distintos productos y ofertas a diferentes grupos de usuarios. Colócalos en pantallas separadas y configura las condiciones en un botón de navegación. - Quieres saltarte ciertos pasos para usuarios que ya completaron un tutorial en una sesión anterior de la app. Las acciones condicionales funcionan como una cadena if / else-if / else. La aplicación lee las reglas de arriba abajo y se detiene en la primera coincidencia: 1. **IF**: El flow comprueba la condición principal. - ¿Es True? El flow ejecuta las acciones THEN inmediatamente y se detiene. - ¿Es False? El flow pasa a la siguiente sección. 2. **ELSE IF**: Aquí puedes añadir comprobaciones adicionales (por ejemplo, "Si no es Premium, ¿el usuario está en un Trial?"). 3. **ELSE** (Fallback): Si ninguna de las reglas anteriores coincidió, el flow ejecuta las acciones de esta sección final. :::important - Si se añade una regla pero no tiene ninguna acción asignada, que coincida con la condición no produce ningún efecto. - Una regla incompleta (sin operador o valor) [bloquea la previsualización y la publicación](builder-save-publish#troubleshooting). ::: Para cada regla, selecciona una variable a evaluar y una acción a ejecutar. Puedes definir más de una acción por regla. :::important El flow ejecuta solo una regla: la primera con la que coincide. Si necesitas ejecutar **IF** y **ELSE IF** al mismo tiempo, añade ambas acciones a **IF**. ::: Para aprender a hacer elementos seleccionables y organizarlos en grupos para usarlos en condiciones, consulta [Elementos seleccionables y grupos](flow-selectable-elements). ## Solucionar problemas \{#troubleshooting\} Cualquier acción con campos obligatorios vacíos bloquea la previsualización y la publicación. Consulta [Guardar y publicar flows](builder-save-publish#troubleshooting) para ver la lista completa. --- # File: builder-loaders-and-progress-bars --- --- title: "Indicadores de progreso y cargadores" description: "Muestra el progreso por pasos y estados de carga en un flow." --- La categoría **Progress** ofrece dos tipos de elementos: uno para el progreso por pasos a través de un flow multipantalla, y otro para indicadores de actividad en línea. ## Indicadores de progreso \{#progress-indicators\} ### Estilos de indicador \{#indicator-styles\} Un elemento **Progress** muestra la posición del usuario en un flow de varias pantallas. La categoría ofrece tres variantes visuales: - **Linear** — Una barra única que se rellena conforme el usuario avanza. - **Segmented** — Barras separadas por cada paso que se van rellenando una a una. - **Connectors** — Círculos con etiquetas conectados por líneas (p. ej., Step 1, Step 2, Step 3 en secuencia). ### Asociar pasos a pantallas Por defecto, el indicador de progreso hace un seguimiento de todas las pantallas del flow. Para limitarlo a un subconjunto, elige las opciones en el desplegable **Screens**. También puedes abrir la pantalla que quieras excluir y desmarcar la casilla **Include screen in progress indicator**. Desactiva **One segment per screen** si necesitas un control más preciso sobre el número de pasos. :::warning La posición del paso sigue el orden de la lista de pantallas, no el orden en que el usuario las ve realmente. En flows no lineales, el paso mostrado en el indicador puede avanzar o retroceder. ::: ### Estados de los pasos \{#step-states\} Cada paso tiene tres estados: **Completed**, **Current** y **Upcoming**. Selecciona un paso dentro del Progress Indicator para editar el estilo de un estado en el panel derecho. Usa **Apply changes to all states** para copiar tus cambios a los otros dos. Editar un paso afecta a todos los pasos dentro del mismo indicador. ### Diseño y posicionamiento \{#layout-and-positioning\} El indicador de progreso es un elemento global, por lo que no puedes colocarlo dentro de un [contenedor](builder-containers). Tampoco puedes establecer su posición: utiliza posicionamiento absoluto por defecto. Cuando el usuario desplaza la pantalla, el indicador permanece fijo y el contenido se desplaza por debajo. Para controlar el espacio alrededor del indicador, usa los controles **Margin** y **Padding** de la sección **Spacing** en lugar de mover el elemento. Si el diseño no se ve bien, ajusta el margen o el relleno tanto en el indicador como en el elemento adyacente. ## Loaders Un **Loader** es un elemento animado que indica que hay un proceso en curso: por ejemplo, procesar las respuestas de un cuestionario para preparar un plan personalizado. La categoría ofrece tres plantillas: - **Spinner** — Un spinner circular. - **Spinner with label** — Un spinner circular con una etiqueta (p. ej., "Cargando..."). - **Loader** — Una barra horizontal que se rellena a medida que avanza el proceso. {/* - **Loader with label** — Una barra horizontal con una etiqueta y porcentaje (p. ej., "Analizando... 47%"). */} :::warning Un loader necesita un **desencadenador** para aparecer y desaparecer. Abre la pestaña **Interactions** para configurar esta lógica — por ejemplo, mostrarlo después de que el usuario envíe un cuestionario. ::: --- # File: onboarding-variables --- --- title: "Variables" description: "Usa variables para mostrar datos dinámicos en tus flows." --- Las variables te permiten mostrar contenido dinámico en tus flows — precios de productos, detalles de ofertas y otros datos que se actualizan según el contexto de cada usuario. Úsalas para controlar la visibilidad de elementos y personalizar el contenido de las pantallas. Para abrir el panel de variables, haz clic en el icono **{ }** en el panel izquierdo. El panel tiene tres pestañas: - **[Personalizadas](#custom-variables)**: Variables que creas y gestionas tú mismo. - **[Producto](#product-variables)**: Variables integradas que extraen datos localizados de productos y ofertas del store. - **[Elemento](#element-variables)**: Variables vinculadas a los estados de los elementos en el lienzo. ## Variables personalizadas \{#custom-variables\} ### Crear una variable personalizada \{#create-a-custom-variable\} 1. En el panel de variables, haz clic en **+**. 2. Escribe un nombre para la variable. 3. Selecciona un tipo: String, Number o Boolean. 4. Establece un valor inicial. Este es el valor que tendrá la variable cuando comience el flow. 5. Haz clic en **Create variable**. :::tip Usa puntos en los nombres para agrupar variables relacionadas — por ejemplo, `user.score` o `user.goal`. ::: ### Actualizar una variable mediante una interacción \{#update-a-variable-via-an-interaction\} :::link Consulta el artículo de [Acciones](onboarding-actions) para más detalles. ::: Puedes actualizar el valor de una variable en tiempo de ejecución añadiendo una acción **Set up variables** a cualquier elemento. 1. Selecciona un elemento en el lienzo. 2. En la pestaña **Interactions**, haz clic en **Add trigger**. 3. Selecciona **On tap** y haz clic en **Add action**. En el desplegable **Action type**, selecciona **Set up variables**. 4. Haz clic en **Add variable**. Selecciona la variable y establece el nuevo valor. :::tip Por ejemplo, puedes asignar un valor diferente a `user.goal` según la respuesta que seleccione el usuario en un cuestionario y, a continuación, usar esa variable para llevarlo a una pantalla diferente. ::: ## Variables de producto \{#product-variables\} Las variables de producto obtienen datos localizados directamente de las stores. Úsalas en campos de texto para mostrar precios localizados, títulos y detalles de ofertas, o en condiciones para mostrar u ocultar contenido según la elegibilidad de la oferta. | Variable | Descripción | Ejemplo | | :--- | :--- | :--- | | `prod_title` | Título localizado del producto | Premium Subscription | | `prod_price` | Precio localizado para un período de facturación | $9.99 | | `prod_price_per_day` | Precio de la suscripción dividido entre los días del período de facturación. Vacío para productos que no son suscripciones. | $0.33 | | `prod_price_per_week` | Precio de la suscripción dividido entre las semanas del período de facturación. Vacío para productos que no son suscripciones. | $2.33 | | `prod_price_per_month` | Precio de la suscripción ajustado a un mes. Vacío para productos que no son suscripciones. | $9.99 | | `prod_price_per_year` | Precio de la suscripción ajustado a un año. Vacío para productos que no son suscripciones. | $119.88 | | `offer_price` | Precio localizado de una oferta introductoria o promocional. Vacío si el usuario no es elegible para ninguna oferta. | $0.99 | | `offer_billing_period` | Período de facturación localizado de una oferta. Igual que `offer_full_duration` para ofertas de prueba gratuita y pago por adelantado. Vacío si el usuario no es elegible. | 1 week | | `offer_full_duration` | Duración total localizada de una oferta. Vacío si el usuario no es elegible. | 1 month | | `is_free_trial` | Devuelve `true` si el usuario es elegible para una oferta con prueba gratuita. | true | | `is_pay_up_front` | Devuelve `true` si el usuario es elegible para una oferta de pago por adelantado. | true | | `is_pay_as_you_go` | Devuelve `true` si el usuario es elegible para una oferta de pago por uso. | true | :::tip Usa `is_free_trial`, `is_pay_up_front` e `is_pay_as_you_go` con visibilidad condicional para mostrar u ocultar elementos según la oferta para la que el usuario sea elegible. Por ejemplo, muestra una línea de tiempo de prueba gratuita solo cuando `is_free_trial` sea `true`. ::: Los valores de las variables de oferta dependen del tipo de oferta para la que el usuario es elegible. Para ilustrarlo, tomemos una suscripción semanal llamada "Premium Subscription" a $5, con tres posibles ofertas: - **Pay As You Go**: Las primeras 3 semanas por $3 (facturado semanalmente), luego $5/semana. - **Pay Up Front**: Las primeras 3 semanas por $8 (facturado de inmediato), luego $5/semana. - **Free Trial**: Primera semana gratis, luego $5/semana. En este ejemplo, `prod_title` devuelve "Premium Subscription" y `prod_price` devuelve $5. Los valores de las variables de oferta dependen de la oferta para la que el usuario sea elegible: | Variable | Pay As You Go | Pay Upfront | Free Trial | | :--- | :--- | :--- | :--- | | `offer_price` | $3 | $8 | $0 | | `offer_billing_period` | 1 semana | 3 semanas | 1 semana | | `offer_full_duration` | 3 semanas | 3 semanas | 1 semana | Para las ofertas de pago por adelantado y prueba gratuita, `offer_billing_period` y `offer_full_duration` devuelven el mismo valor. En cambio, para Pago por uso, difieren porque el período de facturación es una semana, pero la duración total es de tres semanas. :::note Para obtener más información sobre las ofertas y cómo configurarlas, consulta [Ofertas](offers). ::: ## Variables de elemento \{#element-variables\} Las variables de elemento capturan las elecciones del usuario: qué seleccionó en los cuestionarios, en qué pestaña está y si el toggle de prueba está activado. Los tipos de variables de elemento dependen del grupo: - **Opción única**: Cuestionarios de opción única y pestañas: - `selected_id`: ID del elemento para usar en condiciones - `selected_title`: Título del elemento para usar en texto dinámico - **Opción múltiple**: Cuestionarios de opción múltiple: - `selected_ids`: IDs de elementos para usar en condiciones - `selected_titles`: Títulos de elementos para usar en texto dinámico - **Toggle**: Toggle de prueba: - `is_selected`: Valor booleano Los casos de uso más habituales son: - Mostrar contenido diferente según si el interruptor de prueba está activado. - [Navegar a los usuarios a distintas pantallas](onboarding-navigation-branching) según sus respuestas al cuestionario ## Usar variables en texto \{#use-variables-in-text\} Para insertar una variable en un elemento de texto: 1. Selecciona un elemento de texto en el canvas. 2. En la pestaña **Design**, busca el campo **Content** y escribe tu texto. 3. Haz clic en el icono **{ }** del campo. 4. Selecciona una variable de la lista. :::tip También puedes usar variables en otros elementos: - Usa variables en enlaces y alertas para hacerlos dinámicos - Crea condiciones dinámicas basadas en variables. Por ejemplo, la condición puede ser `if experience.current > experience.target, navigate to...` ::: ### Variables de estilo \{#style-variables\} No puedes aplicar formato de texto enriquecido a una variable de forma individual. Seleccionar una variable en el campo **Content** y aplicar negrita, cursiva, subrayado, tachado o cambio de color no tiene ningún efecto. Los ajustes de texto enriquecido se aplican únicamente al bloque de texto completo. Para dar estilo al texto, usa la sección **Typography** de la pestaña **Design**, o aplica un [estilo de texto](onboarding-text#set-up-text-styles) guardado. ### Reutilizar contenido en varias pantallas \{#reuse-content-across-screens\} Algunos contenidos se repiten en tu flow: una etiqueta de botón como "Continuar", una llamada a la acción recurrente o un aviso legal que aparece en varias pantallas. Lo mismo ocurre con textos más largos, como la descripción de una función reutilizada en múltiples pantallas. En lugar de escribir ese contenido en cada elemento, guárdalo en una variable personalizada. Esto resulta especialmente útil cuando diriges a distintos usuarios a diferentes pantallas pero quieres mantener un texto coherente en todas ellas. 1. [Crea una variable personalizada](#create-a-custom-variable) de tipo String y establece su valor inicial con el texto que quieres reutilizar. Por ejemplo, nómbrala `button.navigation` y establece el valor como `Continue`. 2. Inserta esta variable en el campo **Content** de cada elemento donde deba aparecer el texto. Para cambiar el texto en todas partes, actualiza el valor inicial de la variable una sola vez. Todos los elementos que usan la variable se actualizan automáticamente, así que no tienes que editar cada pantalla a mano. --- # File: onboarding-element-visibility --- --- title: "Visibilidad condicional" description: "Muestra u oculta elementos según condiciones." --- <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/3w3YSOmI3tQ?si=vPhoQGt44SI285Ru" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Puedes controlar si un elemento es visible añadiéndole una condición. Un elemento condicional solo es visible para los usuarios que cumplen los criterios especificados. :::important Si muestras u ocultas un elemento usando la acción **Show** u **Hide** [action](onboarding-actions), dicha acción tiene prioridad sobre la condición de **Visibility** establecida en ese elemento. Usa condiciones de **Visibility** para elementos que deben mostrarse u ocultarse siempre en función de un criterio fijo. Usa acciones cuando la visibilidad deba cambiar según la interacción del usuario; por ejemplo, mostrar un botón después de que el usuario responda una pregunta de un cuestionario. ::: Para añadir una condición a un elemento: 1. Selecciona el elemento en el canvas o en el panel de capas. 2. En la sección **Visibility** del panel derecho, selecciona **Conditional**. 3. Configura la condición eligiendo un tipo de propiedad en una de las tres pestañas: - **Custom**: Variables que creas y gestionas tú; sus valores se pueden actualizar mediante interacciones del usuario. Consulta [Variables](onboarding-variables) para más detalles. - **Products**: Propiedades de los productos de tu flow, como el precio o el nombre. - **Elements**: Estados de otros elementos del flow, como si un interruptor de prueba está activo. 4. Introduce el **Value** que quieres comparar. 5. Haz clic en el operador para cambiarlo si es necesario. 6. (Opcional) Haz clic en **Add condition** para añadir más condiciones. Usa el selector para exigir que se cumplan todas las condiciones o al menos una de ellas. --- # File: paywall-dark-mode --- --- title: "Modo oscuro" description: "Configura el modo oscuro para los flows en Adapty y mejora la experiencia de usuario." --- Los flows de Adapty admiten el modo oscuro de serie. Por defecto, los estilos de color tienen una alternativa clara y una oscura: cuando aplicas un estilo de color a un elemento, el flow usa el valor adecuado según el modo actual del dispositivo. Adapty incluye un conjunto de estilos de color preconfigurados, y puedes crear los tuyos propios. ## Configurar estilos de color \{#configure-color-styles\} Cada **estilo de color** define una alternativa de color para modo claro y otra para modo oscuro. Cuando un elemento usa un estilo con nombre, cambia entre ambas automáticamente. Puedes gestionar los estilos de color en **Style** > **Colors** en la barra lateral izquierda. Para añadir un estilo de color: 1. En **Style** > **Colors**, haz clic en **Create style**. 2. Selecciona las alternativas de color para modo claro y modo oscuro. Para renombrar un estilo, haz clic en **⋮** junto a él y selecciona **Rename**. ## Establecer el tema de la barra de estado \{#set-the-status-bar-theme\} Si **Status bar** está activada en el panel **Screen settings**, puedes configurar su tema de forma independiente: selecciona **Light**, **Dark** o **Auto** en las opciones de **Status bar theme**. ## Vista previa en modos claro y oscuro \{#preview-light--dark-modes\} Para ver cómo queda tu flow en cada modo, usa el botón de sol/luna situado en la parte inferior del área de vista previa. ## Eliminar el modo oscuro \{#remove-dark-mode\} Para eliminar por completo el soporte del modo oscuro, en el panel **Style** > **Colors**, haz clic en **⋮** > **Delete dark theme**. --- # File: add-paywall-locale-in-adapty-paywall-builder --- --- title: "Añadir idioma en el Flow Builder" description: "Añade contenido localizado en el Flow Builder de Adapty para llegar a usuarios de todo el mundo en su idioma." --- Localizar tus flows los hace disponibles en varios idiomas. En el Flow Builder, la localización se organiza por pantalla, y cada una muestra un porcentaje de completado para hacer seguimiento del progreso de la traducción. :::tip Termina de configurar tu flow en el idioma predeterminado antes de añadir otros idiomas. ::: ## Añadir y configurar la localización \{#add-and-set-up-localization\} 1. En el panel izquierdo, haz clic en Localizations. Luego, haz clic en **Add locale**. Selecciona los idiomas que quieras añadir. 2. Cada idioma añadido aparece como una columna en la tabla de localización, con los valores del idioma predeterminado ya rellenados. 3. Para centrarte solo en lo que falta, activa el toggle **Missing only** en el panel izquierdo. La tabla filtrará y mostrará únicamente las filas sin traducir. ## Exportar e importar para traducción externa \{#export-and-import-for-external-translation\} Puedes exportar el archivo de localización para compartirlo con traductores e importar los resultados traducidos. En la barra de herramientas superior, haz clic en **Import / Export**. ### Formato del archivo de exportación \{#export-file-format\} La exportación genera un archivo `.tsv` (separado por tabulaciones) con una fila por cada elemento traducible. Las columnas son: | Columna | Descripción | |--------|-------------| | `Screen` | La pantalla a la que pertenece el elemento (p. ej., `Welcome`, `Quiz`) | | `Element` | Identificador del elemento generado automáticamente dentro de esa pantalla. Puedes cambiarlo en **Interactions** > **Element ID**. | | `Property` | El tipo de propiedad (p. ej., `content`) | | `[default_locale]` | El código del idioma predeterminado (p. ej., `en`) | | `[locale]` | Una columna por cada idioma añadido (p. ej., `fr`, `es`) | Ejemplo: Pantalla Elemento Propiedad en fr es Welcome title content Turn words into art Transformez les mots en art Welcome subtitle content Create stunning images in seconds with AI Créez des images en quelques secondes Quiz quiz-title content What will you create? :::note Deja en blanco las columnas de idioma para las líneas sin traducir — Adapty las tratará como ausentes. ::: ### Requisitos del archivo de importación \{#import-file-requirements\} - **Formato**: `.tsv` (valores separados por tabulaciones) - **Encabezados**: debe incluir `Screen`, `Element`, `Property` y al menos una columna de configuración regional - **Nombres de columna de configuración regional**: deben coincidir con los códigos de configuración regional ya añadidos al flow. Importar un archivo con códigos de configuración regional que no estén presentes en el flow provoca un error. - **Importación parcial**: puedes incluir solo un subconjunto de filas; las filas que no estén en el archivo conservan sus valores actuales ## Traducir manualmente \{#translate-manually\} También puedes escribir las traducciones directamente en cualquier celda de la tabla de localización. Para gestionar una fila específica, abre su menú contextual (**⋮**): - **Reset to default**: Revierte la traducción de la fila a los valores del idioma predeterminado. ## Vista previa de la localización \{#preview-the-localization\} Para revisar tus traducciones, cambia el idioma activo en el Flow Builder y revisa cada pantalla. --- # File: add-flow-remote-config-locale --- --- title: "Localizar un flow con Remote Config" description: "Añade locales al Remote Config de un flow para servir diferentes valores según el idioma o la región." --- El Remote Config de un flow puede contener un payload JSON independiente para cada locale. En tiempo de ejecución, el SDK devuelve el payload que coincide con el locale del usuario, de modo que puedes servir traducciones, imágenes distintas u otros valores específicos por locale sin publicar una nueva versión de la app. ## Añadir una configuración regional \{#add-a-locale\} Para añadir una configuración regional al Remote Config de un flow: 1. Abre el flow en Flow Builder. 2. Haz clic en el icono de Remote Config que aparece encima de la vista previa de pantalla. 3. Haz clic en **Add locale** encima del editor. 4. Rellena el diálogo: - **Code**: El código de la configuración regional, por ejemplo `en`, `fr` o `de`. - **Name**: El nombre de visualización, por ejemplo English o French. Adapty añade una nueva columna en el editor JSON para esa configuración regional. ## Editar valores por idioma \{#edit-values-per-locale\} Cada columna de idioma acepta sus propios datos en formato JSON. Usa las mismas claves en todas las columnas y traduce los valores para cada idioma. Por ejemplo, la columna en inglés: ```json showLineNumbers { "title": "Try for free!", "cta": "Continue", "trial_days": 7 } ``` Y la columna en español: ```json showLineNumbers { "title": "¡Prueba gratis!", "cta": "Continuar", "trial_days": 7 } ``` Las columnas son independientes: editar una no afecta a las demás. ## Lee el locale correspondiente en tu app \{#read-the-matching-locale-in-your-app\} El SDK expone una entrada `AdaptyRemoteConfig` por locale en `AdaptyFlow.remoteConfigs`. Selecciona la entrada cuyo `locale` coincida con tu usuario y luego lee su `dictionary` o `jsonString` para usar los valores en tiempo de ejecución. ## Hacer copia de seguridad o mover locales \{#back-up-or-move-locales\} Usa el menú **Import/Export** sobre el editor para hacer una copia de seguridad de tu Remote Config o copiarlo entre flows. El archivo JSON exportado contiene los datos de todos los locales a la vez. Consulta [Personalizar el flow con Remote Config](customize-flow-with-remote-config) para ver el formato del archivo. --- # File: customize-flow-with-remote-config --- --- title: "Personalizar flow con Remote Config" description: "Personaliza tu flow de Flow Builder con un payload JSON de Remote Config." --- :::important Esta guía cubre Remote Config para Flow Builder. Para paywalls clásicos creados sin Flow Builder, consulta [Diseñar paywall con Remote Config](customize-paywall-with-remote-config). ::: Remote Config te permite almacenar un payload JSON personalizado que el SDK lee en tiempo de ejecución. Úsalo para definir valores como títulos, imágenes, fuentes, colores o indicadores de funcionalidades sin publicar una nueva versión de la app. ## Trabajar con Remote Config \{#work-with-remote-config\} Para abrir el Remote Config de un flow, haz clic en el icono Remote Config que aparece encima de la vista previa de pantalla en el editor del flow. En la vista **JSON**, puedes introducir cualquier dato en formato JSON. El editor muestra una columna por cada idioma que hayas añadido: :::warning Si el Remote Config contiene JSON no válido, no podrás **guardar** ni **publicar** el flow. Consulta [Guardar y publicar flows](builder-save-publish#troubleshooting) para ver la lista completa de problemas que bloquean la vista previa y la publicación. ::: Más adelante puedes acceder a estos datos desde el SDK a través del array `remoteConfigs` en `AdaptyFlow`. Adapty almacena una entrada `AdaptyRemoteConfig` por idioma; elige la que coincida con el idioma del usuario y lee el `dictionary` parseado o el `jsonString` sin procesar para ajustar tu flow en tiempo de ejecución. A continuación encontrarás algunos ejemplos de cómo puedes usar un Remote Config. <Tabs> <TabItem value="Titles" label="Títulos" default> ```json showLineNumbers { "screen_title": "Today only: Subscribe, and get 7 days for free!" } # Test titles or other texts ``` </TabItem> <TabItem value="Images" label="Imágenes"> ```json showLineNumbers { "background_image": "https://adapty.io/media/paywalls/bg1.webp" } # Test images on your flow ``` </TabItem> <TabItem value="Fonts" label="Fuentes"> ```json showLineNumbers { "font_family": "San Francisco", "font_size": 16 } # Test fonts ``` </TabItem> <TabItem value="Color" label="Color"> ```json showLineNumbers { "subscribe_button_color": "purple" } # Test colors of buttons, texts etc. ``` </TabItem> <TabItem value="HTML" label="HTML"> ```json showLineNumbers { "photo_gallery": "https://adapty.io/media/paywalls/link-to-html-snippet.html" } # Any HTML code that can be displayed in the flow ``` </TabItem> <TabItem value="Soft/Hard Paywall" label="Paywall Suave/Estricto"> ```json showLineNumbers { "hard_paywall": true } # By setting it to true, you disallow skipping the paywall without subscribing # You have to handle this logic in your app ``` </TabItem> <TabItem value="Translations" label="Traducciones"> ```json showLineNumbers { "title": { "en": "Try for free!", "es": "¡Prueba gratis!", "ru": "Попробуй бесплатно!" } } ``` </TabItem> </Tabs> Puedes combinar cualquiera de estos patrones o definir tus propias claves para probar textos alternativos, diseños o comportamientos distintos. A continuación, [crea un placement](create-placement) y añade el flow a él. Luego renderiza el flow en tu app: [iOS](present-remote-config-paywalls) o [Android](present-remote-config-paywalls-android). ## Añadir una configuración regional \{#add-a-locale\} Para localizar tu flow, haz clic en **Add locale** sobre el editor y selecciona las configuraciones regionales. Adapty añade una nueva columna en el editor para esa configuración regional. Edita cada columna de forma independiente — en tiempo de ejecución, el SDK devuelve la entrada `AdaptyRemoteConfig` cuyo `locale` coincide con la selección del usuario. ## Importar y exportar JSON \{#import-and-export-json\} Usa el menú **Import/Export** situado sobre el editor para hacer copias de seguridad, compartir o editar en bloque tu Remote Config en todos los idiomas a la vez. - **Export JSON**: descarga un único archivo JSON con todos los idiomas incluidos. - **Import JSON**: sube un archivo JSON en el mismo formato. El archivo subido reemplaza el Remote Config actual. El archivo usa códigos de idioma como claves de nivel superior, con el contenido de cada idioma como valor: ```json showLineNumbers { "en": { "title": "Get Premium", "cta": "Continue", "trial_days": 7, "features": ["sync", "export", "ai"] }, "fr": { "title": "Passez à Premium", "cta": "Continuer", "trial_days": 7, "features": ["synchronisation", "exportation", "IA"] } } ``` Cada bloque de idioma sigue la misma estructura JSON que introducirías directamente en una columna de idioma. --- # File: paywall-device-compatibility-preview --- --- title: "Previsualizar flows" description: "Previsualiza la compatibilidad del flow en distintos dispositivos para una experiencia optimizada." --- Tienes dos formas de previsualizar tu flow en distintos tipos de pantalla: - **Previsualizar en dispositivos**: Comprueba cómo se ve tu flow en dispositivos reales en cualquier fase del desarrollo. - **Previsualizar en el Adapty Dashboard**: Previsualiza tu flow mientras lo diseñas. ## Vista previa en dispositivos \{#preview-on-devices\} Para previsualizar tu flow en un dispositivo real: 1. [Descarga la app de Adapty desde el App Store](https://apps.apple.com/us/app/adapty/id6739359219). 2. En el flow builder, haz clic en **Test on device**. 3. Selecciona el idioma del flow. 4. Escanea el código QR con la cámara del dispositivo o abre el enlace. Esto abrirá tu flow en la app móvil de Adapty. :::note En modo de prueba, Adapty no puede acceder a tus productos en los stores, por lo que los precios que se muestran en el flow no son reales. ::: ### Solución de problemas \{#troubleshooting\} No puedes publicar ni previsualizar tu flow si existe alguno de los siguientes problemas. - Una interacción con una configuración incompleta. Casos habituales: - Una acción **Open URL** sin URL de destino. - Una acción **Navigate to screen** sin destino — también ocurre cuando la pantalla de destino se elimina después de configurar la acción. - Una **acción condicional** sin operador ni valor. - Una acción **Set Variable** sin asignación de variable o valor. - Una acción **Purchase** sin producto (store in-app) ni URL de Web Paywall (pago web). - Una acción **Custom** sin Action ID. - Una acción **Show alert** con el título o el mensaje vacíos. - Una acción **Show** o **Hide** sin ningún elemento seleccionado. - Una **pantalla sin elementos**. - Un elemento de producto **sin producto asociado** — puede ocurrir si eliminas el producto referenciado. - Un JSON de **Remote Config** inválido interrumpe todo el proceso de entrega — ni siquiera puedes guardar el borrador. ## Vista previa en el Adapty Dashboard \{#preview-in-the-adapty-dashboard\} :::tip Para asegurarte de que tu flow está listo para publicar, [pruébalo en un dispositivo real](#preview-on-devices) y confirma que se muestra sin errores. ::: Puedes previsualizar tu flow en distintos tipos de pantalla desde el área de vista previa del flow builder. Esto te ayuda a comprobar que el flow se ve bien en diferentes dispositivos y tamaños de pantalla. Con los controles de vista previa que aparecen debajo de la previsualización, puedes: - Seleccionar el dispositivo en el que previsualizar tu flow. - Cambiar entre los modos de vista horizontal y vertical. - Cambiar entre los modos claro y oscuro. - Cambiar entre idiomas. :::tip - Previsualiza siempre distintos idiomas, ya que la longitud de las palabras varía según el idioma y el diseño de pantalla puede verse diferente en cada uno. - Para previsualizar variables personalizadas, establece valores iniciales para ellas. Por ejemplo, si añades una variable `name`, puedes establecer el valor inicial como `Jane Doe` para previewarla. ::: --- # File: builder-save-publish --- --- title: "Guardar y publicar flows" description: "Guarda flows como borradores y publícalos para los usuarios" --- El [Flow Builder](adapty-flow-builder) separa el guardado de la publicación. Los borradores conservan tu trabajo en el Adapty Dashboard, y publicar hace que la versión actual esté disponible para los usuarios a través del SDK. Este artículo explica ambas acciones y cuándo usar cada una. ## Guardar un flow como borrador \{#save-a-flow-as-a-draft\} :::warning Un [Remote Config](customize-flow-with-remote-config) no válido impide guardar el borrador. ::: El Flow Builder guarda tu progreso automáticamente una vez por minuto. Para guardar un borrador manualmente, haz clic en **Save draft** en la parte superior derecha del Flow Builder, o pulsa **Cmd/Ctrl + S**. Los borradores son privados en el Dashboard. No afectan lo que ven los usuarios en tu app, aunque el flow ya esté asignado a un [placement](placements). ## Publicar un flow \{#publish-a-flow\} Al publicar, la versión actual de tu flow queda disponible para los usuarios a través del SDK. Una vez publicada, la nueva versión reemplaza cualquier versión publicada anteriormente del mismo flow. :::note Para añadir un flow a un [placement](placements), publícalo primero. Un flow en estado Draft no se puede añadir. ::: Para publicar tu flow, haz clic en **Publish to Live** en la parte superior derecha del Flow Builder. Lo que ocurre a continuación depende de si el flow ya está asignado a un placement: - **Flow ya en un placement**: Los usuarios empiezan a ver la nueva versión en su próxima solicitud a ese placement. - **Flow no incluido en un placement**: Añade el flow a un [placement](create-placement) para empezar a mostrárselo a los usuarios. :::tip Un flow está listo para publicarse cuando cada acción, pantalla y elemento de producto está completamente configurado. Consulta [Solución de problemas](#troubleshooting) para ver los errores más comunes. ::: :::warning Las [fuentes personalizadas](using-custom-fonts-in-flow-builder) no se incluyen con el flow — debes añadir cada archivo de fuente al bundle de tu app. Sin el archivo, los usuarios verán la fuente del sistema como alternativa. Para cambiar una fuente en un flow publicado sin romper versiones anteriores: duplica el flow, cambia la fuente en la copia y dirige la copia a una [audiencia](add-audience-paywall-ab-test) en las versiones de la app que incluyan la fuente. ::: ## Estado del flow \{#flow-status\} Cada flow muestra un estado en la lista de Flows. El estado refleja en qué punto del ciclo de guardado y publicación se encuentra el flow. | Estado | Significado | | :----- | :------ | | **Draft** | El flow nunca se ha publicado. Solo existe un borrador, así que los usuarios aún no lo ven. Debes publicar el borrador primero para añadirlo a un [placement](placements). | | **Dirty** | El flow fue publicado, pero tiene ediciones guardadas que aún no se han publicado. Los usuarios siguen viendo la última versión publicada hasta que publiques de nuevo. | | **Publishing** | Hay una publicación en curso. | | **Failed** | El último intento de publicación falló. Los usuarios siguen viendo la última versión publicada, si existe alguna. | | **Published** | La última versión guardada está activa. No hay ediciones sin publicar. | | **Archived** | El flow fue eliminado. | ## Solución de problemas \{#troubleshooting\} No puedes publicar ni previsualizar tu flow si existe alguno de los siguientes problemas. - Una interacción con una configuración incompleta. Casos habituales: - Una acción **Open URL** sin URL de destino. - Una acción **Navigate to screen** sin destino — también ocurre cuando la pantalla de destino se elimina después de configurar la acción. - Una **acción condicional** sin operador ni valor. - Una acción **Set Variable** sin asignación de variable o valor. - Una acción **Purchase** sin producto (store in-app) ni URL de Web Paywall (pago web). - Una acción **Custom** sin Action ID. - Una acción **Show alert** con el título o el mensaje vacíos. - Una acción **Show** o **Hide** sin ningún elemento seleccionado. - Una **pantalla sin elementos**. - Un elemento de producto **sin producto asociado** — puede ocurrir si eliminas el producto referenciado. - Un JSON de **Remote Config** inválido interrumpe todo el proceso de entrega — ni siquiera puedes guardar el borrador. Previsualiza tu flow en la [app de Adapty](paywall-device-compatibility-preview) para detectar cualquier problema antes de publicarlo. Si el flow no carga en la previsualización, consulta el mensaje de error para obtener más detalles. --- # File: flow-metrics --- --- title: "Métricas de flow" description: "Rastrea y analiza las métricas de rendimiento de los flows para mejorar los ingresos por suscripción." --- Adapty recopila una serie de métricas para ayudarte a medir el rendimiento de tus flows. A diferencia de las métricas de paywall, las métricas de flow incluyen seguimiento de completado, por lo que puedes ver dónde abandonan los usuarios a lo largo de las pantallas del flow. Todas las métricas se actualizan en tiempo real, excepto las vistas, que se actualizan cada varios minutos. Este documento describe las métricas disponibles, sus definiciones y cómo se calculan. :::important Los ingresos del flow se calculan a partir de todas las transacciones que ocurrieron después de que se mostró el flow. ::: Las métricas de los flows están disponibles en la lista de flows, donde puedes ver un resumen del rendimiento de todos tus flows. Esta vista consolidada muestra métricas agregadas para cada flow, lo que te permite comparar su efectividad e identificar áreas de mejora. Para un análisis más detallado de cada flow, navega a las métricas de detalle del flow. Allí encontrarás métricas completas específicas del flow seleccionado, con una visión más profunda de su rendimiento. ## Controles de métricas \{#metrics-controls\} El sistema muestra las métricas según el período de tiempo seleccionado y las organiza por el parámetro de la columna izquierda con tres niveles de sangría. En los flows publicados, las métricas cubren el período desde la fecha de publicación del flow hasta la fecha actual. Los flows en borrador y archivados se incluyen en la tabla de métricas, pero si no hay datos disponibles, aparecen sin métricas. ### Opciones de vista para los datos de métricas \{#view-options-for-metrics-data\} La página del flow ofrece dos opciones de vista para los datos de métricas: - Vista por placement: Las métricas se agrupan según los [placements](placements) asociados al flow. Usa esta vista para comparar el rendimiento de un mismo flow en distintos placements. - Vista por audiencia: Las métricas se agrupan según la [audiencia](audience) objetivo del flow. Usa esta vista para evaluar las métricas específicas de distintos segmentos de audiencia. El menú desplegable en la parte superior de la página del flow te permite seleccionar la vista que prefieras. ### Filtrar métricas por fecha de instalación \{#filter-metrics-by-install-date\} La casilla **Filter metrics by install date** te permite analizar datos según cuándo los usuarios instalaron tu app, en lugar de cuándo ocurrieron las transacciones o las visualizaciones. Es útil para medir el rendimiento de la adquisición de usuarios en una cohorte específica. ### Rangos de tiempo \{#time-ranges\} Puedes analizar los datos de métricas usando un rango de tiempo, lo que te permite centrarte en duraciones específicas como días, semanas, meses o rangos de fechas personalizados. ### Filtros y grupos \{#filters-and-groups\} Adapty ofrece herramientas para filtrar y personalizar el análisis de métricas según tus necesidades. La página de métricas te da acceso a distintos rangos de tiempo, opciones de agrupación y posibilidades de filtrado. - Filtrar por: Atribución (fuente, grupo de anuncios, conjunto de anuncios, creatividad, campaña), País, Store. - Agrupar por: Flow (por defecto), País o Store. Las opciones de agrupación aparecen en el desplegable solo cuando hay datos para esa dimensión; por ejemplo, si todas las visualizaciones de un flow provienen de un único país, País no estará disponible. Puedes encontrar más información sobre los controles, filtros, opciones de agrupación y cómo usarlos en [esta documentación](controls-filters-grouping-compare-proceeds). ### Gráfico de métrica única \{#single-metric-chart\} La sección del gráfico muestra tus datos en un gráfico de barras sencillo. El gráfico te ayuda a ver de un vistazo: - Los valores exactos de cada métrica. - Los datos por período. Junto al gráfico aparece la suma total, para que tengas el panorama completo de un vistazo. Haz clic en el icono de flecha para expandir el gráfico. ### Resumen de métricas totales \{#total-metrics-summary\} Junto al gráfico de métrica individual, hay una sección de resumen de métricas totales. Esta sección muestra los valores acumulados de las métricas seleccionadas en un momento específico. Puedes cambiar la métrica que se muestra usando el menú desplegable. ## Definiciones de métricas \{#metrics-definitions\} ### Vistas y vistas únicas \{#views--unique-views\} **Views** cuenta las veces que los usuarios inician tu flow (llegan a la primera pantalla). Si alguien lo inicia dos veces, se cuentan como dos vistas pero una sola vista única. Esta métrica te ayuda a entender con qué frecuencia se ha mostrado tu flow. ### Completions y completions únicos \{#completions--unique-completions\} **Completions** cuenta las veces que los usuarios llegan a la última pantalla de tu flow. Si alguien lo completa dos veces, se cuentan dos completions pero un único completion. ### Tasa de finalizaciones únicas \{#unique-completions-rate\} El número de finalizaciones únicas dividido entre el número de visualizaciones únicas. Usa esta métrica para entender cómo avanzan los usuarios a través del flow e identificar en qué punto lo abandonan. :::note Adapty convierte otras divisas a USD según el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo de cambio se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: ### Ingresos \{#revenue\} **Revenue** muestra el total de ganancias en USD procedentes de compras y renovaciones atribuidas al flow. Es el importe antes de cualquier deducción, incluida la comisión del App Store / Play Store. ### Ingresos netos \{#proceeds\} Los [**Ingresos netos**](analytics-cohorts#revenue-vs-proceeds) muestran lo que recibes después de que App Store / Play Store cobra su comisión, pero antes de impuestos. :::important Notifica a Adapty si tu app está inscrita en un programa de comisión reducida. Para garantizar cálculos correctos, especifica el estado de tu [Small Business Program](app-store-small-business-program) y [Reduced Service Fee program](google-reduced-service-fee) en los [ajustes de tu app](general). ::: ### Ingresos netos \{#net-proceeds\} Tus ganancias finales una vez descontadas tanto las comisiones de la store como los impuestos. ### ARPPU ARPPU es el ingreso promedio por usuario de pago. Se calcula dividiendo los ingresos totales entre el número de usuarios de pago únicos. $15.000 de ingresos / 1.000 usuarios de pago = $15 de ARPPU. ### ARPU \{#arpu\} ARPU es el ingreso medio por usuario que ha visto el flow. Se calcula dividiendo el ingreso total entre el número de visitantes únicos. ### ARPAS ARPAS es el ingreso promedio por suscriptor activo. Se calcula dividiendo los ingresos totales entre el número de suscriptores que han activado una prueba o suscripción. Por ejemplo: $5.000 de ingresos / 1.000 suscriptores = $5 de ARPAS. ### Compras CR y compras CR únicas \{#cr-purchases--unique-cr-purchases\} **Tasa de conversión a compras** muestra qué porcentaje de las visualizaciones del flow resultan en compras. Por ejemplo, 10 compras de 100 visualizaciones equivalen a una tasa de conversión del 10%. **CR único de compras** mide qué porcentaje de los usuarios únicos que ven tu flow acaban realizando una compra, contando a cada usuario solo una vez independientemente de cuántas veces lo vean. ### CR de trials y CR único de trials \{#cr-trials--unique-cr-trials\} **La tasa de conversión a trials** muestra qué porcentaje de las visualizaciones del flow resultan en el inicio de un trial. Por ejemplo, 10 trials de 100 visualizaciones equivalen a una tasa de conversión del 10%. **El CR único de trials** mide qué porcentaje de los usuarios únicos que visualizan tu flow inician un trial, contando a cada usuario una sola vez independientemente de cuántas veces lo hayan visto. ### Compras \{#purchases\} **Purchases** contabiliza todas las transacciones de tu flow, excepto las renovaciones. Esto incluye: - Compras directas nuevas. - Conversiones de pruebas activadas en el flow. - Cambios de plan (mejoras, rebajas, cambios entre planes). - Restauraciones de suscripciones en el flow, como cuando se reactiva una suscripción tras su vencimiento sin renovación automática. Esta métrica te da una imagen completa de la actividad de nuevas transacciones generada por tu flow. ### Pruebas gratuitas \{#trials\} **Trials** cuenta el número de usuarios que iniciaron períodos de prueba gratuita a través de tu flow. Usa esta métrica para saber con qué eficacia tu oferta de prueba atrae a los usuarios antes de que decidan pagar. ### Pruebas canceladas \{#trials-cancelled\} **Trials cancelled** muestra cuántos usuarios desactivaron la renovación automática durante su período de prueba. Esto te indica cuántas personas decidieron no continuar con una suscripción de pago después de probar tu servicio. ### Reembolsos \{#refunds\} **Reembolsos** contabiliza cuántas compras y suscripciones fueron devueltas para su reembolso, independientemente del motivo. ### Tasa de reembolso \{#refund-rate\} La **tasa de reembolso** muestra el porcentaje de primeras compras que fueron reembolsadas. Ejemplo: 5 reembolsos de 1.000 primeras compras = 0,5% de tasa de reembolso. Las renovaciones no se tienen en cuenta en este cálculo. --- # File: fallback-flows --- --- title: "Flows de respaldo" description: "Configura flows de respaldo locales en Adapty para mantener tu flow visible cuando el dispositivo está sin conexión." --- Para mantener una experiencia de usuario fluida, es importante configurar **versiones de respaldo** de tus [flows](adapty-flow-builder). Cuando tu aplicación solicita un flow, el SDK de Adapty contacta con nuestros servidores para obtener su configuración. Si el dispositivo no puede llegar a Adapty (problema de red, caída del servidor), el SDK recurre a los datos locales: - Si el usuario ya ha visto el flow anteriormente, el SDK sirve la copia en caché. - Si no existe caché, el SDK carga un archivo de configuración de respaldo incluido en la app. Adapty genera estos archivos de respaldo automáticamente. El bundle de respaldo del flow se comparte con los paywalls: un único archivo JSON por plataforma contiene las variaciones de respaldo para ambos. El SDK lee la sección que necesita en cada caso. :::important Los fallbacks de flow se incluyen en el paquete del **SDK de Adapty 4.0+**. Si seleccionas una versión anterior del SDK en el diálogo de descarga, el archivo solo contiene variaciones de paywall y onboarding, sin flows. Asegúrate de que tu app use una versión del SDK compatible con flows antes de depender de un flow de respaldo. ::: ## Antes de empezar \{#before-you-start\} 1. Crea un [flow](adapty-flow-builder) en el Flow Builder. 2. [Crea un placement](create-placement) para el flow. ## Descarga el archivo de respaldo \{#download-the-fallback-file\} 1. Abre la página **[Placements](https://app.adapty.io/placements)**. 2. Haz clic en el botón **Fallbacks** en la parte superior derecha. 3. Selecciona tu plataforma de destino en el desplegable. 4. Elige la versión del SDK que coincida con la que se incluye en tu app. Selecciona **Adapty SDK v4.0.0 and higher** (o una opción posterior) para recibir un bundle que incluya flows. El navegador descarga un archivo JSON por plataforma; por ejemplo, `ios_4_0_0_fallback.json`. <details> <summary>Ejemplo de entrada de fallback de flow (haz clic para expandir)</summary> ```json "PLACEMENT_ID": { "data": [ { "developer_id": "PLACEMENT_ID", "variation_id": "cb1c0ef8-aecd-4a53-a6f3-b98266e66884", "flow_id": "daf25858-3fa2-4981-8500-9c8a30e5b7e6", "flow_name": "FLOW_NAME", "flow_version_id": "FLOW_VERSION_ID", "placement_audience_version_id": "a9eb3ab8-3178-477d-84d4-ef9d3978e48b", "audience_name": "All Users", "ab_test_name": "", "cross_placement_info": null, "weight": 100, "variations": [ { "variation_id": "cb1c0ef8-aecd-4a53-a6f3-b98266e66884", "paywall_id": "PAYWALL_ID", "paywall_name": "PAYWALL_NAME", "ab_test_name": "", "products": [], "revision": 1, "custom_payload": null, "weight": 100 } ], "remote_configs": [] } ], "meta": { "placement": { "developer_id": "PLACEMENT_ID", "is_tracking_purchases": true, "audience_name": "All Users", "placement_audience_version_id": "a9eb3ab8-3178-477d-84d4-ef9d3978e48b", "revision": 0, "ab_test_name": "" } } } ``` La forma exacta puede cambiar entre versiones del SDK. Usa siempre el archivo que Adapty generó para tu versión del SDK en lugar de crearlo manualmente. </details> ## Tras la descarga \{#after-the-download\} Añade el archivo al código de tu app y sigue la guía de configuración correspondiente a tu plataforma. Las mismas APIs que cargan los paywall de respaldo también cargan los flow de respaldo una vez que tu app esté en una versión del SDK compatible con flows: - [iOS](ios-use-fallback-paywalls) - [Android](android-use-fallback-paywalls) - [React Native](react-native-use-fallback-paywalls) - [Capacitor](capacitor-use-fallback-paywalls) ## Limitaciones \{#limitations\} Los flows de respaldo están codificados y almacenados localmente, por lo que no cuentan con todas las capacidades dinámicas de los flows en vivo: - **Una variación por placement.** Si un placement tiene más de un flow (distintas audiencias, variantes de prueba A/B), el archivo de respaldo usa la variación con mayor peso, o la audiencia más amplia. - **Sin pruebas A/B.** Una prueba A/B de flow en vivo se resuelve en el servidor; el respaldo siempre sirve una única variación elegida. - **Sin actualizaciones remotas.** Actualizar el respaldo requiere publicar una nueva versión de la app. Para los cambios que normalmente harías mediante Remote Config en tiempo de ejecución, publícalos a través del flow en vivo. - **Solo la configuración regional predeterminada.** El respaldo usa la configuración regional `en`; las variantes localizadas no se incluyen en el bundle. --- # File: create-product --- --- title: "Crear producto" description: "Guía paso a paso para crear nuevos productos de suscripción en Adapty para una mejor gestión de ingresos." --- La forma de crear productos en Adapty depende de si ya existen en las stores: - **[Si los productos no existen aún en App Store y/o Google Play, créalos en Adapty y publícalos directamente en las stores](#create-product-and-push-to-store)**. - **[Si los productos ya existen en App Store y/o Google Play, créalos en Adapty y conéctalos con los productos existentes en las stores.](#create-product-and-connect-existing-store-products)** :::tip También puedes crear productos mediante programación usando la [CLI para desarrolladores](developer-cli-reference#adapty-products-create). ::: ## Crear un producto y publicarlo en la store \{#create-product-and-push-to-store\} :::warning Antes de empezar, asegúrate de haber configurado la integración con las stores que necesitas: - [App Store](initial_ios) - [Google Play](initial-android) Si configuraste la integración con App Store hace algún tiempo, comprueba también que hayas [añadido la clave API de App Store Connect](app-store-connection-configuration#step-6-add-app-store-connect-api-key). ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/qUpC2XG-r5E?si=7Komyv4_PUQ4FaEH" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Para añadir un nuevo producto a tu app: 1. Ve a **[Products](https://app.adapty.io/products)** desde el menú principal de Adapty. <img src="/assets/shared/img/products-tab.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en **Create product** en la esquina superior derecha. Adapty admite todo tipo de productos: suscripciones, no consumibles \(incluido el acceso de por vida\) y consumibles. 3. Selecciona **Create a new product and push to stores**. <img src="/assets/shared/img/push-to-stores.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Introduce los siguientes datos: - **Product name**: introduce el nombre del producto que se usará en el Adapty Dashboard. El nombre es principalmente para tu referencia, así que elige el que te resulte más cómodo usar en el Adapty Dashboard. - **Access Level**: selecciona el [nivel de acceso](access-level) al que pertenece el producto. El nivel de acceso determina las funcionalidades que se desbloquean tras comprar el producto. Ten en cuenta que esta lista solo contiene los niveles de acceso creados previamente. El nivel de acceso `premium` se crea en Adapty por defecto, pero también puedes [añadir más niveles de acceso](access-level). - **Subscription duration**: selecciona la duración de la suscripción en la lista. - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**: la duración de la suscripción. - **Lifetime**: usa el período de por vida para los productos que desbloquean las funcionalidades premium de la app de forma permanente. - **Non-Subscriptions**: para los productos que no son suscripciones y, por tanto, no tienen duración, usa non-subscriptions. Pueden utilizarse para desbloquear funcionalidades adicionales, productos consumibles, etc. - **Consumables**: los artículos consumibles pueden comprarse varias veces y se consumen durante la vida de la aplicación. Algunos ejemplos son la moneda del juego y los extras. Ten en cuenta que los productos consumibles no afectan a los niveles de acceso. Para otorgar un nivel de acceso desde una compra única, usa **Non-Subscriptions** en su lugar. - **Price (USD)**: el precio del producto en USD. Este precio se usará como base para calcular y establecer automáticamente los precios en todos los países. Podrás [personalizar el precio para distintos países y regiones](edit-product#set-country-specific-prices) más adelante. <img src="/assets/shared/img/create-product-push.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en **Save & Continue**. 6. Configura la información del producto para App Store si tienes previsto publicar allí: - **Product ID**: Crea un ID único y permanente para el producto. - **Product group**: Selecciona un grupo de productos existente que hayas creado en App Store Connect o haz clic en **Create new Product Group** e introduce su nombre. Una vez que Adapty lo cree, podrás seleccionarlo desde el desplegable. - **Screenshot**: Sube una captura de pantalla de la compra in-app que muestre claramente el artículo o servicio ofrecido. Esta captura de pantalla se usa únicamente para la revisión de App Store y no se muestra en la App Store. Consulta los requisitos de tamaño y formato [aquí](https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications/). <img src="/assets/shared/img/push-app-store.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Haz clic en **Push data to App Store**. :::warning Si es el primer producto de esta app, deberás enviarlo manualmente a revisión en App Store Connect. Esto no será necesario más adelante. Una vez finalizada la revisión, el estado del producto en Adapty se actualizará automáticamente. ::: 8. Configura la información del producto para Google Play si piensas publicarlo allí: - **Base Product ID**: Crea un ID único y permanente para el producto. - **Subscription**: Selecciona un grupo de suscripción existente que hayas creado en Google Play Console o haz clic en **Create new Product Group** y define su nombre e ID. Una vez que Adapty lo cree, podrás seleccionarlo desde el desplegable. :::note El período de gracia y el período de retención de cuenta se establecerán automáticamente con los valores predeterminados según las reglas de Play Store. Puedes cambiarlos más adelante en Google Play Console. ::: <img src="/assets/shared/img/push-google-play.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. Haz clic en **Push data to Play Store**. 10. Para iOS, configura la oferta introductoria – prueba gratuita – seleccionando su **Free duration** en el desplegable. Para esta configuración inicial, puedes añadir una prueba gratuita introductoria. Una vez que el store apruebe el producto principal, podrás [añadir más ofertas](offers) (p. ej., promocionales, de recuperación) vinculando sus IDs existentes desde la consola de tu store. <img src="/assets/shared/img/intro.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Las ofertas introductorias no se sincronizan automáticamente con Google Play. A diferencia del App Store, Google Play no tiene un tipo de "oferta introductoria" separado: tanto las pruebas gratuitas como las ofertas con descuento se configuran como **offers** en un plan base. [Crea la oferta en Google Play Console y vincúlala a tu producto de Adapty](google-play-offers). ::: 11. Por último, haz clic en **Save** para confirmar la creación del producto. ## Crear producto y conectar productos de store existentes \{#create-product-and-connect-existing-store-products\} :::warning Antes de empezar, asegúrate de haber: - Configurado la integración con los stores que necesitas: - [App Store](initial_ios) - [Google Play](initial-android) - Creado productos en los stores que necesitas: - [App Store](app-store-products) - [Google Play](android-products) **Si no tienes ningún producto creado**, considera seguir la guía [Enviar a stores](#create-product-and-push-to-store) para crearlos en Adapty y en los stores al mismo tiempo. ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/nlkdKCF0SwY?si=VVigzHcpv3waKJmI" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Para añadir un nuevo producto a tu app: 1. Ve a **[Products](https://app.adapty.io/products)** desde el menú principal de Adapty. <img src="/assets/shared/img/products-tab.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en **Create product** en la esquina superior derecha. Adapty admite todo tipo de productos: suscripciones, no consumibles \(incluido acceso de por vida\) y consumibles. 3. Selecciona **Connect an existing store product**. <img src="/assets/shared/img/existing-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Introduce los siguientes datos: - **Product name**: introduce el nombre del producto que se usará en el Adapty Dashboard. Este nombre es principalmente para tu referencia, así que elige el que te resulte más cómodo usar en el Adapty Dashboard. - **Access Level ID**: Selecciona el [nivel de acceso](access-level) al que pertenece el producto. El nivel de acceso determina las funciones que se desbloquean tras comprar el producto. Ten en cuenta que esta lista solo contiene niveles de acceso creados previamente. El nivel de acceso `premium` se crea en Adapty por defecto, pero también puedes [añadir más niveles de acceso](access-level). - **Duración de la suscripción**: selecciona la duración de la suscripción en la lista. - **Semanal/Mensual/2 meses/3 meses/6 meses/Anual**: La duración de la suscripción. - **Lifetime**: Usa el período de por vida para los productos que desbloquean las funciones premium de la app para siempre. - **Non-Subscriptions**: Para los productos que no son suscripciones y, por tanto, no tienen duración, usa non-subscriptions. Pueden servir para desbloquear funciones adicionales, productos consumibles, etc. - **Consumables**: Los artículos consumibles se pueden comprar varias veces. Se pueden agotar durante la vida útil de la aplicación. Algunos ejemplos son la moneda del juego y los extras. Ten en cuenta que los productos consumibles no afectan a los niveles de acceso. Para otorgar un nivel de acceso a partir de una compra única, usa **Non-Subscriptions** en su lugar. - **Precio (USD)**: El precio del producto en USD. Si tu producto ya está en el store, este valor no afectará a su precio real en el store; puedes seleccionar cualquier valor de la lista. Más adelante, puedes [personalizar los precios para distintas regiones](edit-product#set-country-specific-prices) directamente en el Adapty Dashboard. <img src="/assets/shared/img/product-info.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en **Continue**. 6. Configura la información del producto en cada store: - **App Store:** - **App Store Product ID:** Este identificador único se usa para acceder a tu producto en los dispositivos. Selecciónalo de la lista. Si no aparece, revisa su configuración en App Store Connect y asegúrate de que sea correcto y pertenezca a esta app. - **Play Store:** - **Google Play Product ID:** Es el identificador del producto en la Play Store. Selecciónalo de la lista. Si no aparece, revisa su configuración en Google Play Console y asegúrate de que sea correcto y pertenezca a esta app. - **Base Plan ID:** Este ID define el plan base del producto en la Play Store. Al añadir el Product ID de una suscripción en la Play Store, debes proporcionar un Base Plan ID. Un plan base define los detalles esenciales de una suscripción: el período de facturación, el tipo de renovación (automática o prepago) y el precio asociado. Ten en cuenta que, en Adapty, cada combinación de la misma suscripción con distintos planes base se trata como un producto independiente. - **Legacy fallback product**: Un producto de respaldo que se usa exclusivamente para apps con versiones antiguas del SDK de Adapty (versiones 2.5 e inferiores). Al marcar un producto como compatible con versiones anteriores en Google Play Console, Adapty puede determinar si puede ser adquirido por versiones antiguas del SDK. Para este campo, especifica el valor con el siguiente formato: `<subscription_id>:<base_plan_id>`. - **Stripe**: - **Stripe Product ID**: Identificador único de un producto en Stripe. - **Stripe Price ID**: En Stripe, los objetos de precio incluyen más que solo el importe; también contemplan el comportamiento fiscal, los niveles de volumen y los intervalos de suscripción. Como un mismo producto puede tener varios precios, especifica el ID de precio correcto al crear un producto en Adapty. - **Paddle**: - **Paddle Product ID**: Identificador único de un producto en Paddle. - **Paddle Price ID**: En Paddle, los objetos de precio incluyen más que solo el importe; también contemplan el comportamiento fiscal, los niveles de volumen y los intervalos de suscripción. Como un mismo producto puede tener varios precios, especifica el ID de precio correcto al crear un producto en Adapty. 7. **Opcional:** Puedes añadir productos de cualquier store personalizada haciendo clic en **Add custom store**. En la ventana **Manage custom store info**, puedes seleccionar una store personalizada existente o añadir una nueva y asociarle un producto. Ten en cuenta que Adapty solo registra transacciones de App Store, Google Play y Stripe. Para las stores personalizadas, deberás enviar las transacciones mediante el método Set transaction de la API server-side de Adapty. 8. Haz clic en **Save product** para finalizar la creación del producto. La sincronización del estado del producto puede tardar hasta cinco minutos, así que espera a que se actualice en la tabla. 9. Si lo necesitas, puedes [crear ofertas](create-offer) para el producto. Para añadir ofertas, haz clic en **Yes, add offers**. Si no, haz clic en **No, thanks**. :::note Las ofertas introductorias solo se crean en Adapty cuando se publica un producto en el store. Al importar productos o en productos creados anteriormente, las ofertas introductorias no se sincronizan ni se muestran en Adapty, aunque seguirán funcionando correctamente en la app. ::: ## Próximos pasos \{#next-steps\} ¡Enhorabuena! Has añadido tus productos a Adapty. ¿Qué viene ahora? - Si aún no has configurado las ofertas introductorias/promocionales, puedes [hacerlo](offers) ahora. - Si no quieres hacerlo o ya lo has hecho, continúa con la [configuración de los paywalls](quickstart-paywalls) para habilitar las compras in-app. - Si quieres hacer ajustes en los productos del store (p. ej., establecer precios regionales o configurar el período de gracia), hazlo en App Store Connect o Google Play Console. - Lee cómo puedes [editar productos](edit-product) más adelante. --- # File: edit-product --- --- title: "Editar producto" description: "Modifica y gestiona tus productos de suscripción en Adapty para un mejor seguimiento de ingresos." --- En Adapty, puedes editar el nombre del producto, el nivel de acceso, los precios regionales y los IDs de store conectados, además de ver el registro de auditoría para hacer seguimiento de los cambios de precio. La duración de la suscripción no es editable una vez creado el producto, por lo que tendrás que crear uno nuevo si necesitas cambiarla. :::warning Aunque puedes editar cualquier producto, es fundamental asegurarte de que los cambios en productos ya usados en paywalls activos no generen discrepancias en tus analíticas. **No se recomienda editar el nivel de acceso, el ID de producto de App Store ni el ID de producto de Play Store**, ya que puede afectar la claridad de las analíticas. Edítalos únicamente si cometiste un error, como una errata en el ID del producto. Si ya no usas el producto y quieres reemplazarlo por otro, te recomendamos encarecidamente crear un nuevo producto y actualizar los paywalls y las pruebas A/B correspondientes. ::: ## Editar producto \{#edit-product\} Para editar el producto: 1. Ve a **[Products](https://app.adapty.io/products)** desde el menú principal de Adapty. 2. Haz clic en la fila del producto en la tabla, o haz clic en los tres puntos junto al producto y selecciona **Edit**. 3. En la ventana **Edit** que se abre, realiza los cambios necesarios. Para más detalles sobre las opciones disponibles, consulta la sección [Crear producto](create-product). 4. Haz clic en **Save**. :::warning Los cambios que hagas en App Store Connect o Google Play Console no se sincronizan de vuelta con Adapty. El precio que se muestra en Adapty se establece cuando creas el producto y no se actualiza si cambias el precio en la store. Esto no afecta a tus analíticas de ingresos: Adapty obtiene los datos de ingresos directamente de las stores. El campo de precio en el dashboard es solo para tu referencia. ::: :::note Si cambias el nivel de acceso, el cambio se aplica únicamente a las nuevas suscripciones. Para los suscriptores existentes, el nivel de acceso actual permanece sin cambios y se actualiza automáticamente en la próxima renovación de la suscripción. ::: <img src={require('./img/edit-product.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Establecer precios por país \{#set-country-specific-prices\} Puedes configurar precios diferentes para distintas regiones directamente en el Adapty Dashboard, y estos precios por país se aplicarán automáticamente a tus productos en App Store Connect y/o Google Play Console. Para establecer precios por país: 1. [Abre el producto para editarlo](#edit-product). 2. Haz clic en **Download** para exportar tus precios actuales de las stores en el formato correcto, o crea un nuevo archivo CSV. <img src={require('./img/download-prices.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Actualiza los precios en el archivo CSV. Respeta el [formato](#csv-file-format). Si dejas el precio de algún país sin cambios o no lo incluyes en el archivo, no ocurrirá nada. Al subir el CSV, Adapty compara los precios y actualiza únicamente los que sean diferentes. 4. En la ventana **Edit**, haz clic en **Upload** y selecciona el archivo CSV. <img src={require('./img/upload-prices.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Si quieres que los cambios también se apliquen a los suscriptores existentes, selecciona **Apply to existing subscribers**. 6. Revisa los cambios que se aplicarán y haz clic en **Save changes**. <img src={require('./img/country-level-price.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Formato del archivo CSV \{#csv-file-format\} :::tip Puedes reutilizar el mismo archivo CSV si tienes productos similares en una misma app o si quieres establecer los mismos precios en diferentes apps. ::: La forma más sencilla de editar precios en CSV es [descargar un archivo con los precios actuales y editarlo directamente](#set-country-specific-prices). Sin embargo, si lo creas tú mismo, el archivo debe contener las siguientes columnas: - `region_name` - `region_code` - `app_store_currency` - `app_store_requested_price` - `play_store_currency` - `play_store_requested_price` Ejemplo: ``` region_name,region_code,app_store_currency,app_store_requested_price,play_store_currency,play_store_requested_price United States,US,,8.99,,8.99 United Arab Emirates,AE,USD,8.99,AED,39.99 Germany,DE,USD,8.99,USD,8.99 ``` ## Ver el registro de auditoría \{#view-audit-log\} Adapty registra todos los cambios de precio de cada producto, para que puedas hacer seguimiento de quién realizó los cambios y cuándo. Para ver el registro de auditoría: 1. Ve a **[Products](https://app.adapty.io/products)** desde el menú principal de Adapty. 2. Haz clic en los tres puntos junto al producto y selecciona **Audit log**. La tabla del registro de auditoría muestra cada cambio de precio con la fecha, el nombre y rol del miembro del equipo, y el número de cambios. Para descargar un desglose CSV detallado de un evento, haz clic en el icono de descarga de esa fila. <img src={require('./img/audit-log.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: delete-product --- --- title: "Eliminar producto" description: "Descubre cómo eliminar un producto de suscripción en Adapty sin interrumpir el flujo de ingresos de tu app." --- Solo puedes eliminar productos que no estén en uso en ningún paywall. Para eliminar el producto: 1. Ve a **[Products](https://app.adapty.io/products)** desde el menú principal de Adapty. 2. Haz clic en el botón de **3 puntos** junto al producto y selecciona **Delete**. <img src="/assets/shared/img/delete-product.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Introduce el nombre del producto que vas a eliminar. <img src="/assets/shared/img/b945add-delete_product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Delete forever**. --- # File: add-product-to-paywall --- --- title: "Añadir producto a un paywall" description: "Aprende a añadir y gestionar productos en paywalls en Adapty." --- Para que un producto sea visible y seleccionable dentro de un [paywall](paywalls) para los usuarios de tu app, sigue estos pasos: 1. Al [configurar un paywall](create-paywall), haz clic en **Add product** bajo el título **Products**. 2. En el menú desplegable que se abre, selecciona los productos que se mostrarán a tus clientes. La lista contiene únicamente los productos creados previamente. El orden de los productos se mantiene en el lado del SDK, por lo que es importante tener en cuenta el orden deseado al configurar el paywall. Además, puedes especificar una oferta para un producto si lo deseas. <img src="/assets/shared/img/0479b51-ad_product_to_paywall.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Create as draft** o **Save and publish** según el estado del paywall. Ten en cuenta que, tras la creación, no se recomienda editar, añadir ni eliminar productos del paywall, ya que esto puede afectar a las métricas del paywall. --- # File: virtual-currencies --- --- title: "Monedas virtuales" description: "Define monedas in-app en Adapty, vincúlalas a productos para conceder créditos automáticamente y lleva el saldo de cada usuario." --- <CustomDocCardList ids={['virtual-currency-quickstart', 'create-virtual-currency', 'virtual-currency-balance']} /> Otorga a tus usuarios moneda virtual — tokens de IA, créditos o monedas — cuando compran productos o renuevan suscripciones. Define una moneda una sola vez, vincúlala a los productos que deben concederla y Adapty acredita a cada usuario automáticamente y mantiene su saldo. Tu aplicación lee y gasta ese saldo a través de la [API del lado del servidor](getting-started-with-server-side-api) — por ejemplo, para cobrar tokens por cada generación. ## Cómo funciona \{#how-it-works\} 1. [Crea una moneda virtual](create-virtual-currency): Abre **Products** y haz clic en la pestaña **Virtual currency**. Haz clic en **New virtual currency**. Define una moneda con un código, un nombre y una descripción opcional. 2. **Vincula la moneda a productos**: Asocia la moneda a productos de compra única o suscripción y establece cuántos créditos otorga cada compra. 3. **Los créditos se conceden automáticamente**: Cuando un usuario compra un producto único vinculado o renueva una suscripción vinculada, Adapty añade los créditos configurados a su saldo. 4. **Lee y gasta el saldo**: Tu app lee el saldo de cada usuario y concede o gasta créditos a través de la API de servidor. 5. **Registra cada cambio**: Cada cambio de saldo aparece en el [perfil del usuario](virtual-currency-balance). Para un recorrido completo —desde la configuración de la moneda hasta el uso de créditos a través de la API— sigue la [guía de inicio rápido de moneda virtual](virtual-currency-quickstart). ## Casos de uso \{#use-cases\} | Tipo de app | Cómo usar las monedas virtuales | |-------------|----------------------------------| | **Apps de IA** (generación de imágenes, vídeo o texto) | Cobra créditos por cada generación. Incluye una cuota mensual de créditos en la suscripción y vende paquetes de créditos como productos únicos. | | **Apps de cortometrajes y vídeo** | Cobra monedas para desbloquear episodios. Otorga a los suscriptores una cuota recurrente de monedas y vende paquetes de monedas como productos únicos. | | **Aprendizaje de idiomas y educación** | Da a los usuarios gratuitos una cuota limitada de corazones o vidas a través de la API del servidor. Otorga una cuota mayor, o elimina la comprobación de saldo, en un plan de pago. | | **Juegos móviles** | Gestiona una moneda blanda y una dura en paralelo: otorga oro por el juego a través de la API del servidor, vende gemas por dinero y convierte entre ellas en una única transacción atómica. | | **Apps de productividad** | Limita las acciones costosas, como el OCR o las exportaciones, con créditos. Da a los usuarios gratuitos una pequeña cuota, otorga más con una suscripción y vende paquetes de créditos para trabajo en lote. | :::tip Para las asignaciones de suscripción, activa la [expiración de créditos](create-virtual-currency#link-products). Cuando los créditos no utilizados se reinician en cada renovación, la asignación sigue siendo un motivo para mantener la suscripción, y los usuarios más activos compran paquetes de créditos en lugar de agotar un stock acumulado. ::: ## Limitaciones \{#limitations\} - **El acceso entre dispositivos requiere identificación**: Un saldo pertenece a un perfil. Un usuario anónimo mantiene su saldo en el dispositivo donde lo ganó, por lo que debes identificar a los usuarios para que su saldo esté disponible en todos los dispositivos. Consulta [Saldos, perfiles y dispositivos](virtual-currency-balance#balances-profiles-and-devices). - **Solo en el lado del servidor**: Todavía no existe ningún método del SDK para leer o gastar un saldo, por lo que tu app necesita un backend. Este lee los saldos y otorga o gasta créditos a través de la API del lado del servidor. - **Hasta 20 monedas por app**: Puedes crear hasta 20 monedas virtuales en una sola app. --- # File: virtual-currency-quickstart --- --- title: "Inicio rápido de moneda virtual" description: "Configura una moneda virtual de extremo a extremo: crea una moneda de tokens, otorga tokens con una suscripción y gástalos a través de la API del lado del servidor." --- :::link Artículo principal: [Monedas virtuales](virtual-currencies) ::: Este inicio rápido configura una moneda de tokens de extremo a extremo: una suscripción otorga a los usuarios 1000 tokens al mes, y las acciones de pago en tu app cuestan tokens. Adapty gestiona cada saldo, por lo que tu backend solo necesita leerlos y gastarlos. 1. Sigue la guía [Crear moneda virtual](create-virtual-currency) para crear una moneda `TOKENS`. Vincúlala a tu suscripción Pro y establece **Credit per cycle** en 1000. Para vender paquetes de tokens adicionales, vincula también productos de paquetes de tokens de compra única. 2. Llama a [List virtual currency balances](api-adapty/operations/listVirtualCurrencyBalances) para leer el saldo del usuario, por ejemplo, antes de iniciar una generación: ```bash title="Read balances" curl https://api.adapty.io/api/v2/server-side-api/vc/balances/ \ -H "Authorization: Api-Key {your secret key}" \ -H "adapty-customer-user-id: user-42" ``` La respuesta lista todas las monedas que tiene el usuario, por ejemplo 1000 `TOKENS`: ```json title="Response" { "data": [ { "code": "TOKENS", "name": "Tokens", "balance": 1000, "held": 0, "available": 1000 } ] } ``` 3. Cuando el usuario ejecute una generación, gasta los tokens llamando a [Crear transacción de moneda virtual](api-adapty/operations/createVirtualCurrencyTransaction) con un `amount` negativo. En este ejemplo, una generación de imagen cuesta 100 tokens: ```bash title="Spend tokens" curl -X POST https://api.adapty.io/api/v2/server-side-api/vc/transactions/ \ -H "Authorization: Api-Key {your secret key}" \ -H "adapty-customer-user-id: user-42" \ -H "Content-Type: application/json" \ -d '{"items": [{"currency_code": "TOKENS", "amount": -100}]}' ``` La transacción es atómica y devuelve el saldo actualizado. Si el usuario no puede cubrir el coste, la solicitud devuelve `insufficient_balance` y no se produce ningún cambio. Incluye una cabecera `Idempotency-Key` para reintentar solicitudes de forma segura. 4. Para lanzar una promo de recuperación, otorga tokens a través del mismo endpoint con un `amount` positivo. Los créditos otorgados de esta forma nunca expiran. 5. Revisa cada cambio en el [perfil](virtual-currency-balance) del usuario o en el [historial de transacciones](api-adapty/operations/listVirtualCurrencyTransactions) para auditar la economía en cualquier momento. --- # File: create-virtual-currency --- --- title: "Crear moneda virtual" description: "Crea una moneda virtual en Adapty, vincúlala a productos para que las compras otorguen créditos y configura si esos créditos expiran." --- :::link Artículo principal: [Monedas virtuales](virtual-currencies) ::: Para vender créditos de moneda virtual, define una moneda virtual una sola vez y luego vincúlala a tus productos. Esto se hace en dos pasos, ambos descritos en este artículo. Una vez que vinculas un producto, cada compra o renovación otorga créditos automáticamente. ## Crear una moneda virtual \{#create-a-virtual-currency\} En el Adapty Dashboard, abre **Products** > [**Virtual currency**](https://app.adapty.io/virtual-currency). Para crear una moneda virtual: 1. Haz clic en **New virtual currency**. El panel se abre en el paso **General**. 2. Rellena los detalles de la moneda: - **Code**: El identificador único y **permanente** de la moneda dentro de tu aplicación, por ejemplo, `COINS`. Identifica la moneda en la API del lado del servidor y realiza un seguimiento del saldo de cada usuario. Utiliza letras latinas, dígitos y guiones bajos, hasta 32 caracteres. Las letras minúsculas se convierten automáticamente a mayúsculas. - **Name**: El nombre visible, como `Gold`. Puedes cambiarlo más adelante. - **Description**: Una nota opcional sobre la moneda. 3. Haz clic en **Continue** para pasar al paso **Link Products**. :::important No puedes cambiar el **Code** de la moneda una vez creada. Sin embargo, puedes editar el **Name** en cualquier momento. ::: Cuando introduces una nueva moneda, el saldo inicial de cada perfil de usuario es 0. ## Vincular productos \{#link-products\} Vincula un producto a una moneda virtual para que una compra o renovación otorgue créditos en esa moneda. Puedes vincular un producto a varias monedas y una moneda a varios productos. Puedes vincular productos ahora, durante el paso **Link Products**, o añadirlos más tarde al editar la moneda. Durante el paso **Link Products**, la sección **Associated products** muestra los productos que otorgan esta moneda. Para añadir uno, haz clic en **Add associated products** y selecciona un producto. Cada concesión se suma al saldo del usuario en lugar de reemplazarlo, por lo que los créditos de distintos productos se acumulan. La configuración de créditos depende del tipo de producto: - **Los productos de suscripción** tienen tres configuraciones de créditos: - **Credit per cycle** (obligatorio): Los créditos que se otorgan en cada renovación, incluida la que convierte una prueba gratuita en pago. - **Credit on trial start** (opcional): Una cantidad única y separada que se otorga cuando comienza una prueba gratuita. - **Credits expire at the end of each billing cycle** (interruptor): Cuando está activado, Adapty restablece los créditos no utilizados a 0 en cada renovación, antes de conceder los créditos del nuevo ciclo. Cuando está desactivado, los créditos se acumulan entre ciclos y nunca caducan. - **Productos de compra única**: Define el **Credit amount**, es decir, el número de créditos que se otorgan por cada compra. Los créditos de compras únicas nunca caducan. Haz clic en **Save** para crear la moneda con sus vínculos de producto. Los vínculos de producto solo se aplican a partir de ese momento. Un producto recién vinculado otorga créditos desde su próxima compra o renovación. Adapty no concede automáticamente la moneda a los suscriptores actuales — primero tienen que renovar la suscripción. Al eliminar un vínculo, los futuros otorgamientos se detienen, pero los créditos ya concedidos se mantienen. Cuando el interruptor de expiración está activado, los créditos con fecha de caducidad siguen el ciclo de vida de la suscripción: - Durante el reintento de cobro o el período de gracia, Adapty no otorga nuevos créditos. - Tras una cancelación, los créditos se mantienen hasta el final del período pagado. - Cuando la suscripción expira, Adapty restablece los créditos restantes a 0. ## Pasos siguientes \{#next-steps\} Una vez que hayas creado una moneda virtual y vinculado productos, las compras otorgarán créditos automáticamente. A partir de aquí: - Consulta el saldo de cada usuario en la página [Virtual currency balance](virtual-currency-balance). - Otorga, gasta y lee saldos en tiempo de ejecución a través de la API del servidor: [Create virtual currency transaction](api-adapty/operations/createVirtualCurrencyTransaction) y [List virtual currency balances](api-adapty/operations/listVirtualCurrencyBalances). Para un ejemplo práctico, sigue la [Virtual currency quickstart](virtual-currency-quickstart). --- # File: virtual-currency-balance --- --- title: "Saldo de moneda virtual" description: "Consulta el saldo de monedas virtuales de un usuario en su perfil y revisa cada cambio de saldo en el historial de eventos." --- :::link Artículo principal: [Monedas virtuales](virtual-currencies) ::: Cada perfil de usuario en [Profiles/CRM](profiles-crm) muestra el saldo de monedas virtuales de ese usuario, junto con un historial de cada cambio. ## Ver saldos en el perfil de un usuario \{#view-balances-in-a-user-profile\} Abre el [perfil](https://app.adapty.io/profiles/users) del usuario. La tarjeta **Virtual currency** muestra todas las monedas que tiene el usuario. Cada fila muestra: - El **código** de la moneda, por ejemplo `COINS`. - El **saldo** actual, como número entero. Los saldos se actualizan a medida que el usuario gana, gasta o recibe créditos. Un saldo no puede bajar de 0: si una transacción intenta gastar más de lo que el usuario tiene, falla con `insufficient_balance` y no cambia nada. Un perfil sin saldos muestra una tarjeta vacía. Para leer un saldo desde tu propio código, llama a [List virtual currency balances](api-adapty/operations/listVirtualCurrencyBalances) en la API del lado del servidor. ## Revisar cambios de saldo en el historial de eventos \{#review-balance-changes-in-the-event-history\} Cada cambio de saldo aparece en el historial de eventos del perfil, del más reciente al más antiguo. Cada entrada indica el tipo de cambio y las monedas que se vieron afectadas. El historial muestra tres tipos de eventos de moneda virtual: - **Virtual currency credited**: una compra, renovación o inicio de prueba otorgó créditos. - **Virtual currency transaction**: una llamada a la API del servidor acreditó o debitó el saldo. - **Virtual currency expired**: los créditos con vencimiento se reiniciaron al final de un ciclo de facturación o cuando terminó la suscripción. Cada entrada muestra, para cada moneda modificada: - **Moneda virtual**: El nombre y código de la moneda, como `Gold Coins (COINS)`. - **Importe**: El cambio con signo — positivo para un abono, negativo para un cargo. - **Saldo tras el cambio**: El saldo de la moneda una vez aplicado el cambio. Un evento puede cambiar varias monedas a la vez; por ejemplo, una sola [transacción](api-adapty/operations/createVirtualCurrencyTransaction) que carga una moneda y abona otra para convertir entre ellas. ## Saldos, perfiles y dispositivos \{#balances-profiles-and-devices\} Un saldo siempre pertenece a exactamente un [perfil](profiles-crm) y nunca se transfiere a otro. Adapty no comparte ni transfiere saldos entre perfiles de la misma manera que puede [compartir niveles de acceso entre cuentas de usuario](profiles-crm#sharing-paid-access-between-user-accounts). En la práctica, esto significa: - **Usuarios identificados**: Un ID de usuario siempre se resuelve en el mismo perfil, por lo que el usuario ve el mismo saldo en todos los dispositivos en los que inicie sesión. - **Usuarios anónimos**: Un perfil anónimo puede acumular y gastar créditos, pero solo existe en un dispositivo. Cuando el mismo usuario abre tu app en otro dispositivo sin iniciar sesión, Adapty crea un nuevo perfil anónimo con saldo cero. Para gestionar una economía de moneda virtual entre dispositivos, identifica a tus usuarios. Puedes identificarlos en el código de la app a través del SDK (consulta [Identificar usuarios](ios-quickstart-identify) en la guía de inicio rápido) o desde tu backend a través de la [API del lado del servidor](getting-started-with-server-side-api). Identificar a un usuario tarde tiene su propio inconveniente: si un usuario acumula créditos de forma anónima y luego inicia sesión con un customer user ID que ya pertenece a otro perfil, el dispositivo cambia a ese perfil y su saldo. Los créditos acumulados de forma anónima se quedan en el perfil anterior y dejan de ser accesibles. Si el customer user ID es nuevo, se asocia al perfil actual y el usuario conserva el saldo. Para mayor seguridad, identifica a los usuarios antes de que puedan acumular o comprar créditos. En las llamadas a la [API de servidor](getting-started-with-server-side-api), identifica el perfil con el encabezado `adapty-profile-id` o `adapty-customer-user-id` — ambos resuelven a un único perfil. Para perfiles anónimos, usa `adapty-profile-id`. --- # File: app-store-offers --- --- title: "Ofertas en App Store" description: "Configura y gestiona las ofertas de App Store para aumentar la retención de usuarios." --- :::info Configura tus [productos de la store](quickstart-products) antes de seguir esta guía. ::: Las ofertas en App Store son promociones especiales, periodos de prueba o descuentos para suscripciones de renovación automática. Incluyen descuentos y ofertas combinadas que ayudan a atraer nuevos usuarios y aumentar la conversión. Hay cuatro tipos de ofertas en el App Store, y Adapty los admite todos: - **[Ofertas introductorias](#introductory-offers) para nuevos usuarios**: - Períodos de suscripción gratuitos o con descuento - Solo los nuevos usuarios son elegibles (aquellos que nunca han activado una oferta introductoria ni han tenido una suscripción) - No necesitas vincularlas a productos en Adapty. Adapty aplica las ofertas automáticamente para los usuarios elegibles que compren el producto. - **Ofertas [promocionales](#promotional-offers) y de [recuperación](#win-back-offers)**: - Adapty aplica estas ofertas automáticamente en el momento de la compra, pero primero debes configurar las ofertas en tus productos y paywalls. - Las ofertas promocionales incluyen períodos de suscripción gratuitos, descuentos por porcentaje y descuentos de precio fijo. Cualquier usuario puede ser elegible. - Las ofertas de recuperación incluyen períodos de suscripción gratuitos o descuentos por porcentaje. Solo los usuarios que han abandonado la suscripción son elegibles. - **Códigos de oferta**: Para más información, consulta [Canjear códigos de oferta en iOS](making-purchases#redeem-offer-codes-in-ios). :::important Para usar las ofertas de App Store, sube tu [clave de suscripción](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers) al Adapty Dashboard. ::: ## Ofertas introductorias \{#introductory-offers\} Adapty aplica automáticamente las ofertas introductorias en iOS si el usuario cumple los requisitos. Para habilitar ofertas introductorias en los productos que vendes, solo necesitas crearlas en App Store Connect: 1. Abre tu app en App Store Connect y ve a **Monetization > Subscriptions**. 2. Selecciona un grupo de suscripciones y navega hasta la suscripción que necesitas. La suscripción debe tener una duración configurada. 3. Haz clic en **View all Subscription Pricing** y cambia a la pestaña **Introductory offers**. Haz clic en **Set up introductory offer**. 4. Selecciona los países y regiones donde estará disponible la oferta introductoria. 5. Selecciona las fechas de inicio y fin de la oferta introductoria. Si la oferta introductoria no tiene una fecha de fin específica, selecciona **No end date**. Haz clic en **Next**. 6. Selecciona el tipo de oferta introductoria. Según lo que elijas, también tendrás que definir la duración y el precio de la oferta. Consulta más detalles en la [documentación de Apple](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-introductory-offers-for-auto-renewable-subscriptions). 7. Revisa tu selección y haz clic en **Confirm**. A medida que completas esta configuración, no necesitas hacer nada más en Adapty. La oferta se activa automáticamente para los usuarios elegibles que compren el producto. Asegúrate de mostrar un paywall con este producto solo a los usuarios que sean elegibles para la oferta. ## Ofertas promocionales Adapty aplica automáticamente las ofertas promocionales cuando los usuarios son elegibles. Primero configura tus ofertas en App Store Connect y luego añádelas a un producto y paywall en Adapty: 1. Abre tu app en App Store Connect y selecciona **Monetization > Subscriptions** en el menú lateral. 2. Selecciona un grupo de suscripciones y navega hasta la suscripción que necesitas. La suscripción debe tener una duración configurada. 3. Haz clic en **View all Subscription Pricing** y cambia a la pestaña **Promotional offers**. Haz clic en **Set up promotional offer**. 4. Configura los detalles de la oferta promocional. Estos valores no se pueden cambiar después de crearla y se reutilizarán, así que elige con cuidado. - **Promotional offer reference name**: El nombre de la oferta promocional. No será visible para tus usuarios. - **Promotional offer identifier**: El código de identificación de la oferta promocional. Lo usarás para añadir la oferta a Adapty. 5. Selecciona el tipo de oferta promocional. El tipo determina si los usuarios pagan un precio reducido o disfrutan de un período gratuito. Para un descuento, selecciona **Pay as you go** o **Pay up front**. Para un período de suscripción gratuito, selecciona **Free**. A continuación, establece la duración y el precio de la oferta. Consulta más información en la [documentación de Apple](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-promotional-offers-for-auto-renewable-subscriptions). 6. Si es necesario, establece precios diferentes para distintos países y regiones y haz clic en **Next**. 7. Revisa tu elección y haz clic en **Confirm**. 8. [Añade la oferta promocional](create-offer) a Adapty. ## Ofertas de recuperación \{#win-back-offers\} :::important Antes de crear una oferta de recuperación, tu suscripción debe estar aprobada por App Review. ::: Adapty aplica automáticamente las ofertas de recuperación cuando los usuarios son elegibles. Configura tus ofertas primero en App Store Connect y luego añádelas a un producto y paywall en Adapty: 1. Abre tu app en App Store Connect y ve a **Monetization > Subscriptions** en el menú de la izquierda. 2. Selecciona un grupo de suscripciones y navega hasta la suscripción que necesites. La suscripción debe tener una duración configurada. 3. Haz clic en **View all Subscription Pricing** y ve a la pestaña **Win-back offers**. Haz clic en **Create offer**. 4. Configura los detalles de la oferta de recuperación. Estos valores no se pueden cambiar después de crearla. - **Reference name**: el nombre de la oferta. Tus usuarios no lo verán. - **Offer identifier**: el código de identificación de la oferta. Lo usarás para añadir la oferta a Adapty. 5. Configura el tipo de oferta, la duración y el precio. Consulta más detalles en la [documentación de Apple](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-win-back-offers). 6. Revisa tu elección y haz clic en **Confirm**. 7. [Añade la oferta](create-offer) a Adapty. ## Pasos siguientes \{#next-steps\} Después de añadir las ofertas, continúa con la configuración: - Si también tienes **apps en Google Play**, configura las [ofertas de Google Play](google-play-offers). - Si tienes **ofertas promocionales o de recuperación**, [añádelas a Adapty](create-offer). - Si solo tienes **ofertas introductorias** y no tienes ofertas promocionales ni de recuperación, ya has terminado. Estas secciones pueden seguir siendo útiles: - [Trabajar con ofertas en el Paywall Builder de Adapty](create-offer#paywall-builder) - [Cómo funciona Adapty con las ofertas](create-offer#how-adapty-works-with-offers) --- # File: google-play-offers --- --- title: "Ofertas en Google Play" description: "Configura las ofertas de Google Play para mejorar la monetización y la retención de tu app." --- En Google Play, las ofertas de cualquier tipo (períodos de prueba gratuitos o pagos con descuento) se añaden como **offers**. Para crear una oferta, primero debes crear una suscripción y añadir un plan base de renovación automática. Las ofertas siempre se crean para planes base dentro de suscripciones. En la captura de pantalla a continuación, puedes ver una suscripción `premium_access`(1) con dos planes base: `1-month` (2) y `1-year` (3). <img src="/assets/shared/img/c0b1dfa-001930-November-03-XYnbieeu.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Para crear una oferta en Google Play Console: 1. Haz clic en **Add offer** y elige el plan base de la lista. <img src="/assets/shared/img/75a5d69-eb0bc9a-001931-November-03-eQdthUMx.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Introduce el ID de la oferta. Se usará más adelante en los análisis y en el Adapty Dashboard, así que dale un nombre descriptivo. <img src="/assets/shared/img/ff282c2-c0b1dfa-001930-November-03-XYnbieeu.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Elige los criterios de elegibilidad: 1. **New customer acquisition**: la oferta estará disponible solo para nuevos suscriptores que no la hayan utilizado antes. Es la opción más habitual y la que deberías usar por defecto. 2. **Upgrade**: esta oferta estará disponible para los clientes que actualicen desde otra suscripción. Úsala cuando quieras promocionar planes más caros a tus suscriptores actuales; por ejemplo, clientes que pasan del nivel bronce al nivel oro de tu suscripción. 3. **Developer determined**: puedes controlar quién puede usar esta oferta desde el código de la app. Úsala con precaución en producción para evitar posibles fraudes: los clientes podrían activar una suscripción gratuita o con descuento una y otra vez. Un buen caso de uso para este tipo de oferta es recuperar suscriptores que han cancelado. <img src="/assets/shared/img/ee302dc-a506e5a-001934-November-03-TVBLOz2L.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Añade hasta dos fases de precio a tu oferta. Hay tres tipos de fase disponibles: 1. **Free trial**: la suscripción se puede usar gratis durante un período de tiempo configurado (mínimo 3 días). Es la oferta más habitual. 2. **Single payment**: la suscripción es más barata si los clientes pagan por adelantado. Por ejemplo, normalmente un plan mensual cuesta 9,99 $, pero con este tipo de oferta, los primeros tres meses cuestan 19,99 $, un descuento del 30%. 3. **Discounted recurring payment**: la suscripción es más barata durante los primeros `n` períodos. Por ejemplo, normalmente un plan mensual cuesta 9,99 $, pero con este tipo de oferta, cada uno de los primeros tres meses cuesta 4,99 $, un descuento del 50%. Una oferta puede tener dos fases. En ese caso, la primera fase debe ser un Free trial y la segunda puede ser un Single payment o un Discounted recurring payment. Se aplicarán en ese orden. <img src="/assets/shared/img/d6267f3-a48f79e-001936-November-03-A13wutRh.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Ten en cuenta que los paywalls creados con el Adapty Paywall Builder mostrarán únicamente la primera fase de una oferta de suscripción de Google con varias fases. No obstante, cuando un usuario compre el producto, todas las fases de la oferta se aplicarán tal como están configuradas en Google Play. ::: 5. Activa la oferta para usarla en la app. <img src="/assets/shared/img/d3fc09b-f149ba6-001937-November-03-MO9Gz3ap.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Continúa con [cómo añadir la oferta a Adapty](create-offer). :::note Los IDs de oferta pueden ser iguales para distintos planes base. ::: ## Pasos siguientes \{#next-steps\} Una vez añadidas las ofertas, continúa con la configuración: - Si también tienes **apps en App Store**, consulta la [guía de App Store](app-store-offers). - Si tienes **apps solo en Google Play**, sigue [esta guía](create-offer) para añadir ofertas a Adapty. --- # File: create-offer --- --- title: "Añadir ofertas a Adapty" description: "Crea y gestiona ofertas especiales de suscripción con las herramientas de Adapty." --- Adapty te permite ofrecer trials o descuentos a suscriptores nuevos, actuales o que han abandonado. Una vez que los hayas configurado en App Store Connect o Google Play Console, necesitas añadirlos a Adapty en dos pasos: 1. [Añade las ofertas a los productos en Adapty usando los IDs de oferta de los stores.](#1-create-offer) 2. [Muestra la oferta en un flow o paywall.](#2-display-offer) :::warning Las ofertas introductorias (App Store) se aplican automáticamente si el usuario es elegible. No las añadas a los productos en Adapty. Esta guía explica cómo configurar ofertas promocionales (App Store), ofertas de recuperación (App Store) y todas las ofertas de Google Play. ::: ## 0. Antes de empezar \{#before-you-start\} Antes de configurar ofertas en Adapty, asegúrate de lo siguiente: 1. Has creado todas las ofertas que necesitas en el store: - [App Store](app-store-offers) - [Google Play](google-play-offers) 2. Has creado los [productos](create-product) en Adapty y has añadido sus IDs. 3. Para App Store: has subido [la clave de compra in-app para ofertas promocionales](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers). ## 1. Añade la oferta al producto en Adapty \{#1-add-offer-to-product-in-adapty\} Una vez que hayas configurado tu oferta promocional (tanto para Play Store como para App Store) u oferta de recuperación (para App Store) en los stores, añadirla a Adapty es sencillo: 1. Abre [**Products**](https://app.adapty.io/products) desde el menú principal de Adapty. Localiza el producto al que quieres añadir una oferta. 2. Encuentra el producto al que quieres añadir una oferta. En la columna **Actions**, haz clic en el botón de **3 puntos** junto al producto y selecciona **Edit**. 3. En la ventana **Edit product**, haz clic en **+** y selecciona **Add offers**. 4. Haz clic en **Add offer**. 5. A continuación, introduce los detalles de la oferta para el producto. Estos son los campos de la oferta: - **Offer name**: Asigna un nombre a la oferta para identificarla fácilmente en Adapty. Usa el nombre que más te convenga. - **App Store Offer type**: Selecciona el tipo de oferta de App Store que estás añadiendo: Promotional o Win-back. (Las ofertas introductorias no es necesario añadirlas, ya que se aplican automáticamente si están disponibles.) - **App Store Offer ID**: Es el ID único de la oferta [que configuraste en App Store](app-store-products). - **Play Store Offer ID**: De igual forma, es el ID único de la oferta [que configuraste en Play Store](android-products). :::tip Si el campo **App Store Offer ID** o **Play Store Offer ID** no está activo, cambia a la pestaña **Products** y selecciona un ID de producto. ::: 6. (opcional) Añade más ofertas si es necesario haciendo clic en **Add offer**. 7. Haz clic en **Save** para añadir las ofertas al producto. ## 2. Mostrar la oferta \{#2-display-offer\} Una vez que la oferta está vinculada a un producto, muéstrala donde los usuarios ven ese producto: en un flow o en un paywall. ### Añadir una oferta a un flow \{#add-offer-to-flow\} En el [Flow Builder](adapty-flow-builder), una oferta se asocia a un producto dentro del elemento Products. Primero añade el elemento de productos y asígnales productos — consulta [Configurar compras](paywall-product-block). Para asociar una oferta: 1. En el canvas, selecciona la tarjeta de producto que debe mostrar la oferta. 2. En el panel derecho, en **Product**, selecciona el producto y luego elige la oferta en el desplegable **Select offer (optional)**. ### Añadir una oferta a un paywall \{#add-offer-to-paywall\} :::info No puedes añadir ofertas a paywalls en estado **live**. Si quieres añadir una oferta a un paywall existente, [duplícalo](duplicate-paywalls) y configura los productos en el nuevo paywall. ::: Para que una oferta sea visible y seleccionable dentro de un [paywall](paywalls) para los usuarios de tu app, sigue estos pasos: 1. Al crear o editar un paywall, en la pestaña **General**, añade el producto al que acabas de añadir la oferta. 2. Elige la oferta que creaste anteriormente para este producto en la lista **Offer**. La lista solo está disponible para los productos que tienen ofertas. 3. Si lo necesitas, añade más productos y ofertas, pero solo puedes añadir una oferta por producto. ## Cómo funciona Adapty con las ofertas \{#how-adapty-works-with-offers\} Ten en cuenta lo siguiente sobre cómo funcionan las ofertas en Adapty: - Cuando un usuario es elegible para una oferta, Adapty aplica automáticamente la oferta que hayas configurado cuando el usuario realiza una compra. - Si un producto tiene configurada tanto una oferta introductoria como ofertas promocionales en el App Store, los usuarios elegibles recibirán primero la oferta introductoria. Una vez finalizado su período, si el usuario todavía es elegible para la oferta promocional y la has configurado en Adapty, se aplicará cuando intente comprar el producto de nuevo. - Si quieres tener más control sobre cómo se aplican las ofertas o necesitas vender tu producto sin ofertas en determinados casos, tienes varias opciones: - Configura los criterios de elegibilidad en el App Store o en Google Play Console - Crea un producto separado sin ofertas en el App Store o en Google Play Console - Crea un producto separado sin ofertas en Adapty, añade paywalls que contengan ambas variantes del producto a un [placement](placements) y usa [segmentos](segments) de audiencia para controlar qué paywall se muestra a cada usuario. Por ejemplo, puedes crear segmentos basados en el **Subscription product** o en el **Paid access level**, o usar [atributos personalizados](profiles-crm) para implementar tu propia lógica. --- # File: create-access-level --- --- title: "Crear nivel de acceso" description: "Crea y asigna niveles de acceso en Adapty para una mejor segmentación de usuarios." --- Los niveles de acceso te permiten controlar lo que los usuarios de tu app pueden hacer sin necesidad de codificar IDs de productos específicos. Cada producto define cuánto tiempo obtiene el usuario un determinado nivel de acceso. Así, cuando un usuario realiza una compra, Adapty concede acceso a la app durante un período específico (para suscripciones) o de forma permanente (para compras de por vida). Cuando creas una app en el Adapty Dashboard, el nivel de acceso `premium` se genera automáticamente. Este es el nivel de acceso predeterminado y no se puede eliminar. :::tip También puedes crear niveles de acceso de forma programática usando el [Developer CLI](developer-cli-reference#adapty-access-levels-create). ::: Para crear un nuevo nivel de acceso: 1. Ve a **[Products](https://app.adapty.io/access-levels)** desde el menú principal de Adapty y selecciona la pestaña **Access levels**. <img src="/assets/shared/img/access-level-list.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en **Create access level**. <img src="/assets/shared/img/b8646ca-image.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la ventana **Create access level**, asígnale un ID. Este ID servirá como identificador dentro de tu app, permitiendo el acceso a funciones adicionales cuando el usuario realice una compra. Además, este identificador ayuda a distinguir un nivel de acceso de otros dentro de la app. Asegúrate de que sea claro y fácil de entender para tu comodidad. 4. Haz clic en **Create access level** para confirmar la creación del nivel de acceso. --- # File: assigning-access-level-to-a-product --- --- title: "Asignar nivel de acceso a un producto" description: "Asigna niveles de acceso a los productos para optimizar la gestión de suscripciones." --- Cada [producto](product) necesita un nivel de acceso asociado para garantizar que los usuarios reciban el contenido restringido correspondiente tras la compra. Adapty determina automáticamente la duración de la suscripción, que sirve como fecha de expiración del nivel de acceso. En el caso de un producto de acceso de por vida, si un usuario lo compra, el nivel de acceso permanece activo indefinidamente sin fecha de expiración. Para vincular un nivel de acceso a un producto: 1. Al [configurar un producto](create-product), selecciona el nivel de acceso en la lista **Access Level ID**. <img src="/assets/shared/img/access-level-product.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en **Save**. --- # File: give-access-level-to-specific-customer --- --- title: "Asignar nivel de acceso a un cliente específico" description: "Asigna niveles de acceso específicos a clientes usando las herramientas avanzadas de Adapty." --- Puedes ajustar manualmente el nivel de acceso de un cliente concreto directamente desde el Adapty Dashboard. Esto resulta muy útil en escenarios de soporte. Por ejemplo, si quieres ampliar el uso premium de un usuario una semana extra como agradecimiento por haber dejado una reseña estupenda. ## Asignar nivel de acceso a un cliente específico en el Adapty Dashboard \{#give-access-level-to-a-specific-customer-in-the-adapty-dashboard\} 1. Ve a **[Profiles and Segments](https://app.adapty.io/placements)** desde el menú principal de Adapty. <img src="/assets/shared/img/profiles-list.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el cliente al que quieres conceder acceso. 3. Haz clic en **Add access level**. <img src="/assets/shared/img/add-access-level.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Selecciona el nivel de acceso que quieres conceder y cuándo debe expirar para ese cliente. 5. Haz clic en **Apply**. ## Asignar nivel de acceso a un cliente específico mediante la API \{#give-access-level-to-a-specific-customer-via-api\} También puedes conceder un nivel de acceso a un cliente desde tu servidor usando la API de Adapty. Esto es muy útil si tienes bonificaciones por referidos u otros eventos relacionados con tus productos. Consulta más detalles en la página [Conceder nivel de acceso con la API del servidor](api-adapty/operations/grantAccessLevel). --- # File: local-access-levels --- --- title: "Niveles de acceso locales" description: "Gestiona los niveles de acceso en caso de interrupciones temporales." --- :::important Ten en cuenta lo siguiente: - Los niveles de acceso locales son compatibles con el SDK de Adapty a partir de la versión 3.12. - Por defecto, los niveles de acceso locales están desactivados en Android por seguridad adicional. Si los necesitas, actívalos durante la inicialización del SDK: [Android](sdk-installation-android#enable-local-access-levels), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#enable-local-access-levels-android). ::: Cada producto que configuras tiene un [**nivel de acceso**](access-level) asociado. Cuando tus usuarios realizan una compra, el SDK de Adapty asigna el nivel de acceso al [perfil](profiles-crm) del usuario, por lo que debes usar este nivel de acceso para determinar si los usuarios pueden acceder al contenido de pago en la app. El SDK de Adapty es muy fiable y es muy raro que sus servidores no estén disponibles. Sin embargo, incluso en ese caso excepcional, tus usuarios no lo notarán. Si un usuario realiza una compra pero Adapty no puede recibir respuesta, el SDK pasa a verificar las compras directamente en el store. Por tanto, el nivel de acceso se concede de forma local en la app y no se necesita ninguna configuración adicional para activarlo. El SDK lo gestiona automáticamente en segundo plano, y los usuarios accederán a lo que han pagado con total normalidad. Ten en cuenta lo siguiente sobre cómo funcionan los niveles de acceso locales: - Cuando los usuarios vuelven a estar en línea, la información de las transacciones se envía automáticamente a los servidores de Adapty, que aplican las transacciones al perfil del usuario y devuelven el perfil actualizado al SDK. - Los datos actualizados no aparecerán en los análisis de Adapty hasta que se envíen los datos. - Los niveles de acceso locales solo funcionan cuando los servidores de Adapty están caídos. En caso contrario, el SDK utilizará los datos en caché. - Los niveles de acceso locales no funcionan con productos consumibles, excepto cuando un producto consumible tiene asignado un tipo de suscripción (mensual, anual, semanal, etc.) en el dashboard de Adapty. --- # File: choose-meaningful-placements --- --- title: "Elige placements significativos" description: "Optimiza los placements de flows y paywalls con Adapty para aumentar la interacción con los usuarios y los ingresos." --- Cuando [creas placements](create-placement), es fundamental tener en cuenta el flujo lógico de tu app y la experiencia de usuario que quieres ofrecer. La mayoría de las apps no necesitan más de 5 [placements](placements) para poder ejecutar experimentos sin restricciones. Aquí tienes un ejemplo de cómo puedes estructurar tus placements: <img src="/assets/shared/img/placement-flows.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. **Flow de onboarding:** Esta etapa representa la primera interacción de tus usuarios con tu app. Es una oportunidad excelente para presentarles la propuesta de valor combinando placements de flow, onboarding y paywall. Más del 80% de las suscripciones se activan durante el proceso de onboarding, por lo que es importante centrarse en vender las suscripciones más rentables aquí. Con Adapty, puedes tener fácilmente diferentes [flows](adapty-flow-builder), [onboardings](onboardings) y [paywalls](paywalls) para distintas audiencias, y ejecutar pruebas A/B para encontrar la mejor opción para tu app. Por ejemplo, puedes ejecutar una prueba A/B para usuarios de EE. UU., mostrando suscripciones más caras el 50% del tiempo. 2. **Configuración de la app:** Si el usuario no se ha suscrito durante el proceso de onboarding, puedes crear un placement de flow o paywall dentro de tu app. Puede estar en la configuración de la app o tras completar una acción objetivo específica. Como los usuarios dentro de la app tienden a pensárselo más antes de suscribirse, los productos aquí pueden ser algo menos costosos que los de la etapa de onboarding. 3. **Promo:** Si el usuario no se ha suscrito después de ver el flow o el paywall varias veces, puede indicar que los precios son demasiado altos para él o que tiene dudas sobre las suscripciones. En ese caso, puedes mostrarle una oferta especial con la suscripción más asequible o incluso un producto de acceso de por vida. Esto puede ayudar a convencer a los usuarios sensibles al precio o escépticos ante las suscripciones para que realicen una compra. La mayoría de las apps siguen una lógica y unos placements similares, que acompañan el recorrido del usuario y los puntos clave donde se pueden mostrar flows, paywalls, onboardings o pruebas A/B para impulsar conversiones e ingresos. Puedes configurarlos en cada placement para experimentar y optimizar tus estrategias de monetización. --- # File: create-placement --- --- title: "Crear un placement" description: "Crea y gestiona placements en Adapty para mejorar el rendimiento de flows y paywalls." --- Un [Placement](placements) es una ubicación específica dentro de tu app móvil donde puedes mostrar un flow, un paywall, un onboarding o una prueba A/B. Por ejemplo, una pantalla de elección de suscripción puede aparecer en el flow de inicio, mientras que un producto consumible (como monedas de oro) podría mostrarse cuando al usuario se le acaben las monedas en un juego. Puedes mostrar los mismos o diferentes flows, paywalls, onboardings o pruebas A/B en distintos placements o para diferentes segmentos de usuarios, que en Adapty se denominan "audiencias". Consulta la sección [Elige placements con sentido](choose-meaningful-placements) para ver consejos sobre cómo elegir el placement adecuado. :::tip También puedes crear placements mediante programación usando la [CLI para desarrolladores](developer-cli-reference#adapty-placements-create). ::: :::info Aunque el proceso de creación de placements es similar para flows, paywalls y onboardings, no puedes crear un mismo placement que sirva para más de un tipo: cada tipo de placement procesa métricas diferentes. ::: ## Crear y configurar un placement \{#create-and-configure-a-placement\} 1. Ve a **[Placements](https://app.adapty.io/placements)** desde el menú principal de Adapty. Cambia a la pestaña **Flows**, **Paywalls** o **Onboardings** según el tipo de placement que quieras crear. 2. Haz clic en **Create placement**. <img src="/assets/shared/img/create-placement-2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Escribe un **Placement name**. Es un identificador interno en el Adapty Dashboard. Puedes editarlo más adelante si lo necesitas. 4. Escribe un **Placement ID**. Usarás este ID en el SDK de Adapty para llamar a los [flows](adapty-flow-builder), [paywalls](paywalls), [onboardings](onboardings) y [pruebas A/B](ab-tests) del placement. No puedes editarlo después, ya que es único para cada placement. A continuación, asigna un flow, paywall, onboarding o prueba A/B al placement. Adapty admite [audiencias](audience) — segmentos de usuarios basados en [segmentos](segments) — para que puedas mostrar contenido diferente a grupos de usuarios distintos. Si no necesitas segmentación, la audiencia predeterminada *All users* cubre a todos. :::note Para continuar, asegúrate de haber creado un flow, paywall, onboarding o prueba A/B que quieras ejecutar, así como una audiencia que quieras especificar. ::: 1. En la ventana **Placements/ Your placement**, añade un flow, paywall, onboarding o prueba A/B para mostrar a la audiencia predeterminada *All users*. Para ello, haz clic en el botón **Run flow**, **Run paywall** o **Run A/B test** (la etiqueta depende del tipo de placement) y selecciona el flow, paywall, onboarding o prueba A/B deseado en la lista desplegable. 2. Si quieres usar más de una audiencia en el placement para crear contenido personalizado adaptado a distintos grupos de usuarios, haz clic en el botón **Add audience** y elige el segmento de usuarios deseado de la lista. <Zoom> <img src="/docs/img/placement-add-audience.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Ahora añade el flow, paywall, onboarding o prueba A/B que se mostrará a esta audiencia. 4. Añade tantas audiencias como necesites. 5. Si tienes más de una audiencia, comprueba que tienen las prioridades correctas. 6. Haz clic en el botón **Save and publish**. Una vez que tu placement esté guardado y publicado, tienes todo lo necesario: usa el **Placement ID** en el código de tu app para obtenerlo y mostrarlo. ## Próximos pasos \{#next-steps\} Muestra paywalls en tu app: [iOS](ios-present-paywalls) | [Android](android-present-paywalls) | [React Native](react-native-present-paywalls) | [Flutter](flutter-present-paywalls) | [Unity](unity-present-paywalls) | [Kotlin Multiplatform](kmp-present-paywalls) | [Capacitor](capacitor-present-paywalls) Mostrar onboardings en tu app: [iOS](ios-present-onboardings) | [Android](android-present-onboardings) | [React Native](react-native-present-onboardings) | [Flutter](flutter-present-onboardings) | [Unity](unity-present-onboardings) | [Kotlin Multiplatform](kmp-present-onboardings) | [Capacitor](capacitor-present-onboardings) --- # File: edit-placement --- --- title: "Editar placement" description: "Aprende a editar placements en Adapty para optimizar la visibilidad de flows y paywalls y mejorar la interacción con los usuarios." --- Un [Placement](placements) designa una ubicación específica dentro de tu aplicación móvil donde se puede mostrar un flow, un paywall, un onboarding o una prueba A/B. Por ejemplo, una opción de suscripción puede aparecer en un flow de inicio, mientras que un producto consumible (como monedas de oro) podría mostrarse cuando al usuario se le acaban las monedas en un juego. Tienes la flexibilidad de mostrar los mismos o diferentes flows, paywalls, onboardings o pruebas A/B en varios placements o segmentos de usuarios, llamados audiencias en Adapty. Para editar un placement existente: 1. Ve a **[Placements](https://app.adapty.io/placements)** desde el menú principal de Adapty. Cambia a la pestaña **Flows**, **Paywalls** u **Onboardings** según el tipo de placement que quieras editar. 2. Haz clic en el placement que quieras editar. 3. Haz clic en **Edit placement** en la parte superior derecha. 4. Realiza los cambios que necesites. Para más detalles sobre las opciones de esta ventana, consulta la sección [Crear placement](create-placement). 5. Haz clic en el botón **Save and publish** para confirmar los cambios. --- # File: export-placements --- --- title: "Exportar placement" description: "Aprende cómo exportar placements en Adapty para optimizar la visibilidad de flows y paywalls y la interacción con los usuarios." --- Cuando trabajas con varios flows, paywalls y onboardings, es importante saber cuáles se muestran a cada usuario. Puedes exportar todos los ajustes de [placement](placements) a un archivo CSV para ver qué flow/paywall/onboarding aparece para cada audiencia y revisar tu configuración tras realizar cambios o ejecutar experimentos. :::tip Si te resulta más cómodo, puedes [exportar placements mediante la API server-side](api-export-analytics/operations/retrievePlacementInfo). ::: Para exportar los placements de flows, paywalls u onboardings: 1. Ve a **[Placements](https://app.adapty.io/placements)** en el menú principal. Cambia a la pestaña **Flows**, **Paywalls** u **Onboardings** — los placements de cada tipo se exportan por separado. 2. Haz clic en **Export to CSV**. <img src="/assets/shared/img/export-placement.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> El archivo CSV exportado contiene la siguiente información sobre tus placements: - ID de placement - Nombre del placement - Nombre de audiencia - Nombre de segmento - Nombre de prueba A/B entre placements - Nombre de prueba A/B - Nombre de flow, nombre de paywall o nombre de onboarding (según la pestaña desde la que hayas exportado) :::note Las pruebas A/B entre placements no son compatibles con los placements de flow, por lo que esa columna estará vacía en las exportaciones de flows. ::: --- # File: delete-placement --- --- title: "Eliminar un placement" description: "Descubre cómo eliminar un placement en Adapty sin afectar el rendimiento de tu flow o paywall." --- Un [placement](placements) designa una ubicación específica dentro de tu app móvil donde se puede mostrar un flow, paywall, onboarding o prueba A/B. :::danger Aunque tienes la opción de eliminar cualquier placement, es fundamental asegurarte de no eliminar un placement que esté en uso activo en tu app. Eliminar un placement de flow o paywall activo hará que se muestre permanentemente el paywall de respaldo local si lo has [configurado](fallback-paywalls), y no podrás reemplazarlo nunca con un flow o paywall dinámico en las versiones de la app ya publicadas. ::: Para eliminar un placement existente: 1. Ve a **[Placements](https://app.adapty.io/placements)** desde el menú principal de Adapty. Cambia a la pestaña **Flows**, **Paywalls** u **Onboardings** según el tipo de placement que quieras eliminar. 2. Haz clic en el botón de **3 puntos** junto al placement y selecciona la opción **Delete**. <img src="/assets/shared/img/delete-placement.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la ventana **Delete placement** que se abre, escribe el nombre del placement que vas a eliminar. <img src="/assets/shared/img/8177c51-delete_placement.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Haz clic en el botón **Delete forever** para confirmar la eliminación. --- # File: add-audience-paywall-ab-test --- --- title: "Añadir audiencia y flow, paywall o prueba A/B a un placement" description: "Ejecuta pruebas A/B en flows y paywalls para diferentes segmentos de audiencia en Adapty." --- :::note Para continuar, asegúrate de haber creado un flow, paywall, onboarding o prueba A/B que quieras ejecutar, así como una audiencia que quieras especificar. ::: 1. En la ventana **Placements/ Your placement**, añade un flow, paywall, onboarding o prueba A/B para mostrar a la audiencia predeterminada *All users*. Para ello, haz clic en el botón **Run flow**, **Run paywall** o **Run A/B test** (la etiqueta depende del tipo de placement) y selecciona el flow, paywall, onboarding o prueba A/B deseado en la lista desplegable. 2. Si quieres usar más de una audiencia en el placement para crear contenido personalizado adaptado a distintos grupos de usuarios, haz clic en el botón **Add audience** y elige el segmento de usuarios deseado de la lista. <Zoom> <img src="/docs/img/placement-add-audience.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Ahora añade el flow, paywall, onboarding o prueba A/B que se mostrará a esta audiencia. 4. Añade tantas audiencias como necesites. 5. Si tienes más de una audiencia, comprueba que tienen las prioridades correctas. 6. Haz clic en el botón **Save and publish**. Las **audiencias** en Adapty son grupos de usuarios definidos mediante [segmentos](segments). Te permiten mostrar flows, paywalls, onboardings y pruebas A/B a los usuarios que deben verlos. Crea segmentos con filtros para asegurarte de que cada grupo recibe el contenido adecuado. Cuando añades una audiencia a un [placement](placements), diriges flows, paywalls, onboardings o pruebas A/B a un grupo de usuarios concreto. Vincular una audiencia a un placement garantiza que los usuarios correctos vean el contenido correcto en el momento adecuado de su experiencia en la app. Abre el placement donde quieras añadir un flow, paywall, onboarding o prueba A/B, o crea uno nuevo desde el menú [Placements](https://app.adapty.io/placements). :::note Para continuar, asegúrate de haber creado un flow, paywall, onboarding o prueba A/B que quieras ejecutar, así como una audiencia que quieras especificar. ::: 1. En la ventana **Placements/ Your placement**, añade un flow, paywall, onboarding o prueba A/B para mostrar a la audiencia predeterminada *All users*. Para ello, haz clic en el botón **Run flow**, **Run paywall** o **Run A/B test** (la etiqueta depende del tipo de placement) y selecciona el flow, paywall, onboarding o prueba A/B deseado en la lista desplegable. 2. Si quieres usar más de una audiencia en el placement para crear contenido personalizado adaptado a distintos grupos de usuarios, haz clic en el botón **Add audience** y elige el segmento de usuarios deseado de la lista. <Zoom> <img src="/docs/img/placement-add-audience.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Ahora añade el flow, paywall, onboarding o prueba A/B que se mostrará a esta audiencia. 4. Añade tantas audiencias como necesites. 5. Si tienes más de una audiencia, comprueba que tienen las prioridades correctas. 6. Haz clic en el botón **Save and publish**. --- # File: change-audience-priority --- --- title: "Cambiar la prioridad de audiencia en un placement" description: "Ajusta las prioridades de audiencia en Adapty para dirigirte a los usuarios con ofertas personalizadas." --- Cuando tienes diferentes audiencias de usuarios en un mismo [placement](placements), un usuario puede pertenecer a más de una audiencia. Por ejemplo, si has definido audiencias como "Principiantes", "Corredores" y una audiencia general como "Todos los usuarios", es fundamental determinar qué audiencia concreta considerar primero cuando un usuario cae en varias categorías. <img src="/assets/shared/img/afee54f-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> En este caso, nos basamos en la prioridad de audiencia. La prioridad de audiencia es un orden numérico donde el n.º 1 es la más alta. Determina la secuencia en la que se comprueban las audiencias. En términos más sencillos, la prioridad de audiencia ayuda a Adapty a decidir qué audiencia aplicar primero al seleccionar el paywall, onboarding o prueba A/B que se mostrará. Si la prioridad de una audiencia es baja, los usuarios que potencialmente encajan pueden quedar excluidos y ser derivados a otra audiencia con mayor prioridad. Las audiencias multiplacement, es decir, las creadas para [pruebas A/B multiplacement](ab-tests#ab-test-types), siempre tienen prioridad sobre las audiencias normales. La audiencia "Todos los usuarios" siempre tiene la prioridad más baja, ya que es un fallback e incluye a todos los que no coinciden con ninguna otra audiencia. Para ajustar las prioridades de audiencia en un placement: 1. Al crear un nuevo placement o editar uno existente, haz clic en **Edit priority**. El botón solo es visible si se han añadido al menos tres audiencias al placement ("Todos los usuarios" y otras dos). Si hay menos, el orden es obvio: la audiencia "Todos los usuarios" va siempre al final. <img src="/assets/shared/img/edit-priority.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. En la ventana **Edit audience priorities** que se abre, arrastra y suelta las audiencias para reordenarlas correctamente. <img src="/assets/shared/img/reorder_audiences.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en el botón **Save**. --- # File: placement-metrics --- --- title: "Métricas de placement" description: "Analiza las métricas de placement en Adapty para mejorar el rendimiento de tus paywalls." --- Con Adapty, puedes crear y gestionar múltiples placements en tu app, cada uno asociado a paywalls o pruebas A/B distintas. Esta flexibilidad te permite dirigirte a segmentos de usuarios específicos, experimentar con diferentes ofertas o modelos de precios, y optimizar la estrategia de monetización de tu app. Para obtener información valiosa sobre el rendimiento de tus placements y la interacción de los usuarios con tus ofertas, Adapty registra diversas interacciones de usuarios y transacciones relacionadas con los paywalls mostrados. El sólido sistema de analíticas captura métricas como vistas, vistas únicas, compras, pruebas, reembolsos, tasas de conversión e ingresos. Las métricas recopiladas se actualizan continuamente en tiempo real y pueden consultarse y analizarse cómodamente desde el dashboard de Adapty. Puedes personalizar el rango de tiempo para el análisis, aplicar filtros basados en distintos parámetros y comparar métricas entre varios placements, segmentos de usuarios o productos. Las métricas de placement están disponibles en la lista de placements, donde puedes obtener una visión general del rendimiento de todos tus placements. Esta vista de alto nivel ofrece métricas agregadas para cada placement, lo que te permite comparar su rendimiento e identificar tendencias. Para un análisis más detallado de cada placement, puedes navegar a las métricas de detalle del placement. En esta página encontrarás métricas completas específicas del placement seleccionado. Estas métricas ofrecen una visión más profunda del rendimiento de un placement concreto, lo que te permite evaluar su efectividad y tomar decisiones basadas en datos. <img src="/assets/shared/img/3e711fc-CleanShot_2023-07-26_at_14.55.042x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Filtrar métricas por fecha de instalación \{#filter-metrics-by-install-date\} Las métricas de paywall, periodo de prueba y compra se pueden agrupar por dos tipos de fechas distintos: - **La fecha del evento**: cuando se visualizó el paywall, empezó el periodo de prueba o se realizó la compra. - **La fecha de instalación**: cuando el usuario abrió la app por primera vez. Ambas vistas pueden mostrar números muy diferentes para el mismo rango de fechas. La casilla **Filter metrics by install date** controla cuál de las dos usa el dashboard: - **Sin marcar (por defecto)**: Las métricas se agrupan por fecha del evento. - **Marcada**: Las métricas se agrupan por fecha de instalación. **Ejemplo.** Estableces el rango de fechas del 1 al 30 de abril y observas los periodos de prueba. - **Sin marcar**: Muestra los periodos de prueba que *comenzaron* en abril, independientemente de cuándo se instalaron esos usuarios. - **Marcada**: Muestra los periodos de prueba de usuarios que *instalaron* la app en abril, independientemente de cuándo empezaron sus periodos de prueba. Usa la vista por fecha de instalación para medir el rendimiento de adquisición de usuarios de una cohorte concreta. Usa la vista por fecha del evento para medir la actividad del paywall u onboarding en un periodo específico. ### Controles de métricas \{#metrics-controls\} El sistema muestra las métricas en función del período de tiempo seleccionado y las organiza según el parámetro de la columna izquierda con cuatro niveles de sangría. #### Opciones de visualización de datos de métricas \{#view-options-for-metrics-data\} La página de métricas de placement ofrece dos opciones de visualización de datos: por paywall y por audiencia. <img src="/assets/shared/img/9d26b32-Export-1690376094858.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> En la vista por paywall, las métricas se agrupan por los placements asociados al paywall. Esto permite analizar las métricas según los distintos placements. En la vista por audiencia, las métricas se agrupan por la audiencia objetivo del paywall. Los usuarios pueden evaluar las métricas específicas de cada segmento de audiencia. #### Rangos de tiempo \{#time-ranges\} Puedes elegir entre varios períodos de tiempo para analizar datos de métricas, lo que te permite centrarte en duraciones específicas como días, semanas, meses o rangos de fechas personalizados. <img src="/assets/shared/img/15d2c3e-CleanShot_2023-07-26_at_16.49.272x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### Filtros y agrupación disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: Adapty ofrece potentes herramientas para filtrar y personalizar el análisis de métricas según tus necesidades. En la página de métricas de Adapty tienes acceso a distintos rangos de tiempo, opciones de agrupación y posibilidades de filtrado. - ✅ Filtrar por: Audiencia, paywall, grupo de paywalls, placement, país, store. - ✅ Agrupar por: Segmento, store y producto #### Gráfico de métrica individual \{#single-metrics-chart\} Una de las partes principales de la página de métricas de placement es la sección de gráficos, que representa visualmente las métricas seleccionadas y facilita su análisis. La sección de gráfico en la página de métricas de placements incluye un gráfico de barras horizontales que representa visualmente los valores de la métrica seleccionada. Cada barra del gráfico corresponde a un valor de la métrica y es proporcional en tamaño, lo que facilita entender los datos de un vistazo. La línea horizontal indica el período de tiempo analizado, y la columna vertical muestra los valores numéricos de las métricas. El valor total de todos los valores de la métrica se muestra junto al gráfico. <img src="/assets/shared/img/4623c5b-Export-1690375597411.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Además, al hacer clic en el icono de flecha en la esquina superior derecha de la sección del gráfico, la vista se amplía y muestra las métricas seleccionadas en la línea completa del gráfico. #### Resumen total de métricas \{#total-metrics-summary\} Junto al gráfico de métrica individual, se muestra la sección de resumen de métricas totales, que presenta los valores acumulados de las métricas seleccionadas en un momento concreto, con la posibilidad de cambiar la métrica mostrada mediante un menú desplegable. <img src="/assets/shared/img/0f647cf-CleanShot_2023-07-26_at_14.55.492x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Definiciones de métricas \{#metrics-definitions\} Aprovecha al máximo las métricas de placement con nuestras definiciones completas. Desde los ingresos hasta las tasas de conversión, obtén información valiosa que impulsará tus estrategias de monetización y el éxito de tu app. <img src="/assets/shared/img/771a0f0-Export-1690375049771.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Adapty convierte otras divisas a USD según el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo de cambio se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: #### Ingresos \{#revenue\} Esta métrica representa el importe total en USD generado por compras y renovaciones dentro de placements específicos. Ten en cuenta que el cálculo de ingresos no incluye la comisión de Apple App Store ni de Google Play Store y se calcula antes de deducir cualquier comisión. #### Proceeds Esta métrica representa el importe real recibido por el propietario de la aplicación en USD, procedente de compras y renovaciones dentro de placements específicos, una vez deducida la comisión correspondiente de Apple App Store o Google Play Store. Refleja los ingresos netos que contribuyen directamente a las ganancias de la aplicación. Para más información sobre cómo se calculan los ingresos netos, consulta la [documentación](analytics-cohorts#revenue-vs-proceeds) de Adapty. #### ARPPU \{#arppu\} ARPPU son las siglas de Average Revenue Per Paying User (ingreso medio por usuario de pago) y mide el ingreso medio generado por cada usuario de pago dentro de placements específicos. Se calcula dividiendo el ingreso total entre el número de usuarios de pago únicos. Por ejemplo, si el ingreso total es $15,000 y hay 1,000 usuarios de pago, el ARPPU sería $15. #### ARPAS ARPAS, o ingresos medios por suscriptor activo (Average Revenue Per Active Subscriber), permite medir los ingresos medios generados por cada suscriptor activo dentro de placements específicos. Se calcula dividiendo los ingresos totales entre el número de suscriptores que han activado una prueba o suscripción. Por ejemplo, si los ingresos totales son 5.000 $ y hay 1.000 suscriptores, el ARPAS sería de 5 $. Esta métrica ayuda a evaluar el potencial de monetización medio por suscriptor. #### ARPU \{#arpu\} Solo para placements de onboarding. El ARPU es el ingreso promedio por usuario que vio el onboarding. Se calcula dividiendo el ingreso total entre el número de visualizaciones únicas. #### CR única a compras \{#unique-cr-to-purchases\} La tasa de conversión única a compras se calcula dividiendo el número de compras dentro de placements específicos entre el número de visualizaciones únicas. Se centra en la relación entre compras y el número único de visualizaciones, lo que permite entender la efectividad a la hora de convertir visitantes únicos en clientes de pago dentro de placements específicos. #### CR a compras \{#cr-to-purchases\} La tasa de conversión a compras se calcula dividiendo el número de compras dentro de placements específicos entre el número total de vistas de paywalls. Indica el porcentaje de vistas dentro de placements específicos que resultan en compras, lo que ofrece información sobre la eficacia de tu paywall para convertir usuarios en clientes de pago. #### CR único a pruebas \{#unique-cr-to-trials\} La tasa de conversión única a pruebas gratuitas se calcula dividiendo el número de pruebas iniciadas en placements específicos entre el número de vistas únicas. Mide el porcentaje de vistas únicas en placements específicos que resultan en activaciones de prueba, y ofrece información sobre la efectividad de tu paywall para convertir visitantes únicos en usuarios de prueba. #### Compras \{#purchases\} Las compras representan el total acumulado de diversas transacciones realizadas en el paywall dentro de placements específicos. Las siguientes transacciones se incluyen en esta métrica (las renovaciones no están incluidas): - Las nuevas compras se realizan directamente dentro de placements específicos. - Las conversiones de prueba de trials que se activaron inicialmente dentro de placements específicos. - Los cambios de categoría (downgrades, upgrades y cross-grades) de suscripciones realizados dentro de placements específicos. - Las restauraciones de suscripciones dentro de placements específicos, como cuando se restablece una suscripción tras su expiración sin renovación automática. Al tener en cuenta estos diferentes tipos de transacciones, la métrica de compras ofrece una visión completa de la actividad general de adquisición y monetización dentro de placements específicos. #### Trials \{#trials\} La métrica de trials representa el número total de trials activados en placements concretos. Refleja cuántos usuarios han iniciado períodos de prueba a través de tu paywall en esos placements. Esta métrica ayuda a medir la efectividad de tu oferta de trial y puede aportar información sobre el engagement de los usuarios y la conversión de trials a suscripciones de pago. #### Trials cancelados \{#trials-canceled\} La métrica de pruebas canceladas representa el número de pruebas dentro de placements específicos en las que se ha desactivado la renovación automática. Esto ocurre cuando los usuarios cancelan manualmente la prueba, lo que indica su decisión de no continuar con la suscripción una vez finalizado el período de prueba. Hacer un seguimiento de las pruebas canceladas proporciona información valiosa sobre el comportamiento de los usuarios y permite entender la tasa a la que estos optan por salir de la prueba dentro de placements específicos. #### Reembolsos \{#refunds\} La métrica de reembolsos representa el número de compras y suscripciones reembolsadas en placements específicos. Esto incluye transacciones que han sido revertidas o reembolsadas por diversas razones, como solicitudes de clientes, problemas de pago u otras políticas de reembolso aplicables. #### Tasa de reembolso \{#refund-rate\} La tasa de reembolso se calcula dividiendo el número de reembolsos en placements específicos entre el número de primeras compras (las renovaciones no se incluyen). Por ejemplo, si hay 5 reembolsos y 1.000 primeras compras, la tasa de reembolso sería del 0,5%. #### Vistas \{#views\} La métrica de vistas representa el número total de veces que el paywall dentro de placements específicos ha sido visualizado por los usuarios. Cada vez que un usuario visita el paywall dentro de esos placements, se cuenta como una vista independiente. El seguimiento de las vistas te ayuda a entender el nivel de participación e interacción de los usuarios con tu paywall, proporcionando información sobre el comportamiento del usuario y la efectividad del placement y diseño de tu paywall en áreas específicas de tu app. #### Vistas únicas \{#unique-views\} La métrica de vistas únicas representa el número de instancias únicas en las que los usuarios han visto el paywall dentro de placements específicos. A diferencia de las vistas totales, que cuentan cada visita como una vista separada, las vistas únicas cuentan la visita de cada usuario al paywall dentro de esos placements solo una vez, independientemente de cuántas veces acceda a él. Registrar las vistas únicas ayuda a obtener una medida más precisa del engagement de los usuarios y el alcance de tu paywall dentro de placements específicos, ya que se centra en usuarios individuales en lugar del número total de visitas. #### Completions y completions únicas \{#completions--unique-completions\} Solo para placements de onboarding. Las completions cuentan el número de veces que los usuarios completan tu placement de onboarding, es decir, que pasan de la primera a la última pantalla. Si alguien lo completa dos veces, eso cuenta como dos **completions** pero una **unique completion**. #### Tasa de unique completions \{#unique-completions-rate\} Solo para placements de onboarding. El número de unique completions dividido entre el número de unique views. Esta métrica te ayuda a entender cómo interactúa la gente con el placement de onboarding y a realizar cambios si detectas que lo ignoran. --- # File: create-paywall --- --- title: "Crear paywall" description: "Aprende a crear paywalls de alta conversión usando el Paywall Builder de Adapty." --- Un [paywall](paywalls) es una configuración de Adapty que define qué productos ofrecer. En Adapty, los paywalls son la única forma de recuperar productos en tu app. Necesitas un paywall independientemente de cómo lo muestres: - [**Paywall Builder**](adapty-paywall-builder): Diseña una pantalla en el editor sin código. Adapty la renderiza y gestiona las compras. - **Paywall personalizado**: Implementa tu propia interfaz y usa la configuración del paywall para recuperar los productos. Una vez creado, asigna el paywall a un [placement](placements) — los placements controlan qué paywall ven los usuarios. Los productos de un paywall en producción son fijos, por lo que sus métricas siempre reflejan la misma combinación, lo que te permite comparar el rendimiento entre distintos conjuntos de productos y precios. :::tip También puedes crear paywalls mediante programación usando la [CLI para desarrolladores](developer-cli-reference#adapty-paywalls-create). ::: <details> <summary>Antes de empezar a crear paywalls (haz clic para expandir)</summary> 1. [Crea al menos un producto](create-product). 2. (opcional) [Crea una oferta](create-offer). </details> ## Crear paywall \{#create-paywall\} Para crear un nuevo paywall en el Adapty Dashboard: 1. Ve a [**Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty. Esta página muestra un resumen de todos tus paywalls y sus métricas. 2. Haz clic en **Create paywall**. 3. En la página **Paywalls / New paywall**, introduce un **Paywall name** para identificar este paywall en todo el Adapty Dashboard. 4. Haz clic en **Add product**. 5. Selecciona los productos que se mostrarán a tus clientes. :::note - El orden de los productos en esta lista se mantendrá en el SDK, así que ordénalos según tus preferencias. - Una vez que un paywall se muestre en producción, no podrás cambiar sus productos, ya que esto podría afectar a las métricas del paywall. ::: 6. Si ofreces pruebas gratuitas u otras ofertas para tus productos, agrégalas aquí, o no estarán disponibles. Elige una oferta que hayas [creado anteriormente](create-offer) para ese producto desde la lista **Offer**. La lista solo está disponible para los productos que tienen ofertas. 7. Haz clic en **Create as a draft** para confirmar la creación del paywall. ¡Tu paywall ya está creado! <img src="/assets/shared/img/create-paywall.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '900px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Próximos pasos \{#next-steps\} Después de crear tu primer paywall: 1. Agrégalo a un [placement](placements). Los IDs de placement serán las únicas entidades hardcodeadas. Los usarás para obtener los productos que quieres vender. 2. El modo de trabajo con el paywall a partir de aquí depende de tu implementación: - Si quieres usar el [Adapty Paywall Builder](adapty-paywall-builder), diseña el paywall en el editor sin código. Adapty renderizará el paywall y gestionará la lógica de compra, mientras que tú solo necesitarás mostrar el paywall en el código de la app. - Si tienes un paywall personalizado que quieres usar, consulta nuestras guías para implementar compras in-app con Adapty en tu plataforma: - [iOS](ios-implement-paywalls-manually) - [Android](android-implement-paywalls-manually) - [React Native](react-native-implement-paywalls-manually) - [Flutter](flutter-implement-paywalls-manually) - [Unity](unity-implement-paywalls-manually) - [Kotlin Multiplatform](kmp-implement-paywalls-manually) --- # File: customize-paywall-with-remote-config --- --- title: "Diseña un paywall con Remote Config" description: "Personaliza tu paywall con Remote Config en Adapty para un mejor targeting." --- :::important Esta guía cubre Remote Config para paywalls clásicos. Para Flow Builder, consulta [Personalizar flow con Remote Config](customize-flow-with-remote-config). ::: El Remote Config de paywall es una herramienta muy útil que ofrece opciones de configuración flexibles. Permite usar payloads JSON personalizados para ajustar tus paywalls con precisión. Con él puedes definir distintos parámetros como títulos, imágenes, fuentes, colores y más. <details> <summary>Antes de empezar a personalizar un paywall (haz clic para expandir)</summary> 1. [Crea un producto](create-product). 2. [Crea un paywall y añade el producto](create-paywall). </details> Para empezar a personalizar un paywall usando el Remote Config: 1. Abre la sección [**Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty. 2. Haz clic en el paywall para abrirlo. <img src="/assets/shared/img/remote-config.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Cambia a la pestaña **Remote config**. <img src="/assets/shared/img/remote-config-3.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> El Remote Config tiene 2 vistas: - [Tabla](customize-paywall-with-remote-config#table-view-of-the-remote-config) - [JSON](customize-paywall-with-remote-config#json-view-of-the-remote-config) Tanto la vista **Tabla** como la vista **JSON** incluyen los mismos elementos de configuración. La única diferencia es de preferencia personal, con la salvedad de que la vista de tabla ofrece un menú contextual, que puede ser útil para corregir errores de localización. Puedes cambiar entre vistas haciendo clic en la pestaña **Table** o **JSON** cuando lo necesites. Independientemente de la vista que hayas elegido para personalizar tu paywall, puedes acceder a estos datos desde el SDK usando las propiedades `remoteConfig` o `remoteConfigString` de `AdaptyPaywall` y hacer ajustes en tu paywall. También puedes actualizar los valores del Remote Config mediante programación usando la [API del lado del servidor](api-adapty/operations/updatePaywall) para modificar dinámicamente las configuraciones de paywall sin actualizaciones manuales en el dashboard. Aquí tienes algunos ejemplos de cómo puedes usar un Remote Config. <Tabs groupId="current-os" queryString> <TabItem value="Titles" label="Títulos" default> ```json showLineNumbers { "screen_title": "Today only: Subscribe, and get 7 days for free!" } # Test titles or others texts ``` </TabItem> <TabItem value="Images" label="Imágenes" default> ```json showLineNumbers { "background_image": "https://adapty.io/media/paywalls/bg1.webp" } # Test images on your paywall ``` </TabItem> <TabItem value="Fonts" label="Fuentes" default> ```json showLineNumbers { "font_family": "San Francisco", "font_size": 16 } # Test fonts ``` </TabItem> <TabItem value="Color" label="Color" default> ```json showLineNumbers { "subscribe_button_color": "purple" } # Test colors of buttons, texts etc. ``` </TabItem> <TabItem value="HTML" label="HTML" default> ```json showLineNumbers { "photo_gallery": "https://adapty.io/media/paywalls/link-to-html-snippet.html" } # Any HTML code that can be displayed on the paywall ``` </TabItem> <TabItem value="Soft/Hard Paywall" label="Paywall Suave/Duro" default> ```json showLineNumbers { "hard_paywall": true } # By setting it to true, you disalow skipping paywall without subscribing # You have to handle this logic in your app ``` </TabItem> <TabItem value="Translations" label="Traducciones" default> ```json showLineNumbers { "title": { "en": "Try for free!", "es": "¡Prueba gratis!", "ru": "Попробуй бесплатно!" } } ``` </TabItem> </Tabs> Puedes combinar distintas opciones y crear las tuyas propias. Así puedes probar diferentes títulos, textos, imágenes, fuentes, colores, etc. ### Vista JSON del Remote Config \{#json-view-of-the-remote-config\} En la vista **JSON** del Remote Config puedes introducir cualquier dato con formato JSON: <img src="/assets/shared/img/3356ff5-remote_config_JSON.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Vista de tabla del Remote Config \{#table-view-of-the-remote-config\} Si no es habitual para ti trabajar con código y necesitas corregir algunos valores del JSON, Adapty tiene la vista **Table** para ti. <img src="/assets/shared/img/4c27b2f-remote_config_table.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Es una copia de tu JSON en formato de tabla, fácil de leer y entender. El código de colores ayuda a identificar los distintos tipos de datos. Para añadir una clave, haz clic en el botón **Add row**. Comprobamos automáticamente la correspondencia entre valores y tipos, y mostramos una alerta si tus cambios pueden producir un JSON no válido. <img src="/assets/shared/img/ef682d8-add_raw.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Las opciones adicionales de fila son especialmente útiles para las [localizaciones de paywall](add-remote-config-locale): <img src="/assets/shared/img/17bcf80-remote_config_table_options.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Ahora es el momento de [crear un placement](create-placement) y añadir el paywall a él. Después, puedes <InlineTooltip tooltip="mostrar tus paywalls de Remote Config">[iOS](present-remote-config-paywalls), [Android](present-remote-config-paywalls-android), [React Native](present-remote-config-paywalls-react-native), [Flutter](present-remote-config-paywalls-flutter), y [Unity](present-remote-config-paywalls-unity)</InlineTooltip> en tu app. --- # File: add-paywall-locale-in-adapty-paywall-builder --- --- title: "Añadir idioma en el Flow Builder" description: "Añade contenido localizado en el Flow Builder de Adapty para llegar a usuarios de todo el mundo en su idioma." --- Localizar tus flows los hace disponibles en varios idiomas. En el Flow Builder, la localización se organiza por pantalla, y cada una muestra un porcentaje de completado para hacer seguimiento del progreso de la traducción. :::tip Termina de configurar tu flow en el idioma predeterminado antes de añadir otros idiomas. ::: ## Añadir y configurar la localización \{#add-and-set-up-localization\} 1. En el panel izquierdo, haz clic en Localizations. Luego, haz clic en **Add locale**. Selecciona los idiomas que quieras añadir. 2. Cada idioma añadido aparece como una columna en la tabla de localización, con los valores del idioma predeterminado ya rellenados. 3. Para centrarte solo en lo que falta, activa el toggle **Missing only** en el panel izquierdo. La tabla filtrará y mostrará únicamente las filas sin traducir. ## Exportar e importar para traducción externa \{#export-and-import-for-external-translation\} Puedes exportar el archivo de localización para compartirlo con traductores e importar los resultados traducidos. En la barra de herramientas superior, haz clic en **Import / Export**. ### Formato del archivo de exportación \{#export-file-format\} La exportación genera un archivo `.tsv` (separado por tabulaciones) con una fila por cada elemento traducible. Las columnas son: | Columna | Descripción | |--------|-------------| | `Screen` | La pantalla a la que pertenece el elemento (p. ej., `Welcome`, `Quiz`) | | `Element` | Identificador del elemento generado automáticamente dentro de esa pantalla. Puedes cambiarlo en **Interactions** > **Element ID**. | | `Property` | El tipo de propiedad (p. ej., `content`) | | `[default_locale]` | El código del idioma predeterminado (p. ej., `en`) | | `[locale]` | Una columna por cada idioma añadido (p. ej., `fr`, `es`) | Ejemplo: Pantalla Elemento Propiedad en fr es Welcome title content Turn words into art Transformez les mots en art Welcome subtitle content Create stunning images in seconds with AI Créez des images en quelques secondes Quiz quiz-title content What will you create? :::note Deja en blanco las columnas de idioma para las líneas sin traducir — Adapty las tratará como ausentes. ::: ### Requisitos del archivo de importación \{#import-file-requirements\} - **Formato**: `.tsv` (valores separados por tabulaciones) - **Encabezados**: debe incluir `Screen`, `Element`, `Property` y al menos una columna de configuración regional - **Nombres de columna de configuración regional**: deben coincidir con los códigos de configuración regional ya añadidos al flow. Importar un archivo con códigos de configuración regional que no estén presentes en el flow provoca un error. - **Importación parcial**: puedes incluir solo un subconjunto de filas; las filas que no estén en el archivo conservan sus valores actuales ## Traducir manualmente \{#translate-manually\} También puedes escribir las traducciones directamente en cualquier celda de la tabla de localización. Para gestionar una fila específica, abre su menú contextual (**⋮**): - **Reset to default**: Revierte la traducción de la fila a los valores del idioma predeterminado. ## Vista previa de la localización \{#preview-the-localization\} Para revisar tus traducciones, cambia el idioma activo en el Flow Builder y revisa cada pantalla. --- # File: add-remote-config-locale --- --- title: "Localizar paywalls con Remote Config" description: "Añade localizaciones de Remote Config para personalizar los paywalls de Adapty." --- Adaptar los paywalls a diferentes idiomas es fundamental en un mundo con culturas diversas. La localización te permite crear experiencias personalizadas para usuarios de regiones específicas. Para cada paywall puedes añadir versiones en distintos idiomas, asegurando que tu producto conecte con las audiencias locales. Si no usas el Paywall Builder de Adapty para diseñar paywalls, aún puedes localizar tus paywalls personalizados y gestionar las localizaciones sin volver a publicar la app: 1. Creas un Remote Config con variables en el Adapty Dashboard. Las variables pueden representar texto, contenido multimedia u otros tipos de contenido. 2. Defines los valores de cada variable para cada localización. 3. Gestionas las variables en el código de la app. 4. Cuando obtienes un paywall con productos y envías una localización, recibes los valores de las variables correspondientes. De esta forma, las localizaciones no están codificadas en el código de la app y puedes ajustarlas en cualquier momento. Tanto en la vista de tabla como en formato JSON, puedes ajustar fácilmente los ajustes de cada idioma. Por ejemplo, traducir claves de texto, cambiar valores booleanos (p. ej., `TRUE` para inglés, `FALSE` para italiano), o incluso intercambiar imágenes de fondo. ## Configurar la localización para paywalls con Remote Config \{#set-up-localization-for-remote-configured-paywalls\} 1. Ve a la sección [**Paywalls**](https://app.adapty.io/paywalls) en Adapty. 2. Haz clic en el paywall para abrirlo. 3. Ve a la pestaña **Remote config**. <img src="/assets/shared/img/switch_to_remote_config.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Haz clic en **Locales** y selecciona los idiomas que quieres admitir. Guarda los cambios para añadir estas localizaciones al paywall. <img src="/assets/shared/img/add_locale.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Ahora puedes traducir el contenido manualmente, usar IA o exportar el archivo de localización para traductores externos. ## Traducir paywalls con IA \{#translate-paywalls-with-ai\} La traducción con IA es una forma rápida y eficaz de localizar tu paywall. Puedes traducir tanto valores de tipo **String** como **List**. Por defecto, todas las líneas están seleccionadas (resaltadas en violeta). Las líneas que ya han sido traducidas aparecen en verde y no se incluirán en la nueva traducción por defecto. Las líneas que no están seleccionadas ni traducidas aparecen en gris. <img src="/assets/shared/img/localization-table.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/localization-json.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. Selecciona las líneas que quieres traducir. Es recomendable desmarcar las líneas con IDs, URLs y variables para que la IA no las traduzca. 2. Selecciona los idiomas para la traducción. <img src="/assets/shared/img/localization-table-language.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **AI Translate** para aplicar las traducciones. Las líneas seleccionadas se traducirán y se añadirán al paywall, quedando marcadas en verde. ## Exportar archivos de localización para traductores externos \{#exporting-localization-files-for-external-translation\} Aunque la localización con IA es cada vez más popular, puede que prefieras un método más fiable, como usar traductores profesionales o una agencia de traducción con experiencia contrastada. En ese caso, puedes exportar los archivos de localización para compartirlos con tus traductores e importar los resultados traducidos de vuelta a Adapty. Al exportar con el botón **Export** se crean archivos `.json` individuales para cada idioma, agrupados en un único archivo comprimido. Si solo necesitas un archivo, puedes exportarlo directamente desde el menú específico del idioma. <img src="/assets/shared/img/localization-single-export.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Una vez que hayas recibido los archivos traducidos, usa el botón **Import** para subirlos todos a la vez o de forma individual. Adapty validará automáticamente los archivos para asegurarse de que tienen el formato correcto. ### Formato del archivo de importación \{#import-file-format\} Para que la importación se realice correctamente, el archivo debe cumplir los siguientes requisitos: - **Nombre y extensión del archivo:** El nombre del archivo debe coincidir con la localización que representa y tener extensión `.json`. Puedes verificar y copiar el nombre de la localización en el Adapty Dashboard. Si el nombre no se reconoce, la importación fallará. <img src="/assets/shared/img/locale-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **JSON válido:** El archivo debe ser un JSON válido. Si no lo es, la importación fallará. ## Localización manual \{#manual-localization\} A veces puede que quieras ajustar traducciones, añadir imágenes diferentes para localizaciones específicas o incluso modificar las configuraciones remotas directamente. 1. Elige el elemento que quieres traducir e introduce un nuevo valor. Puedes actualizar tanto valores de tipo **String** como **List**, o reemplazar imágenes por otras más adecuadas para la localización. <img src="/assets/shared/img/032b429-remote_config_localization.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Aprovecha el menú contextual de la localización en inglés para resolver problemas de localización de forma eficiente: - **Copy this value to all locales**: Sobreescribe los cambios realizados en localizaciones que no sean la inglesa para la fila seleccionada, reemplazándolos con el valor de la localización en inglés. - **Revert all row changes to original values**: Descarta los cambios realizados durante la sesión actual y restaura los valores al último estado guardado. <img src="/assets/shared/img/d7e70f1-remote_confi_loc_table_options.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Después de añadir localizaciones a un paywall, asegúrate de implementar correctamente los códigos de localización en el código de tu app. Consulta <InlineTooltip tooltip="las guías sobre cómo usar localizaciones y códigos de localización en tu app">[iOS](localizations-and-locale-codes), [Android](android-localizations-and-locale-codes)</InlineTooltip> --- # File: web-paywall-configuration --- --- title: "Configuración del web paywall" --- Una vez que hagas clic en **Create web paywall** en la página **Web paywall**, se te redirigirá a una página independiente para configurar el diseño del web paywall y el método de pago. ## Configura un método de pago \{#set-up-a-payment-method\} Primero, necesitas conectar un proveedor de pagos que gestione las compras. Las opciones disponibles son: - Stripe - Paddle - Paypal - Solidgate :::important Para garantizar un seguimiento preciso de los análisis del web paywall en Adapty, necesitas [añadir tus productos](product) junto con los IDs de producto correspondientes de Stripe/Paddle/otro proveedor de pagos en Adapty. ::: Para configurar un proveedor de pagos: 1. En la página de lista de web paywalls, haz clic en **Settings** y cambia a la pestaña **Integrations**. 2. Selecciona un proveedor de pagos y sigue las instrucciones de integración que aparecen en pantalla. <img src="/assets/shared/img/web-paywall-configuration-1.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. ⚠️ Si eliges Stripe, asegúrate de usar las claves del entorno **Test Mode** aunque la interfaz diga **Sandbox**. De lo contrario, tu web paywall no funcionará. Los **Sandboxes** de Stripe aún no son compatibles. <img src="/assets/shared/img/web-paywall-configuration-stripe.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Configura la verificación de dominio de Apple Pay \{#set-up-apple-pay-domain-verification\} En **Settings > Domains**, selecciona tu proveedor de pagos principal para usarlo en la verificación de dominio. Luego, verifica los dominios de tu paywall con el proveedor correspondiente: **Stripe**: 1. Ve a [Payment method domain settings](https://dashboard.stripe.com/settings/payment_method_domains) y haz clic en **Add a new domain**. 2. Añade `app.funnelfox.com` y tu subdominio personal del paywall (tendrá un aspecto similar a `paywalls-....fnlfx.com`). Para encontrar tu subdominio, ve a **Settings > Domains** y copia el valor de **Hosted subdomain**. **Paddle**: 1. En la consola de Paddle, ve a **Checkout > Website approval** y haz clic en **Add a new domain**. 2. Añade `app.funnelfox.com` y tu subdominio personal del paywall (tendrá un aspecto similar a `paywalls-....fnlfx.com`). Para encontrar tu subdominio, ve a **Settings > Domains** y copia el valor de **Hosted subdomain**. El proceso de aprobación en Paddle es manual, por lo que tendrás que esperar hasta que los dominios pasen de `Pending` a `Approved`. **FunnelFox Billing**: Sigue las [instrucciones de integración de FunnelFox Billing](https://funnelfox.com/docs/billing/integration-billing-funnelfox). **SolidGate**: 1. En tu Solidgate Dashboard, ve a **Developers > Apple Pay Domains**. 2. Haz clic en **+ Add new domain** y pega el dominio de tu proyecto (desde **Settings > Domains** en FunnelFox). Añade también tu dominio personalizado, si corresponde. 3. Para usar Apple Pay en modo de vista previa, añade también `http://app.funnelfox.com/`. ## Crea y configura un web paywall \{#create-and-configure-a-web-paywall\} 1. En la página de lista de web paywalls, haz clic en **Create a paywall**. 2. Introduce un nombre para el paywall y haz clic en **Create**. <img src="/assets/shared/img/web-paywall-configuration-2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Se te redirigirá a una plantilla básica con dos opciones de suscripción y el botón de compra de Apple Pay. La primera pantalla muestra los planes de suscripción. La segunda y la tercera son pantallas de pago. Cada pantalla corresponde a un plan que ofreces. Si solo tienes un plan, elimina la pantalla extra. Si tienes más, debes duplicar las pantallas de pago. La última pantalla que ven los usuarios tras una compra exitosa es donde debes indicar claramente que pueden volver a tu app. <img src="/assets/shared/img/web-paywall-configuration-10.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Configura la lista de planes: añade o elimina planes y precios. Todos los precios y planes que ves en pantalla no se añaden de forma dinámica, por lo que debes configurarlos manualmente. <img src="/assets/shared/img/web-paywall-configuration-8.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Añade o configura una pantalla de pago para cada plan que tengas. Te recomendamos añadir el importe total en cada pantalla de pago para que los usuarios sepan cuánto van a pagar antes de hacer clic en el botón de compra. 6. En las pantallas de pago, ya tienes el botón de Apple Pay. Para que funcione, configura en cada pantalla: 1. **Product type**: Selecciona si quieres añadir un período de prueba o un descuento. 2. **Trial period**: Introduce la duración del período de prueba. 3. **Product**: Selecciona tu producto de tu proveedor de pagos. :::important Asegúrate de que el producto esté añadido en Adapty. De lo contrario, el resultado de la compra se establecerá por defecto. ::: 4. **Subscription discount**: Opcionalmente, selecciona un cupón de tu proveedor de pagos. <img src="/assets/shared/img/web-paywall-configuration-6.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Ahora necesitas asociar los planes con las pantallas de pago. En la pantalla de selección de plan, haz clic en el botón **Continue** y selecciona una pantalla de destino para cada plan. <img src="/assets/shared/img/web-paywall-configuration-9.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Cuando tengas el paywall listo, necesitas obtener su enlace para activarlo en Adapty. La forma de obtenerlo depende de si lo estás probando o lanzando en el entorno de producción: 1. **Para pruebas en sandbox**: Haz clic en **Preview** en la parte superior derecha y copia el enlace. 2. **Para producción**: Haz clic en **Publish** en la parte superior derecha. Haz clic en **Home** y copia el enlace de la columna **URL**. <img src="/assets/shared/img/web-paywall-configuration-11.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ¡Listo! Usa este enlace para [continuar con la configuración](web-paywall#step-2-trigger-the-paywall). --- # File: fallback-paywalls --- --- title: "Paywalls de respaldo" description: "Usa paywalls de respaldo para garantizar una experiencia de usuario fluida en Adapty." --- Para mantener una experiencia de usuario fluida, es importante que configures **versiones de respaldo** para tus [paywalls](paywalls) y [onboardings](onboardings). Cuando tu aplicación carga un paywall, el SDK de Adapty solicita los datos de configuración del paywall desde nuestros servidores. ¿Pero qué ocurre si el dispositivo no puede conectarse a Adapty por problemas de red o interrupciones del servidor? * Si el usuario accedió al paywall anteriormente y el dispositivo guardó sus datos en caché, la aplicación carga los datos del paywall **desde la caché**. * Si el dispositivo no tiene el paywall en caché, la aplicación busca un archivo de configuración almacenado localmente. Esto le permite mostrar el paywall sin errores. Adapty genera automáticamente archivos de configuración de respaldo para que los descargues y uses. Cada archivo contiene configuraciones específicas de plataforma para *todos* tus placements. ## Primeros pasos \{#get-started\} 1. [Descarga el archivo de configuración de respaldo](/local-fallback-paywalls) desde Adapty. 2. Usa el SDK de Adapty para configurar tus paywalls de respaldo: * [iOS](ios-use-fallback-paywalls) * [Android](android-use-fallback-paywalls) * [React Native](react-native-use-fallback-paywalls) * [Flutter](flutter-use-fallback-paywalls) * [Unity](unity-use-fallback-paywalls) * [Kotlin Multiplatform](kmp-use-fallback-paywalls) * [Capacitor](capacitor-use-fallback-paywalls) ## Limitaciones \{#limitations\} Los paywalls de respaldo están codificados de forma fija y se almacenan localmente, por lo que carecen de las capacidades dinámicas de los paywalls regulares de Adapty. * Los paywalls de respaldo no admiten [internacionalización](paywall-localization). Cuando Adapty genera el archivo de configuración, utiliza el idioma predeterminado `en`. * Cada placement solo puede tener un paywall de respaldo. Si tu configuración incluye distintos paywalls para diferentes [audiencias](audience), Adapty usa la configuración destinada a "All users". * Los paywalls de respaldo no admiten [pruebas A/B](ab-tests). Si un paywall participa en una prueba A/B, su archivo de configuración de respaldo incluirá la variante con mayor peso. * Los paywalls de respaldo no se pueden [gestionar de forma remota](customize-paywall-with-remote-config). Si quieres actualizar el archivo de configuración, deberás publicar una nueva versión de la app en App Store / Google Play. --- # File: local-fallback-paywalls --- --- title: "Descargar paywalls de respaldo" description: "Usa paywalls de respaldo locales en Adapty para garantizar flujos de suscripción sin interrupciones." --- Adapty genera automáticamente archivos de configuración JSON para tus [paywalls de respaldo](/fallback-paywalls), uno por plataforma. Estos archivos también contienen los datos de respaldo para tus onboardings. Si un placement tiene más de un paywall u onboarding, la versión de respaldo incluirá la variación con el mayor peso o la audiencia más amplia. Adapty actualiza estos archivos cada vez que modificas tus paywalls u onboardings. Sigue los pasos a continuación para descargar tus configuraciones de respaldo: 1. Abre la página **[Placements](https://app.adapty.io/placements)**. 2. Haz clic en el botón **Fallbacks**. 3. Selecciona tu plataforma de destino (*iOS* o *Android*) en el menú desplegable. 4. Selecciona la versión de tu SDK para iniciar la descarga. <img src="/assets/shared/img/9c63367-placements.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Después de la descarga \{#after-the-download\} Sigue la guía de configuración para tu plataforma: * [iOS](ios-use-fallback-paywalls) * [Android](android-use-fallback-paywalls) * [React Native](react-native-use-fallback-paywalls) * [Flutter](flutter-use-fallback-paywalls) * [Unity](unity-use-fallback-paywalls) * [Kotlin Multiplatform](kmp-use-fallback-paywalls) * [Capacitor](capacitor-use-fallback-paywalls) --- # File: paywall-metrics --- --- title: "Métricas de paywall" description: "Rastrea y analiza las métricas de rendimiento de tus paywalls para mejorar los ingresos por suscripción." --- Adapty recopila una serie de métricas para ayudarte a medir mejor el rendimiento de los paywalls. Todas las métricas se actualizan en tiempo real, excepto las vistas, que se actualizan cada varios minutos. Todas las métricas, excepto las vistas, se atribuyen al producto dentro del paywall. Este documento describe las métricas disponibles, sus definiciones y cómo se calculan. Las métricas de los paywalls están disponibles en la lista de paywalls, lo que te ofrece una visión general del rendimiento de todos tus paywalls. Esta vista consolidada muestra métricas agregadas para cada paywall, lo que te permite evaluar su efectividad e identificar áreas de mejora. Para un análisis más detallado de cada paywall, puedes navegar a las métricas de detalle del paywall. En esta sección encontrarás métricas exhaustivas específicas del paywall seleccionado, con información más profunda sobre su rendimiento. ### Filtrar métricas por fecha de instalación \{#filter-metrics-by-install-date\} Las métricas de paywall, periodo de prueba y compra se pueden agrupar por dos tipos de fechas distintos: - **La fecha del evento**: cuando se visualizó el paywall, empezó el periodo de prueba o se realizó la compra. - **La fecha de instalación**: cuando el usuario abrió la app por primera vez. Ambas vistas pueden mostrar números muy diferentes para el mismo rango de fechas. La casilla **Filter metrics by install date** controla cuál de las dos usa el dashboard: - **Sin marcar (por defecto)**: Las métricas se agrupan por fecha del evento. - **Marcada**: Las métricas se agrupan por fecha de instalación. **Ejemplo.** Estableces el rango de fechas del 1 al 30 de abril y observas los periodos de prueba. - **Sin marcar**: Muestra los periodos de prueba que *comenzaron* en abril, independientemente de cuándo se instalaron esos usuarios. - **Marcada**: Muestra los periodos de prueba de usuarios que *instalaron* la app en abril, independientemente de cuándo empezaron sus periodos de prueba. Usa la vista por fecha de instalación para medir el rendimiento de adquisición de usuarios de una cohorte concreta. Usa la vista por fecha del evento para medir la actividad del paywall u onboarding en un periodo específico. ### Controles de métricas \{#metrics-controls\} El sistema muestra las métricas según el período de tiempo seleccionado y las organiza de acuerdo con el parámetro de la columna izquierda con tres niveles de sangría. Para el paywall en directo, las métricas cubren el período desde la fecha de inicio del paywall hasta la fecha actual. Para los paywalls inactivos, las métricas abarcan todo el período desde la fecha de inicio hasta el final del período de tiempo seleccionado. Los paywalls en borrador y archivados están incluidos en la tabla de métricas, pero si no hay datos disponibles para esos paywalls, aparecerán en la lista sin ninguna métrica. #### Opciones de visualización de datos de métricas \{#view-options-for-metrics-data\} La página del paywall ofrece dos opciones de visualización de datos de métricas: por placement y por audiencia. En la vista por placement, las métricas se agrupan según los placements asociados al paywall. Esto permite analizar las métricas por diferentes placements. En la vista basada en audiencia, las métricas se agrupan según la audiencia objetivo del paywall. Los usuarios pueden evaluar métricas específicas para distintos segmentos de audiencia. Puedes seleccionar la vista preferida mediante el menú desplegable en la parte superior de la página de detalles del paywall. #### Rangos de tiempo \{#time-ranges\} Puedes elegir entre varios períodos de tiempo para analizar los datos de métricas, lo que te permite centrarte en duraciones específicas como días, semanas, meses o rangos de fechas personalizados. #### Filtros y agrupación disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: Adapty ofrece potentes herramientas para filtrar y personalizar el análisis de métricas según tus necesidades. En la página de métricas de Adapty tienes acceso a distintos rangos de tiempo, opciones de agrupación y posibilidades de filtrado. - Filtrar por: Audiencia, país, paywall, estado del paywall, grupo de paywalls, placement, país, store, producto y store del producto. - Agrupar por: Producto y store. #### Gráfico de métrica individual \{#single-metrics-chart\} Una de las partes principales de la página de métricas del paywall es la sección de gráficos, que representa visualmente las métricas seleccionadas y facilita el análisis. La sección del gráfico en la página de métricas del paywall incluye un gráfico de barras horizontales que representa visualmente los valores de la métrica seleccionada. Cada barra corresponde a un valor de la métrica y es proporcional en tamaño, lo que facilita la comprensión de los datos de un vistazo. La línea horizontal indica el período de tiempo analizado, y la columna vertical muestra los valores numéricos de las métricas. El valor total de todos los valores de la métrica se muestra junto al gráfico. Además, al hacer clic en el icono de flecha en la esquina superior derecha de la sección del gráfico, la vista se amplía y muestra las métricas seleccionadas en la línea completa del gráfico. #### Resumen de métricas totales \{#total-metrics-summary\} Junto al gráfico de métrica individual, se muestra la sección de resumen de métricas totales, que presenta los valores acumulados de las métricas seleccionadas en un momento determinado, con la posibilidad de cambiar la métrica mostrada mediante un menú desplegable. ### Definiciones de métricas \{#metrics-definitions\} :::note Adapty convierte otras divisas a USD según el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo de cambio se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: #### Ingresos \{#revenue\} Esta métrica representa el importe total en USD generado por compras y renovaciones. Ten en cuenta que el cálculo de ingresos no incluye la comisión de App Store / Play Store y se calcula antes de deducir cualquier comisión. #### Ganancias netas \{#proceeds\} Esta métrica representa el importe real en USD recibido por el propietario de la app por compras y renovaciones, tras deducir la comisión aplicable de App Store / Play Store. :::important Notifica a Adapty si tu app está inscrita en un programa de comisión reducida. Para garantizar cálculos correctos, especifica el estado de tu [Small Business Program](app-store-small-business-program) y [Reduced Service Fee program](google-reduced-service-fee) en los [ajustes de tu app](general). ::: Refleja los ingresos netos que contribuyen directamente a las ganancias de la app. Para más información sobre cómo se calculan los ingresos netos, consulta la [documentación](analytics-cohorts#revenue-vs-proceeds) de Adapty. #### ARPPU \{#arppu\} ARPPU es el ingreso promedio por usuario de pago. Se calcula dividiendo los ingresos totales entre el número de usuarios de pago únicos. $15.000 de ingresos / 1.000 usuarios de pago = $15 de ARPPU. #### ARPAS \{#arpas\} El ingreso promedio por suscriptor activo te permite medir el ingreso promedio generado por cada suscriptor activo. Se calcula dividiendo los ingresos totales entre el número de suscriptores que han activado una prueba o suscripción. Por ejemplo, si los ingresos totales son $5.000 y hay 1.000 suscriptores, el ARPAS sería $5. Esta métrica ayuda a evaluar el potencial de monetización promedio por suscriptor. #### Tasa de conversión (CR) única a compras La tasa de conversión única a compras se calcula dividiendo el número de compras entre el número de vistas únicas. Por ejemplo, si hay 10 compras y 100 vistas únicas, la tasa de conversión única a compras sería del 10%. Esta métrica se centra en la proporción de compras respecto al número único de vistas, y ofrece información sobre la eficacia para convertir visitantes únicos en clientes de pago. #### CR a compras La tasa de conversión a compras se calcula dividiendo el número de compras entre el total de vistas. Por ejemplo, si hay 10 compras y 100 vistas, la tasa de conversión a compras sería del 10%. Esta métrica indica el porcentaje de vistas que resultan en compras, lo que ofrece información sobre la efectividad de tu paywall para convertir usuarios en clientes de pago. #### CR único a trials \{#unique-cr-to-trials\} La tasa de conversión única a pruebas se calcula dividiendo el número de pruebas iniciadas entre el número de vistas únicas. Por ejemplo, si se han iniciado 30 pruebas y hay 100 vistas únicas, la tasa de conversión única a pruebas sería del 30%. Esta métrica mide el porcentaje de vistas únicas que se convierten en activaciones de prueba, lo que permite evaluar la eficacia de tu paywall para convertir visitantes únicos en usuarios de prueba. #### Compras \{#purchases\} Las compras representan el total acumulado de distintas transacciones realizadas en el paywall. Las siguientes transacciones se incluyen en esta métrica (las renovaciones no están incluidas): - Nuevas compras realizadas directamente en el paywall. - Conversiones de prueba de trials que se activaron inicialmente en el paywall. - Cambios de plan (downgrades, upgrades y cross-grades) de suscripciones realizados en el paywall. - Restauraciones de suscripción en el paywall, como cuando una suscripción se reactiva tras expirar sin renovación automática. Al considerar estos diferentes tipos de transacciones, la métrica de compras ofrece una visión completa de la actividad general de adquisición y monetización en tu paywall. #### Pruebas gratuitas \{#trials\} La métrica de pruebas gratuitas representa el número total de pruebas que se han activado. Refleja el número de usuarios que han iniciado períodos de prueba a través de tu paywall. Esta métrica ayuda a medir la eficacia de tu oferta de prueba y puede aportar información sobre la participación de los usuarios y la conversión de pruebas a suscripciones de pago. #### Pruebas gratuitas canceladas \{#trials-canceled\} La métrica de pruebas canceladas representa el número de pruebas en las que se ha desactivado la renovación automática. Esto ocurre cuando los usuarios cancelan manualmente la suscripción durante el período de prueba, lo que indica su decisión de no continuar con la suscripción al finalizar dicho período. Hacer seguimiento de las pruebas canceladas proporciona información valiosa sobre el comportamiento de los usuarios y permite entender la tasa a la que estos optan por abandonar la prueba. #### Reembolsos \{#refunds\} La métrica de reembolsos representa el número de compras y suscripciones reembolsadas. Esto incluye transacciones que han sido revertidas o reembolsadas por diversos motivos, como solicitudes de clientes, problemas de pago u otras políticas de reembolso aplicables. #### Tasa de reembolso \{#refund-rate\} La tasa de reembolso se calcula dividiendo el número de reembolsos entre el número de compras por primera vez (las renovaciones no se incluyen). Por ejemplo, si hay 5 reembolsos y 1000 compras por primera vez, la tasa de reembolso sería del 0,5%. #### Vistas \{#views\} La métrica de vistas representa el número total de veces que los usuarios han visto el paywall. Cada vez que un usuario visita el paywall, se cuenta como una vista independiente. Por ejemplo, si un usuario visita el paywall dos veces, se registrarán dos vistas. Hacer seguimiento de las vistas te ayuda a entender el nivel de interacción de los usuarios con tu paywall, y te da información sobre el comportamiento de los usuarios y la efectividad del placement y diseño del paywall. #### Vistas únicas \{#unique-views\} La métrica de vistas únicas representa el número de instancias únicas en las que los usuarios han visto el paywall. A diferencia de las vistas totales, que cuentan cada visita como una vista independiente, las vistas únicas cuentan la visita de cada usuario al paywall una sola vez, independientemente de cuántas veces accedan a él. Por ejemplo, si un usuario visita el paywall dos veces, se registrará como una única vista. Hacer seguimiento de las vistas únicas permite medir con mayor precisión la interacción de los usuarios y el alcance de tu paywall, ya que se centra en usuarios individuales en lugar de en el número total de visitas. :::warning Asegúrate de enviar las visualizaciones del paywall a Adapty usando el método `.logShowFlow()` (iOS SDK v4+) / `.logShowPaywall()`. De lo contrario, las visualizaciones del paywall no se contabilizarán en las métricas y las conversiones no serán relevantes. ::: --- # File: migrate-paywalls --- --- title: "Migrar paywalls entre apps" description: "Aprende cómo migrar paywalls de otras apps en Adapty." --- Con Adapty no necesitas crear un paywall desde cero para cada app. Si gestionas varias apps, puedes migrar la configuración del Paywall Builder de cualquier paywall creado con el builder de una app a otra. La migración copia toda la configuración visual: - Ajustes de diseño del paywall y de todos sus elementos - Contenido multimedia - Localización La migración solo aplica a la configuración del builder y no copia los productos ni el Remote Config. :::note Si migras una configuración del Paywall Builder que usa fuentes personalizadas, pruébalas en un dispositivo, ya que pueden mostrarse incorrectamente. ::: ## Migrar un paywall \{#migrate-paywall\} :::important Solo puedes migrar paywalls creados en el **nuevo** Paywall Builder de Adapty. Para migrar paywalls del builder **legacy**, primero debes migrarlos al nuevo Paywall Builder. ::: Para migrar una configuración del Paywall Builder: 1. **Para un paywall nuevo**: Comienza la [creación del paywall](create-paywall) y añade productos. Luego haz clic en **Build no-code paywall** para abrir la biblioteca de plantillas. **Para un paywall existente**: Ve a la sección **Layout settings** de la pestaña **Builder & Generator** y haz clic en **Change template**. 2. Haz clic en **Choose paywall** dentro del recuadro **Copy a design from your apps** al editar la plantilla del paywall. <img src="/assets/shared/img/migrate-paywall-builder.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Selecciona la app y el paywall del que quieres copiar la configuración. <img src="/assets/shared/img/migrate-app.png" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Haz clic en **Copy Selected Paywall**. Tras la migración, puedes hacer los cambios que necesites sin que afecten al paywall original. --- # File: duplicate-paywalls --- --- title: "Duplicar paywall" description: "Aprende a gestionar paywalls duplicados y optimizar el rendimiento de los paywalls en Adapty." --- Si necesitas hacer pequeños cambios en un paywall existente de Adapty, especialmente cuando ya se está usando en tu app y no quieres estropear tus analíticas, puedes simplemente duplicarlo. Luego puedes usar esos duplicados para reemplazar los paywalls originales en algunos o todos los placements según necesites. Esto crea una copia del paywall con todos sus detalles, como su nombre, productos y cualquier promoción. Al nombre del nuevo paywall se le añadirá "Copy" para que puedas distinguirlo fácilmente del original. Para duplicar un paywall en el Adapty Dashboard: 1. Abre la sección [**Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty. La página de lista de paywalls del Adapty Dashboard ofrece una vista general de todos los paywalls presentes en tu cuenta. 2. Haz clic en el botón de **3 puntos** junto al paywall y selecciona la opción **Duplicate**. <img src="/assets/shared/img/duplicate.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Ajusta el nuevo paywall y haz clic en el botón **Save**. 4. Adapty te pedirá que reemplaces los paywalls originales por sus duplicados en los placements si el paywall original se está usando actualmente en algún placement. Si eliges **Create and replace original**, los nuevos paywalls pasarán a estar **Live** inmediatamente. También puedes crearlos como nuevos paywalls en estado **Draft** y añadirlos a los placements más adelante. --- # File: archive-paywalls --- --- title: "Archivar paywall" description: "Aprende cómo archivar paywalls obsoletos en Adapty sin perder datos." --- A medida que trabajas con Adapty y ajustas la configuración de tus paywalls, puede que acumules paywalls que ya no encajan con tu estrategia o campañas actuales. Estos paywalls sin uso, dejados como `Inactive`, pueden desordenar tu espacio de trabajo y dificultar encontrar los que realmente importan. Para resolver esto, Adapty ofrece la opción de archivar estos paywalls innecesarios. Archivarlos garantiza que se guarden de forma segura sin eliminarlos permanentemente, listos para consultarse si es necesario en el futuro. Además, los paywalls archivados se pueden filtrar de la vista predeterminada, despejando tu espacio de trabajo y simplificando la interfaz. En esta guía, te explicamos cómo archivar paywalls en Adapty de forma eficiente para que tengas mayor control sobre la gestión de tus paywalls. Un recordatorio importante: los paywalls activos que estén en uso en al menos un placement no se pueden archivar. Si quieres archivar uno de esos paywalls, primero elimínalo de todos los placements. :::note No puedes archivar un paywall si está siendo utilizado en una prueba A/B no archivada. De este modo, el usuario puede ver las métricas detalladas de una prueba A/B completada, y el paywall vinculado forma parte de esos datos. ::: **Para archivar un paywall:** 1. Abre la sección [**Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty. 2. Haz clic en el botón **3-dot** junto al paywall y selecciona la opción **Archive**. <img src="/assets/shared/img/archive-paywall.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Cuando estés en la ventana **Archive paywall**, escribe el nombre del paywall que deseas archivar y haz clic en el botón **Archive**. --- # File: restore-paywall --- --- title: "Restaurar paywall desde el archivo" description: "Restaura paywalls en Adapty para garantizar servicios de suscripción ininterrumpidos para los usuarios." --- La posibilidad de archivar paywalls es una funcionalidad muy útil para simplificar la gestión de tus paywalls. Te permite ocultar los paywalls que ya no necesitas, reduciendo el desorden en tu espacio de trabajo. Además, la opción de restaurar paywalls archivados te da flexibilidad para reincorporarlos a tu estrategia si vuelven a ser útiles. Los paywalls archivados pueden estar excluidos de la vista predeterminada. Para verlos, selecciona **Archived** en el filtro **State**. **Para devolver un paywall desde el archivo** 1. Abre la sección [**Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty. 2. Asegúrate de que los paywalls archivados se muestran en la lista. Si no es así, actualiza el filtro de la derecha. <img src="/assets/shared/img/paywall-filter.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en el botón de **3 puntos** junto al paywall archivado y selecciona **Back to active**. <img src="/assets/shared/img/restore-paywall.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: profiles-crm --- --- title: "Perfiles/CRM" description: "Gestiona perfiles de usuario y datos de CRM en Adapty para mejorar la segmentación de audiencias." --- Profiles es un CRM para tus usuarios. Con Profiles puedes: 1. Encontrar usuarios concretos por profile ID, customer user ID, email o transaction ID. 2. Ver la línea de tiempo de eventos del usuario, incluidos problemas de facturación, períodos de gracia y otros [eventos](events). 3. Analizar las propiedades del usuario, como el estado de suscripción, ingresos totales y más. 4. Conceder al usuario una suscripción. :::note Los eventos del feed de eventos llegan al dashboard con un retraso. Los nuevos perfiles y los cambios de atributos pueden no ser visibles de inmediato. ::: <img src="/assets/shared/img/profiles.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::link Para entender cómo Adapty crea y vincula perfiles de usuario, consulta [Cómo funcionan los perfiles](how-profiles-work). ::: ## Búsqueda de usuarios \{#finding-users\} En la lista de Profiles puedes buscar un usuario concreto por: - **Profile ID**: el identificador interno de Adapty para el usuario (también llamado Adapty ID). - **Customer user ID**: el identificador de tu app para el usuario, si lo has configurado. - **Email**: el email del usuario, si se ha enviado como atributo personalizado. - **Transaction ID**: el transaction ID del store de una compra. Haz clic en cualquier fila para abrir el perfil completo del usuario. ## Estado de suscripción \{#subscription-state\} En la lista de Profiles puedes filtrar y ordenar usuarios por estado de suscripción. Los valores posibles son: | **Estado** del usuario | Descripción | | :--------------------- | :----------------------------------------------------------- | | Subscribed | El usuario tiene una suscripción activa con la renovación automática habilitada. | | Auto-renew off | El usuario desactivó la renovación automática, pero sigue teniendo acceso a las funciones premium hasta que finalice el período de suscripción. | | Subscription cancelled | El usuario canceló su suscripción y esta ha finalizado por completo. | | Billing issue | No se pudo cobrar al usuario debido a un problema de facturación, ya sea tras el vencimiento de su suscripción o del período de prueba. | | Grace period | El usuario se encuentra actualmente en un período de gracia debido a un problema de facturación que ocurrió al intentar cobrarle tras el vencimiento de su suscripción o del período de prueba. | | Active trial | El usuario tiene una suscripción activa que se encuentra actualmente en su período de prueba. | | Trial cancelled | El usuario canceló el período de prueba y no tiene una suscripción activa. | | Never subscribed | El usuario nunca se ha suscrito ni ha iniciado un período de prueba, y sigue siendo un usuario freemium. | ## Atributos de usuario \{#user-attributes\} <img src="/assets/shared/img/ce8df4d-CleanShot_2023-06-26_at_20.32.232x.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> Puedes enviar propiedades adicionales del usuario a Adapty mediante el SDK. Por defecto, Adapty establece: | Propiedad | Descripción | | ---------------- | ------------------------------------------------------------ | | Customer user ID | Un identificador de tu usuario final en tu sistema. | | Adapty ID | Identificador interno de Adapty para tu usuario final, llamado Profile ID. | | IDFA | El Identifier for Advertisers, asignado por Apple al dispositivo del usuario. Requiere permiso de App Tracking Transparency (ATT) en iOS 14+. No disponible en Android. | | Country | País de tu usuario final. | | OS | El sistema operativo utilizado por el usuario final. | | Device | El nombre del modelo de dispositivo visible para el usuario final. | | Install date | La fecha en que el usuario se registró por primera vez en Adapty: <ul><li>La fecha en que se creó el usuario. </li><li>Si el usuario instaló tu app antes de que integraras Adapty, la fecha de instalación refleja la fecha de su primera transacción.</li><li>Si corresponde, la fecha proporcionada durante una importación de datos históricos.</li></ul> | | Created at | La fecha en que se creó el usuario. | Envía al menos el ID interno de tu usuario o su email. Esto te permite encontrar usuarios por estos identificadores en la lista de Profiles. Una vez que instales el SDK, Adapty recoge automáticamente los eventos del usuario desde la cola de pagos y los muestra en el perfil del usuario. Los atributos de la tabla anterior se recopilan automáticamente — no necesitas enviarlos. ### Atributos personalizados \{#custom-attributes\} En la sección **Attributes** de un perfil puedes ver los atributos personalizados definidos mediante el SDK o la API. También puedes asignar atributos manualmente usando el botón **Add attribute**. <img src="/assets/shared/img/378c1fb-add_attribute.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> ## Conceder una suscripción \{#granting-a-subscription\} En un perfil puedes ampliar una suscripción activa o conceder al usuario acceso de por vida a un nivel de acceso, sin necesidad de que realice una compra. <img src="/assets/shared/img/b1d74fd-edit_paid_access_level.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> Esto resulta especialmente útil para: - Compensar a un usuario tras un problema de facturación o soporte. - Ejecutar promociones manuales o programas beta. - Probar flujos de suscripción sin una compra real. Para conceder acceso, abre el perfil del usuario, ve a la sección **Access levels** y haz clic en **Edit**. Establece la fecha de vencimiento y guarda. La fecha de vencimiento debe ser futura y no puede reducirse una vez establecida. Ajustarla en suscripciones activas no afecta a los pagos en curso. :::note Conceder acceso no crea eventos de compra en App Store ni en Google Play. El historial de eventos y los análisis del usuario diferirán de un flujo de compra real. ::: También puedes conceder acceso de forma programática mediante el método de API [Grant access level](api-adapty/operations/grantAccessLevel). ## Compartir acceso de pago entre cuentas de usuario \{#sharing-paid-access-between-user-accounts\} :::link Artículo principal: [Compartir acceso de pago entre cuentas de usuario](sharing-paid-access-between-user-accounts) ::: ### Historial de uso compartido de acceso \{#access-sharing-history\} Cuando se comparten o transfieren niveles de acceso, el perfil del usuario muestra un enlace al perfil conectado — el perfil que compartió el acceso o el que lo recibió. Para ver el perfil conectado, en el **Profile** del usuario, haz clic en el enlace situado junto al nivel de acceso. <img src="/assets/shared/img/profile-access-level-origin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Los saldos de moneda virtual no se comparten ni se transfieren entre perfiles como los niveles de acceso. Cada saldo permanece en un único perfil; consulta [Saldos, perfiles y dispositivos](virtual-currency-balance#balances-profiles-and-devices). ::: ## Próximos pasos \{#next-steps\} - Para entender cómo Adapty crea y vincula perfiles, consulta [Cómo funcionan los perfiles](how-profiles-work). - Para configurar la política de compartición de acceso, consulta [Compartir acceso de pago entre cuentas de usuario](sharing-paid-access-between-user-accounts). - Para conceder acceso de forma programática, consulta el método de API [Grant access level](api-adapty/operations/grantAccessLevel). --- # File: how-profiles-work --- --- title: "Cómo funcionan los perfiles" description: "Entiende cómo Adapty crea, rastrea y vincula perfiles de usuario, incluyendo perfiles anónimos, usuarios identificados y relaciones padre/heredero." --- Cada usuario de tu app tiene un perfil de Adapty que registra sus compras, eventos y estado de suscripción. Entender cómo se crean y vinculan los perfiles te ayuda a prevenir errores de integración, evitar la fragmentación de datos e interpretar correctamente la información en la sección [Perfiles](profiles-crm). ## Creación de perfil \{#profile-creation\} Adapty crea automáticamente un perfil la primera vez que un usuario abre tu app. **Sin un Customer User ID**, el perfil es anónimo. Se crea un nuevo perfil anónimo cada vez que: - Un usuario reinstala la app - Un usuario cierra sesión en tu app (cuando tu app llama a `Adapty.logout()`) Las compras están vinculadas a la instalación de la app, no a una identidad de usuario persistente. **Con un Customer User ID**, el perfil persiste entre reinstalaciones y dispositivos. Usar un Customer User ID te permite: 1. Rastrea a un usuario en reinstalaciones y múltiples dispositivos. 2. Busca usuarios por su customer user ID en la sección [**Profiles**](profiles-crm). 3. Usa el customer user ID en la [API server-side](getting-started-with-server-side-api). 4. Adapty envía el customer user ID a todas las integraciones. El comportamiento del perfil con un customer user ID depende de cuándo lo configures: - **Al activar el SDK**: Adapty usa el perfil existente con ese customer user ID (para usuarios que regresan) o crea un nuevo perfil (para usuarios nuevos). - **Después de activar el SDK**: Adapty crea un perfil anónimo al activarse. Cuando identificas al usuario más adelante, Adapty vincula el customer user ID al perfil anónimo (para usuarios nuevos) o cambia al perfil existente con ese ID (para usuarios que regresan). **Qué enfoque usar:** - **ID de usuario disponible al iniciar la app** (por ejemplo, guardado de una sesión anterior): pásalo a `activate()` al inicializar el SDK. - **Los usuarios inician sesión después de abrir la app**: llama a `identify()` tras la autenticación. Adapty vincula el ID al perfil actual (si el ID es nuevo) o cambia al perfil existente (si el ID ya existe). - **Los usuarios pueden comprar antes de iniciar sesión**: llama a `identify()` después del login. Si el customer user ID ya existe en Adapty, recupera el perfil a continuación para sincronizar el nivel de acceso actual. Para más detalles de implementación, consulta la guía del SDK sobre [identificación de usuarios](identifying-users). :::note Si un usuario que regresa usaba tu app anteriormente sin un customer user ID, esos perfiles anónimos no se fusionan automáticamente cuando empiezas a identificar en la activación del SDK. Para mantener el historial completo de esos usuarios, usa `identify()` después del inicio de sesión. ::: ## Perfiles padre e hijo \{#parent-and-inheritor-profiles\} Cuando la misma suscripción de la store está asociada a más de un perfil de Adapty, Adapty trata esos perfiles como una cadena: un perfil **padre** y uno o más perfiles **herederos** que comparten el acceso de la misma compra. Esto ocurre cuando: - [El acceso de pago compartido entre cuentas de usuario](sharing-paid-access-between-user-accounts) está habilitado y un usuario inicia sesión en un dispositivo donde un perfil diferente realizó previamente la compra. - Un usuario reinstala la app sin `customer_user_id`, y el nuevo perfil recoge la compra de la instalación anterior. - Diferentes usuarios identificados restauran compras en el mismo dispositivo. - Una app se transfiere entre Team IDs de Apple y la nueva app recoge las compras realizadas con el antiguo Team ID. **Cómo se selecciona el perfil principal.** El perfil padre es el **primer perfil en registrar la compra** — determinado por el orden de los recibos de compra en Adapty, no por el orden de creación del perfil. Por ejemplo: instalas la app y no realizas ninguna compra, luego la reinstales y compras una suscripción. El segundo perfil se convierte en el padre porque realizó la compra. El primer perfil se convierte en el heredero y obtiene acceso a través del uso compartido. **Cómo se distribuyen los eventos:** - **Eventos transaccionales** (compras, renovaciones, cancelaciones, problemas de facturación, períodos de gracia, reembolsos): Aparecen únicamente en el **perfil principal** que realizó la compra. Todas las renovaciones y actualizaciones de suscripción siguen apareciendo en ese perfil. - **Eventos `access_level_updated`**: Aparecen en **el perfil principal y en los perfiles herederos** cada vez que cambia el estado del nivel de acceso. Esto mantiene todos los perfiles vinculados al día sobre su estado de acceso actual. El perfil padre muestra el historial completo de transacciones. Los perfiles herederos solo muestran sus actualizaciones de nivel de acceso y un enlace al perfil padre en la sección **Access level**. <img src="/assets/shared/img/98d0dad-non-original_profile.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> **Seguimiento de la misma suscripción en varios perfiles.** Cada perfil heredero tiene su propio `profile_id`, por lo que este no es estable a lo largo de una cadena. Para identificar la misma suscripción en varios perfiles —por ejemplo, al reconciliar eventos de webhook o relacionar perfiles del dashboard con un mismo usuario subyacente— utiliza el identificador del lado del store. | Campo | Usar para | | --- | --- | | `store_original_transaction_id` | Identificar una cadena de suscripciones entre perfiles. Único por suscripción de Apple. | | `profiles_sharing_access_level` (campo de webhook) | Todos los perfiles con nivel de acceso activo gracias a la suscripción, cuando el uso compartido está habilitado. | | `profile_id` | **No** es adecuado para el seguimiento entre perfiles: cada heredero tiene el suyo propio. | ## Transacciones sin perfil \{#transactions-without-profiles\} Algunas transacciones en Adapty no están asociadas a ningún perfil: aparecen en los análisis y exportaciones, pero no en la lista de perfiles. Esto ocurre con las **notificaciones S2S (servidor a servidor) del store** recibidas para usuarios cuyas cuentas nunca se conectaron a tu app mediante el SDK de Adapty. Las fuentes conocidas son: - Notificaciones S2S del App Store (incluidos los eventos de reembolso) - Notificaciones S2S de Google Play - Eventos de webhook de Stripe y Paddle Estas transacciones: - **Aparecen en los gráficos de análisis** (cuentan para las métricas globales) - **Aparecen en las exportaciones** (S3, GCS, BigQuery) con `profile_id` establecido como `null` - **No aparecen en la lista de Perfiles** — no hay ningún perfil al que asociarlos Si ves más eventos en los análisis o las exportaciones de los que puedes encontrar en la interfaz de Perfiles, la diferencia probablemente se deba a estas transacciones sin perfil. Para encontrarlas en una exportación, filtra las filas donde `profile_id IS NULL`. ## Compartir acceso de pago entre cuentas de usuario \{#sharing-paid-access-between-user-accounts\} :::link Artículo principal: [Compartir acceso de pago entre cuentas de usuario](sharing-paid-access-between-user-accounts) ::: Para configurar tu política de compartición de nivel de acceso, en la página de configuración [**General**](general), selecciona una opción de sharing. Puedes establecer una política separada para el [entorno sandbox](test-purchases-in-sandbox). **Activado (predeterminado)** Los usuarios identificados (aquellos con un [Customer User ID](identifying-users#set-customer-user-id-on-configuration)) pueden compartir el mismo [nivel de acceso](access-level) proporcionado por Adapty si su dispositivo está conectado al mismo Apple/Google ID. Esto es útil cuando un usuario reinstala la app e inicia sesión con un correo diferente: seguirá teniendo acceso a su compra anterior. Con esta opción, varios usuarios identificados pueden compartir el mismo nivel de acceso. Aunque el nivel de acceso se comparte, todas las transacciones pasadas y futuras se registran como eventos en el Customer User ID original para mantener una analítica coherente y conservar un historial completo de transacciones — incluidos períodos de prueba, compras de suscripciones, renovaciones y más, vinculadas al mismo perfil. **Transferir acceso al nuevo usuario** Los usuarios identificados pueden seguir accediendo al [nivel de acceso](access-level) proporcionado por Adapty, incluso si inician sesión con un [Customer User ID](identifying-users#set-customer-user-id-on-configuration) diferente o reinstalan la app, siempre que el dispositivo esté conectado al mismo Apple/Google ID. A diferencia de la opción anterior, Adapty transfiere la compra entre usuarios identificados. Esto garantiza que el contenido adquirido esté disponible, pero solo un usuario puede tener acceso a la vez. Por ejemplo, si UserA compra una suscripción y UserB inicia sesión en el mismo dispositivo y restaura las transacciones, UserB obtendrá acceso a la suscripción y se le revocará a UserA. Si uno de los usuarios (ya sea el nuevo o el antiguo) no está identificado, el nivel de acceso seguirá compartiéndose entre esos perfiles en Adapty. Aunque el nivel de acceso se transfiere, todas las transacciones pasadas y futuras se registran como eventos en el Customer User ID original para mantener una analítica coherente y conservar un historial completo de transacciones — incluidos períodos de prueba, compras de suscripciones, renovaciones y más, vinculadas al mismo perfil. Tras activar **Transferir acceso al nuevo usuario**, los niveles de acceso no se transferirán entre perfiles de forma inmediata. El proceso de transferencia para cada nivel de acceso específico solo se activa cuando Adapty recibe un evento del store, como una renovación de suscripción, una restauración o al validar una transacción. **Desactivado** El primer perfil de usuario identificado que obtenga un nivel de acceso lo conservará de forma permanente. Esta es la mejor opción si tu lógica de negocio requiere que las compras estén vinculadas a un único Customer User ID. Ten en cuenta que los niveles de acceso siguen compartiéndose entre usuarios anónimos. Puedes "desvincular" una compra [eliminando el perfil del usuario propietario](https://adapty.io/docs/es/api-adapty/operations/deleteProfile). Tras la eliminación, el nivel de acceso queda disponible para el primer perfil de usuario que lo reclame, ya sea anónimo o identificado. Desactivar el uso compartido solo afecta a los nuevos usuarios. Las suscripciones que ya se comparten entre usuarios seguirán compartiéndose aunque se desactive esta opción. :::warning Apple y Google exigen que las compras in-app se compartan o transfieran entre usuarios porque se basan en el Apple/Google ID para asociar la compra. Sin el uso compartido, restaurar las compras podría no funcionar en reinstalaciones posteriores. Desactivar el uso compartido puede impedir que los usuarios recuperen el acceso después de iniciar sesión. Recomendamos desactivar el uso compartido solo si tus usuarios **están obligados a iniciar sesión** antes de realizar una compra. De lo contrario, un usuario identificado podría comprar una suscripción, iniciar sesión en otra cuenta y perder el acceso de forma permanente. ::: ### ¿Qué opción debo elegir? \{#which-setting-should-i-choose\} | Mi app... | Opción a elegir | | ------------------------------------------------------------ | ------------------------------------------------------------ | | No tiene sistema de inicio de sesión y solo utiliza los IDs de perfil anónimos de Adapty. | Usa la opción predeterminada, ya que los niveles de acceso siempre se comparten entre IDs de perfil anónimos en las tres opciones. | | Tiene un sistema de inicio de sesión opcional y permite a los clientes realizar compras antes de crear una cuenta. | Elige **Transferir acceso al nuevo usuario** para garantizar que los clientes que compren sin cuenta puedan restaurar sus transacciones más adelante. | | Requiere que los clientes creen una cuenta antes de comprar, pero permite que las compras estén vinculadas a varios Customer User IDs. | Elige **Transferir acceso al nuevo usuario** para garantizar que solo un Customer User ID tenga acceso a la vez, permitiendo además que los usuarios inicien sesión con un Customer User ID diferente sin perder su acceso de pago. | | Requiere que los clientes creen una cuenta antes de comprar, con reglas estrictas que vinculan las compras a un único Customer User ID. | Elige **Desactivado** para garantizar que las transacciones nunca se transfieran entre cuentas. | ## Marcas de tiempo de eventos con fechas futuras (Apple/iOS) \{#event-timestamps-with-future-dates-appleios\} Este comportamiento es exclusivo de la App Store de Apple. El sistema de notificaciones de Google Play no envía eventos con antelación. Las marcas de tiempo de eventos en los perfiles e integraciones pueden mostrar fechas futuras porque Apple envía los eventos de renovación por adelantado. - **Por qué ocurre**: Apple hace esto para garantizar que las suscripciones se renueven automáticamente antes de que expiren, evitando interrupciones en el servicio del usuario. Para más detalles, consulta el Apple Developer Forum: [Server Notifications for Subscriptions](https://developer.apple.com/forums/tags/app-store-server-notifications). - **Tipos de eventos afectados**: Por lo general, esto aplica a las renovaciones de suscripción y las conversiones de prueba a pago. Estos eventos pueden tener marcas de tiempo futuras porque Apple notifica a los sistemas con antelación. - **Otros tipos de eventos**: Las compras in-app adicionales y los cambios de plan de suscripción se registran con sus marcas de tiempo reales, ya que estos eventos no se pueden predecir con antelación. - **Impacto en Analytics y el Event Feed**: Estos eventos solo aparecerán en **Analytics** y el **Event Feed** una vez que sus marcas de tiempo hayan pasado. Los eventos con marcas de tiempo futuras no se muestran en ninguna de las dos secciones. - **Impacto en las integraciones**: Adapty envía los eventos a las integraciones en cuanto los recibe. Si un evento tiene una marca de tiempo futura, Adapty lo envía a tu integración con esa marca de tiempo futura sin modificar. ## Pasos siguientes \{#next-steps\} - Para usar el dashboard de Profiles para encontrar y gestionar usuarios, consulta [Profiles](profiles-crm). - Para configurar la identificación de usuarios en tu app, consulta la guía de SDK para [identificar usuarios](identifying-users). - Para configurar la política de compartición de acceso, consulta [Compartir acceso de pago entre cuentas de usuario](sharing-paid-access-between-user-accounts). --- # File: sharing-paid-access-between-user-accounts --- --- title: "Compartir acceso de pago entre cuentas de usuario" description: "Cómo compartir el acceso de pago entre diferentes cuentas de usuario para usuarios con varios dispositivos o múltiples perfiles en la app" --- Cuando un usuario realiza una compra, Adapty asigna un nuevo [nivel de acceso](access-level) a su [perfil](identifying-users) activo. Este nivel de acceso autoriza al comprador a acceder al contenido de pago. El perfil del comprador puede cambiar involuntariamente si reinstala la app o inicia sesión en una nueva cuenta dentro de la app. Para garantizar un acceso ininterrumpido, Adapty comparte automáticamente el nivel de acceso del usuario entre el perfil original y los que le siguen. Este enfoque funciona bien para la mayoría de las aplicaciones. Sin embargo, si tu lógica de negocio lo requiere, puedes seleccionar una política de compartición de acceso de pago más restrictiva. Abre la página de [General Settings](https://app.adapty.io/settings/general) para configurar una política de compartir niveles de acceso. Para facilitar las pruebas, puedes cambiar esta configuración solo para el [entorno sandbox](#sharing-paid-access-on-sandbox). <Details> :::important Si tu aplicación no autentica usuarios, puedes ignorar esta configuración. Los perfiles anónimos asociados a la misma cuenta de la store *siempre* comparten su nivel de acceso. ::: <summary>¿Qué política de compartir acceso debo elegir? (Haz clic para expandir)</summary> | Mi aplicación... | Mejor opción | | ------------------------------------------------------------ | ------------------------------------------------------------ | | No tiene capacidades de autenticación y solo usa los IDs de perfil anónimos de Adapty. | Usa la opción **Enabled (default)**. | | Permite autenticar usuarios, pero les permite hacer compras sin una cuenta. | Activa la opción **Transfer access to new user**. Los usuarios podrán registrarse y reclamar las compras anónimas. | | Requiere que los clientes creen una cuenta antes de comprar, pero puede vincular un único producto a varios Customer User IDs. | Activa la opción **Transfer access to new user**. Varias cuentas podrán acceder al producto, pero solo de forma secuencial. | | Requiere que los clientes creen una cuenta antes de comprar, con reglas estrictas que vinculan las compras a un único Customer User ID. | **Desactiva** el uso compartido del nivel de acceso. | </Details> <img src="/assets/shared/img/sharing-paid-access.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Habilitado (por defecto) \{#enabled-default\} Esta configuración funciona mejor para aplicaciones **sin autenticación integrada**. Tras la compra, todos los perfiles asociados a la misma cuenta del store heredan automáticamente el nivel de acceso. * Si un usuario inicia sesión en tu app con un nuevo conjunto de credenciales, conserva el acceso al contenido de pago. * Si un usuario reinstala tu aplicación tras un restablecimiento de fábrica, conserva el acceso al contenido de pago. * Si un usuario instala la aplicación en otros dispositivos con la misma cuenta de la store, la compra estará disponible en todos los dispositivos, incluso si cada instancia de la aplicación tiene su propio perfil de cliente. ## Transferir el acceso a un nuevo usuario \{#transfer-access-to-new-user\} Esta configuración es ideal para aplicaciones que permiten compras **con o sin autenticación**, o que quieren aplicar una política de **un dispositivo por usuario**. Adapty limita el acceso a las compras a 1 customer ID a la vez. El propietario del dispositivo puede reinstalar la app, iniciar y cerrar sesión, pero no puede acceder al mismo producto desde más de un customer ID al mismo tiempo. Con esta opción activada, los perfiles anónimos (por ejemplo, un perfil que se activa cuando el usuario cierra sesión) siempre heredan el nivel de acceso del último ID de cliente activo. Esto es necesario para evitar que se pierda el acceso más adelante. :::warning Cuando desactivas la configuración predeterminada y activas **Transfer access to new user**, Adapty no actualiza inmediatamente los niveles de acceso de los perfiles de cliente existentes. El cambio se produce cuando el usuario genera un nuevo evento en el store: por ejemplo, renueva la suscripción o restaura sus compras. ::: :::important Adapty revoca el perfil antiguo solo cuando el nuevo perfil tiene un [Customer User ID](identifying-users#set-customer-user-id-on-configuration) en el momento en que el SDK propaga la transacción. Si `restorePurchases` se ejecuta en un perfil anónimo, tanto el antiguo Customer User ID como el nuevo perfil anónimo obtendrán el nivel de acceso. El perfil antiguo se revoca más adelante, cuando identificas el perfil anónimo. Para evitarlo, llama a los métodos del SDK en este orden: `activate` → `identify` → `restorePurchases`. ::: ## Desactivar el acceso compartido de pago \{#disable-paid-access-sharing\} Esta configuración **solo es adecuada** para aplicaciones con **autenticación obligatoria** o una implementación propia de gestión de accesos. En cualquier otro caso, los usuarios podrían no poder acceder a sus compras, y tu aplicación corre el riesgo de **no superar la revisión obligatoria del store**. Si deshabilitas el acceso compartido de pago, Adapty vincula el producto al [ID de cliente](identifying-users#set-customer-user-id-on-configuration) activo en el momento de la compra y no comparte el nivel de acceso con ningún otro perfil. Esta política permite una distribución estricta de productos en proporción 1 a 1. :::warning Al deshabilitar el acceso compartido de pago, impides que los IDs de cliente hereden el acceso de pago. Si un ID de cliente ya heredó acceso de pago en el pasado, ese acceso no se puede revocar automáticamente. ::: :::important En situaciones de emergencia, puede que necesites [eliminar un perfil de usuario](api-adapty/operations/deleteProfile) para que el siguiente perfil disponible (ya sea identificado o anónimo) pueda reclamar su nivel de acceso. ::: ## Referencia práctica \{#practical-reference\} Una vez elegido un modo, los contratos a continuación describen qué esperar: qué perfiles verán el acceso, cuándo lo pierde el perfil anterior y qué eventos de webhook se disparan. | Modo | ¿Varios perfiles comparten una compra? | ¿Se revoca el perfil antiguo al transferir? | Cuándo se revoca el perfil antiguo | Eventos de webhook cuando un segundo perfil reclama la suscripción | | --- | --- | --- | --- | --- | | **Habilitado (por defecto)** | Sí — todos los perfiles que restauran o inician sesión heredan el acceso | Nunca | N/A | `access_level_updated` (`is_active=true`) por cada nuevo perfil que hereda | | **Transferir acceso al nuevo usuario** | No — exclusivo, pero se puede mover entre perfiles | Sí | Inmediatamente cuando el nuevo dispositivo identificado propaga la transacción (`restorePurchases`, identify, o el siguiente evento del store) | Perfil nuevo: `access_level_updated` (`is_active=true`). Perfil antiguo: `access_level_updated` (`is_active=false`) | | **Deshabilitado** | No — un Customer User ID por compra, de forma permanente | N/A — el acceso nunca se transfiere | N/A | Ninguno en el segundo perfil. El SDK no muestra acceso para ese perfil | ## Compartir acceso de pago en sandbox \{#sharing-paid-access-on-sandbox\} Puedes establecer una política de compartición de acceso de pago específicamente para el entorno sandbox. Al probar compras en sandbox, ten en cuenta el siguiente comportamiento: * Apple almacena información sobre tus compras anteriores en el historial de compras de la cuenta. El SDK de Adapty también puede acceder a él. * Si reinstalaas la aplicación y Adapty detecta que el producto ya fue comprado, el perfil activo heredará el nivel de acceso. * Si Apple detecta una compra existente para el producto, no permitirá realizar la misma compra dos veces, aunque el perfil activo no tenga el nivel de acceso necesario. Este comportamiento se produce **independientemente de la configuración de acceso de pago compartido**. Si tu app no muestra el paywall, no puedes comprar el producto. La única solución es **borrar el historial de compras de tu cuenta**. Consulta la [guía de pruebas en sandbox](test-purchases-in-sandbox) para obtener instrucciones detalladas. :::warning Las suscripciones sandbox en Apple se renuevan automáticamente cada pocos minutos. Estas renovaciones rápidas pueden cambiar qué perfil trata Adapty como [principal](how-profiles-work#parent-and-inheritor-profiles) — un patrón de cadena que raramente se reproduce en producción. Prueba el modo que usas en producción y confirma el comportamiento con un Apple ID real antes de sacar conclusiones del sandbox. ::: ## Compartición de acceso de pago en los análisis \{#paid-access-sharing-in-analytics\} * Adapty registra las transacciones a medida que se producen. Una misma transacción puede estar asociada a más de un perfil, pero no se contabiliza más de una vez. * Si dos o más perfiles comparten el mismo nivel de acceso, la compra se atribuye al [perfil principal](how-profiles-work#parent-and-inheritor-profiles). * La herencia del nivel de acceso no afecta a las estadísticas de instalación. Para saber cómo Adapty contabiliza las instalaciones, puedes seleccionar una de las dos [definiciones de instalación](installs#calculation) disponibles en la página de configuración. --- # File: segments --- --- title: "Segmentos" description: "Crea y gestiona segmentos de usuarios para una mejor segmentación en Adapty." --- Un **segmento** es un conjunto de filtros que agrupa usuarios con características comunes. Usa segmentos para dirigir paywalls y pruebas A/B de forma más efectiva. :::note Los eventos del feed de eventos llegan al dashboard con un retraso. Los nuevos perfiles y los cambios de atributos pueden no ser visibles de inmediato. ::: Después de crear un segmento, puedes [usarlo como **audiencia** en Placements y pruebas A/B](audience) para controlar qué paywall ven los usuarios (uno o varios). Ejemplos: - Muestra un paywall estándar a los no suscriptores y ofrece un descuento a los usuarios que hayan cancelado previamente una suscripción o prueba. - Muestra paywalls diferentes a usuarios de distintos países. - Segmenta usuarios basándote en datos de atribución de Apple Search Ads. - Asegúrate de que los usuarios en versiones antiguas de la app sigan viendo el paywall actual, mientras que las versiones más recientes reciban el actualizado. - [En Analytics](controls-filters-grouping-compare-proceeds#filter-and-group-data), filtra por segmentos para ver el rendimiento de grupos de usuarios específicos. Agrupa por segmento para comparar el rendimiento o la contribución dentro de **All users**. <img src="/assets/shared/img/3244407-Segments.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Creación \{#creation\} Para crear un segmento, introduce un nombre y selecciona los atributos que definen sus filtros. Si seleccionas varios atributos, los usuarios deben cumplir todas las condiciones. Adapty aplica lógica AND entre atributos. <img src="/assets/shared/img/1af9744-new_cohort.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Atributos disponibles \{#available-attributes\} :::note Aunque muchos atributos de usuario se establecen automáticamente (como **Country** o **Calculated total revenue USD**), los atributos **Age**, **App user ID**, **Attribution**, **Gender** y **Custom attributes** no se definen automáticamente. Debes [establecer los atributos de usuario](setting-user-attributes) o [pasar los datos de atribución](attribution-integration) si quieres usarlos para segmentación. ::: :::tip Para los atributos basados en fechas, puedes filtrar usando: - **Fecha fija**: Selecciona fechas concretas en un calendario (por ejemplo, mostrar una oferta especial a usuarios que instalaron la app entre el Black Friday y el Cyber Monday) - **Rango relativo**: Define ventanas de tiempo dinámicas como "Últimos 7 días" o "Últimos 3 meses" (por ejemplo, volver a captar usuarios que no se han visto en más de 30 días, o apuntar a instalaciones recientes) Los rangos relativos se actualizan automáticamente, lo que los hace ideales para campañas continuas. Las fechas fijas funcionan mejor para promociones acotadas en el tiempo. ::: | Atributo | Filtrar por | |---------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Age** | La edad del usuario. Ten en cuenta que la edad se calcula cuando Adapty la recibe por primera vez y no se actualiza posteriormente. | | **App User ID** | El identificador del usuario en tu app ([customer_user_id](profiles-crm#user-attributes)). Puedes filtrar por su presencia o ausencia, por ejemplo, para mostrar un paywall solo a usuarios que no han iniciado sesión. | | **App version (current)** | La versión actual de la app instalada en el dispositivo del usuario donde Adapty recibió datos de eventos por última vez — **se actualiza a medida que el usuario actualiza la app**, por lo que siempre refleja la versión que está usando en este momento. Úsalo cuando quieras llegar a todos los usuarios que ejecutan una versión específica, incluidos los que actualizaron desde una versión anterior. Al crear un segmento, selecciona el icono de lápiz junto a **App version** y añade una nueva versión para poder usarla de inmediato.<br/> La condición **version > X.X** te permite medir el impacto en la conversión de todas las versiones de la app anteriores o posteriores a una específica sin tener que listar cada versión individualmente.<br/><br/> **Formato:** Las cadenas de versión deben seguir el formato [SemVer](https://semver.org/). Los ceros a la izquierda en cualquier parte no son válidos — `26.03.4` no coincidirá, mientras que `26.3.4` sí. Las versiones no válidas se excluyen silenciosamente del segmento. | | **App version (on install)** | La versión de la app instalada en el dispositivo del usuario cuando Adapty recibió datos de eventos por primera vez — **fijada en el momento de la instalación y nunca actualizada**, incluso después de que el usuario actualice la app. Úsalo para segmentar usuarios según la versión que instalaron originalmente, no su versión actual. `App version (on install) = 1.5.7` solo coincide con usuarios cuya primera instalación fue 1.5.7 y excluye silenciosamente a quienes actualizaron a 1.5.7 desde una versión anterior — para capturar también a quienes actualizaron, usa **App version (current)**.<br/><br/> **Formato:** Las cadenas de versión deben seguir el formato [SemVer](https://semver.org/). Los ceros a la izquierda en cualquier parte no son válidos — `26.3.04` no coincidirá, mientras que `26.3.4` sí. Las versiones no válidas se excluyen silenciosamente del segmento. | | **Attribution: Ad Group** | El grupo de anuncios de atribución. | | **Attribution: Ad Set** | El conjunto de anuncios de atribución. | | **Attribution: Campaign** | El nombre de la campaña de marketing. | | **Attribution: Creative** | La palabra clave creativa de atribución. | | **Attribution: Channel** | El nombre del canal de marketing. | | **Attribution: Source** | El origen de la atribución. | | **Attribution: Status** | El estado de la atribución. Valores posibles: <ul><li> **Organic** – El usuario instaló la app sin ninguna influencia de marketing de pago (p. ej., búsqueda directa en el App Store/Google Play, recomendaciones boca a boca o alcance orgánico en redes sociales).</li><li> **Non-organic** – El usuario fue captado a través de un canal de marketing de pago (p. ej., anuncios, campañas de influencers, programas de referidos).</li><li> **Unknown** – No hay datos de atribución disponibles para este usuario.</li></ul> | | **Calculated subscription state** | El [estado actual de la suscripción](profiles-crm#subscription-state) del usuario, que indica si la suscripción está activa, cancelada o si hubo un problema de facturación que sigue sin resolverse. | | **Calculated total revenue USD** | Los ingresos totales generados por este usuario. | | **Country** | El país del cliente, determinado por su dirección IP más reciente. Adapty actualiza la señal de IP como máximo una vez por semana, por lo que puede desviarse si el usuario cambia de ubicación o usa una VPN. Para segmentar por el país de la cuenta del App Store / Play Store del usuario, usa **Country from store account**. | | **Country from store account** | El país asociado a la cuenta de la store iOS o Android del usuario. Ten en cuenta que Adapty solo recoge el país de la store para dispositivos iOS con la versión 13 o posterior. | | **Creation date** | La fecha en que se creó el perfil (cuando la app se instaló por primera vez en el dispositivo del usuario). | | **Device** | Tipo de dispositivo basado en metadatos. Por ejemplo, 'Samsung Galaxy' o 'iPhone 13'. | | **Gender** | El género del usuario. Ten en cuenta que el valor lo estableces tú mismo. | | **Installation date** | La fecha en que el usuario instaló la app. | | **Language** | El idioma del dispositivo del usuario. <Callout type="warning">Adapty almacena el idioma como código `ISO 639-1` de 2 letras. No uses configuraciones regionales extendidas como `zh-Hant-TW` o `pt-BR`. Pueden aparecer en el desplegable pero no coinciden con ningún usuario.</Callout> <Callout type="tip">Para acotar aún más la segmentación por idioma, combina **Language** con **Country**. Por ejemplo, **Simplified Chinese (`zh`)** + **Country = TW, HK, MO** se dirige a usuarios con el sistema de escritura en chino tradicional.</Callout> | | **Last seen** | La última fecha en que el usuario abrió la app. | | **OS** | La versión del sistema operativo del dispositivo del usuario. | | **Paid access level** | El nivel de acceso otorgado al usuario. | | **Platform** | La plataforma del dispositivo del usuario. Valores posibles: `iOS`, `macOS`, `iPadOS`, `visionOS`, `Android`. <br/> Si los usuarios acceden a tu app desde varias plataformas (p. ej., iOS y Android), la pertenencia al segmento se evalúa por separado para cada plataforma usando los datos más recientes de ese dispositivo específico. Esto permite una segmentación por plataforma incluso para el mismo perfil de usuario. | | **Subscription expiration date** | La fecha de vencimiento de la suscripción o su presencia/ausencia. Muestra `none` para compras de por vida y permanece vacío si el usuario tiene un perfil pero nunca ha tenido una prueba, suscripción o compra de por vida. | | **Subscription product** | El ID del producto más reciente de la suscripción activa del cliente. | | **[Custom attributes](profiles-crm#custom-attributes)** | Define tus propios atributos para crear segmentos muy específicos basados en propiedades únicas de tu app o negocio. | ## Atributos personalizados \{#custom-attributes\} Define atributos personalizados para crear segmentos más precisos basados en propiedades únicas de tu app o negocio. :::note - Puedes configurar atributos personalizados en el SDK o en el Adapty Dashboard. Para la configuración mediante SDK, sigue las instrucciones [aquí](setting-user-attributes#custom-user-attributes). - Cambiar un atributo personalizado después de usarlo en un segmento puede desincronizar al usuario de ese segmento en [analytics](controls-filters-grouping-compare-proceeds#filter-and-group-data). Los datos reflejarán el valor anterior. ::: ### Cómo configurar un atributo personalizado \{#how-to-configure-a-custom-attribute\} En el Adapty Dashboard, selecciona **Create custom attributes** en el menú desplegable de atributos. <img src="/assets/shared/img/883d3b2-CleanShot_2023-03-16_at_17.20.452x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> | Campo | Descripción | | ------ |--------------------------------------------------------------------------------------------------------------------------------------| | **Name** | Una etiqueta para el atributo personalizado, usada solo en el Adapty Dashboard. | | **Key** | Un identificador único para el atributo. Debe coincidir con la clave utilizada en el SDK. | | **Type** | Elige entre:<ul><li>String: Requiere una lista predefinida de valores posibles.</li><li>Number: Solo acepta valores numéricos.</li></ul> | | **Values** | Si seleccionas `String`, introduce la lista de valores posibles. Si eliges `Number`, el atributo solo aceptará valores numéricos. Los atributos numéricos admiten decimales y pueden usarse con operadores de comparación. | Después de rellenar los campos obligatorios, puedes usar los atributos personalizados en tus segmentos, [pruebas A/B](ab-tests) y mucho más. Cada perfil puede tener hasta 30 atributos personalizados. ## Total de usuarios y muestra aleatoria \{#total-number-and-random-sample\} Una vez creado un segmento, Adapty muestra el número total de usuarios que cumplen los criterios del segmento. Adapty también muestra una muestra aleatoria de 40 usuarios que se ajustan a los criterios. Úsala para comprobar tu segmento y asegurarte de que está configurado correctamente. <img src="/assets/shared/img/segment-random-set.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Duplicar segmentos \{#duplicate-segments\} Si necesitas un segmento similar a uno existente, duplícalo en lugar de crearlo desde cero. Esto ahorra tiempo a los equipos que gestionan varias campañas o pruebas A/B con grupos de usuarios que se solapan. Duplicar un segmento crea una copia con todos sus filtros y descripción. Al nombre del nuevo segmento se le añade "(copy)" para que puedas distinguirlo del original. El nuevo segmento es independiente del original: los cambios en uno no afectan al otro. Para duplicar un segmento en el Adapty Dashboard: 1. Abre la sección **Profiles & Segments** en el menú principal de Adapty y cambia a la pestaña [**Segments**](https://app.adapty.io/segments). 2. Haz clic en el botón de **3 puntos** junto al segmento y selecciona **Duplicate**. <img src="/assets/shared/img/duplicate-segment.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Abre el nuevo segmento y ajusta sus filtros según sea necesario. ## Eliminar segmentos \{#delete-segments\} Cuando ya no necesites un segmento, puedes eliminarlo de forma permanente. Adapty bloquea la eliminación si el segmento se está usando como audiencia en alguno de los siguientes casos: - **Un placement**: Al menos un placement no eliminado usa el segmento como audiencia. - **Una prueba A/B (activa o completada)**: Al menos una prueba A/B no eliminada usa el segmento como audiencia. Para eliminar segmentos, Adapty considera como activas tanto las pruebas A/B **Live** como las **Completed**. Una prueba completada sigue utilizando la audiencia para mostrar el paywall o onboarding posterior a la prueba a los usuarios que coincidan, y las métricas históricas de la prueba están vinculadas a ese segmento. El segmento solo se libera cuando se elimina la propia prueba A/B. :::warning La eliminación de un segmento es permanente. El segmento no se puede restaurar. ::: Para eliminar un segmento en el Adapty Dashboard: 1. Ve a **Profiles & Segments** en el menú principal de Adapty y cambia a la pestaña [**Segments**](https://app.adapty.io/segments). 2. Haz clic en el botón de **3 puntos** junto al segmento y selecciona **Delete**. 3. Escribe el nombre del segmento en el campo de confirmación y haz clic en **Delete forever**. :::info Si el segmento está en uso, el cuadro de diálogo muestra los placements y las pruebas A/B que lo referencian. Para desbloquear la eliminación, abre cada placement o prueba A/B de la lista y elimina el segmento de su audiencia, o elimina el placement o la prueba A/B por completo. Una vez que nada haga referencia al segmento, podrás eliminarlo. ::: --- # File: event-feed --- --- title: "Feed de eventos" description: "Monitoriza y analiza la actividad de los usuarios con el feed de eventos de Adapty." --- El feed de eventos te permite hacer un seguimiento visual de los [Eventos](events) generados por Adapty y comprobar el estado de su exportación a integraciones de terceros, incluido el webhook. :::warning El Feed de Eventos no muestra: - **Transacciones de la API server-side v1**: Creadas usando la [API server-side (versión 1)](server-side-api-specs-legacy#requests). Usa la [API server-side (versión 2)](api-adapty/operations/setTransaction) en su lugar para que aparezcan. - **Eventos sin perfil**: Transacciones que llegaron antes de que el SDK identificara a un usuario — por ejemplo, notificaciones del servidor de la store. Para incluirlas en las exportaciones, activa **Include events without profile** en la integración de [S3](s3-exports) o [Google Cloud Storage](google-cloud-storage). ::: <img src="/assets/shared/img/event-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note El estado de envío de AppsFlyer, Facebook Ads y Branch puede ser inexacto porque no siempre devuelven errores cuando se producen. ::: Para ver el perfil del usuario que ha iniciado la transacción, haz clic en el botón **View Profile** en los detalles del evento. --- # File: ab-tests --- --- title: "Prueba A/B" description: "Optimiza los precios de suscripción con pruebas A/B en Adapty para mejorar las tasas de conversión." --- :::tip Puedes obtener un plan de prueba A/B accionable sin tener que hacer la investigación tú mismo. [AI Growth Advisor](autopilot) analiza tu paywall, compara a tus competidores y genera sugerencias a partir de datos anonimizados de 20.000 apps de suscripción rastreadas por Adapty. ::: Aumenta los ingresos de tu app ejecutando pruebas A/B en Adapty. Compara distintos flows, paywalls y onboardings para descubrir qué convierte mejor, sin necesidad de cambiar el código. Por ejemplo, puedes probar: - Precios de suscripción - Diseño, textos y estructura del paywall - Períodos de prueba y duraciones de suscripción - Diseños de onboarding ## Requisitos previos \{#prerequisites\} Antes de configurar una prueba A/B, necesitas tener: - **Placements**: Uno o más [placements](placements) donde se muestre un flow, un paywall o un onboarding. - **Para flows**: Al menos dos [flows](adapty-flow-builder). - **Para paywalls**: Al menos dos [paywalls](paywalls). - **Para onboardings**: Al menos dos [onboardings](onboardings). :::warning Si no estás usando el [Adapty Flow builder](adapty-flow-builder) o el [Adapty Paywall builder](adapty-paywall-builder), [envía las vistas de paywall a Adapty](present-remote-config-paywalls#track-paywall-view-events) con `.logShowFlow()` (iOS SDK v4+) / `.logShowPaywall()`. Sin este método, Adapty no puede calcular las vistas de paywall en la prueba, y las estadísticas de conversión serán inexactas. ::: ## Tipos de prueba A/B \{#ab-test-types\} Adapty admite tres tipos de prueba A/B: - **Regular**: Se ejecuta en un único placement de paywall. - **Onboarding**: Se ejecuta en un único placement de onboarding. - **Crossplacement**: Se ejecuta en múltiples placements de paywall, mostrando la misma variante al usuario en todos ellos. Para una comparación completa de tipos, casos de uso y reglas de prioridad, consulta [Tipos de prueba A/B](ab-test-types). ## Próximos pasos \{#next-steps\} - [AI Growth Advisor](autopilot) — Analiza tu paywall, obtén información del mercado y genera un plan de prueba A/B - [Tipos de prueba A/B](ab-test-types) — Aprende sobre los tipos de prueba y cuándo usar cada uno - [Crear, ejecutar y detener una prueba A/B](run_stop_ab_tests) — Configura y ejecuta tu primera prueba - [Resultados y métricas de la prueba A/B](results-and-metrics) — Entiende los datos de tu prueba A/B y elige un ganador --- # File: ab-test-types --- --- title: "Tipos de prueba A/B" description: "Aprende sobre los tipos de prueba A/B en Adapty." --- Adapty ofrece dos tipos de prueba A/B, cada uno pensado para distintos escenarios de prueba: - **Prueba A/B regular:** Una prueba A/B creada para un único placement de [flow](adapty-flow-builder)/[paywall](paywalls)/[onboarding](onboardings). - **Prueba A/B multiplacement:** Una prueba A/B creada para varios placements de paywall en tu app. Una vez que la prueba A/B asigna una <InlineTooltip tooltip="variante">Las variantes de una prueba A/B son versiones alternativas del flow, paywall u onboarding que se quieren probar.</InlineTooltip>, muestra esa variante de forma consistente en todas las secciones seleccionadas de tu app. :::warning Las pruebas A/B entre placements solo están disponibles para SDKs de Adapty a partir de la versión 3.5.0. Las pruebas A/B entre placements solo funcionan con paywalls. Las pruebas A/B de flows requieren el SDK de Adapty v4.0.0+. Las pruebas A/B de onboardings requieren el SDK de Adapty v3.8.0+ (iOS, Android, React Native, Flutter), v3.14.0+ (Unity) o v3.15.0+ (Kotlin Multiplatform, Capacitor). Los usuarios con versiones anteriores las omiten. ::: Cada flow/paywall/onboarding recibe un peso que distribuye el tráfico durante la prueba. Por ejemplo, con pesos del 70 % y el 30 %, el primer paywall se muestra a aproximadamente 700 de cada 1 000 usuarios, y el segundo a unos 300. En las pruebas Crossplacement, los pesos se establecen por variante, no por paywall. Esta configuración te permite comparar distintos flows y paywalls para tomar decisiones basadas en datos sobre la estrategia de monetización de tu app. ## Cuándo usar cada tipo \{#when-to-use-each-type\} Cada tipo de prueba A/B es útil en los siguientes casos: - **Pruebas A/B regulares**: - Solo tienes un placement en tu aplicación. - Quieres ejecutar tu prueba A/B en un único placement y hacer seguimiento de los cambios económicos solo para ese placement, aunque tu app tenga varios placements. - Quieres ejecutar una prueba A/B en usuarios antiguos (aquellos que han visto al menos un paywall de Adapty). - **Prueba A/B multiplacement**: - Quieres sincronizar variantes entre varios placements. Por ejemplo, podrías cambiar los precios en el flow de onboarding y en los ajustes de tu app al mismo tiempo. - Quieres evaluar la economía general de tu app. Ejecutar la prueba en todos los placements hace que las estadísticas de la prueba A/B sean más fáciles de analizar que probar placements de forma aislada. - Quieres ejecutar una prueba A/B solo en usuarios nuevos, es decir, los que nunca han visto ningún paywall de Adapty. - Quieres usar varios paywalls dentro de una sola variante: <img src="/assets/shared/img/ab-test-variants.png" alt="Ejemplo de múltiples paywalls dentro de una única variante de prueba A/B entre placements" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Diferencias clave \{#key-differences\} | Característica | Prueba A/B Regular | Prueba A/B Crossplacement | | ------------------------------- |----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------| | **Qué se prueba** | Un flow/paywall/onboarding | Conjunto de paywalls pertenecientes a una variante | | **Consistencia de variante** | La variante se determina de forma independiente para cada placement | La misma variante se usa en todos los placements de paywalls | | **Segmentación de audiencia** | Definida por placement de flow/paywall/onboarding | Compartida en todos los placements de paywalls | | **Analíticas** | Analizas un placement de flow/paywall/onboarding | Analizas toda la app en los placements que forman parte de la prueba | | **Distribución de peso de variante** | Por flow/paywall/onboarding | Por conjunto de paywalls | | **Usuarios** | Para todos los usuarios | Solo usuarios nuevos (los que no han visto un paywall de Adapty) | | **Versión del SDK de Adapty** | Para flows: v4.0.0+. Cualquiera para paywalls. Para onboardings: v3.8.0+ (iOS, Android, React Native, Flutter), v3.14.0+ (Unity), v3.15.0+ (KMP, Capacitor) | 3.5.0+ | | **Ideal para** | Probar cambios independientes en un único placement de flow/paywall/onboarding sin tener en cuenta la economía global de la app | Evaluar estrategias de monetización globales en toda la app | ## Lógica de selección de pruebas A/B \{#ab-test-selection-logic\} **Las pruebas A/B Crossplacement tienen prioridad sobre las pruebas A/B normales.** Sin embargo, las pruebas Crossplacement solo se muestran a **nuevos usuarios** — aquellos que aún no han visto ningún paywall de Adapty (el método `getPaywall` del SDK nunca ha sido llamado para ellos). Esto garantiza la coherencia de los resultados entre placements. El siguiente diagrama muestra la lógica que utiliza Adapty para seleccionar una prueba A/B para un placement: <img src="/assets/shared/img/ab-tests-scheme.webp" alt="Diagram showing the A/B test selection logic for a paywall placement" style={{ border: '1px solid #727272', /* border width and color */ width: '350px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> En la página **A/B Tests**, las pruebas de paywall, onboarding, flow y Crossplacement aparecen en pestañas separadas. <img src="ab-tests-tabs.webp" alt="Página de lista de pruebas A/B con pestañas para los tipos de prueba Regular, Onboarding y Crossplacement" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Limitaciones de las pruebas A/B multiplacement \{#crossplacement-ab-test-limitations\} :::warning Las pruebas A/B multiplacement no pueden incluir placements de flow ni de onboarding. ::: Las pruebas A/B multiplacement garantizan que cada usuario vea la misma variante en todos los placements incluidos en la prueba. Esto genera las siguientes limitaciones: * Solo pueden participar usuarios nuevos. Un usuario nuevo es aquel que no ha visto ningún paywall de Adapty y cuya app nunca ha llamado a `getPaywall`. Adapty no puede garantizar una cadena de paywalls coherente para el resto de usuarios. * El primer placement que el usuario encuentre determina qué paywall muestra Adapty. No es posible cambiar la asignación de un usuario ni inscribir al mismo usuario en más de una prueba A/B Cross-placement. :::warning Una vez que un usuario recibe un paywall Cross-placement, lo verá durante 90 días, incluso después de detener la prueba. Para cambiar esta duración, en los ajustes de **General**, modifica **[Cross-placement variation stickiness](general#9-cross-placement-variation-stickiness)**. ::: ## Prioridad de las pruebas A/B entre placements \{#crossplacement-ab-test-priority\} * Las pruebas A/B entre placements siempre tienen prioridad sobre las pruebas A/B regulares y de onboarding. Si un nuevo usuario cumple los requisitos tanto de una prueba entre placements como de una prueba regular en el mismo placement, se mostrará la prueba entre placements. * Cuando varias pruebas A/B entre placements con la misma audiencia comparten el mismo placement, Adapty asigna automáticamente la prioridad de las pruebas según el orden en que se añadieron. La primera prueba recibe la mayor prioridad. No es posible cambiarla manualmente. * Las pruebas que se dirigen a segmentos más pequeños de tu audiencia tienen prioridad automáticamente sobre las que se dirigen al segmento de todos los usuarios. :::note En Analytics, una prueba A/B Crossplacement aparece como varias pruebas secundarias, una por placement. Las pruebas secundarias siguen el patrón de nomenclatura `<test-name> child-0`, `<test-name> child-1`, y así sucesivamente. La numeración coincide con el orden de los placements en la página de detalles de la prueba A/B. Para ver los resultados de un placement específico, filtra por **Placement**. ::: ## Próximos pasos \{#next-steps\} - [Crear, ejecutar y detener una prueba A/B](run_stop_ab_tests) — Configura y lanza tu primera prueba - [Resultados y métricas de pruebas A/B](results-and-metrics) — Analiza el rendimiento y elige un ganador --- # File: run_stop_ab_tests --- --- title: "Crear, ejecutar y detener una prueba A/B" description: "Guía paso a paso para crear, ejecutar y detener pruebas A/B en Adapty." --- Este artículo cubre el ciclo de vida completo de una prueba A/B en Adapty: crear la prueba, ejecutarla y detenerla cuando estés listo para revisar los resultados. ## Requisitos previos \{#prerequisites\} Antes de configurar una prueba A/B, necesitas tener: - Al menos dos [flows](adapty-flow-builder)/[paywalls](paywalls)/[onboardings](onboardings) creados - Un [placement](placements) configurado en tu app :::warning Si no usas el [Adapty Flow Builder](adapty-flow-builder) ni el [Adapty Paywall Builder](adapty-paywall-builder), [envía las vistas de paywall a Adapty](present-remote-config-paywalls#track-paywall-view-events) con `.logShowPaywall()`. Sin este método, Adapty no puede calcular las vistas del paywall en la prueba y las estadísticas de conversión serán inexactas. ::: :::info Las pruebas A/B en Adapty siguen un proceso de dos pasos. Primero creas la prueba y la guardas como borrador — no se publica de inmediato. Cuando estés listo, la ejecutas por separado. Esto te permite revisar la configuración antes de que los usuarios la vean. ::: ## Crear una prueba A/B \{#create-an-ab-test\} Al crear una nueva prueba A/B, debes incluir al menos dos [flows](adapty-flow-builder)/[paywalls](paywalls)/[onboardings](onboardings). Para crear una nueva prueba A/B: 1. Ve a [A/B tests](ab-tests) desde el menú principal de Adapty. <img src="/assets/shared/img/go-to-abtests.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. En la esquina superior derecha, haz clic en **Create A/B test**. <img src="/assets/shared/img/create-abtest.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la ventana **Create the A/B test**, introduce un **Test name**. Este campo es obligatorio. Elige un nombre que describa claramente el objetivo de la prueba para que puedas identificarla al revisar los resultados. 4. Rellena el campo **Test goal** para describir qué quieres conseguir (por ejemplo, aumentar las suscripciones o reducir la cancelación). 5. Haz clic en **Select placement** y elige un placement de flow, paywall u onboarding. 6. Configura el contenido de la prueba en la tabla **Variants**. Cada fila es una variante y cada columna es un placement. Añade un paywall en cada intersección. Por defecto, la tabla tiene 2 variantes y 1 placement. Puedes añadir hasta 20 variantes. Si añades un segundo placement, la prueba se convierte en una prueba A/B de cross-placement. Ten en cuenta que las pruebas A/B de cross-placement solo están disponibles para paywalls. <img src="/assets/shared/img/abtest-variants.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 7. Guarda tu prueba. Tienes dos opciones: 1. **Save as draft**: La prueba no se publicará de inmediato. Puedes lanzarla más tarde desde el placement o la lista de pruebas A/B. Úsala para revisar la configuración antes del lanzamiento. 2. **Run A/B test**: Lanza la prueba de inmediato. La prueba se pone en marcha en cuanto haces clic en este botón. Una vez guardada como borrador, continúa en [Ejecutar una prueba A/B](#run-an-ab-test). ## Editar una prueba A/B \{#edit-an-ab-test\} Solo puedes editar las pruebas A/B que están guardadas como borradores. Una vez que la prueba está activa, no se puede modificar. Para actualizar una prueba en curso, usa la opción **Modify** — esto crea un duplicado con el mismo nombre donde puedes hacer cambios. Adapty detiene la prueba original, y tanto la versión original como la modificada aparecen por separado en tus analíticas. ## Ejecutar una prueba A/B \{#run-an-ab-test\} Ejecutar una prueba A/B en Adapty significa asignarla a un placement para que empiece a mostrar paywalls y onboardings a los usuarios. 1. Ve a la sección [Pruebas A/B](ab-tests) desde el menú principal de Adapty. 2. Asegúrate de estar viendo la lista correcta: las pruebas A/B de **Paywall**, **Flow**, **Onboardings** y **Crossplacement** se muestran en pestañas separadas entre las que puedes alternar. 3. Cambia a la pestaña **Drafts**. Solo se pueden iniciar las pruebas en borrador. 4. Junto a la prueba que quieres lanzar, haz clic en **Run A/B test**. 5. Se abre la ventana **Edit A/B test**. Revisa la configuración y realiza los últimos cambios que necesites. Si falta el placement o la audiencia, agrégalos ahora. 6. Tras revisar la configuración, haz clic en **Run A/B test** para comenzar. Después de lanzar la prueba, puedes hacer un seguimiento de su progreso y ver los datos de rendimiento en la página [Resultados y métricas de la prueba A/B](results-and-metrics). ## Detener una prueba A/B \{#stop-an-ab-test\} Cuando detienes una prueba A/B, esta finaliza y puedes revisar los resultados. También decides qué mostrar a los usuarios en los placements afectados una vez que concluya la prueba. <img src="/assets/shared/img/stop-ab-test.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. Abre la sección [A/B tests](https://app.adapty.io/ab-tests) y ve a la pestaña **Live**. 2. Junto a la prueba que quieres detener, haz clic en el menú de tres puntos y elige **Stop A/B test**. 3. En la ventana **Stop the A/B test**, decide qué debe ocurrir cuando termine la prueba. Tienes tres opciones: | Opción | Descripción | |----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Mostrar uno de los paywalls/onboardings probados | Elige el paywall u onboarding ganador según los resultados de la prueba, como ingresos, probabilidad de ser el mejor (**P2BB**) e ingresos por cada 1000 usuarios. Este paywall u onboarding se mostrará para el placement y la audiencia seleccionados. | | Seleccionar paywalls/onboardings que no participan en la prueba A/B | Elige cualquier paywall u onboarding que no forme parte de la prueba A/B actual. Usa esta opción cuando ninguna de las variantes probadas haya cumplido tus objetivos. | | No mostrar ningún paywall/onboarding específico | Para el placement y la audiencia seleccionados, no se seleccionará ningún paywall u onboarding específico al finalizar la prueba A/B. En su lugar, se mostrará el siguiente paywall u onboarding disponible según la prioridad de la audiencia. Es una buena opción si prefieres dejar que tu configuración actual decida qué paywall u onboarding mostrar, sin seleccionar uno manualmente. | :::note Detener una prueba A/B es irreversible: no se puede reiniciar. Asegúrate de haber recopilado suficientes datos antes de decidir detenerla. ::: 4. Haz clic en el botón **Stop and complete this A/B test**. Una vez finalizada la prueba A/B, dejará de estar activa y los paywalls u onboardings asociados dejarán de mostrarse a nuevos usuarios. Puedes seguir consultando los resultados y métricas de la prueba A/B en la [página de métricas de pruebas A/B](results-and-metrics#metrics-controls) para revisar el rendimiento de los usuarios que participaron mientras la prueba estaba activa. Las métricas pueden seguir actualizándose a medida que se atribuyen nuevos eventos de compra o ingresos a esos usuarios. --- # File: ab-test-no-paywall-variants --- --- title: "Añadir variantes de prueba A/B sin paywalls" description: "Ejecuta una prueba A/B donde una variante omite el paywall, usando una bandera de Remote Config para controlar si el paywall se muestra." --- Puedes medir el impacto de tu paywall ejecutando una prueba A/B contra una variante vacía. Una variante muestra tu paywall; la otra no muestra nada. Tu app lee una bandera del Remote Config del paywall para decidir si renderizarlo. ## Cómo funciona \{#how-it-works\} La configuración usa dos paywalls en el mismo placement: - **Paywall A**: El paywall que quieres probar, con `show_paywall` establecido en `true` en su Remote Config. - **Paywall B**: Un paywall vacío con `show_paywall` establecido en `false` en su Remote Config. Cuando `getPaywall` devuelve un paywall, tu app lee la bandera `show_paywall`. Si la bandera es `true`, la app renderiza el paywall. Si la bandera es `false`, la app omite el renderizado y el usuario continúa sin ver un paywall. ## 1. Añade el flag show_paywall en el Remote Config \{#1-add-the-show_paywall-flag-in-remote-config\} Necesitas dos flows o paywalls en el mismo placement: Flow/Paywall A (el que quieres probar) y Flow/Paywall B (uno vacío). Añade un campo `show_paywall` a cada uno para que tu app pueda ramificar en la misma clave para ambas variantes. Para añadir el flag al Flow/Paywall A: 1. Abre la sección [**Flows**](https://app.adapty.io/flows)/[**Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty y selecciona el Flow/Paywall A. 2. Abre la sección **Remote config**. 3. Crea un campo con el nombre `show_paywall` y el valor `true`. En la vista **JSON**, la entrada queda así: ```json showLineNumbers { "show_paywall": true } ``` 4. Guarda los cambios. Repite los mismos pasos para el Flow/Paywall B, pero establece `show_paywall` en `false`. Para obtener todos los detalles sobre Remote Config, consulta [Personalizar el flow con Remote Config](customize-flow-with-remote-config) o [Diseñar el paywall con Remote Config](customize-paywall-with-remote-config). :::tip Establecer `show_paywall` en ambas variantes mantiene la ruta de código idéntica para ambos grupos y facilita extender la prueba con más variantes más adelante. ::: ## 2. Configura la prueba A/B \{#2-set-up-the-ab-test\} 1. [Crea una prueba A/B](run_stop_ab_tests) en el placement y añade ambos paywalls como variantes. 2. Establece los pesos de las variantes para distribuir el tráfico entre los usuarios que ven el paywall y los que no. ## 3. Comprueba la flag en tu app \{#3-check-the-flag-in-your-app\} Lee `show_paywall` desde el Remote Config devuelto por el SDK. Si la flag es `false`, omite el renderizado y deja que el usuario continúe. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") let config = flow.remoteConfigs.first(where: { $0.locale == "en" }) ?? flow.remoteConfigs.first let showPaywall = config?.dictionary?["show_paywall"] as? Bool ?? true if showPaywall { // render the flow or paywall } } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android"> ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value val showPaywall = paywall.remoteConfig?.dataMap?.get("show_paywall") as? Boolean ?: true if (showPaywall) { // Render the paywall } } is AdaptyResult.Error -> { // handle the error } } } ``` </TabItem> <TabItem value="react-native" label="React Native"> ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: "YOUR_PLACEMENT_ID" }); const showPaywall = paywall.remoteConfig?.data?.["show_paywall"] ?? true; if (showPaywall) { // Render the paywall } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter"> ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(id: "YOUR_PLACEMENT_ID"); final bool showPaywall = paywall.remoteConfig?.dictionary?['show_paywall'] as bool? ?? true; if (showPaywall) { // Render the paywall } } on AdaptyError catch (adaptyError) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity"> ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if (error != null) { // handle the error return; } var showPaywall = paywall.RemoteConfig?.Dictionary?["show_paywall"] as bool? ?? true; if (showPaywall) { // Render the paywall } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform"> ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID" ).onSuccess { paywall -> val showPaywall = paywall.remoteConfig?.dataMap?.get("show_paywall") as? Boolean ?: true if (showPaywall) { // Render the paywall } }.onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor"> ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID' }); const showPaywall = paywall.remoteConfig?.data?.['show_paywall'] ?? true; if (showPaywall) { // Render the paywall } } catch (error) { // handle the error } ``` </TabItem> </Tabs> El valor de respaldo `true` mantiene el flow/paywall visible cuando la bandera no existe, por lo que los flows/paywalls existentes que no incluyan la bandera no se ven afectados. :::important Si renderizas el paywall tú mismo (sin el [Flow Builder](adapty-flow-builder) ni el [Paywall Builder](adapty-paywall-builder)), llama a [`logShowFlow` (iOS SDK v4+) / `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events) cuando muestres el Flow/Paywall A. Sin esto, Adapty no puede contabilizar las visualizaciones en la prueba. No registres una visualización para el Flow/Paywall B, ya que nunca se muestra. ::: ## Próximos pasos \{#next-steps\} - [Crear, ejecutar y detener una prueba A/B](run_stop_ab_tests) — Configura el test que incluye ambas variantes - [Resultados y métricas de la prueba A/B](results-and-metrics) — Compara la variante sin paywall con tu paywall --- # File: results-and-metrics --- --- title: "Resultados y métricas de la prueba A/B" description: "Analiza los resultados y las métricas clave en Adapty para mejorar el rendimiento de las suscripciones y la participación de los usuarios en tu app." --- Descubre datos e información valiosa de tus [pruebas A/B](ab-tests), comparando diferentes paywalls y onboardings para ver cómo afectan al comportamiento de los usuarios, su engagement y las tasas de conversión. Analizando las métricas y los resultados disponibles aquí, puedes tomar decisiones inteligentes y mejorar el rendimiento de tu app. Explora los datos para encontrar insights accionables y potenciar el éxito de tu app. ## Resultados de la prueba A/B \{#ab-test-results\} Estas son las tres métricas que Adapty proporciona para los resultados de la prueba A/B: <img src="/assets/shared/img/ab-test-results.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Adapty convierte otras divisas a USD según el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo de cambio se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: **Ingresos**: Esta métrica muestra el importe total en USD generado por compras y renovaciones, menos los reembolsos realizados a usuarios. Incluye tanto la compra inicial como las renovaciones posteriores de suscripciones. Los ingresos te ayudan a entender el rendimiento financiero de cada variante de la prueba A/B y a identificar cuál genera más dinero. Obtén más información sobre las métricas de [paywall](paywall-metrics). **Probabilidad de ser el mejor**: Adapty utiliza un sólido marco de análisis matemático para analizar los resultados de las pruebas A/B y proporciona una métrica llamada Probabilidad de ser el mejor. Esta métrica evalúa la probabilidad de que una variante concreta sea la opción con mejor rendimiento (en términos de ingresos a largo plazo) entre todas las variantes probadas. La métrica se expresa como un valor porcentual que oscila entre el 1% y el 100%. Para más información sobre cómo Adapty calcula esta métrica, consulta la [documentación.](maths-behind-it) La opción con mejor rendimiento, determinada por los ingresos por cada 1000 usuarios, se resalta en verde y se selecciona automáticamente como opción predeterminada. **Ingresos por 1000 usuarios**: La métrica de ingresos por 1000 usuarios calcula los ingresos medios generados por cada 1000 usuarios en cada variante de la prueba A/B. Esta métrica te ayuda a entender la eficiencia de ingresos de tus variantes, independientemente del número total de usuarios. Te permite comparar el rendimiento de distintas variantes en una escala estandarizada y tomar decisiones informadas basadas en la eficiencia de generación de ingresos. **Intervalos de predicción para los ingresos por 1.000 usuarios**: La métrica de ingresos por 1.000 usuarios también incluye intervalos de predicción. Estos intervalos representan el rango dentro del cual se predice que caerán los ingresos reales por 1.000 usuarios para una variante determinada, según los datos disponibles y el análisis estadístico. En el contexto de las pruebas A/B, al analizar los ingresos generados por las distintas variantes, calculamos los ingresos medios por cada 1.000 usuarios para cada variante. Dado que los ingresos pueden variar entre usuarios, los intervalos de predicción ofrecen una indicación clara de los valores plausibles para los ingresos por 1.000 usuarios, teniendo en cuenta la variabilidad y la incertidumbre asociadas al proceso de predicción. Al incorporar intervalos de predicción en la métrica de ingresos por cada 1000 usuarios, Adapty te permite evaluar la eficiencia de ingresos de las variantes de tu prueba A/B teniendo en cuenta el rango de posibles resultados de ingresos. Esta información te ayuda a tomar decisiones basadas en datos y a optimizar tu estrategia de suscripción de forma eficaz, considerando la incertidumbre en el proceso de predicción y los valores plausibles de ingresos por cada 1000 usuarios. Al analizar estas métricas que ofrece Adapty, puedes obtener información sobre el rendimiento financiero, la significancia estadística y la eficiencia de ingresos de las variantes de tu prueba A/B, lo que te permite tomar decisiones basadas en datos y optimizar tu estrategia de suscripción de manera efectiva. ## Métricas de la prueba A/B \{#ab-test-metrics\} Adapty ofrece un conjunto completo de métricas para ayudarte a medir con eficacia el rendimiento de tus pruebas A/B realizadas en variantes de paywall u onboarding. Estas métricas se actualizan continuamente en tiempo real, excepto las vistas, que se actualizan de forma periódica. Entender estas métricas te ayudará a evaluar la efectividad de las distintas variantes y a tomar decisiones basadas en datos para optimizar tu estrategia de paywall u onboarding. Las métricas de las pruebas A/B están disponibles en la lista de pruebas A/B, donde puedes obtener una visión general del rendimiento de todas tus pruebas A/B. Esta vista completa ofrece métricas agregadas para cada variante de prueba, lo que te permite comparar su rendimiento e identificar diferencias significativas. Para un análisis más detallado de cada prueba A/B, puedes acceder a las métricas de detalle de la prueba A/B. Esta sección proporciona métricas en profundidad específicas para la prueba A/B seleccionada, lo que te permite analizar en detalle el rendimiento de cada variante. Todas las métricas, excepto las vistas, se atribuyen al producto dentro del paywall o onboarding. ## Controles de métricas \{#metrics-controls\} El sistema muestra las métricas según el período de tiempo seleccionado y las organiza de acuerdo con el parámetro de la columna izquierda con tres niveles de sangría. ### Filtrado por fecha de instalación del perfil \{#profile-install-date-filtration\} <img src="/assets/shared/img/2bf4d9f-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> La casilla **Filter metrics by install date** permite filtrar las métricas según la fecha de instalación del perfil, en lugar de los filtros predeterminados que usan la fecha de prueba/compra para las transacciones o la fecha de visualización para las vistas de paywall u onboarding. Al marcar esta casilla, puedes centrarte en medir el rendimiento de adquisición de usuarios para un período específico, alineando las métricas con la fecha de instalación del perfil. Esta opción es útil para personalizar el análisis de métricas según tus necesidades concretas. ### Rangos de tiempo \{#time-ranges\} Puedes elegir entre un rango de períodos de tiempo para analizar los datos de métricas, lo que te permite centrarte en duraciones específicas como días, semanas, meses o rangos de fechas personalizados. <img src="/assets/shared/img/ab-test-time-ranges.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: Adapty ofrece herramientas potentes para filtrar y personalizar el análisis de métricas según tus necesidades. En la página de métricas de Adapty tienes acceso a distintos rangos de tiempo, opciones de agrupación y posibilidades de filtrado. - ✅ Filtrar por: audiencia, atribución, país, paywall, estado del paywall, grupo de paywalls, onboarding, placement, país, store, producto y store del producto. - ✅ Agrupar por: producto y store. :::note Cuando filtras por prueba A/B, las pruebas A/B entre placements aparecen como pruebas secundarias independientes (por ejemplo, `My test child-0`, `My test child-1`), una por placement. Consulta [Limitaciones de las pruebas A/B entre placements](ab-test-types#crossplacement-ab-test-limitations) para más detalles. ::: ## Gráfico de métrica individual \{#single-metrics-chart\} Uno de los componentes clave de la página de métricas del paywall o del onboarding es la sección de gráficos, que representa visualmente las métricas seleccionadas y facilita su análisis. <img src="/assets/shared/img/e6b0674-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> El apartado de gráficos de la página de métricas de pruebas A/B incluye un gráfico de barras horizontales que representa visualmente los valores de la métrica seleccionada. Cada barra corresponde a un valor de la métrica y su tamaño es proporcional al dato que representa, lo que facilita interpretar la información de un vistazo. La línea horizontal indica el período de tiempo analizado, y la columna vertical muestra los valores numéricos de las métricas. El valor total de todos los valores de la métrica se muestra junto al gráfico. Además, al hacer clic en el icono de flecha situado en la esquina superior derecha de la sección del gráfico, se amplía la vista y se muestran las métricas seleccionadas en la línea completa del gráfico. ## Resumen de la prueba A/B \{#ab-test-summary\} Junto al gráfico de métrica individual, se muestra la sección de resumen de detalles de la prueba A/B, que incluye información sobre el estado, la duración, los placements y otros detalles relacionados con la prueba A/B. <img src="/assets/shared/img/90fa3f5-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Definiciones de métricas \{#metrics-definitions\} Estas son las métricas clave disponibles para las pruebas A/B: <img src="/assets/shared/img/30c7b68-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Ingresos \{#revenue\} Los ingresos representan el importe total en USD generado a partir de las compras y renovaciones derivadas de la prueba A/B. Incluyen la compra inicial y las renovaciones de suscripción posteriores. La métrica de ingresos se calcula antes de deducir la comisión de App Store o Play Store. Obtén más información sobre las métricas de [ingresos del paywall](paywall-metrics#revenue). ### CR to purchases \{#cr-to-purchases\} La tasa de conversión a compras mide la efectividad de tu prueba A/B para convertir vistas en compras reales. Se calcula dividiendo el número de compras por el número de vistas. Por ejemplo, si tuviste 10 compras y 100 vistas, la tasa de conversión a compras sería del 10%. ### CR trials \{#cr-trials\} La tasa de conversión (CR) a trials es el número de trials iniciados desde la prueba A/B dividido por el número de vistas. La tasa de conversión a trials mide la efectividad de tu prueba A/B para convertir vistas en activaciones de trial. Se calcula dividiendo el número de trials iniciados por el número de vistas. ### Purchases \{#purchases\} La métrica de compras representa el número total de transacciones realizadas dentro del paywall o el onboarding como resultado de la prueba A/B. Incluye los siguientes tipos de compras: - Nuevas compras realizadas. - Conversiones de trials activados. - Cambios de nivel inferior, superior y cruzados de suscripciones. - Restauraciones de suscripciones (por ejemplo, cuando una suscripción expira sin renovación automática y se restaura posteriormente). Ten en cuenta que las renovaciones no están incluidas en la métrica de compras. ### Trials \{#trials\} La métrica de trials indica el número total de trials activados como resultado de la prueba A/B. ### Trials cancelled \{#trials-cancelled\} La métrica de trials cancelados representa el número de trials en los que se ha desactivado la renovación automática. Esto ocurre cuando los usuarios cancelan manualmente su suscripción al trial. ### Refunds \{#refunds\} Los reembolsos de la prueba A/B representan el número de compras y suscripciones reembolsadas específicamente relacionadas con las variaciones probadas. ### Views \{#views\} Las vistas son el número de visualizaciones de los paywalls o los onboardings que componen la prueba A/B. Si el usuario visita dos veces, se contarán como dos visitas. ### Unique views \{#unique-views\} Las vistas únicas son el número de visualizaciones únicas del paywall o del onboarding. Si el usuario lo visita dos veces, se contará como una única vista. ### Probability to be the best \{#probability-to-be-the-best\} La métrica Probability to be the best cuantifica la probabilidad de que una variante específica dentro de una prueba A/B sea la opción de mejor rendimiento entre todos los paywalls u onboardings probados. Proporciona una probabilidad numérica que indica el rendimiento relativo de cada paywall u onboarding. Se expresa como un porcentaje que va del 1% al 100%. ### ARPU (Average revenue per user) \{#arpu-average-revenue-per-user\} Solo para pruebas A/B de onboarding. Mide el promedio de ingresos generados por cada usuario en un período específico. Se calcula dividiendo los ingresos totales por el número de usuarios únicos. ### ARPPU (Average revenue per paying user) \{#arppu-average-revenue-per-paying-user\} ARPPU son las siglas de Average Revenue Per Paying User (ingresos medios por usuario de pago) resultantes de la prueba A/B. Se calcula como los ingresos totales divididos por el número de usuarios de pago únicos. Por ejemplo, si has generado 15.000 $ en ingresos de 1.000 usuarios de pago, el ARPPU sería de 15 $. ### ARPAS (Average revenue per active subscriber) \{#arpas-average-revenue-per-active-subscriber\} ARPAS es una métrica que permite medir el promedio de ingresos generados por suscriptor activo en la ejecución de la prueba A/B. Se calcula dividiendo los ingresos totales por el número de suscriptores que han activado un trial o una suscripción. Por ejemplo, si los ingresos totales son 5.000 $ y tienes 1.000 suscriptores, el ARPAS sería de 5 $. Esta métrica ayuda a evaluar el potencial de monetización promedio por suscriptor. ### Proceeds \{#proceeds\} La métrica de proceeds para la prueba A/B representa el importe real en USD recibido por el propietario de la app por compras y renovaciones, tras deducir la comisión aplicable de App Store o Play Store. Refleja los ingresos netos específicamente asociados a las variaciones probadas en la prueba A/B, contribuyendo directamente a las ganancias de la app. Para más información sobre cómo se calculan los proceeds, puedes consultar la [documentación](analytics-cohorts#revenue-vs-proceeds) de Adapty. ### Unique subscribers \{#unique-subscribers\} La métrica de suscriptores únicos representa el recuento de personas distintas que se han suscrito o activado un trial a través de las variaciones de la prueba A/B. Cada suscriptor se contabiliza una sola vez, independientemente del número de suscripciones o trials que inicie. ### Unique paid subscribers \{#unique-paid-subscribers\} La métrica de suscriptores de pago únicos representa el número de personas únicas que han completado con éxito una compra y se han convertido en suscriptores de pago a través de las variaciones de la prueba A/B. ### Refund rate \{#refund-rate\} La tasa de reembolso para la prueba A/B se calcula dividiendo el número de reembolsos específicamente asociados a las variaciones de la prueba por el número de primeras compras (las renovaciones quedan excluidas). Por ejemplo, si hay 5 reembolsos y 1.000 primeras compras, la tasa de reembolso sería del 0,5%. ### Unique CR purchases \{#unique-cr-purchases\} La tasa de conversión única a compras para la prueba A/B se calcula dividiendo el número de compras específicamente asociadas a las variaciones de la prueba por el número de vistas únicas. Por ejemplo, si hay 10 compras y 100 vistas únicas, la tasa de conversión única a compras sería del 10%. ### Unique CR trials \{#unique-cr-trials\} La tasa de conversión única a trials para la prueba A/B se calcula dividiendo el número de trials iniciados específicamente asociados a las variaciones de la prueba por el número de vistas únicas. Por ejemplo, si hay 30 trials iniciados y 100 vistas únicas, la tasa de conversión única a trials sería del 30%. ### Completions y completions únicos \{#completions--unique-completions\} Solo para pruebas A/B de onboarding. Los completions cuentan el número de veces que los usuarios completan tu onboarding a través de las variaciones de la prueba A/B, es decir, que pasan de la primera a la última pantalla. Si alguien lo completa dos veces, eso cuenta como dos **completions** pero un único **unique completion**. ### Unique completions rate \{#unique-completions-rate\} Solo para pruebas A/B de onboarding. El número de completions únicos dividido por el número de vistas únicas. Esta métrica te ayuda a entender cómo interactúan los usuarios con el onboarding a través de las variaciones de la prueba A/B y a realizar cambios si detectas que los usuarios lo ignoran. --- # File: maths-behind-it --- --- title: "Las matemáticas detrás de las pruebas A/B" description: "Entiende las matemáticas detrás del análisis de suscripciones para obtener mejores insights de ingresos." --- Las pruebas A/B son una técnica muy eficaz para comparar el rendimiento de dos versiones distintas de un flow, un paywall o un onboarding. El objetivo final es determinar cuál de las dos versiones es más eficaz, tomando como referencia el ingreso medio por usuario a lo largo de un período de 12 meses. Sin embargo, esperar un año completo para recopilar datos y tomar decisiones no es práctico. Por eso se utiliza el ingreso por usuario en 2 semanas como métrica proxy, elegida a partir del análisis de datos históricos para aproximar la métrica objetivo. Para obtener resultados precisos y fiables, es fundamental emplear un método estadístico sólido que pueda manejar distintos tipos de datos. La estadística bayesiana, un enfoque muy extendido en el análisis de datos moderno, ofrece un marco flexible e intuitivo para las pruebas A/B. Al incorporar conocimiento previo y actualizarlo con nuevos datos, los métodos bayesianos permiten tomar mejores decisiones en condiciones de incertidumbre. Este documento es una guía completa del análisis matemático que utiliza Adapty para evaluar los resultados de las pruebas A/B y proporcionar información valiosa para la toma de decisiones basada en datos. ## Enfoque de Adapty para el análisis estadístico \{#adaptys-approach-to-statistical-analysis\} Adapty emplea un enfoque integral de análisis estadístico para evaluar el rendimiento de las pruebas A/B y ofrecer resultados precisos y fiables. Nuestra metodología consta de los siguientes pasos clave: 1. **Definición de la métrica:** Para llevar a cabo una prueba A/B con éxito, es necesario identificar y definir la métrica clave que se alinee con los objetivos específicos del análisis. Adapty aprovechó una gran cantidad de datos históricos de apps de suscripción para determinar cuál encaja mejor como métrica proxy del objetivo a largo plazo —el ingreso promedio tras 1 año— y el resultado es el ARPU a los 14 días. 2. **Formulación de la hipótesis:** Creamos dos hipótesis para la prueba A/B. La hipótesis nula (H0) asume que no existe una diferencia significativa entre el grupo de control (A) y el grupo de prueba (B). La hipótesis alternativa (H1) plantea que sí existe una diferencia significativa entre los dos o más grupos. 3. **Selección de la distribución:** Elegimos la familia de distribución más adecuada en función de las características de los datos y la métrica que observamos. La elección más habitual es la distribución log-normal (teniendo en cuenta los valores cero). 4. **Cálculo de la probabilidad de ser el mejor:** Utilizando el enfoque bayesiano para las pruebas A/B, calculamos la probabilidad de ser la mejor opción para cada variante de paywall u onboarding que participa en el test. Este valor está relacionado con los p-valores que usábamos antes, pero es fundamentalmente un enfoque diferente, más robusto y más fácil de interpretar. 5. **Interpretación de los resultados:** La probabilidad de ser el mejor es exactamente lo que sugiere el nombre. Cuanto mayor sea la probabilidad, mayor será la posibilidad de que una opción concreta sea la mejor elección para la tarea. El umbral para la toma de decisiones debes establecerlo tú mismo; dependerá de muchos otros factores propios de tu situación, pero una elección habitual es el 95%. 6. **Intervalos de predicción:** Adapty calcula intervalos de predicción para las métricas de rendimiento de cada grupo, lo que proporciona un rango de valores dentro del cual es probable que se encuentre el verdadero parámetro poblacional. Esto ayuda a cuantificar la incertidumbre asociada a las métricas de rendimiento estimadas. ## Determinación del tamaño de la muestra \{#sample-size-determination\} Determinar un tamaño de muestra adecuado es fundamental para obtener resultados fiables y concluyentes en las pruebas A/B. Adapty tiene en cuenta factores como la potencia estadística y el tamaño de efecto esperado —que siguen siendo relevantes incluso con el enfoque bayesiano— para garantizar un tamaño de muestra suficiente. Los métodos de estimación del tamaño de muestra requerido, específicos del enfoque bayesiano que empleamos actualmente, aseguran la fiabilidad del análisis. Para obtener más información sobre la funcionalidad de las pruebas A/B, te recomendamos consultar nuestra documentación sobre [cómo crearlas](ab-tests) y [cómo ejecutar pruebas A/B](run_stop_ab_tests), así como comprender las distintas [métricas y resultados de las pruebas A/B](results-and-metrics). El marco analítico de Adapty para las pruebas A/B utiliza ahora un enfoque bayesiano, aunque el foco sigue siendo la definición de métricas, la formulación de hipótesis y la selección de distribuciones. Sin embargo, en lugar de calcular p-valores, ahora calculamos las distribuciones posteriores y la probabilidad de que cada variante sea la mejor. También determinamos los intervalos de predicción. Este enfoque revisado, aunque igualmente exhaustivo e incluso más robusto, está diseñado para ofrecer conclusiones más intuitivas y fáciles de interpretar. El objetivo sigue siendo el mismo: ayudar a las empresas a optimizar sus estrategias, mejorar el rendimiento y crecer a partir de un análisis estadístico sólido de sus pruebas A/B. --- # File: autopilot-how-it-works --- --- title: "Asesor de crecimiento con IA: Cómo funciona" description: "Comprende la lógica del Asesor de crecimiento con IA y confía en nosotros para hacer crecer tus ingresos." --- [AI Growth Advisor](autopilot) te ayuda a descubrir qué experimentos ejecutar basándose en tus datos de rendimiento reales y en cómo les va a aplicaciones similares en tu mercado. En lugar de adivinar qué podría funcionar, recibes recomendaciones específicas para pruebas con más probabilidades de mejorar tus resultados. Este artículo ofrece una visión transparente de cómo piensa el AI Growth Advisor: qué datos utiliza, cómo evalúa las oportunidades y por qué aparecen ciertas recomendaciones. El objetivo es que puedas usarlo con confianza como parte de tu flujo de crecimiento. ## Qué hace realmente el Asesor de Crecimiento IA \{#what-ai-growth-advisor-actually-does\} El Asesor de Crecimiento IA analiza las métricas de tu app y tus paywalls para identificar los experimentos con mayor probabilidad de aumentar tus ingresos. Tiene en cuenta: - **Tu configuración actual**: precios, pruebas, productos y cómo convierten - **Patrones del mercado**: cómo estructuran sus ofertas apps similares y qué cobran - **Tu historial de pruebas**: qué experimentos ya has ejecutado y qué revelaron - **Potencial de crecimiento**: qué cambios tienen más posibilidades de marcar la diferencia Growth Advisor utiliza IA para evaluar estos factores de forma conjunta y convertirlos en pruebas A/B que puedes lanzar de inmediato. Obtienes un plan listo para usar sin tener que investigar a la competencia ni adivinar qué probar a continuación. ## Los datos detrás de AI Growth Advisor \{#the-data-behind-ai-growth-advisor\} Cada recomendación se construye a partir de tres fuentes de datos principales que trabajan juntas. #### Los datos propios de tu app \{#your-apps-own-data\} AI Growth Advisor analiza cómo rinde tu app hoy: - Métricas de conversión en tus paywalls - Estructura de precios y productos Esto le da a AI Growth Advisor una base de partida antes de sugerir cualquier cambio. :::note No usamos los datos de rendimiento de tu app para entrenar recomendaciones para otras apps. Tus datos son privados. ::: #### Análisis del paywall \{#paywall-analysis\} El AI Growth Advisor analiza la captura de pantalla de tu paywall y compara su diseño con los patrones consolidados que utilizan las apps de mayor rendimiento en tu categoría. Evalúa las decisiones de maquetación, el copy, los desglosados de suscripción y los elementos orientados a la conversión, como las etiquetas de ahorro o las secciones de reseñas. Este análisis genera dos tipos de recomendaciones: - **Recomendaciones basadas en benchmarks** sobre lo que hacen de forma diferente las apps con mejor rendimiento, cada una respaldada por un dato concreto (por ejemplo, «Usada por el 72% de las apps de Educación con mejor rendimiento»). - **Recomendaciones de análisis visual** generadas por IA a partir de tu captura de pantalla, que cubren mejoras de copy, cambios de layout y otros ajustes de diseño. Estas recomendaciones se incorporan directamente a tu [plan de crecimiento](autopilot-growth-plan#view-the-growth-plan) como hipótesis que puedes [lanzar como pruebas A/B](autopilot-execute-plan). #### Datos de competidores \{#competitor-data\} AI Growth Advisor compara tu configuración con aplicaciones similares de tu mercado utilizando información pública como precios, estructuras de suscripción y patrones habituales en tu categoría. Estas comparaciones son específicas por país, ya que los precios y las estructuras de la competencia varían según el mercado. Los precios de la competencia provienen de fuentes de terceros y públicas como la App Store, que son distintas de los datos anonimizados de la red de Adapty que utiliza el análisis de métricas. De esta forma, estás probando estrategias que ya funcionan en apps similares a la tuya, no ideas al azar. Al ver el análisis, puedes comparar tus métricas de referencia y los precios de la competencia uno al lado del otro. Si apps similares obtienen mejores resultados con una estructura de precios diferente, es una buena señal de que el mismo enfoque podría funcionar también para ti. :::tip AI Growth Advisor selecciona automáticamente los competidores más relevantes en función de con quiénes puedes competir de manera realista. En general, recomendamos mantener estas sugerencias en lugar de añadir aplicaciones que estén muy por delante o muy por detrás. Si tu aplicación pertenece a varias categorías, puede que quieras ajustar la lista para centrarte en el segmento de mercado más relevante. ::: #### Puntos de referencia del sector \{#industry-benchmarks\} El AI Growth Advisor se basa en datos anonimizados de 20.000 aplicaciones de suscripción rastreadas por Adapty para mostrarte cómo te comparas con la media de la categoría en un país específico. Los datos se agregan en toda la red y nunca se vinculan a una aplicación concreta. Por ejemplo, tu embudo de conversión y los ingresos por instalación se comparan con la media de aplicaciones de tu categoría y país. Esto te ayuda a ver si estás por debajo del promedio, en la media o ya por delante. #### Datos de mercado geográfico \{#geographic-market-data\} El Asesor de Crecimiento IA analiza mercados geográficos individuales —basándose en patrones de la red de 20.000 aplicaciones de Adapty— para identificar dónde los ajustes regionales de precios podrían generar más ingresos. Para cada país, evalúa: - **Tasa de conversión**: cómo se compara la tasa de instalación a pago con la media global. Una tasa más alta puede indicar margen para subir precios; una más baja puede señalar sensibilidad al precio. - **Índice de precios**: la posición del país en el [Índice de Precios de Adapty](https://uploads.adapty.io/adapty_pricing_index.pdf), que refleja el poder adquisitivo de sus habitantes. Puedes actuar sobre estas recomendaciones creando pruebas A/B a partir de las [sugerencias de precios por región](autopilot-growth-plan#geo-pricing-hypotheses) de tu plan de crecimiento. ## Cómo decide AI Growth Advisor qué recomendar \{#how-ai-growth-advisor-decides-what-to-recommend\} AI Growth Advisor genera un conjunto de sugerencias para mejorar la conversión de tu paywall. Estas sugerencias están diseñadas para probarse de una en una, de modo que puedas medir de forma fiable el impacto de cada cambio. Así es como AI Growth Advisor elabora sus sugerencias: 1. **Encontrar las mayores oportunidades** AI Growth Advisor analiza tus precios, productos y el rendimiento de tu embudo, y los compara con patrones del sector y aplicaciones similares. El análisis se ejecuta en la moneda de tu mercado principal —no solo en USD—, de modo que las recomendaciones de precio se ajusten a lo que realmente pagan tus suscriptores. Identifica dónde tienes más margen de mejora, ya sea ajustando el precio, añadiendo un período de prueba o cambiando la estructura de tu oferta. 2. **Seleccionar el siguiente experimento** Cada hipótesis se genera a partir de tu historial de pruebas existente. AI Growth Advisor sabe qué experimentos ya has realizado, cuáles ganaron y qué direcciones siguen valiendo la pena explorar. La siguiente sugerencia parte de lo que reveló la anterior, en lugar de seguir una secuencia fija. 3. **Ejecuta pruebas de ganador vs. retador** Tras cada experimento, el ganador se convierte en tu nueva línea base. Ese resultado da forma a la siguiente recomendación en tu plan de crecimiento: AI Growth Advisor conserva lo que funcionó, descarta lo que no, y elige la siguiente prueba a partir de ahí. 4. **Mantenlo práctico** El AI Growth Advisor solo sugiere pruebas que puedes lanzar con tus productos y configuración actuales, o con cambios mínimos como crear un nuevo producto o ajustar un precio. El objetivo es que las pruebas sean rápidas y manejables. 5. **Mostrarte el razonamiento** Para cada recomendación, el AI Growth Advisor ofrece una hipótesis clara que explica exactamente por qué vale la pena ejecutar esa prueba. Verás cómo se comparan tus métricas actuales con las de la competencia y los promedios del sector, cuál es la oportunidad y qué métricas clave esperamos mejorar. Esto convierte la experimentación en un proceso repetible donde cada prueba te enseña algo y te acerca a un paywall más efectivo. ## Qué ocurre después de cada experimento \{#what-happens-after-each-experiment\} Las recomendaciones no se agotan. Cada prueba completada se convierte en la base para nuevos experimentos. Mientras sigas probando, AI Growth Advisor seguirá sugiriendo qué probar a continuación. Para actualizar los datos de mercado subyacentes, vuelve a ejecutar el análisis sobre el mismo placement. Cada nueva ejecución incorpora los precios actualizados de la competencia, los benchmarks de conversión y las tendencias de categoría, y añade las nuevas hipótesis identificadas a tu plan de crecimiento sin alterar lo que ya existe. Las hipótesis generadas por IA, las hipótesis personalizadas y las pruebas A/B en curso se conservan entre ejecuciones. Una vez que hayas optimizado tu línea base, también puedes optar por competir con competidores más avanzados. Este enfoque iterativo te ayuda a maximizar continuamente tus ingresos a medida que tu app crece y el mercado evoluciona. :::tip ¿Listo para probarlo? Lanza [AI Growth Advisor](autopilot-analysis) para analizar tus paywalls y generar un plan de crecimiento con pruebas A/B. Usa el [asistente integrado](autopilot-execute-plan) para lanzar pruebas complejas sin complicaciones: te guiará a través de la creación de productos, duplicación de paywalls y configuración de segmentos. ::: --- # File: autopilot-analysis --- --- title: "Análisis de Paywall y Mercado" description: "Genera un plan de crecimiento basado en datos adaptado a tu app." --- Sigue los pasos de este artículo para ejecutar el análisis del AI Growth Advisor y generar un plan de crecimiento. Si ya generaste un plan de crecimiento para el placement objetivo, este análisis generará nuevas hipótesis entre las que podrás elegir. :::tip Asegúrate de cumplir los [requisitos para el análisis](autopilot#prerequisites) antes de empezar. ::: ## Análisis del paywall \{#paywall-analysis\} ### Selecciona un paywall para analizar \{#select-a-paywall-for-analysis\} 1. Abre la página **AI Growth Advisor** y haz clic en el botón [Get Growth plan](https://app.adapty.io/ab-tests/analysis/start). 2. En la página **Paywall Diagnostic**, selecciona un **Placement** y un **Paywall** en los desplegables. Adapty preselecciona el placement con mayores ingresos y su paywall principal. Para analizar un paywall diferente, cambia primero el placement. 3. Sube una captura de pantalla. AI Growth Advisor necesita una captura de pantalla para analizar el diseño y el contenido de tu paywall. 4. Revisa los productos activos del paywall. Las tarjetas de productos a la derecha muestran la duración de la suscripción, el precio y el período de prueba de cada producto. 5. Haz clic en **Confirm & Analyze** para continuar. Adapty analiza tu paywall y muestra el informe de diagnóstico. ### Informe de análisis de paywall \{#paywall-analysis-report\} Después de seleccionar un paywall y subir una captura de pantalla, Adapty analiza tu paywall en busca de patrones de diseño establecidos, destacando tanto los aciertos como las oportunidades de mejora. #### Lo que está funcionando bien \{#whats-working-well\} Esta sección destaca el uso de patrones establecidos que maximizan la conversión. Por ejemplo: una insignia de ahorro visible, una sección de reseñas de usuarios destacada o desgloses claros de la suscripción. #### Qué mejorar en tu paywall \{#what-to-fix-on-your-paywall\} Adapty agrupa sus recomendaciones en dos categorías: - **Recomendaciones basadas en benchmarks**: Sugerencias respaldadas por datos de las apps con mejor rendimiento en tu categoría. Cada recomendación incluye una estadística de referencia (por ejemplo, "Usada por el 72% de las apps de Educación con mejor rendimiento") y una descripción del cambio propuesto. - **Recomendaciones por análisis visual**: Sugerencias generadas por IA a partir de la captura de pantalla de tu paywall. Incluyen: mejoras de copy, cambios de diseño y más. :::tip Tu [plan de crecimiento](autopilot-growth-plan#view-the-growth-plan) incluirá hipótesis basadas en las recomendaciones de referencia. Puedes añadir sugerencias de análisis visual al plan manualmente. ::: Haz clic en **Get Market Insights** para continuar. ## Análisis de mercado y competencia \{#market-and-competitor-analysis\} :::note El análisis de mercado y competencia requiere completar primero el [análisis de paywall](#paywall-analysis). ::: El análisis de Market Insights compara los precios y métricas de conversión de tu app con los de sus competidores y la media del sector. Las comparaciones son específicas por país. Para ofrecer un punto de referencia, Adapty agrega y analiza datos de apps de la App Store en tu subcategoría y país; información que no está disponible públicamente en ningún otro lugar. ### Seleccionar competidores \{#select-competitors\} Selecciona hasta 5 competidores para la comparación. Adapty elegirá 5 automáticamente y sugerirá 5 más. También puedes añadir apps manualmente con un enlace de App Store. Para obtener mejores resultados, selecciona apps con un MRR mayor que el tuyo. Haz clic en **Generate report** para confirmar la lista y espera a que finalice el análisis. ### Seleccionar un país \{#select-a-country\} Usa el desplegable **Country** para seleccionar uno de tus principales países y obtener un análisis detallado. ### Distribución de ingresos \{#revenue-distribution\} Este gráfico de distribución de ingresos muestra de qué países provienen tus ingresos, con desglose por porcentajes. Destaca tus 5 países principales, que son el foco del resto del análisis. ### Precios de la competencia \{#competitor-pricing\} La tabla de precios de la competencia compara los precios de suscripción de tu paywall con los de tus competidores en el [país seleccionado](#select-a-country). Incluye columnas separadas para cada duración de suscripción. ### Embudo de conversión \{#conversion-funnel\} El gráfico muestra tus tasas de conversión — Vistas a prueba, Prueba a pago y Vistas a pago — junto a los promedios de aplicaciones similares. ### Distribución de ingresos por duración \{#revenue-distribution-by-duration\} Este gráfico muestra qué duraciones de suscripción contribuyen más a tus ingresos, en comparación con la media del sector. Si tus ingresos están muy concentrados en una sola duración, puede ser una señal de oportunidad para optimizar tu estrategia de precios. ### ARPU de Activación \{#activation-arpu\} El gráfico **Activation ARPU: your app vs. category** compara el ingreso medio por nueva instalación de tu app con el promedio de la categoría. Úsalo junto con el [embudo de conversión](#conversion-funnel): - La conversión muestra cuántos usuarios pagan. - El ARPU de Activación muestra el ingreso medio por usuario. Una tasa de conversión alta con un ARPU de Activación bajo puede indicar que las ofertas tienen un precio demasiado bajo. La métrica es **basada en cohortes**. Adapty toma los usuarios que instalaron la app en los últimos 90 días y divide los ingresos que generaron entre su número. #### Comparación con otras métricas \{#comparison-to-other-metrics\} El ARPU de activación no coincidirá con los valores de ARPU que ves en otras partes del dashboard, ya que cada métrica mide algo diferente. - **[El gráfico de análisis ARPU](arpu)**: incluye las renovaciones de cohortes anteriores, por lo que el número es varias veces superior al ARPU de activación. - **[Gráfico de ingresos](revenue), filtro de período configurado en "Activation"**: solo cuenta el primer pago de cada usuario. No cuenta las renovaciones realizadas por la cohorte dentro de la ventana de 90 días. - **[Ingresos por cohorte](analytics-cohorts) (90 días)**: el equivalente más cercano; usa esta métrica como referencia. ## Próximos pasos \{#next-steps\} Lee el artículo [Gestionar y ejecutar tu plan de crecimiento](autopilot-growth-plan) para aprender a ejecutar pruebas A/B basadas en los resultados del análisis. Siempre puedes ver los resultados de tu análisis desde la página del Plan de crecimiento. Solo tienes que hacer clic en la pestaña Analysis Results. --- # File: autopilot-growth-plan --- --- title: "Gestiona tu plan de crecimiento" description: "Añade hipótesis personalizadas, archívalas y actualiza el plan de crecimiento." --- Una vez que completas [el análisis](autopilot-analysis), Adapty te presenta tu plan de crecimiento: una lista de **hipótesis de mejora accionables**. Cada elemento sugiere un nuevo precio o una mejora de diseño. Abre una hipótesis para [probarla con una prueba A/B](autopilot-execute-plan). Cada placement tiene su propio plan de crecimiento. A medida que cambian las condiciones del mercado, puedes volver a ejecutar el análisis para actualizar las sugerencias. Las ejecuciones anteriores se guardan en el historial de versiones. ## Hipótesis \{#hypotheses\} Usa las pestañas en la parte superior del plan de crecimiento para filtrar hipótesis por tipo: - **Prioridad máxima** incluye las hipótesis de mayor impacto que merecen tu atención. Cuando ninguna hipótesis cumple los requisitos, esta pestaña se oculta. - **Todas** muestra todas las hipótesis de tu plan activo. - Las hipótesis de **Precios** exploran nuevos puntos de precio o configuraciones de prueba. Cada una se basa en una recomendación específica del diagnóstico de paywall o del informe de insights de mercado. - Las hipótesis **Visuales** son sugerencias de mejora de diseño. Pueden implicar cambios en el texto, el diseño o cualquier otro elemento visual. - Las hipótesis de [**Precios por geo**](#geo-pricing-hypotheses) prueban ajustes de precio específicos por país. - Las hipótesis [**Archivadas**](#archive-a-hypothesis) son sugerencias que eliminaste de tu plan activo. Puedes restaurarlas en cualquier momento. Puedes [añadir tus propias hipótesis](#add-your-own-hypothesis), o [archivar](#archive-a-hypothesis) las que no quieras probar. Prueba estas hipótesis de una en una, en cualquier orden. Las pruebas de precios por región son la excepción: sus audiencias no se solapan, así que pueden ejecutarse en paralelo. ### Hipótesis de precios por región \{#geo-pricing-hypotheses\} :::important Las compras únicas no son elegibles para la optimización regional de precios. ::: Abre la pestaña **Geo-pricing** para ver la lista de recomendaciones de precios por región. Cada recomendación apunta a un país con un único cambio de precio y se ejecuta como una prueba A/B independiente. Adapty detecta los países que necesitan ajustes de precios y ofrece recomendaciones basadas en datos validadas por el [Índice de Precios de Adapty](https://uploads.adapty.io/adapty_pricing_index.pdf). <br /> #### Cómo genera Adapty las sugerencias de precios por región \{#how-adapty-makes-geo-pricing-suggestions\} - Las recomendaciones de precios se basan en datos de App Store. La prueba A/B resultante puede ejecutarse tanto en App Store como en Google Play. - El porcentaje de cambio de precio es el mismo para todas las duraciones de suscripción. - Todos los precios se redondean al nivel de precio de App Store más cercano. - Los precios aparecen en moneda local (por ejemplo, EUR o GBP) si Adapty dispone de datos de transacciones para ese país. Cuando no hay datos locales disponibles, los precios se muestran en USD. ### Insignias de estado de hipótesis \{#hypothesis-status-badges\} 1. **Rayo** — indica sugerencias de máxima prioridad. 2. **Estado de sincronización del producto** — aparece cuando se requiere alguna acción con el producto para lanzar la prueba A/B. - **Draft** — la configuración del producto está incompleta (del lado de Adapty) - **Action required** — la configuración del producto está incompleta (del lado de la store) - **Pending...** — Adapty está esperando a que la store complete la revisión o la sincronización inicial. - **Approved** — la store ha aprobado el producto y está listo para las pruebas. - **Rejected** — la store rechazó el producto. - **Not connected** — el producto aún no está vinculado a la store. 3. **Estado de la prueba A/B** — aparece cuando lanzas la prueba A/B: - **Draft test** — la prueba está en borrador pero aún no está en ejecución. - **Running** — la prueba está activa. - **Completed** — la prueba ha finalizado. - **Archived test** — la prueba fue archivada sin concluirse. ## Obtener nuevas hipótesis generadas por IA \{#get-new-ai-generated-hypotheses\} Tras cada prueba, Adapty actualiza automáticamente tus hipótesis en función del resultado. Para obtener los últimos precios de la competencia, referencias de conversión y tendencias de categoría, haz clic en **Update** Refresh en la cabecera del plan de crecimiento, o en **Update Analysis** en la página de inicio de AI Growth Advisor. Cuando se te solicite, haz clic en **Get New Ideas**. Adapty abre el asistente de análisis con tu placement preseleccionado. Repite el análisis del paywall y la investigación de competidores, y Adapty identificará nuevas hipótesis a partir de los resultados. Elige las que quieras añadir, o haz clic en **Add All To Plan** Plus para aceptarlas todas. Las hipótesis recién añadidas aparecen en la parte superior de la lista. Las hipótesis generadas por IA, las hipótesis personalizadas y las pruebas A/B en curso no se ven afectadas. Si sales de una actualización antes de terminarla, puedes **reanudarla** para continuar o **descartarla** para empezar de cero. ## Añade tu propia hipótesis \{#add-your-own-hypothesis\} Haz clic en **Add Hypothesis** Plus para crear tu propia sugerencia de precios o visual. Rellena el formulario: título, descripción y tipo de hipótesis (**Monetization** o **Visual**). - Selecciona las métricas que esperas mejorar en el menú desplegable. - Las hipótesis de monetización también requieren seleccionar los productos de prueba. ## Archivar una hipótesis \{#archive-a-hypothesis\} Para archivar una hipótesis, haz clic en el botón Close y luego en Skip para confirmar. Opcionalmente puedes explicar el motivo — esto ayuda a Adapty a mejorar las sugerencias futuras. La hipótesis se traslada a la pestaña **Archived**. Para restaurar una hipótesis archivada a tu plan activo, haz clic en **Restore** en la tarjeta. ## Revisar y reutilizar hipótesis anteriores \{#revisit-and-reuse-old-hypotheses\} Para consultar las sugerencias generadas por análisis anteriores, haz clic en **Clock** Clock en la cabecera del plan de crecimiento. El modal **Version history** muestra todos los análisis anteriores del placement: fecha, paywall y cuántas de sus hipótesis aceptaste en su momento. Haz clic en una ejecución anterior para ver las hipótesis que generó. Puedes añadir cualquiera de ellas a tu plan activo con **Add to Plan** Plus — útil para recuperar sugerencias que no aplicaste la primera vez. --- # File: autopilot-execute-plan --- --- title: "Ejecuta tu plan de crecimiento" description: "Lanza pruebas A/B desde las hipótesis de tu plan de crecimiento." --- Puedes ejecutar las pruebas en cualquier orden, pero tienes que ejecutarlas **de una en una**. Como las audiencias de las pruebas de precios por ubicación geográfica no se solapan, pueden ejecutarse en paralelo entre sí. Cuando termine una prueba, avanza la estrategia ganadora a la siguiente ronda. Cada ronda va afinando la configuración más eficiente para tu app. Según nuestras estimaciones, ejecutar el conjunto completo de pruebas recomendadas podría aumentar tus ingresos **hasta un 80%**. :::important Cada sugerencia incluye la duración mínima para la prueba A/B. Sigue estas recomendaciones para obtener los datos más precisos antes de pasar a la siguiente etapa. Deberás detener la prueba A/B manualmente. ::: Abre una hipótesis y haz clic en **Set Up & Run Test** para iniciar el asistente de creación de pruebas A/B. ## Paso 1: Ver la hipótesis \{#step-1-view-the-hypothesis\} El primer paso muestra una visión general de la hipótesis. Describe los cambios sugeridos y explica el razonamiento detrás de ellos. Haz clic en "Set up & Run Test" para continuar con el siguiente paso. {/* TODO: REPLACE SCREENSHOT */} ## Paso 2: Crear nuevos productos \{#step-2-create-new-products\} Si la prueba implica un cambio de precio, este paso te ayuda a crear nuevos productos para la variante. Las hipótesis visuales omiten este paso. * Haz clic en **Create a new product and push to stores** para crear un nuevo producto desde cero. * Haz clic en **Connect an existing product** si el producto necesario ya existe en la configuración de tu store. ## Paso 3: Configura el segmento y el paywall \{#step-3-set-up-segment-and-paywall\} El tercer paso te permite configurar la variante de prueba del paywall. Adapty te sugiere duplicar el paywall y aplicar los cambios recomendados. Para las **hipótesis de precios por zona geográfica**, el asistente te pide que selecciones un segmento con restricción geográfica existente o que crees uno nuevo. Haz clic en **Next** cuando el nuevo paywall esté listo y el segmento configurado. ## Paso 4: Revisión y lanzamiento \{#step-4-review--launch\} El último paso es un resumen del test que está a punto de comenzar. Incluye: - Las métricas clave de **Variante A vs Variante B**: nombre del paywall, selección de productos, duración del período de prueba y precio. - La **Duración**, el **Tráfico** (distribución) y los **Suscriptores** (tamaño mínimo de muestra) del test. - Una sección **Cómo interpretar los resultados** que describe qué señales indican el éxito del test. Revisa la configuración y haz clic en **Launch Test** para iniciar la prueba A/B. --- # File: how-adapty-analytics-works --- --- title: "Cómo funciona Adapty Analytics" description: "Aprende cómo funciona Adapty Analytics para rastrear el rendimiento de suscripciones de forma eficiente." --- Este artículo describe cómo funciona Adapty Analytics: qué datos muestra, de dónde provienen y cómo se procesan. También explica las decisiones de diseño que hacen que Adapty Analytics sea diferente y cómo estas decisiones te benefician. ## Adapty Analytics vs. análisis del store \{#adapty-analytics-vs-store-analytics\} - **Variedad de datos**: Los stores solo pueden mostrar sus propios datos y no tienen acceso al comportamiento del usuario dentro de la app. Adapty puede combinar datos de múltiples stores, así como de fuentes adicionales: plataformas de marketing y redes publicitarias. El SDK de Adapty registra las interacciones de los usuarios con los paywalls y los onboardings. - **Frecuencia de actualización**: Los stores de apps suelen actualizar sus datos una vez al día, lo que puede limitar tu capacidad de tomar decisiones en tiempo real. Adapty ofrece análisis [casi en tiempo real](#data-processing). - **Métricas avanzadas**: Los stores muestran métricas básicas como descargas, ingresos y tasas de retención. Adapty también calcula métricas avanzadas, como ingresos recurrentes o ingresos medios por usuario. Las secciones dedicadas analizan los problemas de suscripción: abandono de usuarios, fallos de facturación, etc. Consulta el artículo [Tabla de comparación de métricas](metric-comparison-table) para ver la lista completa. - **Predicciones**: Adapty utiliza algoritmos avanzados de machine learning para [predecir el LTV y los ingresos futuros](predicted-ltv-and-revenue). ## Datos y sus fuentes \{#data-and-its-sources\} Adapty Analytics procesa los siguientes datos en [gráficos y tablas](analytics): - [Eventos de suscripción](events) generados a lo largo del ciclo de vida del usuario — inicios de prueba, compras, renovaciones, cancelaciones, fallos de facturación, reembolsos. Adapty los agrega en los [gráficos de analíticas](analytics) y los reenvía en tiempo real a [webhooks](webhook), el [feed de eventos](event-feed) e [integraciones basadas en eventos](analytics-integration). - [Datos de transacciones](revenue) — ingresos, reembolsos, país del comprador, etc. - **Datos de la aplicación**, como el número de instalaciones o las [interacciones con paywalls](paywalls). - [Datos de atribución para transacciones](attribution-integration): fuentes de tráfico y campañas publicitarias. Este dato proviene de las siguientes fuentes: - El <InlineTooltip tooltip="SDK de Adapty">[iOS](ios-sdk-overview), [Android](android-sdk-overview), [React Native](react-native-sdk-overview), [Flutter](flutter-sdk-overview), [Unity](unity-sdk-overview), [Kotlin Multiplatform](kmp-sdk-overview), [Capacitor](capacitor-sdk-overview) </InlineTooltip> reporta datos de comportamiento del usuario desde dentro de la app. Si Adapty gestiona tu flujo de compras, el SDK comparte información de primera mano sobre los eventos de compra. Si usas el [modo observador](observer-vs-full-mode), el SDK recibe [informes de eventos](report-transactions-observer-mode) que configuras manualmente. - Los stores utilizan comunicación servidor a servidor para notificar a Adapty sobre transacciones (pruebas, renovaciones de suscripciones, cancelaciones, etc.). - Los [servicios de atribución](attribution-integration) de terceros (Appsflyer, Adjust, Branch, etc.) comparten datos sobre fuentes de tráfico y campañas publicitarias. Si configuras [Adapty Attribution](adapty-user-acquisition), Adapty puede gestionar los datos de tus campañas publicitarias de forma autónoma, sin necesidad de este paso. - Los usuarios pueden [importar manualmente datos de transacciones históricas](importing-historical-data-to-adapty) para que Adapty los analice y muestre. Un problema con una de las fuentes puede afectar la calidad general de tus datos de análisis. Consulta la sección [Solución de problemas](#troubleshooting) para más información. ## Integraciones de terceros \{#third-party-integrations\} Puedes habilitar [Adapty User Acquisition](adapty-user-acquisition) para ampliar las capacidades analíticas de Adapty con datos de campañas publicitarias. Esto te ayudará a descubrir correlaciones entre el gasto en campañas publicitarias y el comportamiento de los usuarios. Del mismo modo, puedes [exportar](analytics-integration) datos de analíticas a plataformas de terceros o a un [servidor privado](webhook), y analizar los datos de Adapty en otra plataforma. ## Procesamiento de datos \{#data-processing\} Adapty ofrece análisis en tiempo casi real, lo que permite a los usuarios reaccionar rápidamente ante cambios en las métricas clave. - **Gráficos de Analytics**: los datos aparecen con un **retraso de 15–30 minutos** tras producirse una transacción. Adapty necesita ese tiempo para validar la transacción, aplicar comisiones e impuestos y agregar los datos. - **[Feed de eventos](event-feed)**: se actualiza en tiempo real, en cuanto los stores entregan un evento. - **[Webhooks](webhook) e integraciones basadas en eventos** (AppsFlyer, Branch, etc.): Adapty reenvía los eventos según se producen — el retraso de 15–30 minutos de analytics no aplica aquí. El servicio receptor puede introducir su propio tiempo de procesamiento. Cada superficie tiene su propio timing. El mismo evento puede aparecer en momentos ligeramente distintos en los gráficos, el Event Feed y tus integraciones. Pequeñas discrepancias entre ellos son algo esperado. ## Comisiones e impuestos \{#commissions-and-taxes\} Al consultar gráficos relacionados con ingresos, puedes elegir entre **Ingresos brutos**, **Ingresos tras comisiones** e **Ingresos tras comisiones e impuestos**. ### Comisiones \{#commissions\} Los stores deducen una comisión de cada transacción. Si tu organización está inscrita en un programa de comisión reducida, cambia tu configuración de Adapty para modificar el cálculo de las tasas de comisión: * [App Store Small Business Program](app-store-small-business-program) * [Programa de tarifa de servicio reducida](google-reduced-service-fee) de Google Los stores informan automáticamente si otros factores reducen la comisión de tu transacción: * [Renovaciones de suscripciones de App Store de 1 año o más](https://developer.apple.com/app-store/subscriptions/) — comisión del 15% * Tarifas específicas por país (por ejemplo, [21% para apps de App Store distribuidas en Japón](https://developer.apple.com/support/app-distribution-in-japan/#business-terms)) ### Impuestos \{#taxes\} **Adapty no calcula impuestos.** Apple y Google determinan el tipo impositivo que se aplica a cada transacción y lo notifican a Adapty, que muestra el valor tal como lo recibe. El tipo impositivo que aparece en una transacción depende de: - El **país de facturación del comprador** y el tipo impositivo local vigente en ese lugar. - Las **reglas de gestión de impuestos del store**. En algunas jurisdicciones, el store recauda y remite los impuestos en nombre del desarrollador; en otras, es el desarrollador quien se encarga. - En las transacciones de App Store, la **categoría fiscal** asignada a la app o a la compra in-app (libros, noticias, vídeos, etc.) — las categorías pueden tributar a tipos distintos según la normativa local. Las tasas impositivas pueden variar considerablemente entre apps —e incluso entre transacciones dentro de la misma app— debido a la combinación de países de los compradores, las reglas de gestión de los stores y (en el caso de App Store) la categoría fiscal asignada. Para conocer las reglas oficiales, consulta la documentación de los stores: - [App Store: Understanding taxes](https://developer.apple.com/help/app-store-connect/making-payments-to-apple/understanding-taxes/) - [Google Play: Tax rates and VAT](https://support.google.com/googleplay/android-developer/answer/138000) ## Solución de problemas \{#troubleshooting\} :::link Artículo principal: [Discrepancias y solución de problemas](discrepancies-and-troubleshooting) ::: * Una fuente de datos mal configurada o ausente puede afectar negativamente a todo el sistema de analíticas. Si encuentras problemas con los datos, asegúrate de que tus integraciones con stores y plataformas de terceros estén configuradas y activas. * Si comparas los gráficos de Adapty con otras plataformas de análisis, es posible que notes discrepancias. Esto es un comportamiento esperado que puede deberse a diferencias en el procesamiento de datos. Lee el artículo de la [guía sobre discrepancias](discrepancies-and-troubleshooting) para conocer las causas más comunes. --- # File: metric-comparison-table --- --- title: "Comparar diferentes métricas" description: "Tablas de referencia para las métricas de análisis de Adapty, organizadas por categoría." --- Esta es una descripción general de las métricas disponibles en Adapty Analytics. Úsala para entender qué mide cada métrica y en qué se diferencia de las métricas relacionadas. Para una explicación más detallada de cómo Adapty procesa los datos de análisis, consulta [Cómo funciona Adapty Analytics](how-adapty-analytics-works). :::note Este artículo no cubre las métricas de [Atribución de Adapty](adapty-user-acquisition). Consulta [Análisis de Atribución de Adapty](ua-analytics) para obtener más información sobre las métricas de campañas publicitarias (Gasto, CPI, ROAS, CTR, entre otras). ::: ## Métricas globales \{#global-metrics\} Las métricas globales hacen seguimiento del rendimiento de toda la app, en todos los placements y paywalls. ### Ingresos \{#revenue\} Estas métricas miden cuánto dinero genera la app y de qué fuentes proviene. | Métrica | Descripción | Diferencia clave | |--------|-------------|----------------| | [Revenue](revenue) | Ingresos totales de suscripciones y compras únicas, menos reembolsos | Ingresos reales generados. Puede mostrar ingresos brutos, ingresos tras comisión, o ingresos tras impuestos y comisión según los [controles del gráfico](controls-filters-grouping-compare-proceeds) | | [MRR](mrr) | Ingresos recurrentes mensuales de suscripciones activas | Ingresos mensuales predecibles de tu app. Excluye compras únicas y suscripciones no recurrentes. | | [ARR](arr) | Ingresos recurrentes anuales de suscripciones activas | Se calcula como el MRR pero a escala anual. Útil para proyectar ingresos anuales | | [ARPU](arpu) | Ingresos medios por usuario | Divide los ingresos entre el número total de usuarios — de pago y gratuitos. Muestra cuántos ingresos genera cada usuario de media | | [ARPPU](arppu) | Ingresos medios por usuario de pago | Solo cuenta los usuarios que realizaron una compra durante el período seleccionado, incluidas las transacciones reembolsadas. Siempre es mayor que el ARPU | | [LTV (lifetime value)](ltv) | Ingresos de clientes de pago divididos entre el número de clientes de pago en una cohorte | Valor realizado por cliente de pago a lo largo del tiempo. A diferencia del ARPPU (período único), el LTV muestra los ingresos totales durante toda la relación con el cliente. Se puede ver por renovaciones o por días naturales | | [LTV predicho](predicted-ltv-and-revenue) | Valor estimado de por vida por usuario en una cohorte | Estimación prospectiva. A diferencia del LTV realizado, proyecta el valor futuro a partir de los patrones históricos de retención de cohortes. Disponible para 3, 6, 9, 12, 18 y 24 meses | | [Revenue predicho](predicted-ltv-and-revenue) | Ingresos totales estimados que generará una cohorte | Estimación prospectiva. A diferencia del Revenue realizado, predice el total que generará una cohorte durante el período seleccionado. Se actualiza a diario | | [Non-subscriptions](non-subscriptions) | Recuento de compras in-app: consumibles, no consumibles y suscripciones no renovables | Excluye las suscripciones de renovación automática. | | [Refund events](refund-events) | Recuento de compras o suscripciones reembolsadas | Se atribuye a la fecha del reembolso, no a la fecha de compra original. | | [Refund money](refund-money) | Importe total reembolsado durante el período seleccionado | Impacto financiero de los reembolsos. Se calcula antes de las comisiones del store. A diferencia de Refund events (un recuento), este muestra el importe monetario | ### Suscriptores y conversión \{#subscribers-and-conversion\} Estas métricas muestran cómo los usuarios llegan a la app y avanzan por el embudo. | Métrica | Descripción | Diferencia clave | |--------|-------------|----------------| | [Instalaciones](installs) | Número de instalaciones de la app durante el período | Cuenta una de las siguientes opciones según [la definición de instalación](general#4-installs-definition-for-analytics): <br /> • Instalaciones de dispositivo (un usuario que reinstala la app se cuenta de nuevo) <br /> • Usuarios únicos (solo cuenta usuarios que hayan configurado `customer_user_id`. Los usuarios anónimos se excluyen por completo — si no hay usuarios identificados, el recuento es 0) | | [Nuevas pruebas](new-trials) | Pruebas activadas durante el período | Cuenta cada inicio de prueba, aunque la prueba ya haya expirado o convertido a pago en el momento en que consultas el gráfico | | [Pruebas activas](active-trials) | Número de pruebas que aún no han expirado | Solo cuenta las pruebas activas al final del período | | [Nuevas suscripciones](reactivated-subscriptions) | Suscripciones activadas por primera vez durante el período, incluyendo tanto primeras compras sin prueba como conversiones de prueba a pago | Excluye renovaciones y reactivaciones. No equivale al evento de integración `subscription_started`, que solo cuenta primeras compras sin prueba — las conversiones de prueba disparan `trial_converted` en su lugar | | [Suscripciones activas](active-subscriptions) | Número de suscripciones de pago que aún no han expirado | Excluye pruebas y suscripciones con renovación cancelada | | [Instalación a prueba](analytics-conversion#install---trial) | Porcentaje de usuarios instaladores que iniciaron una prueba | El denominador incluye a todos los instaladores, no solo a quienes vieron un paywall, por lo que la tasa puede ser inferior a «Vista de paywall a prueba». Las dos métricas también pueden diferir si la app no registra las vistas de paywall. Esto puede ocurrir con un paywall personalizado que no llama a `logShowFlow` (SDK de iOS v4+) / `logShowPaywall`, o cuando un usuario inicia su prueba desde una [compra in-app promocionada](https://developer.apple.com/documentation/storekit/supporting-promoted-in-app-purchases-in-your-app). | | [Vista de paywall a prueba](analytics-conversion#paywall-view---trial) | Porcentaje de visitantes del paywall que iniciaron una prueba | Solo cuenta usuarios que vieron un paywall, por lo que la tasa puede ser superior a «Instalación a prueba» | | [Prueba a pago](analytics-conversion#trial---paid) | Porcentaje de usuarios en prueba que compraron una suscripción | Mide la calidad de la prueba y la eficiencia de conversión. A diferencia de «Instalación a pago», se centra únicamente en usuarios que completaron una prueba | | [Instalación a pago](analytics-conversion#install---paid) | Porcentaje de usuarios instaladores que compraron una primera suscripción | Cuenta a todos los instaladores, no solo a quienes vieron un paywall. La tasa puede ser inferior a «Vista de paywall a pago». Incluye tanto compras directas como conversiones de prueba a pago | | [Vista de paywall a pago](analytics-conversion#paywall-view---paid) | Porcentaje de visitantes del paywall que finalmente compraron una suscripción | Solo cuenta usuarios que vieron un paywall, por lo que la tasa puede ser superior a «Instalación a pago». Incluye usuarios que completaron primero una prueba | ### Retención y renovación de suscripciones \{#retention-and-subscription-renewal\} Estas métricas miden con qué eficacia la app retiene a los suscriptores de pago a lo largo del tiempo. | Métrica | Descripción | Diferencia clave | |---------|-------------|-----------------| | [Retención](analytics-retention) | Proporción de suscriptores originales que permanecen tras cada período de facturación — 1.ª renovación, 2.ª renovación, etc. | Hace seguimiento de los suscriptores desde el primer pago. A diferencia de las métricas período a período que se muestran a continuación, siempre compara con el grupo original, por lo que tienes la imagen completa de un vistazo | | [Paid to 2nd period](analytics-conversion#paid---2nd-period) | Porcentaje de suscriptores nuevos que renovaron para el segundo período | Mide la transición entre dos períodos adyacentes específicos. A diferencia de Retención, se centra en la renovación más crítica: la primera | | [2nd to 3rd period](analytics-conversion#2nd-period---3rd-period) | Porcentaje que renueva del 2.º al 3.er período | Indica la estabilidad de retención temprana tras la renovación inicial | | [3rd to 4th period](analytics-conversion#3rd-period---4th-period) | Porcentaje que renueva del 3.er al 4.º período | Indicador de retención a medio plazo | | [4th to 5th period](analytics-conversion#4th-period---5th-period) | Porcentaje que renueva del 4.º al 5.º período | Indicador de fidelidad a largo plazo | | [6 Months+](analytics-conversion#6-months-) | Porcentaje de suscriptores nuevos que permanecen suscritos más de 6 meses | Mide tiempo en calendario, no número de renovaciones. Un suscriptor anual cuenta como retenido a los 6 meses aunque no haya realizado ninguna renovación | | [1 Year+](analytics-conversion#1-year-) | Porcentaje de suscriptores nuevos que permanecen suscritos más de 12 meses | Hito de retención anual | | [2 Years+](analytics-conversion#2-years-) | Porcentaje de suscriptores nuevos que permanecen suscritos más de 24 meses | Hito de retención a largo plazo | ### Pérdida de usuarios \{#churn\} Estas métricas miden cuántos suscriptores y usuarios en período de prueba pierde la app. | Métrica | Descripción | Diferencia clave | |---------|-------------|------------------| | [Renovaciones de prueba canceladas](trials-renewal-cancelled) | Pruebas en las que el usuario desactivó la renovación automática | El usuario mantiene acceso durante el período de prueba pero no se convertirá a pago automáticamente. A diferencia de Renovaciones de suscripción canceladas, aplica a usuarios en prueba que aún no han pagado | | [Pruebas expiradas (churned)](expired-churned-trials) | Pruebas que expiraron: el usuario perdió el acceso a las funciones premium | El usuario ya ha perdido el acceso. Se atribuye a la fecha de expiración, aunque el usuario haya cancelado la renovación en un período anterior. Se puede agrupar por motivo (voluntario vs. facturación) | | [Renovaciones de suscripción canceladas](cancelled-subscriptions) | Suscripciones en las que el usuario desactivó la renovación automática | El usuario sigue teniendo acceso hasta que finalice el período. Indica riesgo de churn, no churn real: el usuario puede volver a activar la renovación automática antes de que expire | | [Suscripciones expiradas (churned)](churned-expired-subscriptions) | Suscripciones que expiraron: el usuario perdió el acceso a las funciones premium | Churn real. El usuario ya ha perdido el acceso. Se atribuye a la fecha de expiración, aunque el usuario haya cancelado la renovación en un período anterior. Se puede agrupar por motivo (voluntario vs. facturación) | ### Problemas de facturación y recuperación de ingresos \{#billing-issues-and-revenue-recovery\} Estas métricas miden la eficacia con la que la app recupera ingresos perdidos por problemas de facturación. | Métrica | Descripción | Diferencia clave | |--------|-------------|----------------| | [Período de gracia](grace-period) | Suscripciones que entraron en período de gracia por un fallo de facturación | Incluye usuarios que superaron el período de gracia y perdieron el acceso | | [Período de gracia a pago](analytics-conversion#grace-period---paid) | Porcentaje de usuarios en período de gracia que renovaron antes de que terminara el período de gracia | Una tasa (%). Responde a "¿qué proporción de usuarios en período de gracia se recuperó?" | | [Período de gracia convertido](grace-period-converted) | Número absoluto de suscripciones en período de gracia que se renovaron con éxito | Mismos eventos que Período de gracia a pago, pero mostrados como recuento en lugar de porcentaje | | [Ingresos convertidos en período de gracia](grace-period-converted-revenue) | Ingresos de las recuperaciones en período de gracia | Impacto financiero de la función de período de gracia | | [Problema de facturación](billing-issue) | Suscripciones que entraron en estado de problema de facturación | Comienza tras expirar el período de gracia. A diferencia del período de gracia, solo cuenta los usuarios que ya han perdido el acceso premium | | [Problema de facturación a pago](analytics-conversion#billing-issue---paid) | Porcentaje de usuarios con problema de facturación que renovaron antes de que terminara el ciclo de facturación | Una tasa (%). Responde a "¿qué proporción de usuarios con problema de facturación se recuperó?" | | [Problema de facturación convertido](billing-issue-converted) | Número absoluto de suscripciones con problema de facturación que se renovaron con éxito | Recuento de suscripciones con problema de facturación que se renovaron con éxito. Mismos eventos que Problema de facturación a pago, pero mostrados como recuento en lugar de porcentaje | | [Ingresos convertidos por problema de facturación](billing-issue-converted-revenue) | Ingresos de las recuperaciones por problema de facturación | Impacto financiero de la recuperación por problema de facturación | ## Métricas de paywall, placement y onboarding \{#paywall-placement-and-onboarding-metrics\} Estas métricas se calculan para [paywalls](paywall-metrics), [placements](placement-metrics) y onboardings individuales. Miden el rendimiento de un paywall o placement concreto, no el de la aplicación en su conjunto. La columna **Associated global metric** muestra la métrica correspondiente de la sección de analíticas globales. | Métrica | Descripción | Diferencia clave | Métrica global asociada | |--------|-------------|----------------|---------------| | [Proceeds](paywall-metrics#proceeds) | Ingresos tras impuestos y comisión para un placement concreto | Equivalente a [Revenue](revenue) tras impuestos y comisión | [Revenue](revenue) | | [ARPPU](paywall-metrics#arppu) | Ingresos medios por usuario de pago para este paywall o placement | Mismo cálculo que el ARPPU global, pero limitado a un solo paywall o placement | [ARPPU](arppu) | | [ARPAS](paywall-metrics#arpas) | Ingresos divididos entre el número de suscriptores activos (en prueba y de pago) | Incluye usuarios en prueba. A diferencia del ARPPU, refleja el potencial de ingresos de toda la base de suscriptores | — | | [Views](paywall-metrics#views) | Número total de veces que se mostró un paywall o placement | Cuenta cada visualización. Si un mismo usuario ve el mismo paywall dos veces, se contabilizan 2 vistas | — | | [Unique views](paywall-metrics#unique-views) | Número de usuarios únicos que vieron un paywall o placement | Cada usuario se cuenta una sola vez independientemente de cuántas veces lo haya visto. A diferencia de Views, mide el alcance y no la frecuencia de interacción | — | | [CR to purchases](paywall-metrics#cr-to-purchases) | Compras divididas entre el total de vistas | Usa el total de vistas (incluidas las repetidas por el mismo usuario) como denominador | [Paywall view to paid](analytics-conversion#paywall-view---paid) | | [Unique CR to purchases](paywall-metrics#unique-conversion-rate-cr-to-purchases) | Compras divididas entre vistas únicas | Usa vistas únicas como denominador. La tasa es más alta que la CR no única porque los usuarios que repiten se cuentan una sola vez | [Paywall view to paid](analytics-conversion#paywall-view---paid) | | [CR to trials](paywall-metrics#unique-cr-to-trials) | Pruebas iniciadas divididas entre el total de vistas | Mide con qué eficacia convierte un paywall las vistas en pruebas | [Paywall view to trial](analytics-conversion#paywall-view---trial) | | [Unique CR to trials](paywall-metrics#unique-cr-to-trials) | Pruebas iniciadas divididas entre vistas únicas | Se calcula igual que CR to trials, pero usando usuarios únicos como denominador | [Paywall view to trial](analytics-conversion#paywall-view---trial) | | [Purchases](paywall-metrics#purchases) | Número total de transacciones para este paywall: nuevas compras, conversiones de prueba, upgrades, downgrades y suscripciones que regresan | Excluye renovaciones. | [Revenue](revenue) | | [Trials](paywall-metrics#trials) | Total de pruebas activadas a través de este paywall | Limitado a este paywall únicamente | [New trials](new-trials) | | [Trials canceled](paywall-metrics#trials-canceled) | Número de pruebas en las que el usuario desactivó la renovación automática | Limitado a las pruebas de este paywall únicamente | [Trials renewal cancelled](trials-renewal-cancelled) | | [Refund rate](paywall-metrics#refund-rate) | Reembolsos divididos entre las compras por primera vez (renovaciones excluidas) | Es una tasa (%), no un recuento. Normaliza los reembolsos respecto al número de compras | [Refund events](refund-events) (recuento, no tasa) | | Completions | Número de veces que los usuarios completaron un flow de onboarding de principio a fin | Solo para placements y onboardings. Cuenta cada finalización, incluidas las repetidas por el mismo usuario | — | | Unique completions | Número de usuarios únicos que completaron un flow de onboarding | Solo para placements y onboardings. Cada usuario se cuenta una sola vez. A diferencia de Completions, mide cuántas personas terminaron el flow | — | | Unique completions rate | Unique completions divididas entre vistas únicas | Solo para placements y onboardings. Mide la efectividad del onboarding: qué porcentaje de los usuarios que lo iniciaron realmente lo terminaron | — | --- # File: overview --- --- title: "Página de resumen de analíticas" description: "Consulta varios gráficos de analíticas de Adapty en una misma página para tener una visión general del rendimiento de tu app" --- La [página de resumen](https://app.adapty.io/overview) muestra las métricas combinadas de todas tus apps en un solo lugar. Es la página de inicio del dashboard y también está disponible desde el menú lateral izquierdo. Para ver los datos de una sola app, abre un [gráfico](charts) individual. ## Gráficos \{#charts\} El resumen muestra un subconjunto personalizable de los [gráficos de analíticas](charts) de Adapty. Para ver descripciones y una comparación detallada de todos los gráficos disponibles, consulta la [tabla de comparación de métricas](metric-comparison-table). Para personalizar qué gráficos aparecen y en qué orden, haz clic en **Edit** en la esquina superior derecha. Desde ahí puedes eliminar, añadir o reordenar los gráficos: Los siguientes gráficos están disponibles: - [Revenue](revenue) - [MRR](mrr) - [ARR](arr) - [ARPU](arpu) - [ARPPU](arppu) - [ARPAS](placement-metrics#arpas) - [Installs](installs) - [New trials](new-trials) - [New subscriptions](reactivated-subscriptions) - [Active trials](active-trials) - [Active subscriptions](active-subscriptions) - [New non-subscriptions](non-subscriptions) - [Refund events](refund-events) - [Refund money](refund-money) - [Subscriptions renewal canceled](cancelled-subscriptions) - [Conversion rate from Install to Trial, Install to Paid, and Trial to Paid](analytics-conversion) ## Controles \{#controls\} La página de resumen es compatible con la mayoría de los [controles de analíticas](controls-filters-grouping-compare-proceeds), incluidos el filtrado, la agrupación y la comparación de periodos de tiempo. La única función exclusiva del resumen es la agrupación y el filtrado por app. Como la página combina datos de todas tus apps, la vista por app muestra cómo contribuye cada una a tus métricas de negocio: ## Conteo de instalaciones y zona horaria \{#install-count-and-timezone\} El resumen combina datos de todas tus apps con **su propia zona horaria y configuración de conteo de instalaciones** — los valores por app no se aplican aquí. - **Installs**: elige cómo contabilizar las instalaciones. **By device installations** trata cada instalación en un dispositivo —incluidas las reinstalaciones— como independiente. **By unique users** solo cuenta la primera instalación por usuario identificado. Para cambiar la configuración, haz clic en **Edit Metrics** y selecciona una [opción diferente](general#4-installs-definition-for-analytics) en el desplegable. - **Timezone**: para cambiar la zona horaria del resumen, haz clic en **Edit Metrics** y selecciona una zona horaria en el desplegable. Esto es especialmente útil si las distintas apps de tu cuenta usan zonas horarias diferentes. --- # File: controls-filters-grouping-compare-proceeds --- --- title: "Controles de análisis" description: "Filtra, agrupa y compara los datos de análisis de Adapty." --- Adapty ofrece controles para refinar los datos en cada pestaña de análisis: rango de tiempo, comparación de períodos, filtrado, agrupación y visualización de gráficos. La disponibilidad varía según la pestaña. **Controles disponibles por pestaña de análisis:** | Control | Gráficos | Cohortes | Embudos | Retención | Conversión | LTV | | :--- | :---: | :---: | :---: | :---: | :---: | :---: | | Rango de fechas | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Comparación de períodos | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | Filtro | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Grupo | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | | Visualización de gráfico | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | Vista de tabla | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Exportación CSV | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Comisiones e impuestos | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ### Establece el rango de fechas \{#set-the-date-range\} Usa el calendario **Date range** que aparece sobre cada gráfico para elegir el período de tiempo. Los análisis de Adapty utilizan la **zona horaria UTC**; la [página de Overview](overview) tiene su propia zona horaria configurable. #### Rangos predefinidos \{#preset-ranges\} Usa la opción **Custom** para especificar fechas de inicio y fin arbitrarias. Los rangos predefinidos son: | Preset | Starts | Ends | | --- | --- | --- | | Last 7 days | Hace 6 días | Hoy | | Last 28 days | Hace 27 días | Hoy | | Last month | La misma fecha del mes anterior | Hoy | | Last 3 months | Hace 3 meses | Hoy | | Last 6 months | Hace 6 meses | Hoy | | Last year | Hace 1 año | Hoy | | Previous month | Primer día del mes anterior | Último día del mes anterior | | This month | Día 1 del mes actual | Hoy | | This quarter | Día 1 del trimestre actual | Hoy | | This year | 1 de enero del año actual | Hoy | :::tip Usa **Últimos 28 días** para hacer seguimiento de productos con suscripción semanal: el rango cubre cuatro ciclos semanales completos, así que ninguna semana parcial distorsiona la comparación. ::: #### Escala de tiempo \{#time-scale\} Cada punto de datos del gráfico representa un bloque de tiempo: elige entre día, semana, mes, trimestre y año en el menú desplegable. Día y semana muestran subidas y bajadas a corto plazo; mes, trimestre y año reflejan tendencias a más largo plazo. En los análisis de [cohortes](analytics-cohorts) y [LTV](ltv), esta misma configuración se denomina **cohort length** — consulta esos artículos para más detalles. ### Comparar dos períodos de tiempo \{#compare-two-time-periods\} Haz clic en la opción de comparación junto al calendario para superponer el período actual con uno anterior. La primera comparación que ofrece Adapty es el período inmediatamente anterior, con la misma duración. Para cambiar el rango de comparación, vuelve a hacer clic en la opción y elige un rango personalizado. La comparación aparece: - **En el gráfico** — líneas, áreas o columnas superpuestas, con cero o una agrupación seleccionada. - **Como valor numérico** — la diferencia entre los dos períodos, mostrada en verde (mayor) o rojo (menor). - **En un tooltip** — pasa el cursor sobre cualquier punto de datos para ver la diferencia numérica de ese punto. ### Filtrar y agrupar datos \{#filter-and-group-data\} **Filtra** para restringir el gráfico a los datos que coincidan con uno o más atributos (por ejemplo, un único país o producto). **Agrupa** para dividir el total del gráfico en series separadas: una por cada valor de atributo. Por ejemplo, agrupa los ingresos por país para obtener una línea de ingresos independiente para cada país en lugar de un total combinado. **Atributos disponibles para filtrar y agrupar:** | Atributo | Filtro | Grupo | Descripción | | --- | :---: | :---: | --- | | Atribución | ✅ | ✅ | Reported by, Status, Channel, Campaign, Ad Group, Ad Set y Creative (Keyword). Requiere [integración de atribución](attribution-integration). | | Audiencia | ✅ | ✅ | La [audiencia](audience) a la que pertenece el usuario. | | Estado de renovación | ❌ | ✅ | Si la suscripción se renovará en el siguiente período. | | Período | ✅ | ✅ | Etapa del ciclo de vida de la suscripción: **Trial**, **Activation** (primer pago) o **Renewal 1**–**Renewal 5**, **Renewals 6+** (renovaciones posteriores). | | País | ✅ | ✅ | El país del store del usuario. Si no está disponible, Adapty lo deduce a partir del código de moneda o la IP del dispositivo. | | Tipo de oferta | ✅ | ✅ | La oferta aplicada a la transacción: <ul><li>**Introductory** — una oferta introductoria en el período inicial de la suscripción. Usa **Offer Discount Type** para distinguir entre intros de pago y períodos de prueba gratuitos.</li><li>**Promotional** — Ofertas promocionales de App Store y equivalentes.</li><li>**Offer Code** — códigos promocionales que el cliente introduce en el store.</li><li>**No offer** — sin oferta aplicada.</li></ul> | | ID de oferta | ✅ | ✅ | Un ID de oferta específico. | | Tipo de descuento de oferta | ✅ | ✅ | El modelo de precios de una oferta introductoria o promocional: **Free Trial**, **Pay As You Go** o **Pay Up Front**. Combínalo con **Offer Type** para distinguir, por ejemplo, un período de prueba gratuito de una intro de pago. | | Paywall | ✅ | ✅ | El [paywall](paywalls) utilizado para la compra. | | Pruebas A/B | ✅ | ❌ | La [prueba A/B](ab-tests) activa durante la compra. | | Placement | ✅ | ✅ | El [placement](placements) donde se realizó la compra. | | Store | ✅ | ✅ | El store que procesó la transacción: App Store, Google Play, Stripe, etc. | | Producto | ✅ | ✅ | El [producto](product): suscripciones y compras únicas. | | Duración | ✅ | ✅ | La duración del producto. | | Segmento | ✅ | ✅ | Un [segmento](segments) de usuarios. Agrupa por segmento para comparar el rendimiento de cada segmento con **All users**. <ul><li>Los embudos no admiten agrupación por segmento.</li><li>Si cambias un atributo personalizado después de que un segmento lo utilice, Adapty puede excluir al usuario de ese segmento en los análisis. Los datos seguirán mostrando el valor anterior.</li></ul> | | Motivo de reembolso | ✅ | ✅ | El motivo por el que se reembolsó una transacción (por ejemplo, **Refund** o **Upgraded**). Disponible en los gráficos de reembolsos y resolución de problemas de facturación. | | Motivo de expiración | ❌ | ✅ | El motivo por el que expiró una suscripción o período de prueba: **Cancelled by customer**, **Billing issue**, **Customer hasn't agreed to price increase**, **Unknown** o **Refund**. Disponible en suscripciones y períodos de prueba Expired (Churned). | | Cohorte (solo LTV) | ❌ | ✅ | En el gráfico de LTV, agrupa por duración de cohorte: **Day**, **Week**, **Month** o **Year**. Reemplaza Group by Attribution en este gráfico. | No todas las vistas de análisis admiten todos los filtros o atributos de agrupación mencionados. ARPU e Installs en la pestaña Charts están limitados a Attribution, Country, Segment, Store y (solo como filtro) pruebas A/B. Las pestañas LTV, Cohorts, Funnels, Retention y Conversion admiten cada una un subconjunto diferente. Para ver la compatibilidad exacta, consulta el artículo correspondiente a ese gráfico o pestaña. ### Cómo se determina el país \{#how-country-is-determined\} A cada transacción se le asigna un país en el momento en que se crea. La fuente de ese país, por orden de preferencia, es: 1. El **país de la IP del dispositivo** del usuario en el momento de la transacción. 2. El **país del store** del usuario — el país de su cuenta en App Store o Google Play. 3. El **país de IP** más reciente conocido del usuario. El país del store no está disponible para pagos web (Stripe, Paddle), accesos concedidos manualmente o transacciones en las que el store no lo proporcionó. En esos casos, Adapty recurre al país basado en la IP. Dado que el país se registra por transacción, un usuario que cambie el país de su App Store después de la instalación tendrá valores de país distintos en las transacciones anteriores y posteriores al cambio. Las transacciones pasadas conservan su país original. **GB y United Kingdom.** Los datos de país se almacenan como códigos ISO 3166-1 alpha-2 (es decir, "GB", no "United Kingdom"). La capa de visualización del dashboard mapea los códigos a nombres completos mediante una tabla de búsqueda que incluye un alias heredado `'UK' → 'United Kingdom'`, por eso ambos pueden aparecer como opciones al crear un segmento. ### Cambiar la visualización del gráfico \{#change-the-chart-visualization\} Elige cómo mostrar el gráfico desde el menú desplegable de visualización: - **Columna apilada** — cada columna muestra el total, dividido en segmentos de color por grupo. - **Área apilada** — igual que la columna apilada, pero con áreas rellenas que conectan los puntos de datos. - **Línea** — una línea por grupo, sin relleno. - **Columna apilada al 100%** — cada columna alcanza la altura total del gráfico; los segmentos muestran la proporción relativa (porcentaje) de cada grupo en lugar de los valores reales. Útil para visualizar proporciones a lo largo del tiempo. - **Área apilada al 100%** — igual que la columna apilada al 100%, pero con áreas rellenas en lugar de columnas. ### Ver los datos como tabla \{#view-data-as-a-table\} Debajo de cada gráfico hay una tabla con los mismos datos, con fechas como columnas. La fila y la columna Total muestran agregados que no son visibles en el propio gráfico. ### Exportar datos a CSV \{#export-data-to-csv\} Haz clic en el botón **Export** para descargar los datos del gráfico como archivo CSV. :::tip Para acceso programático o programado, usa la [API de exportación](export-analytics-api) — devuelve los mismos datos que la descarga CSV. ::: ### Mostrar ingresos brutos o netos \{#display-gross-or-net-revenue\} Para los gráficos relacionados con ingresos ([Revenue](revenue), [MRR](mrr), [ARR](arr), [ARPU](arpu), [ARPPU](arppu)), Adapty ofrece un desplegable con tres modos de visualización: - **Gross revenue** — ingresos totales antes de cualquier deducción. - **Proceeds after store commission** — ingresos menos la comisión del store, con impuestos incluidos. - **Proceeds after store commission and taxes** — ingresos menos comisión e impuestos. Para más información sobre el cálculo de comisiones e impuestos, consulta [Comisiones e impuestos](how-adapty-analytics-works#commissions-and-taxes) en *Cómo funciona Adapty Analytics*. --- # File: revenue --- --- title: "Ingresos" description: "Rastrea y analiza los ingresos de tu app con los análisis de suscripciones de Adapty." --- El gráfico Revenue muestra los ingresos totales generados por suscripciones y compras únicas, menos los ingresos reembolsados posteriormente. Es la métrica principal para monitorizar el rendimiento financiero de tu app. Cambia a resolución mensual para evaluar las tendencias generales de los últimos 12 meses. Agrupa el gráfico por producto, segmento de usuarios o fuente de atribución para ver de dónde provienen los ingresos, y observa la combinación de nuevos ingresos frente a renovaciones para entender qué parte del negocio impulsa el crecimiento. ## Cálculo \{#calculation\} :::warning La calculadora que aparece a continuación **no tiene en cuenta** [la comisión del store ni los impuestos](how-adapty-analytics-works#commissions-and-taxes). Compara el resultado con tus cálculos de **ingresos brutos**. ::: Los ingresos son la suma de todas las transacciones pagadas en el período (nuevas suscripciones, renovaciones, conversiones de prueba, compras únicas) menos los reembolsos procesados en ese período: **Ingresos = total de transacciones − reembolsos**. El importe completo de cada transacción se registra el día de la compra, sin distribuirse a lo largo de la duración de la suscripción. El gráfico muestra los ingresos brutos por defecto. Usa los [controles del gráfico](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue) para cambiar entre las vistas de ingresos brutos, tras comisión, o tras comisión e impuestos. <CompoundCalculator client:load heading="Ingresos" formuLatex="\sum P_i \times Q_i - D" variables={[ { nameInTheFormula: "P", variableName: "price", variableDescription: "Precio por unidad", variableValue: 10 }, { nameInTheFormula: "Q", variableName: "qty", variableDescription: "Cantidad", variableValue: 1, isInteger: true }, { nameInTheFormula: "D", variableName: "refunds", variableDescription: "Importe reembolsado", variableValue: 35, global: true } ]} rowFormula="price * qty" resultFormula="_sum - refunds" defaultRows={[ { price: 10, qty: 5 }, { price: 50, qty: 10 }, { price: 100, qty: 1 } ]} /> ## Gestión de reembolsos \{#refund-handling\} Los ingresos restan cada reembolso en la fecha en que se procesó, no en la fecha de la compra original. El gráfico puede mostrar un valor negativo para un grupo o día concreto cuando los reembolsos de ese período superan los ingresos nuevos. Para comparar en detalle cómo funciona esto en todas las métricas, consulta [Cómo gestionan los reembolsos las métricas](refund-events#how-metrics-handle-refunds). ## Moneda \{#currency\} Adapty muestra todos los gráficos monetarios en **dólares estadounidenses**, independientemente de la moneda original de la transacción. Esto incluye Revenue, MRR, ARR, ARPU, ARPPU, LTV, ingresos previstos, dinero reembolsado y las cifras de ingresos dentro de los informes de cohortes y pruebas A/B. No hay ningún ajuste para mostrarlos en otra moneda. Adapty convierte cada transacción a USD usando un tipo de cambio de [currencylayer.com](https://currencylayer.com/) que se actualiza cada 8 horas y queda **fijado en el momento de la transacción**. Los valores históricos en USD no se recalculan cuando el tipo de cambio varía. Los valores en moneda local están disponibles por transacción en: - Los campos `price_local` y `currency` en los webhooks - Las columnas `_local` (como `revenue_local` y `proceeds_local`) y `currency` en las exportaciones a S3, GCS y BigQuery - La página de perfil (vista por transacción) Para informes financieros en moneda local, extrae los valores en moneda local por transacción de una exportación y agrégalos tú mismo. ## Precios de renovación \{#renewal-pricing\} Adapty calcula los ingresos por renovación al precio actual del producto, incluso para los usuarios que tenían un precio anterior cuando se suscribieron por primera vez. Después de cambiar un precio en App Store Connect o Google Play, las cifras de Revenue, MRR y ARR del dashboard para los suscriptores existentes pueden diferir de los ingresos reales recaudados: Adapty aplica el nuevo precio, aunque el store haya mantenido a esos usuarios en el precio anterior. Para verificarlo, compara el campo `price` por transacción en la exportación de S3, GCS o BigQuery con el dashboard para las mismas transacciones. El campo de exportación refleja lo que el store reportó (el precio que el cliente pagó realmente); el dashboard refleja el precio actual del producto. ## Filtros y agrupación disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de analíticas](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Período, Estado de renovación, Producto, País, Store, Paywall, Audiencia, Placement, Duración, Tipo de oferta, Tipo de descuento de oferta, ID de oferta, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#revenue). - [MRR](mrr) - [ARR](arr) - [ARPU](arpu) - [ARPPU](arppu) --- # File: mrr --- --- title: "MRR" description: "Entiende y optimiza los Ingresos Recurrentes Mensuales (MRR) en Adapty." --- El gráfico de Ingresos Recurrentes Mensuales (MRR) muestra los ingresos generados por tus suscripciones de pago activas, normalizados a una cifra mensual. Refleja los ingresos estables que genera tu negocio de suscripciones, independientemente de la duración de cada suscripción. Para ver cómo contribuye cada cohorte de suscriptores a los ingresos recurrentes a lo largo del tiempo, agrupa el gráfico por mes de primera compra y cambia a una resolución mensual. La vista de área apilada muestra la contribución de cada cohorte mes a mes. ## Cálculo \{#calculation\} :::warning La calculadora a continuación **no tiene en cuenta** la [comisión del store ni los impuestos](how-adapty-analytics-works#commissions-and-taxes). Compara el resultado con tus cálculos de **ingresos brutos**. ::: El MRR normaliza los ingresos de cada suscripción a un equivalente mensual: una suscripción anual de $240 aporta $20 cada mes, no $240 de golpe. Esto mantiene el MRR estable independientemente de cómo se distribuyan los periodos de facturación de las suscripciones. El MRR es la suma de (precio × suscriptores activos ÷ período de facturación en meses) para todos tus tipos de suscripción. Las suscripciones semanales usan un período de facturación de ≈0,23 meses. <SimpleCalculator client:load heading="MRR" formuLatex="\sum_{subscriptions}^{}\frac{P_s\times N_s}{D_m}" variables={[ { nameInTheFormula: "P_s", variableName: "subscriptionPrice", variableDescription: "Precio", variableValue: 10 }, { nameInTheFormula: "N_s", variableName: "activeSubs", variableDescription: "Suscriptores", variableValue: 1, isInteger: true }, { nameInTheFormula: "D_m", variableName: "duration", variableDescription: "Período de suscripción", variableValue: 1, options: [ { label: "Semanal", value: 0.23 }, { label: "Mensual", value: 1 }, { label: "2 meses", value: 2 }, { label: "3 meses", value: 3 }, { label: "6 meses", value: 6 }, { label: "Anual", value: 12 } ] } ]} formulaCalculation="(subscriptionPrice * activeSubs) / duration" isSum={true} defaultRows={[ { subscriptionPrice: 240, activeSubs: 2, duration: 12}, { subscriptionPrice: 30, activeSubs: 10, duration: 1}, { subscriptionPrice: 10, activeSubs: 20, duration: 0.23}, ]} /> El MRR no tiene en cuenta los productos que no generan ingresos recurrentes: - compras únicas - consumibles - suscripciones no renovables Tu base de usuarios puede generar ingresos constantes a través de productos de compra única, pero estos ingresos no cuentan para el MRR porque las propias compras no son recurrentes. ## Gestión de reembolsos \{#refund-handling\} Cuando se reembolsa una suscripción, el MRR elimina su contribución de cada fecha del gráfico en la que se había contabilizado anteriormente. Los valores históricos del MRR pueden disminuir tras registrarse un reembolso. Para una comparación completa entre métricas, consulta [Cómo gestionan los reembolsos las métricas](refund-events#how-metrics-handle-refunds). ## Moneda \{#currency\} Adapty muestra todos los gráficos monetarios en **dólares estadounidenses**, independientemente de la moneda original de la transacción. Esto incluye Revenue, MRR, ARR, ARPU, ARPPU, LTV, ingresos previstos, dinero reembolsado y las cifras de ingresos dentro de los informes de cohortes y pruebas A/B. No hay ningún ajuste para mostrarlos en otra moneda. Adapty convierte cada transacción a USD usando un tipo de cambio de [currencylayer.com](https://currencylayer.com/) que se actualiza cada 8 horas y queda **fijado en el momento de la transacción**. Los valores históricos en USD no se recalculan cuando el tipo de cambio varía. Los valores en moneda local están disponibles por transacción en: - Los campos `price_local` y `currency` en los webhooks - Las columnas `_local` (como `revenue_local` y `proceeds_local`) y `currency` en las exportaciones a S3, GCS y BigQuery - La página de perfil (vista por transacción) Para informes financieros en moneda local, extrae los valores en moneda local por transacción de una exportación y agrégalos tú mismo. ## Precios de renovación \{#renewal-pricing\} Adapty calcula los ingresos por renovación al precio actual del producto, incluso para los usuarios que tenían un precio anterior cuando se suscribieron por primera vez. Después de cambiar un precio en App Store Connect o Google Play, las cifras de Revenue, MRR y ARR del dashboard para los suscriptores existentes pueden diferir de los ingresos reales recaudados: Adapty aplica el nuevo precio, aunque el store haya mantenido a esos usuarios en el precio anterior. Para verificarlo, compara el campo `price` por transacción en la exportación de S3, GCS o BigQuery con el dashboard para las mismas transacciones. El campo de exportación refleja lo que el store reportó (el precio que el cliente pagó realmente); el dashboard refleja el precio actual del producto. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Período, Estado de renovación, Producto, País, Store, Paywall, Audiencia, Placement, Duración, Tipo de oferta, Tipo de descuento de oferta, ID de oferta, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación lado a lado de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#revenue). - [Revenue](revenue) - [ARR](arr) - [ARPU](arpu) - [ARPPU](arppu) --- # File: arr --- --- title: "ARR" description: "Realiza un seguimiento de los Ingresos Recurrentes Anuales (ARR) y optimiza tu estrategia de suscripciones." --- El gráfico de ingresos recurrentes anuales muestra los ingresos de todas las suscripciones de renovación automática activas normalizados a un año. El gráfico considera activa cualquier suscripción de pago que no haya expirado. El ARR es una métrica clave para seguir el crecimiento de tu negocio de suscripciones y predecir los ingresos futuros. ## Cálculo \{#calculation\} :::warning La calculadora a continuación **no tiene en cuenta** [la comisión del store ni los impuestos](how-adapty-analytics-works#commissions-and-taxes). Compara el resultado con tus cálculos de **ingresos brutos**. ::: ARR es la versión anualizada de tus ingresos recurrentes por suscripción. Es más útil cuando las suscripciones anuales son tu producto principal; para negocios con suscripciones mayoritariamente mensuales o semanales, el [MRR](mrr) es más informativo. ARR es la suma de (precio × suscriptores activos ÷ período de facturación en años) para todos tus tipos de suscripción. Usa 1/12 para mensuales y 1/52 para semanales. <SimpleCalculator client:load heading="ARR" formuLatex="\sum \frac{P_s \times U_s}{D_y}" variables={[ { nameInTheFormula: "P_s", variableName: "price", variableDescription: "Precio de la suscripción", variableValue: 240 }, { nameInTheFormula: "U_s", variableName: "subs", variableDescription: "Suscripciones de pago activas", variableValue: 2, isInteger: true }, { nameInTheFormula: "D_y", variableName: "periods", variableDescription: "Período de suscripción", variableValue: 1, options: [ { label: "Semanal", value: "1/52" }, { label: "Mensual", value: "1/12" }, { label: "2 meses", value: "2/12" }, { label: "3 meses", value: "3/12" }, { label: "6 meses", value: "6/12" }, { label: "Anual", value: 1 } ] } ]} formulaCalculation="(price * subs ) / periods" isSum={true} defaultRows={[ { price: 240, subs: 2, periods: "1" }, { price: 30, subs: 10, periods: "1/12" }, { price: 10, subs: 20, periods: "1/52" } ]} /> ## Gestión de reembolsos \{#refund-handling\} Cuando se reembolsa una suscripción, el ARR elimina su contribución de cada fecha del gráfico en la que se había contabilizado anteriormente. Los valores pasados de ARR pueden disminuir tras un reembolso. Para ver la comparativa completa entre métricas, consulta [Cómo gestionan los reembolsos las métricas](refund-events#how-metrics-handle-refunds). ## Moneda \{#currency\} Adapty muestra todos los gráficos monetarios en **dólares estadounidenses**, independientemente de la moneda original de la transacción. Esto incluye Revenue, MRR, ARR, ARPU, ARPPU, LTV, ingresos previstos, dinero reembolsado y las cifras de ingresos dentro de los informes de cohortes y pruebas A/B. No hay ningún ajuste para mostrarlos en otra moneda. Adapty convierte cada transacción a USD usando un tipo de cambio de [currencylayer.com](https://currencylayer.com/) que se actualiza cada 8 horas y queda **fijado en el momento de la transacción**. Los valores históricos en USD no se recalculan cuando el tipo de cambio varía. Los valores en moneda local están disponibles por transacción en: - Los campos `price_local` y `currency` en los webhooks - Las columnas `_local` (como `revenue_local` y `proceeds_local`) y `currency` en las exportaciones a S3, GCS y BigQuery - La página de perfil (vista por transacción) Para informes financieros en moneda local, extrae los valores en moneda local por transacción de una exportación y agrégalos tú mismo. ## Precios de renovación \{#renewal-pricing\} Adapty calcula los ingresos por renovación al precio actual del producto, incluso para los usuarios que tenían un precio anterior cuando se suscribieron por primera vez. Después de cambiar un precio en App Store Connect o Google Play, las cifras de Revenue, MRR y ARR del dashboard para los suscriptores existentes pueden diferir de los ingresos reales recaudados: Adapty aplica el nuevo precio, aunque el store haya mantenido a esos usuarios en el precio anterior. Para verificarlo, compara el campo `price` por transacción en la exportación de S3, GCS o BigQuery con el dashboard para las mismas transacciones. El campo de exportación refleja lo que el store reportó (el precio que el cliente pagó realmente); el dashboard refleja el precio actual del producto. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Período, Estado de renovación, Producto, País, Store, Paywall, Audiencia, Placement, Duración, Tipo de oferta, Tipo de descuento de oferta, ID de oferta, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#revenue). - [Revenue](revenue) - [MRR](mrr) - [ARPU](arpu) - [ARPPU](arppu) --- # File: arpu --- --- title: "ARPU" description: "Analiza el Average Revenue Per User (ARPU) para optimizar la generación de ingresos." --- El gráfico ARPU (average revenue per user) muestra los ingresos medios generados por usuario en un período determinado. Esta métrica se calcula dividiendo los ingresos totales generados por una cohorte de clientes entre el número de usuarios de esa cohorte. Usa el ARPU para comparar el rendimiento de ingresos entre segmentos de usuarios: por fuente de atribución, país o producto. ## Cálculo \{#calculation\} :::warning La calculadora de abajo **no tiene en cuenta** [la comisión del store ni los impuestos](how-adapty-analytics-works#commissions-and-taxes). Compara el resultado con tus cálculos de **ingresos brutos**. ::: El ARPU muestra los ingresos medios que genera tu app por usuario, un indicador habitual de la eficiencia de monetización. El ARPU es el resultado de dividir los ingresos del período (menos los reembolsos) entre el número total de usuarios de la app en ese período. <CompoundCalculator client:load heading="ARPU" formuLatex="\frac{\sum P_i \times Q_i - D}{U_p}" variables={[ { nameInTheFormula: "P", variableName: "price", variableDescription: "Precio del producto", variableValue: 10 }, { nameInTheFormula: "Q", variableName: "qty", variableDescription: "Productos comprados", variableValue: 1, isInteger: true }, { nameInTheFormula: "D", variableName: "refunds", variableDescription: "Importe reembolsado", variableValue: 35, global: true }, { nameInTheFormula: "Up", variableName: "users", variableDescription: "Total de usuarios", variableValue: 160, global: true, isInteger: true } ]} rowFormula="price * qty" resultFormula="(_sum - refunds) / users" defaultRows={[ { price: 10, qty: 5 }, { price: 50, qty: 10 }, { price: 100, qty: 1 } ]} /> ## Gestión de reembolsos \{#refund-handling\} Los reembolsos se restan del numerador de ingresos en la fecha en que se procesó el reembolso. Para una comparación completa entre métricas, consulta [Cómo gestionan los reembolsos las métricas](refund-events#how-metrics-handle-refunds). ## Moneda \{#currency\} Adapty muestra todos los gráficos monetarios en **dólares estadounidenses**, independientemente de la moneda original de la transacción. Esto incluye Revenue, MRR, ARR, ARPU, ARPPU, LTV, ingresos previstos, dinero reembolsado y las cifras de ingresos dentro de los informes de cohortes y pruebas A/B. No hay ningún ajuste para mostrarlos en otra moneda. Adapty convierte cada transacción a USD usando un tipo de cambio de [currencylayer.com](https://currencylayer.com/) que se actualiza cada 8 horas y queda **fijado en el momento de la transacción**. Los valores históricos en USD no se recalculan cuando el tipo de cambio varía. Los valores en moneda local están disponibles por transacción en: - Los campos `price_local` y `currency` en los webhooks - Las columnas `_local` (como `revenue_local` y `proceeds_local`) y `currency` en las exportaciones a S3, GCS y BigQuery - La página de perfil (vista por transacción) Para informes financieros en moneda local, extrae los valores en moneda local por transacción de una exportación y agrégalos tú mismo. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, País, Pruebas A/B, Segmento y Store. - ✅ Agrupar por: País, Store, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#revenue). - [Revenue](revenue) - [MRR](mrr) - [ARPPU](arppu) - [ARR](arr) --- # File: arppu --- --- title: "ARPPU" description: "Entiende el ARPPU (Ingreso Promedio por Usuario de Pago) y cómo impacta la monetización de tu app." --- El gráfico de ingresos medios por usuario de pago (ARPPU) muestra los ingresos medios por usuario de pago. Refleja los ingresos reales generados por los clientes de pago, divididos entre el número de clientes, descontando los reembolsos. Agrupa el ARPPU por atribución para ver qué canales de adquisición atraen a usuarios de pago de mayor valor. ## Cálculo \{#calculation\} :::warning La calculadora de abajo **no tiene en cuenta** [la comisión del store ni los impuestos](how-adapty-analytics-works#commissions-and-taxes). Compara el resultado con tus cálculos de **ingresos brutos**. ::: ARPPU muestra los ingresos medios por usuario de pago — normalmente mucho más altos que el [ARPU](arpu), ya que los usuarios que no pagan quedan excluidos del denominador. El ARPPU es el ingreso del período (menos los reembolsos) dividido entre el número de usuarios de pago en ese período. <CompoundCalculator client:load heading="ARPPU" formuLatex="\frac{\sum P_i \times Q_i - D}{U_p}" variables={[ { nameInTheFormula: "P", variableName: "price", variableDescription: "Precio del producto", variableValue: 10 }, { nameInTheFormula: "Q", variableName: "qty", variableDescription: "Productos comprados", variableValue: 1, isInteger: true }, { nameInTheFormula: "D", variableName: "refunds", variableDescription: "Importe reembolsado", variableValue: 35, global: true }, { nameInTheFormula: "Up", variableName: "users", variableDescription: "Usuarios de pago", variableValue: 16, global: true, isInteger: true } ]} rowFormula="price * qty" resultFormula="(_sum - refunds) / users" defaultRows={[ { price: 10, qty: 5 }, { price: 50, qty: 10 }, { price: 100, qty: 1 } ]} /> ## Gestión de reembolsos \{#refund-handling\} Los reembolsos se restan del numerador de ingresos en la fecha en que se procesó el reembolso. Un usuario cuya compra fue reembolsada posteriormente sigue contando en el denominador de usuarios de pago, por lo que un volumen alto de reembolsos hace que el ARPPU baje más rápido de lo esperado. Para una comparación completa entre métricas, consulta [Cómo gestionan los reembolsos las métricas](refund-events#how-metrics-handle-refunds). ## Moneda \{#currency\} Adapty muestra todos los gráficos monetarios en **dólares estadounidenses**, independientemente de la moneda original de la transacción. Esto incluye Revenue, MRR, ARR, ARPU, ARPPU, LTV, ingresos previstos, dinero reembolsado y las cifras de ingresos dentro de los informes de cohortes y pruebas A/B. No hay ningún ajuste para mostrarlos en otra moneda. Adapty convierte cada transacción a USD usando un tipo de cambio de [currencylayer.com](https://currencylayer.com/) que se actualiza cada 8 horas y queda **fijado en el momento de la transacción**. Los valores históricos en USD no se recalculan cuando el tipo de cambio varía. Los valores en moneda local están disponibles por transacción en: - Los campos `price_local` y `currency` en los webhooks - Las columnas `_local` (como `revenue_local` y `proceeds_local`) y `currency` en las exportaciones a S3, GCS y BigQuery - La página de perfil (vista por transacción) Para informes financieros en moneda local, extrae los valores en moneda local por transacción de una exportación y agrégalos tú mismo. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Período, Estado de renovación, Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#revenue). - [Revenue](revenue) - [MRR](mrr) - [ARPU](arpu) - [ARR](arr) --- # File: installs --- --- title: "Instalaciones" description: "Rastrea las instalaciones de la app y comprende su impacto en las suscripciones con Adapty." --- El gráfico de Instalaciones muestra cuántos usuarios instalaron tu app durante el período seleccionado. Lo que cuenta como una instalación —y cómo se agrupa cada una— depende de la configuración de conteo de instalaciones. Este artículo explica cómo seleccionar el modo de recuento adecuado y cómo [resolver las posibles discrepancias](#troubleshooting) entre las distintas fuentes de análisis. ## Qué cuenta como una instalación \{#what-counts-as-an-install\} El SDK de Adapty registra una "instalación" y la envía a Adapty cuando el usuario lanza la aplicación por primera vez. Esto tiene dos consecuencias: - Una instalación aparece en Adapty cuando el usuario abre la app por primera vez, lo que puede ocurrir horas o días después de descargarla. - Si un usuario descarga la app pero nunca la abre, Adapty no lo contabiliza. Tu **zona horaria de informes** en App Settings determina en qué día se registra cada instalación. Una instalación a las 23:30 UTC el 1 de junio se registra el 2 de junio si tu zona horaria de informes es +02:00, mientras que App Store Connect o Google Play pueden mostrarla el 1 de junio. ### Modos de conteo \{#counting-modes\} El ajuste **Installs definition for analytics** determina qué cuenta como una nueva instalación. Para cambiarlo, abre [App Settings → General → Installs definition for analytics](general#4-installs-definition-for-analytics). | Modo | Qué se contabiliza | Ejemplo | Métrica de terceros | Posibles discrepancias | | --- | --- | --- | --- | --- | | **Nuevos device_ids** (recomendado) | **Cada instalación de la app** — incluyendo reinstalaciones. La autenticación, la creación de perfiles y las actualizaciones de versión no se suman al recuento. | Un usuario en 5 dispositivos = 5 instalaciones. <br /> <br /> Reinstalar en el mismo dispositivo = 2 instalaciones. | App Store: <br /> **Total Active Devices** <br /> <br /> Google Play: **Devices** | **Mayor que el número de descargas de la app** si las reinstalaciones son frecuentes. <br /> <br /> **Menor que el número de descargas de la app** si muchos usuarios descargan la app sin abrirla. | | **Nuevos customer_user_ids** | Solo **la primera instalación** por [usuario identificado](identifying-users). Los dispositivos adicionales y los usuarios anónimos no se cuentan. | Un usuario en 5 dispositivos = 1 instalación. <br /> <br /> Reinstalar e iniciar sesión de nuevo = ninguna instalación nueva. <br /> <br /> Usar la app sin cuenta = ninguna instalación nueva. | Estadísticas de registro de tu sistema de autenticación | **Se mantiene vacío** si no identificas usuarios en absoluto. | | **Nuevos perfiles en Adapty** (legacy) | Contabiliza cada instalación y reinstalación, **así como los perfiles anónimos creados al cerrar sesión**. | Un usuario, un dispositivo, 3 cierres de sesión = 4 instalaciones. | Ninguna | **Mayor que todas las métricas externas**. Cuenta cada perfil anónimo creado al cerrar sesión como una instalación. | Usa **New device_ids** a menos que tengas una razón concreta para cambiar. ## Solución de problemas \{#troubleshooting\} ### El recuento de Adapty es mayor que el de App Store Connect o Google Play \{#adaptys-count-is-higher-than-app-store-connect-or-google-play\} Dos causas probables: - **Reinstalaciones.** Si tu [modo de recuento](#counting-modes) está configurado como **New device_ids**, Adapty cuenta tanto los primeros lanzamientos como las reinstalaciones posteriores. "Total Downloads" de App Store Connect solo cuenta la descarga inicial. - **La fecha del primer lanzamiento ≠ fecha de descarga.** Los stores atribuyen por fecha de descarga. Los usuarios que abren la app tarde aparecen en días distintos. Para comparar de forma más limpia, abre **App Store Connect → Total Active Devices** o **Google Play → Devices**. Esas métricas son a nivel de dispositivo y se acercan más al modo **New device_ids** de Adapty. ### El recuento de Adapty es cero \{#adaptys-count-is-zero\} Si tu modo de conteo es **New customer_user_ids**, pero no <InlineTooltip tooltip="autentificas usuarios">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Unity](unity-identifying-users), [Kotlin Multiplatform](kmp-quickstart-identify), [Capacitor](capacitor-quickstart-identify)</InlineTooltip>, Adapty no registrará ninguna instalación. En ese modo, las instalaciones anónimas se excluyen. Cambia a **New device_ids** o implementa la identificación de usuarios. ### El recuento de Adapty difiere del de AppsFlyer o Adjust \{#adaptys-count-differs-from-appsflyer-or-adjust\} Los MMPs atribuyen las instalaciones mediante su propio inicio de SDK o el evento de primer contacto. Estos se activan en un momento diferente al del primer inicio del SDK de Adapty, por lo que cierta discrepancia es normal. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, País, Pruebas A/B, Segmento y Store. - ✅ Agrupar por: País, Store, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#subscribers-and-conversion). - [Nuevas suscripciones](reactivated-subscriptions) - [Suscripciones activas](active-subscriptions) - [Nuevas pruebas](new-trials) --- # File: active-subscriptions --- --- title: "Suscripciones activas" description: "Supervisa y gestiona las suscripciones activas con los potentes análisis de Adapty." --- El gráfico **Active subscriptions** muestra el número de suscripciones de pago únicas que aún no han expirado al final de cada período seleccionado. Incluye suscripciones in-app regulares (no vencidas) que comenzaron y están actualmente activas, y excluye tanto las pruebas gratuitas como las suscripciones con renovación cancelada. Es un indicador del tamaño y el crecimiento de tu base de suscriptores. ## Cálculo \{#calculation\} La métrica de suscripciones activas contabiliza las suscripciones de pago no vencidas al final de cada período. En el caso de suscripciones sin período de gracia, el vencimiento se produce cuando la próxima fecha de renovación llega sin que se haya completado una renovación con éxito. Por ejemplo: 500 suscripciones activas al final del mes pasado, más 50 nuevas este mes, menos 25 que vencieron este mes = 525 suscripciones activas al final de este mes. ## Gestión de reembolsos \{#refund-handling\} Cuando se reembolsa una suscripción, Adapty la elimina del recuento de activas, tanto en el momento actual como de forma retroactiva para fechas pasadas. Para ver la comparación completa entre métricas, consulta [Cómo gestionan los reembolsos las métricas](refund-events#how-metrics-handle-refunds). ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Período, Estado de renovación, Producto, País, Store, Paywall, Audiencia, Placement, Duración, Tipo de oferta, Tipo de descuento de oferta, ID de oferta, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#subscribers-and-conversion). - [Suscripciones canceladas (expiradas)](churned-expired-subscriptions) - [Suscripciones canceladas](cancelled-subscriptions) - [No suscripciones](non-subscriptions) --- # File: reactivated-subscriptions --- --- title: "Nuevas suscripciones" description: "Rastrea las nuevas suscripciones en Adapty para monitorizar las primeras conversiones y las conversiones de prueba gratuita a pago." --- El gráfico **New subscriptions** muestra el número de nuevas suscripciones (activadas por primera vez) en tu app. Esta métrica refleja las suscripciones que comienzan en un período de tiempo concreto, tanto las que parten de cero como los períodos de prueba gratuitos que se convierten en suscripciones de pago. No incluye renovaciones de suscripciones ni suscripciones reactivadas. ## Cálculo \{#calculation\} La métrica de nuevas suscripciones cuenta las primeras activaciones de suscripción durante el período, tanto las suscripciones que comienzan desde cero como los períodos de prueba gratuitos que se convierten en suscripciones de pago. ## Gestión de reembolsos \{#refund-handling\} Las nuevas suscripciones **no** descuentan los reembolsos — el recuento incluye suscripciones que posteriormente fueron reembolsadas. Para evaluar el impacto neto, compara con [Eventos de reembolso](refund-events). Para la comparación completa entre métricas, consulta [Cómo gestionan los reembolsos las métricas](refund-events#how-metrics-handle-refunds). ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Estado de renovación, Producto, País, Store, Paywall, Audiencia, Placement, Duración, Tipo de oferta, Tipo de descuento de oferta, ID de oferta, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#subscribers-and-conversion). - [Suscripciones activas](active-subscriptions) - [Suscripciones canceladas (expiradas)](churned-expired-subscriptions) - [Suscripciones canceladas](cancelled-subscriptions) - [Compras no suscripción](non-subscriptions) --- # File: non-subscriptions --- --- title: "Compras no suscritas" description: "Aprende a gestionar productos sin suscripción en Adapty y a hacer un seguimiento eficiente de las compras de los usuarios." --- El gráfico de compras no suscritas (Non-subscriptions) cuenta las compras in-app que no son suscripciones de renovación automática: consumibles, no consumibles y suscripciones no renovables. Excluye las renovaciones. :::note "No suscripciones" es un concepto más amplio que "compras únicas": los consumibles y las suscripciones no renovables pueden adquirirse más de una vez. ::: ## Cálculo \{#calculation\} Cada compra in-app única se clasifica en uno de estos tres tipos: - **Consumibles**: artículos que los usuarios pueden comprar varias veces, como comida para peces en una app de pesca o moneda adicional en el juego. - **No consumibles**: artículos que los usuarios compran una sola vez y usan para siempre, como una pista de carreras en un juego o una versión sin anuncios. - **Suscripciones no renovables**: suscripciones que caducan tras un período determinado y no se renuevan automáticamente, como un acceso anual a un catálogo de contenido. El contenido puede ser estático, pero la suscripción no se renueva al expirar. :::note Este gráfico solo cuenta los eventos de compra y no resta las compras reembolsadas. Si tienes productos que no son suscripciones y se reembolsan con frecuencia, el recuento mostrado será mayor que el número real de compras que generaron ingresos. ::: ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Paywall, Pruebas A/B, Placement, Segmento, Store y Producto. - ✅ Agrupar por: Producto, País, Store, Paywall, Audiencia, Placement, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [Tabla de comparación de métricas](metric-comparison-table#revenue). - [Suscripciones activas](active-subscriptions) - [Nuevas suscripciones](reactivated-subscriptions) - [Suscripciones canceladas (expiradas)](churned-expired-subscriptions) - [Suscripciones canceladas](cancelled-subscriptions) --- # File: cancelled-subscriptions --- --- title: "Cancelación de renovación de suscripciones" description: "Gestiona de forma eficiente las suscripciones canceladas con las herramientas de administración de Adapty." --- El gráfico **Subscriptions renewal canceled** muestra el número de suscripciones cuya renovación automática ha sido desactivada (cancelada por el usuario). Cuando se desactiva la renovación automática de una suscripción, esta no se renovará automáticamente para el siguiente período. Sin embargo, el usuario conserva el acceso a las funciones premium de la aplicación hasta que finalice el período actual. ## Cálculo \{#calculation\} La métrica de cancelaciones de renovación de suscripción cuenta las suscripciones cuya renovación automática se desactivó durante el período. El usuario mantiene el acceso premium hasta que finalice el período de facturación actual, pero la suscripción no se renovará automáticamente después de eso. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#churn). - [Suscripciones activas](active-subscriptions) - [Suscripciones canceladas (expiradas)](churned-expired-subscriptions) - [Nuevas suscripciones](reactivated-subscriptions) - [Compras no recurrentes](non-subscriptions) --- # File: churned-expired-subscriptions --- --- title: "Suscripciones canceladas (expiradas)" description: "Gestiona las suscripciones canceladas y expiradas para mejorar la retención de usuarios." --- El gráfico de suscripciones canceladas (expiradas) muestra el número de suscripciones que han expirado, es decir, aquellas en las que el usuario ya no tiene acceso a las funciones premium de la aplicación. Esto ocurre normalmente cuando el usuario decide dejar de pagar al final del período de suscripción o cuando encuentra un problema de facturación. Agrupa por motivo de expiración para separar la cancelación voluntaria de la ocasionada por problemas de facturación. ## Cálculo \{#calculation\} La métrica de suscripciones canceladas (expiradas) cuenta las suscripciones que expiraron durante el período, es decir, los usuarios que perdieron acceso a las funciones premium. Esto incluye tanto a los usuarios que decidieron no renovar como a los que perdieron la suscripción por un problema de pago. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de analíticas](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Paywall, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Motivo de expiración, Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [Tabla de comparación de métricas](metric-comparison-table#churn). - [Suscripciones activas](active-subscriptions) - [Nuevas suscripciones](reactivated-subscriptions) - [Suscripciones canceladas](cancelled-subscriptions) - [No suscripciones](non-subscriptions) --- # File: active-trials --- --- title: "Pruebas activas" description: "Realiza un seguimiento y gestiona las pruebas de suscripción activas con la analítica de Adapty." --- El gráfico de pruebas activas en Adapty muestra el número de pruebas gratuitas no expiradas que están activas al final de un período determinado. Activas significa suscripciones que aún no han expirado; por lo tanto, los usuarios todavía tienen acceso a las funciones de pago de la aplicación. ## Cálculo \{#calculation\} La métrica de pruebas activas cuenta las pruebas gratuitas no vencidas al final de cada período. Cancelar la renovación automática no elimina una prueba del recuento — solo lo hace el vencimiento. Por ejemplo: 100 pruebas activas ayer, más 10 nuevas hoy, menos 5 que vencieron hoy = 105 pruebas activas hoy. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Período, Estado de renovación, Producto, País, Store, Paywall, Audiencia, Placement, Duración, Tipo de oferta, Tipo de descuento de oferta, ID de oferta, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#subscribers-and-conversion). - [Nuevas pruebas](new-trials) - [Cancelación de renovación de prueba](trials-renewal-cancelled) - [Pruebas expiradas](expired-churned-trials) --- # File: new-trials --- --- title: "Nuevas pruebas gratuitas" description: "Gestiona las nuevas pruebas de suscripción y optimiza las tasas de conversión de prueba a pago." --- El gráfico de nuevas pruebas gratuitas muestra el número de pruebas activadas durante el período de tiempo seleccionado. Úsalo para hacer seguimiento del volumen de pruebas procedentes de campañas publicitarias y otros esfuerzos de captación. ## Cálculo \{#calculation\} La métrica de nuevos trials cuenta los trials iniciados durante el período, independientemente de si siguen activos al final del período. Por ejemplo, si 50 usuarios inician un trial en mayo, el punto de datos de mayo muestra 50, aunque algunos ya hayan expirado o se hayan convertido a pago cuando consultes el gráfico. ## Filtros y agrupación disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Audiencia, Placement, Duración, Tipo de oferta, Tipo de descuento de oferta, ID de oferta, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#subscribers-and-conversion). - [Pruebas activas](active-trials) - [Cancelación de renovación de prueba](trials-renewal-cancelled) - [Pruebas expiradas](expired-churned-trials) --- # File: trials-renewal-cancelled --- --- title: "Renovaciones de prueba canceladas" description: "Comprende las renovaciones de prueba, cancelaciones y flows de suscripción con los insights de Adapty." --- El gráfico Trials renewal cancelled muestra el número de pruebas con renovación cancelada (cancelada por el usuario). Cuando se desactiva la renovación de una prueba, significa que esa prueba no se convertirá automáticamente en una suscripción de pago, aunque el usuario seguirá disfrutando de las funciones premium de la app hasta el final del período actual. ## Cálculo \{#calculation\} La métrica de renovaciones canceladas de prueba cuenta los trials cuya renovación automática fue desactivada por el usuario durante el período. El usuario conserva el acceso de prueba hasta que este finalice, pero el trial no se convertirá automáticamente en una suscripción de pago. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Paywall, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#churn). - [Nuevas pruebas](new-trials) - [Pruebas activas](active-trials) - [Pruebas expiradas](expired-churned-trials) --- # File: expired-churned-trials --- --- title: "Pruebas expiradas (canceladas)" description: "Gestiona eficazmente las pruebas expiradas y canceladas con los análisis de Adapty." --- El gráfico de pruebas expiradas (canceladas) muestra el número de pruebas que han caducado, dejando a los usuarios sin acceso a las funciones premium de la app. En la mayoría de los casos, esto ocurre cuando los usuarios deciden no pagar por la app o tienen problemas de facturación. ## Cálculo \{#calculation\} La métrica de pruebas expiradas contiene las pruebas que finalizaron durante el período, es decir, el usuario perdió acceso a las funciones premium. Esto incluye tanto a los usuarios que decidieron no convertir como a aquellos cuya conversión falló por un problema de facturación. Agrupa por **Expiration reason** para separar el abandono voluntario del abandono causado por problemas de facturación. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Paywall, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Motivo de vencimiento, Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#churn). - [Nuevos trials](new-trials) - [Trials activos](active-trials) - [Trials con renovación cancelada](trials-renewal-cancelled) --- # File: refund-events --- --- title: "Eventos de reembolso" description: "Gestiona los eventos de reembolso en Adapty para reducir la pérdida de clientes y optimizar los ingresos." --- El gráfico de eventos de reembolso muestra cuántas compras y suscripciones han sido reembolsadas. Adapty asocia cada evento de reembolso a la fecha en que se emitió el reembolso, no a la fecha de inicio de la suscripción. ## Cálculo \{#calculation\} Adapty contabiliza cada compra o suscripción reembolsada dentro del período seleccionado. Cada reembolso se atribuye a la fecha en que ocurrió, no a cuando comenzó la suscripción. Los reembolsos de períodos de prueba se excluyen porque los períodos de prueba no generan ingresos. ## Cómo gestionan los reembolsos las métricas \{#how-metrics-handle-refunds\} Las distintas métricas tratan los reembolsos de forma diferente. Un mismo evento de reembolso puede reducir un gráfico de forma inmediata, reducir otro de forma retroactiva (modificando valores de períodos pasados) o no afectar a un tercero en absoluto. La tabla siguiente muestra las reglas por métrica. | Métrica | ¿Se aplican reembolsos? | Fecha de atribución | ¿Puede ser negativo? | Notas | | --- | --- | --- | --- | --- | | [Ingresos](revenue) | Sí | Fecha del reembolso — no la fecha de compra original | Sí — en días en que los reembolsos superan los ingresos nuevos | Ingresos = total de transacciones − reembolsos. | | [MRR](mrr) | Sí, de forma retroactiva | La suscripción se elimina de todos los períodos en que estuvo activa | No | Los valores de períodos pasados pueden disminuir tras un reembolso. | | [ARR](arr) | Sí, de forma retroactiva | Igual que MRR | No | Los valores de períodos pasados pueden disminuir tras un reembolso. | | [ARPU](arpu) | Sí | Fecha del reembolso | Sí (en períodos con muchos reembolsos) | Los reembolsos se restan del numerador de ingresos. | | [ARPPU](arppu) | Sí, solo el numerador | Fecha del reembolso | Sí (en períodos con muchos reembolsos) | Los reembolsos se restan del numerador de ingresos. Un usuario reembolsado sigue contando en el denominador de usuarios de pago, por lo que muchos reembolsos reducen el ARPPU más rápido de lo esperado. | | [Suscripciones activas](active-subscriptions) | Sí, de forma retroactiva | La suscripción se elimina del recuento | No | | | [Suscripciones nuevas](reactivated-subscriptions) | **No** | — | No | El recuento incluye suscripciones reembolsadas posteriormente. Compara con [Eventos de reembolso](refund-events) para ver el impacto neto. | | [Dinero reembolsado](refund-money) / [Eventos de reembolso](refund-events) | Los reembolsos **son** el dato | Fecha del reembolso | No (siempre ≥ 0) | | | [Retención](analytics-retention) | **No** | — | No | Los usuarios reembolsados permanecen en la curva de retención. Esto puede hacer que la Retención parezca más alta que las [Suscripciones activas](active-subscriptions) o los [Ingresos](revenue) para la misma cohorte. | | [Ingresos por cohorte](analytics-cohorts) | Sí, de forma acumulativa | Fecha del reembolso | No (las sustracciones acumulativas no llevan los ingresos de la cohorte por debajo de cero) | Los reembolsos se restan de los ingresos de la cohorte a medida que ocurren. Para las demás métricas de cohortes, consulta [Cohortes > Gestión de reembolsos](analytics-cohorts#refund-handling). | | Métricas de [paywall](paywall-metrics) / [prueba A/B](results-and-metrics) (recuentos) | **No** | — | No | Los recuentos de Suscriptores, Suscriptores de pago y ARPPU en estas páginas no deducen los reembolsos. | | Exportaciones GCS / S3 | El reembolso como fila de evento propia | `event_datetime` = marca de tiempo del reembolso | Las columnas netas pueden ser negativas al agregar | La fila de reembolso incluye `is_refund = true` (S3/GCS) o el tipo de evento `subscription_refunded` (webhooks). | ### Valores negativos \{#negative-values\} En las vistas agregadas (el gráfico de Revenue, los análisis personalizados en exportaciones), una métrica puede mostrar un valor negativo para un período o agrupación concreta cuando los reembolsos de ese bloque superan los ingresos nuevos del mismo bloque. No es un error: es la aritmética funcionando como se diseñó. Por ejemplo: un país no tuvo nuevas compras un martes, pero ese día se procesó un reembolso de 100 $ por una compra anterior. Los ingresos de ese país para el martes aparecerán como −100 $. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, Motivo de reembolso, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Motivo de reembolso, Producto, País, Store, Paywall, Audiencia, Placement, Duración, Tipo de oferta, Tipo de descuento de oferta, ID de oferta, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#revenue). - [Dinero reembolsado](refund-money) - [Problema de facturación](billing-issue) - [Período de gracia](grace-period) --- # File: refund-money --- --- title: "Reembolso de dinero" description: "Aprende a procesar reembolsos de suscripciones en Adapty sin perder ingresos." --- El gráfico Refund money muestra el importe reembolsado durante el período seleccionado. Adapty asocia cada evento de reembolso a la fecha en que se emitió, por lo que los ingresos disminuyen en ese mismo período. ## Cálculo \{#calculation\} Adapty cuenta únicamente las transacciones que generan ingresos: nuevas suscripciones de pago, renovaciones y compras únicas. Los períodos de prueba gratuitos, que no generan ingresos y no pueden ser reembolsados, quedan excluidos. Cada importe de reembolso se asocia a la fecha en que fue procesado, por lo que la reducción de ingresos aparece en ese mismo período. :::info El importe del reembolso se calcula antes de deducir la comisión del store. ::: ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Attribution, Audience, Refund Reason, Country, Offer Type, Offer ID, Offer Discount Type, Paywall, A/B tests, Placement, Period, Segment, Store, Product y Duration. - ✅ Agrupar por: Refund Reason, Product, Country, Store, Paywall, Audience, Placement, Duration, Offer Type, Offer Discount Type, Offer ID, Segment y Attribution. ## Gestión de solicitudes de reembolso \{#refund-request-management\} El Refund saver ayuda a los usuarios de Adapty a gestionar las solicitudes de reembolso de la App Store de Apple de forma más eficiente mediante la automatización. Ahorra tiempo y reduce la pérdida de ingresos al simplificar el proceso. Con notificaciones en tiempo real e información útil, esta herramienta facilita atender las solicitudes de reembolso cumpliendo con las directrices de Apple. Más información sobre [Refund saver](refund-saver). ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [Tabla de comparación de métricas](metric-comparison-table#revenue). - [Eventos de reembolso](refund-events) - [Problema de facturación](billing-issue) - [Período de gracia](grace-period) --- # File: grace-period --- --- title: "Período de gracia" description: "Entiende cómo funcionan los períodos de gracia de suscripción y mejora la retención de usuarios." --- El gráfico Grace period muestra el número de suscripciones que han entrado en el estado de período de gracia debido a un [problema de facturación](billing-issue). Durante este período, la suscripción permanece activa mientras el store intenta cobrar al suscriptor. Si el pago no se recibe correctamente antes de que finalice el período de gracia, la suscripción entra en el estado de problema de facturación. ## Cálculo \{#calculation\} La métrica del período de gracia cuenta las suscripciones que entraron en el período de gracia durante el intervalo de tiempo seleccionado. El período de gracia comienza cuando falla el pago de renovación de una suscripción y dura hasta 6 días para las suscripciones semanales o 16 días para otros períodos de facturación. Si el pago se procesa durante este período, la suscripción continúa con normalidad; si no, la suscripción entra en el estado de [problema de facturación](billing-issue). ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación lado a lado de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#billing-issues-and-revenue-recovery). - [Dinero reembolsado](refund-money) - [Eventos de reembolso](refund-events) - [Problema de facturación](billing-issue) --- # File: grace-period-converted --- --- title: "Período de gracia convertido" description: "Realiza un seguimiento del número de suscripciones que entraron en el período de gracia y se renovaron antes de que terminara." --- El gráfico **Grace period converted** muestra el número de suscripciones que entraron en el estado de [período de gracia](grace-period) y se renovaron correctamente antes de que el período finalizara. ### Cálculo \{#calculation\} El gráfico Grace period converted muestra el número diario de renovaciones de suscripciones de usuarios en período de gracia. El período de gracia comienza cuando la suscripción entra en estado de problema de facturación debido a un fallo en el pago y termina tras un tiempo determinado (6 días para suscripciones semanales, 16 días para el resto de suscripciones) o cuando el pago se recibe correctamente. El gráfico permite evaluar la efectividad del período de gracia y puede ayudar a identificar posibles problemas en el procesamiento de pagos o en la gestión de suscripciones. ### Filtros disponibles \{#available-filters\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, Motivo de reembolso, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ### Uso del gráfico de conversión en período de gracia \{#grace-period-converted-chart-usage\} Usa este gráfico para hacer un seguimiento de la efectividad del período de gracia a la hora de recuperar suscripciones con problemas de pago. Monitorizando las tendencias de conversión a lo largo del tiempo, puedes identificar patrones en la resolución de pagos y evaluar el impacto de los cambios en tus flujos de actualización de pago o en las estrategias de comunicación durante los períodos de gracia. ### Métricas similares \{#similar-metrics\} - [Problema de facturación](billing-issue) - [Problema de facturación convertido](billing-issue-converted) - [Ingresos de problema de facturación convertido](billing-issue-converted-revenue) - [Período de gracia](grace-period) - [Ingresos de período de gracia convertido](grace-period-converted-revenue) - [Dinero reembolsado](refund-money) - [Eventos de reembolso](refund-events) --- # File: grace-period-converted-revenue --- --- title: "Ingresos por conversiones en período de gracia" description: "Realiza un seguimiento de los ingresos totales por conversiones en período de gracia." --- El gráfico **Grace period converted revenue** muestra los ingresos generados por las [conversiones en período de gracia](grace-period-converted): suscripciones que entraron en estado de [período de gracia](grace-period) y se renovaron correctamente antes de que dicho período expirara. ### Cálculo \{#calculation\} El gráfico de ingresos convertidos en período de gracia muestra los ingresos diarios generados por las renovaciones de suscripciones de usuarios que se encuentran en un período de gracia. El período de gracia comienza cuando la suscripción entra en estado de problema de facturación debido a un fallo en el pago y finaliza tras un tiempo determinado (6 días para las suscripciones semanales, 16 días para el resto) o cuando el pago se recibe correctamente. El gráfico ofrece información sobre la eficacia del período de gracia y puede ayudar a identificar posibles problemas en el procesamiento de pagos o en la gestión de suscripciones. ### Filtros disponibles \{#available-filters\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, Motivo de reembolso, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ### Uso del gráfico de ingresos convertidos en período de gracia \{#grace-period-converted-revenue-chart-usage\} Usa este gráfico para medir el impacto financiero de la función de período de gracia haciendo seguimiento de los ingresos recuperados de suscripciones con problemas de pago. Esto te ayuda a cuantificar la efectividad de tu estrategia de período de gracia y a evaluar el retorno de la inversión de implementar funciones o comunicaciones relacionadas con el período de gracia. ### Métricas similares \{#similar-metrics\} - [Problema de facturación](billing-issue) - [Problema de facturación convertido](billing-issue-converted) - [Ingresos por problema de facturación convertido](billing-issue-converted-revenue) - [Período de gracia](grace-period) - [Período de gracia convertido](grace-period-converted) - [Dinero reembolsado](refund-money) - [Eventos de reembolso](refund-events) --- # File: billing-issue --- --- title: "Problema de facturación" description: "Resuelve problemas de facturación de suscripciones con las herramientas de soporte de Adapty." --- El gráfico Billing issue muestra el número de suscripciones que han entrado en el estado de problema de facturación. Este estado se activa generalmente cuando el store, como Apple o Google, no puede cobrar al suscriptor por algún motivo, como una tarjeta de crédito vencida o fondos insuficientes. ## Cálculo \{#calculation\} La métrica de problemas de facturación contiene las suscripciones que entraron en el estado de problema de facturación durante el período. Una suscripción entra en este estado cuando el store (Apple o Google) no puede procesar el pago de renovación, generalmente debido a una tarjeta de crédito vencida o fondos insuficientes. Durante el estado de problema de facturación, la suscripción no está activa. Si la función de [período de gracia](grace-period) está habilitada, la suscripción entra en el estado de problema de facturación solo después de que el período de gracia expire sin que se haya realizado el pago. ## Filtros y agrupaciones disponibles \{#available-filters-and-grouping\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ## Métricas similares \{#similar-metrics\} Para una comparación en paralelo de estas métricas, consulta la [tabla de comparación de métricas](metric-comparison-table#billing-issues-and-revenue-recovery). - [Billing issue converted](billing-issue-converted) - [Billing issue converted revenue](billing-issue-converted-revenue) - [Refund money](refund-money) - [Refund events](refund-events) - [Grace period](grace-period) - [Grace period converted](grace-period-converted) - [Grace period converted revenue](grace-period-converted-revenue) --- # File: billing-issue-converted --- --- title: "Problema de facturación resuelto" description: "Realiza un seguimiento del número de problemas de facturación que se resuelven antes del fin del ciclo de facturación." --- El gráfico Billing issue converted muestra el número diario de suscripciones que entraron en estado [Billing Issue](billing-issue) y se renovaron antes del fin del ciclo de facturación. ### Cálculo \{#calculation\} El gráfico Billing issue converted muestra el número de suscripciones que entraron en el estado [Billing Issue](billing-issue) en el ciclo de facturación actual y se renovaron ese día. Una suscripción entra en el estado Billing Issue cuando el store (p. ej., Apple, Google) no puede procesar el pago del suscriptor por algún motivo, como una tarjeta de crédito caducada o fondos insuficientes. Durante el estado Billing Issue, la suscripción no se considera activa; si la función de período de gracia está habilitada en la configuración del store, la suscripción solo pasará al estado Billing Issue una vez que el período de gracia haya expirado. ### Filtros disponibles \{#available-filters\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, Motivo de reembolso, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ### Uso del gráfico "Billing issue converted" \{#billing-issue-converted-chart-usage\} Usa este gráfico para hacer un seguimiento de la efectividad con la que se resuelven los problemas de facturación dentro del ciclo de facturación tras expirar el período de gracia. Al monitorear las tendencias de resolución a lo largo del tiempo, puedes identificar patrones en la recuperación de pagos y evaluar el impacto de los cambios en tu lógica de reintentos de pago o en las estrategias de comunicación durante los problemas de facturación. ### Métricas similares \{#similar-metrics\} - [Problema de facturación](billing-issue) - [Ingresos convertidos por problema de facturación](billing-issue-converted-revenue) - [Dinero reembolsado](refund-money) - [Eventos de reembolso](refund-events) - [Período de gracia](grace-period) - [Período de gracia convertido](grace-period-converted) - [Ingresos convertidos del período de gracia](grace-period-converted-revenue) --- # File: billing-issue-converted-revenue --- --- title: "Ingresos convertidos por problema de facturación" description: "Resuelve problemas de facturación en suscripciones con las herramientas de soporte de Adapty." --- El gráfico **Billing issue converted revenue** muestra los ingresos procedentes de [conversiones de problemas de facturación](billing-issue-converted): suscripciones que entraron en el estado [Billing Issue](billing-issue) y se renovaron antes de que finalizara el ciclo de facturación. ### Cálculo \{#calculation\} El gráfico **Billing issue converted revenue** muestra los ingresos diarios de las suscripciones que entraron en el estado [Billing Issue](billing-issue) en el ciclo de facturación actual y se renovaron ese día. Cuando el store (por ejemplo, Apple o Google) no puede procesar el pago de un suscriptor por algún motivo (como una tarjeta de crédito caducada o fondos insuficientes), la suscripción entra en el estado de Problema de pago. En este estado, la suscripción no se considera activa. Si la función de período de gracia está habilitada en la configuración del store, la suscripción solo pasará al estado de Problema de pago una vez que dicho período haya expirado. ### Filtros disponibles \{#available-filters\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, Motivo de reembolso, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Audiencia, Placement, Duración, Segmento y Atribución. ### Uso del gráfico de ingresos convertidos por problemas de facturación \{#billing-issue-converted-revenue-chart-usage\} Usa este gráfico para medir el impacto financiero de los problemas de facturación resueltos, haciendo seguimiento de los ingresos recuperados de suscripciones una vez que expira el período de gracia. Esto te ayuda a cuantificar la efectividad de tu estrategia de recuperación de problemas de facturación y a evaluar el retorno de inversión de implementar mecanismos de reintento de cobro o comunicaciones dirigidas a los usuarios. ### Métricas similares \{#similar-metrics\} - [Problema de facturación](billing-issue) - [Problema de facturación convertido](billing-issue-converted) - [Dinero reembolsado](refund-money) - [Eventos de reembolso](refund-events) - [Período de gracia](grace-period) - [Período de gracia convertido](grace-period-converted) - [Ingresos de período de gracia convertido](grace-period-converted-revenue) --- # File: ltv --- --- title: "Lifetime Value (LTV)" description: "Aprende a calcular y optimizar el Lifetime Value (LTV) en Adapty." --- El gráfico Realized LTV (Lifetime value) per paying customer muestra los ingresos que una cohorte de clientes de pago generó realmente después de deducir los reembolsos, divididos entre el número de clientes de pago de esa cohorte. Por tanto, este gráfico indica cuántos ingresos generas de media por cada cliente de pago. Adapty diseña el gráfico LTV para responder a varias preguntas importantes sobre los ingresos y el comportamiento de los clientes en tu app, como: 1. ¿Cuánto dinero aporta cada cohorte a lo largo de su ciclo de vida con tu app? 2. ¿En qué momento una cohorte empieza a ser rentable? 3. ¿Cómo puedes optimizar el gasto en marketing y adquisición para atraer clientes valiosos con un LTV alto? 4. ¿Cuánto tiempo se tarda en recuperar la inversión en la adquisición de nuevos clientes? El gráfico de LTV trabaja con los datos de la app que recopilamos a través de nuestro SDK y los eventos in-app. Con esta información, podrás obtener una visión detallada del rendimiento de tus suscripciones y los ingresos generados por tus suscriptores durante un período de tiempo determinado. Puedes usar esta información para tomar decisiones fundamentadas sobre tus ofertas de suscripción, el gasto en publicidad y las estrategias de adquisición de clientes. Además, los filtros te permiten segmentar los datos por país, atribución y otras variables, lo que te da una comprensión más detallada de tu base de clientes. ### LTV por renovaciones \{#ltv-by-renewals\} La vista **LTV by renewals** presenta datos relacionados con el período de suscripción (P), capturando específicamente la primera instancia en la que un cliente realiza un pago. En el caso de una suscripción semanal, esto corresponde al período de suscripción semanal siguiente. ### LTV por días \{#ltv-by-days\} La vista **LTV by days** organiza y filtra los datos en intervalos diarios, semanales o mensuales. Muestra los ingresos totales generados por todos los usuarios que instalaron la aplicación en un día, semana o mes concretos, divididos entre el número de usuarios de pago durante ese mismo período. Esta vista ofrece información valiosa para el seguimiento de ingresos y permite comprender de forma integral el comportamiento de los usuarios a lo largo del tiempo. ### Duración de cohorte y marco temporal \{#cohort-length-and-time-frame\} Dos ajustes de tiempo controlan lo que muestra la tabla: - **Time frame** — el rango de fechas. Se configura en el calendario que hay encima de la tabla. - **Cohort length** — el tamaño de cada fila: día, semana, mes, trimestre o año. Con una longitud mensual, cada fila cubre un mes de instalaciones. Los dos funcionan de forma independiente. Por ejemplo: un time frame de 6 meses más una cohort length mensual te da una tabla con 6 filas. Un time frame de 1 año más una cohort length semanal te da 52 filas. ### Cálculo \{#calculation\} El LTV realizado se calcula utilizando los ingresos totales generados por cada cohorte de clientes, menos los reembolsos. _LTV del día/semana/mes = Ingresos obtenidos de todos los usuarios de pago que instalaron la aplicación ese día/semana/mes / número de usuarios de pago que instalaron la aplicación ese día/semana/mes_ El cálculo del LTV incluye actualizaciones, reducciones y reactivaciones, como cuando un usuario cambia su plan de suscripción o precio. Tiene en cuenta los ingresos generados tanto por la suscripción inicial como por las renovaciones posteriores basadas en el plan actualizado. ### Agrupaciones y filtros disponibles \{#available-grouping-and-filtering\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: Tanto los filtros como las agrupaciones se pueden aplicar a las vistas de renovaciones y días del gráfico LTV, lo que te permite profundizar en cohortes específicas y entender su comportamiento a lo largo del tiempo - ✅ Filtrar por: Atribución, Audiencia, País, Paywall, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Duración, Segmento y Cohorte (Día, Semana, Mes o Año). El gráfico de LTV realizado en Adapty ayuda a obtener información valiosa sobre el comportamiento de los clientes, optimizar las estrategias de marketing, hacer seguimiento del rendimiento de los ingresos y tomar decisiones basadas en datos para maximizar el valor a largo plazo de los clientes. --- # File: analytics-cohorts --- --- title: "Análisis de cohortes" description: "Usa las cohortes de análisis en Adapty para hacer seguimiento del engagement de usuarios y las tendencias de suscripción." --- Las cohortes de Adapty están diseñadas para responder varias preguntas importantes: 1. ¿En qué día se amortiza una cohorte? 2. ¿Cuánto dinero genera la app para una cohorte concreta? 3. ¿Cuánto puedo gastar para atraer a un cliente de pago? 4. ¿Cuánto tiempo se tarda en recuperar la inversión publicitaria? Las cohortes funcionan con los datos de la app que recopilamos a través del SDK y las notificaciones del store, y no requieren ninguna configuración adicional por tu parte. ## Cohortes por renovaciones o por días \{#cohorts-by-renewals-or-by-days\} Puedes analizar cohortes por renovaciones o por días. El control cambia los encabezados de las columnas y, por tanto, también el enfoque del análisis. El seguimiento **por días** ofrece información muy útil para presupuestar y entender los plazos de pago. Es especialmente práctico para hacer seguimiento de productos que no son suscripciones, como consumibles o compras únicas. En este modo, el color azul en las celdas de la tabla tiende a concentrarse en el centro de las filas por dos razones principales. Por un lado, ver las cohortes por días permite detectar con anticipación los pagos asociados a productos de corta duración, mientras que en la vista de renovaciones estos se agrupan junto con las renovaciones mensuales y anuales. Por otro lado, los pagos diferidos contribuyen a este patrón de distribución, ya que algunos usuarios pagan más tarde de lo esperado. Mientras que el seguimiento **por renovaciones** muestra la retención y el abandono de las cohortes de un pago a otro sin tener en cuenta la fecha. Así, los usuarios que pagaron con cualquier retraso (que puede ser de meses) se añaden al número de su período de suscripción. Este enfoque no refleja la situación de los ingresos por calendario, pero es definitivamente más cómodo para analizar la retención y el abandono de las cohortes y obtener conclusiones de su comportamiento. Elige el modo que más te convenga o usa ambos para sacar más conclusiones e ideas. ## Cómo construye Adapty las cohortes \{#how-adapty-builds-cohorts\} Veamos con el ejemplo de cohortes por renovaciones cómo se forma la tabla. Para construir las cohortes, usamos dos métricas: instalaciones de la app y transacciones (compras). Cada fila de una cohorte representa un intervalo de tiempo concreto: desde un día hasta un año. Cada fila comienza con el número de usuarios que instalaron la app durante ese intervalo y activaron una suscripción o realizaron una compra de producto de por vida o sin suscripción. Cada columna siguiente de la fila muestra el número de usuarios que renovaron una suscripción en ese período. M3 significa mes 3 e indica que los suscriptores han tenido 3 renovaciones consecutivas hasta ese punto; W7 significa semana 7, e Y2 significa año 2. En ocasiones puede aparecer P2 en las cohortes. P significa Período de suscripción. Adapty lo muestra en lugar de W/M/Y cuando hay varios productos con diferentes períodos de renovación en la misma cohorte. Usamos colores degradados para destacar las diferencias entre los valores de la cohorte. Los números más altos tienen colores más saturados. En la imagen de abajo puedes ver una cohorte típica. 1. Esta cohorte muestra los datos únicamente para productos semanales (marca #1). 2. No excluye los ingresos brutos y muestra los ingresos como valores absolutos (marca #2). 3. El período de tiempo con el que trabajamos son los últimos 6 meses y la duración de la cohorte es de 1 mes (marca #3). 4. La fila **Total** (marca #4) muestra el valor acumulado para cada período. Los $442K en la primera celda de la fila **Total** acumulan los ingresos del primer período (activación de suscripción) de todos los meses (nov., dic. y así sucesivamente) hasta el final del período. La celda Total muestra el número de clientes que instalaron la app durante todo el período. 5. La primera columna de la fila nov. 2023 (marca #5) muestra los ingresos del primer período (activación de suscripción) de $37,7K de los clientes que instalaron la app en nov. 2023. El número de clientes que instalaron la app en nov. 2023, que es 95.129, se muestra en la columna de encabezado. La segunda columna de la fila nov. 2023 muestra los ingresos de la semana 2 (suscripciones renovadas hasta la 2.ª semana) de $8,77K de quienes instalaron la app en nov. 2023. 6. En la tabla se pueden ver los ingresos totales, ARPU, ARPPU y ARPAS (marca #6). Puedes leer más sobre ellos un poco más adelante en este artículo. 7. Puedes configurar las columnas en la parte derecha de la tabla usando el desplegable **Columns** (marca #7). 8. Encima de la tabla, a la derecha (marca #8), hay también un desplegable para calcular las comisiones de los stores y los impuestos aplicables al análisis de cohortes específico. Puedes saber más sobre cómo Adapty calcula las comisiones de los stores y los impuestos en [este artículo](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). Al elegir la opción correspondiente en el desplegable, los datos de ingresos se recalcularán en función de ella. 9. En el lado derecho de la tabla puedes ver los ingresos predichos (Predicted Revenue) y el valor de vida predicho (Predicted LTV) (marca #9). El campo **Predicted Revenue** estima los ingresos totales generados por una cohorte de suscriptores dentro de un período determinado, mientras que el campo **Predicted LTV** representa el valor anticipado de cada usuario de la cohorte. Puedes pasar el cursor por cualquier celda de la cohorte para ver métricas detalladas de ese período. Las celdas con líneas oblicuas en el fondo son períodos que aún no han terminado, por lo que sus valores pueden aumentar. ## Duración de la cohorte y período de tiempo \{#cohort-length-and-time-frame\} Dos ajustes de tiempo controlan lo que muestra la tabla: - **Time frame** — el rango de fechas. Se configura en el calendario que hay encima de la tabla. - **Cohort length** — el tamaño de cada fila: día, semana, mes, trimestre o año. Con una longitud mensual, cada fila cubre un mes de instalaciones. Los dos funcionan de forma independiente. Por ejemplo: un time frame de 6 meses más una cohort length mensual te da una tabla con 6 filas. Un time frame de 1 año más una cohort length semanal te da 52 filas. ## Filtros, métricas, segmentos de cohorte y exportación a CSV \{#filters-metrics-cohort-segments-and-export-in-csv\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds) ::: Por defecto, Adapty crea cohortes usando datos de todas las compras. Puedes filtrar por duración del producto, productos específicos, país, store, paywall, segmento y datos de atribución. A la derecha del panel de control hay un botón para exportar los datos de cohorte a CSV. Luego puedes abrirlo en Excel o Google Sheets, o importarlo en tu propio sistema de análisis. Hay 6 métricas que se pueden mostrar en las cohortes: Subscriptions, Payers, Revenue, ARPU, ARPPU y ARPAS. Puedes mostrarlas como valores absolutos o como cambio relativo desde el inicio de la cohorte. ## Suscripciones, pagadores, ingresos totales, ARPU, ARPPU y ARPAS \{#subscriptions-payers-total-revenue-arpu-arppu-and-arpas\} **Suscripciones** es el total de suscripciones activas, compras de por vida y compras únicas realizadas por una cohorte dentro del período seleccionado. Monitorizar esta métrica te ayuda a entender el comportamiento de los clientes y la efectividad de tus ofertas, lo que te permite refinar tu estrategia de producto, ajustar tus acciones de marketing y optimizar tus fuentes de ingresos. **Payers** es el número total de usuarios que realizaron una compra dentro de una cohorte. Te ayuda a entender cuántos usuarios únicos contribuyen a tus ingresos. Para apps con una cantidad significativa de compras in-app que no son suscripciones, esta métrica puede mostrar el verdadero alcance de tus productos, indicando si una amplia base de usuarios está realizando compras o si los ingresos los genera un grupo reducido de compradores recurrentes. Conocer el número de payers es útil para evaluar el engagement de los clientes, planificar marketing dirigido y optimizar las estrategias de ingresos. **Los ingresos totales** se acumulan para una cohorte dentro de un período de tiempo seleccionado (25 nov 2022 — 24 may 2023). Sirve para entender cuánto dinero has recaudado de los usuarios de una cohorte específica y calcular el ROAS. Por ejemplo, si el gasto en publicidad de septiembre de 2022 fue de $10 000 y los ingresos totales de la cohorte de septiembre de 2022 son de $30 000, entonces ROAS=3:1. **ARPU** es el ingreso promedio por usuario. Se calcula como ingresos totales / número de usuarios únicos. $60.000 de ingresos / 5.000 usuarios = $12 de ARPU. Es útil comparar este valor con el coste por instalación (CPI) para entender la efectividad de tus campañas de marketing. **ARPPU** es el ingreso promedio por usuario de pago. Se calcula como ingresos totales / número de usuarios de pago únicos. $60.000 de ingresos / 1.000 usuarios de pago = $60 de ARPPU. Te ayuda a entender cuánto dinero genera de media un cliente de pago. **ARPAS** es el ingreso promedio por suscriptor activo. Se calcula como ingresos totales / número de suscriptores activos. Por suscriptores entendemos aquellos que han activado un período de prueba o una suscripción. $60 000 de ingresos / 1500 suscriptores = $40 ARPAS. ## Comisiones y tasas fiscales \{#commission-fees-and-taxes\} Un aspecto importante del cálculo de ingresos en las cohortes es la inclusión de las comisiones del store y las tasas fiscales (que pueden variar según el país de la cuenta del usuario en el store). Adapty admite el cálculo de comisiones e impuestos tanto para App Store como para Play Store en el análisis de cohortes. Para más detalles sobre cómo Adapty calcula los impuestos y las comisiones en sus analíticas, consulta nuestra [documentación](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). ## Ingresos vs Ganancias \{#revenue-vs-proceeds\} Tanto los Ingresos como las Ganancias son métricas de dinero. Puedes pensar en los Ingresos como ingresos brutos y en las Ganancias como ingresos netos. Los Ingresos no tienen en cuenta las comisiones del App Store / Play Store, mientras que las Ganancias sí. Por eso las Ganancias son siempre inferiores a los Ingresos. La comisión real que se deduce varía en función de varios factores, entre ellos la elegibilidad para programas como el [Small Business Program](app-store-small-business-program) (15%), las tarifas reducidas para suscripciones de larga duración (15% tras un año de renovación), las tarifas específicas por país y las tarifas estándar (hasta el 30%). Adapty determina automáticamente la tasa de comisión aplicable para cada transacción que realizan tus clientes y calcula los ingresos netos en función de ella. Para más información sobre cómo se determinan las tasas de comisión, consulta la documentación de [Comisión del store e impuestos](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). ## Gestión de reembolsos \{#refund-handling\} Hay dos reglas universales para todas las métricas de cohorte: - Un reembolso se fecha en el día en que se emite, no en la fecha de la compra original. Un reembolso afecta a una cohorte solo cuando esa fecha de reembolso cae dentro del período de tiempo seleccionado. - Un reembolso nunca saca a un usuario de su cohorte ni modifica el recuento de instalaciones. La pertenencia a una cohorte se fija en el momento en que el usuario instala la app. Más allá de estas reglas, el efecto de un reembolso depende de la métrica y del [modo de visualización](#cohorts-by-renewals-or-by-days). Consulta la columna correspondiente al modo que utilices. | Métrica | Por renovaciones | Por días | | --- | --- | --- | | Instalaciones (tamaño de cohorte) | No se ve afectado. El usuario permanece en su cohorte. | Igual que "Por renovaciones". | | Suscripciones | Las suscripciones reembolsadas siguen contando. | Un reembolso elimina la suscripción del recuento. | | Pagadores | Los usuarios pagadores reembolsados siguen contando. | Un reembolso elimina al usuario del recuento, aunque haya realizado otros pagos exitosos. | | Ingresos | El importe reembolsado se resta de la columna del período de renovación donde se contabilizó el pago originalmente. | El importe reembolsado se resta a partir del día del reembolso. | | ARPU | Ingresos / instalaciones. Un reembolso reduce los ingresos; el recuento de instalaciones nunca cambia. | Igual que "Por renovaciones". | | ARPPU | Ingresos / usuarios pagadores. Un reembolso puede reducir tanto los ingresos como el número de usuarios pagadores, por lo que el ARPPU puede variar más bruscamente que los ingresos por sí solos. | Igual que "Por renovaciones". | | ARPAS | Ingresos / suscriptores activos. Un reembolso reduce los ingresos; el recuento de suscriptores no cambia. | Igual que "Por renovaciones". | | Retención | No se ve afectado. Contabiliza eventos de prueba y de compra, no reembolsos. | Igual que "Por renovaciones". | Un reembolso resta el importe reembolsado de los ingresos independientemente del modo de contabilidad de ingresos activo. ### Tasa de conversión y ARPPU \{#conversion-rate-and-arppu\} Los reembolsos no afectan a la tasa de conversión basada en instalaciones, ya que el número de instalaciones nunca cambia. La tasa de conversión basada en usuarios de pago es diferente. Los reembolsos la reducen en la vista **by days**, pero no en la vista **by renewals**. En la vista **por renovaciones**, las columnas de Ingresos y Pagadores muestran cada período de forma individual. La columna ARPPU no. Cada celda de ARPPU acumula todo desde el primer período de la cohorte hasta el período de la columna. Por tanto, siempre abarca varios períodos a la vez y excluye a los usuarios que solicitaron reembolso. Por eso, dividir los Ingresos de un único período entre sus usuarios pagadores no reproduce el ARPPU mostrado. **Ejemplo.** Un usuario instala en febrero y compra una suscripción, luego recibe un reembolso completo en abril. Al ver la cohorte de febrero **por días**: - Período de tiempo febrero–marzo, antes de que el reembolso entre en la ventana: el usuario cuenta como 1 usuario de pago, y sus ingresos se incluyen en su totalidad. - Período de tiempo febrero–junio, después de que el reembolso entre en la ventana: el usuario cuenta como 0 usuarios de pago, y sus ingresos caen a 0. En ambos períodos de tiempo, el recuento de instalaciones de febrero y la Retención se mantienen igual. [Cómo gestionan las métricas los reembolsos](refund-events#how-metrics-handle-refunds) compara las mismas reglas entre el MRR, los gráficos de ingresos y las exportaciones de datos. ## Predicción: Ingresos y LTV \{#prediction-revenue-and-ltv\} **El ingreso predicho** es el total estimado de ingresos que se espera que genere una cohorte de suscriptores de pago dentro del período seleccionado tras la creación de la cohorte. Se calcula multiplicando el LTV predicho de la cohorte por el número predicho de usuarios de pago dentro de ella. Por ejemplo, si el LTV predicho es $50 y hay 100 usuarios de pago en una cohorte, el ingreso predicho sería $5.000. **LTV previsto** es el valor de vida estimado por suscriptor de pago, que representa los ingresos medios que se espera que genere cada suscriptor de pago dentro del período seleccionado tras la creación de la cohorte. Estas predicciones se basan en patrones históricos de retención de cohortes, utilizando los datos propios de la app cuando hay suficiente historial disponible y promedios entre apps en caso contrario. Para documentación detallada sobre el modelo de predicción de Adapty, consulta nuestra [documentación de Predicción](predicted-ltv-and-revenue). Las cohortes de Adapty ofrecen información detallada sobre el comportamiento de los usuarios y el rendimiento financiero de tu app. Al analizar cohortes por renovaciones o días, puedes determinar cuándo se vuelven rentables, hacer seguimiento de los ingresos, calcular el ingreso medio por usuario y entender el tiempo necesario para recuperar el gasto publicitario. Con filtros, métricas y opciones de exportación personalizables, Adapty te permite tomar decisiones basadas en datos y optimizar las estrategias de adquisición de usuarios y monetización para maximizar el éxito de tu app. --- # File: analytics-funnels --- --- title: "Análisis de embudo" description: "Comprende los embudos de análisis en Adapty para monitorear el comportamiento de los usuarios y mejorar las conversiones." --- Los embudos de Adapty están diseñados para ayudarte a responder preguntas como: 1. ¿Qué porcentaje de las instalaciones se convierte en clientes de pago? 2. ¿Qué proporción de quienes probaron el producto se convirtieron en usuarios fieles? 3. ¿En qué pasos hay mayor abandono y requieren más atención? 4. ¿Por qué los clientes dejan de pagar? Con un gráfico de embudo, también puedes obtener más información sobre el comportamiento de los usuarios configurando filtros y grupos. Los embudos trabajan con los datos que recopilamos a través del SDK y las notificaciones del store, y no requieren ninguna configuración adicional de tu parte. :::note Los embudos reflejan los datos de instalación según tu definición de instalación en [App Settings](general#4-installs-definition-for-analytics). ::: <img src="/assets/shared/img/funnels-tab.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## El gráfico de embudo paso a paso \{#funnel-chart-step-by-step\} Repasemos los elementos de un embudo para entender cómo leer el recorrido del usuario en el gráfico. ### Instalaciones \{#installs\} La 1ª columna (1) muestra el número de instalaciones. Se presenta como valor absoluto (2) del total de instalaciones (no usuarios únicos) y también como 100%, que es el número de entrada más alto para el cálculo relativo de conversiones posteriores. Si un usuario elimina la app y la vuelve a instalar, se contabilizan dos instalaciones por separado. El área gris adyacente representa los parámetros de transición entre pasos. El porcentaje de conversión al siguiente paso (Paywall mostrado) se muestra en una etiqueta (3). El porcentaje de abandono y el valor absoluto de la pérdida se muestran a continuación (4). <img src="/assets/shared/img/00416f9-CleanShot_2022-06-23_at_14.02.06.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Paywall mostrado \{#paywall-displayed\} La 2ª columna (5) muestra el número de usuarios de la app que vieron un paywall al menos una vez (6). Solo se tienen en cuenta los usuarios cuya instalación ocurrió en el período seleccionado. Si un usuario ve un paywall en el período seleccionado pero la fecha de su instalación está fuera del rango, esa visualización no se contabiliza. También se muestra el porcentaje de esas visualizaciones respecto al 1er paso (7). Puedes observar que este porcentaje es igual a la bandera gris (3) del 1er paso. Esta igualdad solo se da en estos primeros pasos. Recopilamos los datos de este paso a partir de todos tus paywalls que usan el método `logShowFlow()` (iOS SDK v4+) / `logShowPaywall()`. Asegúrate de enviar cada visualización de paywall a Adapty mediante este método, tal como se describe en la [documentación](present-remote-config-paywalls#track-paywall-view-events). El área gris junto a la 2ª columna representa la transición. El porcentaje de conversión al siguiente paso (Trial) se muestra en una bandera (8). El porcentaje de abandono y el valor absoluto de clientes perdidos tras el paywall se muestran a continuación (9). <img src="/assets/shared/img/fb11650-CleanShot_2022-06-23_at_15.54.32.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Pruebas gratuitas \{#trials\} La 3.ª columna (10) muestra el número de pruebas gratuitas activadas en los paywalls por los usuarios que instalaron la aplicación dentro del período seleccionado (11). Si el filtro está configurado para productos sin prueba gratuita, este valor es cero y la columna aparece vacía. Observa también el porcentaje de trials tomado desde el 1er paso, que muestra la conversión de instalaciones a trials (12). Puede que notes que este porcentaje no coincide ahora con la bandera gris (8) de la conversión del paso anterior. Esto se debe a que comparamos el valor actual con el 1er paso en la parte superior del gráfico y con el paso anterior en las banderas grises. Por tanto, el área gris junto a la 3ª columna muestra el porcentaje de conversión al siguiente paso (Paid), que se indica en una bandera (13). El porcentaje de abandono y el valor absoluto de clientes que se dieron de baja durante el período de trial se muestran a continuación (14). <img src="/assets/shared/img/7b88909-CleanShot_2022-06-23_at_15.54.32_-_2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Suscripciones y renovaciones \{#subscriptions-and-renewals\} La 4ª columna muestra el número de suscripciones activadas (15). Para productos sin período de prueba, este número incluye las suscripciones directas desde un paywall. Para productos con período de prueba, contiene el número de pruebas convertidas en suscripciones de pago. Si tienes ambos tipos de productos, con y sin período de prueba, será la suma de ambos. El porcentaje en la parte superior muestra la conversión desde las instalaciones (16). El porcentaje en una bandera gris muestra la conversión al siguiente paso (renovación al 2.º período) (17). La pérdida antes de la renovación al 2.º período, en porcentaje y valor absoluto, se muestra debajo de la conversión (18). <img src="/assets/shared/img/d13bf9b-CleanShot_2022-06-23_at_15.54.32-3.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Este paso da inicio a una secuencia de pasos con una estructura similar. Tras la 2.ª renovación llega la 3.ª, luego la 4.ª, etc. Si hay suficientes datos en el historial de tu app, puedes ver decenas de períodos usando el scroll horizontal. La lógica de estos pasos es siempre la misma: - porcentaje sobre instalaciones en la parte superior, - porcentaje sobre el paso anterior en la parte inferior, - cantidad absoluta de renovaciones en la parte superior, - cantidad absoluta de cancelaciones en la parte inferior, - un hover para el pop-up con los motivos de cancelación. ### Razones de abandono \{#churn-reasons\} Adapty detalla las estadísticas de *abandono* para la etapa de prueba y las siguientes. Todo usuario que entró en una etapa pero no en la siguiente cuenta como una instancia de abandono. * Si un evento concreto (por ejemplo, la expiración de una prueba o un problema de facturación) fue la causa de la falta de conversión, Adapty muestra la razón. * El estado **unknown** es un estado temporal. Indica que el usuario aún no ha encontrado el evento que le permite pasar a la siguiente etapa. En la etapa de prueba, esto suele significar que la prueba aún no ha terminado. Esto ocurre habitualmente al ver embudos para rangos de fechas cortos o días individuales, ya que las pruebas necesitan tiempo para resolverse. Adapty actualizará la información una vez que el usuario convierta o cancele la prueba. <img src="/assets/shared/img/churn-reasons.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Vista de tabla, filtros y exportación CSV \{#table-view-filters-and-csv-export\} El gráfico de embudo se complementa con una tabla de datos para facilitar el trabajo con los números. <img src="/assets/shared/img/4787aff-CleanShot_2022-06-23_at_21.01.44.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Esta tabla sigue el mismo enfoque del embudo con algunas modificaciones. Hay columnas que muestran datos de todos los pasos excepto el de la 1.ª suscripción de pago. En su lugar, hay dos columnas separadas: Install → Paid y Trial → Paid. Muestran el punto clave de conversión en el que un usuario gratuito se convierte en de pago. Puede parecer que existe una división por tipo de producto: la columna Instalación → Pago muestra solo productos sin períodos de prueba, mientras que la columna Prueba → Pago contiene únicamente productos con períodos de prueba. Pero no es exactamente así. También tenemos en cuenta a aquellos usuarios cuyo período de prueba ha expirado y que compran un producto con período de prueba como si no lo tuviera. <img src="/assets/shared/img/a9bcbc7-CleanShot_2022-06-23_at_21.29.12.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Si profundizas en los números, encontrarás potentes herramientas de filtrado para generar nuevas hipótesis. Establece condiciones en distintas dimensiones y extrae insights reales basados en datos. Varía: 1. Tipo de producto: economía, duración, etc. 2. Rango de fechas. 3. Segmentación por país. 4. Atribución de tráfico. 5. Store. Selecciona Número absoluto, Porcentaje relativo o ambos para ver solo los datos que necesitas. <img src="/assets/shared/img/1475e42-CleanShot_2022-06-23_at_21.50.33_-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Por último, a la derecha del panel de control hay un botón para exportar los datos del embudo a CSV. Luego puedes abrirlo en Excel, en Google Sheets, o importarlo a tu propio sistema de análisis. :::important Notifica a Adapty si tu aplicación está inscrita en un programa de comisión reducida. Para garantizar cálculos correctos, especifica el estado de tu [Programa para Pequeñas Empresas](app-store-small-business-program) y el [programa de Tarifa de Servicio Reducida](google-reduced-service-fee) en los [ajustes de tu aplicación](general). ::: <img src="/assets/shared/img/ff23846-CleanShot_2022-06-23_at_22.15.49.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: analytics-retention --- --- title: "Análisis de retención" description: "Comprende los análisis de retención de usuarios y optimiza tu estrategia de suscripción." --- Los gráficos de retención pueden ayudarte a responder las siguientes preguntas: 1. ¿Cómo retiene tu app a los clientes de período en período? 2. ¿Qué productos son más atractivos y fidelizan mejor? 3. ¿Qué grupos de usuarios son más leales? 4. ¿Qué nivel de retención puede usarse como referencia para el crecimiento? 5. Y, por supuesto, cómo ahorrar dinero invirtiendo en la audiencia ya captada en lugar de buscar nuevos usuarios. Encontrarás información valiosa sobre el comportamiento de los usuarios configurando filtros y grupos. La retención se calcula con los datos que recopilamos a través del SDK y las notificaciones de la store, sin necesidad de ninguna configuración adicional por tu parte. ### ¿Cómo calculamos la retención? \{#how-do-we-calculate-retention\} Al observar el gráfico de retención, puedes ver cómo varía el número de usuarios según el paso: trial (si la casilla "mostrar trials" está marcada), el 1.er pago, el 2.º pago, etc. A continuación se explica qué usuarios se contabilizan al seleccionar un rango de fechas para el gráfico de retención. Por ejemplo, si has seleccionado los últimos 3 meses en el calendario y la casilla "mostrar trials" no está marcada, se contabilizan únicamente los usuarios que hayan realizado su 1.ª suscripción durante ese período. Si la casilla "mostrar trials" está marcada y se han seleccionado los últimos 3 meses, se contabilizan todos los usuarios que hayan iniciado un trial durante ese período. Para estos suscriptores, la retención absoluta en el paso N se muestra como el número de usuarios que realizaron el pago N. El valor relativo de retención para el paso N se calcula como la proporción entre la cantidad absoluta del pago N y el total de suscripciones (o trials) del rango de fechas seleccionado. :::info La retención cambia de forma retroactiva Independientemente de cuándo consultes el gráfico, el número de referencia (100%) permanece igual para el período de tiempo seleccionado. Sin embargo, la retención al siguiente período puede aumentar con el tiempo. Por ejemplo, en una suscripción mensual, si se realizan 20 primeras compras entre el 1 y el 31 de diciembre, es de esperar que la retención al segundo período vaya creciendo a lo largo de enero (e incluso después) a medida que los usuarios vayan entrando en el siguiente período de suscripción, a tiempo o con algo de retraso por distintos motivos (por ejemplo, el período de gracia). ::: ### Gestión de reembolsos \{#refund-handling\} Los reembolsos **no** se excluyen de la retención. Un usuario con reembolso sigue contando en la curva de retención, lo que puede hacer que la Retención parezca más alta que las [Suscripciones activas](active-subscriptions) o los [Ingresos](revenue) para la misma cohorte. Para una comparación completa entre métricas, consulta [Cómo gestionan los reembolsos las métricas](refund-events#how-metrics-handle-refunds). ### Oportunidades de retención \{#retention-opportunities\} Veamos cómo sacarle el máximo partido a la función de retención de Adapty. Más allá de la pura pasión por los números, lo que realmente nos interesa es ver el valor de negocio real que surge al aplicar los resultados del análisis. Por eso, conviene pensar primero en los objetivos. Con un análisis profundo de las funciones del gráfico, es útil entender qué impacto puede tener esta información. Así que veamos juntos el POR QUÉ y el CÓMO. 1 - trabaja con tu audiencia. Ante todo, la retención tiene que ver con tu audiencia objetivo, sus preferencias y si tu producto cumple o no sus expectativas a lo largo del ciclo de consumo. Si alguna vez te has preguntado cómo medir la relación central de tu negocio que genera dinero, la retención es tu respuesta. Medir esto es valioso porque, por lo general, vender a un cliente existente es más barato que vender a alguien nuevo. Y ese coste es menor por dos razones: menos esfuerzo de venta y un ticket medio más alto. Así que, cuando la retención baja, puede ser buena idea invertir en la fidelidad de tus suscriptores. 2 - trabaja con el producto. La segunda razón del POR QUÉ es que los gráficos de retención muestran la vida útil real de consumo de tu producto y te permiten hacer previsiones a largo plazo. Y si quieres mejorar, corrige el trabajo que entrega el producto para cambiar su vida útil, y luego vuelve a hacer previsiones para acercarte a tus objetivos de negocio. Estas actualizaciones pueden formar parte de una visión estratégica que funcione junto con una rutina de previsión. Y sí, este proceso no termina nunca, porque todos corremos rápido para quedarnos en el mismo sitio en un entorno en constante cambio. 3 - trabajar con el mercado. Moverse más rápido que los principales competidores está bien, pero a veces salir de la carrera habitual puede traer más beneficios. Cuando analizas el comportamiento de los usuarios en diferentes países y stores, algunas particularidades locales pueden abrir insights destacados y nuevas oportunidades para el negocio. El contexto cultural y de mercado puede analizarse desde la perspectiva de la retención para usarlo después en la segmentación y el desarrollo futuro. Por ejemplo, puedes encontrar océanos azules en algunas regiones y crecer allí más rápido. El uso de los datos de retención no se limita, por supuesto, a esta interpretación básica, pero puede ser un buen punto de partida si quieres obtener valor real rápidamente. ### Curvas, vista de tabla, filtros y exportación CSV \{#curves-table-view-filters-and-csv-export\} Ahora que estamos en la misma página en cuanto a los objetivos de retención y las formas básicas de interpretación, repasemos las herramientas que lo hacen todo más manejable. El núcleo de la función de retención en Adapty es el gráfico. Muestra cómo varía el nivel de retención a lo largo de las etapas del ciclo de vida de un cliente. Las etapas se muestran en el eje horizontal: Trial, Paid (la 1.ª suscripción), P2 (la 2.ª suscripción), P3, P4, etc. Ten en cuenta que el eje comienza con la etapa Trial únicamente cuando la casilla **Show trials** está seleccionada. En cuanto al cálculo de datos, esta casilla funciona así: cuando **Show trials** está seleccionada y el eje empieza en la etapa Trial, solo se muestran los escenarios que incluyen trials; no se incluyen transacciones directas desde instalaciones, y la etapa Paid contiene únicamente las transacciones que provienen de trials. Cuando **Show trials** no está seleccionada y el eje comienza en la etapa Paid, ese primer paso incluye todas las primeras transacciones: tanto las que vienen de trials como las que vienen directamente de instalaciones. Al pasar el cursor sobre el gráfico, se muestra un pop-up con un resumen de los datos. Y si pasas el cursor sobre una columna de la tabla inferior, también verás un pop-up con los datos relevantes del gráfico. La tabla contiene los mismos agrupamientos y filtros seleccionados para el gráfico. Combina filtros y agrupaciones para un análisis avanzado. Extrae conclusiones reales a partir de los datos. Varía: 1. Tipo de producto. 2. Duración. 3. Rango de tiempo. 4. País. 5. Atribución de tráfico. 6. Store. Usa el control #Absolute y %Relative para ver los datos que necesitas. Por último, a la derecha del panel de control, hay un botón para exportar los datos del embudo a CSV. Puedes abrirlos en Excel, en Google Sheets o importarlos en tu propio sistema de análisis para seguir analizando y haciendo previsiones en el entorno que prefieras. :::warning Asegúrate de indicar que tu app está incluida en el Small Business Program en [Adapty General Settings](https://app.adapty.io/settings/general). ::: --- # File: analytics-conversion --- --- title: "Análisis de conversión" description: "Mide las tasas de conversión de suscripciones con las herramientas de análisis de Adapty." --- Mientras que los embudos ofrecen una visión general de alto nivel y la retención se centra en la fidelización, el análisis de conversión está diseñado para ayudarte a evaluar la efectividad en cada paso clave del recorrido del usuario, a lo largo del tiempo. Las conversiones responden a las siguientes preguntas: 1. ¿Cómo cambian las conversiones de la app con el tiempo? ¿Existen tendencias estacionales? 2. ¿Cómo se ven afectadas las conversiones en el momento de actividades de marketing u otras circunstancias nuevas? 3. ¿Cómo responden los usuarios de diferentes regiones a las actualizaciones de tu app? 4. ¿Qué tipos de producto convierten mejor a lo largo del tiempo? La conversión se calcula con los datos que recopilamos a través del SDK de Adapty y las notificaciones del store, y no requiere ninguna configuración adicional de tu parte. ## Controles principales y gráficos \{#main-controls-and-charts\} Aunque los ingresos suelen ser la métrica de referencia para medir el éxito, son solo una parte del panorama general. Entender cómo evoluciona tu negocio a lo largo del tiempo, considerando diferentes comportamientos de usuario y etapas del ciclo de vida, es igual de importante. Ahí es donde entra el análisis de conversiones. Puedes encontrar información más valiosa sobre el comportamiento de los usuarios configurando filtros y grupos. Para identificar y analizar tendencias, controla cómo evolucionan tus conversiones de forma diaria, mensual o anual. En el lado izquierdo del gráfico encontrarás el control de pasos de conversión. Esto te permite elegir qué conversiones específicas rastrear, como Instalación → Prueba, Prueba → Pago, o Pago → Renovación. Cada métrica de conversión sigue esta lógica: - Sea **X** el número de usuarios que entraron al estado inicial en una fecha seleccionada (p. ej., instalaciones). - Sea **Y** el número de esos usuarios que finalmente alcanzaron el estado objetivo (p. ej., inicio de prueba). - La tasa de conversión se calcula como: **Conversión = (Y / X) × 100%** :::note La fecha que aparece en el gráfico corresponde a cuándo los usuarios entraron al estado inicial (X), es decir, el momento en que se volvieron elegibles para convertir. ::: A continuación encontrarás la explicación de cada conversión, con un ejemplo de referencia. ### Instalación -> Pago \{#install---paid\} Esta métrica muestra qué porcentaje de usuarios que instalaron la app en una fecha determinada terminaron comprando su primera suscripción. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de instalaciones en una fecha seleccionada (igual para todos los productos, ya que no se elige ningún producto en el momento de la instalación). - **Y** = número de esos usuarios que finalmente compraron su primera suscripción (con o sin prueba). **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero hubo 100 instalaciones. - Para el 8 de enero, 20 de esos usuarios se habían suscrito. - El 8 de enero, la conversión del 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de febrero, 30 usuarios más del grupo del 1 de enero habían comprado una suscripción. - El 1 de febrero, la conversión del 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto significa que el 50% de los usuarios que instalaron la app el 1 de enero acabaron convirtiéndose a una suscripción de pago, hasta el momento actual. </details> ### Instalación -> Prueba \{#install---trial\} Esta métrica muestra el porcentaje de usuarios que instalaron la app en una fecha determinada y finalmente iniciaron una prueba. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de instalaciones en una fecha seleccionada (igual para todos los productos, ya que no se elige ningún producto en el momento de la instalación). - **Y** = número de esos usuarios que finalmente activaron una prueba, en cualquier momento. **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero hubo 100 instalaciones. - Para el 8 de enero, 20 de esos usuarios habían iniciado una prueba. - El 8 de enero, la conversión del 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de febrero, 30 usuarios más del grupo del 1 de enero habían iniciado una prueba. - El 1 de febrero, la conversión del 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto significa que el 50% de los usuarios que instalaron la app el 1 de enero finalmente iniciaron una prueba, hasta el momento actual. </details> ### Vista de paywall -> Prueba \{#paywall-view---trial\} Esta métrica rastrea cuántos usuarios iniciaron la prueba después de ver un paywall. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de usuarios que vieron un paywall en una fecha seleccionada. - **Y** = número de usuarios que iniciaron la prueba en cualquier momento posterior. **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero hubo 100 vistas del paywall. - Para el 8 de enero, 20 de esos usuarios habían iniciado la prueba. - El 8 de enero, la conversión del 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de febrero, 30 usuarios más habían iniciado la prueba. - El 1 de febrero, la conversión del 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto muestra que el 50% de los usuarios que vieron un paywall el 1 de enero iniciaron la prueba, hasta el momento actual. </details> ### Vista de paywall -> Pago \{#paywall-view---paid\} Esta métrica rastrea cuántos usuarios realizaron una compra después de ver un paywall. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de usuarios que vieron un paywall en una fecha seleccionada. - **Y** = número de usuarios que realizaron una compra en cualquier momento posterior. **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero hubo 100 vistas del paywall. - Para el 8 de enero, 20 de esos usuarios habían realizado una compra. - El 8 de enero, la conversión del 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de febrero, 30 usuarios más habían realizado una compra. - El 1 de febrero, la conversión del 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto muestra que el 50% de los usuarios que vieron un paywall el 1 de enero realizaron una compra, hasta el momento actual. </details> ### Prueba -> Pago \{#trial---paid\} Esta métrica muestra el porcentaje de usuarios que iniciaron una prueba en una fecha determinada y más tarde compraron su primera suscripción. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de pruebas iniciadas en una fecha seleccionada. - **Y** = número de esos usuarios que finalmente compraron una suscripción tras su prueba. **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero se iniciaron 100 pruebas. - Para el 8 de enero, 20 de esos usuarios se habían suscrito. - El 8 de enero, la conversión del 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de febrero, 30 usuarios más del grupo del 1 de enero se habían suscrito. - El 1 de febrero, la conversión del 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto significa que el 50% de los usuarios que iniciaron una prueba el 1 de enero acabaron convirtiéndose a una suscripción de pago, hasta el momento actual. </details> ### Pago -> 2.º Período \{#paid---2nd-period\} Esta métrica muestra el porcentaje de usuarios que renovaron su suscripción tras el primer pago. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de primeras suscripciones en una fecha seleccionada. - **Y** = número de usuarios que renovaron para un segundo período, en cualquier momento posterior (normalmente tras un ciclo de suscripción; incluye renovaciones durante el período de gracia). - **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero hubo 100 primeras suscripciones. - Para el 8 de enero, 20 de ellas habían renovado. - El 8 de enero, la conversión del 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de febrero, 30 usuarios más de ese grupo habían renovado. - El 1 de febrero, la conversión del 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto muestra que el 50% de los usuarios que realizaron su primer pago de suscripción el 1 de enero renovaron para un segundo período, hasta el momento actual. </details> ### 2.º Período -> 3.er Período \{#2nd-period---3rd-period\} Esta métrica rastrea cuántos usuarios renovaron de nuevo tras su segundo período de suscripción. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de suscripciones en segundo período en una fecha seleccionada. - **Y** = número de usuarios que renovaron para un tercer período, en cualquier momento posterior (normalmente tras otro ciclo de facturación; incluye renovaciones durante el período de gracia). **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero hubo 100 suscripciones en segundo período. - Para el 8 de enero, 20 de esos usuarios habían renovado. - El 8 de enero, la conversión del 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de febrero, 30 usuarios más habían renovado. - El 1 de febrero, la conversión del 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto muestra que el 50% de los usuarios que entraron en su segundo período de suscripción el 1 de enero renovaron para un tercero, hasta el momento actual. </details> ### 3.er Período -> 4.º Período \{#3rd-period---4th-period\} Esta métrica muestra el porcentaje de usuarios que renovaron tras su tercer período de suscripción. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de suscripciones en tercer período en una fecha seleccionada. - **Y** = número de usuarios que renovaron para un cuarto período en cualquier momento posterior (normalmente tras un ciclo de facturación; incluye renovaciones durante el período de gracia). **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero hubo 100 suscripciones en tercer período. - Para el 8 de enero, 20 usuarios habían renovado. - El 8 de enero, la conversión del 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de febrero, 30 usuarios más renovaron. - El 1 de febrero, la conversión del 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto significa que el 50% de los usuarios que entraron en su tercer período de suscripción el 1 de enero renovaron para un cuarto, hasta el momento actual. </details> ### 4.º Período -> 5.º Período \{#4th-period---5th-period\} Esta métrica muestra el porcentaje de usuarios que renovaron tras su cuarto período de suscripción. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de suscripciones en cuarto período en una fecha seleccionada. - **Y** = número de usuarios que renovaron para un quinto período en cualquier momento posterior (normalmente tras un ciclo de facturación; incluye renovaciones durante el período de gracia). **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero hubo 100 suscripciones en cuarto período. - Para el 8 de enero, 20 usuarios habían renovado. - El 8 de enero, la conversión del 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de febrero, 30 usuarios más renovaron. - El 1 de febrero, la conversión del 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto significa que el 50% de los usuarios que entraron en su cuarto período de suscripción el 1 de enero renovaron para un quinto, hasta el momento actual. </details> ### 6 Meses + \{#6-months-\} Esta métrica muestra el porcentaje de usuarios que permanecieron suscritos durante más de 6 meses desde su primera suscripción. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de primeras suscripciones en una fecha seleccionada. - **Y** = número de esos usuarios que renovaron al menos una vez después de 6 meses desde la fecha de suscripción original. **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero hubo 100 primeras suscripciones. - Para la primera semana de julio, 20 de ellas habían renovado (p. ej., en su 25.ª suscripción semanal). - El 8 de julio, la conversión del 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de agosto, 30 más habían renovado después de 6 meses. - El 1 de agosto, la conversión del 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto significa que el 50% de los usuarios que se suscribieron el 1 de enero permanecieron suscritos más de 6 meses a fecha del 1 de agosto. </details> ### 1 Año + \{#1-year-\} Esta métrica muestra el porcentaje de usuarios que permanecieron suscritos durante más de 12 meses desde su primera suscripción. <details> <summary>Cómo funciona</summary> **Sea**: - **X** = número de primeras suscripciones en una fecha seleccionada. - **Y** = número de esos usuarios que renovaron al menos una vez después de 12 meses desde la fecha de suscripción original. **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero de 2021 hubo 100 primeras suscripciones. - Para la primera semana de enero de 2022, 20 habían renovado. - El 8 de enero de 2022, la conversión = (20 / 100) × 100% = 20% - Para el 1 de febrero de 2022, 30 más habían renovado después de 12 meses. - El 1 de febrero de 2022, la conversión = ((20 + 30) / 100) × 100% = 50% Esto significa que el 50% de los usuarios que se suscribieron el 1 de enero de 2021 permanecieron activos durante más de un año. </details> ### 2 Años + \{#2-years-\} Esta métrica muestra el porcentaje de usuarios que permanecieron suscritos durante más de 24 meses desde su primera fecha de pago. <details> <summary>Cómo funciona</summary> **Sea**: - X = número de primeras suscripciones en una fecha seleccionada. - Y = número de esos usuarios que renovaron al menos una vez después de 24 meses desde la fecha de suscripción original. **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero de 2020 hubo 100 primeras suscripciones. - Para la primera semana de enero de 2022, 20 de ellas habían renovado. - El 8 de enero de 2022, la conversión = (20 / 100) × 100% = 20% - Para el 1 de febrero de 2022, 30 más habían renovado después de 2 años. - El 1 de febrero de 2022, la conversión = ((20 + 30) / 100) × 100% = 50% Esto significa que el 50% de los usuarios que se suscribieron el 1 de enero de 2020 seguían activos después de 2 años, a fecha del 1 de febrero de 2022. </details> ### Período de gracia -> Pago \{#grace-period---paid\} Esta métrica muestra el porcentaje de usuarios que entraron en un [período de gracia de suscripción](grace-period) y resolvieron el problema *antes* de que finalizara dicho período. <details> <summary>Cómo funciona</summary> **Sea**: - X = número de suscriptores que entraron en el período de gracia. - Y = número de esos usuarios que renovaron la suscripción antes de que expirase el período de gracia. **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero de 2025, la suscripción de 100 personas no pudo renovarse automáticamente. Entraron en un período de gracia de 16 días, con vencimiento el 17 de enero. - 50 personas actualizaron su información de pago entre el 1 y el 17 de enero, y su suscripción se renovó con éxito. - El 17 de enero de 2025, la conversión = (50 / 100) × 100% = 50% </details> ### Problema de facturación -> Pago \{#billing-issue---paid\} Esta métrica muestra el porcentaje de usuarios que tuvieron un [problema de facturación](/billing-issue) y reanudaron el pago antes de que finalizara el ciclo de facturación. <details> <summary>Cómo funciona</summary> **Sea**: - X = número de suscriptores que tuvieron un problema de facturación. - Y = número de esos usuarios que renovaron su suscripción en el tiempo transcurrido entre el problema de facturación y el final del ciclo de facturación. **Fórmula**: Conversión = (Y / X) × 100% **Ejemplo**: - El 1 de enero, 100 suscriptores tuvieron un problema de facturación cuando su suscripción no pudo renovarse automáticamente. - Nota: si hay un período de gracia habilitado, el estado de problema de facturación comienza solo tras el vencimiento del período de gracia. En este ejemplo, se asume que el período de gracia finalizó el 1 de enero. - Para el 8 de enero, 10 de esos usuarios habían resuelto el problema de pago y renovado. - El 8 de enero, la conversión del 1 de enero = (10 / 100) × 100% = 10% - Para el 31 de enero (fin del ciclo de facturación), 10 usuarios más habían renovado. - El 31 de enero, la conversión del 1 de enero = ((10 + 10) / 100) × 100% = 20% Esto muestra que el 20% de los usuarios que entraron en estado de problema de facturación el 1 de enero resolvieron el problema y renovaron antes del final de su ciclo de facturación. </details> ## Agrupación y rangos de fechas \{#grouping-and-time-ranges\} El objeto de análisis cuando se selecciona la conversión es el gráfico. Muestra cómo cambia el porcentaje de conversión a lo largo del tiempo. Usa el selector de fechas para elegir opciones rápidas del período de tiempo. El gráfico suele contener varias curvas. Hasta cinco de ellas se seleccionan por defecto en la lista de agrupación, y puedes cambiar la selección marcando las casillas en el área a la derecha del gráfico. Cuando abres la página por primera vez, la duración del producto se selecciona como agrupación predeterminada. Luego, tu configuración se guarda en caché y la próxima vez verás el grupo que seleccionaste recientemente. Las siguientes agrupaciones están disponibles: - Producto - País - Store - Paywall - Duración - Atribución de marketing Si el rango de fechas elegido no es suficiente para mostrar resultados, puede aparecer una notificación que sugiere una fecha relevante y la opción de ajustar el rango automáticamente para hacerlo con un solo clic. ## Vista de tabla, filtros y exportación CSV \{#table-view-filters-and-csv-export\} La comparación de las curvas ofrece una imagen clara, y para sacar más partido usa la vista de tabla debajo del gráfico. La tabla está sincronizada con el gráfico, de modo que al pasar el cursor sobre una columna verás el pop-up asociado sobre las curvas. La agrupación mencionada anteriormente afecta tanto a los gráficos como a la tabla. Establece un filtro rápido por producto o usa otros más avanzados, como Producto, País, Store, Duración y Atribución. Sabemos que es importante poder trabajar con los números como prefieras. Por eso, a la derecha del panel de control hay un botón para exportar los datos del embudo a CSV. Puedes abrirlo en Excel o Google Sheets, o importarlo en tu propio sistema analítico para continuar el análisis y las previsiones en tu entorno preferido. :::important Notifica a Adapty si tu app está inscrita en un programa de comisión reducida. Para garantizar cálculos correctos, especifica el estado de tu [Small Business Program](app-store-small-business-program) y tu [programa de tarifa de servicio reducida](google-reduced-service-fee) en la [configuración de tu app](general). ::: --- # File: reports --- --- title: "Informes" description: "Genera informes detallados de suscripción en Adapty para analizar los ingresos de la app y el comportamiento de los usuarios." --- Recibe información puntual y relevante directamente en tu bandeja de entrada: ingresos, tasa de cancelación, suscriptores activos, pruebas activas y más, las mismas métricas disponibles en [Charts](charts). Estos informes pueden llegar de forma diaria, semanal o mensual y muestran la evolución comparando el período más reciente con el anterior. Los datos que enviamos en los informes se basan en lo que hayas configurado en tu página [**Overview**](https://app.adapty.io/overview): métricas, su orden, zona horaria de los informes y tipo de ingresos. Tienes la flexibilidad de elegir el nivel de detalle que prefieras para tus informes: resumen o por app. Un informe de resumen es un único correo con información agregada de todas tus apps (o del subconjunto que hayas seleccionado). Un informe por app, en cambio, solo contendrá los datos de una app específica. Te recomendamos activar los informes de resumen para todas las apps y los informes por app para las que hayas lanzado recientemente, las de alta prioridad o aquellas de las que seas directamente responsable. Independientemente del nivel de detalle elegido, los informes por correo se entregan a las 9 AM en tu zona horaria local: los diarios llegan cada día, los semanales los lunes y los mensuales el primer día del mes. Cada informe incluye los datos actuales junto con comparaciones respecto al período anterior (por ejemplo, el informe diario de hoy compara los datos de ayer con los del día anterior; el informe semanal de hoy compara los de la semana pasada con los de la anterior, etc.). Sea cual sea el informe que selecciones, recibirás la información más actualizada y precisa directamente en tu bandeja de entrada. ## Activar informes \{#enable-reports\} 1. Abre la sección [**Account**](https://app.adapty.io/account) en el menú superior de Adapty. 2. En la sección **Email reports**, elige los tipos de informes que deseas recibir: diarios, semanales y/o mensuales. 2. Personaliza cada tipo de informe seleccionando las apps correspondientes. Para ello, haz clic en el botón **Edit**. 3. En la ventana del informe, elige las apps que quieres incluir. 4. Por último, haz clic en el botón **Save changes** para aplicar tu selección. ## Configurar tu zona horaria \{#set-your-time-zone\} 1. Abre la sección [**Overview**](https://app.adapty.io/overview) en el menú principal de Adapty. 2. Haz clic en el botón **Edit** y elige tu zona horaria. 3. Haz clic en el botón **Done** para guardar. --- # File: discrepancies-and-troubleshooting --- --- title: "Solucionar discrepancias en los datos" description: "Encuentra la causa de las divergencias en los datos" --- Los usuarios de Adapty pueden encontrar **discrepancias** al comparar conjuntos de datos similares de distintas fuentes. En particular, esto puede ocurrir cuando comparas: * Gráficos de Adapty con informes del store * Gráficos de Adapty con gráficos de terceros * Diferentes gráficos dentro de Adapty ## Algoritmo de resolución de problemas \{#troubleshooting-algorithm\} La mayoría de las discrepancias entre Adapty y otras plataformas son esperadas y normales. Ocurren porque **distintas fuentes procesan los mismos datos de forma diferente**. Otras veces, indican un **problema con tu configuración de Adapty**. Si sospechas que tus datos varían de una plataforma a otra, lo mejor es [exportar los datos brutos](export-analytics-api-requests) y **comparar los archivos**. * Incluso los stores pueden tener problemas relacionados con el procesamiento y la presentación de datos. Accede a los **datos de transacciones sin procesar** de los stores para una comparación más precisa. * Al comparar Adapty con otra plataforma de análisis, utiliza los informes de transacciones del store como fuente de verdad y punto de comparación. * Es más fácil identificar inconsistencias con un conjunto de datos limitado. Compara volúmenes pequeños de datos: céntrate en un producto específico y un solo día. * Identifica si tu discrepancia proviene de una diferencia en el **precio** o en el **número de eventos**. Los problemas de precios se pueden solucionar con una [actualización del producto](#product-pricing). Los problemas con eventos pueden indicar [problemas en el servidor](#issues-with-server-notifications-and-rtdn). * Consulta el [feed de eventos](event-feed) para monitorizar los eventos entrantes: puede que notes comportamientos inesperados. Después de identificar dónde divergen los datos, puedes revisar las siguientes causas habituales: ## Problemas con las notificaciones del servidor y RTDN \{#issues-with-server-notifications-and-rtdn\} Adapty no recibe los datos de eventos necesarios si no configuraste correctamente las conexiones con el store. Esto afecta especialmente a los eventos que ocurren sin la intervención directa del usuario: renovaciones de suscripción, problemas de facturación, etc. Completa la configuración servidor a servidor lo antes posible ([App Store](enable-app-store-server-notifications) | [Play Store](enable-real-time-developer-notifications-rtdn)) y [espera](#data-delays) a que los stores establezcan la conexión. Puedes [subir manualmente](importing-historical-data-to-adapty) los datos de App Store Connect que falten a Adapty. ## Datos ausentes \{#missing-data\} ### Usuarios con versiones desactualizadas de la app \{#users-with-out-of-date-app-versions\} Si algunos de tus usuarios utilizan una versión antigua de tu app sin el SDK de Adapty, Adapty no recibirá sus datos. Por eso, las cifras de Adapty y de otras fuentes diferirán. ### Problemas de integración \{#integration-issues\} Algunas integraciones de Adapty (por ejemplo, Adjust o AppsFlyer) requienen código adicional en la aplicación para funcionar. Si configuras el Adapty Dashboard pero no actualizas tu aplicación, los datos necesarios no aparecerán en Adapty. ### Datos históricos faltantes \{#missing-historical-data\} Adapty no tiene acceso a los datos históricos de tu aplicación, a menos que los [importes manualmente](importing-historical-data-to-adapty). Si el [rango de fechas](controls-filters-grouping-compare-proceeds#set-the-date-range) de un gráfico empieza antes de que integraras Adapty y no importaste datos históricos, sus valores diferirán de otras fuentes. ## Retrasos en los datos \{#data-delays\} Adapty aspira a ofrecer un análisis casi en tiempo real de la economía de tu aplicación. Se aplican las siguientes limitaciones y excepciones: * Cuando integras Adapty por primera vez, es posible que los datos no aparezcan de inmediato. * Cuando activas una integración con una plataforma de terceros, puede haber un retraso antes de que los datos se sincronicen por completo. * Una vez que Adapty recibe los datos del store, se necesitan otros **15-30 minutos** para procesarlos y mostrarlos en la página de Analytics. * El intercambio de datos entre Adapty y terceros **no siempre es instantáneo** debido a la cantidad de variables implicadas. * Los cálculos de algunas métricas avanzadas (como las [predicciones de cohorte](predicted-ltv-and-revenue)) requieren una cierta cantidad de datos. Adapty solo realizará estos cálculos cuando haya recopilado suficientes datos. ## Tiempo y calendario \{#time-and-calendar\} #### Fechas y zonas horarias \{#dates-and-timezones\} Una de las razones más habituales de las discrepancias en los datos es la diferencia en la configuración de zonas horarias. Adapty cuenta los días según la zona horaria `UTC`. Si otra plataforma usa una zona horaria distinta, los cálculos diferirán. La diferencia se reduce a medida que aumenta la escala. Puedes [cambiar la configuración de zona horaria](general#3-reporting-timezone) para cada aplicación. #### El calendario fiscal de Apple \{#the-apple-fiscal-calendar\} Apple utiliza su propio [calendario fiscal](https://adapty.io/apple-fiscal-calendar/) para determinar los períodos de ventas y las fechas de pago. Cada "mes" en este calendario consta de **4 o 5 semanas** y **puede incluir días de los meses del calendario adyacentes**. Los pagos se emiten normalmente entre 30 y 45 días después de que finaliza el período de ventas. Por ejemplo, el período de ventas de "enero de 2026" comienza el 28 de diciembre de 2025, es decir, 4 días antes del inicio del mes natural. La fecha de pago estimada para este período es el 5 de marzo. No compares los datos de los informes de pagos de Apple con meses del calendario. En su lugar, selecciona un [rango de fechas personalizado](controls-filters-grouping-compare-proceeds#set-the-date-range) que corresponda al período de ventas que necesitas. #### Fechas de transacción \{#transaction-dates\} Algunos servicios (por ejemplo, AppsFlyer) pueden aplicar reglas de [cohorte](analytics-cohorts) al mostrar transacciones y atribuirlas a la fecha de instalación de la aplicación, en lugar de a la fecha en que ocurrió la propia transacción. ## Cálculo de ingresos \{#revenue-calculation\} ### Tarifas e impuestos \{#fees-and-taxes\} Dependiendo de la [configuración](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue), los gráficos de Adapty pueden mostrar tus **ingresos brutos**, los **ingresos tras la comisión del store** o los **ingresos tras la comisión del store e impuestos**. Algunos stores y plataformas de terceros pueden no tener la capacidad de mostrar los ingresos brutos o deducir impuestos automáticamente. Si observas una discrepancia entre dos gráficos de ingresos distintos, asegúrate de que la comparación es válida. ### Cancelaciones y reembolsos \{#cancellations-and-refunds\} Las distintas plataformas muestran los datos de reembolsos de forma diferente. Adapty trata los reembolsos como ingresos negativos. Si un usuario se suscribe y solicita un reembolso al día siguiente, ambos eventos se reflejarán en los gráficos de Adapty, cada uno en su propio día. Otras plataformas pueden restar el importe del reembolso de la transacción original. ## Compras en sandbox \{#sandbox-purchases\} El [feed de eventos](event-feed) muestra las compras realizadas por cuentas sandbox. Los gráficos de análisis no. Sin embargo, si tus datos de importación histórica contienen compras sandbox, Adapty no podrá distinguirlas, y sus gráficos reflejarán esas compras sandbox históricas. ## Instalaciones y descargas \{#installs-and-downloads\} Los stores (especialmente Apple App Store) pueden registrar las descargas directamente. Sus estadísticas pueden incluir casos en los que la aplicación fue instalada pero nunca se abrió. Adapty solo puede registrar una instalación cuando un usuario abre la aplicación, independientemente de tu [definición de instalación](general#4-installs-definition-for-analytics). ## País y store \{#country-and-store\} Para garantizar informes precisos, Adapty [puede inferir](controls-filters-grouping-compare-proceeds#filter-and-group-data) el país del usuario a partir de su IP. Los stores siempre atribuyen las descargas y compras a un app store concreto. Si necesitas distinguir claramente entre ambos, puedes [crear un nuevo segmento de usuarios](segments) con el atributo `Country by store account` y [filtrar los análisis por segmento](controls-filters-grouping-compare-proceeds#filter-and-group-data). ## Precios del producto \{#product-pricing\} Si un precio incorrecto del producto provoca una discrepancia en los ingresos, cambiar el precio no lo corrige de forma retroactiva. Para cambiar los precios de las transacciones existentes, debes sobreescribirlos forzosamente importando los datos correctos. Cuando un usuario restaura una compra antigua tras un cambio de precio, Apple puede informar incorrectamente del valor de esa compra. Debes importar los datos históricos para que Adapty refleje el valor correcto. ## Conflictos de atribución \{#attribution-conflicts\} Adapty solo puede usar [una única fuente de atribución](attribution-integration#prevent-data-issues) para cada transacción. No es posible sobrescribir estos datos una vez asignados. Si tu configuración incluye varios proveedores de atribución que no coinciden entre sí, la misma transacción puede aparecer con dos fuentes de tráfico distintas en dos plataformas diferentes. ## Diferencias en la terminología \{#differences-in-terminology\} Las distintas plataformas pueden usar nombres diferentes para el mismo concepto. Las métricas relacionadas con los [ingresos](#fees-and-taxes) varían en nombre según la plataforma: | Adapty | App Store Connect | Google Play Console | |--------|-------------------|----------------------| | **Gross revenue** | Sales | Gross Revenue | | **Proceeds after store commission** | N/A | N/A | | **Proceeds after store commission and taxes** | Proceeds | Earnings | | **ARPPU** | Proceeds per paying user | ARPPU | Otras métricas también pueden diferir en su definición: - **Suscripciones**: - Adapty no cuenta los nuevos trials como suscripciones. Una [nueva suscripción](reactivated-subscriptions) siempre comienza con una transacción económica. - Otras plataformas, como Google Play Console, pueden contar **cada trial como una nueva suscripción**, incluso antes de que se haya realizado el primer pago. - **Retención**: - Adapty mide la retención en función del número de renovaciones de suscripción. - App Store Connect considera que un usuario es retenido si abre la aplicación en el día especificado. Un usuario sin suscripción cuenta, pero el usuario suscrito que no abrió la app ese día no. - La métrica "Retained Installers" de Google Play Console mide la retención en función del número de días que la aplicación permanece instalada en el dispositivo del usuario. Los usuarios que no abren la aplicación también cuentan para esta métrica. ## Métrica de nuevas suscripciones vs el evento `subscription_started` \{#new-subscriptions-metric-vs-the-subscription_started-event\} La métrica [Nuevas suscripciones](reactivated-subscriptions) y el evento de integración `subscription_started` [evento de integración](events) miden cosas distintas, por lo que sus totales no coinciden. La métrica cuenta tanto las primeras compras realizadas sin prueba como las conversiones de prueba a pago. El evento `subscription_started` solo se activa para las primeras compras realizadas sin prueba; cuando una prueba se convierte en pago, Adapty envía `trial_converted` en su lugar. En consecuencia, el recuento de Nuevas suscripciones es mayor que el número de eventos `subscription_started` siempre que tu app tenga conversiones de prueba. --- # File: predicted-ltv-and-revenue --- --- title: "Predicciones en cohortes" description: "Usa el análisis predictivo de Adapty para pronosticar el LTV y los ingresos." --- Las predicciones de Adapty están diseñadas para ayudarte a responder las siguientes preguntas: 1. ¿Cuál es el valor de vida (LTV) previsto de tus cohortes de usuarios? 2. ¿Qué cohortes tienen más probabilidades de generar los mayores ingresos en el futuro? 3. ¿Cuánto puedes invertir dado el retorno previsto? Con Adapty Predictions, puedes tomar decisiones basadas en datos sobre ingresos y crecimiento. El modelo de predicción de Adapty estima el potencial de ingresos a largo plazo de las cohortes de usuarios de tu app. Para cada cohorte, proyecta cómo evolucionarán los ingresos, el número de suscriptores de pago y el LTV promedio a lo largo del tiempo. Esto te ayuda a tomar decisiones informadas sobre adquisición de usuarios, estrategias de marketing y desarrollo de producto. Adapty ofrece valor de vida útil (LTV) predicho e ingresos predichos para cohortes de suscriptores de pago. Las predicciones se muestran en la página de análisis de cohortes para 3, 6, 9, 12, 18 y 24 meses después de la creación de la cohorte. Para apps con un historial muy limitado, el modelo recurre a promedios entre apps, por lo que las predicciones para apps más nuevas pueden no reflejar del todo el comportamiento específico de sus usuarios. ## Cómo funciona el modelo \{#how-the-model-works\} El modelo de predicción de Adapty utiliza patrones de retención de datos históricos de cohortes para proyectar ingresos futuros y LTV. Para cada combinación de app y tipo de suscripción, el modelo mide cómo cambian los suscriptores de pago y los ingresos totales de un período de renovación al siguiente. Calcula dos tasas de retención —una para suscriptores y otra para ingresos— basándose en las cohortes pasadas de la app. Estas tasas se aplican luego a nuevas cohortes para proyectar su evolución a los 3, 6, 9, 12, 18 y 24 meses tras la creación de la cohorte. Los datos utilizados están completamente anonimizados. El modelo genera dos valores para cada cohorte: - **Predicted revenue**: Los ingresos totales que se prevé que genere una cohorte dentro del horizonte seleccionado. - **Predicted LTV**: Los ingresos previstos divididos entre el número previsto de suscriptores de pago en la cohorte. ### Pesos específicos de la app y entre apps \{#app-specific-and-cross-app-weights\} Por defecto, las predicciones para una cohorte usan pesos de retención aprendidos de las cohortes históricas de esa misma app, lo que refleja el comportamiento específico de sus usuarios. Cuando una app no tiene suficiente historial para un horizonte de predicción concreto, Adapty recurre a pesos de retención promediados entre todas las apps del mismo tipo de suscripción. Por ejemplo, una proyección a 12 meses para una app que solo lleva seis meses activa utiliza el respaldo entre apps. Este respaldo se aplica de forma independiente por horizonte, de modo que la misma cohorte puede usar los pesos propios de la app para la predicción a 3 meses y los pesos entre apps para la predicción a 12 meses. ### Disponibilidad y actualizaciones \{#availability-and-updates\} Las predicciones están disponibles después de que una cohorte completa su primer período de renovación —normalmente una semana después de la creación para suscripciones semanales y aproximadamente cuatro semanas para suscripciones mensuales. A partir de entonces, las predicciones se actualizan diariamente con los datos transaccionales más recientes, de modo que reflejan el comportamiento actual de la cohorte. ### Limitaciones \{#limitations\} - **Calidad de los datos**: El comportamiento inusual de una cohorte o las cohortes con muy pocos suscriptores de pago reducen la precisión. Las cohortes con menos de 100 suscriptores de pago quedan excluidas de los datos de entrenamiento del modelo. - **Apps nuevas**: Las apps sin suficiente historial utilizan pesos de fallback entre apps, que puede que no reflejen el comportamiento específico de la app. - **Antigüedad de la cohorte**: Las predicciones para un horizonte determinado se ocultan cuando la cohorte supera ese horizonte. Por ejemplo, las predicciones a 3 meses dejan de mostrarse después de tres meses, y no se muestran predicciones para cohortes con más de 24 meses de antigüedad. ## En el Dashboard \{#in-the-dashboard\} Para ver las predicciones, ve a la página de análisis de cohortes en tu Adapty Dashboard. Para más detalles sobre las cohortes, consulta [Análisis de cohortes](analytics-cohorts). <img src="/assets/shared/img/4d808b4-Export-1691486610612.gif" alt="Página de análisis de cohortes mostrando las columnas Predicted Revenue y Predicted LTV" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> La columna **Predicted revenue** muestra los ingresos totales estimados que se espera que genere una cohorte de suscriptores durante el período de tiempo seleccionado tras su creación. Este valor se calcula mediante el modelo de predicción de Adapty, basado en los patrones históricos de retención de cohortes de la app. La columna **Predicted LTV** muestra el valor de vida estimado de cada usuario en la cohorte seleccionada. Este valor se calcula dividiendo los ingresos predichos entre el número predicho de usuarios de pago en la cohorte. ### Seleccionar el horizonte \{#select-the-horizon\} Para cambiar el horizonte de predicción, selecciona un valor en el desplegable **Predictions**. Las opciones disponibles son 3, 6, 9, 12, 18 y 24 meses desde la creación de la cohorte. ### Filtrar por producto \{#filter-by-product\} Puedes filtrar los ingresos proyectados y el LTV por producto. Por defecto, las predicciones se construyen a partir de todos los datos de compra; filtrar por producto muestra la contribución de cada producto. <img src="/assets/shared/img/66a9c61-Export-1691486288948.gif" alt="Análisis de cohortes filtrado por producto" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Cuándo no hay predicciones disponibles \{#when-predictions-are-unavailable\} Cuando no se puede generar una predicción para una cohorte, las columnas Predicted Revenue y Predicted LTV muestran guiones largos (—) en lugar de valores. Esto puede ocurrir por varias razones: - **Tiempo insuficiente desde la creación de la cohorte**: Las predicciones solo están disponibles una vez que la cohorte completa su primer período de renovación: aproximadamente una semana para las suscripciones semanales y unas cuatro semanas para las mensuales. - **Tamaño de cohorte pequeño**: Hay muy pocos suscriptores de pago para generar una proyección fiable. - **Comportamiento inusual de la cohorte**: La cohorte se desvía significativamente de los patrones que espera el modelo. Esperar algunas semanas puede resolver esto a medida que se acumulan más datos. - **Horizonte superado**: La cohorte es más antigua que el horizonte de predicción seleccionado. Por ejemplo, la predicción a 3 meses se oculta tras tres meses, la predicción a 12 meses tras doce meses, y no se muestran predicciones para cohortes con más de 24 meses de antigüedad. :::warning Al activar las predicciones, ten en cuenta que puede haber un retraso máximo de 24 horas antes de que los datos de predicción de Revenue y LTV estén disponibles en tu Adapty Dashboard. ::: --- # File: predictions-in-ab-tests --- --- title: "Predicciones en pruebas A/B" description: "Aprende cómo las predicciones en las pruebas A/B ayudan a perfeccionar las estrategias de precios de suscripción." --- Bienvenido a la documentación de Análisis Predictivo de Adapty para nuestra funcionalidad de pruebas A/B. Esta herramienta te proporcionará información sobre los resultados futuros de tus pruebas A/B en curso y te ayudará a tomar decisiones basadas en datos más rápidamente 🚀 con las predicciones potenciadas por ML de Adapty. ### ¿Qué son las predicciones en pruebas A/B? \{#what-are-ab-test-predictions\} Las predicciones de pruebas A/B de Adapty utilizan técnicas avanzadas de aprendizaje automático (concretamente modelos de gradient boosting) para pronosticar el potencial de ingresos a largo plazo de los paywalls que se comparan en una prueba A/B. Este modelo predictivo te permite seleccionar el paywall más efectivo basándote en los ingresos proyectados al cabo de un año, en lugar de basarte únicamente en las métricas que observas mientras la prueba está en curso. Esto te permite decidir el ganador de forma más fiable y rápida, sin tener que esperar semanas a que se acumulen los datos. ### ¿Cómo funciona el modelo? \{#how-does-the-model-work\} El modelo se entrena con un amplio historial de datos de pruebas A/B procedentes de una gran variedad de apps en distintas categorías. Incorpora un amplio conjunto de características para predecir los ingresos que es probable que genere un paywall en el año siguiente al inicio del experimento. Estas características incluyen: - Transacciones de usuarios y tasas de conversión en diferentes períodos - Distribución geográfica de los usuarios - Plataforma de uso (iOS o Android) - Tasas de cancelación y reembolso - Productos de suscripción y sus duraciones (diaria, mensual, anual, etc.) - Otros datos relacionados con transacciones El modelo también tiene en cuenta los períodos de prueba en los paywalls, utilizando tasas de conversión históricas para predecir los ingresos como si los usuarios ya hubieran convertido. Esto garantiza una comparación justa entre paywalls con y sin ofertas de prueba, ya que también se tienen en cuenta las pruebas activas que potencialmente podrían generar ingresos en el futuro. ### ¿En qué se diferencia el P2BB Predicho del P2BB normal? \{#how-is-predicted-p2bb-different-from-just-the-p2bb\} Nuestras pruebas A/B utilizan el enfoque bayesiano: básicamente modelamos la distribución de los ingresos por usuario (o "Ingresos por cada 1.000 usuarios", para ser más precisos) y luego calculamos la probabilidad de que una distribución sea "realmente" mejor que la otra y no por pura casualidad — a esto lo llamamos Probabilidad de ser el mejor o P2BB (puedes obtener más información sobre nuestro enfoque [aquí](maths-behind-it)). Es importante tener en cuenta que al hacer esto, nos basamos únicamente en los ingresos que se han acumulado durante el tiempo que lleva ejecutándose la prueba. Por tanto, si quisieras realizar una prueba comparando una suscripción anual con una semanal, tendrías que esperar mucho tiempo para entender realmente cuál rinde mejor. Algo similar ocurre cuando comparas suscripciones con período de prueba frente a suscripciones sin período de prueba en una prueba A/B — ya que las pruebas activas que podrían potencialmente cambiar la dinámica del ganador nunca se tienen en cuenta en los ingresos. Aquí es donde entra en juego nuestro modelo predictivo. Con la distribución de ingresos actual de una prueba A/B y entrenado sobre un amplio conjunto de datos, es capaz de predecir la versión futura de la distribución de ingresos (concretamente tras 1 año). Y tras hacerlo, produce un P2BB predicho — el que obtendrías si ejecutaras la prueba durante todo el año. Ten en cuenta que a veces el P2BB predicho puede contradecir el P2BB actual. Cuando esto ocurre, resaltamos las filas de variación en amarillo, así: <img src="/assets/shared/img/74577c6-CleanShot_2024-02-15_at_13.08.452x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Consideramos que esto es una señal de que deberías acumular más datos para confirmar el ganador o profundizar en la prueba A/B para averiguar la causa. En general, recomendamos confiar en el P2BB predicho sobre el P2BB actual porque simplemente tiene en cuenta más datos, aunque la decisión final es, por supuesto, tuya. ### Precisión y certeza del modelo \{#model-accuracy-and-certainty\} El modelo alcanza un alto nivel de precisión, con un Error Porcentual Absoluto Medio (MAPE) ligeramente inferior al 10%. Este nivel de precisión permite a las empresas confiar en las predicciones del modelo al tomar decisiones basadas en datos. Para garantizar aún más la estabilidad, el modelo emplea un criterio de "certeza" basado en tres factores: - Un intervalo de predicción estrecho: el modelo tiene confianza en su resultado - Una cantidad suficiente de suscripciones e ingresos en la prueba - Han transcurrido al menos 2 semanas desde el inicio de la prueba Una predicción se considera fiable cuando se cumplen al menos dos de estos tres criterios. Cuando comienza una nueva prueba A/B, el modelo proporciona una predicción de ingresos por 1.000 usuarios a un año vista (nuestra métrica principal en pruebas A/B) para cada paywall. Las predicciones solo se muestran cuando cumplen los criterios de certeza. Si los datos son insuficientes, el modelo indicará "datos insuficientes para la predicción". ### Limitaciones y consideraciones \{#limitations-and-considerations\} Aunque nuestro modelo predictivo es una herramienta potente, es importante tener en cuenta sus limitaciones. El rendimiento del modelo depende de la calidad y representatividad de los datos disponibles. El comportamiento inusual de una cohorte o las apps nuevas que no están incluidas en el conjunto de entrenamiento pueden afectar a la precisión de las predicciones. No obstante, las predicciones se actualizan diariamente para reflejar los datos y comportamientos de usuario más recientes. Esto garantiza que la información que recibes siempre se basa en los datos más actuales. 🚧 Nota: Esta herramienta es un complemento, no un sustituto, de tu criterio experto y tu comprensión de la dinámica particular de tu app. Utiliza estas predicciones como guía junto con otras métricas y el conocimiento del mercado para tomar decisiones fundamentadas. --- # File: adapty-ads-manager --- --- title: "Adapty Ads Manager" description: "Obtén análisis en tiempo real de Apple Ads y gestiona y optimiza tus campañas" --- **Adapty Ads Manager** es una plataforma todo en uno diseñada para ayudarte a gestionar, optimizar y escalar tus campañas de Apple Ads de manera más eficiente. Conecta el rendimiento de tus Apple Search Ads con métricas de ingresos clave como instalaciones, trials, suscripciones y valor de vida útil sin necesitar un MMP. Con análisis en tiempo real, predicciones impulsadas por IA y automatización inteligente, Adapty Ads Manager elimina los tediosos cambios manuales de pujas, las hojas de cálculo y las suposiciones, y los reemplaza con información clara y herramientas que te ayudan a actuar más rápido. Con Adapty Ads Manager, obtienes: - **[Resumen](ads-manager-overview)**: Todas las métricas clave de un vistazo — gasto, ingresos, ROAS, CPA y más — cada una con un gráfico de tendencia diaria - **[Agente de IA](ads-manager-ai-agent)**: Haz preguntas en lenguaje natural y obtén respuestas y recomendaciones de embudo completo - **Datos de rendimiento en tiempo real**: A lo largo de campañas, grupos de anuncios y palabras clave - **Seguimiento de ingresos de extremo a extremo**: Desde búsqueda → instalación → prueba → suscripción → LTV - **Predicciones y recomendaciones de IA**: Para un escalado rentable - **Gestión masiva**: De pujas, presupuestos, estados y estructuras - **[Automatizaciones basadas en reglas](ads-manager-automations)**: Gestiona el ciclo de vida completo de las palabras clave - **[Market Intelligence](ads-manager-market-intelligence)**: Estrategias de palabras clave de la competencia en más de 50 países - **[Pruebas A/B de CPP](ads-manager-cpp-ab-tests)**: Compara páginas de producto personalizadas entre sí y encuentra la que mejor funciona <CustomDocCardList ids={['adapty-ads-manager-get-started', 'ads-manager-overview', 'ads-manager-ai-agent', 'adapty-ads-manager-analytics', 'ads-manager-create-campaign', 'ads-manager-create-ad-group', 'ads-manager-manage-keywords', 'ads-manager-automations', 'ads-manager-market-intelligence', 'ads-manager-cpp-ab-tests']} /> ## ¿Por qué elegir Adapty Ads Manager? \{#why-choose-adapty-ads-manager\} Porque te ofrecemos **los datos más precisos del mercado.** A diferencia de la consola nativa de Apple Ads o los MMP, nuestros datos son **en tiempo real, sin pérdidas y completamente conectados** a trials, suscripciones y LTV — sin retrasos ni brechas de atribución. Con su fácil implementación y una experiencia de usuario fluida, puedes gestionarlo todo en un solo lugar sin tener que cambiar entre múltiples herramientas. ## Primeros pasos \{#get-started\} Para empezar con Adapty Ads Manager, sigue la [guía](adapty-ads-manager-get-started) y ya estarás listo para explorar --- # File: adapty-ads-manager-get-started --- --- title: "Primeros pasos con Adapty Ads Manager" description: "Importa tus datos históricos de Apple Ads y empieza a recibir actualizaciones en tiempo real en el dashboard" --- [Adapty Ads Manager](adapty-ads-manager) es tu plataforma de optimización y análisis para Apple Ads. En esta guía aprenderás a empezar a trabajar con Adapty Ads Manager en dos pasos: 1. Instala el SDK de Adapty y deja que registre los datos de tus compras. 2. Conecta Adapty Ads Manager a tu cuenta de Apple Ads para importar tus datos históricos y empezar a registrar actualizaciones en tiempo real. :::note Adapty Ads Manager no utiliza la [integración de Apple Ads](apple-search-ads) de **App settings**. Para usar Adapty Ads Manager, solo necesitas completar la configuración descrita en esta guía. ::: ## 1. Instala el SDK de Adapty \{#1-install-the-adapty-sdk\} :::important Adapty Ads Manager es un **producto independiente**. Puedes usarlo aunque tus paywalls, suscripciones o analíticas no estén gestionados por Adapty: no es necesario migrar toda tu infraestructura a Adapty. Para obtener datos de ingresos precisos, la configuración mínima consiste en instalar el SDK de Adapty en modo observador y activar las notificaciones de servidor de App Store en Adapty. ::: Para conectar tus datos de ingresos con el rendimiento de las campañas, deja que Adapty registre tus compras: 1. El primer paso depende de si ya tienes compras in-app implementadas: - Si **ya tienes compras in-app implementadas con Adapty**, no necesitas hacer nada más en esta etapa. - Si **ya tienes compras in-app implementadas sin Adapty** y no planeas migrar a Adapty, instala el SDK para tu plataforma en modo observador. En esta etapa solo necesitas añadir el SDK a tu proyecto, activarlo con el modo observador habilitado y reportar las transacciones: - [iOS](implement-observer-mode) - [Android](implement-observer-mode-android) - [React Native](implement-observer-mode-react-native) - [Flutter](implement-observer-mode-flutter) - [Unity](implement-observer-mode-unity) - [Kotlin Multiplatform](implement-observer-mode-kmp) - [Capacitor](implement-observer-mode-capacitor) - Si **todavía no tienes compras in-app implementadas y quieres usar Adapty**, completa los pasos de la [guía de inicio rápido](quickstart) para delegar la gestión de compras a Adapty. 2. Para recibir actualizaciones relacionadas con ingresos directamente desde la App Store, [habilita las notificaciones del servidor de App Store en Adapty](enable-app-store-server-notifications). ## 2. Conectar Apple Ads \{#connect-apple-ads\} :::important Necesitas tener el rol **Account Admin** en Apple Ads para conectar Apple Ads a Adapty. ::: Ahora, tienes que conectar tu cuenta de Adapty Ads Manager con tu cuenta de Apple Ads: 1. Haz clic en el logo de Adapty en la cabecera y elige **Search Ads**. 2. Haz clic en **Continue with Apple**. 3. Inicia sesión en tu cuenta de Apple. 4. Selecciona qué acceso quieres conceder a Adapty Ads Manager: - **Read and Write**: Proporciona acceso a todos los grupos de campañas. - **Limited access**: Elige grupos de campañas específicos y asigna el rol **Read & Write** para conceder acceso solo a esos grupos. 5. Haz clic en **Grant access**. Después de esto, Adapty comenzará a sincronizar tus datos históricos de Apple Ads. Ya puedes empezar a explorar Adapty Ads Manager, aunque tardará un tiempo hasta que todos los datos históricos se hayan importado. ## Próximos pasos \{#whats-next\} Una vez que hayas sincronizado correctamente tus datos de transacciones, continúa aprendiendo cómo: - [Gestionar tus campañas, grupos de anuncios y palabras clave](ads-manager) - [Configurar reglas de automatización para ajustar pujas según el rendimiento de las campañas](ads-manager-automations) --- # File: ads-manager-overview --- --- title: "Resumen en Adapty Ads Manager" description: "Ve todas tus métricas clave de Apple Ads en un solo lugar, cada una con un gráfico de tendencia." --- La página **Overview** muestra todas las métricas clave de Apple Ads en un solo lugar, cada una con un gráfico de tendencia. Por defecto, muestra datos de todas las apps conectadas. Para ver una sola app, selecciónala en el desplegable de apps del encabezado. Para abrirlo, ve a **Overview** en la barra lateral izquierda de Adapty Ads Manager. :::tip Para obtener un resumen de lo que necesita tu atención en lugar de revisar las métricas manualmente, consulta el [Agente de IA](ads-manager-ai-agent). ::: ## Métricas \{#metrics\} Cada métrica aparece como una tarjeta con un gráfico de tendencia para el rango de fechas seleccionado. Para ver las definiciones y fórmulas de las métricas, consulta [Métricas en Apple Ads Manager](adapty-ads-manager-metrics). ## Configurar las métricas mostradas \{#configure-displayed-metrics\} Para cambiar qué métricas aparecen en la página **Overview**, haz clic en **Edit metrics**. Desde ahí puedes: - **Añadir una métrica**: Haz clic en **Add metric** y marca las casillas de las métricas que quieras. - **Eliminar una métrica**: Desmarca su casilla en el panel **Add metric**, o haz clic en **×** junto a ella. ## Controles \{#controls\} Usa los controles de la parte superior para ajustar lo que muestra la página **Overview**: - **Rango de fechas**: Elige un período preestablecido (**Last 7 days**, **Last 30 days**, **Last 90 days**) o introduce un rango personalizado. Todos los gráficos y valores de resumen se actualizan según el período seleccionado. - **Tipo de gráfico**: Cambia entre vistas de columna apilada, línea y gráfico circular. - **Visualización de ingresos**: Elige cómo se calculan las métricas de ingresos: - **Gross revenue**: Ingresos totales antes de cualquier deducción. - **Proceeds after store commission**: Ingresos después de deducir la comisión de Apple. - **Proceeds after store commissions and taxes**: Ingresos netos después de deducir tanto la comisión de Apple como los impuestos aplicables. --- # File: ads-manager-ai-agent --- --- title: "Agente IA en Adapty Ads Manager" description: "Haz preguntas en lenguaje natural sobre tu cuenta de Apple Ads y obtén respuestas y recomendaciones sobre todo el funnel." --- El Agente IA es un asistente de chat en Adapty Ads Manager que responde preguntas sobre tu cuenta de Apple Ads en lenguaje natural. Se basa en todo tu funnel — impresión, instalación, prueba, suscripción e ingresos — para razonar sobre revenue y ROAS, no solo sobre clics. La atribución integrada de Adapty pone estos datos del funnel a disposición del agente casi en tiempo real. El agente es orientativo: analiza tu cuenta y te recomienda qué hacer, pero no modifica campañas, pujas ni presupuestos por ti. ## Qué puedes preguntar \{#what-you-can-ask\} Pregúntale al agente sobre cualquier parte de tu cuenta de Apple Ads. El agente puede: - **Resumen de cuenta**: Resume lo que está pasando en tu cuenta y qué necesita atención primero. - **Objetos con bajo rendimiento**: Encuentra campañas que no están dando resultados y palabras clave que no generan conversiones. - **Presupuesto**: Identifica campañas que han alcanzado su límite de presupuesto diario y aconseja si conviene aumentarlo. - **Decisiones de puja y pausa**: Recomienda si subir una puja o mantenerla, y si pausar una campaña o seguir ejecutándola. - **Rendimiento geográfico**: Muestra qué países rinden por encima o por debajo de la media y dónde reasignar el presupuesto. ## Ejemplos de preguntas \{#example-questions\} El agente da las respuestas más útiles cuando indicas una métrica, un período de tiempo y la decisión que estás evaluando. Por ejemplo: - ¿Qué palabras clave tienen el ROAS más alto y cómo debería redistribuir el presupuesto hacia ellas? - ¿Qué palabras clave tienen un gasto elevado pero sin trials ni suscripciones en los últimos 30 días, y cuáles debería pausar? - ¿Qué campañas han alcanzado su límite de presupuesto diario manteniéndose rentables, y cuánto debería aumentar cada una? - Compara mis países por ROAS y coste por suscripción: ¿dónde debería mover el presupuesto? - ¿Debería bajar la puja de esta palabra clave, pausarla o darle más tiempo? Muéstrame los datos del embudo que respaldan tu respuesta. - ¿Qué palabras clave generan instalaciones baratas que rara vez se convierten en suscripciones de pago? ## Abrir el agente de IA \{#open-the-ai-agent\} Para abrir el agente, haz clic en **Ask AI Agent** en la cabecera de la cuenta. Antes de hacer cualquier pregunta, selecciona una app para definir el ámbito del agente. Luego escribe tu pregunta y envíala. El agente ejecuta las tareas en segundo plano, así que puedes seguir trabajando en Adapty Ads Manager mientras prepara la respuesta. Para cambiar qué modelo responde tus preguntas, usa el selector que aparece junto al campo de entrada de mensajes. ## Acceder a conversaciones anteriores \{#access-previous-chats\} El panel compacto muestra únicamente tu conversación actual. Para revisar chats anteriores, haz clic en el botón de expansión en la parte superior del panel para entrar en modo pantalla completa. Se abrirá una lista de **Chats** a la izquierda, donde puedes buscar conversaciones pasadas o iniciar una nueva con **New chat**. ## Limitaciones \{#limitations\} El Agente de IA tiene carácter consultivo. Recomienda acciones, pero no las aplica por ti. Revisa sus recomendaciones y aplica los cambios tú mismo [al gestionar campañas](ads-manager-create-campaign) y [palabras clave](ads-manager-manage-keywords). --- # File: adapty-ads-manager-metrics --- --- title: "Métricas en Apple Ads Manager" description: "Consulta el análisis de la app en Apple Ads Manager." --- Apple Ads Manager ofrece métricas completas para medir el rendimiento de las campañas y el comportamiento de los usuarios. ## Rendimiento \{#performance\} | Métrica | Descripción | |--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Spend | La suma del coste de cada toque de un cliente en tu anuncio. | | Impressions | El número de veces que tu anuncio patrocinado apareció en los resultados de búsqueda de App Store durante el período de informe. | | CPM | El importe medio que pagas por cada mil impresiones del anuncio. CPM medio = Gasto / (Impresiones / 1000) Nota: para campañas de resultados de búsqueda de App Store con modelo de precios CPT, muestra el CPM efectivo. | | Taps | El número de veces que los usuarios tocaron el anuncio durante el período de informe. | | CPT | El importe medio que pagas por cada toque en tu anuncio. CPT medio = Gasto / Toques | | TTR | El número de veces que los clientes tocaron tu anuncio dividido por el total de impresiones recibidas. TTR = Toques / Impresiones * 100% | | Downloads (Total) | El número total de nuevas descargas y redescargas por toque y por visualización de un anuncio durante el período de informe. | | Downloads (View-Through) | El número de descargas y redescargas de usuarios que vieron tu anuncio en una ventana de 24 horas pero no lo tocaron. | | Downloads (Tap-Through) | El número total de nuevas descargas y redescargas de usuarios que tocaron tu anuncio en una ventana de 30 días. | | Avg CPA (Total) | El coste por adquisición (CPA) total medio es el gasto total de la campaña dividido por el total de descargas resultantes de una visualización o un toque en tu anuncio durante el período de informe. | | Avg CPA (Tap-Through) | El coste por adquisición (CPA) medio por toque es el gasto total de la campaña dividido por el número de descargas por toque durante el período de informe. | | Download Rate (Total) | El total de descargas resultantes de una visualización o un toque en tu anuncio dividido por el número total de toques durante el período de informe. Fórmula: (Descargas totales / Toques) × 100% si Toques > 0, en caso contrario 0% | | Download Rate (Tap-Through) | El total de descargas resultantes de toques en tu anuncio dividido por el número total de toques durante el período de informe. Fórmula: (Descargas por toque / Toques) × 100% si Toques > 0, en caso contrario 0% | | DPM (Total) | Descargas por mil (DPM) es el número de descargas por cada mil impresiones. Fórmula: (Descargas totales / Impresiones) × 1000 si Impresiones > 0, en caso contrario 0 | ## Conversiones \{#conversions\} :::note Los ingresos, ARPU, ARPPU, ARPAS, ROAS y ROI también están disponibles como métricas de cohorte para el análisis temporal de grupos de usuarios. ::: | Métrica | Descripción | |--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Conversions | Conversions es el número total de eventos de conversión en el período del informe. Fórmula: Trials started + Subscriptions started + Non-subscriptions | | Conversion CR | Conversion CR (Tasa de conversión) es el porcentaje del total de descargas que resultaron en una conversión. Fórmula: (Conversions / Total Downloads) × 100% si Total Downloads > 0, de lo contrario 0% | | Cost per Conversion | Cost per Conversion es el gasto total dividido entre el número de conversiones. Fórmula: Spend / Conversions si Conversions > 0, de lo contrario 0 | | Revenue | Revenue es el importe total generado por compras, renovaciones u otras conversiones monetizadas en tu app dentro del período seleccionado (antes de la comisión del store). | | ROAS | ROAS (Return on Ad Spend) es el revenue generado por tus anuncios dividido entre el gasto publicitario, expresado como porcentaje. Fórmula: (Revenue / Spend) × 100% si Spend > 0, de lo contrario 0% | | ROI | ROI (Return on Investment) mide el beneficio neto en relación con el gasto. Fórmula: ((Revenue − Spend) / Spend) × 100% si Spend > 0, de lo contrario 0% | | ARPU | ARPU (Average Revenue per User) es el revenue medio por usuario. Se calcula como el revenue total dividido entre el número de usuarios únicos. $60 000 de revenue / 5000 usuarios = $12 de ARPU. Es útil comparar este valor con el coste por instalación (CPI) para entender la efectividad de tus campañas de marketing. | | ARPPU | ARPPU (Average Revenue per Paying User) es el revenue medio por usuario de pago. Se calcula como el revenue total dividido entre el número de usuarios de pago únicos. $60 000 de revenue / 1000 usuarios de pago = $60 de ARPPU. Te ayuda a entender cuánto dinero genera de media un cliente de pago. | | ARPAS | ARPAS es el revenue medio por suscriptor activo. Se calcula como revenue total / número de suscriptores activos. Por suscriptores entendemos aquellos que han activado un período de prueba o una suscripción. $60 000 de revenue / 1500 suscriptores = $40 de ARPAS. | | Installs | Installs es el número total de usuarios que han instalado la app por primera vez, así como cualquier reinstalación por parte de usuarios existentes. Esto incluye múltiples instalaciones del mismo usuario en distintos dispositivos. Ten en cuenta que las descargas incompletas o las instalaciones canceladas antes de finalizar no se contabilizan. | | Installs CR | Installs CR (Tasa de conversión) es el porcentaje de usuarios del total de descargas que instalaron la app. Fórmula: (Installs / Total Downloads) × 100% si Total Downloads > 0, de lo contrario 0% | | CPI | CPI (Cost per Install) es el coste por instalación registrado por Adapty. Fórmula: Spend / Installs si Installs > 0, de lo contrario 0 | | Trials | Trials es el número de nuevas suscripciones de prueba iniciadas durante el período del informe. | | Trial CR | Trial CR (Tasa de conversión) es el porcentaje del total de descargas que iniciaron una prueba. Fórmula: (Trials / Total Downloads) × 100% si Total Downloads > 0, de lo contrario 0% | | Cost per Trial | Cost per Trial es el gasto total dividido entre el número de nuevos inicios de prueba. Fórmula: Spend / Trials si Trials > 0, de lo contrario 0 | | Trials converted | Trials converted es el número de suscripciones de prueba que se convirtieron en suscripciones de pago dentro del período del informe. | | Trial converted CR | Trial converted CR (Tasa de conversión) es el porcentaje de suscripciones de prueba que se convirtieron en suscripciones de pago. Fórmula: (Trials converted / Trials) × 100% si Trials > 0, de lo contrario 0% | | Cost per Trial converted | Cost per Trial converted es el gasto total dividido entre el número de pruebas convertidas en el mismo período. Fórmula: Spend / Trials converted si Trials converted > 0, de lo contrario 0 | | Subscriptions | Subscriptions es el número total de nuevas altas de suscripción (sin período de prueba) en el período del informe. | | Subscription CR | Subscription CR (Tasa de conversión) es el porcentaje del total de descargas que inician una suscripción de pago (sin prueba gratuita). Fórmula: (Subscriptions / Total Downloads) × 100% si Total Downloads > 0, de lo contrario 0% | | Cost per Subscription | Cost per Subscription es el gasto total dividido entre el número de nuevas suscripciones iniciadas. Fórmula: Spend / Subscriptions si Subscriptions > 0, de lo contrario 0 | | Non-subscriptions started | Non-subscriptions started es el número total de compras in-app únicas que no son suscripciones. | | Non-subscription CR | Non-subscription CR (Tasa de conversión) es el porcentaje del total de descargas que realizaron una compra sin suscripción en tu app. Fórmula: (Non-subscriptions started / Total Downloads) × 100% si Total Downloads > 0, de lo contrario 0% | | Cost per Non-subscription | Cost per Non-subscription es el gasto total dividido entre el número de compras sin suscripción. Fórmula: Spend / Non-subscriptions started si Non-subscriptions started > 0, de lo contrario 0 | ## Descargas avanzadas \{#advanced-downloads\} | Métrica | Descripción | |--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Nuevas descargas (total) | El número total de nuevas descargas por clic y por visualización dentro del período de informe. | | Nuevas descargas (por visualización) | Nuevas descargas de usuarios que vieron tu anuncio pero no hicieron clic en él y no habían descargado tu app anteriormente. Se contabilizan en una ventana de 24 horas. | | Nuevas descargas (por clic) | Nuevas descargas de usuarios que hicieron clic en tu anuncio y no habían descargado tu app anteriormente. Se contabilizan en una ventana de atribución de 30 días. | | Proporción de nuevas descargas (por clic) | Muestra qué porcentaje del total de descargas por clic corresponde a nuevas descargas (de usuarios que hicieron clic en tu anuncio dentro de una ventana de atribución de 30 días). | | Recargas (total) | El número total de recargas por clic y por visualización dentro del período de informe. | | Recargas (por visualización) | Recargas de usuarios que vieron tu anuncio pero no hicieron clic en él dentro de una ventana de 24 horas. Se contabilizan cuando un usuario descarga tu app, la elimina y la vuelve a descargar en el mismo dispositivo o en uno diferente después de ver un anuncio. | | Recargas (por clic) | Recargas de usuarios que hicieron clic en tu anuncio dentro de una ventana de atribución de 30 días. Se contabilizan cuando un usuario descarga tu app, la elimina y la vuelve a descargar en el mismo dispositivo o en uno diferente después de hacer clic en un anuncio. | | Proporción de recargas (por clic) | Muestra el porcentaje del total de descargas por clic que corresponde a recargas. | ## Información \{#insights\} | Métrica | Descripción | |--------|------------------------------------------------------------------------------------------------------------------------------------------| | Impression Share | Impression Share es el porcentaje de impresiones que recibió tu anuncio en comparación con el total de impresiones para el mismo término de búsqueda. | | Rank | Rank (actual) es la posición actual de tu app en términos de cuota de impresiones para el término de búsqueda seleccionado en un país o región específicos. | | Search Popularity | Search Popularity (actual) es la popularidad de los términos de búsqueda según el país o la región. La clasificación va de 1 a 5, donde 5 indica el mayor volumen de búsqueda. | --- # File: ads-manager-create-campaign --- --- title: "Gestionar campañas en Adapty Ads Manager" description: "Crea y edita campañas de Apple Ads en Adapty Ads Manager." --- Adapty Ads Manager tiene una integración bidireccional con Apple Ads: obtienes datos de rendimiento en tiempo casi real y puedes crear y editar campañas directamente desde el Adapty Dashboard de una forma mucho más cómoda que en la interfaz nativa. Si creas una campaña en el dashboard nativo de Apple Ads, aparecerá automáticamente en Adapty Ads Manager en un plazo de 24 horas. Además de [explorar métricas completas de campaña](adapty-ads-manager-analytics), puedes gestionar todos los ajustes de la campaña: - Crear campañas - Editar campañas existentes - Lanzar y pausar campañas :::tip Para encontrar campañas que necesiten atención —como campañas que han alcanzado su límite de presupuesto diario o que no están siendo rentables— consulta al [Agente de IA](ads-manager-ai-agent). ::: ## ¿Qué es una campaña? \{#what-is-a-campaign\} Una campaña se centra en una sola aplicación y muestra anuncios en un placement en el App Store. Cada campaña incluye un presupuesto diario y [grupos de anuncios](ads-manager-create-ad-group) enfocados en una estrategia específica para promocionar tu aplicación. La campaña seguirá gastando según la configuración de su presupuesto. :::important Ten en cuenta que una campaña no puede funcionar por sí sola; los grupos de anuncios son el nivel donde se configuran la puja predeterminada, la audiencia y las palabras clave. Sin un grupo de anuncios, una campaña no tiene segmentación ni pujas y no se publicará. Crea la campaña y luego [añade al menos un grupo de anuncios](ads-manager-create-ad-group) para activarla. ::: ## Crear campañas \{#create-campaigns\} Para crear una campaña: 1. Abre la página **Ads Manager** y haz clic en **+**. Selecciona **Create campaign** para iniciar el asistente de campaña. 2. Elige un **placement** para tu anuncio y haz clic en **Start**: | Placement | Dónde aparece tu anuncio | | --- | --- | | **Search Results** | En la parte superior de los resultados de búsqueda de la App Store. | | **Search Tab** | En la lista de apps sugeridas de la pestaña Buscar, antes de que el usuario realice una búsqueda. | | **Today Tab** | En la página Today de la App Store. | | **Product Pages** | En la lista **You Might Also Like** de las páginas de otros productos. Apple selecciona las páginas relevantes por ti. | | **Duplicate a Campaign** | Reutiliza el placement, los grupos de anuncios y las palabras clave de una campaña existente. | 3. Para las campañas de **Search Result campaigns**, elige el **campaign type**. Agrupar las campañas por tipo organiza los datos de tu informe y permite que Adapty sugiera una estrategia de palabras clave adecuada. :::note Elige **Max Conversions** para omitir la selección manual de palabras clave y dejar que la puja automatizada de Apple optimice las conversiones. ::: | Tipo de campaña | Descripción | | --- | --- | | **Generic** | Términos no relacionados con ninguna marca que describen lo que hace tu app. | | **Competitor** | Palabras clave de marca de tus competidores. | | **Discovery** | Search Match muestra nuevas palabras clave automáticamente para que puedas aprovechar las ganadoras. | | **Max Conversions** | Puja automatizada de Apple optimizada para conversiones. Establece el **Target CPA** en el paso de configuración. | | **Brand** | Palabras clave asociadas con la marca de tu app. | | **Custom** | Sin estrategia predefinida. Crea la campaña desde cero. | 4. Selecciona la **app** (lo que quieres promocionar) y el **campaign group** (la cuenta de Apple Ads que gestiona y paga la campaña). 5. Selecciona los **países o regiones** a los que quieres llegar. Para facilitar la optimización, usa una campaña por país. 6. Configura los **ajustes básicos** de la campaña. La segmentación de audiencia y las palabras clave se gestionan en los [ad groups](ads-manager-create-ad-group) de la campaña, que añades después de crearla. | Configuración | Descripción | | --- | --- | | **Campaign name** | Se rellena automáticamente con el nombre de la app, el placement y el país. Puedes editarlo en cualquier momento. | | **Daily budget** | El importe máximo que puede gastar la campaña al día. | | **Target CPA** | El objetivo de coste por adquisición para el sistema de pujas automatizado. Solo aparece en campañas de tipo Max Conversions. | | **Ad scheduling** | Opcional. La campaña comienza de inmediato salvo que establezcas una fecha de inicio posterior; añade una fecha de fin para detenerla en un momento concreto. | Las cuentas con línea de crédito también completan los detalles de facturación en este paso. 7. Revisa el resumen, confirma los detalles y haz clic en **Create campaign**. 8. Continúa para [configurar el grupo de anuncios](ads-manager-create-ad-group). Las campañas no pueden ejecutarse sin grupos de anuncios: estos determinan la audiencia y/o las palabras clave. ## Editar campañas \{#edit-campaigns\} Para editar cualquier campaña creada: 1. Abre la configuración de la campaña con cualquiera de estos métodos: - Haz clic en el nombre de la campaña en **Ads Manager > Campaigns**. Luego, haz clic en **Edit campaign** en la parte superior derecha. - O selecciona la casilla junto al nombre de la campaña y haz clic en **Actions > Edit campaign settings**. 2. Ajusta la configuración de la campaña. Puedes editar el nombre de la campaña, el tipo de campaña (para campañas de resultados de búsqueda), los países y el presupuesto diario. Para cambiar la ubicación del anuncio, la estrategia de puja o la programación, crea una nueva campaña. 3. Haz clic en **Save changes**. También puedes cambiar el tipo de una campaña directamente en la columna **Campaign type** de la tabla de campañas. :::note Los cambios realizados directamente en Apple Ads se sincronizan automáticamente con Adapty Ads Manager, aunque pueden tardar un tiempo en aparecer. ::: ## Exportar campañas \{#export-campaigns\} Para exportar la tabla de campañas como CSV, haz clic en el icono de descarga que aparece encima de la tabla y selecciona **Export current page** o **Export all pages**. **Export all pages** descarga todas las campañas de todas las páginas en un único archivo. Una ventana de progreso muestra el estado de la descarga, y puedes cancelarla en cualquier momento. Hay dos filtros opcionales disponibles: - **Enabled only**: incluye solo las campañas activas. - **With spend ≥**: incluye solo las campañas con un gasto superior al umbral especificado. - **Group by country**: desglosa cada fila de campaña por país. La tabla se exporta tal como aparece en tu dashboard, con las columnas que hayas seleccionado para mostrar. ## Lanzar y pausar campañas \{#launch--pause-campaigns\} Para lanzar o pausar cualquier campaña desde Adapty Ads Manager: 1. Ve a **Ads Manager > Campaigns**. 2. Activa o desactiva el interruptor junto al nombre de la campaña en la columna **Status**. ## Eliminar campañas \{#delete-campaigns\} Adapty Ads Manager no puede eliminar campañas. En su lugar, puedes [pausar](#launch-pause-campaigns) la campaña para detener el gasto, conservando su configuración y analíticas. --- # File: ads-manager-create-ad-group --- --- title: "Gestionar grupos de anuncios en Adapty Ads Manager" description: "Crea y edita grupos de anuncios de Apple Ads en Adapty Ads Manager." --- Adapty Ads Manager tiene una integración bidireccional con Apple Ads: obtienes datos de rendimiento casi en tiempo real y puedes crear y editar campañas directamente desde el Adapty Dashboard de una forma mucho más cómoda que con la interfaz nativa. Si creas un grupo de anuncios en el dashboard nativo de Apple Ads, aparecerá automáticamente en Adapty Ads Manager en un plazo de 24 horas. Además de [explorar las métricas completas de campaña](adapty-ads-manager-analytics), puedes gestionar todos los ajustes de los grupos de anuncios: - Crear grupos de anuncios - Editar grupos de anuncios existentes - Lanzar y pausar grupos de anuncios ## Qué es un grupo de anuncios \{#what-is-ad-group\} Un grupo de anuncios pertenece a una [campaña](ads-manager-create-campaign) y es donde configuras la segmentación y la estrategia de puja para tus anuncios. Cada grupo de anuncios incluye configuraciones de puja, segmentación de audiencia y [palabras clave](ads-manager-manage-keywords) que determinan cuándo y a quién se muestran tus anuncios. Los grupos de anuncios te permiten organizar tu estrategia publicitaria dentro de una campaña y probar distintos enfoques de segmentación. :::important Ten en cuenta que una campaña no puede ejecutarse sin grupos de anuncios. Los grupos de anuncios son el nivel donde se configuran la puja predeterminada, la audiencia y las palabras clave, por lo que sin al menos un grupo de anuncios la campaña no tiene segmentación ni pujas y no se publicará. Crea primero la campaña y luego [añade al menos un grupo de anuncios](ads-manager-create-ad-group) para activarla. ::: ## Crear grupos de anuncios \{#create-ad-groups\} Para crear un nuevo grupo de anuncios de Apple Ads: 1. Ve a **Ads Manager** desde el menú de la barra lateral. En cualquier pestaña, haz clic en **+** encima de la tabla y selecciona **Create ad group**. 2. Selecciona la aplicación a la que quieres añadir el grupo de anuncios. 3. Selecciona la campaña a la que quieres añadir el grupo de anuncios. 4. Configura los ajustes del grupo de anuncios: - **Ad group name**: La etiqueta que asignas para identificar y buscar tu grupo de anuncios en el dashboard. - **Default max CPT bid**: El importe máximo que estás dispuesto a pagar por un tap en tu anuncio. Esta puja se aplica a todas las palabras clave del grupo de anuncios, salvo que establezcas pujas individuales para cada una. - **CPA cap (limits impressions)** (Opcional): Este ajuste especifica el importe máximo que estás dispuesto a gastar por conversión de tap (por ejemplo, una descarga u otra acción objetivo). Establece un límite de puja para todas las palabras clave de tu grupo de anuncios. El techo de puja se calcula multiplicando el límite CPA que proporcionas por tu tasa de conversión de tap-through: `CPA Cap × CR (Tap-Through)`. Si la puja máxima de CPT para la palabra clave es inferior a este valor, se aplicará la puja máxima de CPT más baja. Por ejemplo, si tu límite CPA es de $5 y tu tasa de conversión de tap-through es del 65%, la puja máxima aplicada a todas las palabras clave del grupo de anuncios sería de $3,25. Si el CPT máximo está fijado en $4, la puja máxima aplicada seguiría siendo $3,25. - **Search Match**: Actívalo para que tus anuncios se emparejen automáticamente con búsquedas relevantes sin necesidad de especificar palabras clave. Cuando está activado, Apple Ads puede mostrar tu anuncio en búsquedas relacionadas con los metadatos y la categoría de tu app. - **Audience**: Los criterios de segmentación que determinan qué usuarios ven tus anuncios. - **All eligible users**: Muestra tus anuncios a todos los usuarios elegibles para tu campaña. - **Specific audiences**: Dirige tus anuncios a segmentos específicos de usuarios configurando: - **Devices**: Elige iPad, iPhone o ambos. - **Customer type**: Dirige tus anuncios a todos los usuarios, usuarios nuevos o usuarios que regresan. - **Gender**: Segmenta por género o dirígete a todos los usuarios. - **Age range**: Selecciona rangos de edad específicos o todos los usuarios. - **Ad scheduling** (Opcional, disponible cuando seleccionas **Specific audiences**): Define cuándo empiezan a publicarse tus anuncios: - **Start date and time**: Cuándo debe empezar a servir anuncios tu grupo de anuncios. - **End date** (Opcional): Cuándo debe dejar de servir anuncios tu grupo de anuncios. 5. Haz clic en **Create**. 6. Si el tipo de placement de tu campaña es **Search results**, ahora puedes [añadir palabras clave](ads-manager-manage-keywords) para empezar a publicar anuncios. En otros tipos de placement, ya está todo listo. :::note En una campaña **Max Conversions**, el grupo de anuncios tiene un botón de alternancia **Bidding strategy**: **Standard** o **Automated**. No hay ningún campo **Default max CPT bid**. Con **Automated**, estableces un CPA objetivo y Apple optimiza las pujas por ti. ::: ## Editar grupos de anuncios \{#edit-ad-groups\} Para editar cualquier grupo de anuncios creado: 1. Abre la configuración de la campaña usando cualquiera de estos métodos: - Haz clic en el nombre de la campaña en **Ads Manager > Ad groups**. Luego, haz clic en **Edit ad group** en la parte superior derecha. - O selecciona la casilla junto al nombre del grupo de anuncios y haz clic en **Actions > Edit ad group settings**. 2. Ajusta la configuración del grupo de anuncios. No se pueden modificar la app, la campaña, el tipo de audiencia ni la fecha y hora de inicio. En un grupo de anuncios automatizado, solo se puede editar el nombre. 3. Haz clic en **Save changes**. Para duplicar un grupo de anuncios, selecciónalo en **Ads Manager > Ad groups** y haz clic en **Actions > Duplicate ad group**. :::note Las ediciones realizadas directamente en un grupo de anuncios en Apple Ads se sincronizan automáticamente con Adapty Ads Manager, pero pueden tardar un tiempo en aparecer en Adapty Ads Manager. ::: ## Exportar grupos de anuncios \{#export-ad-groups\} Para exportar la tabla de grupos de anuncios como CSV, haz clic en el icono de descarga que aparece sobre la tabla y selecciona **Export current page** o **Export all pages**. **Export all pages** descarga todos los grupos de anuncios de todas las páginas en un único archivo. Un modal de progreso muestra el estado de la descarga; puedes cancelarla en cualquier momento. Hay dos filtros opcionales disponibles: - **Enabled only**: incluye solo los grupos de anuncios activos. - **With spend ≥**: incluye solo los grupos de anuncios con gasto superior al umbral especificado. - **Group by country**: desglosa cada fila de grupo de anuncios por país. La tabla se exporta tal como aparece en el dashboard, con las columnas que hayas seleccionado para mostrar. ## Activar y pausar grupos de anuncios \{#launch--pause-ad-groups\} Para activar o pausar cualquier grupo de anuncios desde Apple Ads Manager: 1. Ve a **Ads Manager > Ad groups**, o navega a la página de una campaña para ver sus grupos de anuncios. 2. Activa o desactiva el interruptor junto al nombre del grupo de anuncios en la columna **Status**. --- # File: ads-manager-manage-keywords --- --- title: "Gestionar palabras clave en Adapty Ads Manager" description: "Añade y gestiona palabras clave de Apple Ads, palabras clave negativas y palabras clave SKAG en Adapty Ads Manager." --- Adapty Ads Manager tiene una integración bidireccional con Apple Ads: obtienes datos de rendimiento casi en tiempo real y puedes crear y editar palabras clave directamente desde el Adapty Dashboard de una forma mucho más cómoda que en la interfaz nativa. Si creas una palabra clave en el dashboard nativo de Apple Ads, aparecerá automáticamente en Adapty Ads Manager en un plazo de 24 horas. Además de [explorar análisis completos](adapty-ads-manager-analytics), puedes gestionar todos los ajustes de tus palabras clave: - Añadir palabras clave a grupos de anuncios - Añadir palabras clave negativas - Añadir palabras clave como SKAG (Single Keyword Ad Group) - Editar palabras clave directamente en la tabla - Realizar acciones masivas sobre varias palabras clave - Activar y pausar palabras clave :::tip Para encontrar palabras clave con bajo rendimiento en tu cuenta — por ejemplo, palabras clave con gasto pero sin conversiones — consulta el [Agente de IA](ads-manager-ai-agent). ::: ## Qué son las palabras clave \{#what-are-keywords\} Las palabras clave son los términos de búsqueda que hacen que tus anuncios aparezcan en los resultados de búsqueda de la App Store. Se organizan dentro de [grupos de anuncios](ads-manager-create-ad-group), que pertenecen a [campañas](ads-manager-create-campaign). Esta estructura jerárquica te permite organizar y gestionar tu estrategia publicitaria de forma eficaz. :::important Las palabras clave solo se aplican a campañas con el tipo de placement **Search results**. Para campañas con otros tipos de placement (Search tab o Product pages), no se usan palabras clave. ::: ### Palabras clave estándar \{#standard-keywords\} Las palabras clave estándar son los términos principales sobre los que pujas para activar tus anuncios. Cuando los usuarios buscan estos términos en la App Store, tu anuncio puede aparecer en los resultados de búsqueda. ### Palabras clave negativas \{#negative-keywords\} Las palabras clave negativas evitan que tu anuncio aparezca en búsquedas que no son relevantes para tu app. Al añadirlas, puedes reducir el gasto en búsquedas irrelevantes. Las palabras clave negativas se pueden añadir a nivel de grupo de anuncios o como palabras clave negativas entre grupos, que se aplican a varias campañas a la vez. ### Palabras clave como SKAG (Single Keyword Ad Group) \{#keywords-as-skag-single-keyword-ad-group\} SKAG (Single Keyword Ad Group) es una estrategia en la que creas grupos de anuncios individuales, cada uno con una sola palabra clave. Este enfoque te permite: - Tener un control preciso sobre las pujas para palabras clave de alto valor - Analizar mejor el rendimiento a nivel de palabra clave SKAG es especialmente útil para identificar las palabras clave con mejor rendimiento y maximizar su potencial mediante grupos de anuncios dedicados. ## Añadir palabras clave \{#add-keywords\} Para añadir palabras clave a un grupo de anuncios: :::note Las palabras clave en campañas **Maximize Conversions** no usan pujas — la puja la gestiona automáticamente el objetivo de CPA de la campaña. El campo **CPT bid** no se aplica a estas campañas. ::: 1. Ve a **Ads Manager** desde el menú lateral. En cualquier pestaña, haz clic en **+** encima de la tabla y selecciona **Add keywords** en el desplegable. 2. En el modal, selecciona las campañas y los grupos de anuncios a los que quieres añadir palabras clave. Tras seleccionar los grupos de anuncios de una campaña, puedes seleccionar otra campaña y añadir más grupos de anuncios a la lista. 3. Haz clic en **Select** para continuar. 4. En el cuadro de diálogo **Add keywords**, introduce las palabras clave en el campo **Keywords list**. Si tienes un archivo delimitado por comas con palabras clave, puedes pegar su contenido para que Adapty Ads Manager las suba todas en bloque. 5. Para cada palabra clave de la tabla, configura: - **Match type**: Selecciona el tipo de coincidencia **Exact** o **Broad** - **CPT bid**: Establece la puja máxima de coste por toque para esta palabra clave, o déjala vacía para usar la puja CPT máxima predeterminada del grupo de anuncios 6. Revisa tus palabras clave y haz clic en **Add X keywords** (donde X es el número de palabras clave que estás añadiendo). :::important Una vez que guardes una palabra clave, no se puede cambiar su tipo de coincidencia. Si necesitas cambiarlo, elimina la palabra clave y agrégala de nuevo con el tipo de coincidencia deseado. ::: ## Añadir palabras clave negativas \{#add-negative-keywords\} Para añadir palabras clave negativas: 1. Ve a **Ads Manager** desde el menú lateral. En cualquier pestaña, haz clic en **+** encima de la tabla y selecciona **Add negative keywords** en el menú desplegable. 2. En el modal **Add negative keywords to**, selecciona en qué nivel quieres añadir las palabras clave negativas: - **Selected campaigns**: Añade palabras clave negativas a nivel de campaña. - **Selected ad groups**: Añade palabras clave negativas a nivel de grupo de anuncios. - **All ad groups in selected campaigns**: Añade palabras clave negativas a nivel de grupo de anuncios en todos los grupos de anuncios de las campañas seleccionadas. :::note Ten en cuenta lo siguiente: - Las palabras clave negativas a nivel de grupo de anuncios tienen mayor prioridad que las de nivel de campaña. - Si añades palabras clave negativas a todos los grupos de anuncios de las campañas seleccionadas, tendrás que añadirlas manualmente si decides agregar nuevos grupos de anuncios a esas campañas más adelante. ::: 3. Introduce las palabras clave negativas en el campo **Keywords list**. Si tienes un archivo delimitado por comas con palabras clave, puedes pegar su contenido para que Adapty Ads Manager las cargue todas de una vez. 4. Para cada palabra clave de la tabla, selecciona el **Match type**: - **Exact**: Excluye únicamente la palabra clave exacta o variaciones muy similares. - **Broad**: Excluye la palabra clave y los términos de búsqueda relacionados. O bien, marca las casillas junto a ellas y cambia el tipo de concordancia en bloque. 5. Revisa tus palabras clave negativas y haz clic en **Add X keywords** (donde X es el número de palabras clave que estás añadiendo). :::note Las palabras clave negativas entre grupos son especialmente útiles cuando quieres excluir ciertos términos de búsqueda en varias campañas a la vez, ahorrando tiempo y garantizando coherencia en toda tu estrategia publicitaria. ::: ## Añadir palabras clave como SKAG \{#add-keywords-as-skag\} Para añadir palabras clave como SKAG (Single Keyword Ad Group): 1. Ve a **Ads Manager** desde el menú lateral. En cualquier pestaña, haz clic en **+** encima de la tabla y selecciona **Add keywords as SKAG** en el desplegable. 2. Selecciona las campañas en las que quieres crear los grupos de anuncios SKAG. Puedes seleccionar varias campañas. 3. Por defecto, los nuevos grupos de anuncios se crearán con la configuración predeterminada dirigida a todos los usuarios. Si quieres cambiarlo, selecciona **Copy settings from ad group** y elige un grupo de anuncios existente del que copiar la configuración. 4. Configura los ajustes para los nuevos grupos de anuncios: - **Ad group name prefix**: Prefijo opcional que se añade al nombre de cada grupo de anuncios (por ejemplo, "SKAG_" creará "SKAG_keyword1", "SKAG_keyword2", etc.). Puedes hacer clic en **Tag** para añadir dinámicamente la palabra clave, el nombre de la campaña y el país a los nombres de los grupos. - **CPT bid** y **CPA cap**: Establece la puja para todas las palabras clave a la vez, o selecciona **Set CPT bid and CPA cap for each word manually** para configurarlas individualmente para cada palabra clave. 5. Introduce las palabras clave en el campo **Keywords list**. Si tienes un archivo delimitado por comas con palabras clave, puedes pegar su contenido para que Adapty Ads Manager las suba todas en bloque. 6. Para cada palabra clave de la tabla, selecciona el **Match type**: - **Exact**: Coincide solo con la palabra clave exacta o variaciones muy similares - **Broad**: Coincide con la palabra clave y términos de búsqueda relacionados O marca las casillas junto a ellas y cambia el tipo de coincidencia en bloque. 7. Selecciona **Check for duplicates in target campaign** para asegurarte de que no hay palabras clave idénticas en las campañas de destino. 8. Haz clic en **Create** para crear los grupos de anuncios SKAG. Cada keyword se colocará en su propio grupo de anuncios dentro de cada campaña seleccionada, lo que te permite gestionarlos y optimizarlos de forma independiente. ## Editar palabras clave \{#edit-keywords\} Para editar palabras clave existentes: 1. Ve a **Ads Manager > Keywords** o **Ads Manager > Negative keywords** y busca la palabra clave que quieras editar en la tabla, o navega hasta la página de una campaña, luego a la página de un grupo de anuncios, y encuentra la palabra clave. 2. Edita los valores directamente en la tabla: - **CPT bid**: Haz clic en el valor de la puja e introduce un nuevo coste máximo por toque - **Status**: Usa el interruptor para pausar o activar la palabra clave :::note Los cambios en las palabras clave realizados directamente en Apple Ads se sincronizan automáticamente con Adapty Ads Manager, aunque pueden tardar un tiempo en aparecer en Adapty Ads Manager. ::: ## Acciones masivas \{#bulk-actions\} Puedes realizar acciones masivas sobre múltiples palabras clave para ahorrar tiempo y gestionar tus palabras clave de forma más eficiente. Para realizar acciones masivas: 1. Ve a la pestaña **Ads Manager > Keywords** o **Ads Manager > Negative keywords**. 2. Selecciona varias palabras clave marcando las casillas junto a las que quieras gestionar. 3. Haz clic en el desplegable **Actions** y elige una de las siguientes opciones: - **Add as keywords**: Añade las palabras clave seleccionadas como palabras clave estándar. - **Add as negative keywords**: Añade las palabras clave seleccionadas como palabras clave negativas. - **Add as SKAG**: Crea grupos de anuncios de palabra clave única (SKAG) para las palabras clave seleccionadas. - **Activate**: Activa las palabras clave seleccionadas. - **Pause**: Pausa las palabras clave seleccionadas. - **Create segment from keywords**: Crea un segmento de audiencia a partir de las palabras clave seleccionadas. - **Copy keywords**: Copia los nombres de las palabras clave seleccionadas al portapapeles. - **Edit CPT bids**: Edita las pujas CPT de las palabras clave seleccionadas. Puedes editarlas de distintas formas: - **Set to**: Establece varias pujas en un importe concreto. - **Increase by/decrease by**: Aumenta o reduce las pujas en un importe fijo en USD o en un porcentaje de la puja. Opcionalmente, define un límite máximo de puja para evitar gastos accidentales. - **Set to average CPT**: Usa la métrica CPT (coste por tap) para alinear la puja con ella. Define un coeficiente multiplicador. Por ejemplo, usa 0,9 cuando el rendimiento esté por debajo de lo esperado, o 1,1 cuando lo supere. - **Set to average CPA**: Usa la métrica CPA (coste por adquisición) para alinear la puja con ella. Define un coeficiente multiplicador. La pestaña **Negative keywords** incluye un conjunto limitado de acciones: - **Add as keywords** - **Add as negative keywords** - **Add as SKAG** - **Delete keywords** :::tip Las acciones masivas son especialmente útiles para: - Convertir palabras clave entre distintos tipos (estándar, negativa, SKAG) - Añadir rápidamente palabras clave con otros tipos de concordancia para varias palabras clave a la vez - Filtrar las palabras clave con mejor rendimiento y ajustar sus pujas - Identificar las palabras clave con bajo rendimiento y pausarlas ::: ## Exportar palabras clave \{#export-keywords\} Para exportar la tabla de palabras clave en formato CSV, haz clic en el icono de descarga que hay sobre la tabla y selecciona **Export current page** o **Export all pages**. **Export all pages** descarga todas las palabras clave de todas las páginas en un único archivo. Un modal de progreso muestra el avance de la descarga; puedes cancelarlo en cualquier momento. Hay dos filtros opcionales disponibles: - **Enabled only**: incluye solo las palabras clave activas. - **With spend ≥**: incluye solo las palabras clave con gasto superior al umbral especificado. - **Group by country**: desglosa cada fila de palabra clave por país. La tabla se exporta tal como aparece en tu dashboard, con las columnas que hayas seleccionado para mostrar. ## Explora los gráficos por palabra clave \{#explore-keyword-level-charts\} Puedes abrir un gráfico para cualquier palabra clave directamente desde la tabla **Ads Manager > Keywords**. Esto permite analizar el rendimiento día a día de cada palabra clave de forma precisa. Para mostrar un gráfico, haz clic en el icono de gráfico junto a la palabra clave en la tabla. Por defecto, el gráfico mostrará la métrica **Spend** para la palabra clave seleccionada. Puedes mostrar varias métricas a la vez para detectar correlaciones y cambios a lo largo del tiempo. Haz clic en **+** para añadir una nueva métrica. Haz clic en **Reset** para empezar de cero o simplemente desmarca las casillas de las métricas para ocultarlas. ## Historial de pujas \{#bid-history\} Para ver el historial de pujas de una palabra clave, haz clic en el **icono de gráfico** que aparece junto a ella en la tabla. Se abrirá un panel con dos pestañas: **Metrics** y **Bid History**. - La pestaña **Metrics** muestra un gráfico con métricas a lo largo del tiempo. Puedes añadir y eliminar métricas del mismo modo que en los gráficos a nivel de palabra clave: haz clic en **+** para añadir, o desmarca las casillas para ocultarlas. Aparece un marcador en cada punto donde se modificó la puja; pasa el cursor sobre él para ver el importe exacto de la puja en esa fecha. Úsalo para correlacionar los cambios de puja con las variaciones en el rendimiento: si una métrica bajó o subió bruscamente tras un cambio, el marcador señala exactamente cuándo ocurrió. - La pestaña **Bid History** enumera cada cambio de puja: su fecha, tipo, valores anterior y nuevo, y qué lo provocó: un cambio manual o una regla de automatización (indicada con el ID de la regla). --- # File: ads-manager-manage-ads --- --- title: "Gestionar anuncios en Adapty Ads Manager" description: "Crea y edita anuncios de Apple Ads en Adapty Ads Manager." --- Adapty Ads Manager tiene una integración bidireccional con Apple Ads: obtienes datos de rendimiento en tiempo casi real y puedes crear y editar anuncios directamente desde el Adapty Dashboard de forma mucho más cómoda que en la interfaz nativa. Si creas un anuncio en el dashboard nativo de Apple Ads, aparecerá automáticamente en Adapty Ads Manager en un plazo de 24 horas. ## Qué son los anuncios \{#what-are-ads\} Un anuncio es un creativo asignado a un [grupo de anuncios](ads-manager-create-ad-group) dentro de una [campaña](ads-manager-create-campaign). Puedes asignar un anuncio activo por grupo de anuncios. ## Crear anuncios \{#create-ads\} Antes de comenzar, asegúrate de haber creado: - **Grupo de anuncios**. Puedes [crearlo directamente en el dashboard de Adapty Ads Manager](ads-manager-create-ad-group). - **Página de producto personalizada**. Debes [configurarla directamente en Apple Ads](https://developer.apple.com/help/app-store-connect/create-custom-product-pages/configure-multiple-product-page-versions/). Debe estar aprobada por App Store antes de poder usarla en tu anuncio. :::note Si ya tienes un anuncio activo dentro del grupo de anuncios seleccionado, se pausará para ejecutar el nuevo anuncio. ::: Para crear un nuevo anuncio de Apple Ads: 1. Ve a **Ads Manager** desde el menú lateral. En cualquier pestaña, haz clic en **+** encima de la tabla y selecciona **Create ad**. 2. Selecciona la app para la que quieres lanzar el anuncio. 3. Selecciona uno o más grupos de anuncios. Adapty crea el anuncio en cada grupo de anuncios seleccionado. 4. Escribe el nombre del anuncio. 5. Establece el estado del anuncio. Desactiva el botón **Status** para empezar a publicar el anuncio más adelante. 6. Haz clic en **Select CPP**. Verás todas las páginas de producto personalizadas de tu app aprobadas por App Store. Solo puedes seleccionar una página de producto personalizada. 7. Haz clic en **Create ad**. ## Editar anuncios \{#edit-ads\} :::note Una vez creado un anuncio, puedes editar su nombre y estado. No puedes cambiar su CPP ni moverlo a otro grupo de anuncios. ::: Para editar el nombre de un anuncio, usa cualquiera de las siguientes opciones: - Haz clic en el nombre del anuncio en **Ads Manager > Ads**. Edita el nombre y haz clic en la marca de verificación que aparece junto a él. - Activa la casilla junto al nombre del anuncio y haz clic en **Actions > Edit ad**. Cambia el nombre o el estado del anuncio y haz clic en **Save changes**. :::note Las ediciones realizadas directamente en un anuncio de Apple Ads se sincronizan automáticamente con Adapty Ads Manager, pero pueden tardar un tiempo en aparecer en Adapty Ads Manager. ::: ## Exportar anuncios \{#export-ads\} Para exportar la tabla de anuncios como CSV, haz clic en el icono de descarga que hay encima de la tabla y selecciona **Export current page** o **Export all pages**. **Export all pages** descarga todos los anuncios de todas las páginas en un único archivo. Un modal de progreso muestra el estado de la descarga; puedes cancelarla en cualquier momento. Hay dos filtros opcionales disponibles: - **Enabled only**: incluye solo los anuncios activos. - **With spend ≥**: incluye solo los anuncios con gasto por encima de un umbral especificado. - **Group by country**: desglosa cada fila de anuncio por país. La tabla se exporta tal como aparece en tu dashboard, con las columnas que hayas seleccionado para mostrar. ## Lanzar y pausar anuncios \{#launch--pause-ads\} Para lanzar o pausar cualquier anuncio desde el Adapty Ads Manager, usa cualquiera de las siguientes opciones: - Activa o desactiva el interruptor **Status** en **Ads Manager > Ads**. - Marca la casilla junto al nombre del anuncio, haz clic en **Actions > Edit ad**, cambia el interruptor **Status** y haz clic en **Save changes**. --- # File: ads-manager-create-segments --- --- title: "Crear segmentos basados en atribución de Apple Ads en Adapty Ads Manager" description: "Crea segmentos a partir de campañas, grupos de anuncios y palabras clave en dos clics en Adapty Ads Manager." --- Puedes crear [segmentos](segments) de usuarios directamente desde [Adapty Ads Manager](adapty-ads-manager) seleccionando campañas, grupos de anuncios o palabras clave y convirtiéndolos en segmentos en pocos clics. Así es fácil personalizar paywalls y ofertas según la fuente de adquisición, sin necesidad de configurar las condiciones del segmento manualmente. Una vez creado un segmento, puedes usarlo para asignar diferentes productos y precios, ejecutar pruebas A/B y personalizar el aspecto del paywall. ## Casos de uso \{#use-cases\} Aquí tienes algunos ejemplos de cómo se pueden usar en la práctica los segmentos creados a partir de datos de Apple Ads: - **Paywalls basados en palabras clave**. Muestra un paywall orientado a funciones a los usuarios que provienen de palabras clave de alta intención, y un paywall general a los usuarios que llegan por palabras clave de descubrimiento más amplias. - **Ofertas a nivel de campaña**. Ofrece períodos de prueba más largos o precios especiales a los usuarios procedentes de campañas de Apple Ads seleccionadas, mientras mantienes una oferta estándar para el resto. - **Coherencia entre anuncio y paywall**. Dirige a los usuarios de grupos de anuncios que promocionan funciones específicas hacia paywalls que destacan esas funciones en primer lugar. - **Optimización de campañas con alto ROI**. Muestra un paywall premium con precio completo a los usuarios que vienen de campañas que generan sistemáticamente un mayor valor de vida del cliente. ## Crear segmentos \{#create-segments\} Para crear un segmento desde Adapty Ads Manager: 1. Ve a **Ads Manager** y cambia a la pestaña **Campaigns**, **Ad groups** o **Keywords**. Marca las casillas junto a las entidades que quieras usar. Ten en cuenta que, si seleccionas varias entidades, se usarán para crear un único segmento para todas ellas y no un segmento individual para cada entidad. 2. Haz clic en **Actions > Create segment from campaigns/ad groups/keywords**. 3. Si es necesario, actualiza los detalles del segmento en la ventana **Create segment**: - **Adapty project**: La app en Adapty en la que deseas crear este segmento. - **Build segment from campaign/ad group**: Al crear un segmento a partir de campañas o grupos de anuncios, puedes ajustar las campañas o grupos de anuncios seleccionados en este paso. - **Segment name** - **Segment description** 4. Haz clic en **Create**. 5. Una vez creado el segmento, puedes prepararte para usarlo: - Añádelo a un [placement](placements) para usarlo con un paywall u onboarding existente - Diseña un nuevo [paywall](adapty-paywall-builder) u [onboarding](onboardings) que se mostrará a los usuarios del segmento - Ejecuta una [prueba A/B](ab-tests) --- # File: ads-manager-automations-keyword-rules --- --- title: "Reglas de palabras clave en Adapty Ads Manager" description: "Gestiona automáticamente el ciclo de vida de las palabras clave — ajusta pujas, activa o pausa palabras clave y muévelas entre grupos de anuncios — según el rendimiento de la campaña." --- Las reglas de palabras clave actúan automáticamente sobre tus palabras clave en función del rendimiento en todo el embudo: desde instalaciones hasta pruebas, suscripciones e ingresos. Define condiciones usando métricas como gasto, CPA, ROAS y datos de cohorte, y elige qué hace la regla cuando se cumplen esas condiciones. Las reglas se ejecutan según la programación que establezcas. Responden a los cambios de rendimiento sin intervención manual. ## Acciones disponibles \{#available-actions\} Cada regla de palabras clave realiza una acción cuando se cumplen sus condiciones: | Acción | Qué hace | |--------|-------------| | **Change bid** | Aumenta, reduce o establece la puja CPT | | **Enable keyword** | Reactiva una palabra clave en pausa | | **Pause keyword** | Pausa una palabra clave activa | | **Add as keyword to…** | Copia la palabra clave a otro grupo de anuncios con una puja y tipo de concordancia especificados | | **Add as negative keyword to…** | Añade la palabra clave como negativa en los grupos de anuncios o campañas indicados | ## Crear una regla de palabras clave \{#create-a-keyword-rule\} Puedes crear reglas de palabras clave desde plantillas o manualmente desde cero. ### Desde una plantilla \{#from-a-template\} Adapty ofrece plantillas listas para usar en los escenarios de optimización más habituales. Entre las más comunes se incluyen: - **Reducir el gasto en palabras clave que no convierten**: disminuye las pujas cuando el Gasto > X y las Instalaciones o Pruebas = 0. - **Escalar palabras clave ganadoras**: aumenta las pujas cuando el ROAS > objetivo o el CPA < objetivo. Para crear una regla desde una plantilla: 1. En el panel lateral izquierdo, ve a **Automations** y haz clic en **Templates**. 2. Elige una plantilla y haz clic en **Next**. 3. Revisa y ajusta la configuración predefinida: - **Rule name**: Se establece automáticamente con el nombre de la plantilla y la fecha actual (por ejemplo, "Scale Winning Keywords - [2025-11-12]"). - **Apply to**: Selecciona los grupos de campaña, apps, campañas o grupos de anuncios donde debe aplicarse la regla. - **Conditions**: Modifica las condiciones preconfiguradas si es necesario. - **Action**: Modifica la acción predefinida si es necesario. - **Schedule**: Define con qué frecuencia debe ejecutarse la regla. 4. Haz clic en **Save** para activar la regla. ### Manualmente \{#manually\} Para crear una regla de palabras clave personalizada desde cero: 1. En la barra lateral izquierda, ve a **Automations**, haz clic en **Create rule** y selecciona **Keywords** como tipo de regla. 2. Introduce un **Rule name** descriptivo. 3. En la sección **Apply to**, selecciona los grupos de campaña, apps, campañas o grupos de anuncios donde debe aplicarse la regla. 4. Haz clic en **Add condition** y selecciona una [métrica](adapty-ads-manager-metrics) de la lista. Las métricas se calculan para el rango de tiempo seleccionado en la moneda de tu cuenta. Los datos se actualizan casi en tiempo real, por lo que las reglas siempre utilizan datos de rendimiento recientes. 5. Establece el período de tiempo (por ejemplo, Previous 3 days o Previous 7 days), elige el operador de comparación e introduce el valor umbral. 6. Para añadir más condiciones, haz clic en **Add condition** y selecciona un operador **And** u **Or** en la parte izquierda. 7. En la sección **Action**, selecciona qué ocurre cuando se cumplen las condiciones: **Cambiar puja** - **Tipo de acción**: Selecciona **Increase by**, **Decrease by** o **Set to**. - **Tipo de valor**: Alterna entre **$** (absoluto) y **%** (relativo a la puja actual en el momento en que se ejecuta la regla). - **Límite superior de puja** (opcional): Límite máximo de puja para evitar pujar en exceso si la regla se activa repetidamente ante señales fuertes. **Activar palabra clave** - Sin configuración adicional. La regla reactiva las palabras clave pausadas que cumplen las condiciones. **Pausar palabra clave** - Sin configuración adicional. La regla pausa las palabras clave activas que cumplen las condiciones. **Añadir como palabra clave en…** - **Target ad groups**: Selecciona los grupos de anuncios que recibirán las palabras clave copiadas. - **CPT bid**: Establece la puja inicial para las palabras clave copiadas. - **Match type**: Selecciona **Exact** o **Broad**. - **Skip if keyword already exists**: Cuando está activado, omite los términos que ya existen en el grupo de anuncios de destino. **Añadir como palabra clave negativa en…** - **Scope**: Selecciona los grupos de anuncios o campañas donde se añadirá la palabra clave negativa. - **Match type**: Selecciona **Exact** o **Broad**. 8. En la sección **Schedule**: - Elige la frecuencia: **Every day**, **Every 2 days**, **Every week**, etc. - Selecciona la hora de ejecución (todas las horas están en UTC). Las reglas se ejecutan a la hora programada en UTC. La ejecución suele terminar en pocos minutos, tras lo cual puedes ver los cambios en Logs y en el dashboard principal. 9. Haz clic en **Save** para crear la regla. ## Mejores prácticas \{#best-practices\} - **Empieza con un alcance reducido**: Aplica las nuevas reglas a unas pocas campañas o grupos de anuncios primero para validar el comportamiento antes de ampliar. - **Usa ventanas de análisis cortas para campañas activas**: En campañas de ritmo rápido, los 3–7 días anteriores suelen funcionar mejor que 30 días. - **Combina gasto y conversiones**: Evita las reglas de métrica única. Usa Gasto junto con Instalaciones, Trials o ROAS para obtener señales más fiables. - **Establece límites de puja en las reglas de cambio de puja**: El límite superior de puja evita pujas desbocadas cuando una señal fuerte activa la regla varias veces. - **Usa Habilitar keyword con datos de cohorte**: Una keyword pausada pronto por un CPA inicial bajo puede mostrar un ROAS D31 o D61 sólido una vez que los datos de cohorte maduran. Establece una condición sobre el ROAS de cohorte y vuelve a habilitarla automáticamente cuando supere tu objetivo. - **Usa Añadir como keyword a… para pipelines de prueba a escalado**: Cuando una keyword en una campaña de prueba alcanza tu objetivo de CPA, cópiala automáticamente a una campaña de escala. - **Usa Añadir como keyword negativa a… para mantener limpias las campañas Discovery**: Cuando una keyword está confirmada como keyword de coincidencia exacta, niégala en tus campañas Discovery o Search Match para evitar competir por la misma consulta. - **Espera antes de que las reglas de puja actúen sobre keywords recién promovidas**: Si usas [automatizaciones de términos de búsqueda](ads-manager-automations-search-terms) para promover términos a campañas de keywords, dale a esas keywords uno o dos días para acumular datos primero. --- # File: ads-manager-automations-search-terms --- --- title: "Automatizaciones de términos de búsqueda en Adapty Ads Manager" description: "Promociona automáticamente los términos de búsqueda ganadores a palabras clave y niégalos en la fuente para escalar el tráfico de descubrimiento sin trabajo manual" --- Las campañas Discovery y Search Match generan datos de términos de búsqueda. Convertir esos datos en una lista de palabras clave estructurada requiere descargar informes, filtrar términos y añadirlos manualmente a los grupos de anuncios. Las automatizaciones de términos de búsqueda hacen esto de forma automática: cuando un término cumple tus condiciones, la regla actúa sobre él según la acción que hayas configurado. Hay dos tipos de acción para las reglas de términos de búsqueda: - **Add as keyword**: Promueve el término como palabra clave de coincidencia exacta en un grupo de anuncios objetivo y, opcionalmente, lo niega en la campaña de origen para evitar gasto duplicado. - **Add as negative keyword**: Niega el término directamente, sin promocionarlo. Úsalo para filtrar términos de búsqueda irrelevantes o ineficientes de las campañas Discovery y Search Match. El caso de uso típico de **Add as keyword**: dejar que las campañas de Discovery o Search Match recopilen consultas reales de usuarios, luego usar una regla para detectar términos que superen un umbral de rendimiento y añadirlos como palabras clave de coincidencia exacta en una campaña Probing, mientras se niegan en la campaña de origen. Una campaña Probing es una campaña de Apple Search Ads dedicada a probar palabras clave promocionadas con pujas controladas. El caso de uso típico de **Add as negative keyword**: si un término aparece con frecuencia pero nunca convierte (por ejemplo, muchas impresiones con cero taps), negarlo automáticamente para dejar de malgastar presupuesto en él. ## Crear una regla de automatización de términos de búsqueda \{#create-a-search-term-automation-rule\} Puedes crear reglas de automatización de términos de búsqueda a partir de plantillas o manualmente desde cero. :::note Antes de crear una regla, asegúrate de tener campañas de Discovery o Search Match activas recopilando datos de términos de búsqueda. Las campañas de solo coincidencia exacta no generan informes de términos de búsqueda, por lo que la regla no tendrá nada sobre lo que actuar. ::: ### Desde una plantilla \{#from-a-template\} Para crear una regla desde una plantilla: 1. En la barra lateral izquierda, ve a **Automations** y haz clic en **Templates**. 2. Elige una plantilla y haz clic en **Next**. 3. Revisa y ajusta la configuración predefinida: - **Rule name**: Se establece automáticamente con el nombre de la plantilla y la fecha actual. - **Apply to**: Selecciona grupos de campañas, apps, campañas o grupos de anuncios donde la regla debe buscar términos de búsqueda. - **Conditions**: Modifica las condiciones preconfiguradas si es necesario. - **Actions**: Ajusta los grupos de anuncios de destino, la puja CPT y el alcance de las palabras clave negativas si es necesario. - **Schedule**: Define con qué frecuencia debe ejecutarse la regla. 4. Haz clic en **Save** para activar la regla. ### Manualmente \{#manually\} Para crear una regla de automatización de términos de búsqueda personalizada desde cero: 1. En el panel lateral izquierdo, ve a **Automations**, haz clic en **Create rule** y selecciona **Search terms** como tipo de regla. 2. Escribe un **Rule name** descriptivo que identifique el propósito de la regla. 3. En la sección **Apply to**, selecciona los grupos de campañas, apps, campañas o grupos de anuncios en los que la regla debe buscar términos de búsqueda. 4. Haz clic en **Add condition** y selecciona una [métrica](adapty-ads-manager-metrics) de la lista. Las métricas se calculan para el rango de tiempo seleccionado en la moneda de tu cuenta. Los datos se actualizan casi en tiempo real, por lo que las reglas siempre utilizan datos de rendimiento actualizados. 5. Establece el período de tiempo (por ejemplo, Previous 3 days o Previous 7 days), elige el operador de comparación e introduce el valor umbral. 6. Para añadir más condiciones, haz clic en **Add condition** y selecciona un operador **And** u **Or** a la izquierda. 7. En la sección **Action**, selecciona qué ocurre cuando un término de búsqueda cumple las condiciones: **Agregar como palabra clave** Promueve los términos de búsqueda coincidentes como palabras clave de coincidencia exacta en un grupo de anuncios de destino. - **Target ad groups**: Selecciona los grupos de anuncios que recibirán las palabras clave promovidas. Para crear un pipeline de descubrimiento, selecciona grupos de anuncios en una campaña de Probing u otra campaña estructurada. - **CPT bid**: Establece la puja inicial de coste por toque para cada palabra clave promovida. Opciones: puja predeterminada del grupo de anuncios, CPT actual del término de búsqueda o un valor específico. - **Skip if keyword already exists**: Cuando está activado, omite los términos que ya existen en el grupo de anuncios de destino. - **Add as negative**: Añade los mismos términos como palabras clave negativas para evitar pagar dos veces por el mismo tráfico. - **Scope**: Selecciona los grupos de anuncios donde se añaden las palabras clave negativas. :::tip Activa **Add as negative** en la misma regla — promociona el término a una campaña estructurada y niégalo en el origen en un solo paso. Esto mantiene tus campañas Discovery limpias y construye tu embudo de palabras clave automáticamente. ::: **Add as negative keyword** Niega el término de búsqueda coincidente sin promocionarlo. - **Scope**: Selecciona los grupos de anuncios o campañas donde se añade la palabra clave negativa. - **Match type**: Selecciona **Exact** o **Broad**. Usa esta acción para excluir términos de búsqueda irrelevantes o de baja calidad en campañas de Discovery y Max Conversion. Por ejemplo: si un término tiene más de 50 impresiones y 0 taps, exclúyelo automáticamente. 8. En la sección **Schedule**: - Elige la frecuencia: **Every day**, **Every 2 days**, **Every week**, etc. - Selecciona la hora de ejecución (todas las horas están en UTC). Las reglas se ejecutan a la hora programada en UTC. La ejecución suele terminar en pocos minutos, tras lo cual puedes ver los cambios en Logs y en el dashboard principal. 9. Haz clic en **Save** para crear la regla. Una vez que la regla se ejecute, ve a **Automations** → **Logs** y abre la entrada correspondiente a tu regla. Una ejecución exitosa lista cada término de búsqueda evaluado con su campaña de origen, grupo de anuncios de destino, resultado de la acción y resultado de negación. Si no aparece ningún término, las condiciones no se cumplieron: revisa tu umbral o amplía la ventana de retrospección. ## Mejores prácticas \{#best-practices\} - **Usa campañas Discovery o Search Match como fuente**: Estos tipos de campaña recopilan consultas reales de usuarios, lo que le da a tus reglas un amplio grupo de términos de búsqueda para evaluar. - **Ajusta el umbral a tu ventana de seguimiento**: Dos o más descargas en 7 días es un buen punto de partida. Una ventana más larga (14–30 días) baja el listón efectivo — los términos pueden pasar con conversiones poco frecuentes. Para apps de alto volumen, acorta la ventana y sube el umbral. - **Niega siempre en la fuente cuando promociones**: Si añades un término como palabra clave en una campaña Probing pero no lo niegas en Discovery, ambas campañas compiten por la misma consulta. Activa **Add as negative** en la misma regla. - **Elige los grupos de anuncios de destino con criterio**: Dirige los términos promocionados a un grupo de anuncios Probing específico en lugar de a una campaña amplia. Esto mantiene limpia tu estructura de palabras clave y facilita el análisis de rendimiento. - **Revisa los registros después de cada ejecución**: Consulta la pestaña Logs para confirmar qué términos se promocionaron y dónde. Al principio, ejecuta la regla manualmente tras configurarla para validar que funciona como se espera. Consulta [Automations](ads-manager-automations#explore-logs) para saber cómo leer los registros. - **Dale tiempo a las palabras clave promocionadas antes de que actúen las reglas de palabras clave**: Si usas reglas de palabras clave en las mismas campañas, excluye las palabras clave recién promocionadas o espera uno o dos días antes de que las reglas se ejecuten sobre ellas. Una regla de palabras clave puede dispararse sobre un término nuevo sin historial de rendimiento y recortar su puja antes de que convierta. - **Usa Add as negative keyword para términos con muchas impresiones y cero taps**: Las campañas Discovery y Max Conversion a menudo muestran términos de búsqueda irrelevantes. Una regla con "Impressions > 50 AND Taps = 0" los detecta automáticamente y los niega antes de que acumulen más impresiones desperdiciadas. ## Exportar términos de búsqueda \{#export-search-terms\} Para exportar la tabla de términos de búsqueda como CSV, haz clic en el icono de descarga sobre la tabla y selecciona **Export current page** o **Export all pages**. **Export all pages** descarga todos los términos de búsqueda de todas las páginas en un único archivo. Un modal de progreso rastrea la descarga; puedes cancelarla en cualquier momento. La tabla se exporta tal como aparece en tu dashboard, con las columnas que hayas seleccionado para mostrar. --- # File: ads-manager-automations-ad-group-rules --- --- title: "Reglas de grupo de anuncios en Adapty Ads Manager" description: "Ajusta automáticamente las pujas y los objetivos de CPA de los grupos de anuncios, y activa o pausa grupos de anuncios según el rendimiento de la campaña." --- Añade una **regla de grupo de anuncios** cuando quieras que la configuración de un grupo de anuncios cambie automáticamente según su rendimiento. A diferencia de las reglas de palabras clave, que actúan sobre palabras clave individuales, una regla de grupo de anuncios modifica el grupo de anuncios completo de una sola vez. Cada regla combina una condición con una acción. Por ejemplo: si un grupo de anuncios gasta más de 50 $ en tres días sin ningún trial, reduce su puja un 20 %. Adapty comprueba tus grupos de anuncios según la frecuencia que establezcas —cada hora, a diario, semanalmente, etc.— y aplica la acción cada vez que se cumple la condición. Y como Adapty hace seguimiento de trials, suscripciones e ingresos, tus condiciones pueden responder a los ingresos reales, no solo a las instalaciones. ## Condiciones disponibles \{#available-conditions\} Una regla supervisa un conjunto de grupos de anuncios y se activa cuando su rendimiento supera el umbral que hayas definido. Primero, elige qué grupos de anuncios supervisa: - **Ad groups in selected campaign groups** - **Ad groups in selected apps** - **Ad groups in selected campaigns** - **Selected ad groups** A continuación, define la condición que activa la regla. Cada condición combina las siguientes partes: | Parte | Descripción | Ejemplo | | --- | --- | --- | | **Metric** | Cualquier [métrica que Adapty Ads Manager rastrea](adapty-ads-manager-metrics) — gasto, instalaciones, trials, suscripciones, ingresos y más. | Spend | | **Time window** | El período durante el que se mide la métrica. | Previous 3 days | | **Comparison** | Cómo se compara la métrica con tu valor. | is greater than | | **Threshold** | El valor con el que comparas. | $50 | Combina varias condiciones con **And** o **Or** para una segmentación más precisa — por ejemplo, spend > $50 **and** trials = 0. ## Acciones disponibles y su configuración \{#available-actions-and-their-settings\} Cuando un grupo cumple tu condición, Adapty puede modificar su configuración ejecutando una de las siguientes acciones: | Acción | Qué hace | Cuándo usarla | Configuración | | --- | --- | --- | --- | | **Change default bid** | Aumenta, disminuye o establece la **puja máxima predeterminada por toque (CPT)** del grupo de anuncios — el importe máximo que pagarás por un toque en tu anuncio. | Presiona más en los grupos de anuncios que convierten; retrocede en los que no. | **Action type**: Increase by, Decrease by o Set to.<br/>**Value type**: $ (absoluto) o % (sobre la puja actual).<br/>**Limit** (opcional): Un límite superior al aumentar, o un límite inferior al disminuir — limita hasta dónde pueden mover la puja las ejecuciones repetidas. | | **Change CPA goal** | Aumenta, disminuye o establece el **objetivo de CPA (tope)** del grupo de anuncios — coste objetivo por adquisición. | Ajusta el techo de coste a medida que optimizas, o amplíalo para ganar más volumen. | **Action type**: Increase by, Decrease by o Set to.<br/>**Value type**: $ o %.<br/>**Limit** (opcional): Un límite superior al aumentar, o un límite inferior al disminuir. | | **Enable ad group** | Reactiva un grupo de anuncios pausado. | Recupera grupos de anuncios que mejoran una vez que llegan los ingresos tardíos de pruebas y suscripciones. | Ninguna. | | **Pause ad group** | Pausa un grupo de anuncios activo. | Detén los grupos de anuncios que siguen gastando sin convertir. | Ninguna. | ## Crear una regla de grupo de anuncios \{#create-an-ad-group-rule\} 1. Ve a **Automations**, haz clic en **Create rule** y selecciona **For ad groups**. 2. Introduce un **Rule name**. 3. En **Apply to**, elige un [alcance](#available-conditions) y selecciona los grupos de anuncios. 4. En **Conditions**, añade una o más [condiciones](#available-conditions). Puedes combinar condiciones con operadores lógicos. 5. En **Action**, elige una [acción](#available-actions-and-their-settings) y configura sus opciones. 6. En **Schedule**, define con qué frecuencia se ejecuta la regla y su hora de inicio (UTC), o selecciona **Run immediately**. 7. Haz clic en **Save**. Después de guardar, la regla aparece en la pestaña **Automations**. Las columnas **Date last run** y **Date next run** muestran su programación. Para confirmar los cambios de estado entre ejecuciones, abre **Automations > Logs**. Consulta [Automaciones](ads-manager-automations) para saber cómo pausar, duplicar, eliminar una regla o ejecutarla de inmediato. --- # File: ads-manager-automations-campaign-rules --- --- title: "Reglas de campaña en Adapty Ads Manager" description: "Ajusta automáticamente los presupuestos diarios de campaña y activa o pausa campañas según su rendimiento." --- Añade una **regla de campaña** cuando quieras que el presupuesto o el estado de una campaña cambie automáticamente en función de su rendimiento. A diferencia de las reglas de grupo de anuncios, que actúan sobre un único grupo, una regla de campaña modifica toda la campaña a la vez. Cada regla combina una condición con una acción. Por ejemplo: si una campaña gasta más de 200 $ en tres días sin ninguna suscripción, reduce su presupuesto diario un 20 %. Adapty revisa tus campañas según el calendario que establezcas —cada hora, a diario, semanalmente, etc.— y aplica la acción cada vez que se cumple la condición. Y como Adapty hace seguimiento de trials, suscripciones e ingresos, tus condiciones pueden responder a los ingresos reales, no solo a las instalaciones. ## Condiciones disponibles \{#available-conditions\} Una regla observa un conjunto de campañas y se activa cuando su rendimiento supera un umbral que tú defines. Primero, elige qué campañas observa: - **Campaigns in selected campaign groups** - **Campaigns in selected apps** - **Selected campaigns** A continuación, define la condición que activa la regla. Cada condición combina estas partes: | Parte | Descripción | Ejemplo | | --- | --- | --- | | **Métrica** | Cualquier [métrica que Adapty Ads Manager rastrea](adapty-ads-manager-metrics) — gasto, instalaciones, trials, suscripciones, ingresos y más. | Spend | | **Ventana de tiempo** | El período durante el que se mide la métrica. | Yesterday | | **Comparación** | Cómo se compara la métrica con tu valor. | is greater than | | **Umbral** | El valor con el que comparas — una cantidad fija o el **Daily Budget** propio de la campaña. | Daily Budget | Combina varias condiciones con **And** o **Or** para una segmentación precisa — por ejemplo, gasto > Daily Budget **and** ROAS < 100%. ## Acciones disponibles y su configuración \{#available-actions-and-their-settings\} Cuando una campaña cumple tu condición, Adapty puede modificar su configuración ejecutando una de las siguientes acciones: | Acción | Qué hace | Cuándo usarla | Configuración | | --- | --- | --- | --- | | **Change daily budget** | Aumenta, reduce o establece el **presupuesto diario** de la campaña — lo máximo que puede gastar por día. | Reduce el presupuesto en campañas que gastan demasiado; auméntalo en las que escalan de forma rentable. | **Action type**: Increase by, Decrease by o Set to.<br/>**Value type**: $ (valor absoluto) o % (del presupuesto actual).<br/>**Limit** (opcional): Un límite superior al aumentar, o inferior al reducir — controla hasta dónde pueden mover el presupuesto las ejecuciones repetidas. | | **Enable campaign** | Reactiva una campaña pausada. | Recupera campañas que vuelven a rendir una vez que llegan los ingresos tardíos de trials y suscripciones. | Ninguna. | | **Pause campaign** | Pausa una campaña activa. | Detiene campañas que siguen gastando sin convertir. | Ninguna. | ## Crear una regla de campaña \{#create-a-campaign-rule\} Para empezar desde una plantilla, haz clic en **Templates** en el encabezado de **Automations** y selecciona **Decrease budget for low-performing campaigns**, luego revisa y guarda. Para crear una regla desde cero: 1. Ve a **Automations**, haz clic en **Create rule** y selecciona **For campaigns**. 2. Introduce un **Rule name**. 3. En **Apply to**, elige un [ámbito](#available-conditions) y selecciona las campañas. 4. En **Conditions**, añade una o más [condiciones](#available-conditions). Puedes combinar condiciones con operadores lógicos. 5. En **Action**, elige una [acción](#available-actions-and-their-settings) y configura sus opciones. 6. En **Schedule**, define con qué frecuencia se ejecuta la regla y su hora de inicio (UTC), o selecciona **Run immediately**. 7. Haz clic en **Save**. Tras guardar, la regla aparece en la pestaña **Automations**. Las columnas **Date last run** y **Date next run** muestran su planificación. Para confirmar los cambios de estado entre ejecuciones, abre **Automations > Logs**. Consulta [Automations](ads-manager-automations) para obtener instrucciones sobre cómo pausar, duplicar, eliminar una regla o ejecutarla de inmediato. --- # File: ads-manager-market-intelligence --- --- title: "Inteligencia de mercado en Adapty Ads Manager" description: "Descubre qué palabras clave usan tus competidores en Apple Ads en más de 50 países y añádelas directamente a tus campañas." --- Market Intelligence muestra las palabras clave en las que pujan tus competidores en Apple Ads, en más de 50 países. Los datos se agregan de los últimos 30 días y se actualizan diariamente. Úsalo para: - **Omite la campaña de Discovery**: Descubre en qué palabras clave pujan ya tus competidores en lugar de gastar presupuesto para averiguarlo. Empieza desde el primer día con una lista de palabras clave probada. - **Encuentra palabras clave de baja competencia**: Identifica términos long-tail donde los competidores tienen poco Share of Voice — menos competencia, menor coste por tap y mejor CPA. - **Protege tu marca**: Comprueba si los competidores pujan por el nombre de tu app y en qué países, y recupera ese tráfico. - **Entra en nuevos mercados con datos**: Antes de gastar un euro, revisa qué palabras clave utilizan los competidores en un país determinado. - **Descubre competidores que no conocías**: Busca por palabra clave para ver quién aparece orgánicamente en los términos de tu categoría y añádelos a tu análisis. ## Ejecutar un análisis de Market Intelligence \{#run-a-market-intelligence-analysis\} ### 1. Selecciona tu app \{#1-select-your-app\} En la barra lateral izquierda, ve a **Market Intelligence**. Selecciona la app que quieres analizar en el desplegable y haz clic en **Continue**. ### 2. Seleccionar competidores \{#select-competitors\} Añade los competidores que quieras analizar: - **Suggested competitors**: Adapty detecta posibles competidores según la categoría de tu app. Revisa la lista y selecciona los que quieras incluir. - **Búsqueda por palabra clave o nombre de app**: Escribe una palabra clave (por ejemplo, "budget tracker") o el nombre de una app en el campo de búsqueda. Cambia el país si es necesario y selecciona las apps de los resultados. Repite con distintas palabras clave para descubrir competidores en diferentes intenciones de búsqueda. - **Lista guardada**: Haz clic en **Create list** para guardar hasta 20 competidores y reutilizarlos en análisis futuros. También puedes cargar una lista creada anteriormente. Cuando hayas seleccionado tus competidores, haz clic en **Run analysis**. ### 3. Explora los resultados \{#3-explore-results\} Los resultados se organizan en cuatro pestañas: **Overview**, **Most Contested**, **By App** y **By Country**. #### Overview \{#overview\} La pestaña predeterminada muestra un resumen del análisis: - **Barra de estadísticas**: Total de competidores analizados, países con actividad en Apple Ads, palabras clave únicas encontradas en todos los mercados y la palabra clave más disputada. - **Palabras clave encontradas por país**: Un gráfico de barras que muestra el volumen de palabras clave por país en los 25 mercados principales. - **Top 10 de competidores por cobertura de palabras clave**: Una tabla clasificada con el recuento total de palabras clave de cada competidor, el Share of Voice promedio (Avg SOV) y los países donde están activos. #### Más Disputadas \{#most-contested\} Muestra las palabras clave en las que el mayor número de competidores están activos al mismo tiempo. Usa esta pestaña para encontrar términos de alta demanda en tu categoría y ver dónde se concentra más la competencia. Usa el campo de búsqueda para filtrar la lista por palabra clave. #### Por App \{#by-app\} Muestra los datos de palabras clave de cada competidor de forma individual. Usa esta pestaña para analizar en detalle la estrategia de palabras clave de una app concreta en distintos países. Haz clic en **Add filter** para filtrar por app, país o palabra clave. Para exportar los datos como CSV, haz clic en el icono de descarga. #### Por país \{#by-country\} Muestra los datos de palabras clave agrupados por mercado. Usa esta pestaña cuando quieras analizar el panorama competitivo de un país concreto antes de entrar o expandirte en él. Haz clic en **Add filter** para filtrar por app, país o palabra clave. Para exportar los datos como CSV, haz clic en el icono de descarga. ### 4. Añadir palabras clave a las campañas \{#add-keywords-to-campaigns\} Una vez identificadas las palabras clave que merece la pena probar, añádelas a tus campañas sin salir de la herramienta: 1. En la tabla de palabras clave, marca las casillas junto a las palabras clave que quieras usar. Para seleccionar todas las palabras clave visibles, usa la casilla del encabezado de la tabla. 2. Haz clic en **Add to campaign**. 3. Elige si añadirlas como palabras clave, palabras clave negativas o SKAG. Luego, selecciona la campaña y el grupo de anuncios de destino, establece el tipo de concordancia y la puja CPT, y confirma. ## Qué buscar \{#what-to-look-for\} Estos patrones merecen atención en los resultados: - **Términos long-tail con bajo Share of Voice**: Las palabras clave donde los competidores tienen una cuota de impresiones baja son menos disputadas. Suelen tener un coste por toque menor y mejores tasas de conversión porque la intención del usuario es más específica. - **Palabras clave que aún no están en tus campañas**: Busca términos que usen tus competidores pero que tú no hayas probado. Son palabras clave que ya han demostrado generar tráfico de Apple Ads en tu categoría. - **Cobertura de palabras clave de marca**: Busca el nombre de tu propia app. Si aparecen competidores, están pujando por tu marca. Añade esas palabras clave a tus campañas con pujas agresivas para proteger tu tráfico. - **Huecos por país**: Comprueba en qué países están activos tus competidores. Los mercados con poca o ninguna actividad de la competencia son más fáciles de entrar y requieren menos presupuesto para ganar presencia. --- # File: ads-manager-cpp-ab-tests --- --- title: "Pruebas A/B de CPP en Adapty Ads Manager" description: "Compara páginas de producto personalizadas en Apple Ads y encuentra la que mejor convierte." --- Las pruebas A/B de CPP te permiten comparar páginas de producto personalizadas (CPPs) entre sí dentro de Apple Ads. Seleccionas entre 2 y 4 páginas de producto, y [Adapty Ads Manager](adapty-ads-manager) distribuye el tráfico entre ellas, recopila datos de rendimiento y te indica cuál convierte mejor. Puedes incluir tu **página de producto predeterminada** como una de las variantes, para comprobar si una página personalizada rinde mejor que la actual. ## Requisitos previos \{#prerequisites\} Antes de crear una prueba A/B de CPP, asegúrate de lo siguiente: - **Apple Ads Manager está conectado**: Sigue la [guía de configuración](adapty-ads-manager-get-started) si aún no lo has hecho. - **El grupo de anuncios de origen tiene tráfico**: El grupo de anuncios que vayas a probar debe tener al menos 28 días de antigüedad e impresiones, toques e instalaciones durante ese período. Apple Ads Manager usa este historial para estimar la duración de la prueba y el tamaño de muestra necesario. - **Tienes al menos una página de producto personalizada**: Crea los CPP en App Store Connect primero. Apple Ads Manager los lee automáticamente. ## Crear una prueba A/B CPP \{#create-a-cpp-ab-test\} Para crear una prueba, en el menú lateral izquierdo ve a **CPP A/B Tests** y haz clic en **Create A/B Tests**. El asistente tiene cuatro pasos: **Ad Group(s)**, **Ad Creative(s)**, **Testing Method** y **Review**. ### 1. Grupo(s) de anuncios \{#1-ad-groups\} Introduce un **Test Name** y haz clic en **Select Ad Group** para seleccionar el grupo de anuncios cuyos CPPs quieres probar. Puedes seleccionar hasta cuatro grupos de anuncios de la misma campaña, pero solo si vas a probar un único creatividad publicitaria entre ellos. Para comparar varios CPPs, selecciona un único grupo de anuncios. ### 2. Creatividad/es del anuncio Selecciona los CPPs que quieres comparar. Puedes incluir la **Default Product Page** (marcada como **Control**) más hasta tres **Custom Product Pages**, para un total de 2 a 4 variantes. - **Default Product Page**: Haz clic en **+ Add Default** para incluir tu página de producto predeterminada como variante de control. - **Custom Product Pages**: Haz clic en **+ Select CPP** para elegir una página de producto personalizada desde App Store Connect. ### 3. Método de prueba \{#3-testing-method\} Configura cómo se ejecuta la prueba. Adapty Ads Manager calcula automáticamente la **Calculated Test Duration**, el **Start Time** y el **End Time** — los valores se actualizan cada vez que cambias uno de los tres ajustes que aparecen a continuación. #### Switch Time Preset \{#switch-time-preset\} Con qué frecuencia el sistema rota entre variantes. Si seleccionas un valor demasiado alto para el nivel de tráfico, el sistema lo reduce automáticamente. | Intervalo | Nivel de tráfico habitual | Duración del slot | Duración habitual del test | |--------------|----------------------------------------|-------------------|----------------------------| | **Hourly** | Alto (5.000+ impresiones por día) | 7 horas | Días | | **Daily** | Normal | 24 horas | Semanas | | **Weekly** | Bajo (menos de 400 impresiones al día) | 7 días | Meses | Un **slot** es el tiempo base que una variante se ejecuta antes de que el sistema considere cambiar a la siguiente. #### Precisión deseada \{#desired-precision\} La diferencia mínima en la tasa de conversión que la prueba puede detectar de forma fiable. Opciones: **1%**, **2%**, **3%**, **4%**, **5%**. Valor por defecto: **5%**. Una prueba con precisión del 1% detecta diferencias pequeñas, pero necesita más datos y tarda más en completarse. Una prueba con precisión del 5% termina antes, pero solo detecta diferencias mayores. | Precisión | Cuándo usarla | |-----------|------------------------------------------------------------------------------------------------| | 1–2% | Esperas diferencias pequeñas entre CPPs y tienes grupos de anuncios con mucho tráfico. | | 3–4% | Valor predeterminado equilibrado para la mayoría de las pruebas. | | 5% | Esperas un ganador claro y quieres resultados rápidamente. | #### Nivel de confianza \{#confidence-level\} Qué tan seguro quieres estar de que el resultado es real y no ruido aleatorio. Opciones: **80%**, **85%**, **90%**, **95%**, **99%**. Por defecto: **90%**. Un nivel de confianza más alto requiere más datos. | Confianza | Compromiso | |-----------|------------| | 80–85% | Termina antes, pero hay más posibilidades de que el resultado sea ruido. | | 90% | Valor predeterminado recomendado para la mayoría de las pruebas. | | 95–99% | El más conservador. Requiere más datos y más tiempo de prueba. | ### 4. Revisión \{#4-review\} Verifica el resumen: grupos de anuncios seleccionados, creatividades, método de prueba, duración, precisión y nivel de confianza; luego haz clic en **Start CPP A/B Tests**. Una vez iniciada la prueba, el sistema clona el grupo de anuncios para cada variante, activa la primera variante y el estado de la prueba cambia a **Running** en unos minutos. ## Monitorear una prueba en curso \{#monitor-a-running-test\} Para abrir la lista de pruebas, ve a **CPP A/B Tests** en la barra lateral izquierda. Las cuatro pestañas en la parte superior de la página filtran las pruebas por estado: - **Live**: Pruebas en ejecución actualmente. - **Completed**: Pruebas que han finalizado. - **Draft**: Pruebas que aún no se han iniciado. - **Archive**: Pruebas antiguas que ya no necesitas en la vista principal. Cada tarjeta de prueba muestra su nombre, estado, intervalo de cambio, precisión deseada y el tiempo que lleva en ejecución. Haz clic en **View metrics** para expandir la tabla de variantes. ### Rendimiento de variantes \{#variant-performance\} La tabla de variantes compara el rendimiento de todas las variantes de la prueba: | Columna | Descripción | |--------------------------|-----------------------------------------------------------------------------------------------------| | **Variant Name** | El CPP que se está probando. La variante A es siempre la primera variante que añadiste. | | **Confidence Level** | Qué tan cerca está la variante del tamaño de muestra requerido, como porcentaje de 0 a 100. | | **Impressions** | El número de veces que Apple mostró un anuncio para esta variante. | | **TTR** | Tasa de clics: toques divididos entre impresiones. | | **Tap → Download CR** | Conversión de toque a descarga. | | **CPT** | Coste medio por toque. | | **Avg CPA (Tap-Through)**| Coste medio por adquisición basado en descargas por toque. | | **Spend** | Gasto total atribuido a la variante. | | **Revenue** | Ingresos totales atribuidos a la variante. | | **ROAS** | Retorno sobre el gasto publicitario: ingresos divididos entre gasto. | Hasta que cada variante tenga impresiones comparables, Adapty Ads Manager no destaca a un ganador. Mientras los datos siguen llegando, verás un banner encima de la tabla: **Winner highlighting is paused — variants don't have comparable impressions yet.** ### Métricas detalladas \{#detailed-metrics\} Para un análisis más profundo de la prueba, haz clic en **View metrics** para abrir la página de métricas detalladas. Incluye curvas de retención por cohorte, comparación de ARPPU y una tabla de métricas agrupadas en dos secciones: - **Top of funnel · Apple Search Ads**: TTR, Download Rate, CPM, CPT y Avg CPA por variante. - **Bottom of funnel · Monetization**: Paid users, Paid CR, Cost per Paid, ARPPU, Revenue y ROAS por variante. La columna **Winner** muestra qué variante lidera en cada métrica. Una variante se marca como ganadora global solo cuando lidera en la métrica principal y alcanza un nivel de confianza de al menos el 95%. Para ver las definiciones de las métricas, consulta [Métricas en Adapty Ads Manager](adapty-ads-manager-metrics). ## Detener una prueba \{#stop-a-test\} Puedes detener una prueba en cualquier momento. La prueba quedará marcada como **Stopped**, el grupo de anuncios original se restaurará y los grupos de anuncios clonados se pausarán. Para detener una prueba en ejecución: 1. Ve a **CPP A/B Tests** en la barra lateral izquierda. 2. Haz clic en **Stop A/B test** en la tarjeta de la prueba, o abre la prueba y haz clic en **Stop Test**. 3. Confirma en el diálogo **Stop A/B Test?**. :::important Detener una prueba es definitivo: no puedes reanudarla. Los resultados recopilados hasta ese momento seguirán disponibles en la pestaña **Completed**. ::: ## Estados de las pruebas \{#test-statuses\} Cada prueba pasa por un conjunto fijo de estados: | Estado | Significado | |---------------|------------------------------------------------------------------------------------------| | **Draft** | Prueba creada pero no iniciada. Aún puedes editarla. | | **Starting** | Configuración en curso: el sistema está clonando grupos de anuncios y creando anuncios. | | **Running** | La prueba está activa. Las variantes se rotan y se recopilan las métricas. | | **Completed** | La duración programada expiró o se alcanzó la confianza. El grupo de anuncios original se restaura. | | **Stopped** | Detuviste la prueba manualmente. El grupo de anuncios original se restaura. | | **Failed** | La configuración falló o se produjeron demasiados errores consecutivos. Puedes reiniciar una prueba fallida. | ## Cómo funciona \{#how-it-works\} Adapty Ads Manager utiliza el método **Ad Group Switch**: 1. Cuando comienza la prueba, el sistema clona el grupo de anuncios de origen una vez por variante. Cada clon apunta a un CPP diferente (uno de ellos puede ser tu página predeterminada). 2. Solo un clon se ejecuta a la vez. El sistema rota qué clon está activo según un horario fijo (por horas, días o semanas). 3. El grupo de anuncios original se pausa mientras se ejecuta la prueba. Se restaura a su estado anterior cuando esta termina. 4. Adapty Ads Manager recopila impresiones, toques y descargas por variante, y realiza un seguimiento de cuánto se acerca cada variante a una muestra estadísticamente significativa. 5. La prueba finaliza automáticamente cuando cada variante tiene suficientes datos o cuando se alcanza la duración programada. ## Qué esperar mientras se ejecuta una prueba \{#what-to-expect-while-a-test-runs\} Hay algunas cosas que conviene saber sobre cómo se comporta una prueba A/B en ejecución en el dashboard: - **Las variantes no cambian en un intervalo fijo**: El intervalo de cambio es una referencia, pero el Gestor de Anuncios de Adapty ajusta los tiempos para que cada variante reciba una parte justa de impresiones. Una variante puede permanecer activa más de un intervalo si va por detrás en impresiones. - **La hora de fin puede adelantarse**: Si a una variante le faltan datos cuando se acerca el final programado, la prueba se extiende automáticamente para seguir recopilando taps. La nueva hora de fin aparece en la tarjeta de la prueba. - **Cuando la prueba finaliza, se restaura el grupo de anuncios original**: Todos los grupos de anuncios clonados se pausan y el grupo de anuncios de origen vuelve a su estado previo a la prueba. Los resultados siguen disponibles en la pestaña **Completed**. --- # File: ads-manager-settings --- --- title: "Configuración en Adapty Ads Manager" description: "Configura los ajustes en Adapty Ads Manager." --- Ve a **Settings** en la esquina inferior izquierda del dashboard de Adapty Ads Manager para configurar los ajustes de tu cuenta. ## Grupos de campañas \{#campaign-groups\} En la pestaña **Campaign groups**, puedes ver todas las cuentas de Apple Ads conectadas a Adapty Ads Manager y añadir nuevas. Si conectas varias cuentas de Apple Ads, todos sus datos analíticos se agregarán en un único dashboard de Adapty Ads Manager. Para añadir una nueva cuenta de Apple Ads, haz clic en **Connect Apple Ads account** y sigue la [guía](adapty-ads-manager-get-started). ## Gestionar suscripción \{#manage-subscription\} En la pestaña **Manage subscription**, puedes ver tu plan de suscripción actual y actualizar tu método de pago. ## Ajustes de usuario \{#user-settings\} En la pestaña **User settings**, puedes activar el interruptor **Hide Paused by Default**. Cuando está activado, las campañas, grupos de anuncios y palabras clave pausadas se ocultarán para que puedas centrarte en los datos de rendimiento activos. Evita activar esta opción si experimentas con frecuencia lanzando y pausando distintas campañas, grupos de anuncios y palabras clave, ya que puede que necesites acceder a los elementos pausados en cualquier momento. --- # File: adapty-user-acquisition --- --- title: "Atribución de Adapty" description: "Elimina la necesidad de MMPs y calcula toda la economía de tu app en un solo lugar." --- <CustomDocCardList ids={['user-acquisition', 'ua-analytics', 'ua-integrations', 'ua-tracking-links', 'ua-deferred-data']} /> La atribución de Adapty es una solución de atribución que conecta campañas publicitarias con instalaciones de la app e ingresos por suscripción combinando datos de plataformas de anuncios, enlaces de seguimiento y tu app. Ofrece un dashboard unificado de análisis de marketing que consolida todos tus datos de adquisición en un solo lugar. - Calcula el ROAS (retorno sobre el gasto publicitario) en todos tus canales - Visualiza toda la economía de tu app en un solo lugar - Obtén datos de atribución precisos para tomar mejores decisiones - Analiza el rendimiento de cohortes y el comportamiento de los usuarios a lo largo del tiempo :::tip ¿Quieres saber más sobre cómo la atribución de Adapty puede servirte? [Reserva una llamada](https://calendly.com/tnurutdinov-adapty/30min) con nosotros. ::: ## ¿Por qué elegir Adapty Attribution? \{#why-choose-adapty-attribution\} Medir el rendimiento de la adquisición de usuarios es complicado. Los datos suelen estar dispersos en distintas plataformas, la atribución se complica con los cambios de privacidad, y crear soluciones propias requiere mucho tiempo. Adapty Attribution ofrece atribución integrada y analíticas unificadas en un único dashboard de marketing. Todas tus métricas de adquisición —desde el gasto publicitario hasta las instalaciones y los ingresos por suscripción— se consolidan automáticamente y se actualizan en tiempo real. Nada de reconciliar datos en hojas de cálculo ni de cambiar entre múltiples herramientas. Puedes centrarte en hacer crecer tu app en lugar de gestionar sistemas de datos. ## Cómo funciona \{#how-it-works\} Adapty Attribution atribuye las instalaciones de la app y los ingresos por suscripciones a campañas publicitarias combinando datos de plataformas de anuncios, enlaces de seguimiento y tu app. En términos generales: - Las plataformas de anuncios proporcionan la estructura de campañas y el gasto publicitario - Los enlaces de seguimiento generados en Adapty Attribution llevan el contexto de la campaña desde la web hasta la instalación de la app - El SDK envía eventos de instalación e ingresos desde tu app El flujo de atribución funciona de la siguiente manera: 1. **Se genera un enlace de seguimiento en Adapty Attribution y se añade a una campaña publicitaria.** El enlace contiene parámetros de campaña como la plataforma, la campaña, el conjunto de anuncios y el creativo. 2. **Un usuario hace clic en el anuncio e instala la app desde el store.** El usuario es redirigido a través del enlace de seguimiento e instala la app desde el App Store o Google Play. 3. **La app envía un evento de instalación a Adapty.** En el primer lanzamiento, el SDK de Adapty envía un evento de instalación. Adapty extrae los parámetros de campaña asociados a esta instalación. 4. **La instalación se atribuye a una campaña.** Con los parámetros de campaña del enlace de seguimiento, Adapty asocia la instalación con la campaña que la generó. 5. **El gasto publicitario y los ingresos quedan vinculados.** Adapty obtiene los datos de gasto publicitario de las plataformas de anuncios compatibles (actualmente, Meta Ads y TikTok for Business) y relaciona los eventos de suscripción y compra con las instalaciones atribuidas. Como resultado, Adapty proporciona métricas a nivel de campaña como instalaciones, ingresos, LTV y ROAS en un dashboard de análisis unificado. Puedes analizar cohortes, hacer seguimiento del rendimiento a lo largo del tiempo y tomar decisiones de optimización basadas en datos, todo sin tener que reconciliar manualmente datos de distintas fuentes. :::tip Los enlaces de seguimiento también pueden incluir parámetros personalizados, lo que permite a tu app gestionar [deferred deep links](ua-deferred-data) y reaccionar a los datos de campaña al procesar el evento de instalación. ::: --- # File: user-acquisition --- --- title: "Primeros pasos con Adapty Attribution" description: "Conecta Adapty Attribution para combinar el gasto publicitario con los ingresos por suscripciones y ver toda la economía de tu app en un solo lugar." --- Adapty Attribution te ayuda a conectar el gasto publicitario con los ingresos por suscripciones en campañas web-to-app, dándote una visión completa de la economía de tu app en un solo lugar. Para ver tus datos de ingresos en Adapty Attribution, primero debes activar la integración en el Adapty Dashboard. No necesitas introducir claves de API, tokens ni identificadores. Solo tienes que actualizar y configurar el SDK. :::important Adapty Attribution está disponible con: - SDK de iOS, Android y Flutter versión 3.9.1 o superior. - SDK de React Native y Capacitor versión 3.10.0 o superior. - SDK de Unity versión 3.12.0 o superior. - SDK de Kotlin Multiplatform versión 3.15.0 o superior. ::: ## Antes de empezar \{#before-you-start\} Para conectar los datos de ingresos con el rendimiento de tus campañas, permite que Adapty registre tus compras: - Si **ya tienes compras in-app implementadas con Adapty**, no necesitas hacer nada más en esta etapa. - Si **aún no tienes compras in-app implementadas y quieres usar Adapty**, completa los pasos de la [guía de inicio rápido](quickstart) para delegar el manejo de compras a Adapty. - Si **ya tienes compras in-app implementadas sin Adapty** y no planeas migrar a Adapty, [instala el SDK de Adapty para tu plataforma en el modo observador](implement-observer-mode). En esta etapa solo necesitas añadir el SDK a tu proyecto, activarlo con el modo observador habilitado y reportar las transacciones: Esta configuración habilita la atribución web-to-app: - Cuando los usuarios instalan tu app, el SDK de Adapty obtiene los detalles de instalación a partir de los parámetros del enlace, para que Adapty Attribution pueda obtener los detalles de la campaña. - El SDK de Adapty conoce todos los eventos relacionados con ingresos dentro de la app y puede atribuirlos a campañas web. ## Paso 1. Abre Adapty Attribution \{#step-1-open-adapty-attribution\} :::important Si **Attribution** no aparece al hacer clic en el logo de Adapty, borra las cookies y los datos del sitio adapty.io en la configuración de tu navegador y vuelve a cargar la página. ::: Haz clic en el logo de Adapty en la cabecera y elige **Attribution**. Los eventos de suscripción empiezan a fluir hacia Adapty Attribution de forma automática. Los datos de campaña aparecen una vez que conectas una fuente de datos en el Paso 2. Para pausar la entrega de eventos, abre **Integrations > Adapty** en el Adapty Dashboard y desactiva el interruptor. ### Eventos compatibles \{#supported-events\} Por defecto, Adapty envía tres grupos de eventos a User Acquisition: - Trials - Suscripciones - Problemas Puedes consultar la lista completa de eventos compatibles [aquí](events). <img src="/assets/shared/img/events-ua.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 2. Conecta tu plataforma de anuncios y añade enlaces de seguimiento \{#step-2-connect-your-ad-platform-and-add-tracking-links\} Adapty usa enlaces de seguimiento para conectar las instalaciones de la app con los datos de campaña. Debes usar un enlace de seguimiento como URL de destino en cada campaña de anuncios que quieras medir en Adapty Attribution. Si publicas anuncios en varias plataformas, configura los enlaces de seguimiento para cada plataforma por separado. Hay dos formas en que Adapty trabaja con las plataformas de anuncios: - **Integraciones nativas (Meta Ads, TikTok Ads).** Adapty se conecta directamente con la plataforma publicitaria. Los enlaces de seguimiento se generan automáticamente y los parámetros de campaña se rellenan de forma dinámica según dónde se use el enlace. Puedes usar el mismo enlace en diferentes campañas, conjuntos de anuncios o creatividades, y Adapty recibirá automáticamente los datos correctos de campaña y gasto publicitario. - **Solo enlaces de seguimiento (el resto de plataformas de anuncios).** Adapty no se conecta a la plataforma publicitaria. Los enlaces de seguimiento se crean manualmente y todos los parámetros de campaña deben definirse explícitamente al crear el enlace. Los datos de gasto publicitario no están disponibles en estas plataformas. <Tabs> <TabItem value="meta" label="Meta Ads" default> Para crear un enlace de seguimiento para Meta Ads: 1. Ve a [Integrations > Meta](https://app.adapty.io/ua/integrations/facebook/accounts) en el Adapty Attribution Dashboard y haz clic en **Continue with Facebook**. 2. Inicia sesión con tu cuenta de Facebook y haz clic en **Continue**. 3. Revisa los permisos solicitados y haz clic en **Save**. 4. Cambia a la pestaña **Web campaigns** y haz clic en **Create campaign**. Selecciona la app y haz clic en **Save**. 5. En la pestaña **General**, despliega la sección **iOS** y/o **Android** y pega las URLs de la aplicación en App Store y/o Google Play. Luego, haz clic en **Save**. 6. Copia el valor del campo **Click link** para **un solo enlace** o para un enlace específico de plataforma. A continuación, en Meta Ads Manager, abre tu anuncio y pega este enlace como URL de destino. :::important En el campo **Website URL**, pega `https://api-ua.adapty.io/api/v1/attribution/click`. Pega el resto del enlace en el campo **URL parameters** de la sección **Tracking**. Esto ayudará a que tu anuncio de Meta sea aprobado. Consulta más [recomendaciones para configurar tus anuncios en Meta Ads Manager](meta-create-campaign). ::: 7. Ahora, cuando publiques tu anuncio en Meta Ads, sus datos estarán disponibles para su análisis en el dashboard de Adapty Attribution. </TabItem> <TabItem value="tiktok" label="TikTok for Business"> Para crear un enlace de seguimiento para TikTok for Business: 1. Ve a [Integrations > TikTok Ads](https://app.adapty.io/ua/integrations/tiktok/accounts) en el Adapty Attribution Dashboard y haz clic en **Continue with TikTok**. 2. Inicia sesión con tu cuenta de TikTok y haz clic en **Continue**. 3. Revisa los permisos solicitados y haz clic en **Save**. 4. Cambia a la pestaña **Web campaigns** y haz clic en **Create campaign**. Selecciona la app y haz clic en **Save**. 5. En la pestaña **General**, expande la sección **iOS** y/o **Android** y pega las URLs de la aplicación de App Store y/o Google Play. Luego haz clic en **Save**. 6. Copia el valor del campo **Click link** de **un enlace** o de un enlace específico para una plataforma. Después, en TikTok Ads Manager, al crear tu anuncio, pega este valor en el campo **Tracking URL** dentro de la sección **Advanced Settings**. Esto permitirá que Adapty conecte las instalaciones y compras con los anuncios en TikTok. Consulta la [guía para configurar tu campaña en TikTok Ads](tiktok-create-campaign). 7. Ahora, cuando lances tu anuncio en TikTok for Business, sus datos estarán disponibles para su análisis en el dashboard de atribución de Adapty. </TabItem> <TabItem value="others" label="Otras plataformas de anuncios"> Para crear un enlace de seguimiento para otras plataformas de anuncios: 1. En el dashboard de Adapty Attribution, ve a **Tracking links** en el menú lateral. Allí, haz clic en **Create link**. 2. Selecciona tu app de la lista y haz clic en **Next**. 3. Rellena los parámetros del enlace para vincularlo con la campaña y el anuncio que quieres rastrear. 4. Por defecto, estás creando un One Link. Detecta automáticamente la plataforma del usuario y lo redirige al App Store o Google Play tras registrar el clic. Si prefieres usar URLs de redirección separadas para cada plataforma, desmarca la casilla **One Link** e introduce manualmente los enlaces de la store para cada plataforma. 5. Haz clic en **Create**. 6. Abre la página de tu enlace de seguimiento y copia el **Click link** de una de las secciones: - **One link** – usa este enlace para rastrear clics y redirigir automáticamente a los usuarios a la store correcta. - **iOS link** o **Android link** — versiones opcionales por plataforma si quieres enlaces separados para cada store. 7. Ve a tu plataforma de anuncios y pega el enlace en tu anuncio como URL de destino. </TabItem> </Tabs> ## Paso 3. Lanza tu campaña web-to-app y visualiza los resultados \{#step-3-launch-your-web-to-app-campaign-and-view-results\} Una vez que tu campaña esté activa y los usuarios comiencen a instalar tu app, Adapty empieza a atribuir las instalaciones y los ingresos a tus campañas. En el [dashboard de analíticas de Adapty UA](ua-analytics), verás métricas a nivel de campaña como: - Instalaciones y conversiones - Ingresos por suscripción y compras - Desglose del rendimiento por plataforma publicitaria, campaña, conjunto de anuncios y creatividad Las métricas aparecen en cuanto se reciben los eventos de instalación e ingresos desde tu app. Los datos de gasto en publicidad están disponibles para las plataformas con integraciones nativas. ## Más información \{#learn-more\} Continúa con la documentación detallada sobre el análisis de atribución de Adapty y guías prácticas para ejecutar campañas en las principales plataformas publicitarias: - [**Analíticas en Adapty Attribution**](ua-analytics): Descubre cómo usar el dashboard de analíticas de forma efectiva. - [**Métricas en Adapty Attribution**](ua-metrics): Explora las métricas disponibles para el análisis de adquisición de usuarios. - [**Integraciones**](ua-integrations): Consulta las plataformas publicitarias e integraciones compatibles con Adapty Attribution. - [**Lanzar anuncios en Meta Ads Manager**](meta-create-campaign): Aprende a configurar y lanzar campañas en Meta Ads Manager. - [**Lanzar anuncios en TikTok for Business**](tiktok-create-campaign): Aprende a configurar y lanzar campañas en TikTok for Business. --- # File: ua-metrics --- --- title: "Métricas en Adapty UA" description: "Aprende sobre las métricas disponibles en Adapty UA." --- Adapty User Acquisition ofrece **métricas** completas para medir el rendimiento de las campañas y el comportamiento de los usuarios. Estas métricas están disponibles como valores estándar, y algunas también se ofrecen como **métricas de cohorte** para el análisis temporal de grupos de usuarios. ## Métricas estándar \{#standard-metrics\} | **Métrica** | Descripción | Cohorte | |--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| | **Spend** | La suma del coste de cada clic de un cliente en tu anuncio. | No | | **Impressions** | El número de veces que se mostró tu anuncio durante el período seleccionado. | No | | **Clicks** | El número de veces que los usuarios hicieron clic en tu anuncio durante el período del informe. | No | | **CPI** | **CPI (Coste por instalación)** es el importe que pagas por cada instalación. <br/>**Fórmula**: `Spend / Installs` | No | | **CPC** | **CPC (Coste por clic)** es el importe que pagas por cada clic en tu anuncio. <br/>**Fórmula**: `Spend / Clicks` | No | | **CPM** | **CPM (Coste por mil)** es el importe que pagas por cada mil impresiones del anuncio. <br/>**Fórmula**: `Spend / (Impressions / 1000)` | No | | **ICR** | **ICR (Tasa de conversión de instalación)** es el porcentaje de clics en el anuncio que resultaron en instalaciones. <br/>**Fórmula**: `(Installs / Clicks) × 100%` | No | | **IPM** | **IPM (Instalaciones por mil)** representa el número de instalaciones por cada mil impresiones del anuncio. <br/>**Fórmula**: `(Installs / Impressions) × 1000` | No | | **CTR** | **CTR (Tasa de clics)** es el porcentaje de impresiones que resultaron en clics. <br/>**Fórmula**: `(Clicks / Impressions) × 100%` | No | | **Inline link clicks** | El número de veces que los usuarios hicieron clic en enlaces en línea de los creativos de tu anuncio o en la página de la app. | No | | **Cost per inline link click** | El importe medio que pagas por cada clic en un enlace en línea. <br/>**Fórmula**: `Spend / Inline Link Clicks` | No | | **Inline link click CTR** | El porcentaje de impresiones que resultaron en clics en enlaces en línea. <br/>**Fórmula**: `(Inline Link Clicks / Impressions) × 100%` | No | | **Installs** | El número total de usuarios que instalaron tu app (incluidas reinstalaciones) durante el período del informe. | No | | **Revenue** | El importe total generado por las compras vinculadas a esta campaña (antes de la comisión del store) durante el período seleccionado. | Sí | | **ROAS** | **ROAS (Retorno sobre el gasto publicitario)** es el ingreso generado por tus anuncios dividido entre el gasto en publicidad, expresado como porcentaje. <br/> **Fórmula**: `(Revenue / Spend) × 100% si Spend > 0, en caso contrario 0%` | Sí | | **ARPU** | **ARPU (Ingreso medio por usuario)** es el ingreso promedio por usuario en la cohorte. <br/>**Fórmula**: `Revenue / Users` | Sí | | **LTV** | **LTV (Valor de vida del usuario)** es el ingreso medio atribuido a un usuario a lo largo de su vida. <br/>**Fórmula**: `Revenue / Installs` | No | | **Cost per trial** | El importe medio que pagas por cada prueba iniciada. <br/>**Fórmula**: `Spend / Count trial started` | No | | **Cost per subscription** | El importe medio que pagas por cada producto de suscripción comprado. <br/>**Fórmula**: `Spend / Count subscription started` | No | | **Count subscription events** | El grupo de métricas para contar los eventos relacionados con suscripciones durante el período del informe. Las métricas son: <br/>- Count subscription started<br/>- Count subscription renewed<br/>- Count subscription renewal cancelled<br/>- Count subscription renewal reactivated<br/>- Count subscription expired<br/>- Count [subscription deferred](https://adapty.io/glossary/subscription-purchase-deferral/)<br/>- Count subscription refunded | Sí | | **Count trial events** | El grupo de métricas para contar los eventos relacionados con pruebas durante el período del informe. Las métricas son: <br/>- Count trial started<br/>- Count trial converted<br/>- Count trial expired<br/>- Count trial renewal reactivated | Sí | | **Count billing issue detected** | El número de problemas de facturación detectados durante el período del informe. | Sí | | **Count entered grace period** | El número de suscripciones que entraron en un período de gracia por problemas de facturación. | Sí | | **Count non-subscription events** | El grupo de métricas para contar los eventos no relacionados con suscripciones durante el período del informe. Las métricas son: <br/>- Count non-subscription purchased<br/>- Count non-subscription refunded | Sí | | **Subscription events rate** | Métricas que muestran la tasa de eventos relacionados con suscripciones en relación con las instalaciones de la app durante el período del informe. Las métricas son: <br/>- Rate subscription started<br/>- Rate subscription renewed<br/>- Rate subscription renewal cancelled<br/>- Rate subscription renewal reactivated<br/>- Rate subscription expired<br/>- Rate [subscription deferred](https://adapty.io/glossary/subscription-purchase-deferral/)<br/>- Rate subscription refunded | Sí | | **Trial events rate** | Métricas que muestran la tasa de eventos relacionados con pruebas en relación con las instalaciones de la app durante el período del informe. Las métricas son: <br/>- Rate trial started<br/>- Rate trial converted<br/>- Rate trial expired<br/>- Rate trial renewal reactivated | Sí | | **Rate billing issue detected** | La tasa de problemas de facturación en relación con las instalaciones de la app durante el período del informe. | Sí | | **Rate entered grace period** | La tasa de suscripciones que entraron en un período de gracia en relación con las instalaciones de la app durante el período del informe. | Sí | | **Non-subscription events rate** | Métricas que muestran la tasa de eventos no relacionados con suscripciones en relación con las instalaciones de la app durante el período del informe. Las métricas son: <br/>- Rate non-subscription purchased<br/>- Rate non-subscription refunded | Sí | ## Métricas predichas \{#predicted-metrics\} Las métricas predichas proyectan el rendimiento futuro de una cohorte a partir de los datos históricos de la propia app. Están disponibles en varios períodos de cohorte, incluyendo D30, D60, D90, D180 y D360, además de períodos personalizados que puedes añadir en días. Para conocer cómo se calculan estos valores, consulta [Métricas predichas en Adapty Attribution](ua-predicted-metrics). | **Métrica** | Descripción | Cohorte | |---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| | **pRevenue** | Ingresos totales previstos que se espera que genere una cohorte hasta el horizonte objetivo. Se modela a partir de la retención histórica de cohortes de la app. | Sí | | **pROAS** | Retorno previsto sobre el gasto publicitario para el mismo horizonte. **Fórmula**: `(pRevenue / Spend) × 100%` | Sí | | **pAdProfit** | Ingresos previstos descontado el gasto publicitario durante el horizonte. **Fórmula**: `pRevenue − Spend` | Sí | | **pARPU** | Ingresos medios previstos por instalación durante el horizonte (LTV predicho). **Fórmula**: `pRevenue / Installs` | Sí | | **pARPPU** | Ingresos medios previstos por usuario de pago durante el horizonte. **Fórmula**: `pRevenue / paying users at d{N}`, donde `d{N}` corresponde al horizonte seleccionado. | Sí | --- # File: ua-predicted-metrics --- --- title: "Métricas predichas en Adapty Attribution" description: "Predice ingresos, ROAS, beneficio publicitario y LTV para cohortes en Adapty Attribution." --- :::important Este artículo cubre las predicciones en Adapty Attribution únicamente. Para el LTV predicho y los ingresos en la página de análisis de cohortes, consulta [Predicciones en cohortes](predicted-ltv-and-revenue). ::: Adapty Attribution proyecta los ingresos futuros y la economía unitaria de cada cohorte, para que puedas comparar campañas antes de que tengan tiempo de madurar. Las predicciones se generan a partir de los datos históricos de cohortes de la propia app y se actualizan diariamente. Son más útiles para evaluar cohortes recientes que aún no han completado ciclos de suscripción largos. ## Métricas predichas \{#predicted-metrics\} | **Métrica** | Descripción | |---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **pRevenue** | Ingresos totales previstos que se espera que genere una cohorte hasta el horizonte objetivo. Modelado a partir de la retención histórica de cohortes de la app. | | **pROAS** | Retorno previsto del gasto publicitario durante el mismo horizonte. **Fórmula**: `(pRevenue / Spend) × 100%` | | **pAdProfit** | Ingresos previstos descontado el gasto publicitario durante el horizonte. **Fórmula**: `pRevenue − Spend` | | **pARPU** | Ingresos medios previstos por instalación durante el horizonte (LTV previsto). **Fórmula**: `pRevenue / Installs` | | **pARPPU** | Ingresos medios previstos por usuario de pago durante el horizonte. **Fórmula**: `pRevenue / paying users at d{N}`, donde `d{N}` corresponde al horizonte seleccionado. | `pRevenue` es el valor base. Las otras cuatro métricas se derivan de él usando datos observados de la cohorte — gasto, instalaciones y usuarios de pago — sin ejecutar el modelo por separado. Cada métrica predicha está disponible en varios períodos de cohorte: D0, D3, D7, D30, D60, D90, D180 y D360. También puedes añadir un período personalizado en días. El período define hasta qué punto en el futuro se proyecta el valor desde la fecha de instalación de la cohorte. ## Cómo se calculan las predicciones \{#how-predictions-are-calculated\} Las predicciones se construyen a partir de las cohortes históricas de cada app. El modelo mide cómo crecieron los ingresos de cohortes pasadas tras su día de referencia y luego proyecta la cohorte actual hacia adelante usando la misma trayectoria. ### Día de referencia \{#baseline-day\} Las predicciones solo están disponibles una vez que la cohorte alcanza su día de referencia. El día de referencia es el primer día en el que, por lo general, se ha recibido el 90% de los ingresos iniciales de una cohorte. Los ingresos iniciales incluyen inicios de suscripción, conversiones de prueba y compras únicas; las renovaciones no cuentan para este umbral. El día de referencia depende de la duración del período de prueba de la app y de la combinación de productos: - **Apps sin pruebas gratuitas**: El día de referencia suele caer en los primeros días tras la instalación. - **Apps con pruebas cortas**: El día de referencia suele situarse poco después de que finalice la prueba. - **Apps con pruebas más largas**: El día de referencia puede estar una semana o más después de la instalación, ya que la mayor parte de los ingresos iniciales solo se materializan cuando termina la prueba. ### Proyección por tipo de suscripción \{#projection-by-subscription-type\} En el día de referencia, los ingresos iniciales de la cohorte se dividen en cinco categorías: suscripciones mensuales, anuales, semanales y trimestrales, más compras únicas. Cada categoría se proyecta hacia adelante de forma independiente usando una trayectoria medida a partir de las cohortes pasadas de la app. El modelo da más peso a las cohortes recientes y a las cohortes con una economía comparable, como un ingreso por transacción similar y una combinación de productos parecida. Por tanto, una predicción refleja cómo han rendido realmente las cohortes más recientes y más similares de la app. ## Cuándo están disponibles las predicciones \{#when-predictions-are-available\} Una predicción solo se muestra si la cohorte tiene suficientes datos para generarla. Cuando no es posible producir un valor, la columna muestra un guion largo (`—`) en su lugar. Razones habituales por las que una predicción no está disponible: - **La cohorte no ha alcanzado su día de referencia**: El modelo necesita que los ingresos iniciales de la cohorte se estabilicen antes de poder proyectar hacia adelante. - **Datos históricos insuficientes para la app**: Si la app no tiene suficientes cohortes pasadas del tipo de suscripción relevante, el modelo no puede ajustar tasas de retención fiables. Las predicciones se recalculan diariamente con los datos transaccionales más recientes, por lo que los valores de una misma cohorte pueden variar a medida que se registra más facturación. --- # File: ua-tracking-links --- --- title: "Links de seguimiento en Adapty Attribution" description: "Realiza seguimiento de tus campañas y mide su éxito en cualquier lugar." --- Los enlaces de seguimiento te permiten medir el origen de tus usuarios y vincular las instalaciones con campañas publicitarias. Cuando alguien hace clic en tu anuncio, Adapty registra el clic y lo asocia después con el evento de instalación enviado por el SDK. Así puedes ver qué canales, campañas, conjuntos de anuncios y anuncios generan más ingresos en tu [página de Analytics](ua-analytics). Puedes crear dos tipos de enlaces de seguimiento: - **One link** — un enlace universal que detecta automáticamente la plataforma del usuario, registra el clic y lo redirige a la App Store o Google Play. - **Enlaces específicos por store** — enlaces orientados a plataforma que registran el clic y redirigen automáticamente a los usuarios al App Store o Google Play. También puedes añadirles parámetros de deep link diferidos. ## Crear enlaces de seguimiento \{#create-tracking-links\} Para crear un enlace de seguimiento: 1. En el dashboard de Adapty Attribution, ve a **Tracking links** desde el menú lateral. Allí, haz clic en **Create link**. 2. Selecciona tu app de la lista y haz clic en **Next**. 3. Rellena los parámetros del enlace para asociarlo con la campaña y el anuncio que quieres rastrear. | Parámetro | Descripción | |-------------------|------------------------------------------------------------------------------------------------------| | **Name** | El nombre interno del enlace de seguimiento. | | **Channel** | La fuente de tráfico, como Meta, Reddit o TikTok. Se usa para agrupar campañas en los análisis. | | **Campaign ID** | El identificador único de la campaña en tu plataforma publicitaria. | | **Campaign name** | El nombre legible de la campaña. | | **Ad set ID** | El identificador único del conjunto de anuncios (grupo de anuncios) en tu plataforma publicitaria. | | **Ad set name** | El nombre del conjunto de anuncios. | | **Ad ID** | El identificador único del anuncio creativo individual. | | **Ad name** | El nombre del anuncio creativo o variación. | 4. Por defecto, estás creando un One Link. Este detecta automáticamente la plataforma del usuario y lo redirige al App Store o Google Play tras registrar el clic. Si prefieres usar URLs de redireccionamiento independientes para cada plataforma, desmarca la casilla **One Link** e introduce manualmente los enlaces a las stores de cada plataforma. 5. Haz clic en **Create**. 6. Abre la página de tu enlace de seguimiento y copia el **Click link** de una de las secciones: - **One link** – usa este enlace para rastrear clics y redirigir automáticamente a los usuarios al store correcto. - **iOS link** o **Android link** — versiones opcionales específicas por plataforma si quieres enlaces separados para cada store. :::tip También puedes configurar parámetros de enlace adicionales para [trabajar con datos diferidos](ua-deferred-data). Por ejemplo, puedes implementar deep linking diferido. ::: 7. Ve a tu plataforma de publicidad y pega el enlace en tu anuncio como URL de destino del anuncio. Ahora, las instalaciones de la app se asociarán con los anuncios y campañas de los que provienen, para que puedas medir la efectividad de las campañas en la página **Analytics**. --- # File: ua-deferred-data --- --- title: "Deeplinks diferidos en Adapty Attribution" description: "Configura deeplinks diferidos en Adapty Attribution." --- Los deeplinks diferidos te permiten pasar datos personalizados a tu app cuando los usuarios la instalan después de hacer clic en tus anuncios. Por ejemplo, puedes dirigirlos a una ubicación específica dentro de la app justo después de que la instalen y la abran por primera vez. Así es como funciona: 1. Cuando un usuario hace clic en tu anuncio, Adapty guarda los datos del clic. 2. Cuando Adapty registra el evento de instalación, obtiene los datos diferidos del clic. 3. Después de que el usuario instala tu app y la abre por primera vez, Adapty recupera los datos almacenados y tu app recibe los parámetros personalizados, lo que te permite reaccionar a distintos valores en el código de la app. Adapty admite los siguientes parámetros de datos diferidos: - `ios_deferred_data` - `android_deferred_data` - `deferred_data_sub[1-10]` Para añadir parámetros de datos diferidos, agrégalos a tu enlace en la configuración de tu campaña: 1. Abre tu campaña desde la página **Integrations -> Meta/TikTok Ads**. O bien, abre tu enlace de seguimiento desde la página **Tracking links**. Copia el enlace de clic que usarás en tu campaña. 2. En tu plataforma de anuncios (Meta, TikTok, Google Ads, etc.), pega el enlace en el campo de URL de destino del anuncio y añade los parámetros de datos diferidos como parámetros de consulta adicionales, cada uno precedido por `&`. Por ejemplo, para enviar usuarios de iOS a una pantalla "Welcome" tras la instalación, añade `&ios_deferred_data=welcome`. La URL de destino final tendrá este aspecto: ``` https://api-ua.adapty.io/api/v1/attribution/click?adpt_cid=__ADAPTY__ID__&ios_deferred_data=welcome&campaign_id=__CAMPAIGN_ID__&adset_id=__AID__&ad_id=__CID__&campaign_name=__CAMPAIGN_NAME__&adset_name=__AID_NAME__&ad_name=__CID_NAME__&redirect_url=__APP_LINK__ ``` 3. Responde a los parámetros en el código de tu app. Ten en cuenta que los parámetros de datos diferidos están en el parámetro `payload`, y que `payload` es un JSON escapado, por lo que necesitas parsearlo en el código de tu app. Por ejemplo, así puedes gestionar las instalaciones donde `ios_deferred_data` es `welcome`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers Adapty.delegate = self nonisolated func onInstallationDetailsSuccess(_ details: AdaptyInstallationDetails) { guard let payloadStr = details.payload, let data = payloadStr.data(using: .utf8), let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let deeplink = payload["ios_deferred_data"] as? String, deeplink == "welcome" else { return } DispatchQueue.main.async { print("Navigate to welcome screen") // navigate to your screen here } } ``` </TabItem> <TabItem value="android" label="Kotlin"> ```kotlin showLineNumbers Adapty.setOnInstallationDetailsListener(object : OnInstallationDetailsListener { override fun onInstallationDetailsSuccess(details: AdaptyInstallationDetails) { details.payload?.let { runCatching { val json = JSONObject(it) if (json.optString("android_deferred_data") == "welcome") { println("Navigate to welcome screen") // navigate here } }.onFailure(Throwable::printStackTrace) } } }) ``` </TabItem> <TabItem value="rn" label="React Native" default> ```typescript showLineNumbers adapty.addEventListener('onInstallationDetailsSuccess', details => { // Parse the payload JSON and navigate to welcome screen if needed try { if (details.payload) { const payload = JSON.parse(details.payload); if (payload.ios_deferred_data === 'welcome') { // Navigate to welcome screen // Replace with your app's navigation logic // For example, using React Navigation: // navigation.navigate('Welcome'); console.log('Navigate to welcome screen'); } } } catch (error) { console.error('Error parsing installation details payload:', error); } }); ``` </TabItem> <TabItem value="flutter" label="Flutter"> ```dart showLineNumbers Adapty().onUpdateInstallationDetailsSuccessStream.listen((details) { final payloadStr = details.payload; if (payloadStr == null) return; final payload = json.decode(payloadStr) as Map<String, dynamic>; if (payload['ios_deferred_data'] == 'welcome') { print('Navigate to welcome screen'); } }); ``` </TabItem> </Tabs> --- # File: ua-attribution-data --- --- title: "Recibir datos de atribución en tu app" description: "Accede a los datos de atribución de campaña en tu app cuando Adapty vincula una instalación a una campaña." --- Cuando Adapty asocia una instalación con una campaña, devuelve datos de atribución a tu app en el callback `onInstallationDetailsSuccess`. Usa estos datos para personalizar la experiencia del usuario según el canal o campaña que generó la instalación. Los datos de atribución se devuelven como un objeto `attribution` anidado dentro del campo `payload`. Contiene los siguientes campos: | Campo | Descripción | |---|---| | `channel` | Canal de adquisición (p. ej. `facebook`, `tiktok`, `google`, `organic`) | | `campaign_id` | Identificador de campaña | | `campaign_name` | Nombre de campaña | | `adset_id` | Identificador del conjunto de anuncios / grupo de anuncios | | `adset_name` | Nombre del conjunto de anuncios / grupo de anuncios | | `ad_id` | Identificador del anuncio / creatividad | | `ad_name` | Nombre del anuncio / creatividad | Todos los campos son opcionales. Para instalaciones orgánicas o cuando no se pudo determinar la atribución, el campo `payload` no incluye el objeto `attribution`. Para leer los datos de atribución en tu app: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers Adapty.delegate = self nonisolated func onInstallationDetailsSuccess(_ details: AdaptyInstallationDetails) { guard let payloadDict = details.payload?.dictionary, let attribution = payloadDict["attribution"] as? [String: Any] else { return } let channel = attribution["channel"] as? String let campaignName = attribution["campaign_name"] as? String let adName = attribution["ad_name"] as? String print("Channel: \(channel ?? "organic")") } ``` </TabItem> <TabItem value="android" label="Kotlin"> ```kotlin showLineNumbers Adapty.setOnInstallationDetailsListener(object : OnInstallationDetailsListener { override fun onInstallationDetailsSuccess(details: AdaptyInstallationDetails) { val payloadStr = details.payload ?: return runCatching { val payload = JSONObject(payloadStr) val attribution = payload.optJSONObject("attribution") ?: return val channel = attribution.optString("channel") val campaignName = attribution.optString("campaign_name") val adName = attribution.optString("ad_name") println("Channel: $channel") }.onFailure(Throwable::printStackTrace) } }) ``` </TabItem> <TabItem value="rn" label="React Native"> ```typescript showLineNumbers adapty.addEventListener('onInstallationDetailsSuccess', details => { try { if (!details.payload) return; const payload = JSON.parse(details.payload); const attribution = payload.attribution; if (!attribution) return; const channel = attribution.channel; const campaignName = attribution.campaign_name; const adName = attribution.ad_name; console.log('Channel:', channel ?? 'organic'); } catch (error) { console.error('Error parsing payload:', error); } }); ``` </TabItem> <TabItem value="flutter" label="Flutter"> ```dart showLineNumbers Adapty().onUpdateInstallationDetailsSuccessStream.listen((details) { final payloadStr = details.payload; if (payloadStr == null) return; final payload = json.decode(payloadStr) as Map<String, dynamic>; final attribution = payload['attribution'] as Map<String, dynamic>?; if (attribution == null) return; final channel = attribution['channel'] as String?; final campaignName = attribution['campaign_name'] as String?; final adName = attribution['ad_name'] as String?; print('Channel: ${channel ?? 'organic'}'); }); ``` </TabItem> </Tabs> --- # File: ua-facebook --- --- title: "Integrar Meta Ads con Adapty Attribution" description: "Conecta Meta Ads a Adapty Attribution para rastrear y optimizar el rendimiento de las campañas en Facebook, Instagram, Messenger y Audience Network." --- La integración de Adapty Attribution con Meta te permite rastrear y optimizar el rendimiento de tus campañas en Facebook, Instagram, Messenger y Audience Network. :::tip Consulta nuestra [guía para configurar anuncios en Meta Ads Manager](meta-create-campaign). ::: ## Paso 1. Conecta tu cuenta de Facebook \{#step-1-connect-your-facebook-account\} Para conectar Meta Ads a Adapty Attribution, ve a **Integrations > Meta** desde la barra lateral izquierda. Tienes dos opciones: - **Continue with Facebook**: se conecta mediante OAuth. Úsala si accedes a Meta Ads Manager con tu cuenta personal o de empresa de Facebook. - **Add system token**: se conecta usando un token de usuario del sistema permanente. Úsala si tu organización gestiona cuentas publicitarias a través de un usuario del sistema de Meta Business. <Tabs> <TabItem value="oauth" label="Continue with Facebook"> :::important Asegúrate de que tu cuenta de Facebook tiene acceso a las campañas y píxeles que necesitas. ::: 1. Haz clic en **Continue with Facebook**. 2. Inicia sesión con tu cuenta de Facebook y haz clic en **Continue**. 3. Revisa los permisos solicitados y haz clic en **Save**. </TabItem> <TabItem value="system" label="Add system token"> Genera un [token de acceso de usuario del sistema](https://developers.facebook.com/documentation/ads-commerce/marketing-api/collaborative-ads/managed-partner-ads/api-guide/prerequisites/generate-access-token-system-user) en Meta Business Settings y añádelo a Adapty Attribution. :::important Antes de empezar, necesitas un [usuario del sistema](https://www.facebook.com/business/help/503306463479099) en tu portfolio de Meta Business con las cuentas publicitarias que quieres rastrear ya asignadas. También necesitas una app añadida al portfolio; la seleccionas al generar el token. ::: **En Meta Business Settings, genera el token:** 1. Ve a **Business Settings**. 2. En **Users**, selecciona **System users**. 3. Selecciona tu usuario del sistema y haz clic en **Generate new token**. 4. Selecciona tu app en el desplegable. 5. En la lista de permisos, activa `ads_read`. Es el único permiso que Adapty Attribution necesita para leer los datos de tus campañas y anuncios. 6. Haz clic en **Generate token**. 7. Copia el token y guárdalo en un lugar seguro. Meta solo lo muestra una vez. :::note El ajuste **Token expiration** controla cuánto tiempo permanece activa la conexión. Un token con fecha de expiración debe regenerarse y reconectarse antes de que expire, o la atribución se detendrá. Un token sin fecha de expiración evita esto, pero es una credencial de larga duración. Guárdalo de forma segura y revócalo si alguna vez queda expuesto. ::: **En Adapty Attribution, añade el token:** 1. Haz clic en **Add system token**. 2. Pega el token y haz clic en **Connect**. </TabItem> </Tabs> Tras esto, todas tus cuentas publicitarias se añadirán a Adapty Attribution. Puedes continuar con la incorporación de campañas. ## Paso 2. Añadir campañas \{#step-2-add-campaigns\} Para añadir una campaña de Meta a Adapty Attribution y hacer un seguimiento de cómo funcionan tus anuncios de Meta en Adapty: 1. Cambia a la pestaña **Web Campaigns** y haz clic en **Create configuration**. 2. En la pestaña **General**, despliega la sección **iOS** y/o **Android** y pega las URLs de la aplicación de App Store y/o Google Play. 3. Copia el valor del campo **Click link**. Luego, en Meta Ads Manager, abre tu anuncio y pega este enlace. Esto permitirá que Adapty conecte las instalaciones y compras con los anuncios en Meta. 4. Para enviar los eventos de conversión de vuelta a Meta, también puedes asociar tus píxeles de Meta con campañas en Adapty Attribution. Para ello, selecciona uno de tus píxeles existentes en el desplegable **Pixel**. Tras seleccionar un píxel, puedes hacer clic en **Send test event** para verificar la conexión. ## Paso 3. Mapear eventos \{#step-3-map-events\} Para enviar eventos de conversión de vuelta a Meta y optimizar tus campañas, necesitas configurar el mapeo de eventos en la sección **Events names**. Esto permite que Adapty envíe automáticamente eventos de suscripción a tu píxel de Meta cuando los usuarios realizan acciones en tu app. En la sección **Events names**, activa los eventos que quieras rastrear en Meta Ads Manager. Para cada evento habilitado, selecciona el evento de Meta correspondiente en el desplegable o define uno personalizado. Por defecto, Adapty mapea los eventos de Adapty a los eventos estándar de Meta. Haz clic en **Save** para aplicar la configuración de mapeo de eventos. ## Configuración adicional \{#additional-configuration\} ### Parámetros adicionales \{#additional-parameters\} El campo **Additional parameter** te permite añadir datos personalizados para análisis fuera de Adapty. Es útil cuando necesitas pasar datos específicos de campaña o usuario a herramientas de análisis externas o partners de atribución. En el campo **Additional parameter**, introduce los datos personalizados que quieras incluir en el seguimiento de atribución. El parámetro adicional se incluirá en todos los datos de atribución enviados a Meta y puede utilizarse para análisis y optimización avanzada de campañas. Por ejemplo, si estás ejecutando varias variaciones de la misma campaña, podrías añadir `variant=A` o `variant=B` para distinguir entre diferentes enfoques creativos. :::important Los parámetros adicionales cambian el **Click link** que pegas en Meta Ads Manager. Si ya has copiado este enlace y añadiste un parámetro personalizado después, asegúrate de copiar y pegar el enlace de clic actualizado que contiene el parámetro personalizado. ::: <br/> ### Configuración \{#settings\} La pestaña **Settings** controla cómo Adapty asocia las acciones de los usuarios con tus campañas de Meta Ads. Estos ajustes determinan las ventanas de tiempo para la atribución determinista y probabilística. Para configurarlos, ve a la pestaña **Settings** en la configuración de tu campaña. Aquí encontrarás dos ajustes principales: - **Ventana de coincidencia determinista**: utiliza identificadores exactos del dispositivo (como IDFA en iOS o Advertising ID en Android) para asociar usuarios con campañas con alta precisión. Configúrala en 168 horas (7 días) para obtener la máxima precisión de atribución — este es el valor predeterminado y recomendado. Cuando un usuario hace clic en tu anuncio de Meta e instala la app dentro de esta ventana, Adapty puede atribuir de forma definitiva la instalación a ese clic concreto usando identificadores del dispositivo. - **Ventana de coincidencia probabilística**: Usa modelado estadístico y huellas de dispositivo para emparejar usuarios cuando la coincidencia determinista no es posible. Configúrala en 6 horas para la mayoría de las campañas; este es el valor predeterminado y funciona bien en la mayoría de los casos. Para campañas con grandes volúmenes de clics, puedes reducirla a 1-2 horas. Para los usuarios que no se pueden emparejar de forma determinista (por configuración de privacidad u otros factores), Adapty utiliza la coincidencia probabilística dentro de esta ventana más corta. Haz clic en **Save** para aplicar tu configuración. ### Anulación de ingresos \{#revenue-override\} Si registras eventos de prueba y quieres que Meta los atribuya como ingresos, usa la sección **Revenue override**. Aparece cuando el evento **Trial started** está habilitado. Para cada objetivo de evento de prueba, establece el porcentaje del precio de la suscripción que quieres reportar como ingresos. Por ejemplo, con un 30 %, Adapty envía el 30 % del precio de la suscripción como valor de conversión para los eventos de prueba. Para añadir una anulación: 1. Activa **Trial started** en la sección **Events names**. 2. En **Revenue override**, haz clic en **Add override**. 3. Selecciona el evento de destino e introduce un porcentaje de ingresos (0–100). 4. Haz clic en **Save**. ### Enviar todos los eventos \{#send-all-events\} Por defecto, Adapty envía eventos a tu píxel solo para los usuarios atribuidos a una campaña de Meta. Activa **Send all events** para también reenviar eventos de usuarios orgánicos y no atribuidos al píxel. Cuando está habilitado, cada evento de instalación y transacción se envía al píxel, independientemente de la atribución de la campaña. Úsalo para proporcionar a Meta datos de conversión más amplios para el modelado de audiencias y la optimización de campañas. Para habilitar esta opción, en la configuración de la campaña selecciona **Send all events (forward organic/non-attributed events to this pixel)** y haz clic en **Save**. --- # File: ua-tiktok --- --- title: "Integrar TikTok for Business con Adapty Attribution" description: "Conecta TikTok for Business a Adapty Attribution para rastrear y optimizar el rendimiento de campañas en TikTok Ads Manager." --- La integración de Adapty Attribution con TikTok for Business te permite rastrear y optimizar el rendimiento de tus campañas en TikTok. :::tip Consulta nuestra [guía para configurar anuncios en TikTok for Business](tiktok-create-campaign). ::: ## Paso 1. Conecta tu cuenta de TikTok \{#step-1-connect-your-tiktok-account\} 1. Ve a **Integrations > TikTok Ads** en la barra lateral izquierda y haz clic en **Continue with TikTok**. 2. Inicia sesión con tu cuenta de TikTok y haz clic en **Continue**. 3. Revisa los permisos solicitados y haz clic en **Save**. Tras esto, todas tus cuentas publicitarias se añadirán a Adapty UA. Ya puedes continuar añadiendo campañas. ## Paso 2. Añadir campañas \{#step-2-add-campaigns\} Para añadir una campaña de TikTok for Business a Adapty Attribution y realizar un seguimiento del rendimiento de tus anuncios de TikTok en Adapty: 1. Cambia a la pestaña **Web Campaigns** y haz clic en **Create configuration**. 2. En la pestaña **General**, despliega la sección **iOS** y/o **Android** y pega las URLs de la aplicación de App Store y/o Google Play. 3. Copia el valor del campo **Click link**. Luego, en TikTok Ads Manager, al crear tu anuncio, pega este valor en el campo **Tracking URL** dentro de la sección **Advanced Settings**. Esto permitirá que Adapty conecte las instalaciones y compras con los anuncios en TikTok. 4. (Opcional) Para enviar los eventos de conversión de vuelta a TikTok, también puedes asociar tus píxeles de TikTok con campañas en Adapty Attribution. Para ello, selecciona uno de tus píxeles existentes en el desplegable **Pixel**. Tras seleccionar un píxel, puedes hacer clic en **Send test event** para verificar la conexión. ## Paso 3. Mapear eventos \{#step-3-map-events\} Para enviar eventos de conversión de vuelta a TikTok y optimizar campañas, necesitas configurar el mapeo de eventos en la sección **Events names**. Esto permite que Adapty envíe automáticamente eventos de suscripción a tu píxel de TikTok cuando los usuarios realizan acciones en tu app. En la sección **Events names**, activa los eventos que quieras rastrear en TikTok Ads Manager. Para cada evento habilitado, selecciona el evento de TikTok correspondiente en el desplegable o define uno personalizado. Por defecto, Adapty mapea los eventos de Adapty a los eventos estándar de TikTok. Haz clic en **Save** para aplicar la configuración del mapeo de eventos. ## Configuración adicional \{#additional-configuration\} ### Parámetros adicionales \{#additional-parameters\} El campo **Additional parameter** te permite añadir puntos de datos personalizados para su análisis fuera de Adapty. Resulta útil cuando necesitas pasar datos específicos de campaña o de usuario a herramientas de análisis externas o socios de atribución. En el campo **Additional parameter**, introduce cualquier dato personalizado que quieras incluir en tu seguimiento de atribución. El parámetro adicional se incluirá en todos los datos de atribución enviados a TikTok y podrá utilizarse para análisis y optimización avanzada de campañas. Por ejemplo, si estás ejecutando múltiples variaciones de la misma campaña, podrías añadir `variant=A` o `variant=B` para distinguir entre diferentes enfoques creativos. :::important Los parámetros adicionales cambian el **Click link** que pegas en TikTok Ads Manager. Si ya has copiado este enlace allí y después has añadido un parámetro personalizado, asegúrate de copiar y pegar un enlace de clic actualizado que contenga el parámetro personalizado. ::: <br/> ### Configuración \{#settings\} La pestaña **Settings** controla cómo Adapty relaciona las acciones de los usuarios con tus campañas de TikTok Ads. Estos ajustes determinan las ventanas de tiempo para la atribución determinista y probabilística. Para configurarlos, ve a la pestaña **Settings** en la configuración de tu campaña. Aquí encontrarás dos ajustes principales: - **Ventana de coincidencia determinista**: utiliza identificadores exactos del dispositivo (como el IDFA en iOS o el Advertising ID en Android) para asociar usuarios a campañas con alta precisión. Configúrala en 168 horas (7 días) para obtener la máxima precisión en la atribución; este es el valor predeterminado y recomendado. Cuando un usuario hace clic en tu anuncio de TikTok e instala la app dentro de esta ventana, Adapty puede atribuir de forma definitiva la instalación a ese clic concreto usando los identificadores del dispositivo. - **Ventana de coincidencia probabilística**: utiliza modelado estadístico y huellas del dispositivo para emparejar usuarios cuando no es posible la coincidencia determinista. Establécela en 6 horas para la mayoría de las campañas; este es el valor predeterminado y funciona bien en la mayoría de los casos. Para campañas con grandes volúmenes de clics, puedes reducirla a 1-2 horas. Para los usuarios que no pueden emparejarse de forma determinista (por configuración de privacidad u otros factores), Adapty utiliza la coincidencia probabilística dentro de esta ventana más corta. Haz clic en **Save** para aplicar los cambios. ### Anulación de ingresos \{#revenue-override\} Si registras eventos de prueba y quieres que TikTok les atribuya ingresos, usa la sección **Revenue override**. Esta sección aparece cuando el evento **Trial started** está habilitado. Para cada objetivo de evento de prueba, establece el porcentaje del precio de la suscripción que se reportará como ingresos. Por ejemplo, con un 30%, Adapty envía el 30% del precio de la suscripción como valor de conversión para los eventos de prueba. Para añadir una anulación: 1. Activa **Trial started** en la sección **Events names**. 2. En **Revenue override**, haz clic en **Add override**. 3. Selecciona el evento objetivo e introduce un porcentaje de ingresos (0–100). 4. Haz clic en **Save**. ### Enviar todos los eventos \{#send-all-events\} Por defecto, Adapty envía eventos a tu píxel solo para los usuarios atribuidos a una campaña de TikTok. Activa **Send all events** para reenviar también los eventos de usuarios orgánicos y no atribuidos al píxel. Cuando está habilitado, cada evento de instalación y transacción se envía al píxel, independientemente de la atribución de campaña. Úsalo para proporcionar a TikTok datos de conversión más amplios para el modelado de audiencias y la optimización de campañas. Para habilitar esta opción, en la configuración de la campaña, selecciona **Send all events (forward organic/non-attributed events to this pixel)** y haz clic en **Save**. --- # File: ua-funnelfox --- --- title: "Integrar FunnelFox con Adapty Attribution" description: "Conecta los funnels web-to-app de FunnelFox con Adapty Attribution para rastrear el camino completo de adquisición, desde el punto de contacto web hasta el suscriptor de pago." --- [FunnelFox](https://funnelfox.com) es una plataforma para construir funnels web2app que te permite captar y cobrar a usuarios fuera del App Store, evitando así sus comisiones y otras restricciones. Una vez conectada, FunnelFox envía eventos de transacciones a Adapty Attribution, lo que te da una ruta de atribución completa desde el punto de contacto web hasta el suscriptor de pago. Para configurar la integración, vincula uno o más proyectos de FunnelFox a tu app de Adapty mediante un **Project ID**. ## Cómo funciona \{#how-it-works\} Cuando un usuario completa una compra en tu embudo de FunnelFox, FunnelFox envía el evento de transacción a Adapty Attribution. Adapty utiliza el **Project ID** para identificar a qué aplicación pertenece la transacción. El evento se almacena y aparece en tus análisis de Adapty Attribution. Cada transacción incluye: - **Evento del ciclo de vida de la suscripción**: iniciada, renovada, cancelada, trial convertido, reembolsada, y más - **Datos de atribución**: identificadores de campaña, adset y anuncio; parámetros UTM; click IDs de plataforma (fbclid, ttclid, gclid) - **Datos de embudo y experimento**: nombre del embudo y del experimento en FunnelFox, para que puedas comparar variantes de pruebas A/B Adapty determina automáticamente el **canal** (Facebook, TikTok, Google u orgánico) a partir del click ID de la transacción. No necesitas configurarlo manualmente. :::note Las transacciones de FunnelFox utilizan la **primera fecha de pago** como fecha de cohorte en lugar de la fecha de instalación, ya que las compras web no tienen un evento de instalación de la app. ::: ## Configurar la integración \{#configure-integration\} ### Paso 1. Obtén tu Project ID en FunnelFox \{#step-1-get-your-project-id-in-funnelfox\} 1. En tu dashboard de FunnelFox, haz clic en **Settings** en la barra lateral izquierda. 2. En la sección **Project info**, copia el valor de **ID**. ### Paso 2. Añade el proyecto en Adapty UA \{#step-2-add-the-project-in-adapty-ua\} 1. En Adapty UA, ve a [**Integrations > FunnelFox**](https://app.adapty.io/ua/integrations/funnelfox). 2. Pega el Project ID que copiaste de FunnelFox. 3. Haz clic en **Save**. Para conectar proyectos adicionales de FunnelFox, haz clic en **Add project** y repite ambos pasos para cada proyecto adicional. --- # File: ua-custom-s3 --- --- title: "S3 personalizado en Adapty Attribution" description: "Exporta datos de adquisición de usuarios a tu almacenamiento compatible con S3 personalizado para análisis e informes avanzados." --- La integración de Adapty Attribution con almacenamiento compatible con S3 personalizado te permite guardar de forma segura los datos de tus campañas de adquisición de usuarios en tu propia solución de almacenamiento compatible con S3. Podrás guardar los datos de rendimiento de campañas, datos de atribución y eventos de adquisición de usuarios en tu bucket S3 personalizado como archivos .csv. Para configurar esta integración, deberás seguir unos sencillos pasos en la consola de tu almacenamiento compatible con S3 y en el dashboard de Adapty Attribution. :::note Adapty Attribution envía tus datos cada **24h** a las 4:00 UTC. Cada archivo contendrá los datos de los eventos generados durante el día calendario anterior completo en UTC. Por ejemplo, los datos exportados automáticamente a las 4:00 UTC del 8 de marzo contendrán todos los eventos del 7 de marzo desde las 00:00:00 hasta las 23:59:59 UTC. ::: ## Configurar la integración de Custom S3 \{#set-up-custom-s3-integration\} Para empezar a recibir datos, configura la integración en Adapty Attribution: 1. Ve a [**Integrations** -> **Custom S3**](https://app.adapty.io/ua/integrations/custom-s3) 2. Activa el toggle **Export install events to custom S3**. 3. Rellena los campos obligatorios para establecer la conexión entre tu almacenamiento Custom S3 y los perfiles de Adapty Attribution. | Campo | Descripción | |:----------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Access Key ID** | Identificador único que se utiliza para autenticar el acceso de un usuario o aplicación a tu servicio de almacenamiento compatible con S3. Encuéntralo en la consola de tu proveedor de almacenamiento. | | **Secret Access Key** | Clave privada que se usa junto con el Access Key ID para autenticar el acceso de un usuario o aplicación a tu servicio de almacenamiento compatible con S3. Encuéntrala en la consola de tu proveedor de almacenamiento. | | **S3 Bucket Name** | Nombre único a nivel global que identifica un bucket S3 específico dentro de tu entorno de almacenamiento. Los buckets S3 son un servicio de almacenamiento simple que permite guardar y recuperar objetos de datos, como archivos e imágenes, en la nube. | | **Region** (Opcional) | Obtén tu región desde la consola de administración. | | **Folder Inside the Bucket** (Opcional) | Nombre de la carpeta que deseas crear dentro del bucket S3 seleccionado. Ten en cuenta que S3 simula carpetas mediante prefijos de clave de objeto, que son esencialmente nombres de carpeta. | | **Custom Endpoint URL** | URL del endpoint para tu servicio de almacenamiento compatible con S3. Tu proveedor de almacenamiento debe facilitártela (por ejemplo, MinIO, DigitalOcean Spaces, Wasabi, etc.). | :::note También puedes especificar directorios anidados en el campo del nombre del bucket de S3, por ejemplo, `adapty-ua-events/com.sample-app` ::: ## Exportación manual de datos \{#manual-data-export\} Además de la exportación automática de datos de eventos a tu almacenamiento S3 personalizado, Adapty UA también ofrece una función de exportación manual de archivos. Con esta función puedes seleccionar una fecha para los datos de adquisición de usuarios y exportarlos manualmente a tu bucket S3. Esto te da mayor control sobre qué datos exportas y cuándo lo haces. ## Estructura de la tabla \{#table-structure\} En la integración personalizada con S3, Adapty Attribution proporciona una tabla para almacenar datos históricos de eventos de instalación. La tabla contiene información sobre el perfil del usuario, ingresos y beneficios, y el store de origen, entre otros datos. :::warning Ten en cuenta que esta estructura puede crecer con el tiempo, ya que nosotros o terceros con los que trabajamos podemos añadir nuevos datos. Asegúrate de que el código que la procesa sea lo suficientemente robusto y se base en campos específicos, no en la estructura en su conjunto. ::: Aquí está la estructura de la tabla para los eventos: | Columna | Descripción | |--------------------------|------------------------------------------------------| | `adapty_profile_id` | Identificador único de perfil de Adapty | | `install_id` | Identificador único de instalación | | `created_at` | Marca de tiempo de creación del registro (ISO 8601) | | `installed_at` | Marca de tiempo de instalación de la app (ISO 8601) | | `store` | Store de la app (`ios`, `android`) | | `country` | Código de país del usuario (ISO 3166-1 alpha-2) | | `ip_address` | Dirección IP del cliente | | `idfa` | Identificador de iOS para anunciantes | | `idfv` | Identificador de iOS para proveedores | | `gaid` | ID de publicidad de Google (Android) | | `android_id` | ID de dispositivo Android | | `app_set_id` | Android App Set ID | | `bundle_id` | Identificador del bundle de la app (p. ej., `com.example.app`) | | `device_brand` | Marca del dispositivo (p. ej., `Apple`, `Samsung`) | | `device_model` | Modelo del dispositivo (p. ej., `iPhone15,2`) | | `os_version` | Versión principal del sistema operativo | | `app_version` | Versión de la app reportada por el SDK de Adapty | | `sdk_version` | Versión del SDK de Adapty | | `channel` | Canal de atribución | | `campaign_id` | Identificador de campaña | | `campaign_name` | Nombre de la campaña | | `adset_id` | Identificador del conjunto de anuncios | | `adset_name` | Nombre del conjunto de anuncios | | `ad_id` | Identificador del anuncio | | `ad_name` | Nombre del anuncio | | `keyword_id` | Identificador de palabra clave | | `keyword_name` | Nombre de la palabra clave | | `asa_org_id` | ID de organización de Apple Search Ads | | `asa_keyword_match_type` | Tipo de coincidencia de palabra clave de ASA (`Exact`, `Broad`) | | `asa_attribution` | Datos de atribución de ASA (cadena JSON) | | `asa_conversion_type` | Tipo de conversión de ASA | | `asa_country_or_region` | País o región de ASA | | `asa_creative_set_name` | Nombre del conjunto creativo de ASA | | `fbclid` | ID de clic de Facebook | | `ttclid` | ID de clic de TikTok | | `utm_source` | Parámetro UTM source | | `utm_medium` | Parámetro UTM medium | | `utm_campaign` | Parámetro UTM campaign | | `utm_term` | Parámetro UTM term | | `utm_content` | Parámetro UTM content | --- # File: ua-amazon-s3 --- --- title: "Amazon S3 en Adapty Attribution" description: "Exporta datos de adquisición de usuarios a S3 para análisis avanzados e informes." --- La integración de Adapty Attribution con Amazon S3 te permite almacenar de forma segura los datos de tus campañas de adquisición de usuarios en un único lugar centralizado. Podrás guardar los datos de rendimiento de tus campañas, datos de atribución y eventos de adquisición de usuarios en tu bucket de Amazon S3 como archivos .csv. Para configurar esta integración, deberás seguir unos sencillos pasos en la AWS Console y en el Adapty Attribution dashboard. :::note Adapty Attribution envía tus datos cada **24h** a las 4:00 UTC. Cada archivo contendrá los datos de los eventos creados durante todo el día natural anterior en UTC. Por ejemplo, los datos exportados automáticamente a las 4:00 UTC del 8 de marzo contendrán todos los eventos creados el 7 de marzo entre las 00:00:00 y las 23:59:59 UTC. ::: ## Cómo configurar la integración con Amazon S3 \{#how-to-set-up-amazon-s3-integration\} Para empezar a recibir datos, necesitarás las siguientes credenciales: 1. Access key ID 2. Secret access key 3. Nombre del bucket de S3 4. Nombre de la carpeta dentro del bucket de S3 :::note Directorios anidados Puedes especificar directorios anidados en el campo del nombre del bucket de Amazon S3, p. ej. adapty-ua-events/com.sample-app ::: ### Paso 1. Crear credenciales de Amazon S3 \{#step-1-create-amazon-s3-credentials\} Esta guía te ayudará a crear las credenciales necesarias en tu AWS Console. #### 1.1. Crear política de acceso \{#11-create-access-policy\} 1. Ve al [IAM Policy Dashboard](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/policies) en tu AWS Console 2. Selecciona la opción **Create Policy** <img src="/assets/shared/img/7af075c-CleanShot_2023-03-21_at_10.52.002x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En el editor de políticas, pega el siguiente JSON y cambia `adapty-s3-integration-test` por el nombre de tu bucket: ```json showLineNumbers title="Json" { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowListObjectsInBucket", "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::adapty-s3-integration-test" }, { "Sid": "AllowAllObjectActions", "Effect": "Allow", "Action": "s3:*Object", "Resource": [ "arn:aws:s3:::adapty-s3-integration-test/*", "arn:aws:s3:::adapty-s3-integration-test" ] }, { "Sid": "AllowBucketLocation", "Effect": "Allow", "Action": "s3:GetBucketLocation", "Resource": "arn:aws:s3:::adapty-s3-integration-test" } ] } ``` <img src="/assets/shared/img/d4e474a-CleanShot_2023-03-21_at_10.56.212x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Una vez completada la configuración de la política, puedes añadir etiquetas (opcional) y luego hacer clic en **Next** para continuar al último paso 5. En este paso, pondrás nombre a tu política y simplemente harás clic en el botón **Create policy** para finalizar el proceso de creación <img src="/assets/shared/img/7dcb02f-CleanShot_2023-03-21_at_11.03.372x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### 1.2. Crear usuario IAM \{#12-create-iam-user\} Para que Adapty Attribution pueda subir informes de datos sin procesar a tu bucket, deberás proporcionarle el Access Key ID y el Secret Access Key de un usuario con acceso de escritura al bucket específico. 1. Ve a la consola de IAM y selecciona la [sección Usuarios](https://console.aws.amazon.com/iamv2/home#/users) 2. Haz clic en el botón **Add users** <img src="/assets/shared/img/bb612c8-CleanShot_2023-03-21_at_11.12.392x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Ponle un nombre al usuario, elige **Access key – Programmatic access** y continúa con los permisos <img src="/assets/shared/img/467ee4d-j6aoX.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. En el siguiente paso, selecciona la opción **Add user to group** y haz clic en el botón **Create group** <img src="/assets/shared/img/bfd0e80-CleanShot_2023-03-21_at_11.24.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. A continuación, asigna un nombre a tu Grupo de Usuarios y selecciona la política que creaste anteriormente. 6. Una vez seleccionada la política, haz clic en el botón **Create group** para completar el proceso. <img src="/assets/shared/img/df29c12-CleanShot_2023-03-21_at_11.28.052x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Una vez creado el grupo, **selecciónalo** y continúa con el siguiente paso <img src="/assets/shared/img/1f3722e-CleanShot_2023-03-21_at_11.36.192x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. Como este es el último paso de esta sección, puedes continuar simplemente haciendo clic en el botón **Create User** <img src="/assets/shared/img/ea43722-CleanShot_2023-03-21_at_11.40.462x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. Por último, puedes **descargar las credenciales en formato .csv** o copiarlas y pegarlas directamente desde el dashboard <img src="/assets/shared/img/bcf35e1-S3created.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Paso 2. Configurar la integración en Adapty Attribution \{#step-2-configure-integration-in-adapty-attribution\} 1. Ve a [**Integrations** -> **Amazon S3**](https://app.adapty.io/ua/integrations/s3) 2. Activa el toggle **Export install events to Amazon S3**. 3. Rellena los siguientes campos para establecer la conexión entre Amazon S3 y los perfiles de Adapty Attribution: | Campo | Descripción | |:-----------------------------| :----------------------------------------------------------- | | **Access Key ID** | Un identificador único que se usa para autenticar el acceso de un usuario o aplicación a un servicio de AWS. Encuéntralo en el [archivo csv](ua-amazon-s3#step-1-create-amazon-s3-credentials) descargado. | | **Secret Access Key** | Una clave privada que se usa junto con el Access Key ID para autenticar el acceso de un usuario o aplicación a un servicio de AWS. Encuéntrala en el [archivo csv](ua-amazon-s3#step-1-create-amazon-s3-credentials) descargado. | | **S3 Bucket Name** | Un nombre único global que identifica un bucket S3 específico dentro de la nube de AWS. Los buckets S3 son un servicio de almacenamiento simple que permite a los usuarios guardar y recuperar objetos de datos, como archivos e imágenes, en la nube. | | **Folder Inside the Bucker** | El nombre de la carpeta que quieres tener dentro del bucket S3 seleccionado. Ten en cuenta que S3 simula carpetas mediante prefijos de clave de objeto, que son esencialmente nombres de carpeta. | | **Region** (Opcional) | Obtén tu región desde la consola de administración de AWS en tu cuenta de usuario IAM. | ## Exportación manual de datos \{#manual-data-export\} Además de la exportación automática de datos de eventos a Amazon S3, Adapty UA también ofrece la funcionalidad de exportación manual de archivos. Con esta función, puedes seleccionar una fecha concreta para los datos de adquisición de usuarios y exportarlos manualmente a tu bucket de S3. Esto te da mayor control sobre qué datos exportas y cuándo lo haces. ## Estructura de la tabla \{#table-structure\} En la integración con AWS S3, Adapty Attribution ofrece una tabla para almacenar datos históricos de eventos de instalación. La tabla contiene información sobre el perfil del usuario, ingresos y beneficios, y el store de origen, entre otros datos. :::warning Ten en cuenta que esta estructura puede crecer con el tiempo, al añadir datos nuevos nosotros mismos o los terceros con los que trabajamos. Asegúrate de que el código que la procesa sea lo suficientemente robusto y se base en campos concretos, no en la estructura en su conjunto. ::: Esta es la estructura de la tabla para los eventos: | Columna | Descripción | |--------------------------|----------------------------------------------------| | `adapty_profile_id` | Identificador único de perfil en Adapty | | `install_id` | Identificador único de instalación | | `created_at` | Marca de tiempo de creación del registro (ISO 8601)| | `installed_at` | Marca de tiempo de instalación de la app (ISO 8601)| | `store` | Store de aplicaciones (`ios`, `android`) | | `country` | Código de país del usuario (ISO 3166-1 alpha-2) | | `ip_address` | Dirección IP del cliente | | `idfa` | Identificador para anunciantes de iOS | | `idfv` | Identificador para proveedores de iOS | | `gaid` | ID de publicidad de Google (Android) | | `android_id` | ID de dispositivo Android | | `app_set_id` | App Set ID de Android | | `channel` | Canal de atribución | | `campaign_id` | Identificador de campaña | | `campaign_name` | Nombre de la campaña | | `adset_id` | Identificador del conjunto de anuncios | | `adset_name` | Nombre del conjunto de anuncios | | `ad_id` | Identificador del anuncio | | `ad_name` | Nombre del anuncio | | `keyword_id` | Identificador de palabra clave | | `keyword_name` | Nombre de la palabra clave | | `asa_org_id` | ID de organización de Apple Search Ads | | `asa_keyword_match_type` | Tipo de concordancia de palabra clave ASA (`Exact`, `Broad`) | | `asa_attribution` | Datos de atribución ASA (cadena JSON) | | `asa_conversion_type` | Tipo de conversión ASA | | `asa_country_or_region` | País o región de ASA | | `asa_creative_set_name` | Nombre del conjunto creativo ASA | | `fbclid` | ID de clic de Facebook | | `ttclid` | ID de clic de TikTok | | `utm_source` | Parámetro de fuente UTM | | `utm_medium` | Parámetro de medio UTM | | `utm_campaign` | Parámetro de campaña UTM | | `utm_term` | Parámetro de término UTM | | `utm_content` | Parámetro de contenido UTM | --- # File: ua-google-cloud-storage --- --- title: "Google Cloud Storage en Adapty Attribution" description: "Integra Google Cloud Storage con Adapty Attribution para almacenar de forma segura los datos de adquisición de usuarios." --- La integración de Adapty Attribution con Google Cloud Storage te permite almacenar de forma segura los datos de tus campañas de adquisición de usuarios en un único lugar centralizado. Podrás guardar los datos de rendimiento de campañas, datos de atribución y eventos de adquisición de usuarios en tu bucket de Google Cloud Storage como archivos .csv. Para configurar esta integración, tendrás que seguir unos sencillos pasos en la Google Cloud Console y en el Adapty Attribution Dashboard. :::note Programación Adapty Attribution envía tus datos a Google Cloud Storage cada 24 horas a las 4:00 UTC. Cada archivo contendrá los datos de los eventos creados durante el día natural anterior completo en UTC. Por ejemplo, los datos exportados automáticamente a las 4:00 UTC del 8 de marzo contendrán todos los eventos creados el 7 de marzo entre las 00:00:00 y las 23:59:59 UTC. ::: ## Cómo configurar la integración con Google Cloud Storage \{#how-to-set-up-google-cloud-storage-integration\} ### Paso 1. Crea las credenciales de Google Cloud Storage \{#step-1-create-google-cloud-storage-credentials\} Esta guía te ayudará a crear las credenciales necesarias en la consola de Google Cloud Platform. Para que Adapty Attribution pueda subir informes de datos brutos a tu bucket designado, se necesita la clave de la cuenta de servicio, así como acceso de escritura al bucket correspondiente. Al proporcionar la clave de la cuenta de servicio y conceder acceso de escritura al bucket, permites que Adapty Attribution transfiera de forma segura y eficiente los informes de datos brutos desde su plataforma a tu entorno de almacenamiento. :::warning Ten en cuenta que solo admitimos la autorización mediante clave HMAC de cuenta de servicio, por lo que es imprescindible asegurarse de que tu clave HMAC de cuenta de servicio tenga los roles "Storage Object Viewer", "Storage Legacy Bucket Writer" y "Storage Object Creator" asignados para permitir el acceso correcto a Google Cloud Storage. ::: #### 2.1. Crear cuenta de servicio \{#21-create-service-account\} 1. Ve a la sección [IAM](https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts) de tu cuenta de Google Cloud y elige el proyecto correspondiente o crea uno nuevo <img src="/assets/shared/img/30a81ef-CleanShot_2023-03-17_at_15.22.142x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. A continuación, crea una nueva cuenta de servicio para la atribución de Adapty haciendo clic en el botón **+ CREATE SERVICE ACCOUNT** <img src="/assets/shared/img/98f8ebf-CleanShot_2023-03-17_at_15.40.062x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Rellena los campos del primer paso, ya que el acceso se concederá en una etapa posterior. Para obtener más detalles sobre esta página, consulta la documentación [aquí](https://docs.cloud.google.com/iam/docs/service-accounts-create) <img src="/assets/shared/img/2190c50-CleanShot_2023-03-17_at_15.48.552x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Para crear y descargar una [clave JSON privada](https://docs.cloud.google.com/iam/docs/keys-create-delete), ve a la sección KEYS y haz clic en el botón "ADD KEY" <img src="/assets/shared/img/8a45468-CleanShot_2023-03-17_at_15.58.092x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. En la sección DETAILS, localiza el valor Email vinculado a la cuenta de servicio recién creada y cópialo. Esta información será necesaria en los próximos pasos para autorizar la cuenta y permitirle escribir en el bucket. <img src="/assets/shared/img/6ccd0f0-CleanShot_2023-03-17_at_16.03.162x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### 2.2. Configurar los permisos del bucket \{#22-configure-bucket-permissions\} 6. Ve a la página de [Buckets](https://console.cloud.google.com/storage/browser) de Google Cloud Storage y selecciona un bucket existente o crea uno nuevo para almacenar los informes de datos de atribución de usuarios de Adapty Attribution 7. Navega a la sección PERMISSIONS y selecciona la opción para [GRANT ACCESS](https://docs.cloud.google.com/identity/docs/how-to?hl=en) <img src="/assets/shared/img/3cdd937-CleanShot_2023-03-17_at_16.14.232x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. En la sección PERMISSIONS, introduce el Email de la cuenta de servicio obtenido en el quinto paso mencionado anteriormente y selecciona el rol Storage Object Creator 9. Por último, haz clic en SAVE para aplicar los cambios <img src="/assets/shared/img/62801f4-CleanShot_2023-03-17_at_16.17.312x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 10. Recuerda guardar el nombre del bucket para consultarlo más adelante. 11. Una vez completados estos pasos, habrás finalizado correctamente la configuración necesaria en Google Cloud Console. El último paso consiste en introducir el nombre del bucket y descargar el archivo JSON para usarlo en Adapty Attribution. <img src="/assets/shared/img/c967e16-CleanShot_2023-03-17_at_16.23.332x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Paso 2. Configurar la integración en Adapty Attribution \{#step-2-configure-integration-in-adapty-attribution\} 1. Ve a [**Integrations** -> **Google Cloud Storage**](https://app.adapty.io/ua/integrations/google-cloud-storage) 2. Activa el toggle **Export install events to Google Cloud Storage** 3. Rellena los campos obligatorios para establecer la conexión entre Google Cloud Storage y Adapty Attribution: | Campo | Descripción | |:------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Google Cloud service account key file** | El [archivo de clave JSON](ua-google-cloud-storage#step-1-create-google-cloud-storage-credentials) privado descargado. | | **Google Cloud bucket name** | El nombre del bucket en Google Cloud Storage donde quieres almacenar tus datos. Debe ser único dentro del entorno de Google Cloud Storage y no puede contener espacios. | | **Folder inside the bucket** | El nombre de la carpeta dentro del bucket donde quieres almacenar tus datos. Debe ser único dentro del bucket y puede usarse para organizar la información. Este campo es opcional. | ## Exportación manual de datos \{#manual-data-export\} Además de la exportación automática de datos de eventos a Google Cloud Storage, Adapty UA también ofrece una funcionalidad de exportación manual de archivos. Con esta función, puedes seleccionar una fecha concreta para los datos de adquisición de usuarios y exportarlos manualmente a tu bucket de GCS. Esto te da mayor control sobre los datos que exportas y cuándo lo haces. ## Estructura de la tabla \{#table-structure\} En la integración con Google Cloud Storage, Adapty Attribution proporciona una tabla para almacenar datos históricos de eventos de instalación. La tabla contiene información sobre el perfil del usuario, los ingresos y los beneficios, y el store de origen, entre otros puntos de datos. :::warning Ten en cuenta que esta estructura puede crecer con el tiempo, ya que nosotros o los terceros con los que trabajamos podemos incorporar nuevos datos. Asegúrate de que el código que la procesa sea lo suficientemente robusto y se base en campos específicos, no en la estructura en su conjunto. ::: Esta es la estructura de la tabla para los eventos: | Columna | Descripción | |--------------------------|----------------------------------------------------| | `adapty_profile_id` | Identificador único del perfil en Adapty | | `install_id` | Identificador único de instalación | | `created_at` | Marca de tiempo de creación del registro (ISO 8601)| | `installed_at` | Marca de tiempo de instalación de la app (ISO 8601)| | `store` | Store de la app (`ios`, `android`) | | `country` | Código de país del usuario (ISO 3166-1 alpha-2) | | `ip_address` | Dirección IP del cliente | | `idfa` | Identificador para anunciantes de iOS | | `idfv` | Identificador para vendors de iOS | | `gaid` | ID de publicidad de Google (Android) | | `android_id` | ID de dispositivo Android | | `app_set_id` | App Set ID de Android | | `channel` | Canal de atribución | | `campaign_id` | Identificador de campaña | | `campaign_name` | Nombre de la campaña | | `adset_id` | Identificador del conjunto de anuncios | | `adset_name` | Nombre del conjunto de anuncios | | `ad_id` | Identificador del anuncio | | `ad_name` | Nombre del anuncio | | `keyword_id` | Identificador de palabra clave | | `keyword_name` | Nombre de la palabra clave | | `asa_org_id` | ID de organización de Apple Search Ads | | `asa_keyword_match_type` | Tipo de concordancia de palabra clave ASA (`Exact`, `Broad`) | | `asa_attribution` | Datos de atribución ASA (cadena JSON) | | `asa_conversion_type` | Tipo de conversión ASA | | `asa_country_or_region` | País o región ASA | | `asa_creative_set_name` | Nombre del conjunto creativo ASA | | `fbclid` | Click ID de Facebook | | `ttclid` | Click ID de TikTok | | `utm_source` | Parámetro de fuente UTM | | `utm_medium` | Parámetro de medio UTM | | `utm_campaign` | Parámetro de campaña UTM | | `utm_term` | Parámetro de término UTM | | `utm_content` | Parámetro de contenido UTM | --- # File: adapty-mail --- --- title: "Adapty Mail" description: "Campañas de email generadas por IA que convierten usuarios en período de prueba en suscriptores de pago." --- <CustomDocCardList ids={['mail-get-started', 'mail-brand', 'mail-collect-emails', 'mail-send-data-via-api', 'mail-sending-domain', 'mail-create-campaign', 'mail-analytics']} /> Adapty Mail convierte los datos de usuarios de Adapty en secuencias de email generadas por IA que transforman a los usuarios en período de prueba en suscriptores de pago. Utiliza los datos de perfil que ya están en tu proyecto de Adapty para crear, enviar y atribuir campañas, sin necesidad de una plataforma de email independiente. ## ¿Por qué Adapty Mail? \{#why-adapty-mail\} Enviar campañas de email segmentadas requiere redacción, diseño, infraestructura de envío y atribución de ingresos. Cada uno de estos es un problema distinto por resolver. Adapty Mail se encarga de todo. Tu perfil de marca se construye a partir de la URL de tu store y de cualquier otra fuente que añadas, y una secuencia de email completa se genera en menos de 2 minutos — enviada desde tu propio dominio con enlaces de checkout personalizados y atribución de compras. ## Cómo funciona \{#how-it-works\} 1. **Recoge emails**: Tu app envía los emails de los usuarios y los valores de `customer_user_id` a Adapty mediante el SDK. Adapty Mail usa estos datos para identificar a los destinatarios y atribuir los ingresos al email concreto que impulsó cada compra. También puedes enviar estos datos desde tu servidor con la [API de Adapty Mail](mail-send-data-via-api). 2. **Crea un paywall web**: La página de checkout a la que enlaza cada email. 3. **Genera una secuencia**: La IA usa tu perfil de marca para crear entre 1 y 15 emails — redacción, diseño, imágenes destacadas y enlaces de checkout personalizados adaptados a la categoría y al tono de tu app. 4. **Lanza un flow**: Elige un desencadenante (nunca ha comprado, renovación cancelada, problema de facturación, suscripción expirada o reembolsada) y un segmento, y asocia tu campaña. Los emails comienzan a enviarse automáticamente, y los ingresos de las compras generadas por email se atribuyen al email concreto que impulsó la conversión. ## Requisitos \{#requirements\} Para usar Adapty Mail necesitas: - Una cuenta de Adapty - La recogida de emails configurada en tu app — consulta [Recoger emails de usuarios](mail-collect-emails) - `customer_user_id` configurado en tu SDK de Adapty - Un dominio propio con acceso a su configuración DNS - Un proveedor de pagos web (Stripe, Paddle o PayPal) ## Primeros pasos \{#get-started\} Sigue la guía [Primeros pasos con Adapty Mail](mail-get-started) para completar la configuración y lanzar tu primera campaña. --- # File: mail-get-started --- --- title: "Empezar con Adapty Mail" description: "Configura Adapty Mail y lanza tu primer email flow." --- En esta guía, configurarás Adapty Mail y lanzarás tu primer email flow. :::note También puedes enviar datos a Adapty Mail desde tu propio servidor, sin usar el SDK. Si ya tienes los correos y compras de los usuarios en tu backend, o importas suscriptores desde otra fuente, consulta [Enviar correos y transacciones a través de la API de Adapty Mail](mail-send-data-via-api). ::: La configuración tiene seis partes: 1. [Configura tu SDK de Adapty](#1-configure-your-adapty-sdk) 2. [Configura tu dominio de envío](#2-set-up-your-sending-domain) 3. [Crea un paywall web](#3-create-a-web-paywall) 4. [Genera una campaña con IA](#4-generate-a-campaign-with-ai) 5. [Lanza un flujo](#5-launch-a-flow) 6. [Activa el envío](#6-enable-sending) :::tip Si te registraste en Adapty Mail a través de Adapty, tu **perfil de marca** se crea automáticamente a partir de la URL de la store de tu proyecto. Abre **Brand** cuando quieras para revisarlo o ajustarlo — consulta [Brand](mail-brand). Si te registraste de forma independiente, configura tu marca en esa misma página antes de crear campañas o paywalls web. ::: ## Antes de empezar \{#before-you-start\} Asegúrate de tener esto listo antes de comenzar: - **Acceso a DNS**: Puedes añadir registros a tu dominio raíz. - **Proveedor de pagos web**: Tienes una cuenta de Stripe, Paddle o PayPal con tus productos de suscripción configurados. ## 1. Configura tu SDK de Adapty \{#1-configure-your-adapty-sdk\} :::important Adapty Mail es un **producto independiente**. Puedes usarlo aunque tus paywalls, suscripciones o análisis no estén gestionados por Adapty — no es necesario migrar toda tu infraestructura. Para obtener datos de ingresos precisos, la configuración mínima consiste en instalar el SDK de Adapty en modo observador y activar las notificaciones del servidor de App Store. ::: Adapty Mail necesita tres cosas de tu app: datos de compra (para poder atribuir los ingresos al email que generó cada conversión), un identificador de usuario estable y los emails de los usuarios. 1. **Deja que Adapty registre tus ingresos.** El primer paso depende de si ya tienes compras in-app implementadas: - Si **ya tienes compras in-app implementadas con Adapty**, no necesitas hacer nada más en esta etapa. - Si **ya tienes compras in-app implementadas sin Adapty** y no tienes previsto migrar a Adapty, instala el SDK de Adapty para tu plataforma en modo observador. En esta etapa solo necesitas añadir el SDK a tu proyecto, activarlo con el modo observador habilitado y reportar las transacciones. Guías por plataforma: [iOS](implement-observer-mode), [Android](implement-observer-mode-android), [React Native](implement-observer-mode-react-native), [Flutter](implement-observer-mode-flutter), [Unity](implement-observer-mode-unity), [Kotlin Multiplatform](implement-observer-mode-kmp), [Capacitor](implement-observer-mode-capacitor). - Si **aún no tienes compras in-app implementadas y quieres usar Adapty**, completa los pasos de la [guía de inicio rápido](quickstart) para delegar la gestión de compras a Adapty. Luego [activa las notificaciones del servidor de App Store en Adapty](enable-app-store-server-notifications) para recibir actualizaciones relacionadas con los ingresos directamente desde App Store. 2. **Configura la identificación de usuarios.** Pasa un ID estable — el ID de usuario de tu backend, un UID de Firebase o similar — ya sea llamando a `Adapty.identify()` o pasando `customerUserId` a `.activate()` al iniciar el SDK. El `customer_user_id` es lo que usa Adapty Mail para vincular campañas, clics y compras al perfil correcto. Guías de plataforma: [iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Unity](unity-identifying-users), [Kotlin Multiplatform](kmp-identifying-users), [Capacitor](capacitor-identifying-users). 3. **Recopila los correos electrónicos de los usuarios.** En cuanto un usuario proporcione su correo en tu app (por ejemplo, durante el registro o el proceso de pago), pásaselo a Adapty llamando a `updateProfile` con el atributo de correo electrónico. Todos los destinatarios de una campaña necesitan este valor. Guías por plataforma: [iOS](setting-user-attributes), [Android](android-setting-user-attributes), [React Native](react-native-setting-user-attributes), [Flutter](flutter-setting-user-attributes), [Unity](unity-setting-user-attributes), [Kotlin Multiplatform](kmp-setting-user-attributes), [Capacitor](capacitor-setting-user-attributes). Si tu app todavía no recopila correos electrónicos, consulta [Estrategias de recopilación de emails](mail-collect-emails#email-collection-strategies). ## 2. Configura tu dominio de envío \{#set-up-your-sending-domain\} Abre Adapty Mail: haz clic en el logo de Adapty en la cabecera y elige **Mail**. Adapty Mail envía desde tu propio dominio. Añades los registros DNS una sola vez y todas las campañas usan el mismo dominio verificado. 1. En Adapty Mail, ve a **Settings → Email Domains**. 2. Introduce tu dominio raíz (por ejemplo, `yourapp.com`) y haz clic en **Preview**. Solo se aceptan dominios apex — los subdominios como `app.yourapp.com` se rechazan al introducirlos. 3. Adapty genera dos subdominios de envío (`mail.yourapp.com` y `email.yourapp.com`). Haz clic en **Confirm** para ver los registros DNS necesarios. 4. En tu registrador de dominio, añade los 10 registros DNS que se muestran (5 por subdominio): - 3 registros CNAME (DKIM) por subdominio - 1 registro MX (Mail-From) por subdominio - 1 registro TXT (SPF, `v=spf1 include:amazonses.com ~all`) por subdominio 5. Opcionalmente, añade un registro TXT DMARC en tu dominio raíz (recomendado). 6. Vuelve a **Settings → Email Domains** y haz clic en **Check Verification**. Un vistazo al proceso de verificación: - **Sondeo automático**: La primera comprobación se realiza unos 5 minutos después de enviar el dominio. Los intervalos aumentan hasta una vez por hora hasta que se detectan los registros. - **Comprobación manual**: Haz clic en **Check Verification** en cualquier momento para lanzar una comprobación inmediata. - **Propagación DNS**: Normalmente tarda unos minutos, aunque en casos excepcionales puede llegar a 48 horas. - **Ventana de verificación**: 7 días. Si caduca, tus registros DNS siguen en su lugar — vuelve a introducir tu dominio en **Settings → Email Domains** para iniciar una nueva ventana. Para más información sobre cada tipo de registro y el calentamiento del dominio, consulta [Configura tu dominio de envío](mail-sending-domain). ## 3. Configura tu dominio de envío \{#3-set-up-your-sending-domain\} Adapty Mail envía desde tu propio dominio. Añades los registros DNS una sola vez — todas las campañas usan el mismo dominio verificado. 1. En Adapty Mail, ve a **Settings → Email Domains**. 2. Introduce tu dominio raíz (por ejemplo, `yourapp.com`) y haz clic en **Preview**. Solo se aceptan dominios apex — los subdominios como `app.yourapp.com` se rechazan al introducirlos. 3. Adapty genera dos subdominios de envío (`mail.yourapp.com` y `email.yourapp.com`). Haz clic en **Confirm** para ver los registros DNS necesarios. 4. En tu registrador de dominio, añade los 10 registros DNS que se muestran (5 por subdominio): - 3 registros CNAME (DKIM) por subdominio - 1 registro MX (Mail-From) por subdominio - 1 registro TXT (SPF, `v=spf1 include:amazonses.com ~all`) por subdominio 5. Opcionalmente, añade un registro DMARC TXT en tu dominio raíz (recomendado). 6. Vuelve a **Settings → Email Domains** y haz clic en **Check Verification**. Tiempos de verificación de un vistazo: - **Comprobación automática**: La primera verificación se ejecuta unos 5 minutos después de que envíes los registros. Los intervalos aumentan hasta una vez por hora hasta que se encuentran los registros. - **Comprobación manual**: Haz clic en **Check Verification** en cualquier momento para lanzar una verificación inmediata. - **Propagación DNS**: Normalmente en minutos, hasta 48 horas en casos excepcionales. - **Ventana de verificación**: 7 días. Si caduca, tus registros DNS se mantienen — vuelve a introducir tu dominio en **Settings → Email Domains** para abrir una nueva ventana. Para más detalles sobre cada tipo de registro y el calentamiento del dominio, consulta [Configura tu dominio de envío](mail-sending-domain). ### Opción A: Generar con IA \{#option-a-generate-with-ai\} La página muestra una lista de verificación de **Prerequisites** con botones en línea — recórrela en orden y luego vuelve para generar. La lista cubre el inicio de sesión en el paywall builder, la conexión con Stripe, la incorporación de productos y la revisión del resultado. Consulta [Configurar el checkout](mail-checkout) para ver el proceso completo. Cuando todos los requisitos estén en verde, haz clic en **Generate** para abrir el diálogo de generación: - **Environment**: Elige **Production** o **Sandbox**. Sandbox usa tus productos en modo de prueba de Stripe y es la opción segura por defecto para entornos de desarrollo y locales. - **Plans**: Selecciona hasta **3 planes de Stripe** (cada plan es un producto + precio). Estas son las ofertas que el paywall generado presenta a los usuarios en el proceso de pago. Haz clic en **Generate** para ejecutar la generación. Cuando termine, abre el editor para revisar y publicar. :::important El paywall debe publicarse antes de poder servir tráfico de pago. Los paywalls sin publicar devuelven un error cuando los usuarios hacen clic en los enlaces de pago por correo electrónico. ::: ### Opción A: Generar con IA \{#option-a-generate-with-ai\} 1. Selecciona **Generate with AI**. 2. Haz clic en **Log in to the paywall builder**. El editor de web paywalls se abre en una nueva pestaña. Si todavía no has iniciado sesión, hazlo con tus credenciales de Adapty. 3. En el editor, activa la integración con tu proveedor de pagos (Stripe, Paddle o PayPal). Consulta [Configuración del web paywall](web-paywall-configuration) para más detalles. 4. Vuelve a Adapty Mail y haz clic en **Proceed to generation**. 5. Revisa el paywall generado, luego guárdalo y publícalo. ## 4. Genera una campaña con IA \{#generate-a-campaign-with-ai\} La IA crea toda la secuencia de correos por ti: textos, diseño, imágenes destacadas y enlaces de checkout personalizados, todo adaptado a tu marca. 1. En Adapty Mail, ve a **Campaigns** y haz clic en **Create**. 2. Escribe el nombre de la campaña. 3. En el desplegable **Web paywall**, selecciona el web paywall que añadiste en el paso anterior. 4. Haz clic en **Generate emails**. 5. Rellena el diálogo de generación: tono, idioma, un prompt personalizado opcional (hasta 2.000 caracteres) y el número de correos (1–15, por defecto 4). Consulta [Crear una campaña](mail-create-campaign) para saber qué hace cada campo. 6. Haz clic en **Generate**. La generación suele tardar unos minutos. El sistema agota el tiempo tras 5 minutos si no puede completarla — vuelve a intentarlo si ocurre. 7. Previsualiza cada correo. La cabecera de vista previa tiene un **Theme toggle** (Auto, Light, Dark) que controla cómo se renderiza la vista previa — el contenido generado es idéntico en todos los modos. Puedes regenerar correos individuales, editar el texto o abrir el editor HTML para un control más detallado. 8. Haz clic en **Create** para guardar la campaña. La campaña se guarda como **borrador** y aún no envía nada: las campañas solo se activan cuando se vinculan a un flujo (siguiente paso). No hay ninguna acción de "publicar" separada en el editor de campañas. ## 5. Lanzar un flujo \{#launch-a-flow\} Un flujo combina un **trigger** (un evento como el vencimiento de una suscripción) con un **segmento**, y envía a ese segmento la **campaña** que elijas. Adapty Mail incluye cinco triggers fijos, cada uno con su propia vista de flujo. 1. En Adapty Mail, ve a **Flows** y abre el activador que quieres configurar: - **Never purchased** — usuarios que se registraron pero aún no han realizado ninguna compra. - **Renewal cancelled** — usuarios que desactivaron la renovación automática pero aún tienen una suscripción activa. - **Billing issue** — pago fallido, tarjeta rechazada o caducada, o período de gracia. - **Expired** — la suscripción ha caducado y el acceso ya no está disponible. - **Refunded** — usuarios que solicitaron un reembolso tras la compra. Consulta [Flows](mail-flows) para conocer el objetivo y el tono recomendado para cada activador. 2. Haz clic en **Create** para abrir el cuadro de diálogo. 3. En el diálogo: - Elige un **Segment** (por ejemplo, **All Users** para dirigirte a todos los usuarios que activen este disparador, o crea un nuevo segmento basado en los atributos del perfil). - Deja el tipo de contenido en **Campaign** (la opción de prueba A/B se explica en [Pruebas A/B](mail-ab-testing)). - Selecciona la **Campaign** que guardaste en el Paso 4. 4. Haz clic en **Save**. El flujo se activa de inmediato — no hay un paso de lanzamiento independiente. A partir de este momento, los usuarios que coincidan con el segmento empezarán a recibir la campaña en cuanto activen el evento disparador. :::note Puedes añadir más de una fila de segmento → campaña al mismo trigger; se ejecutan en orden de prioridad. La fila **All Users**, si se usa, debe ser la última (de menor prioridad) para capturar a todos los usuarios que no coincidan con un segmento más específico. ::: ## 6. Activar el envío \{#enable-sending\} Hasta ahora tu campaña está configurada pero aún no se está ejecutando: la **integración con Adapty** que sincroniza los eventos de suscripción en Adapty Mail sigue desactivada. Activarla es el paso final: los eventos empiezan a fluir, los segmentos empiezan a coincidir y los correos empiezan a enviarse. Este paso solo se desbloquea después del Paso 5. Antes de lanzar un flujo, el botón **Enable** en **Settings → Integrations** aparece desactivado con el tooltip *"Set up at least one flow before enabling Adapty integration."* 1. En Adapty Mail, ve a **Settings → Integrations**. 2. Haz clic en **Enable Adapty integration** (o en **Enable** si la integración ya existe de una configuración anterior). Una vez activada, Adapty envía cada evento de suscripción —nuevas suscripciones, renovaciones, pruebas, conversiones, reembolsos, problemas de facturación— a Adapty Mail. Estos eventos determinan la pertenencia a segmentos, el enrutamiento de campañas y las condiciones de parada que pausan una secuencia cuando un usuario convierte. :::note El interruptor de **integración con Adapty** en Settings *no* es lo mismo que el workspace de socio de Adapty con el que iniciaste sesión en Adapty Mail. El workspace de socio es el que creó tu cuenta y (si te registraste a través de Adapty) tu marca. El interruptor de integración aquí controla la sincronización de eventos — debe activarse por proyecto. ::: ## Solución de problemas \{#troubleshooting\} | Problema | Solución | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | | Verificación DNS bloqueada | Comprueba que los registros coincidan exactamente: sin puntos finales, con los destinos CNAME correctos. Espera 5-10 minutos y vuelve a hacer clic en **Check Verification** | | Ventana de verificación expirada | Tus registros siguen en su lugar. Vuelve a introducir tu dominio en **Settings → Email Domains** para iniciar una nueva ventana | | Generación fallida o por tiempo de espera | Comprueba tu conexión a internet e inténtalo de nuevo. Si el problema persiste, contacta con el soporte de Adapty | ## Más información \{#learn-more\} - **[Recopilar emails de usuarios](mail-collect-emails)**: Estrategias para conseguir cobertura de emails si tu app todavía no los recoge. - **[Configura tu dominio de envío](mail-sending-domain)**: Detalles de los registros DNS, niveles de calentamiento y solución de problemas. - **[Configura el checkout](mail-checkout)**: Anatomía del embudo de checkout y personalización. - **[Analíticas de campañas](mail-analytics)**: Monitoriza la entrega, el engagement y los ingresos. - **[Pruebas A/B](mail-ab-testing)**: Prueba múltiples versiones de secuencias. --- # File: mail-collect-emails --- --- title: "Recopilar emails de usuarios para Adapty Mail" description: "Pasa emails de usuarios e identificadores estables a Adapty para que las campañas lleguen a tus usuarios." --- Adapty Mail necesita un `customer_user_id` estable y un email por cada usuario al que envía mensajes. Configura ambos en el código de tu app antes de lanzar una campaña. ## Recopilar correos electrónicos de los usuarios \{#collect-user-emails\} Para cada usuario deben llegar a Adapty dos valores: un `customer_user_id` estable que lo identifique y el correo electrónico en sí. La identificación debe ir primero — sin ella, Adapty no tiene ningún perfil al que asociar el correo. 1. **Identifica al usuario.** Pasa un ID estable — el ID de usuario de tu backend, un UID de Firebase o similar — bien incluyéndolo como `customerUserId` en `.activate()` al iniciar el SDK, bien llamando a `Adapty.identify()` más adelante (por ejemplo, al iniciar sesión). En cualquier caso, el ID debe estar establecido antes de mostrar cualquier paywall. Guías por plataforma: [iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Unity](unity-identifying-users), [Kotlin Multiplatform](kmp-identifying-users), [Capacitor](capacitor-identifying-users). 2. **Envía el email.** En cuanto el usuario proporcione su email, envíalo a Adapty mediante `updateProfile` usando el parámetro `email`. Guías por plataforma: [iOS](setting-user-attributes), [Android](android-setting-user-attributes), [React Native](react-native-setting-user-attributes), [Flutter](flutter-setting-user-attributes), [Unity](unity-setting-user-attributes), [Kotlin Multiplatform](kmp-setting-user-attributes), [Capacitor](capacitor-setting-user-attributes). :::important - Pasa siempre un `customer_user_id` **estable**, nunca un identificador anónimo. Si un usuario desinstala y reinstala tu app, Adapty usa este ID para vincular la reinstalación al perfil existente y atribuir las compras al usuario correcto. - Obtén el consentimiento explícito del usuario antes de recopilar y enviar correos electrónicos a Adapty. Eres responsable del cumplimiento del RGPD, CAN-SPAM y normativas similares en tus mercados objetivo. ::: <Details> <summary>Verifica la cobertura de correos electrónicos</summary> Tras implementar la recopilación, comprueba la cobertura en Adapty: 1. Ve a **Customers → Profiles**. 2. Filtra los perfiles que tengan un email configurado. Apunta a al menos un 30–50% de cobertura de email entre tus usuarios activos antes de lanzar tu primera campaña. No hace falta esperar al 100% — lanza en cuanto llegues al 30%. Los usuarios que proporcionen su email más adelante se incorporan automáticamente a las campañas activas cuando cumplan los requisitos. </Details> ## Estrategias para recopilar emails \{#email-collection-strategies\} La mayoría de las apps no recopilan emails por defecto. Elige el enfoque que mejor se adapte al estado actual de tu app. | Estrategia | Ideal para | Cómo funciona | | ---------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | **Autenticación existente** | Apps con cualquier tipo de inicio de sesión | Ya tienes el email — pásalo a Adapty después de que el usuario se autentique. Consulta la referencia del método de autenticación a continuación para saber dónde leerlo. | | **Solicitud de email antes del paywall** | Apps sin autenticación — salud, bienestar, astrología, editores de fotos | Añade una pantalla de entrada de email entre el onboarding y el paywall. La conversión suele situarse entre el 70 y el 90 % porque los usuarios ya han invertido tiempo. | | **Checkout con el web paywall builder** | Mínimo trabajo con el SDK; email capturado en la web | La primera pantalla del web paywall builder recoge el email y lo envía a Adapty — útil para usuarios que hacen clic en una campaña antes de que haya un gate in-app activo. | | **Paso en el onboarding** | Onboarding basado en cuestionarios (fitness, nutrición, educación) | Coloca un campo de entrada de email 2 o 3 pasos después del inicio del onboarding. Preséntalo como entrega de valor ("Te enviaremos tu plan personalizado por email") y evita que el paso sea opcional. | | **Adapty Mail API** | Envío de emails desde tu servidor, sin el SDK de Adapty | Envía perfiles al endpoint [Save profile](api-mail/operations/saveProfile) de la Adapty Mail API. Consulta [Enviar emails y transacciones mediante la Adapty Mail API](mail-send-data-via-api). | ## Limitaciones \{#limitations\} - **Usuarios anónimos**: Los usuarios sin un `customer_user_id` estable no pueden recibir campañas. Identifícalos cuando creen una cuenta o inicien sesión — a partir de ese momento, cualquier email que proporcionen se asocia a su perfil de Adapty. - **Usuarios sin email**: Los perfiles sin email quedan excluidos de la entrega de campañas y no aparecen en los análisis de campañas. En cuanto proporcionen un email, pasan a ser elegibles para futuras campañas. --- # File: mail-send-data-via-api --- --- title: "Enviar emails y transacciones a través de la API de Adapty Mail" description: "Envía perfiles de usuario y transacciones a Adapty Mail directamente desde tu servidor, sin necesidad del SDK de Adapty." --- La API de Adapty Mail te permite enviar perfiles de usuario y transacciones a Adapty Mail directamente desde tu servidor, sin enrutar los datos a través del SDK de Adapty. Úsala cuando quieras: - Añadir suscriptores cuando todavía no tienes una base en Adapty Mail. - Reutilizar la base de suscriptores de tus otras apps. - Alimentar Adapty Mail de servidor a servidor, con tu backend como fuente de verdad. :::note **¿API o SDK?** La mayoría de las apps envían datos a Adapty Mail a través del SDK de Adapty, que recoge emails y compras de forma automática. Elige la API cuando tu app no tiene el SDK de Adapty integrado, cuando los datos ya residen en tu servidor, o cuando importas suscriptores desde otra fuente. ::: ## Antes de empezar \{#before-you-start\} :::warning Termina de configurar Adapty Mail antes de enviar datos: necesitas una campaña, segmentos (si los necesitas), un paywall web y un flow lanzado. Adapty Mail solo envía correos a los perfiles creados después de completar esta configuración; los perfiles que envíes antes no recibirán ningún correo. Sigue primero [Primeros pasos con Adapty Mail](mail-get-started) y luego vuelve aquí. ::: También necesitas tu clave API y la URL base: - **Clave API secreta**: En Adapty Mail, ve a **Settings** y copia tu clave API secreta. La clave es específica del proyecto, de modo que la API sabe a qué proyecto pertenecen los datos. - **URL base**: Todas las solicitudes van a `https://api-mail.adapty.io`. - **Autenticación**: Envía la clave en el encabezado **Authorization** como `Bearer {your_secret_api_key}`. :::important Obtén el consentimiento explícito antes de recopilar correos electrónicos y enviarlos a Adapty Mail. Eres responsable del cumplimiento del RGPD, CAN-SPAM y normativas similares en tus mercados. ::: ## Enviar perfiles de usuario \{#send-user-profiles\} Un perfil contiene el email y los atributos del usuario. Para crear o actualizar uno, envía una solicitud POST a `/api/v1/profile/save/`. Se requieren tres campos: - Un `external_profile_id` estable que tu app o backend gestione - El `email` al que Adapty Mail envía las campañas - `external_created_at` — la fecha de creación del usuario, que puedes usar en segmentos :::important Envía siempre un `external_profile_id` estable, nunca un valor anónimo o por instalación. Adapty Mail lo usa para vincular emails, clics y compras a un único perfil. ::: ```bash curl --request POST \ --url 'https://api-mail.adapty.io/api/v1/profile/save/' \ --header 'Authorization: Bearer {your_secret_api_key}' \ --header 'Content-Type: application/json' \ --data '{ "external_profile_id": "user_12345", "external_created_at": "2026-06-01T10:30:00Z", "email": "jane@example.com", "country": "US", "custom_attributes": { "plan": "trial" } }' ``` Consulta la referencia de [Save profile](api-mail/operations/saveProfile) para ver todos los campos disponibles. ## Enviar eventos de transacción \{#send-transaction-events\} :::note Con un perfil que tenga email es suficiente para llegar a los usuarios en el flow de **nunca han comprado**. Los usuarios en cualquier otro flow también necesitan eventos de transacción. ::: Todos los flows excepto el de **nunca han comprado** se basan en el historial de compras. Envía los eventos de transacción de un perfil a medida que gestiones compras, renovaciones y cancelaciones, para que Adapty Mail pueda ubicarlo en el flow correcto. Los eventos de transacción también alimentan la atribución de ingresos. Omítelos solo si ejecutas campañas exclusivamente de **nunca han comprado**. Para registrar una transacción, envía una petición POST a `/api/v1/profile/transaction-event/save/`. Usa el mismo `external_profile_id` que enviaste con el perfil para que Adapty Mail vincule la transacción al usuario correcto. ```bash curl --request POST \ --url 'https://api-mail.adapty.io/api/v1/profile/transaction-event/save/' \ --header 'Authorization: Bearer {your_secret_api_key}' \ --header 'Content-Type: application/json' \ --data '{ "event_type": "subscription_started", "event_id": "evt_abc123", "event_datetime": "2026-06-10T14:20:05Z", "external_profile_id": "user_12345", "store": "app_store", "store_product_id": "premium_monthly", "store_transaction_id": "1000000123456789", "store_original_transaction_id": "1000000123456789", "purchased_at": "2026-06-10T14:20:00Z", "originally_purchased_at": "2026-06-10T14:20:00Z", "price_usd": "9.99" }' ``` Consulta la referencia de [evento Save transaction](api-mail/operations/saveTransactionEvent) para ver todos los campos disponibles. ### Asigna tus eventos a los flows \{#map-your-events-to-flows\} Envía el `event_type` que corresponda a lo que ocurrió. Adapty Mail deduce el estado del perfil a partir del historial de eventos y lo enruta al flow correspondiente. | `event_type` | Envíalo cuando | Flow | | --- | --- | --- | | `subscription_started` | Un usuario inicia una nueva suscripción. | Activo — sin flow de reenganche | | `subscription_renewed` | Una suscripción se renueva automáticamente. | Activo — sin flow de reenganche | | `subscription_renewal_reactivated` | Un usuario reactiva la renovación automática. | Activo — sin flow de reenganche | | `non_subscription_purchase` | Un usuario realiza una compra única. | Activo — sin flow de reenganche | | `subscription_renewal_cancelled` | Un usuario desactiva la renovación automática (sigue activa hasta que expire). | Renovación cancelada | | `billing_issue_detected` | Falla el pago de una renovación. | Problema de facturación | | `entered_grace_period` | El pago falla pero el usuario sigue en el período de gracia. | Problema de facturación | | `subscription_expired` | Una suscripción caduca y se pierde el acceso. | Expirada | | `subscription_refunded` | Se reembolsa una compra de suscripción. | Reembolsada | | `non_subscription_purchase_refunded` | Se reembolsa una compra única. | Reembolsada | --- # File: mail-brand --- --- title: "Marca en Adapty Mail" description: "Revisa y ajusta el perfil de marca que impulsa la generación de correos y los paywalls web." --- Una **marca** es un perfil consolidado que Adapty Mail construye a partir de las fuentes públicas de tu app — el listado de App Store o Google Play, la landing page, las páginas de términos y privacidad, y los perfiles en redes sociales. Define el contenido de los correos, el tono, los elementos visuales, el contenido de los paywalls web y la demo. Hay una marca por proyecto; todas las funciones posteriores leen del mismo perfil. Abre la marca desde la entrada **Brand** en la barra lateral de Adapty Mail. - **Si te registraste en Adapty Mail a través de Adapty**: Tu marca se creó automáticamente a partir de la URL del store de tu proyecto de Adapty. La página Brand se abre con el perfil completo, listo para revisar y ajustar. - **Si te registraste de forma independiente**: La página Brand se abre con una pantalla de configuración inicial — consulta [Configurar desde cero](#set-up-from-scratch). ## Qué contiene un perfil de marca \{#whats-in-a-brand-profile\} Una marca tiene 13 secciones. Adapty Mail las usa directamente para generar emails y paywalls web. - **Identidad**: Nombre de la app, descripción breve, eslogan. - **Identidad visual**: Colores (primario, fondo, secundario, acento, texto, CTA), tipografía, notas de estilo, URL del logotipo. - **Audiencia**: Datos demográficos, idiomas, mercados. - **Características**: Cada característica tiene un nombre, el beneficio que aporta y una descripción opcional. - **Insights**: Propuesta de valor única, observaciones, objeciones frecuentes con sus respuestas, y etiquetas. - **Voz de marca**: Tono, nivel de formalidad, vocabulario, registro emocional. - **Muestras de voz**: Titulares de ejemplo, CTAs de ejemplo y presets de tono usados durante la generación de emails. - **Prueba social**: Número de usuarios, valoración, número de valoraciones, menciones en prensa, métricas clave. - **Redes sociales**: Twitter, Instagram, TikTok, YouTube, Facebook, LinkedIn. - **Enlaces legales**: URL de términos, URL de privacidad, email de soporte. - **Reseñas**: Reseñas de usuarios extraídas de la fuente del store — contenido, autor, valoración, fuente. - **Puntos de dolor**: Declaraciones de dolor fundamentadas en reseñas o señales sociales. - **FAQs**: Preguntas y respuestas usadas en el contenido de emails y en las secciones del paywall web. ## Editar una sección manualmente \{#edit-a-section-manually\} Cada sección en la vista de marca tiene un botón **Edit**. Al hacer clic en él, se abre un editor en línea para esa sección. 1. Haz clic en **Edit** en la sección que quieras modificar. 2. Actualiza los campos directamente. Adapty Mail guarda los cambios sin confirmar como borrador. 3. Haz clic en **Save** en el banner de borrador que aparece en la parte superior de la página para aplicar los cambios. Haz clic en **Discard** para descartarlos. Solo puede estar abierta una sección a la vez. Si intentas abrir una segunda, la primera te pedirá que termines o canceles. :::important Las ediciones se pausan mientras se procesa una fuente: una fuente que termine de procesarse podría sobreescribir los cambios que tengas en curso. Cualquier editor abierto se cierra automáticamente cuando comienza el procesamiento, y el botón **Save** permanece deshabilitado en el banner de borrador hasta que el procesamiento finalice. ::: ## Refinar con IA \{#refine-with-ai\} El botón **Refine with AI** en la esquina inferior derecha abre un panel de chat junto a la marca. Úsalo para describir cambios en lenguaje natural; la IA propone un borrador refinado que puedes guardar o rechazar. 1. Haz clic en **Refine with AI**. 2. Describe el cambio. El alcance del chat es solo ediciones de marca — no responderá preguntas generales ni reescribirá contenido fuera del perfil de marca. 3. Revisa el borrador propuesto en la vista principal. Adapty Mail resalta las secciones modificadas. 4. Haz clic en **Save** en el banner del borrador para aplicar los cambios, o en **Discard** para mantener la marca guardada. Ejemplos de prompts útiles: - "Haz el tono más desenfadado." - "Añade la función de modo sin conexión." - "Refuerza la propuesta de valor única." - "Reescribe la descripción de la audiencia para el mercado de EE. UU." ## Añadir más fuentes \{#add-more-sources\} La fuente del store siembra el perfil. Los tipos de fuente adicionales refinan secciones específicas: las páginas de destino mejoran la identidad visual y el copy, las páginas de términos y privacidad mejoran los enlaces legales, y los perfiles de redes sociales mejoran las muestras de voz y la prueba social. En la vista de marca, el panel **Sources** muestra todas las fuentes y un formulario **Add** debajo. Elige un tipo, pega la URL y haz clic en **Add**. - **App Store**: `https://apps.apple.com/...` - **Google Play**: `https://play.google.com/store/apps/details?id=...` - **Landing page**: Tu sitio de marketing, por ejemplo `https://yourapp.com`. - **Terms / Privacy**: Un enlace directo a tu página de términos o privacidad. - **Social profile**: URL de Twitter, Instagram, TikTok, YouTube, Facebook o LinkedIn. Solo una fuente por tipo. El selector desactiva un tipo una vez que ya hay una fuente de ese tipo en proceso o completada. Para reemplazar una fuente, elimina la marca y vuelve a incorporarla — las fuentes individuales no se pueden eliminar. ## Configuración desde cero \{#set-up-from-scratch\} Si tu proyecto aún no tiene una marca (algo habitual en cuentas de Adapty Mail creadas de forma independiente), la página **Brand** se abre con una pantalla de configuración inicial. El store de origen — App Store o Google Play — es la base; otros tipos de fuente pueden añadirse después. 1. Elige el store (**App Store** o **Google Play**) y pega la URL del listing. 2. Haz clic en **Build my brand**. Adapty Mail obtiene la página, analiza las reseñas e infiere la voz de tu marca. El procesamiento suele tardar menos de un minuto. 3. Cuando termina el procesamiento, se abre la vista de marca con las 13 secciones completadas. Si una fuente falla (URL inválida, página inaccesible, error del analizador), la pantalla muestra el mensaje de error y un botón **Try again**. Corrige la URL y vuelve a enviarla. ## Dónde se usa la marca \{#where-the-brand-is-used\} La marca la consumen todas las funcionalidades que necesitan saber cómo suena y se ve tu app: - **Generación de emails**: El copy, el tono, los elementos visuales, el bloque del remitente y la imagen principal se leen desde la marca. Consulta [Crear una campaña](mail-create-campaign). - **Constructor de paywalls web**: La marca es un requisito previo para generar paywalls — sin `brand_saved`, la generación está bloqueada. Consulta [Configurar el checkout](mail-checkout). - **Onboarding**: El paso **Set up brand** en el checklist de onboarding se marca como completado cuando existe una marca. ## Eliminar una marca \{#delete-a-brand\} La acción **Delete brand** se encuentra en la sección **Danger zone** al final de la vista de la marca. 1. Haz clic en **Delete** en la sección Danger zone. 2. Confirma en el cuadro de diálogo. Al eliminar una marca se borra el perfil y todas sus fuentes. No hay forma de deshacerlo: para recuperarla, pega una URL de App Store o Google Play en la pantalla de inicio y vuelve a realizar el onboarding desde cero. :::warning Las campañas existentes conservan la instantánea de la marca con la que se generaron, pero no podrás crear nuevas campañas ni generar paywalls hasta que vuelvas a incorporar una marca. ::: ## Limitaciones \{#limitations\} - **Una marca por proyecto**: Cada proyecto de Adapty Mail tiene una única marca. Para apuntar a otra app, crea un nuevo proyecto. - **Una fuente por tipo**: Una marca puede tener como máximo una fuente de App Store, una de Google Play, una landing page, una de términos/privacidad y un perfil en redes sociales. - **Sin eliminación por fuente**: Las fuentes individuales no se pueden eliminar desde la interfaz. Usa **Delete brand** si necesitas reemplazar una fuente. - **Edición bloqueada durante el procesamiento**: Las ediciones de secciones, los guardados del chat de refinamiento y la eliminación de la marca quedan bloqueados mientras una fuente está en estado `pending` o `processing`. - **Las fuentes fallidas permanecen en la lista**: Una fuente en estado `failed` sigue visible en el panel con su mensaje de error. Envía el mismo tipo de nuevo para reintentarlo: la entrada fallida se reemplaza cuando una nueva programación tiene éxito. --- # File: mail-sending-domain --- --- title: "Configura tu dominio de envío para Adapty Mail" description: "Añade registros DNS, verifica tu dominio y entiende el calentamiento para que Adapty Mail pueda enviar correos en tu nombre." --- Adapty Mail envía campañas desde tu propio dominio — no desde una dirección compartida — para que la reputación del remitente quede bajo tu control. Lo configuras una sola vez y todas las campañas usan ese mismo dominio verificado. Para los pasos mínimos, consulta la sección de dominio en [Primeros pasos con Adapty Mail](mail-get-started#2-set-up-your-sending-domain). Este artículo cubre la configuración completa, cómo funciona la verificación y el comportamiento de calentamiento automático. ## Requisitos \{#requirements\} - **Dominio raíz (apex)**: introduce tu dominio raíz (por ejemplo, `yourapp.com`), no un subdominio. Las entradas como `app.yourapp.com` se rechazan en la validación. - **Registros NS activos**: el dominio debe resolver. Adapty Mail realiza una consulta DNS durante la configuración y rechaza dominios sin registros NS válidos. - **Un dominio por proyecto de Adapty**: un dominio no puede compartirse entre proyectos. Si el dominio ya está registrado en cualquier proyecto —el tuyo o el de otra persona—, la configuración falla. ## Configura tu dominio de envío \{#set-up-your-sending-domain\} El asistente de configuración tiene tres pantallas: introduce el dominio, confirma los subdominios generados y añade los registros DNS. Las tres están en **Settings → Email Domains**. 1. **Introduce tu dominio.** Escribe tu dominio raíz en el campo **Domain** y haz clic en **Preview**. Adapty Mail valida el formato (ASCII, dos etiquetas, sin guiones al inicio o al final, TLD de 2 o más caracteres) y comprueba que el DNS resuelve. 2. **Confirma los subdominios.** Adapty Mail genera dos subdominios de envío con prefijos fijos — `mail.yourapp.com` y `email.yourapp.com` — cada uno con su propia identidad SES. También crea un subdominio Mail-From bajo cada uno (`hello.mail.yourapp.com` y `hello.email.yourapp.com`). Revísalos y haz clic en **Confirm**. 3. **Agrega los registros DNS.** La pantalla final muestra todos los registros que debes añadir: 10 en total, 5 por subdominio de envío, más un registro DMARC opcional en el dominio raíz. Usa **Download CSV** para exportar la lista completa, o copia los registros uno a uno en tu registrar. Haz clic en **Done** cuando los registros estén configurados. <Details> <summary>Referencia de registros DNS</summary> Para cada subdominio de envío (`mail.yourapp.com` y `email.yourapp.com`), añade: **DKIM — 3 registros CNAME.** Firmas criptográficas que verifican que el correo no fue modificado en tránsito. | Campo | Formato | | ----- | ----------------------------------- | | Tipo | CNAME | | Nombre | `{token}._domainkey.{subdomain}` | | Valor | `{token}.dkim.amazonses.com` | **Mail-From — 1 registro MX.** Gestiona los rebotes. | Campo | Formato | | -------- | --------------------------------------------------------- | | Type | MX | | Name | `hello.{subdomain}` (por ejemplo, `hello.mail.yourapp.com`) | | Priority | `10` | | Value | `feedback-smtp.{region}.amazonses.com` | **SPF — 1 registro TXT.** Autoriza a Adapty para enviar correos en tu nombre. | Campo | Formato | | ----- | -------------------------------------- | | Type | TXT | | Name | `hello.{subdomain}` | | Value | `"v=spf1 include:amazonses.com ~all"` | En tu dominio raíz, añade el registro DMARC opcional: | Campo | Formato | | ----- | --------------------- | | Type | TXT | | Name | `_dmarc.{domain}` | | Value | `v=DMARC1; p=reject` | Los tokens, la región y cualquier otro valor provienen de AWS SES en el momento de la configuración. Cópialos siempre desde la pantalla de registros DNS en Adapty Mail, no desde esta referencia. </Details> ## Cómo funciona la verificación \{#how-verification-works\} Una vez que los registros DNS están configurados, Adapty Mail sondea el DNS automáticamente, aunque también puedes activar las comprobaciones manualmente. - **Sondeo automático**: El sondeo comienza 5 minutos después de enviar, y el intervalo se duplica en cada ronda — 10 min, 20 min, 40 min — hasta alcanzar un máximo de 60 min. Continúa hasta que se encuentren registros o se cierre la ventana de 7 días. - **Comprobación manual**: Haz clic en **Check Verification** para forzar una comprobación inmediata. Hay un tiempo de espera de 60 segundos entre comprobaciones manuales — si se activa demasiado rápido, devuelve *"Verification check is on cooldown."* - **Estados de estado**: El DKIM y el Mail-From de cada subdominio se rastrean de forma independiente como **Pending**, **Success** o **Failed**. Un dominio se considera completamente verificado solo cuando los cuatro estados muestran **Success**. - **Plazo de 7 días**: Si la verificación no se completa en 7 días, la identidad se marca como **Failed**. Los registros DNS permanecen en tu registrador — vuelve a introducir el dominio en **Settings → Email Domains** para iniciar una nueva ventana. - **Después de la verificación**: Si eliminas o cambias los registros DNS más adelante, AWS SES eventualmente degrada la identidad. Mantén los registros en su lugar mientras planees enviar. - **Propagación de DNS**: Normalmente tarda minutos; en casos excepcionales puede tardar hasta 48 horas. ## Warm-up del dominio \{#domain-warm-up\} Los dominios nuevos no tienen reputación ante proveedores de correo como Gmail o Yahoo, por lo que envíos de gran volumen desde un dominio recién creado corren el riesgo de acabar en spam. Adapty Mail gestiona el warm-up automáticamente aumentando tu límite de envíos diarios a lo largo de 14 niveles. No requiere ninguna configuración. ### Cómo funcionan los niveles \{#how-tiers-work\} Tu dominio comienza en el **Nivel 1** (200 envíos/día) y avanza automáticamente cuando las métricas de entregabilidad se mantienen saludables. Si la tasa de rebotes aumenta o la tasa de quejas sube, el avance se pausa y puede revertirse hasta que la reputación se recupere. | Tier | Límite diario | | ---- | ------------- | | 1 | 200 | | 2 | 400 | | 3 | 800 | | 4 | 1,500 | | 5 | 2,500 | | 6 | 4,000 | | 7 | 6,000 | | 8 | 8,000 | | 9 | 10,000 | | 10 | 13,000 | | 11 | 16,000 | | 12 | 20,000 | | 13 | 25,000 | | 14 | 30,000 | Tu nivel actual y límite diario se muestran en **Settings → Email Domains**. ### Impacto en el lanzamiento según el tamaño de la audiencia \{#impact-on-launch-by-audience-size\} | Tamaño de la audiencia | Efecto al lanzar | | ---------------------- | --------------------------------------------- | | Menos de 200 usuarios | Se alcanza a toda la audiencia el primer día | | 200–2.000 usuarios | La entrega se distribuye en varios días | | Más de 2.000 usuarios | La entrega se distribuye en 1 o 2 semanas | :::tip Lanza tu primera campaña en cuanto se complete la verificación DNS. Cuanto antes empieces a enviar, antes avanzará tu dominio por los niveles y alcanzará la capacidad diaria máxima. ::: ## Limitaciones \{#limitations\} - **Un dominio por proyecto**: Solo puedes tener un dominio de envío por proyecto de Adapty. Para cambiarlo, contacta con soporte — el dashboard no tiene ninguna acción de "cambiar dominio". - **Unicidad entre proyectos**: Un dominio ya registrado en otro proyecto no puede reutilizarse. Si ves *"Domain is already registered to another project"*, elige un dominio diferente o contacta con soporte. - **Los dominios verificados no se pueden eliminar**: Una vez que cualquier subdominio alcanza el estado **Success**, el dashboard bloquea su eliminación. Los dominios pendientes sí se pueden eliminar, pero aun así deberás eliminar manualmente los registros DNS desde tu registrador. - **Prefijos de subdominio fijos**: `mail.`, `email.` y el prefijo Mail-From `hello.` están predefinidos — no se pueden personalizar. Si esos subdominios ya están en uso en tu DNS, la configuración entrará en conflicto. - **Solo dominios apex**: Los subdominios, los puntos finales y los nombres de host de una sola etiqueta son rechazados. - **Sin dominios internacionalizados**: Punycode e IDN no están soportados. El dominio debe ser solo ASCII. ## Solución de problemas \{#troubleshooting\} | Problema | Solución | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | | "Enter a valid domain (e.g. example.com)" | Revisa el campo: solo dominio raíz, solo ASCII, TLD de 2+ caracteres, sin guiones al inicio ni al final. | | "Domain does not have valid DNS records" | El dominio raíz debe resolver correctamente. Confirma que tus registros NS estén activos antes de volver a intentarlo. | | "Domain is already registered to another project" | Elige un dominio diferente, o contacta con soporte si crees que el registro es un error. | | "Verification check is on cooldown" | Espera 60 segundos entre comprobaciones manuales. El sondeo automático continúa en segundo plano. | | Verificación bloqueada en Pending | Comprueba que los registros DNS coincidan exactamente: sin puntos finales, targets CNAME correctos. La propagación DNS puede tardar hasta 48 horas. | | "Cannot delete domain: one or more identities have been successfully verified" | Un dominio verificado no se puede eliminar desde el dashboard. Contacta con soporte para obtener ayuda. | | Los correos van a spam | Confirma que tu registro DMARC está publicado. Los dominios nuevos necesitan un período de calentamiento — consulta [Calentamiento de dominio](#domain-warm-up). | | Tasa de rebote alta | Verifica que tu lista de audiencia contiene direcciones válidas y con consentimiento. Los rebotes ralentizan o detienen el avance de nivel. | --- # File: mail-checkout --- --- title: "Configurar el checkout para Adapty Mail" description: "Crea un paywall web y conecta un proveedor de pagos para que tus campañas de email tengan un checkout web personalizado." --- Cada email que envía Adapty Mail contiene un enlace de checkout único para ese destinatario. Al hacer clic, accede a un embudo de checkout web que lo identifica por perfil, le presenta tu oferta y procesa el pago. Los embudos de checkout se encuentran en **Web Paywalls** dentro de Adapty Mail y se editan en el **web paywall builder** incluido. ## Requisitos \{#requirements\} - Un proveedor de pagos web con tus productos de suscripción configurados. **Generate with AI** solo es compatible con Stripe y se conecta directamente en el builder. **Use your own hosted paywall** acepta cualquier proveedor —Stripe, Paddle, PayPal u otros— ya que el pago se gestiona en tu propio lado. No necesitas una cuenta aparte para el editor web de paywalls. Viene integrado con Adapty Mail: la primera vez que inicias sesión se crea un espacio de trabajo automáticamente, y accedes al editor con tus credenciales de Adapty. Esto es independiente de cualquier paywall web que hayas configurado en la página de paywalls del Adapty Dashboard principal — los paywalls web de Adapty Mail son entidades separadas que se gestionan íntegramente desde Adapty Mail. ## Configura tu embudo de pago \{#set-up-your-checkout-funnel\} En Adapty Mail, ve a **Web Paywalls → Create**. Tienes dos opciones: - **Generate with AI**: el creador de paywalls web integrado en Adapty Mail genera el embudo por ti. Solo compatible con Stripe; si usas Paddle o PayPal, utiliza la segunda opción. - **Use your own hosted paywall**: conecta un paywall que ya tengas alojado, con cualquier proveedor de pago. ### Generar con IA \{#generate-with-ai\} La página de creación muestra un panel de **Prerequisites** en la parte superior con botones de acción en línea que te guían por cada condición previa: preparación de marca, inicio de sesión en el builder, conexión con Stripe, productos y un paso final de revisión y publicación. Completa cada uno; el panel se actualiza a medida que terminas cada paso. Cuando los requisitos previos estén en verde, haz clic en **Generate** para abrir el diálogo de generación. Hay dos opciones a elegir: - **Environment**: Elige **Production** o **Sandbox**. Sandbox usa tus productos en modo de prueba de Stripe y es la opción segura por defecto para entornos de desarrollo y locales — su cuenta está aislada de producción, por lo que las transacciones de prueba nunca afectan los datos en vivo. - **Plans**: Elige hasta **3 planes de Stripe**. Cada plan es una combinación de producto + precio. El paywall presenta estos como las ofertas en el checkout. Si eliges menos de 3, el paywall muestra solo los planes que hayas seleccionado. Haz clic en **Generate** para ejecutar la compilación. Cuando termine, abre el editor en el builder para revisar el resultado y publicarlo. Luego, haz clic en **Save**. :::important El paywall debe estar publicado antes de que pueda gestionar el tráfico de checkout. Los paywalls no publicados devuelven un error cuando los usuarios hacen clic en los enlaces de checkout por correo electrónico. ::: Para ver los detalles del proveedor de pagos dentro del builder (cuentas de Stripe, modo de prueba o producción, configuración de productos), consulta [Configuración del paywall web](web-paywall-configuration). ### Usa tu propio paywall alojado \{#use-your-own-hosted-paywall\} 1. En la página de creación, selecciona **Enter URL manually**. 2. Pega la URL de tu paywall. Debe incluir los marcadores `{email}` y `{external_profile_id}` como parámetros de consulta — Adapty Mail los rellena por destinatario para que la página sepa quién es el visitante. Ejemplo: ``` https://example.com/paywall?email={email}&profile={external_profile_id} ``` 3. Guarda. Esta opción funciona con cualquier proveedor de pagos — Adapty Mail solo gestiona la redirección y la sustitución de parámetros; el pago y la personalización ocurren íntegramente en tu lado. ## Cómo se ve el checkout \{#what-the-checkout-looks-like\} Cuando un usuario hace clic en un enlace de checkout, aterriza en la **Main conversion page**. Después de intentar el pago, ve **Payment success** o **Payment failed** — solo se muestra una por intento. **Main conversion page** Una presentación de ventas a página completa. La IA genera el texto y las imágenes de cada sección: | Sección | Qué genera la IA | |---|---| | Titular | Titular en negrita orientado a beneficios | | Subtitular | Propuesta de valor de apoyo | | Insignia de oferta | Insignia de urgencia (sin precios inventados — usa lenguaje promocional vago) | | Botón CTA | Texto orientado a la acción, 2–5 palabras | | Beneficios | 3–6 tarjetas de beneficios con emoji y texto | | Características | 3–8 descripciones de características con título y subtítulo | | Planes | Título de selección de plan y texto del temporizador de oferta | | Prueba social | Texto de prueba comunitaria y 3–5 reseñas de usuarios realistas | | Preguntas frecuentes | 3–6 preguntas y respuestas habituales | | Garantía | Texto de garantía de devolución de dinero o satisfacción | **Payment success** Un mensaje de celebración con los próximos pasos y una imagen generada por IA. **Payment failed** Un mensaje amigable que invita al usuario a intentarlo de nuevo. El estado del checkout se conserva. ## Cómo funciona la personalización \{#how-personalization-works\} Cada correo contiene una URL de pago única con el `customer_user_id` y la dirección de email del destinatario incorporados como parámetros: ``` https://your-funnel.com/?cid={{customer_user_id}}&email={{email}} ``` Adapty genera estas URLs automáticamente al enviar cada correo; no se requiere ninguna configuración en el constructor de paywalls web. Cuando el usuario hace clic, el constructor lee los parámetros para identificarlo. Una vez completada la compra, Adapty asocia el ingreso al correo concreto que generó la conversión. Estos datos aparecen en [Analíticas de campaña](mail-analytics). ## Solución de problemas \{#troubleshooting\} | Problema | Solución | |---|---| | El enlace de pago no se abre | Verifica que el paywall esté publicado en el web paywall builder | | El usuario no está identificado en el pago | Confirma que `Adapty.identify()` se llamó con el ID de usuario correcto antes de enviar el correo | | La compra no se atribuye al correo | Comprueba que el parámetro `cid` esté presente en la URL de pago — contacta con soporte si faltan parámetros | --- # File: mail-email-campaigns --- --- title: "Campañas de email en Adapty Mail" description: "Diseña secuencias de emails, elige el tono adecuado y dirige tus mensajes a los usuarios correctos." --- Una campaña en Adapty Mail es una secuencia completa de emails — textos, diseño, imágenes principales y tiempos de espera — generada para tu app en un solo paso. Una campaña por sí sola no envía mensajes: se guarda como **borrador** y comienza a enviarse solo cuando la vinculas a un [flow](mail-create-flow), que la asocia a un disparador y una audiencia. Usa las guías a continuación para crear campañas, elegir el tono adecuado y dirigirte a los usuarios correctos. <CustomDocCardList ids={['mail-create-campaign', 'mail-suppression']} /> --- # File: mail-create-campaign --- --- title: "Crear una campaña en Adapty Mail" description: "Genera una secuencia de correos completa a partir de los metadatos de tu app en el store y refínala antes de adjuntarla a un flujo." --- Adapty Mail genera una secuencia de correos completa — textos, diseño, imágenes de cabecera, asuntos y tiempos de espera — a partir de los metadatos de tu app en el store. Sin redacción ni diseño por tu parte. La campaña se guarda como borrador; solo empieza a enviarse cuando la vinculas a un [flujo](mail-create-flow). ## Antes de empezar \{#before-you-start\} - **Paywall web guardado**: cada campaña debe estar vinculada a un paywall web. El backend rechaza las campañas que no tienen uno. Consulta [Crear un paywall web](mail-get-started#4-create-a-web-paywall) si aún no tienes uno. - **URL de App Store o Google Play en Settings**: la IA lee los metadatos de la app (nombre, categoría, descripción, capturas de pantalla) desde esta URL para personalizar la secuencia. Añádela en **Settings → App metadata** si aún no está configurada. ## 1. Genera la secuencia \{#1-generate-the-sequence\} 1. En Adapty Mail, ve a **Campaigns** y haz clic en **Create**. 2. Establece el nombre de la campaña. 3. En el desplegable **Web paywall**, selecciona el web paywall al que quieres que enlacen los correos. 4. Haz clic en **Generate emails**. 5. Rellena el diálogo de generación: - **Tone**: Elige de la lista. Las opciones se generan específicamente para la categoría de tu app — una app diferente ve opciones distintas. Tu elección define las líneas de asunto, los titulares, el cuerpo del texto y los CTAs en todos los correos; no afecta al diseño ni a las imágenes destacadas. - **Language**: Elige el idioma del correo. - **Custom prompt** (opcional): Instrucciones en texto libre de hasta 2.000 caracteres. Úsalo para mencionar una promo, una ocasión, matices de audiencia, puntos que deben incluirse o indicaciones de tono adicionales que los preajustes no pueden capturar. - **Number of emails**: Por defecto, la IA elige la cantidad según las mejores prácticas y el contexto de tu app. Para definirlo tú mismo, haz clic en **Set number manually** y selecciona un valor (**1–15**, por defecto **4**). Una vez que haces clic en **Generate**, el tono queda fijado para esa campaña. Para probar otro tono, crea una nueva campaña: cada generación puede producir una combinación diferente de opciones. 6. Haz clic en **Generate**. La generación suele tardar unos minutos. El backend tiene un tiempo de espera de 5 minutos si no puede completarla: vuelve a intentarlo si ocurre. ## 2. Revisión y ajustes \{#review-and-refine\} Tras la generación, la secuencia completa se muestra en una vista previa. Para cada correo puedes ver: - **Variantes de asunto**: Tres opciones de asunto por correo. Adapty Mail las prueba en el envío y sigue enviando la que mejor funciona — consulta [Pruebas A/B](mail-ab-testing). - **Titular, cuerpo del mensaje y CTA**: El bloque de contenido principal. - **Imagen de cabecera**: Una imagen generada acorde al contenido del correo y a tu marca. - **Diseño y retraso**: Cómo está organizado el correo y cuánto tiempo después del anterior se envía. El encabezado de la vista previa tiene un **selector de tema** (Auto, Claro, Oscuro): botones de icono en la esquina superior derecha del panel de vista previa. Controla únicamente cómo se renderiza la vista previa; el contenido generado es idéntico en todos los modos. Úsalo para comprobar el aspecto de cada correo en cada esquema de color sin necesidad de regenerar. Puedes: - **Regenerar emails individuales**: la IA reescribe el texto y genera una nueva imagen principal para un único email. La posición, el timing y el sistema de diseño global (colores, tipografía, modo oscuro) se mantienen igual — solo cambia el email en cuestión. - **Editar el HTML directamente**: se abre un editor de código HTML para tener control total sobre cualquier detalle que la IA no haya resuelto correctamente. :::note Los emails se adaptan automáticamente. Los diseños multicolumna se colapsan en una sola columna en pantallas de menos de 620 px, y cada diseño se prueba en Gmail (web y móvil), Apple Mail (macOS e iOS), Outlook de escritorio, Yahoo Mail y Samsung Mail, tanto en modo claro como oscuro. ::: ## 3. Guardar como borrador \{#save-as-a-draft\} Haz clic en **Create** para guardar la campaña como borrador. Aún no se envía ningún correo; el editor de campaña no tiene una acción separada de "publicar" ni de "lanzar". El estado de una campaña refleja si está vinculada a un flujo activo: - **draft**: No está vinculada a ningún flujo. - **live**: Está vinculada a un flujo y actualmente enrutando usuarios. - **inactive**: Estuvo vinculada, pero la prueba A/B del flujo ha finalizado. - **archived**: Eliminada del dashboard. :::important Un borrador de campaña nunca se envía por sí solo. Para empezar a entregar correos, necesitas: - Adjunta la campaña directamente a un [flujo](mail-create-flow), o - Inclúyela en una [prueba A/B](mail-ab-testing) y adjunta la prueba A/B a un flujo. Hasta que ocurra alguna de estas acciones, la campaña permanece en `draft` y no llega a ningún destinatario. ::: --- # File: mail-suppression --- --- title: "Cancelación de suscripción y supresión en Adapty Mail" description: "Cómo Adapty Mail deja de enviar correos a los usuarios — mediante cancelación de suscripción, rebotes de SES, quejas y el mecanismo de condición de parada." --- Adapty Mail deja de enviar correos a un usuario en dos situaciones distintas: - **Supresión**: El usuario queda excluido de todos los envíos futuros en este proyecto (canceló la suscripción, rebotó, se quejó, fue rechazado o limitado). - **Condición de parada**: La secuencia actual del usuario se cancela porque convirtió. No está suprimido y sigue siendo elegible para otras campañas. Ambos mecanismos son por proyecto. La supresión en un proyecto de Adapty no afecta a otro. ## Cancelación de suscripción \{#unsubscribe\} Todos los correos que envía Adapty Mail incluyen un enlace para cancelar la suscripción en el pie de página. 1. El usuario hace clic en el enlace. Adapty Mail abre una página de confirmación. 2. El usuario confirma. El backend marca el perfil con `suppression_reason = 'unsubscribe'`, cancela la secuencia restante y excluye el perfil de los envíos futuros en el proyecto. El token de la URL de cancelación codifica el `profile_id` y el `scheduled_email_id`, por lo que no se requiere inicio de sesión. :::note Adapty Mail también envía la cabecera `List-Unsubscribe: <URL>, <mailto:>` junto con `List-Unsubscribe-Post: List-Unsubscribe=One-Click`. Gmail y Yahoo lo exigen a los remitentes masivos (RFC 8058). Los clientes que admiten esta cabecera ofrecen un botón de cancelación con un solo clic directamente en la bandeja de entrada, sin necesidad de página de confirmación. ::: ## Supresión automática \{#automatic-suppression\} Adapty Mail escucha los eventos de entrega de AWS SES a través de SNS y suprime al usuario de inmediato en cualquiera de los siguientes casos: | Evento | Código de motivo | Qué significa | | --------- | ---------------- | ---------------------------------------------------------------------------------------- | | Bounce | `bounce` | La dirección de correo no es válida, el buzón está lleno o el dominio no existe. | | Complaint | `complaint` | El usuario marcó el correo como spam. | | Reject | `reject` | SES rechazó el mensaje antes de enviarlo. | | Throttle | `throttle` | La tasa de envío superó los límites de seguridad del dominio. | En todos los casos el resultado es el mismo: el usuario se añade a la lista de supresión, se cancela la secuencia restante y queda excluido de los envíos futuros en el proyecto. :::important Adapty Mail **no** distingue entre rebotes permanentes y temporales. Cualquier rebote — incluidas condiciones temporales como un buzón lleno — suprime al usuario de inmediato. No hay ventana de reintento. ::: ## Condición de parada \{#stop-condition\} Cuando un usuario convierte en mitad de una secuencia, Adapty Mail cancela sus correos pendientes con el motivo `stop_condition`. La conversión ocurre cuando el estado de su suscripción llega a **Subscribed**, o el estado de su compra única llega a **Purchased**. La condición de parada es diferente a la supresión: - **Supresión**: Excluye al usuario de todos los envíos futuros en el proyecto. - **Condición de parada**: Cancela únicamente la secuencia actual. El usuario sigue siendo elegible para otras campañas — por ejemplo, un flujo de renovación o de recuperación dirigido a suscriptores activos. Las cancelaciones por condición de parada aparecen junto a las supresiones en las analíticas de campaña. ## Gestión de la supresión \{#managing-suppression\} Adapty Mail no tiene interfaz en el dashboard para ver o eliminar usuarios suprimidos. Para quitar la supresión de un perfil — por ejemplo, alguien que marcó accidentalmente un correo de prueba como spam — contacta con el soporte de Adapty. ## Qué gestiona Adapty Mail para el cumplimiento normativo \{#what-adapty-mail-handles-for-compliance\} Adapty Mail incluye: - **Enlace de cancelación de suscripción**: Incluido en el pie de página de todos los correos, se procesa de inmediato al confirmarlo el usuario. - **Cabeceras List-Unsubscribe**: Enviadas con cada correo para la cancelación con un solo clic desde la bandeja de entrada (RFC 8058). - **Supresión automática**: Se activa con los eventos de rebote, queja, rechazo y limitación de SES. Aspectos de los que eres responsable: - **Dirección postal física**: CAN-SPAM exige que figure en el pie de página. Adapty Mail no la inyecta automáticamente — añádela en el diseño de tu campaña. - **Consentimiento explícito de opt-in**: Recógelo antes de pasar el correo de un usuario a Adapty. Consulta [Recopilar correos de usuarios](mail-collect-emails). - **Solicitudes de eliminación del RGPD**: Adapty Mail no expone un endpoint de "eliminar mis datos". Coordínate con el soporte de Adapty si un usuario ejerce su derecho al borrado. --- # File: mail-flows --- --- title: "Flows en Adapty Mail" description: "Cómo los flows enrutan campañas a los usuarios adecuados en el momento oportuno — disparadores, segmentos y reglas de prioridad." --- <CustomDocCardList ids={['mail-create-flow']} /> Un **flow** convierte una campaña guardada en entregas programadas. Vincula un evento disparador (el estado de suscripción de un usuario) con un segmento (qué usuarios califican) y la campaña que recibirán. Adapty Mail evalúa cada flow cada vez que se dispara un evento coincidente — sin polling, sin cron, sin lanzamiento manual. ## Disparadores \{#triggers\} Adapty Mail incluye cinco disparadores fijos, cada uno con su propia vista de flow en **Flows**: - **Never purchased**: Usuarios que se registraron pero aún no han realizado una compra. Objetivo: activación y primera conversión. Los usuarios en prueba no aparecen aquí — comenzar una prueba cuenta como una suscripción activa. - **Renewal cancelled**: Usuarios que desactivaron la renovación automática pero aún tienen una suscripción activa. Incluye tanto suscriptores de pago como usuarios en prueba que cancelaron antes de convertir. La ventana más fuerte para retenerlos — todavía tienen acceso. Separa audiencias de pago y prueba mediante filtros de segmento si los mensajes deben diferir. - **Billing issue**: Pago fallido — tarjeta rechazada o vencida, o período de gracia tras un pago perdido. Objetivo: recuperación urgente y útil, no un empuje de ventas. Recupéralos rápido — ya querían pagar. - **Expired**: La suscripción ha expirado y el acceso se ha eliminado. Incluye tanto vencimientos de pago como pruebas que terminaron sin convertir. Objetivo: recuperarlos. Los filtros de segmento pueden adaptar el texto para audiencias con prueba expirada y pago expirado. - **Refunded**: Usuarios que solicitaron un reembolso después de comprar. Objetivo: entender qué salió mal y ofrecer una opción más adecuada. El tono debe mantenerse humilde y curioso, no una reventa agresiva. Los disparadores no son configurables — no puedes crear disparadores personalizados ni ampliar la lista. ## El segmento All Users \{#the-all-users-segment\} Adapty Mail incluye un segmento integrado **All Users** sin filtros — cualquier usuario del proyecto califica. Es más útil en los flows como fila comodín, llegando a quienes no coinciden con ningún segmento más específico por encima de él. All Users no se puede editar ni eliminar. Consulta [Segmentos](mail-segments) para más detalles. ## Prioridad \{#priority\} Cada vista de disparador contiene una lista de filas **segmento → campaña** (o segmento → prueba A/B), ordenadas por prioridad. Cuando un usuario activa el disparador, Adapty Mail: 1. Recorre las filas de arriba hacia abajo. 2. Envía la campaña de la primera fila cuyo segmento coincida. 3. Se detiene. Las filas siguientes no se evalúan para ese usuario. El orden importa. Un segmento amplio colocado por encima de uno más específico absorbe a todos los usuarios que de otro modo habrían coincidido con la fila más específica. Para reordenar, arrastra el control a la izquierda de cualquier fila — el backend reasigna los números de prioridad 1, 2, 3… según el orden guardado. :::important La fila **All Users**, si está presente, debe ser la última (menor prioridad). El backend rechaza los guardados donde All Users no esté en la última posición — de lo contrario absorbería a todos los usuarios antes de que los segmentos más específicos tengan oportunidad de coincidir. ::: ## Tipos de contenido \{#content-types\} Una fila puede entregar una sola campaña o una prueba A/B: - **Campaign**: Envía una campaña a todos los que coincidan con el segmento. - **A/B Test**: Agrupa dos o más campañas con pesos configurables, distribuye los usuarios entrantes entre ellas de forma aleatoria y realiza un seguimiento de las métricas por variante. Consulta [Pruebas A/B](mail-ab-testing). ## Ciclo de vida \{#lifecycle\} Las filas de flow no tienen estado borrador. Una fila está activa en el momento en que la guardas — a partir de entonces, los usuarios que activen el disparador y coincidan con el segmento son enrutados a su campaña. - **Crear una fila**: Comienza a entregar inmediatamente al guardar. - **Editar una fila**: El cambio se aplica a los usuarios que activen el disparador a partir de ese momento. Los usuarios ya en mitad de una secuencia continúan con la configuración anterior. - **Eliminar una fila**: Los nuevos usuarios dejan de entrar en la secuencia. Los usuarios ya en mitad de una secuencia pueden seguir recibiendo sus correos programados — no hay cancelación automática. Las filas de prueba A/B siguen su propio ciclo de vida (**borrador → activa → finalizada**), controlado de forma independiente a la propia fila. Consulta [Pruebas A/B](mail-ab-testing). --- # File: mail-create-flow --- --- title: "Crear y gestionar filas de flujo en Adapty Mail" description: "Añade, reordena, edita y elimina filas en un flujo para enrutar campañas a tus usuarios." --- Cada [flujo](mail-flows) es una lista priorizada de filas **segmento → campaña** dentro de una vista de disparo fija. Esta guía explica cómo añadir, reordenar, editar y eliminar esas filas. Para entender los conceptos de disparadores, prioridades y tipos de contenido, consulta [Flujos](mail-flows). ## Añadir una fila \{#add-a-row\} 1. En Adapty Mail, ve a **Flows** y abre el disparador que quieras configurar. 2. Haz clic en **Create** para abrir el diálogo. 3. En el diálogo: - **Segment**: elige un segmento o **All Users** como opción general. - **Content type**: **Campaign** para una campaña individual, o **A/B Test** para comparar varias — consulta [Pruebas A/B](mail-ab-testing). - **Campaign**: selecciona la campaña a enviar. 4. Haz clic en **Save**. La fila queda activa de inmediato. Los usuarios que activen el disparador y coincidan con el segmento empezarán a recibir la campaña desde ese momento. ## Reordenar filas \{#reorder-rows\} Arrastra el controlador a la izquierda de una fila para cambiar su prioridad. Adapty Mail asigna automáticamente `priority: 1, 2, 3…` según el orden guardado. Una fila de **All Users** debe permanecer en la última posición — arrastrarla por encima de otra fila queda bloqueado al guardar. ## Editar una fila \{#edit-a-row\} Haz clic en **Change content** en una fila para volver a abrir el diálogo con los valores actuales ya rellenos. Puedes cambiar el segmento, el tipo de contenido y la campaña, y luego hacer clic en **Save** para aplicar los cambios. Una fila que use una prueba A/B solo puede editarse mientras la prueba esté en estado **draft**. Una vez lanzada la prueba, su contenido queda bloqueado hasta que la finalices. ## Eliminar una fila \{#delete-a-row\} Abre el menú de acciones de la fila y haz clic en **Delete**. No hay diálogo de confirmación — la fila se elimina de inmediato. - **Filas de campaña**: pueden eliminarse en cualquier momento. - **Filas con una prueba A/B activa**: no pueden eliminarse. Finaliza primero la prueba con **Finish A/B test** y luego elimina la fila. :::note Eliminar una fila impide que nuevos usuarios entren en la secuencia. Los usuarios que ya estén en medio de la secuencia pueden seguir recibiendo sus correos programados — no hay cancelación automática. ::: --- # File: mail-segments --- --- title: "Segmentos en Adapty Mail" description: "Crea audiencias reutilizables basadas en datos de perfil y compras para segmentar flows y pruebas A/B." --- Un **segmento** es una audiencia reutilizable. Lo defines una vez —en **Segments**— y lo referencias desde flows y pruebas A/B. Los segmentos son definiciones de filtros, no instantáneas: se evalúan en el momento en que se dispara un trigger de flow, por lo que la pertenencia siempre refleja los datos más recientes del perfil. ## Crear un segmento \{#create-a-segment\} 1. En Adapty Mail, ve a **Segments** y haz clic en **+ Create**. Se abre la página de creación con el título **New Segment**. 2. Dale un **Name** al segmento (obligatorio) y una **Description** opcional. 3. En **Filters**, haz clic en **Add filter** para cada [regla](#available-filter-fields) que quieras añadir. Cada filtro se convierte en una tarjeta contraíble con el nombre **Filter 1**, **Filter 2**, etc. 4. Para cada filtro, elige un campo, un operador y el valor de comparación. 5. Guarda el segmento. :::important Los filtros se combinan con **AND**: el usuario debe cumplir todos los filtros para pertenecer al segmento. La lógica OR y los grupos anidados no están disponibles. Cada campo puede aparecer solo una vez por segmento; si necesitas comparar el mismo campo con varios valores, divide la lógica en segmentos separados. ::: ## Importar un segmento desde Adapty \{#import-a-segment-from-adapty\} En lugar de crear un segmento desde filtros, puedes importar un segmento de audiencia existente desde el Adapty Dashboard principal. 1. En la página **Segments**, haz clic en **Import**. 2. Revisa la lista. Los **Importable segments** muestran una casilla de verificación y sus condiciones de filtro. Los segmentos que **can't be imported** aparecen en gris con el motivo concreto, como un campo no compatible (por ejemplo, datos de atribución de Apple Ads), un operador no compatible o un estado de suscripción sin equivalente en Adapty Mail. 3. Marca los segmentos que desees y haz clic en **Import**. :::important La importación crea una copia independiente de los filtros del segmento en el momento de la importación — no se sincroniza con el segmento original de Adapty, y volver a importar el mismo segmento crea una copia separada cada vez, ya que Adapty Mail no detecta duplicados. ::: Los segmentos importados comienzan en estado **Draft**, igual que los segmentos que creas manualmente, por lo que puedes editar sus filtros de inmediato. ## Campos de filtro disponibles \{#available-filter-fields\} | Grupo | Campo | Tipo | | -------------- | --------------------------------- | ------- | | Perfil | Email | String | | Perfil | Age | Integer | | Perfil | Country | String | | Perfil | External profile ID | String | | Perfil | Created at | Date | | Estado de compra | Total revenue (USD) | Decimal | | Estado de compra | Subscription state | Enum | | Estado de compra | Subscription purchased at | Date | | Estado de compra | Subscription expires at | Date | | Estado de compra | One-time purchase state | Enum | | Estado de compra | One-time purchased at | Date | **Valores de estado de suscripción**: Never purchased, Subscribed, Auto-renew off, Billing issue, Grace period, Expired, Refunded. **Valores de estado de compra única**: Never purchased, Purchased, Refunded. Operadores disponibles según el tipo de campo: - **String**: equals, not equals, is set, is not set. - **Number**: equals, not equals, less than, greater than, less than or equal, greater than or equal, between, is set, is not set. - **Date**: equals, not equals, before, after, on or before, on or after, between, is set, is not set. ## El segmento de sistema Todos los usuarios \{#the-all-users-system-segment\} Adapty Mail incluye un segmento integrado llamado **All Users** que no tiene filtros: todos los usuarios del proyecto cumplen los requisitos. No se puede editar ni eliminar. Cuando se usa en un flow, actúa como la fila de captura al final (consulta [Flows](mail-flows) para conocer la regla de prioridad). ## Ciclo de vida \{#lifecycle\} El estado de un segmento se calcula en función de cómo se está usando: - **Draft**: Creado, no está asociado a ningún flow ni prueba A/B. - **Live**: Asociado a un flow activo o a una prueba A/B. - **Inactive**: Estaba asociado, pero la prueba A/B ha finalizado o la fila del flow fue eliminada. - **Archived**: Eliminado de forma lógica y oculto de la lista principal. La página Segments tiene un filtro de estado en la barra de herramientas para que puedas filtrar la lista por cualquiera de estos estados. ## Editar y eliminar un segmento \{#edit-and-delete-a-segment\} - **Nombre y descripción**: Siempre editables. - **Filtros en un segmento en borrador**: Completamente editables. - **Filtros en un segmento activo**: Bloqueados. Una vez que un segmento es referenciado por una fila de flow activa o una prueba A/B, los filtros pasan a ser de solo lectura. Solo puedes cambiarle el nombre o actualizar la descripción. Para modificar el targeting, crea un nuevo segmento y reemplaza la fila del flow. - **Eliminar**: Elimina el segmento de forma lógica. Los segmentos activos no se pueden eliminar — primero quítalos del flow (o finaliza la prueba A/B). ## Limitaciones \{#limitations\} - **Sin lógica OR ni anidamiento**: Los filtros se combinan únicamente con AND. - **Un campo por segmento**: Un segmento no puede tener dos filtros sobre el mismo campo (por ejemplo, dos comprobaciones de país). - **Sin vista previa del tamaño**: El editor no muestra cuántos usuarios coinciden actualmente con los filtros. - **Filtros bloqueados una vez activos**: Los segmentos activos son de solo lectura, salvo el nombre y la descripción. --- # File: mail-profiles --- --- title: "Perfiles en Adapty Mail" description: "Consulta todos los clientes de tu proyecto: sus atributos, estado de compra, interacción con emails y el historial completo de actividad." --- Un **perfil** es un cliente de tu proyecto. La página **Profiles** muestra a todas las personas que Adapty Mail conoce, junto con el resultado de sus compras, su interacción con los emails y el historial completo de actividad. Los perfiles se crean automáticamente: a partir de los emails que recoge el SDK de Adapty, o de los datos que envías a través de la API de Adapty Mail. :::tip Para agrupar perfiles en audiencias reutilizables para flows y pruebas A/B, consulta [Segmentos](mail-segments). ::: ## Cómo llegan los perfiles a Adapty Mail \{#how-profiles-get-into-adapty-mail\} Adapty Mail crea perfiles automáticamente desde dos fuentes: - **SDK de Adapty**: El SDK recopila correos electrónicos y compras desde tu app. Consulta [Recopilar correos electrónicos de usuarios](mail-collect-emails). - **API de Adapty Mail**: Tu backend envía perfiles y transacciones de servidor a servidor. Consulta [Enviar datos a través de la API](mail-send-data-via-api). Adapty Mail vincula cada correo, clic y compra a un perfil mediante su `external_profile_id` estable. La página de perfiles es de solo lectura. Puedes ver los perfiles y darlos de baja, pero no puedes crearlos, editarlos ni eliminarlos. Esos datos son propiedad de la aplicación de origen o de la API. ## La lista de perfiles \{#the-profiles-list\} La lista muestra una fila por perfil, de más reciente a más antiguo. Usa el cuadro de búsqueda para encontrar un perfil por correo electrónico, ID de perfil o ID de perfil externo. | Columna | Muestra | | --- | --- | | Profile | El correo del cliente y la campaña que le ha enviado más correos. | | Status | El estado de compra del perfil. Consulta [Estado del perfil](#profile-status). | | Country | El país del cliente. | | Open rate | Aperturas divididas entre enviados en todos los correos. Un guion significa que aún no se han enviado correos. | | LTV | Valor de vida del cliente — ingresos totales de este perfil en todas las fuentes. | | Joined | Cuándo el perfil entró por primera vez en Adapty Mail. | | Last activity | La última vez que el perfil interactuó con un correo, como un envío, apertura o clic. | :::important **Joined** es la fecha en que el perfil entró por primera vez en Adapty Mail, utilizada como fecha de "cliente desde". Es la fecha de ingesta, no la fecha de registro original de tu app. ::: ### Estado del perfil \{#profile-status\} La columna **Status** muestra el estado de compra del perfil: en qué punto se encuentra el cliente respecto a sus suscripciones y compras únicas. | Estado | Significado | | --- | --- | | Never purchased | El perfil no ha comprado nada. | | Purchased | El perfil realizó una compra única. | | Active subscriber | El perfil tiene una suscripción activa. | | Cancelling | La renovación automática está desactivada; el acceso dura hasta que finalice el período actual. | | Billing issue | Un pago de renovación falló. | | Grace period | El pago falló, pero el acceso continúa durante el período de gracia del store. | | Churned | La suscripción expiró y el acceso finalizó. | | Refunded | Se realizó un reembolso de una compra. | :::note El estado de la compra es independiente del estado de suscripción al correo. Un perfil puede ser un **suscriptor activo** y a la vez estar **dado de baja** de tus correos, o estar **cancelado** y seguir **suscrito**. El estado del correo aparece en la página del perfil y controla si Adapty Mail puede enviarle mensajes. ::: ## Detalles del perfil \{#profile-details\} Haz clic en un perfil para abrir su página de detalles. El encabezado muestra el correo electrónico, el país, la plataforma y la fecha de alta como cliente. También muestra tres indicadores de estado: el estado de compra, la tasa de apertura y si el perfil está **Subscribed** o **Unsubscribed**. En la parte superior aparecen cinco métricas de interacción: - **Sent**: Correos electrónicos enviados al perfil. - **Delivered**: Correos electrónicos aceptados por el proveedor de buzón. - **Opened**: Correos electrónicos que el perfil ha abierto. - **Clicked**: Correos electrónicos en los que el perfil hizo clic en un enlace. - **Revenue**: Dos cifras: ingresos atribuidos y valor de ciclo de vida. :::note **El ingreso atribuido** es el ingreso generado por tus emails: compras que el perfil realizó tras interactuar con una campaña. **El valor de vida (LTV)** es el ingreso total del perfil en todas las fuentes, independientemente de si el email intervino o no. El encabezado muestra primero el ingreso atribuido y luego el LTV. ::: La tarjeta **Profile** muestra los atributos del cliente: - **Platform**: La plataforma del dispositivo del cliente, como iOS o Android. - **Country**: El país del cliente. - **Store country**: El país de la cuenta de App Store o Google Play del cliente. - **Gender**: El género del cliente, si se conoce. - **Age**: La edad del cliente, si se proporcionó una fecha de nacimiento. - **Profile ID**: El identificador interno de Adapty Mail para el perfil. - **External ID**: El `external_profile_id` de tu app o backend. - **Custom attributes**: Cualquier par clave-valor que hayas enviado con el perfil. ### Estado de compra \{#purchase-state\} La tarjeta **Purchase state** muestra los ingresos e historial de compras del perfil. El valor de por vida aparece en la parte superior, seguido de hasta dos secciones: - **Subscription**: Precio, store, fecha de inicio, fecha de renovación o vencimiento e ID de producto para la suscripción del perfil. - **One-time purchase**: Precio, store, fecha de compra e ID de producto para la compra única más reciente. Si el perfil no ha comprado nada, la tarjeta muestra **No purchase yet**. La tarjeta **Segments** muestra todos los segmentos a los que pertenece el perfil en ese momento, o **Not in any segment** si no aplica ninguno. La pertenencia se evalúa en tiempo real, por lo que siempre refleja los datos más recientes del perfil. Consulta [Segmentos](mail-segments) para saber cómo crearlos. ## El historial de actividad \{#the-activity-journey\} La sección **Journey** es una línea de tiempo con todo lo que ha ocurrido en el perfil. Comienza con **Profile created** y luego combina dos tipos de eventos: - **Eventos de email**: Cada email enviado al perfil, con su actividad de entrega, apertura y clics. Expande un email para ver los enlaces en los que hizo clic el perfil y cualquier compra que haya generado ese email. - **Eventos de transacción**: Hitos de suscripción y compra única, como inicios, renovaciones, cancelaciones, problemas de facturación, expiraciones y reembolsos. Los eventos de transacción se corresponden con estas etiquetas en el historial: | `event_type` | Etiqueta de Journey | | --- | --- | | `subscription_started` | Subscription started | | `subscription_renewed` | Subscription renewed | | `subscription_renewal_cancelled` | Renewal cancelled | | `subscription_renewal_reactivated` | Renewal resumed | | `billing_issue_detected` | Billing issue | | `entered_grace_period` | Entered grace period | | `subscription_expired` | Subscription expired | | `subscription_refunded` | Subscription refunded | | `non_subscription_purchase` | One-time purchase | | `non_subscription_purchase_refunded` | Purchase refunded | Estos eventos llegan a Adapty Mail automáticamente a través del SDK, o puedes enviarlos tú mismo mediante la API. Consulta [Enviar eventos de transacción](mail-send-data-via-api#send-transaction-events) para ver la referencia de eventos. ## Dar de baja a un perfil \{#unsubscribe-a-profile\} Para dejar de enviar correos a un perfil, abre su página, haz clic en **...** y selecciona **Unsubscribe**. Adapty Mail marca el perfil como dado de baja y lo añade a tu lista de supresión, de modo que las campañas y los flows lo omiten. La acción es idempotente: un perfil que ya está dado de baja permanece así. Para una visión completa de la supresión y cómo los perfiles se dan de baja por sí mismos, consulta [Cancelación de suscripción y supresión](mail-suppression). --- # File: mail-ab-testing --- --- title: "Pruebas A/B en Adapty Mail" description: "Compara campañas de email completas entre sí adjuntando una prueba A/B a un flujo." --- Una prueba A/B en Adapty Mail compara dos o más campañas de email completas entre sí. Cada variante es una campaña independiente y completa. Cuando un usuario coincide con el segmento de la prueba en un [flujo](mail-flows), Adapty Mail lo dirige a una de las variantes según los pesos configurados y registra la entrega, el engagement y los ingresos por variante. ## Qué es una variante \{#what-a-variation-is\} Cada variante es una campaña completa. Las variantes pueden diferir en cualquier aspecto que pueda diferir una campaña: el copy, las imágenes principales, el tono, la longitud de la secuencia o los tiempos de espera entre emails. La propia prueba A/B no expone esos elementos como controles; tú creas las campañas por separado y las añades como variantes. ## Crear una prueba A/B \{#create-an-ab-test\} 1. Crea primero las campañas en **Campaigns**. Cada variante necesita su propia campaña. 2. En Adapty Mail, ve a **A/B Tests** y haz clic en **Create**. 3. Añade cada campaña como variante y establece su peso. Los pesos deben sumar **100%**. 4. Asigna un segmento para controlar a qué usuarios se aplica la prueba. 5. Guarda. La prueba se guarda como **borrador** y aún no envía nada. Para activarla, debe adjuntarse a un flujo. ## Lanzar desde un flujo \{#launch-from-a-flow\} Las pruebas A/B no se pueden lanzar desde la página A/B Tests — tanto el lanzamiento como la finalización se realizan dentro de una fila de flujo. 1. En Adapty Mail, ve a **Flows** y abre el trigger donde quieres ejecutar la prueba. 2. Haz clic en **Create** en una nueva fila. En el diálogo, establece **Content type** como **A/B Test**, selecciona la prueba que guardaste y haz clic en **Save**. 3. En la fila, haz clic en **Launch A/B test**. El estado de la prueba pasa de **borrador** a **activo** y los usuarios entrantes que coincidan con el segmento empezarán a ser dirigidos a las variantes. Consulta [Crear un flujo](mail-create-flow) para más información sobre las filas de flujo. ## Cómo funciona el enrutamiento \{#how-routing-works\} Cuando un usuario activa el trigger del flujo y coincide con el segmento de la prueba A/B, Adapty Mail elige una variante mediante selección **aleatoria ponderada**: el peso de cada variante determina su proporción del sorteo. El enrutamiento no es determinista por usuario. ## Ver los resultados \{#read-results\} En la página A/B Tests, cada variante muestra sus contadores brutos y las tasas derivadas: - **Delivery**: Envíos, Entregas, Rebotes. - **Engagement**: Aperturas, Clics, Bajas. - **Revenue**: Compras, Ingresos. Consulta [Analíticas de campañas](mail-analytics) para saber qué cuenta cada métrica y cómo se atribuyen los ingresos. ## Finalizar la prueba \{#finish-the-test\} Al igual que el lanzamiento, la finalización se realiza desde la fila del flujo, no desde la página A/B Tests. 1. Abre la fila del flujo donde se está ejecutando la prueba. 2. Haz clic en **Finish A/B test**. 3. En el diálogo **Finish A/B test**, selecciona la campaña ganadora en el desplegable **Replace with campaign** — o déjalo vacío para eliminar el segmento del flujo por completo. 4. Confirma. :::note Los usuarios que ya están a mitad de secuencia en cualquier variante — ganadora o perdedora — seguirán recibiendo sus emails programados. No se les cambia a la ganadora. ::: ## Ciclo de vida \{#lifecycle\} Una prueba A/B pasa por cuatro estados: - **Draft**: Creada, aún no adjuntada a una fila de flujo activa. - **Live**: Adjuntada y lanzada; enrutando usuarios entrantes. - **Finished**: Detenida mediante **Finish A/B test**. - **Archived**: Eliminada de forma temporal de la lista. --- # File: mail-analytics --- --- title: "Análisis en Adapty Mail" description: "Analiza el rendimiento de tus campañas por campaña, segmento, variante A/B, mensaje o trigger — y consulta la entrega, el engagement y los ingresos en un mismo lugar." --- La página **Analytics** muestra el rendimiento de tus campañas en cinco dimensiones: campaña, segmento, variante A/B, mensaje y disparador. Combina métricas de entrega con los ingresos atribuidos a cada correo, para que puedas comparar variantes, identificar los segmentos con mejor rendimiento y detectar dónde se concentran los ingresos. La página incluye un gráfico en la parte superior y una tabla de desglose debajo. Haz clic en cualquier fila para profundizar en una entidad concreta. ## Selecciona un rango de tiempo \{#pick-a-time-range\} La barra de herramientas en la parte superior de la página controla la ventana de tiempo y cómo se agrupa: - **Rango de fechas**: Presets (Últimos 7 / 14 / 30 / 90 días, Este mes, Mes pasado, Últimos 12 meses, Año hasta la fecha) o un selector de **Rango personalizado**. El valor predeterminado son los últimos 30 días. - **Granularidad**: Agrupación **Diaria**, **Semanal** o **Mensual**. La granularidad se reduce automáticamente cuando el rango crece: **Diaria** cambia a **Semanal** a partir de los 92 días, y ambas cambian a **Mensual** a partir de los 366 días. - **Estilo del gráfico**: **Línea**, **Área** o **Barras**. Si la página muestra un aviso de "rango demasiado amplio", reduce el intervalo de fechas, aumenta la granularidad o aplica filtros. ## Agrupar, desglosar, filtrar \{#group-break-down-filter\} Hay tres controles debajo de la barra de herramientas que determinan lo que muestra el gráfico y la tabla: - **Group by**: La dimensión que divide el conjunto de datos en filas. Las opciones son **Campaigns**, **Segments**, **A/B variants** y **Triggers**. Con **No grouping**, la página agrupa todo en una única fila agregada **All**. - **Breakdown**: Una segunda dimensión que divide cada fila en subfilas. Si tanto **Group by** como **Breakdown** están configurados, cada fila de la tabla se puede expandir para ver sus subgrupos. Breakdown puede ser cualquier dimensión —incluido **Messages**— excepto la que ya se usa como **Group by**. - **Add filter**: Restringe el conjunto de datos a campañas, segmentos, variantes A/B o triggers específicos. Los filtros se aplican tanto al gráfico como a la tabla. :::note **Messages** está disponible como desglose, pero no como **Agrupar por** o filtro de nivel superior. Para analizar mensajes individuales, agrupa por **Campaigns** con un desglose de **Messages**, luego expande la fila de la campaña o abre un mensaje desde el drilldown. ::: ## Leer el gráfico \{#read-the-chart\} El gráfico muestra las métricas que selecciones durante el período de tiempo elegido. - **Categoría de métrica**: Alterna entre **Email actions** (Sent, Delivered, Opened, Clicked, Bounced, Unsubscribed, Converted) y **Revenue**. - **Pastillas de métrica**: Elige qué métricas representar. En modo agregado (sin agrupación), puedes mostrar varias métricas en el mismo gráfico. Con agrupación activa, el gráfico muestra una sola métrica — una línea por grupo — para que los grupos sean visualmente distinguibles. - **Leyenda**: Cuando la agrupación está activa, la leyenda de la derecha lista todos los grupos y te permite activar o desactivar cada serie. Las casillas de visibilidad de la tabla de métricas también controlan qué filas aparecen en el gráfico. ## Lee la tabla de métricas \{#read-the-metrics-table\} Debajo del gráfico, la tabla de métricas muestra una fila por grupo. La fila de resumen en la parte superior agrega todas las demás filas de la tabla. - **Columnas ordenables**: haz clic en cualquier encabezado de columna para ordenar por Name, Sent, Delivered, Delivery rate, Opened, Open rate, Clicked, Click rate, Converted, Revenue, Bounced o Unsubscribed. - **Casilla de visibilidad**: activa o desactiva si una fila aparece en el gráfico. - **Expandir fila**: cuando se ha configurado un **Breakdown**, el chevron a la izquierda de cada fila la expande en sus subgrupos. - **Abrir el desglose detallado**: haz clic en el nombre de una fila para abrir la vista enfocada de esa entidad. El desglose muestra: - El mismo gráfico y selector de métricas que la página principal, acotado a una entidad. - Ocho tarjetas de resumen en la parte inferior: **Sent**, **Delivered** (con tasa de entrega), **Opened** (con tasa de apertura), **Clicked** (con tasa de clics), **Bounced**, **Unsubscribed**, **Converted** y **Revenue**. El rango de fechas y la granularidad se heredan de la página principal. Usa **Back** en la ruta de navegación para volver. ## Qué se registra \{#whats-tracked\} :::note Adapty convierte otras divisas a USD según el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo de cambio se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: Cada fila — en la página de Analytics, en el desglose y en las vistas en línea que se describen a continuación — expone el mismo conjunto de recuentos sin procesar: - **Sent**: Correos enviados a SES. - **Delivered**: Entregas en bandeja de entrada confirmadas por SES. - **Bounced**: Rebotes notificados por SES. No se distingue entre rebotes duros y blandos — ambos cuentan como un **Bounced**. - **Opened**: Cargas del píxel. Apple Mail Privacy Protection precarga imágenes en iOS 15+ e infla este recuento — confía en los clics y los ingresos como señales más fiables. - **Clicked**: Clics en enlaces del cuerpo del correo. - **Unsubscribed**: Cancelaciones de suscripción desde el enlace del pie de página o la cabecera `List-Unsubscribe`. - **Converted**: Perfiles únicos del grupo con una compra atribuida en el rango de tiempo. Las conversiones se agrupan por fecha de compra — un clic en marzo seguido de una compra en abril cuenta en abril. Un perfil que compra más de una vez sigue contando una sola vez. - **Revenue**: Suma de los ingresos atribuidos (USD) entre inicios de suscripción, renovaciones y compras únicas. ## Tasas derivadas \{#derived-rates\} Cada tasa se calcula a partir de los recuentos brutos anteriores: | Tasa | Fórmula | | ----------------- | -------------------------- | | Tasa de entrega | Entregados / Enviados | | Tasa de apertura | Abiertos / Entregados | | Tasa de clics | Clicados / Entregados | El desglose muestra las mismas tres tasas junto a sus tarjetas de resumen. ## Atribución de ingresos \{#revenue-attribution\} Los ingresos se atribuyen mediante **last-click** en un enlace rastreado: 1. Cuando un destinatario hace clic en cualquier enlace de un correo electrónico, Adapty Mail almacena el `scheduled_email_id` en el perfil de ese usuario en un almacén de corta duración. 2. Si después llega un evento de compra sin atribución previa, Adapty Mail asigna retroactivamente el `scheduled_email_id` almacenado a la transacción, siempre que la marca de tiempo de la compra sea posterior al clic. 3. Las compras sin un clic rastreado previo quedan sin atribuir. El parámetro rastreado es `scheduled_email_id`. La URL de pago también incluye la identidad del destinatario a través de los marcadores `{email}` y `{external_profile_id}`, de modo que el paywall web pueda personalizar el flujo — ese es un mecanismo independiente de la atribución. Consulta [Configurar el pago](mail-checkout). ## Analíticas en línea en Flows y pruebas A/B \{#inline-analytics-in-flows-and-ab-tests\} Las mismas métricas también aparecen en línea junto a las filas que se están midiendo: - **Página de Flows**: cada fila de segmento en una vista de activador muestra sus recuentos de entrega, interacción e ingresos. - **Página de A/B Tests**: las variantes se muestran en paralelo con el mismo conjunto de métricas, lo que facilita comparar variantes directamente. Usa la página de Analytics cuando compares campañas entre sí o cuando quieras profundizar en una entidad concreta, y las vistas integradas cuando ya estés trabajando en una fila de flujo específica o en una prueba A/B. Las definiciones de métricas, las tasas derivadas y las reglas de atribución descritas arriba se aplican de forma idéntica en las tres vistas. ## Limitaciones \{#limitations\} - **Sin distinción entre rebote suave y duro**: todo rebote —temporal o permanente— se agrupa en un único recuento de **Bounced**. - **Consistencia eventual, no en tiempo real**: los recuentos se agregan desde tablas de eventos. Los eventos recientes suelen aparecer en minutos, pero no hay garantía de streaming. - **El tamaño del rango tiene un límite**: rangos de fechas muy amplios combinados con una granularidad fina pueden superar el límite de celdas del gráfico. La página muestra esto como un aviso de "rango demasiado amplio": reduce el rango, aumenta la granularidad o aplica filtros. --- # File: configuration --- --- title: "Configurar integración de terceros" description: "Aprende cómo configurar los ajustes de Adapty para optimizar la gestión de suscripciones." --- Con las integraciones de Adapty, puedes transmitir eventos de suscripción y datos de compras a tu plataforma o flujo de trabajo preferido de forma sencilla. Tanto si buscas información sobre el comportamiento de los usuarios, estrategias de captación de clientes o análisis de producto avanzados para tu equipo de marketing, Adapty puede reenviar sin esfuerzo los eventos de compras in-app a la integración que elijas. Adapty registra automáticamente las compras in-app y los eventos de suscripción, como trials, conversiones, renovaciones y cancelaciones. Estos [eventos](events) se comunican de forma automática a las integraciones que hayas configurado. Esto te permite interactuar con los clientes según la fase en la que se encuentren y analizar las actividades relacionadas con los ingresos dentro de tu app. ## Ajustes de integración \{#integration-settings\} <img src="/assets/shared/img/20bf659-CleanShot_2023-08-22_at_13.26.562x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Las integraciones ofrecen las siguientes opciones de configuración que afectan a todos los eventos enviados a través de ella: | Ajuste | Descripción | |:--------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Reporting Proceeds** | Selecciona cómo se presentan los valores de ingresos: netos de las comisiones de App Store y Play Store, o brutos (antes de deducciones). Activa la casilla "Send sales as proceeds" para mostrar las ventas como ingresos netos tras descontar las comisiones de App Store / Play Store. | | **Send Trial Price** | Si está marcado, Adapty transmitirá el precio de la suscripción para el evento Trial Started. | | **Exclude Historical Events** | Elige excluir los eventos que ocurrieron antes de que el usuario instalara la app con el SDK de Adapty. Esto evita la duplicación de eventos y garantiza informes precisos. Por ejemplo, si un usuario activó una suscripción mensual el 10 de enero y actualizó la app con el SDK de Adapty el 6 de marzo, Adapty omitirá los eventos anteriores al 6 de marzo y conservará los posteriores. | | **Report User's Currency** | Elige si las ventas se reportan en la divisa del usuario o en USD. | | **Send User Attributes** | Si deseas enviar atributos específicos del usuario, como preferencias de idioma, y tu plan de OneSignal admite más de 10 etiquetas, selecciona esta opción. Al activarla, se permite incluir información adicional más allá de las 10 etiquetas predeterminadas. Ten en cuenta que superar los límites de etiquetas puede generar errores. | | **Send Attributions** | Activa esta opción para transmitir información de atribución (por ejemplo, atribución de AppsFlyer) y recibir los detalles correspondientes. | | **Send Play Store purchase token** | Activa esta opción para recibir el token de Play Store necesario para revalidar la compra si es preciso. Añadirá el parámetro `play_store_purchase_token` al evento. | | **Delay events with future datetime** | **Solo para AppsFlyer y webhooks personalizados**: Cuando está activado, los eventos de renovación y conversión de trial se envían en la fecha en que realmente ocurren. Cuando está desactivado (por defecto), estos eventos se envían en cuanto se detectan, aunque la fecha sea futura. | | **Data residency** | **Solo para Mixpanel y Amplitude**: Selecciona la residencia de datos para determinar dónde se procesan y almacenan tus eventos. | ## Configurar los eventos \{#configure-the-events\} Debajo de las credenciales encontrarás tres grupos de eventos que puedes enviar a la plataforma de integración seleccionada desde Adapty. Debes activar los que necesites. Es importante tener en cuenta que la personalización de nombres de eventos está disponible en ciertas integraciones, mientras que en otras los nombres de eventos están fijos y no se pueden modificar. Además, con determinadas integraciones como [Airbridge](airbridge#configure-events-and-tags), por ejemplo, tienes la flexibilidad de asociar varios nombres de eventos a un único evento de Adapty. Consulta la lista completa de eventos que ofrece Adapty [aquí](events). <img src="/assets/shared/img/c79f5cd-screencapture-app-adapty-io-integrations-pushwoosh-2023-08-22-13_31_07.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Aunque recomendamos utilizar los nombres de eventos predeterminados de Adapty, tienes la libertad de adaptarlos según tus necesidades específicas. --- # File: events --- --- title: "Eventos para enviar a integraciones de terceros" description: "Realiza el seguimiento de los eventos clave de suscripción con las herramientas de análisis de Adapty." --- Apple y Google envían los eventos de suscripción directamente a los servidores mediante las [Notificaciones del servidor de App Store](enable-app-store-server-notifications) y las [Notificaciones de desarrollador en tiempo real (RTDN)](enable-real-time-developer-notifications-rtdn). Como resultado, las apps móviles no pueden enviar eventos a los sistemas de análisis en tiempo real de forma fiable. Por ejemplo, si un usuario se suscribe pero nunca vuelve a abrir la app, el desarrollador no recibirá ninguna actualización del estado de la suscripción sin un servidor. Adapty cubre esta brecha recopilando datos de suscripción y convirtiéndolos en eventos legibles. Estos eventos de integración se envían en formato JSON. Aunque todos los eventos comparten la misma estructura, sus campos varían según el tipo de evento, el store y la configuración específica. Puedes consultar los campos exactos incluidos en cada evento en las páginas de integración correspondientes. Para entender cómo determinar si un evento se procesó correctamente o si algo salió mal, consulta la página de [estados de eventos](event-statuses). ## Tipos de eventos \{#event-types\} La mayoría de los eventos se crean y envían a todas las integraciones configuradas si están habilitadas. Sin embargo, el evento **Access level updated** solo se activa si la [integración de webhook](webhook) está configurada y este evento está habilitado. Este evento aparecerá en el [Event Feed](https://app.adapty.io/event-feed) y también se enviará al webhook, pero no se compartirá con otras integraciones. Si no hay ninguna integración de webhook configurada o este tipo de evento no está habilitado, el evento **Access level updated** no se creará y no aparecerá en el [Event Feed](https://app.adapty.io/event-feed). | Nombre del evento | Descripción | |:-----------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | subscription_started | Se activa cuando un usuario activa una suscripción de pago sin período de prueba, es decir, se le cobra de inmediato. | | subscription_renewed | Ocurre cuando se renueva una suscripción y se cobra al usuario. Este evento comienza a partir de la segunda facturación, tanto en suscripciones con prueba como sin ella. | | subscription_renewal_cancelled | El usuario ha desactivado la renovación automática de la suscripción. El usuario conserva el acceso a las funciones premium hasta el final del período de suscripción pagado. | | subscription_renewal_reactivated | Se activa cuando un usuario reactiva la renovación automática de la suscripción. | | subscription_expired | Se activa cuando una suscripción finaliza por completo tras ser cancelada. Por ejemplo, si un usuario cancela una suscripción el 12 de diciembre pero esta permanece activa hasta el 31 de diciembre, el evento se registra el 31 de diciembre cuando la suscripción expira. | | subscription_paused | Ocurre cuando un usuario activa la [pausa de suscripción](https://developer.android.com/google/play/billing/lifecycle/subscriptions#pause) (solo Android). | | subscription_deferred | Se activa cuando una compra de suscripción se [aplaza](https://adapty.io/glossary/subscription-purchase-deferral/), lo que permite a los usuarios retrasar el pago manteniendo el acceso a las funciones premium. Esta función está disponible a través de la Google Play Developer API y puede usarse para pruebas gratuitas o para ayudar a usuarios con dificultades económicas. | | non_subscription_purchase | Cualquier compra que no sea una suscripción, como el acceso de por vida o productos consumibles como monedas del juego. | | trial_started | Se activa cuando un usuario activa una suscripción de prueba. | | trial_converted | Ocurre cuando finaliza una prueba y se cobra al usuario (primera compra). Por ejemplo, si un usuario tiene una prueba hasta el 14 de enero pero se le cobra el 7 de enero, este evento se registra el 7 de enero. | | trial_renewal_cancelled | El usuario desactivó la renovación automática de la suscripción durante el período de prueba. El usuario conserva el acceso a las funciones premium hasta que finalice la prueba, pero no se le cobrará ni comenzará una suscripción. | | trial_renewal_reactivated | Ocurre cuando un usuario reactiva la renovación automática de la suscripción durante el período de prueba. | | trial_expired | Se activa cuando finaliza una prueba sin convertirse en suscripción. | | entered_grace_period | Ocurre cuando falla un intento de pago y el usuario entra en un período de gracia (si está habilitado). El usuario conserva el acceso premium durante este tiempo. | | billing_issue_detected | Se activa cuando ocurre un problema de facturación durante un intento de cobro (p. ej., saldo insuficiente en la tarjeta). | | subscription_refunded | Se activa cuando se reembolsa una suscripción (p. ej., por parte del soporte de Apple). | | non_subscription_purchase_refunded | Se activa cuando se reembolsa una compra que no es una suscripción. | | access_level_updated | Ocurre cuando se actualiza el nivel de acceso de un usuario. | Los eventos anteriores cubren completamente el estado de los usuarios en cuanto a compras. Veamos algunos ejemplos. ### Ejemplo 1 \{#example-1\} _El usuario activó una suscripción mensual el 1 de abril con un periodo de prueba de 7 días. El día 4, se dio de baja._ En ese caso, se enviarán los siguientes eventos: 1. `trial_started` el 1 de abril 2. `trial_renewal_cancelled` el 4 de abril 3. `trial_expired` el 7 de abril ### Ejemplo 2 \{#example-2\} _El usuario activó una suscripción mensual el 1 de abril con un periodo de prueba de 7 días. El día 10, se dio de baja._ En ese caso, se enviarán los siguientes eventos: 1. `trial_started` el 1 de abril 2. `trial_converted` el 7 de abril 3. `subscription_renewal_cancelled` el 10 de abril 4. `subscription_expired` el 1 de mayo Para un desglose detallado de qué eventos se activan en cada escenario, consulta los [flujos de eventos](event-flows). --- # File: event-flows --- --- title: "Flujos de eventos" description: "Descubre esquemas detallados de flujos de eventos de suscripción en Adapty. Aprende cómo se generan y envían los eventos de suscripción a las integraciones, lo que te ayuda a rastrear los momentos clave en el recorrido de tus clientes." --- En Adapty, recibirás distintos eventos de suscripción a lo largo del ciclo de vida de un cliente en tu app. Estos flujos de suscripción describen los escenarios más habituales para ayudarte a entender qué eventos genera Adapty cuando los usuarios se suscriben, cancelan o reactivan suscripciones. Ten en cuenta que Apple procesa los pagos de suscripción varias horas antes del inicio/renovación real. En los flujos siguientes, mostramos el inicio/renovación de la suscripción y el cargo al mismo tiempo para mantener los diagramas claros. Además, los eventos relacionados con la misma acción ocurren de forma simultánea y pueden aparecer en tu **Event Feed** en cualquier orden, que podría diferir de la secuencia mostrada en nuestros diagramas. ## Ciclo de vida de una suscripción \{#subscription-lifecycle\} ### Flujo de compra inicial \{#initial-purchase-flow\} Este flujo ocurre cuando un cliente compra una suscripción por primera vez sin período de prueba. En esta situación, se crean los siguientes eventos: - **Subscription started** - **Access level updated** para conceder acceso al usuario Cuando llega la fecha de renovación de la suscripción, esta se renueva. En ese caso, se crean los siguientes eventos: - **Subscription renewal** para iniciar un nuevo período de la suscripción - **Access level updated** para actualizar la fecha de vencimiento de la suscripción y extender el acceso por otro período Las situaciones en las que el pago no se realiza correctamente o el usuario cancela la renovación se describen en [Flujo de resultado por problema de facturación](event-flows#billing-issue-outcome-flow) y [Flujo de cancelación de suscripción](event-flows#subscription-cancellation-flow), respectivamente. <img src="/assets/shared/img_webhook_flows/Initial_Purchase_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Flujo de cancelación de suscripción \{#subscription-cancellation-flow\} Cuando un usuario cancela su suscripción, se crean los siguientes eventos: - **Subscription renewal canceled** para indicar que la suscripción sigue activa hasta el final del período actual, tras el cual el usuario perderá el acceso - El evento **Access level updated** se crea para deshabilitar la renovación automática del nivel de acceso Una vez que finaliza la suscripción, se activa el evento **Subscription expired (churned)** para marcar el fin de la suscripción. <img src="/assets/shared/img_webhook_flows/Subscription_Cancellation_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Si se aprueba un reembolso, el siguiente evento reemplaza a **Subscription expired (churned)**: - **Subscription refunded** para finalizar la suscripción y proporcionar detalles sobre el reembolso <img src="/assets/shared/img_webhook_flows/Subscription_Cancellation_Flow_with_a_Refund.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> En Stripe, una suscripción puede cancelarse de forma inmediata, saltándose el período restante. En ese caso, todos los eventos se crean simultáneamente: - **Subscription renewal cancelled** - **Subscription expired (churned)** - **Access Level updated** para revocar el acceso del usuario Si se aprueba un reembolso, también se activa el evento **Subscription refunded** cuando se aprueba. <img src="/assets/shared/img_webhook_flows/Subscription_Immediate_Cancellation_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Flujo de reactivación de suscripción \{#subscription-reactivation-flow\} Si un usuario cancela una suscripción, esta expira y luego vuelve a comprar la misma suscripción, se creará un evento **Subscription renewed**. Aunque haya un período sin acceso, Adapty lo trata como una única cadena de transacciones vinculadas por el `vendor_original_transaction_id`. Por eso, la recompra se considera una renovación. Los eventos **Access level updated** se crearán dos veces: - al finalizar la suscripción, para revocar el acceso del usuario - al recomprar la suscripción, para conceder el acceso <img src="/assets/shared/img_webhook_flows/Subscription_Rejoin_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Flujo de pausa de suscripción (solo Android) \{#subscription-pause-flow-android-only\} Este flujo aplica cuando un usuario pausa y luego reanuda una suscripción en Android. Pausar una suscripción tiene efectos diferidos. Si un usuario pausa una suscripción antes de que se renueve, la suscripción sigue activa y el usuario mantiene el acceso de pago por el resto del período de facturación. 1. Cuando el usuario pausa una suscripción, se activa el evento **Subscription paused (Android only)**. 2. Al final del período de suscripción, Adapty activa el evento **Access level updated** para revocar el acceso del usuario. 3. Cuando el usuario reanuda la suscripción, se activan los siguientes eventos: - **Subscription renewed** - **Access level updated** para restablecer el acceso del usuario Estas suscripciones pertenecerán a la misma cadena de transacciones, vinculadas con el mismo **vendor_original_transaction_id**. <img src="/assets/shared/img_webhook_flows/Subscription_Paused_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Flujos de prueba \{#trial-flows\} Si usas períodos de prueba en tu app, recibirás eventos adicionales relacionados con ellos. ### Flujo de prueba con conversión exitosa \{#trial-with-successful-conversion-flow\} El flujo más habitual ocurre cuando un usuario inicia una prueba, introduce una tarjeta de crédito y se convierte en suscriptor estándar al finalizar el período de prueba. En este caso, se crean los siguientes eventos en el momento en que comienza la prueba: - **Trial started** para marcar el inicio de la prueba - **Access level updated** para conceder acceso El evento **Trial converted** se crea cuando comienza la suscripción estándar. <img src="/assets/shared/img_webhook_flows/Trial_Flow_with_Successful_Conversion.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Flujo de prueba sin conversión exitosa \{#trial-without-successful-conversion-flow\} Si un usuario cancela la prueba antes de que se convierta en una suscripción, se crean los siguientes eventos en el momento de la cancelación: - **Trial renewal cancelled** para deshabilitar la conversión automática de la prueba en una suscripción - **Access level updated** para deshabilitar la renovación del acceso El usuario mantendrá el acceso hasta el final del período de prueba, momento en el que se crea el evento **Trial expired** para marcar su fin. <img src="/assets/shared/img_webhook_flows/Trial_Flow_without_Successful_Conversion.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Reactivación de suscripción tras un período de prueba expirado \{#subscription-reactivation-after-expired-trial-flow\} Si un período de prueba expira (por un problema de facturación o cancelación) y el usuario compra una suscripción posteriormente, se crean los siguientes eventos: - **Access level updated** para conceder acceso al usuario - **Trial converted** Aunque haya un intervalo entre el período de prueba y la suscripción, Adapty vincula ambos mediante `vendor_original_transaction_id`. Esta conversión se trata como parte de una cadena de transacciones continua que comienza con un período de prueba de precio cero. Por eso se crea el evento **Trial converted** en lugar de **Subscription started**. <img src="/assets/shared/img_webhook_flows/Subscription_Reactivation_Flow_after_Expired_Trial.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Cambios de producto \{#product-changes\} Esta sección recoge los cambios realizados en suscripciones activas, como actualizaciones, degradaciones o compras de un producto de otro grupo. ### Flujo de cambio inmediato de producto \{#immediate-product-change-flow\} Cuando un usuario cambia de producto, el cambio puede aplicarse en el sistema de forma inmediata antes de que finalice la suscripción (principalmente en casos de mejora o sustitución de producto). En ese momento, al producirse el cambio de producto: - El nivel de acceso cambia y se crean dos eventos **Access level updated**: 1. Para retirar el acceso al primer producto. 2. Para conceder acceso al segundo producto. - La suscripción antigua finaliza y se emite un reembolso (se crea el evento **Subscription refunded** con `cancellation_reason` = `upgraded`). Ten en cuenta que no se crea ningún evento **Subscription expired (churned)**; el evento **Subscription refunded** lo reemplaza. - La nueva suscripción comienza (se crea el evento **Subscription started** para el nuevo producto). <img src="/assets/shared/img_webhook_flows/Immediate_Product_Change_Flow_Upgrade.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Si un usuario cambia a un plan inferior, la primera suscripción se mantendrá activa hasta el final del período pagado, y cuando termine, será reemplazada por la nueva suscripción de nivel inferior. En este caso, solo se creará de inmediato el evento **Access level updated** para deshabilitar la renovación automática del acceso. El resto de eventos se crearán en el momento en que se produzca el cambio real de suscripción: - Se crea otro evento **Access level updated** para dar acceso al segundo producto. - Se crea el evento **Subscription expired (churned)** para finalizar la suscripción del primer producto. - Se crea el evento **Subscription started** para iniciar una nueva suscripción con el nuevo producto. <img src="/assets/shared/img_webhook_flows/Delayed_Product_Change_Downgrade.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Flujo de cambio de producto diferido \{#delayed-product-change-flow\} También existe una variante en la que el usuario cambia el producto en el momento de la renovación de la suscripción. Esta variante es muy similar a la anterior: se creará un evento **Access level updated** de inmediato para desactivar la renovación automática del acceso del producto antiguo. El resto de eventos se crearán en el momento en que el usuario realice el cambio de suscripción y este quede registrado en el sistema: - Se crea otro evento **Access level updated** para conceder acceso al segundo producto. - Se crea el evento **Subscription expired (churned)** para finalizar la suscripción del primer producto. - Se crea el evento **Subscription started** para iniciar una nueva suscripción con el nuevo producto. <img src="/assets/shared/img_webhook_flows/Product_Change_on_Renewal_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Flujo de resultados por problemas de facturación \{#billing-issue-outcome-flow\} Si los intentos de convertir una prueba o renovar una suscripción fallan por un problema de facturación, lo que ocurre a continuación depende de si hay un período de gracia habilitado. Con un período de gracia, si el pago tiene éxito, la prueba se convierte o la suscripción se renueva. Si falla, el store seguirá intentando cobrar al usuario por la suscripción y, si sigue fallando, el store terminará la prueba o suscripción por su cuenta. Por lo tanto, en el momento del problema de facturación, se crean los siguientes eventos en Adapty: - **Billing issue detected** - **Entered grace period** (si el período de gracia está habilitado) - **Access level updated** para mantener el acceso hasta el final del período de gracia Si el pago se realiza correctamente después, Adapty registra un evento **Trial converted** o **Subscription renewed**, y el usuario no pierde el acceso. Si el pago falla definitivamente y el store cancela la suscripción, Adapty genera estos eventos: - **Trial expired** o **Subscription expired (churned)** con `cancellation_reason: billing_error` - **Access level updated** para revocar el acceso del usuario <img src="/assets/shared/img_webhook_flows/Billing_Issue_Outcome_Flow_with_Grace_Period.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Sin un período de gracia, el período de reintento de facturación (el período en el que el store intenta cobrar al usuario de nuevo) comienza de inmediato. Si el pago nunca se completa antes de que finalice el período de gracia, el flujo es el mismo: se crean los mismos eventos cuando el store termina la suscripción automáticamente: - Evento **Trial expired** o **Subscription expired (churned)** con un `cancellation_reason` de `billing_error` - **Access level updated** para revocar el acceso del usuario <img src="/assets/shared/img_webhook_flows/Billing_Issue_Outcome_Flow_without_Grace_Period.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Flujos para compartir compras entre cuentas de usuario \{#sharing-purchases-across-user-accounts-flows\} Cuando un <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration), [Android](android-identifying-users#setting-customer-user-id-on-configuration), [React Native](react-native-identifying-users#setting-customer-user-id-on-configuration), [Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration), y [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip> intenta restaurar o ampliar una suscripción ya vinculada a un <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration), [Android](android-identifying-users#setting-customer-user-id-on-configuration), [React Native](react-native-identifying-users#setting-customer-user-id-on-configuration), [Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration), y [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip> diferente, la configuración **Sharing paid access between user accounts** de Adapty controla cómo se gestiona el acceso. El flujo variará según la opción seleccionada. :::note Para las transacciones de Apple Family Sharing (`in_app_ownership_type=FAMILY_SHARED`), solo se activa el evento **Access level updated** — los eventos de suscripción por producto que aparecen a continuación no se generan. Consulta [Apple Family Sharing](apple-family-sharing) para ver la matriz completa de eventos. ::: :::note Si un usuario pulsa **Restore Purchases** pero ya tiene acceso en el mismo perfil, la restauración no hace nada y no se activan eventos de webhook. Los eventos de esta sección solo se activan cuando el acceso se transfiere realmente entre perfiles. ::: Para ver de un vistazo qué eventos se disparan cuando un segundo perfil reclama una suscripción existente, usa esta matriz. Las secciones siguientes muestran el payload JSON completo de cada flujo. | Evento | Habilitado (predeterminado) | Transferir acceso al nuevo usuario | Deshabilitado | | --- | --- | --- | --- | | Nuevo perfil: **Access level updated** (`is_active=true`) | Se activa | Se activa | No se activa | | Perfil antiguo: **Access level updated** (`is_active=false`) | No se activa — ambos perfiles conservan el acceso | Se activa cuando el nuevo dispositivo identificado propaga la transacción | No se activa — el perfil original conserva el acceso | | Campo `profiles_sharing_access_level` en el nuevo evento | Lista los demás perfiles que comparten el nivel de acceso | `null` | No aplicable — no se activa ningún evento | Las renovaciones, reembolsos y vencimientos de una suscripción transferida siguen generando los eventos `subscription_renewed`, `subscription_refunded` y `subscription_expired` en el perfil que tenga el nivel de acceso en ese momento. El propio evento de transferencia no emite un evento `subscription_started`, ya que no se registra ninguna transacción nueva: solo cambia la atribución. Para más detalles sobre cada modo, consulta la [referencia práctica](sharing-paid-access-between-user-accounts#practical-reference). ### Transferir el nivel de acceso al nuevo usuario \{#transfer-access-to-new-user-flow\} La opción recomendada es transferir el nivel de acceso al nuevo usuario. Esto conserva el historial de transacciones del usuario original para mantener la coherencia en los análisis. Solo se crearán 2 eventos **Access level updated**: 1. para eliminar el acceso del primer usuario 2. para conceder acceso al segundo usuario <img src="/assets/shared/img_webhook_flows/Transfer_Access_to_New_User_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> A continuación se explican los campos relacionados con la asignación y transferencia del nivel de acceso en los eventos generados en este escenario: - **Usuario A: Nivel de acceso actualizado (se envía cuando el Usuario A compra una suscripción en la app)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": UserA, "event_properties": { "profile_has_access_level": true, }, "profiles_sharing_access_level": null } ``` - **Usuario A: Nivel de acceso actualizado (se envía cuando la app se reinstala y el Usuario B inicia sesión, revocando el acceso del Usuario A)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": UserA, "event_properties": { "profile_has_access_level": false, }, "profiles_sharing_access_level": null } ``` - **Usuario B: Nivel de acceso actualizado (enviado cuando el Usuario B inicia sesión y se concede el acceso)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000001", "customer_user_id": UserB, "event_properties": { "profile_has_access_level": true, }, "profiles_sharing_access_level": null } ``` ### Flujo de acceso compartido entre usuarios \{#shared-access-between-users-flow\} Esta opción permite que varios usuarios compartan el mismo nivel de acceso si su dispositivo está conectado con el mismo Apple/Google ID. Resulta útil cuando un usuario reinstala la app e inicia sesión con un correo diferente: seguirá teniendo acceso a su compra anterior. Con esta opción, varios usuarios identificados pueden compartir el mismo nivel de acceso. Aunque el nivel de acceso se comparte, todas las transacciones se registran bajo el <InlineTooltip tooltip="Customer User ID">ID de usuario original [iOS](identifying-users#set-customer-user-id-on-configuration), [Android](android-identifying-users#setting-customer-user-id-on-configuration), [React Native](react-native-identifying-users#setting-customer-user-id-on-configuration), [Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration) y [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip> para mantener el historial completo de transacciones y el análisis de datos. Por lo tanto, solo se creará 1 evento: **Access level updated** para conceder acceso al segundo usuario. <img src="/assets/shared/img_webhook_flows/Share_Access_Between_Users_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> A continuación se describen los campos relacionados con la asignación y el uso compartido del nivel de acceso en los eventos generados en este escenario: **Usuario B: Access level updated (enviado cuando el Usuario B inicia sesión y se concede el acceso)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": UserA, "event_properties": { "profile_has_access_level": true, }, "profiles_sharing_access_level": [ { "profile_id": "00000000-0000-0000-0000-000000000001, "customer_user_id": UserB } ] } ``` ### Flujo de acceso no compartido entre usuarios \{#access-not-shared-between-users-flow\} Con esta opción, solo el primer perfil de usuario que recibe el nivel de acceso lo conserva de forma permanente. Es ideal cuando las compras deben vincularse a un único <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration), [Android](android-identifying-users#setting-customer-user-id-on-configuration), [React Native](react-native-identifying-users#setting-customer-user-id-on-configuration), [Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration), y [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip>. <img src="/assets/shared/img_webhook_flows/Share_Access_Between_Users_Disabled_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: event-statuses --- --- title: "Estados de los eventos de integración" description: "" --- Adapty determina la entregabilidad en función del código de estado HTTP, considerando cualquier respuesta fuera del rango `200-399` como un error. Puedes hacer seguimiento del estado de los eventos de integración en la **Event List** dentro del Adapty Dashboard. El sistema muestra los estados de todas las integraciones habilitadas, independientemente de si un tipo de evento específico está activado para una integración concreta. - Negro: El evento se envió correctamente. - <span style={{ color: 'grey' }}>Gris:</span> El tipo de evento está deshabilitado para esta integración. - <span style={{ color: 'red' }}>Rojo:</span> Hay un problema con la integración que requiere atención. Para más detalles sobre los eventos fallidos, pasa el cursor sobre el nombre de la integración para ver un tooltip con información específica del error. <img src="/assets/shared/img/event-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> El **Event Feed** muestra datos de las últimas dos semanas para optimizar el rendimiento. Esta limitación mejora la velocidad de carga de la página, lo que facilita a los usuarios navegar y analizar los eventos de forma eficiente. --- # File: adjust --- --- title: "Adjust" description: "Conecta Adjust con Adapty para un mejor seguimiento de suscripciones y análisis." --- [Adjust](https://www.adjust.com/) es una de las principales plataformas Mobile Measurement Partner (MMP) que recopila y presenta datos de campañas de marketing. Esto ayuda a las empresas a hacer seguimiento del rendimiento de sus campañas. Adapty proporciona un conjunto completo de datos que te permite rastrear [eventos de suscripción](events) desde los stores en un solo lugar. Con Adapty, puedes ver fácilmente cómo se comportan tus suscriptores, entender qué les gusta y usar esa información para comunicarte con ellos de forma dirigida y efectiva. Por eso, esta integración te permite rastrear eventos de suscripción en Adjust y analizar con precisión cuántos ingresos generan tus campañas. La integración entre Adapty y Adjust funciona de dos maneras principales. 1. **Adapty recibe datos de atribución de Adjust** Una vez que hayas configurado la integración con Adjust, Adapty comenzará a recibir datos de atribución de Adjust. Puedes acceder a estos datos fácilmente y consultarlos en la página de perfil del usuario. <img src="/assets/shared/img/98769d9-CleanShot_2023-08-11_at_14.39.182x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. **Adapty envía eventos de suscripción a Adjust** Adapty puede enviar todos los eventos de suscripción configurados en tu integración a Adjust. Como resultado, podrás hacer seguimiento de estos eventos desde el dashboard de Adjust. Esta integración es útil para evaluar la efectividad de tus campañas publicitarias. ## Configurar la integración \{#set-up-integration\} ### Conectar Adapty con Adjust 1. Abre el Adapty Dashboard y ve a [Integrations > Adjust](https://app.adapty.io/integrations/adjust). 2. Activa el toggle en la parte superior de la página. 3. Rellena los campos e introduce tus credenciales de acceso. <img src="/assets/shared/img/5064125-CleanShot_2023-08-11_at_14.43.382x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Si habilitaste la autorización OAuth en la plataforma de Adjust, es obligatorio proporcionar un **OAuth Token** durante el proceso de integración para tus apps de iOS y Android. 4. A continuación, proporciona los **app tokens** para tus apps de iOS y Android. Abre tu dashboard de Adjust y verás tus apps. <img src="/assets/shared/img/adjust-apps.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Puede que tengas aplicaciones de Adjust distintas para iOS y Android, por lo que en Adapty dispones de dos secciones independientes para ello. Si solo tienes una aplicación de Adjust, introduce la misma información en ambas. ::: 5. Selecciona tu aplicación de la lista y copia el **App Token**. Pega el token en el campo correspondiente del Adapty Dashboard. <img src="/assets/shared/img/adjust-token.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Configurar eventos y etiquetas \{#configure-events-and-tags\} Adjust funciona de forma un poco diferente al resto de plataformas. Necesitas crear los eventos manualmente en el dashboard de Adjust, obtener los tokens de evento y copiarlos en los eventos correspondientes de Adapty. Por tanto, el primer paso es encontrar los tokens de evento de todos los eventos que quieres que Adapty envíe. Para ello: 1. En el dashboard de Adjust, abre tu app y cambia a la pestaña **Events**. <img src="/assets/shared/img/adjust-events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. Copia el token del evento y pégalo en Adapty. Debajo de las credenciales, hay tres grupos de eventos que puedes enviar a Adjust desde Adapty. Consulta la lista completa de eventos que ofrece Adapty [aquí](events). <img src="/assets/shared/img/adjust-event-token.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty enviará eventos de suscripción a Adjust mediante una integración servidor a servidor, lo que te permitirá ver todos los eventos de suscripción en tu dashboard de Adjust y vincularlos a tus campañas de adquisición. :::important Ten en cuenta lo siguiente: - Adjust no admite eventos con más de 58 días de antigüedad. Si tienes un evento que supera ese límite, Adapty lo enviará a Adjust, pero la fecha y hora del evento se reemplazará por la marca de tiempo actual. - Adjust no admite IPv6. Si desactivas la recopilación de IP en el SDK en **App settings** o al activar el SDK, puede enviarse únicamente una IPv6 del backend y el seguimiento puede fallar — mantén la recopilación de IP del SDK habilitada para garantizar el uso de IPv4. ::: ### Conecta tu app con Adjust Después de completar los pasos descritos anteriormente, añade los siguientes dos métodos a tu app. Establecerán la comunicación entre tu app y Adjust: 1. **Para enviar datos de suscripción a Adjust**: Pasa el ID de dispositivo de Adjust al método del SDK `setIntegrationIdentifier()` 2. **Para recibir datos de atribución desde Adjust**: Actualiza los datos de atribución con el método del SDK `updateAttribution()` Para Adjust versión 5.0 o posterior, usa el siguiente ejemplo: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers class AdjustModuleImplementation { func updateAdjustAdid() { Adjust.adid { adid in guard let adid else { return } // Adapty SDK 4.x Adapty.setIntegrationIdentifier(.adjustDeviceId(adid)) // Adapty SDK 3.x Adapty.setIntegrationIdentifier(key: "adjust_device_id", value: adid) } } func updateAdjustAttribution() { Adjust.attribution { attribution in guard let attribution = attribution?.dictionary() else { return } // Adapty SDK 4.x Adapty.updateAttribution(attribution, source: .adjust) // Adapty SDK 3.x Adapty.updateAttribution(attribution, source: "adjust") } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adjust.getAdid { adid -> if (adid == null) return@getAdid Adapty.setIntegrationIdentifier("adjust_device_id", adid) { error -> if (error != null) { // handle the error } } } Adjust.getAttribution { attribution -> if (attribution == null) return@getAttribution Adapty.updateAttribution(attribution, "adjust") { error -> // handle the error } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adjust.getAdid(adid -> { if (adid == null) return; Adapty.setIntegrationIdentifier("adjust_device_id", adid, error -> { if (error != null) { // handle the error } }); }); Adjust.getAttribution(attribution -> { if (attribution == null) return; Adapty.updateAttribution(attribution, "adjust", error -> { // handle the error }); }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers var adjustConfig = new AdjustConfig(appToken, environment); // Before submiting Adjust config... adjustConfig.setAttributionCallbackListener(attribution => { // Make sure Adapty SDK is activated at this point // You may want to lock this thread awaiting of `activate` adapty.updateAttribution(attribution, "adjust"); }); // ... Adjust.create(adjustConfig); Adjust.getAdid((adid) => { if (adid) adapty.setIntegrationIdentifier("adjust_device_id", adid); }); ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```javascript showLineNumbers try { final adid = await Adjust.getAdid(); if (adid == null) { // handle the error } await Adapty().setIntegrationIdentifier( key: "adjust_device_id", value: adid, ); final attributionData = await Adjust.getAttribution(); var attribution = Map<String, String>(); if (attributionData.trackerToken != null) attribution['trackerToken'] = attributionData.trackerToken!; if (attributionData.trackerName != null) attribution['trackerName'] = attributionData.trackerName!; if (attributionData.network != null) attribution['network'] = attributionData.network!; if (attributionData.adgroup != null) attribution['adgroup'] = attributionData.adgroup!; if (attributionData.creative != null) attribution['creative'] = attributionData.creative!; if (attributionData.clickLabel != null) attribution['clickLabel'] = attributionData.clickLabel!; if (attributionData.costType != null) attribution['costType'] = attributionData.costType!; if (attributionData.costAmount != null) attribution['costAmount'] = attributionData.costAmount!.toString(); if (attributionData.costCurrency != null) attribution['costCurrency'] = attributionData.costCurrency!; if (attributionData.fbInstallReferrer != null) attribution['fbInstallReferrer'] = attributionData.fbInstallReferrer!; await Adapty().updateAttribution(attribution, source: "adjust"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers // 1. To update ADID Adjust.GetAdid((adid) => { if (adid == null) { // handle the error return; } Adapty.SetIntegrationIdentifier("adjust_device_id", adid, (error) => { if (error != null) { // handle the error return; } }); }); // 2. To update Attribution // in your adjust configuration scope: adjustConfig.AttributionChangedDelegate = AttributionChangedCallback; public void AttributionChangedCallback(AdjustAttribution attributionData) { var attribution = new Dictionary<string, string>(); if (attributionData.TrackerToken != null) attribution["trackerToken"] = attributionData.TrackerToken; if (attributionData.TrackerName != null) attribution["trackerName"] = attributionData.TrackerName; if (attributionData.Network != null) attribution["network"] = attributionData.Network; if (attributionData.Adgroup != null) attribution["adgroup"] = attributionData.Adgroup; if (attributionData.Creative != null) attribution["creative"] = attributionData.Creative; if (attributionData.ClickLabel != null) attribution["clickLabel"] = attributionData.ClickLabel; if (attributionData.CostType != null) attribution["costType"] = attributionData.CostType; if (attributionData.CostAmount != null) attribution["costAmount"] = attributionData.CostAmount.ToString(); if (attributionData.CostCurrency != null) attribution["costCurrency"] = attributionData.CostCurrency; if (attributionData.FbInstallReferrer != null) attribution["fbInstallReferrer"] = attributionData.FbInstallReferrer; // you will probably need to install Newtonsoft.Json package, if not yet var attributionJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(attribution); Adapty.UpdateAttribution(attributionJsonString, "adjust", (error) => { if (error != null) { // handle the error } }); } ``` </TabItem> </Tabs> ## Estructura de los eventos \{#event-structure\} Adapty envía los eventos seleccionados a Adjust según la configuración de la sección **Events names** en la [**página de integración de Adjust**](https://app.adapty.io/integrations/adjust). Cada evento tiene la siguiente estructura: ```json { "event_token": "EVENT_TOKEN_FROM_CONFIG", "app_token": "APP_TOKEN_FROM_CONFIG", "s2s": 1, "environment": "production", "created_at_unix": 1709294400, "currency": "USD", "revenue": 9.99, "customer_user_id": "user_12345", "external_device_id": "user_12345", "ip_address": "192.168.100.1", "user_agent": "Mozilla/5.0 (Linux; Android 14; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36", "android_id": "875646c2-4a56-4211-8931-168532479006", "gps_adid": "875646c2-4a56-4211-8931-168532479006", "callback_params": "{\"integration_event_id\":\"550e8400-e29b-41d4-a716-446655440000\",\"customer_user_id\":\"user_12345\",\"vendor_product_id\":\"com.example.app.yearly.premium\",\"transaction_id\":\"GPA.3312-4512-1100-55923\",\"original_transaction_id\":\"GPA.3312-4512-1100-55923\",\"store\":\"play_store\",\"store_country\":\"US\",\"price_usd\":9.99,\"proceeds_usd\":8.49,\"price_local\":9.99,\"proceeds_local\":8.49,\"net_revenue_usd\":8.49,\"net_revenue_local\":8.49,\"tax_amount_usd\":0.0,\"tax_amount_local\":0.0,\"consecutive_payments\":3,\"rate_after_first_year\":false}", "partner_params": "{\"integration_event_id\":\"550e8400-e29b-41d4-a716-446655440000\",\"customer_user_id\":\"user_12345\",\"vendor_product_id\":\"com.example.app.yearly.premium\",\"transaction_id\":\"GPA.3312-4512-1100-55923\",\"original_transaction_id\":\"GPA.3312-4512-1100-55923\",\"store\":\"play_store\",\"store_country\":\"US\",\"price_usd\":9.99,\"proceeds_usd\":8.49,\"price_local\":9.99,\"proceeds_local\":8.49,\"net_revenue_usd\":8.49,\"net_revenue_local\":8.49,\"tax_amount_usd\":0.0,\"tax_amount_local\":0.0,\"consecutive_payments\":3,\"rate_after_first_year\":false}" } ``` Dónde | Parámetro | Tipo | Descripción | |:---------------------|:--------|:---------------------------------------------------------------------------------------------------------------------------------------------| | `app_token` | String | El token de app de Adjust obtenido en la configuración de tu integración. | | `event_token` | String | El token de evento de Adjust asociado al evento específico de Adapty. | | `s2s` | Integer | Indicador de evento servidor a servidor. | | `environment` | String | `sandbox` o `production`. | | `created_at_unix` | Integer | Marca de tiempo del evento en segundos. | | `currency` | String | Código de divisa (p. ej., "USD") de la transacción. Solo se incluye cuando los ingresos superan 0,001, ya que Adjust requiere que se envíen tanto los ingresos como la divisa juntos. | | `revenue` | Float | Importe de ingresos de la transacción. Solo se incluye cuando el valor supera 0,001. Ten en cuenta que los eventos de reembolso se envían sin propiedades de ingresos, ya que Adjust no admite valores de ingresos negativos. | | `customer_user_id` | String | El Customer User ID del usuario. | | `external_device_id` | String | Igual que `customer_user_id`. | | `ip_address` | String | Dirección IP del usuario (solo IPv4). | | `user_agent` | String | Cadena User Agent del dispositivo. | | `adid` | String | ID de dispositivo de Adjust (si se conoce). | | `android_id` | String | **Solo Android**. ID de publicidad de Google. | | `gps_adid` | String | **Solo Android**. ID de publicidad de Google. | | `idfa` | String | **Solo iOS**. ID para anunciantes. | | `idfv` | String | **Solo iOS**. ID para proveedores. | | `callback_params` | String | Cadena JSON con todos los [campos de evento](webhook-event-types-and-fields#for-most-event-types) disponibles. Solo se incluyen los campos no nulos. | | `partner_params` | String | Igual que `callback_params`. | ## Resolución de problemas \{#troubleshooting\} ### Discrepancia en los ingresos \{#revenue-discrepancy\} Si hay una discrepancia en los ingresos entre Adapty y Adjust, es posible que no todos tus usuarios estén usando la versión de la app que incluye el SDK de Adapty. Para garantizar la consistencia de los datos, puedes obligar a tus usuarios a actualizar a una versión de la app que incluya el SDK de Adapty. --- # File: airbridge --- --- title: "Airbridge" description: "Conecta Adapty con Airbridge para rastrear información de marketing y atribución." --- [Airbridge](https://www.airbridge.io/) ofrece un análisis integrado del rendimiento de marketing para sitios web y aplicaciones móviles, consolidando datos recopilados de múltiples dispositivos, plataformas y canales. Con el motor de resolución de identidad de Airbridge, puedes combinar datos de identidad dispersos de clientes procedentes de interacciones web y de la app en una identidad unificada basada en personas, lo que se traduce en una atribución más precisa. Adapty proporciona un conjunto completo de datos que te permite rastrear [eventos de suscripción](events) desde los stores en un solo lugar. Con Adapty, puedes ver fácilmente cómo se comportan tus suscriptores, descubrir qué les gusta y usar esa información para comunicarte con ellos de forma dirigida y efectiva. La integración entre Adapty y Airbridge funciona de dos maneras principales. 1. **Recibir datos de atribución desde Airbridge** Una vez configurada la integración con Airbridge, Adapty comenzará a recibir datos de atribución de Airbridge. Puedes acceder y consultar estos datos fácilmente en la página del usuario. 2. **Enviar eventos de suscripción a Airbridge** Adapty puede enviar todos los eventos de suscripción configurados en tu integración a Airbridge. Como resultado, podrás rastrear estos eventos dentro del dashboard de Airbridge. Esta integración es útil para evaluar la efectividad de tus campañas publicitarias. ## Configurar la integración \{#set-up-integration\} ### Conectar Adapty con Airbridge \{#connect-adapty-to-airbridge\} Para integrar Airbridge, ve a [Integrations > Airbridge](https://app.adapty.io/integrations/airbridge), activa el interruptor y rellena los campos. Primero, introduce las credenciales para establecer la conexión entre tus perfiles de Airbridge y Adapty. Se requieren el nombre de la app de Airbridge y el token de API de Airbridge. <img src="/assets/shared/img/2b31d90-Untitled-1_1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Ambos se encuentran en tu dashboard de Airbridge, en la sección [Third-party Integrations > Adapty](https://app.airbridge.io/app/testad/integrations/third-party/adapty). <img src="/assets/shared/img/5a2f627-Screenshot_2023-02-21_at_11.19.29_AM.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> El campo del token de API de Adapty se genera previamente en el backend de Adapty. Debes copiar el valor del token de API de Adapty y pegarlo en el dashboard de Airbridge en el campo Adapty Authorization Token. <img src="/assets/shared/img/ff422d1-CleanShot_2023-03-01_at_17.11.412x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Configurar eventos y etiquetas \{#configure-events-and-tags\} Debajo de las credenciales encontrarás tres grupos de eventos que puedes enviar a Airbridge desde Adapty. <img src="/assets/shared/img/eb4e3a9-CleanShot_2023-08-22_at_13.58.472x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Simplemente activa los que necesites. ### Conectar tu app con Airbridge \{#connect-your-app-to-airbridge\} Para la integración, debes pasar `airbridge_device_id` al perfil y llamar a `setIntegrationIdentifier` tal como se muestra en el siguiente ejemplo: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "airbridge_device_id", value: AirBridge.deviceUUID() ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Airbridge.getDeviceInfo().getUUID(object: AirbridgeCallback.SimpleCallback<String>() { override fun onSuccess(result: String) { Adapty.setIntegrationIdentifier("airbridge_device_id", result) { error -> if (error != null) { // handle the error } } } override fun onFailure(throwable: Throwable) { } }) ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers final deviceUUID = await Airbridge.state.deviceUUID; try { await Adapty().setIntegrationIdentifier( key: "airbridge_device_id", value: deviceUUID, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers try { const deviceId = await Airbridge.state.deviceUUID(); await adapty.setIntegrationIdentifier("airbridge_device_id", deviceId); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> Lee más sobre airbridgeDeviceId en la [documentación de Airbridge.](https://help.airbridge.io/en/developers/airbridge-device-id-faq) Adapty puede tardar hasta 24 horas en recibir los datos de atribución de Airbridge tras un evento de suscripción. Adapty los mostrará en el dashboard de inmediato. ## Estructura del evento \{#event-structure\} Adapty envía los eventos seleccionados a Airbridge según lo configurado en la sección **Events names** de la [**página de integración de Airbridge**](https://app.adapty.io/integrations/airbridge). Cada evento tiene la siguiente estructura: ```json { "user": { "externalUserID": "user_12345", "externalUserEmail": "user@example.com", "attributes": { "is_premium": true } }, "device": { "deviceUUID": "550e8400-e29b-41d4-a716-446655440000", "deviceModel": "iPhone 14 Pro", "osName": "iOS", "osVersion": "17.0.1", "locale": "en-US", "timezone": "America/New_York", "ifa": "00000000-0000-0000-0000-000000000000", "ifv": "00000000-0000-0000-0000-000000000000" }, "app": { "packageName": "com.example.app", "version": "1.2.3" }, "eventUUID": "d4f6f1f4-96fb-4a31-bafd-599fef77be90", "eventTimestamp": 1709294400000, "eventData": { "goal": { "category": "airbridge.subscribe", "customAttributes": { "isTrialConverted": true }, "semanticAttributes": { "transactionID": "GPA.3383-4699-1373-07113", "totalValue": 9.99, "currency": "USD", "period": "P1M", "isRenewal": true, "renewalCount": 2, "products": [ { "productID": "yearly.premium.6999", "name": "yearly.premium.6999", "position": 1 } ] } } } } ``` Donde: | Parámetro | Tipo | Descripción | |:---------------------------------------------|:--------|:-----------------------------------------------------------------------------------| | `user` | Object | Información del usuario. | | `user.externalUserID` | String | El Customer User ID del usuario. | | `user.externalUserEmail` | String | La dirección de correo electrónico del usuario (si está disponible). | | `user.attributes` | Object | Atributos personalizados del usuario. | | `device` | Object | Información del dispositivo. | | `device.deviceUUID` | String | El UUID del dispositivo en Airbridge. | | `device.deviceModel` | String | Modelo del dispositivo (p. ej., "iPhone 14 Pro"). | | `device.osName` | String | Nombre del sistema operativo (p. ej., "iOS", "Android"). | | `device.osVersion` | String | Versión del sistema operativo. | | `device.ifa` | String | **Solo iOS**. ID para anunciantes. | | `device.ifv` | String | **Solo iOS**. ID para proveedores. | | `device.gaid` | String | **Solo Android**. Google Advertising ID. | | `app` | Object | Información de la app. | | `app.packageName` | String | El nombre del paquete / bundle ID de la aplicación. | | `app.version` | String | La versión de la aplicación. | | `eventUUID` | String | ID único del evento en Adapty. | | `eventTimestamp` | Long | Marca de tiempo del evento en milisegundos. | | `eventData` | Object | Detalles del evento. | | `eventData.goal.category` | String | La categoría del evento en Airbridge (mapeada desde el evento de Adapty). | | `eventData.goal.semanticAttributes` | Object | Atributos estándar del evento. | | `...semanticAttributes.transactionID` | String | ID de transacción del store. | | `...semanticAttributes.totalValue` | Float | Importe de los ingresos. | | `...semanticAttributes.currency` | String | Código de divisa (p. ej., "USD"). | | `...semanticAttributes.period` | String | Período de suscripción en formato de duración ISO 8601 (p. ej., "P1M"). | | `...semanticAttributes.isRenewal` | Boolean | `true` si se trata de una transacción de renovación. | | `...semanticAttributes.renewalCount` | Integer | Número de renovaciones exitosas. | | `...semanticAttributes.products` | Array | Lista de productos involucrados en el evento. | | `...semanticAttributes.products[].productID` | String | El ID del producto en el store (p. ej., "yearly.premium.6999"). | | `...semanticAttributes.products[].name` | String | Igual que `productID`. | | `...semanticAttributes.products[].position` | Integer | La posición del producto en la lista (siempre 1). | --- # File: apple-search-ads --- --- title: "Apple Ads" description: "Integra Apple Ads con Adapty para optimizar las conversiones de suscripciones." --- :::important La integración de Apple Ads en **App settings** se utiliza únicamente para análisis básico y para las integraciones con SplitMetrics Acquire y Asapty. [Adapty Ads Manager](adapty-ads-manager) utiliza una conexión independiente. Conecta tu cuenta de Apple Ads en [Adapty Ads Manager](adapty-ads-manager-get-started). ::: Adapty puede ayudarte a obtener datos de atribución de Apple Ads y analizar tus métricas con segmentación por campaña y palabra clave. Adapty recopila los datos de atribución de Apple Ads automáticamente a través de su SDK y el AdServices Framework. Una vez que hayas configurado la integración con Apple Ads, Adapty comenzará a recibir datos de atribución de Apple Ads. Puedes acceder a estos datos y consultarlos fácilmente en la página de perfiles. <img src="/assets/shared/img/ba4a3e9-CleanShot_2023-08-21_at_15.14.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Configurar la integración \{#set-up-integration\} ### Conectar Adapty con el framework AdServices \{#connect-adapty-to-the-adservices-framework\} Apple Ads a través de [AdServices](https://developer.apple.com/documentation/adservices) requiere cierta configuración en el Adapty Dashboard, y también necesitarás habilitarlo en el lado de la app. Para configurar Apple Ads usando el framework AdServices a través de Adapty, sigue estos pasos: #### Paso 1: Obtener la clave pública \{#step-1-obtain-public-key\} En el Adapty Dashboard, ve a [Settings -> Apple Ads.](https://app.adapty.io/settings/apple-search-ads) Localiza la clave pública pregenerada (Adapty te proporciona un par de claves) y cópiala. <img src="/assets/shared/img/baa5998-CleanShot_2023-08-21_at_14.55.542x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Si utilizas un servicio alternativo o tu propia solución para la atribución de Apple Ads, puedes subir tu propia clave privada. ::: #### Paso 2: Configura la gestión de usuarios en Apple Ads \{#step-2-configure-user-management-on-apple-ads\} En tu [cuenta de Apple Ads](https://ads.apple.com/app-store), ve a la página **Settings > User Management**. Para que Adapty pueda obtener datos de atribución, necesitas invitar otra cuenta de Apple ID y concederle acceso como API Account Manager. Puedes usar cualquier cuenta a la que tengas acceso o crear una nueva exclusivamente para este fin. Lo importante es que debas poder iniciar sesión en Apple Ads con ese Apple ID. <img src="/assets/shared/img/ec183b2-kdjsfldsfjkdsfdfd.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### Paso 3: Generar credenciales de API \{#step-3-generate-api-credentials\} Como siguiente paso, inicia sesión en la cuenta recién añadida en Apple Ads. Ve a Settings -> API en la interfaz de Apple Ads. Pega la clave pública copiada anteriormente en el campo correspondiente. Genera nuevas credenciales de API. #### Paso 4: Configurar Adapty con las credenciales de Apple Ads \{#step-4-configure-adapty-with-apple-ads-credentials\} Copia los campos Client ID, Team ID y Key ID de la configuración de Apple Ads. En el Adapty Dashboard, pega estas credenciales en los campos correspondientes. <img src="/assets/shared/img/7356113-CleanShot_2023-08-21_at_15.08.512x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Conectar tu app a la red AdServices \{#connect-your-app-to-the-adservices-network\} Una vez que completes [la configuración del framework AdServices](#connect-the-adservices-framework), Adapty empieza a recopilar automáticamente los datos de atribución de Apple Search Ads. No necesitas añadir ningún código al SDK. En aplicaciones iOS, estos datos de atribución **siempre** tendrán prioridad sobre los datos de otras fuentes. Si este comportamiento no es el deseado, *desactiva* la atribución de ASA siguiendo las instrucciones a continuación. ## Desactivar la integración \{#disable-integration\} Para desactivar la atribución de Apple Search Ads, abre la pestaña [**App Settings** -> **Apple Search Ads**](https://app.adapty.io/settings/apple-search-ads) y desactiva el interruptor **Receive Apple Search Ads attribution**. <img src="/assets/shared/img/asa-disable.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning Ten en cuenta que desactivar esto detendrá por completo la recepción de datos de análisis de ASA. Como resultado, ASA dejará de utilizarse en el análisis y no se enviará a las integraciones. Además, SplitMetrics Acquire y Asapty dejarán de funcionar, ya que dependen de la atribución de ASA para operar correctamente. La atribución recibida antes de este cambio no se verá afectada. ::: ## Subir tus propias claves \{#uploading-your-own-keys\} :::note Opcional Estos pasos no son necesarios para la atribución de Apple Ads, solo para trabajar con otros servicios como Asapty o tu propia solución. ::: Puedes usar tu propio par de claves pública-privada si estás utilizando otros servicios o una solución propia para la atribución de ASA. ### Paso 1 \{#step-1\} Genera la clave privada en el Terminal ```text showLineNumbers title="Text" openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem ``` Súbela en Adapty Settings -> Apple Ads (botón Upload private key) ### Paso 2 \{#step-2\} Genera la clave pública en el Terminal ```text showLineNumbers title="Text" openssl ec -in private-key.pem -pubout -out public-key.pem ``` Puedes usar esta clave pública en los ajustes de Apple Ads de la cuenta con el rol API Account Manager. Así podrás usar los valores generados de Client ID, Team ID y Key ID tanto en Adapty como en otros servicios. --- # File: switch-from-appsflyer-s2s-api-2-to-3 --- --- title: "Cambiar de AppsFlyer S2S API 2 a 3" description: "Actualiza de AppsFlyer S2S API 2 a 3 en Adapty." --- Según las [novedades oficiales de AppsFlyer](https://support.appsflyer.com/hc/en-us/articles/20509378973457-Bulletin-Upgrading-the-AppsFlyer-S2S-API), para ofrecer un uso más seguro de la API y reducir el fraude, AppsFlyer ha actualizado su API servidor a servidor (S2S) para eventos in-app. El endpoint actual quedará obsoleto en el futuro, por lo que recomendamos empezar a planificar la migración. Adapty es compatible con AppsFlyer S2S API 3 y te permite realizar el cambio desde API 2 sin complicaciones. Ten en cuenta que este cambio es unidireccional, por lo que no podrás volver a API 2 una vez realizado. Para cambiar de AppsFlyer S2S API 2 a 3: 1. Abre el [sitio de AppsFlyer](https://www.appsflyer.com/home) e inicia sesión. 2. Haz clic en **Tu nombre de cuenta** -> **Security Center** en la esquina superior izquierda del dashboard. <img src="/assets/shared/img/be299ea-appsflyer_security_center.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la ventana **Manage your account security**, haz clic en el botón **Manage your AppsFlyer API and S2S tokens**. 4. Si no tienes un token S2S, haz clic en el botón **New token**. Si ya lo tienes, continúa con el paso 8. <img src="/assets/shared/img/7934920-appsflyer_new_token.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. En la ventana **New token**, introduce el nombre del token. Este nombre es solo para tu referencia. 6. Selecciona **S2S** en la lista **Choose type**. 7. No olvides hacer clic en el botón **Create new token** para guardar el nuevo token. 8. En la ventana **Tokens**, copia el token S2S. <img src="/assets/shared/img/d014c25-appsflyer_tokens.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. Abre [**Integrations** -> **AppsFlyer**](https://app.adapty.io/integrations/appsflyer) en el Adapty Dashboard. 10. En el campo **AppsFlyer S2S API**, selecciona **API 3**. <img src="/assets/shared/img/c0b3e72-appsflyer_switch_API.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 11. Pega la clave S2S copiada en los campos **Dev key for iOS** y **Dev key for Android**. 12. Haz clic en el botón **Save** para confirmar el cambio. En ese momento, tu integración cambia instantáneamente a AppsFlyer S2S API 3 y los nuevos eventos se enviarán a la nueva URL: `https://api3.appsflyer.com/inappevent`. --- # File: asapty --- --- title: "Asapty" description: "Descubre Asapty y su papel en el ecosistema de suscripciones de Adapty." --- Con la integración de [Asapty](https://asapty.com/) puedes optimizar tus campañas de Search Ads. Adapty envía eventos de suscripción a Asapty para que puedas crear dashboards personalizados basados en la atribución de Apple Search Ads. Esta integración en concreto no añade ningún dato de atribución a Adapty, ya que obtenemos todo lo necesario directamente desde [ASA](apple-search-ads). ## Configurar la integración \{#set-up-integration\} ### Conectar Adapty con Asapty \{#connect-adapty-to-asapty\} Para integrar Asapty, ve a [Integrations > Asapty](https://app.adapty.io/integrations/asapty) en el Adapty Dashboard y rellena el campo con tu Asapty ID. <img src="/assets/shared/img/895de2b-CleanShot_2023-08-14_at_18.57.462x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> El Asapty ID se encuentra en la sección Settings > General de tu cuenta de Asapty. ### Configurar eventos y etiquetas \{#configure-events-and-tags\} Justo debajo de las credenciales encontrarás tres grupos de eventos que puedes enviar a Asapty desde Adapty. Activa únicamente los que necesites. Consulta la lista completa de eventos que ofrece Adapty [aquí](events). <img src="/assets/shared/img/58ddf41-CleanShot_2023-08-15_at_15.11.072x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Recomendamos usar los nombres de evento predeterminados que proporciona Asapty, aunque puedes cambiarlos según tus necesidades. ### Conectar tu app con Asapty \{#connect-your-app-to-asapty\} Una vez completados los pasos anteriores, Adapty recibe automáticamente los datos de atribución de Asapty. No es necesario solicitar explícitamente esos datos en el código de tu aplicación. Para mejorar la precisión de la atribución, configura Asapty para que incluya el `customerUserId` en los datos de cada evento. ## Estructura de eventos de Asapty \{#asapty-event-structure\} Adapty envía eventos a Asapty mediante una petición GET con parámetros de consulta. Cada URL de evento tiene este aspecto: ``` https://asapty.com/_api/mmpEvents/?source=adapty&asaptyid=a1b2c3d4&keywordid=12345&adgroupid=67890&campaignid=11223&conversiondate=1709294400000&event_name=subscription_renewed&install_time=1709100000&app_name=MyApp&json=%7B%22af_revenue%22%3A%229.99%22%2C%22af_currency%22%3A%22USD%22...%7D ``` Parámetros de consulta: | Parámetro | Tipo | Descripción | |:-----------------|:-------|:------------------------------------------------------------------| | `source` | String | Siempre "adapty". | | `asaptyid` | String | El Asapty ID de tus credenciales. | | `keywordid` | String | ID de palabra clave de Apple Search Ads (si está disponible). | | `adgroupid` | String | ID del grupo de anuncios de Apple Search Ads (si está disponible).| | `campaignid` | String | ID de campaña de Apple Search Ads (si está disponible). | | `conversiondate` | Long | Marca de tiempo del evento en **milisegundos**. | | `event_name` | String | Nombre del evento (mapeado desde el evento de Adapty). | | `install_time` | Long | Marca de tiempo de la instalación en segundos. | | `app_name` | String | Título de la app en Adapty (si está disponible). | | `json` | String | Cadena JSON codificada en URL con los detalles del evento (ver más abajo). | El parámetro `json` es una cadena JSON codificada en URL que contiene los siguientes campos: | Parámetro | Tipo | Descripción | |:--------------------------|:-------|:---------------------------------------------------| | `af_revenue` | String | Importe de ingresos como cadena de texto. | | `af_currency` | String | Código de moneda (p. ej., "USD"). | | `transaction_id` | String | ID de transacción del store. | | `original_transaction_id` | String | ID de transacción original del store. | | `purchase_date` | Long | Marca de tiempo de la compra en milisegundos. | | `original_purchase_date` | Long | Marca de tiempo de la compra original en milisegundos. | | `environment` | String | `Production` o `Sandbox`. | | `vendor_product_id` | String | ID del producto en el store. | | `profile_country` | String | Código de país basado en la IP del usuario. | | `store_country` | String | Código de país del store del usuario. | ## Solución de problemas \{#troubleshooting\} - Asegúrate de haber configurado [Apple Search Ads](apple-search-ads) en Adapty y de haber [subido las credenciales](https://app.adapty.io/settings/apple-search-ads); sin ellas, Asapty no funcionará. - Solo los perfiles con atribución de ASA detallada y no orgánica enviarán sus eventos a Asapty. Verás el mensaje "The user profile is missing the required integration data." si la atribución no es suficiente. - Los perfiles creados antes de configurar las integraciones no podrán enviar sus eventos a Asapty. - Si la integración con Adapty no funciona a pesar de estar correctamente configurada, comprueba que el toggle **Receive Apple Search Ads attribution in Adapty** esté activado en la pestaña [**App Settings** -> **Apple Search Ads**](https://app.adapty.io/settings/apple-search-ads). --- # File: branch --- --- title: "Branch" description: "Integra Branch con Adapty para rastrear deep links y conversiones de la app." --- [Branch](https://www.branch.io/) permite a las empresas llegar a sus usuarios, interactuar con ellos y analizar resultados en distintos dispositivos, canales y plataformas. Es una plataforma intuitiva diseñada para aumentar los ingresos móviles mediante enlaces especializados que funcionan sin problemas en todos los dispositivos, canales y plataformas. Adapty ofrece un conjunto completo de datos que te permite rastrear [eventos de suscripción](events) desde los stores en un solo lugar. Con Adapty, puedes ver fácilmente el comportamiento de tus suscriptores, conocer sus preferencias y usar esa información para comunicarte con ellos de forma dirigida y efectiva. La integración entre Adapty y Branch funciona de dos maneras principales. 1. **Recibir datos de atribución de Branch** Una vez que hayas configurado la integración con Branch, Adapty comenzará a recibir datos de atribución de Branch. Puedes consultar y ver estos datos fácilmente en la página del perfil del usuario. <img src="/assets/shared/img/49f4aa7-CleanShot_2023-08-11_at_17.36.072x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. **Envío de eventos de suscripción a Branch** Adapty puede enviar todos los eventos de suscripción configurados en tu integración a Branch. Como resultado, podrás hacer seguimiento de estos eventos desde el dashboard de Branch. ## Configurar la integración \{#set-up-integration\} ### Conectar Adapty con Branch Para integrar Branch, ve a [Integrations > Branch](https://app.adapty.io/integrations/branch) en el Adapty Dashboard, activa el interruptor y rellena los campos. <img src="/assets/shared/img/817a051-CleanShot_2023-08-11_at_15.54.372x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Para obtener el valor de **Branch Key**, abre los [Ajustes de cuenta](https://dashboard.branch.io/account-settings/profile) de Branch y busca el campo **Branch Key**. Úsalo en el campo **Key test** (para Sandbox) o **Key live** (para producción) del Adapty Dashboard. En Branch, cambia entre los entornos Live y Tests para obtener la clave correspondiente. <img src="/assets/shared/img/130e58b-CleanShot_2023-08-11_at_15.24.162x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Configurar eventos y etiquetas \{#configure-events-and-tags\} Debajo de las credenciales, encontrarás tres grupos de eventos que puedes enviar a Branch desde Adapty. Activa simplemente los que necesites. Consulta la lista completa de eventos que ofrece Adapty [aquí](events). Puedes enviar un evento con los ingresos netos \(después del recorte de Apple/Google\) o solo los ingresos brutos. También puedes marcar la casilla para reportar en la moneda del usuario. <img src="/assets/shared/img/a645cf8-CleanShot_2023-08-11_at_15.18.282x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Recomendamos usar los nombres de eventos predeterminados que ofrece Adapty, aunque puedes cambiarlos según tus necesidades. Adapty enviará eventos de suscripción a Branch mediante una integración servidor a servidor, lo que te permitirá ver todos los eventos de suscripción en tu dashboard de Branch y vincularlos a tus campañas de adquisición. ### Conecta tu app con Branch \{#connect-your-app-to-branch\} 1. Llama al método `.setIntegrationIdentifier()` del SDK para inicializar la conexión. Puedes pasar tu Branch Identity ID al parámetro `customerUserId`. :::note Los SDKs de terceros generan los IDs de usuario de forma asíncrona. Es posible que el ID no esté disponible cuando se ejecuta `Adapty.activate()`. Si tu **Customer User ID** proviene de uno de estos SDKs, llama a `Adapty.activate()` sin él. Una vez que el ID esté disponible, llama a `setIntegrationIdentifier()` y luego a `identify()` con el CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.branchId(<BRANCH_IDENTITY_ID>)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "branch_id", value: <BRANCH_IDENTITY_ID> ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers // login and update attribution and identifier Branch.getAutoInstance(this) .setIdentity("YOUR_USER_ID") { referringParams, error -> referringParams?.let { data -> Adapty.updateAttribution(data, "branch") { error -> if (error != null) { //handle the error } } } } // logout Branch.getAutoInstance(context).logout() ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```javascript showLineNumbers import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; FlutterBranchSdk.setIdentity('YOUR_USER_ID'); ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers Branch.setIdentity("your user id"); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers import branch from 'react-native-branch'; branch.setIdentity('YOUR_USER_ID'); ``` </TabItem> </Tabs> 2. Usa el método `.updateAttribution()` para guardar los datos de atribución. Si no especificaste el ID de usuario de Branch en el paso anterior, pásalo al parámetro `networkUserId` aquí. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers class YourBranchImplementation { func initializeBranch() { // Pass the attribution you receive from the initializing method of Branch iOS SDK to Adapty. Branch.getInstance().initSession(launchOptions: launchOptions) { (data, error) in if let data { // Adapty SDK 4.x Adapty.updateAttribution(data, source: .branch) // Adapty SDK 3.x Adapty.updateAttribution(data, source: "branch") } } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers //everything is in the above snippet for Android ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers try { await Adapty().setIntegrationIdentifier( key: "branch_id", value: <BRANCH_IDENTITY_ID>, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; Branch.initSession(delegate(Dictionary<string, object> parameters, string error) { string attributionString = JsonUtility.ToJson(parameters); Adapty.UpdateAttribution( attributionString, "branch", (error) => { // handle the error }); }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import branch from 'react-native-branch'; branch.subscribe({ enComplete: ({ params, }) => { adapty.updateAttribution(params, "branch"); }, }); ``` </TabItem> </Tabs> ## Estructura de los eventos \{#event-structure\} Adapty envía los eventos seleccionados a Branch según lo configurado en la sección **Events names** de la [**página de integración de Branch**](https://app.adapty.io/integrations/branch). Cada evento tiene la siguiente estructura: ```json { "branch_key": "key_live_kaFuWw8WvY7n1ss7...", "name": "PURCHASE", "user_data": { "os": "iOS", "developer_identity": "user_12345", "country": "US", "ip": "192.168.100.1", "idfa": "00000000-0000-0000-0000-000000000000", "idfv": "00000000-0000-0000-0000-000000000000", "aaid": "00000000-0000-0000-0000-000000000000" }, "event_data": { "transaction_id": "GPA.3383-4699-1373-07113", "revenue": 9.99, "currency": "USD" }, "custom_data": { "vendor_product_id": "yearly.premium.6999", "original_transaction_id": "GPA.3383-4699-1373-07113", "store": "play_store", "environment": "production" } } ``` Donde: | Parámetro | Tipo | Descripción | |:-------------------------------|:-------|:-----------------------------------------------------------------------------------------------------------------------------------| | `branch_key` | String | Tu Branch Key. | | `name` | String | El nombre del evento de Branch (mapeado desde el evento de Adapty, p. ej., "PURCHASE"). | | `user_data` | Object | Información del usuario. | | `user_data.os` | String | "Android" o "iOS". | | `user_data.developer_identity` | String | El Customer User ID del usuario. | | `user_data.country` | String | Código de país basado en la IP del usuario. | | `user_data.ip` | String | Dirección IP del usuario. | | `user_data.idfa` | String | **Solo iOS**. ID para Anunciantes. | | `user_data.idfv` | String | **Solo iOS**. ID para Vendors. | | `user_data.aaid` | String | **Solo Android**. Google Advertising ID. | | `event_data` | Object | Métricas estándar del evento (solo presente para PURCHASE y eventos similares). | | `event_data.transaction_id` | String | ID de transacción del store. | | `event_data.revenue` | Float | Importe de ingresos. | | `event_data.currency` | String | Código de moneda (p. ej., "USD"). | | `custom_data` | Object | Atributos detallados del evento (contiene todos los [campos de evento](webhook-event-types-and-fields#for-most-event-types) disponibles). | --- # File: facebook-ads --- --- title: "Facebook Ads" description: "Integra Facebook Ads con Adapty para un marketing de suscripciones efectivo." --- Con la integración de Facebook Ads, puedes consultar fácilmente las estadísticas de tu app en Meta Analytics. Adapty envía eventos al Meta Ads Manager, lo que te ayuda a crear audiencias similares basadas en suscripciones para obtener mejores resultados. Así puedes ver con precisión cuánto dinero generan tus anuncios gracias a las suscripciones. La integración entre Adapty y Facebook Ads funciona de la siguiente manera: Adapty envía todos los eventos de suscripción configurados en tu integración a Facebook Ads. Esta integración es muy útil para evaluar la efectividad de tus campañas publicitarias. ## Configurar la integración \{#set-up-integration\} ### Conectar Adapty con Facebook Ads \{#connect-adapty-to-facebook-ads\} Para integrar Facebook Ads y analizar las métricas de tu app, puedes configurar la integración con Meta Analytics. Al enviar eventos al Meta Ads Manager, puedes crear audiencias similares basadas en eventos de suscripción como las renovaciones. Para configurar esta integración, ve a [Integrations > Facebook Ads](https://app.adapty.io/integrations/facebookanalytics) en el Adapty Dashboard y proporciona las credenciales requeridas. :::note Ten en cuenta que la integración de Facebook Ads solo funciona en iOS 14.5+ para usuarios que hayan dado su consentimiento ATT. ::: <img src="/assets/shared/img/fd84ddf-CleanShot_2023-08-15_at_15.45.442x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. Para encontrar el App ID, abre la página de tu app en [App Store Connect](https://appstoreconnect.apple.com/), ve a la página **App Information** en la sección **General** y busca el **Apple ID** en la parte inferior izquierda de la pantalla. 2. Necesitas una aplicación en la plataforma [Meta for Developers](https://developers.facebook.com/). Inicia sesión en tu app y accede a la configuración avanzada. Encontrarás el **App ID** en la cabecera. <img src="/assets/shared/img/4b326c4-001563-August-23-4tO3JVso.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Desactiva el seguimiento del lado del cliente en la configuración de tu Meta SDK para evitar el doble conteo de ingresos en Meta Ads Manager. Puedes encontrar este ajuste en tu Meta Developer Console en **App Settings > Advanced Settings**. Establece **Log in-app events automatically** en "No". Esto garantizará que los eventos de ingresos solo se registren a través de la integración de Adapty. Para rastrear eventos de instalación y uso, deberás activar el Meta SDK en tu código. Puedes encontrar los detalles de implementación en la documentación del Meta SDK para tu plataforma: - [iOS SDK](https://developers.facebook.com/docs/ios/getting-started) - [Android SDK](https://developers.facebook.com/docs/android/getting-started) - [Unity SDK](https://developers.facebook.com/docs/unity/getting-started/canvas) <img src="/assets/shared/img/c4eb8eb-001565-August-23-483KKBbC.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> También puedes usar esta integración con apps Android. Si configuras la configuración del Android SDK en **App Settings**, con introducir el **Facebook App ID** es suficiente. ### Configurar eventos y etiquetas \{#configure-events-and-tags\} Ten en cuenta que la integración de Facebook Ads está orientada específicamente a empresas que utilizan Meta para sus campañas publicitarias y las optimizan en función del comportamiento de los clientes. Es compatible con los eventos estándar de Meta para fines de optimización. Por ello, no es posible modificar el nombre del evento en la integración de Meta Ads. Adapty mapea automáticamente los eventos de tus clientes a sus correspondientes eventos de Meta para un análisis preciso. | Evento de Adapty | Evento de Meta Ads | | :---------------------------- | :-------------------------- | | Subscription initial purchase | Subscribe | | Subscription renewed | Subscribe | | Subscription cancelled | CancelSubscription | | Trial started | StartTrial | | Trial converted | Subscribe | | Trial cancelled | CancelTrial | | Non subscription purchase | fb_mobile_purchase | | Billing issue detected | billing_issue_detected | | Entered grace period | entered_grace_period | | Auto renew off | auto_renew_off | | Auto renew on | auto_renew_on | | Auto renew off subscription | auto_renew_off_subscription | | Auto renew on subscription | auto_renew_on_subscription | StartTrial, Subscribe y CancelSubscription son eventos estándar. <img src="/assets/shared/img/8a5df9d-CleanShot_2023-07-04_at_12.47.312x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Para activar eventos específicos, simplemente activa los que necesites. Si se seleccionan varios nombres de eventos, Adapty consolidará los datos de todos los eventos seleccionados en un único nombre de evento de Adapty. ### Conectar tu app con Facebook Ads \{#connect-your-app-to-facebook-ads\} Si sigues los pasos anteriores, Facebook recibirá automáticamente los datos de suscripción desde Adapty. Tras los cambios en el IDFA en iOS 14.5, recomendamos que solicites el `facebookAnonymousId` del usuario a Facebook. De este modo, si el IDFA del usuario no está disponible, la integración seguirá funcionando. Sigue la <InlineTooltip tooltip="guía para establecer atributos de usuario">[iOS](setting-user-attributes), [Android](android-setting-user-attributes), [React Native](react-native-setting-user-attributes), [Flutter](flutter-setting-user-attributes) y [Unity](unity-setting-user-attributes)</InlineTooltip> para configurar este parámetro. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "facebook_anonymous_id", value: AppEvents.shared.anonymousID ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier( "facebook_anonymous_id", AppEventsLogger.getAnonymousAppDeviceGUID(context) ) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers try { const anonymousId = await AppEventsLogger.getAnonymousID(); await adapty.setIntegrationIdentifier("facebook_anonymous_id", anonymousId); } catch (error) { // handle `AdaptyError` } ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```text There is no official SDK for Flutter ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp anonymousID is not available in the official SDK https://github.com/facebook/facebook-sdk-for-unity/issues/676 ``` </TabItem> </Tabs> ## Estructura del evento \{#event-structure\} Adapty envía eventos a Facebook Ads (Meta) a través de la Graph API. Cada evento tiene la siguiente estructura: ```json { "event": "CUSTOM_APP_EVENTS", "app_user_id": "user_12345", "advertiser_id": "00000000-0000-0000-0000-000000000000", "advertiser_tracking_enabled": 1, "application_tracking_enabled": 1, "custom_events": "[{\"_eventName\":\"Subscribe\",\"_logTime\":1709294400,\"fb_num_items\":1,\"fb_content_type\":\"in_app\",\"fb_content_id\":\"yearly.premium.6999\",\"fb_currency\":\"USD\",\"fb_order_id\":\"GPA.3383...\",\"fb_transaction_id\":\"GPA.3383...\",\"_valueToSum\":9.99}]", "extinfo": "[\"i2\",\"com.example.app\",\"1.0.0\",\"100\",\"17.0.1\",\"iPhone14,3\",\"en_US\",\"GMT+3\",\"\",0,0,0,0,0,0,\"GMT+3\"]", "anon_id": "facebook_anon_id_123" } ``` Donde: | Parámetro | Tipo | Descripción | |:---|:---|:---| | `event` | String | Siempre "CUSTOM_APP_EVENTS". | | `app_user_id` | String | El Customer User ID del usuario. | | `advertiser_id` | String | IDFA (iOS) o Advertising ID (Android). | | `advertiser_tracking_enabled` | Integer | `1` si el seguimiento está habilitado (ATT autorizado), `0` en caso contrario. | | `application_tracking_enabled` | Integer | Siempre `1`. | | `custom_events` | String | Cadena codificada en JSON con los objetos de evento (ver más abajo). | | `extinfo` | String | Cadena codificada en JSON con información de la app y el dispositivo (p. ej., versión, SO, idioma). | | `anon_id` | String | Facebook Anonymous ID (si está disponible). | El parámetro `custom_events` es un array codificado en JSON de objetos que contiene: | Parámetro | Tipo | Descripción | |:---|:---|:---| | `_eventName` | String | El nombre del evento de Meta Ads (p. ej., "Subscribe"). | | `_logTime` | Long | Marca de tiempo del evento en segundos. | | `_valueToSum` | Float | Importe de los ingresos. | | `fb_content_id` | String | El ID del producto en el store. | | `fb_currency` | String | Código de moneda (p. ej., "USD"). | | `fb_order_id` | String | ID de la transacción original. | | `fb_transaction_id` | String | ID de la transacción original. | | `fb_content_type` | String | Siempre "in_app". | | `fb_num_items` | Integer | Siempre 1 para eventos de compra. | --- # File: singular --- --- title: "Singular" description: "Integra Singular con Adapty para analizar datos de marketing y suscripciones." --- [Singular](https://www.singular.net/) es una de las principales plataformas MMP (Mobile Measurement Partner) que recopila y presenta datos de campañas de marketing, lo que ayuda a las empresas a hacer seguimiento del rendimiento de sus campañas. Adapty proporciona un conjunto completo de datos que te permite rastrear [eventos de suscripción](events) desde los stores en un solo lugar. Con Adapty, puedes ver fácilmente el comportamiento de tus suscriptores, conocer sus preferencias y usar esa información para comunicarte con ellos de forma dirigida y efectiva. Esta integración te permite, por tanto, rastrear eventos de suscripción en Singular y analizar con precisión cuántos ingresos generan tus campañas. Adapty puede enviar a Singular todos los eventos de suscripción configurados en tu integración. Como resultado, podrás hacer seguimiento de esos eventos desde el dashboard de Singular. Esta integración es especialmente útil para evaluar la efectividad de tus campañas publicitarias. ## Configurar la integración \{#set-up-integration\} ### Conectar Adapty con Singular \{#connect-adapty-to-singular\} Para configurar la integración con Singular, ve a [Integrations > Singular](https://app.adapty.io/integrations/singular) en tu Adapty Dashboard, activa el interruptor y rellena los campos. Las siguientes credenciales están disponibles: - **Singular SDK Key**: Obligatoria. La clave SDK de producción de tu app en Singular. - **Singular SDK Key (Sandbox)**: Opcional. La clave SDK para tu app de Singular en sandbox. Si no se configura, los eventos de sandbox no se enviarán a Singular. Ambas claves se encuentran en el dashboard de Singular en **Developer tools -> SDK Keys -> SDK Key (**no** SDK Secret)**: <img src="/assets/shared/img/4bc50d1-singular_sdk_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Debajo de las credenciales encontrarás tres grupos de eventos que puedes enviar a Singular desde Adapty. Consulta la lista completa de eventos que ofrece Adapty [aquí](events). <img src="/assets/shared/img/e67de0c-singular_events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Te recomendamos usar los nombres de evento predeterminados que proporciona Adapty, aunque puedes modificarlos según tus necesidades. Adapty enviará los eventos de suscripción a Singular mediante una integración server-to-server, lo que te permitirá ver todos los eventos de suscripción en tu dashboard de Singular y vincularlos con tus campañas de adquisición. :::warning Los perfiles creados antes de configurar las integraciones no podrán enviar sus eventos a Singular. ::: ### Conectar tu app con Singular \{#connect-your-app-to-singular\} La integración entre Adapty y Singular es server-to-server, por lo que no necesitas añadir ningún código adicional en tu aplicación. ## Estructura del evento \{#event-structure\} Adapty envía eventos a Singular mediante una solicitud GET con parámetros de consulta. Cada evento tiene esta estructura: ```json { "n": "subscription_renewed", "a": "singular_sdk_key_123", "p": "iOS", "i": "com.example.app", "ip": "192.168.100.1", "idfa": "00000000-0000-0000-0000-000000000000", "idfv": "00000000-0000-0000-0000-000000000000", "ve": "17.0.1", "att_authorization_status": 3, "custom_user_id": "user_12345", "utime": 1709294400, "amt": 9.99, "cur": "USD", "purchase_product_id": "yearly.premium.6999", "purchase_transaction_id": "GPA.3383...", "e": "{\"is_revenue_event\":true,\"amt\":9.99,\"cur\":\"USD\",\"purchase_product_id\":\"yearly.premium.6999\",\"purchase_transaction_id\":\"GPA.3383...\"}" } ``` Donde: | Parámetro | Tipo | Descripción | |:---------------------------|:--------|:---------------------------------------------------------------| | `n` | String | El nombre del evento (mapeado desde el evento de Adapty). | | `a` | String | Tu Singular SDK Key. | | `p` | String | Plataforma ("iOS" o "Android"). | | `i` | String | ID de la app en el store (Bundle ID). | | `ip` | String | Dirección IP del usuario. | | `idfa` | String | **Solo iOS**. ID for Advertisers (en mayúsculas). | | `idfv` | String | **Solo iOS**. ID for Vendors (en mayúsculas). | | `aifa` | String | **Solo Android**. Google Advertising ID (en minúsculas). | | `andi` | String | **Solo Android**. Android ID (en minúsculas). | | `asid` | String | **Solo Android**. App Set ID (en minúsculas). | | `ve` | String | Versión del sistema operativo. | | `att_authorization_status` | Integer | **Solo iOS**. Estado ATT (p. ej., `3` para autorizado). | | `custom_user_id` | String | El Customer User ID del usuario. | | `utime` | Long | Marca de tiempo UNIX del evento en segundos. | | `amt` | Float | Importe de los ingresos. | | `cur` | String | Código de moneda (p. ej., "USD"). | | `purchase_product_id` | String | El ID del producto en el store. | | `purchase_transaction_id` | String | ID de transacción original. | | `e` | String | Cadena JSON con los detalles del evento (ver más abajo). | El parámetro `e` (datos de evento personalizados) es una cadena codificada en JSON que contiene: | Parámetro | Tipo | Descripción | |:--------------------------|:--------|:-----------------------------------------------| | `is_revenue_event` | Boolean | `true` si el evento incluye ingresos. | | `amt` | Float | Importe de los ingresos. | | `cur` | String | Código de moneda. | | `purchase_product_id` | String | El ID del producto en el store. | | `purchase_transaction_id` | String | ID de transacción original. | --- # File: tenjin --- --- title: "Integración con Tenjin" description: "" --- Tenjin es una plataforma de atribución y análisis móvil para desarrolladores de apps y marketers. Proporciona herramientas para medir y optimizar campañas de adquisición de usuarios, ofreciendo información detallada sobre el rendimiento de la app y el comportamiento de los usuarios. Con su enfoque transparente y flexible, Tenjin agrega datos de redes publicitarias y stores de aplicaciones, lo que permite a los equipos analizar el ROI, rastrear conversiones y monitorear métricas clave de rendimiento. Al reenviar [eventos de suscripción](events) a Tenjin, puedes ver exactamente de dónde provienen las conversiones y qué campañas generan más valor en todos los canales, plataformas y dispositivos. En esencia, los dashboards de Tenjin ofrecen analíticas avanzadas para campañas de marketing. Al reenviar la atribución de Tenjin a Adapty, enriqueces las analíticas de Adapty con criterios de filtrado adicionales que puedes usar en análisis de cohortes y conversiones. Esta integración funciona de dos maneras principales: 1. **Recibir datos de atribución de Tenjin** Una vez integrado, Adapty recopila datos de atribución de Tenjin. Puedes consultar esta información en la página de perfil del usuario en el Adapty Dashboard. 2. **Enviar eventos de suscripción a Tenjin** Adapty envía eventos de compra a Tenjin en tiempo real. Estos eventos ayudan a evaluar la efectividad de tus campañas publicitarias directamente en el dashboard de Tenjin. | Característica de integración | Descripción | | ----------------------------- | ------------------------------------------------------------ | | Frecuencia | Tiempo real | | Dirección de datos | <p>Transmisión bidireccional:</p><ul><li> **Eventos de Adapty**: Del servidor de Adapty al servidor de Tenjin</li><li> **Atribución de Tenjin**: Del SDK de Tenjin al servidor de Adapty</li></ul> | | Punto de integración de Adapty | <ul><li> SDKs de Tenjin y Adapty en el código de la app</li><li> Servidor de Adapty</li></ul> | ## Configurar la integración \{#set-up-integration\} ### Conectar Adapty con Tenjin 1. Abre la página [**Integrations** -> **Tenjin**](https://app.adapty.io/integrations/tenjin) en el Adapty Dashboard. 2. Activa el toggle para habilitar la integración. <img src="/assets/shared/img/tenjin-toggle.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Inicia sesión en el [Tenjin Dashboard](https://tenjin.com/). 4. Ve a **Configuration** -> **Apps** en el menú de navegación. <img src="/assets/shared/img/tenjin-apps.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Selecciona la app de tu plataforma (iOS o Android) y ve a la pestaña **App and SDK**. 6. En la pestaña **App and SDK**, haz clic en **Copy** en la columna **SDK Key**. Si todavía no tienes una SDK key, haz clic en el botón **Generate SDK Key** para crear una. <img src="/assets/shared/img/tenjin-copy-sdk-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Vuelve al Adapty Dashboard y pega el SDK Key copiado en el campo correspondiente a tu plataforma: - Para apps iOS: pégalo en el campo **iOS SDK Key** o **iOS Sandbox SDK Key** - Para apps Android: pégalo en el campo **Android SDK Key** o **Android Sandbox SDK Key** :::info Tenjin no dispone de un modo Sandbox específico para la integración server-to-server. Usa una app de Tenjin separada o la misma clave tanto para eventos de producción como de sandbox. ::: <img src="/assets/shared/img/tenjin-keys.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. Si tienes apps en ambas plataformas, repite los pasos 5-7 para la otra plataforma. 9. (opcional) Ajusta la sección **How the revenue data should be sent** si es necesario. Para una explicación detallada de sus opciones, consulta los [Ajustes de integración](configuration#integration-settings). 10. Haz clic en **Save** para finalizar la configuración. Adapty enviará ahora los eventos de compra a Tenjin y recibirá datos de atribución. Puedes ajustar el intercambio de eventos en la sección **Events names**. ### Configurar eventos y etiquetas \{#configure-events-and-tags\} Tenjin solo acepta eventos de compra y **Trial started**. En la sección **Events names**, selecciona qué eventos compartir con Tenjin según tus objetivos de seguimiento. <img src="/assets/shared/img/tenjin-events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Conecta tu app a Tenjin \{#connect-your-app-to-tenjin\} Usa el método del SDK `Adapty.updateAttribution()` para obtener datos de atribución de Tenjin y enviarlos a Adapty. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers func updateTenjinId() { guard let tenjinId = TenjinSDK.getAnalyticsInstallationId() else { return } do { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.tenjinAnalyticsInstallationId(tenjinId)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "tenjin_analytics_installation_id", value: tenjinId ) } catch { // handle the error } } func updateTenjinAttribution() { let instance = TenjinSDK.getInstance("<YOUR_TENJIN_API_TOKEN>") instance?.getAttributionInfo { info, _ in guard let info else { return } Task { do { // Adapty SDK 4.x try await Adapty.updateAttribution(info, source: .tenjin) // Adapty SDK 3.x try await Adapty.updateAttribution(info, source: "tenjin") } catch { // handle the error } } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("tenjin_analytics_installation_id", tenjinSdk.analyticsInstallationId) { error -> if (error != null) { // handle the error } } tenjinSdk.getAttributionInfo { attribution -> if (attribution == null) return@getAttributionInfo Adapty.updateAttribution(attribution, "tenjin") { error -> if (error != null) { // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.setIntegrationIdentifier("tenjin_analytics_installation_id", tenjinSdk.getAnalyticsInstallationId(), error -> { if (error != null) { // handle the error } }); tenjinSdk.getAttributionInfo(attribution -> { if (attribution == null) return; Adapty.updateAttribution(attribution, "tenjin", error -> { // handle the error }); }); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers try { final tenjinId = await TenjinSDK.instance.getAnalyticsInstallationId(); if (tenjinId != null) { await Adapty().setIntegrationIdentifier( key: 'tenjin_analytics_installation_id', value: tenjinId, ); } final attribution = await TenjinSDK.instance.getAttributionInfo(); if (attribution != null) { await Adapty().updateAttribution(attribution, source: 'tenjin'); } } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; using System.Linq; BaseTenjin instance = Tenjin.getInstance("<SDK_KEY>"); var tenjinId = instance.GetAnalyticsInstallationId(); Adapty.SetIntegrationIdentifier( "tenjin_analytics_installation_id", tenjinId, (error) => { // handle the error }); instance.GetAttributionInfo((attribution) => { var dynamicAttribution = attribution.ToDictionary( kvp => kvp.Key, kvp => (dynamic)kvp.Value ); Adapty.UpdateAttribution( dynamicAttribution, "tenjin", (error) => { // handle the error }); }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // ... const posthog = usePostHog() // ... try { await adapty.setIntegrationIdentifier("tenjin_analytics_installation_id", await Tenjin.getAnalyticsInstallationId()); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ## Estructura del evento \{#event-structure\} Adapty envía los eventos seleccionados a Tenjin según la configuración de la sección **Events names** en la [**página de integración de Tenjin**](https://app.adapty.io/integrations/tenjin). Cada evento tiene la siguiente estructura: ```json showLineNumbers title="Json" { "price": 99.0, "locale": "en-US", "country": "ME", "postcut": "false", "currency": "USD", "platform": "ios", "quantity": 1, "bundle_id": "com.adapty.adaptydemoapp", "ip_address": "127.0.0.1", "os_version": "18.1.1", "product_id": "month.premium.99", "app_version": "3.2.0", "sdk_version": "server", "device_model": "iPhone 13 Mini", "advertising_id": "00000000-0000-0000-0000-000000000000", "os_version_release": "18.1.1", "developer_device_id": "00000000-0000-0000-0000-000000000000", "analytics_installation_id": "00000000-0000-0000-0000-000000000000" } ``` Where | **Parámetro** | **Tipo** | **Descripción** | | ----------------------------- | ---------------- | ------------------------------------------------------------ | | **price** | Float | El precio unitario del artículo comprado en la unidad estándar de la divisa (p. ej., USD se reporta en dólares). | | **locale** | String | El locale del dispositivo. Para Android: `Locale.getDefault().toString()`. Para iOS: `[[NSLocale currentLocale] localeIdentifier]`. | | **country** | String | El código de país según el estándar de locale ISO (p. ej., US para Estados Unidos). | | **postcut** | String (Boolean) | Indica si la compra se envió después del recorte de la plataforma. 1 para verdadero, 0 para falso. | | **currency** | String | El código de divisa ISO (p. ej., USD para dólares estadounidenses). | | **platform** | String | La plataforma del dispositivo (p. ej., ios, android, windows, amazon). | | **quantity** | Integer | El número de unidades compradas. | | **bundle_id** | String | El identificador de bundle de la app (p. ej., `com.example.app`). | | **ip_address** | String (IPv4) | La dirección IP del usuario. Se utiliza para determinar el país. | | **os_version** | String | La versión del sistema operativo del dispositivo. Para Android: `String.valueOf(Build.VERSION.SDK_INT)`. Para iOS: `[[UIDevice currentDevice] systemVersion]`. | | **product_id** | String | Identificador único del producto comprado. | | **app_version** | Float, Decimal | La versión de la app. Para Android: `context.getPackageManager().getPackageInfo()`. Para iOS: `[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]`. | | **sdk_version** | String | La versión del SDK en uso, siempre establecida en `server`. | | **device_model** | String | El modelo del dispositivo. Para Android: `Build.MODEL`. Para iOS: `sysctl("hw.machine")`. | | **advertising_id** | UUID | El ID de publicidad del dispositivo. Obligatorio para Android. En iOS puede estar vacío o ser todo ceros. | | **os_version_release** | String | La versión de lanzamiento del sistema operativo. Para Android: `String.valueOf(Build.VERSION.RELEASE)`. Para iOS: `[[UIDevice currentDevice] systemVersion]`. | | **developer_device_id** | UUID | El identificador del proveedor (solo iOS). | | **analytics_installation_id** | UUID | ID de instalación de analíticas. Para más detalles, consulta la documentación en `https://docs.tenjin.com`. | --- # File: amplitude --- --- title: "Amplitude" description: "Integra Amplitude con Adapty para obtener mejores insights sobre el comportamiento de los usuarios." --- [Amplitude](https://amplitude.com/) es un potente servicio de analítica para móviles. Con Adapty, puedes enviar eventos a Amplitude fácilmente, ver cómo se comportan los usuarios y tomar decisiones inteligentes. Adapty ofrece un conjunto completo de datos que te permite rastrear [eventos de suscripción](events) desde los stores en un solo lugar y enviarlos a tu cuenta de Amplitude. Esto te permite correlacionar el comportamiento de tus usuarios con su historial de pagos en Amplitude, y basar tus decisiones de producto en datos reales. ### Cómo configurar la integración con Amplitude \{#how-to-set-up-amplitude-integration\} En Adapty puedes configurar flujos separados para **eventos de producción** y **de prueba** provenientes del entorno sandbox de Apple o Stripe, o de una cuenta de prueba de Google. - Para eventos de producción, introduce las claves API de **Production** desde el dashboard de Amplitude, con una clave API única para cada plataforma: iOS, Android y Stripe. - Para eventos de prueba, usa los campos de **Sandbox** según sea necesario. Para configurar la integración con Amplitude: 1. Abre [**Integrations** -> **Amplitude**](https://app.adapty.io/integrations/amplitude) en tu Adapty Dashboard. <img src="/assets/shared/img/3b50552-CleanShot_2023-08-15_at_16.47.102x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Activa **Amplitude integration** para habilitarla. 3. Rellena los campos de la integración: | Campo | Descripción | | ------------------------------------------ | ------------------------------------------------------------ | | **Amplitude iOS/ Android/ Stripe API key** | Introduce la **API Key** de Amplitude para iOS/ Android/ Stripe en Adapty. Encuéntrala en **Project settings** dentro de Amplitude. Para más ayuda, consulta la [documentación de Amplitude](https://amplitude.com/docs/apis/authentication). Comienza con las claves de **Sandbox** para pruebas y luego cambia a las claves de **Production** tras pruebas exitosas. | <img src="/assets/shared/img/2297782-CleanShot_2023-08-15_at_16.53.512x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Ajustes opcionales para mayor personalización: | Parámetro | Descripción | | --------------------------------------- | ------------------------------------------------------------ | | **How the revenue data should be sent** | Elige si enviar los ingresos brutos o los ingresos después de impuestos y comisiones. Consulta [Comisión del store e impuestos](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue) para más detalles. | | **Exclude historical events** | Elige excluir los eventos anteriores a la instalación del SDK de Adapty para evitar datos duplicados. Por ejemplo, si un usuario se suscribió el 10 de enero pero instaló el SDK de Adapty el 6 de marzo, Adapty solo enviará eventos a partir del 6 de marzo. | | **Send User Attributes** | Selecciona esta opción para enviar atributos específicos del usuario, como preferencias de idioma. | | **Always populate user_id** | Adapty envía automáticamente `device_id` como `amplitudeDeviceId`. Para `user_id`, esta configuración define el comportamiento: <ul><li>**ON**: Envía el `profile_id` de Adapty si `amplitudeUserId` o `customer_user_id` no están disponibles.</li><li>**OFF**: Deja `user_id` vacío si ninguno de los IDs está disponible.</li></ul> | 5. Elige los eventos que deseas recibir y [asigna sus nombres](amplitude#events-and-tags). 6. Haz clic en **Save** para confirmar los cambios. Una vez que hagas clic en **Save**, Adapty comenzará a enviar eventos a Amplitude. Además de los eventos, Adapty envía el [estado de la suscripción](subscription-status) y el ID del producto de suscripción a las [propiedades de usuario de Amplitude](https://amplitude.com/docs/data/user-properties-and-events). ### Eventos y etiquetas \{#events-and-tags\} Debajo de las credenciales encontrarás tres grupos de eventos que puedes enviar a Amplitude desde Adapty. Simplemente activa los que necesites. Consulta la lista completa de eventos disponibles en Adapty [aquí](events). <img src="/assets/shared/img/da67694-CleanShot_2023-08-15_at_16.52.352x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Recomendamos usar los nombres de eventos predeterminados que proporciona Adapty. No obstante, puedes cambiarlos según tus necesidades. Adapty enviará los eventos de suscripción a Amplitude mediante una integración servidor a servidor, lo que te permitirá ver todos los eventos de suscripción en tu dashboard de Amplitude. ### Configuración del SDK \{#sdk-configuration\} Usa el método `setIntegrationIdentifier()` para establecer el parámetro `amplitude_device_id`. Es imprescindible configurarlo para que la integración funcione. Si tienes registro de usuarios, también puedes pasar `amplitude_user_id`. :::note Los SDKs de terceros generan los IDs de usuario de forma asíncrona. Es posible que el ID no esté disponible cuando se ejecuta `Adapty.activate()`. Si tu **Customer User ID** proviene de uno de estos SDKs, llama a `Adapty.activate()` sin él. Una vez que el ID esté disponible, llama a `setIntegrationIdentifier()` y luego a `identify()` con el CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="Swift" label="iOS (Swift)" default> **Configurar amplitudeDeviceId** ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "amplitude_device_id", value: Amplitude.instance().deviceId ) } catch { // handle the error } ``` **Configurar amplitudeUserId** ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "amplitude_user_id", value: "YOUR_AMPLITUDE_USER_ID" ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> **Configurar amplitudeDeviceId** ```kotlin showLineNumbers //for Amplitude maintenance SDK (obsolete) val amplitude = Amplitude.getInstance() val amplitudeDeviceId = amplitude.getDeviceId() val amplitudeUserId = amplitude.getUserId() //for actual Amplitude Kotlin SDK val amplitude = Amplitude( Configuration( apiKey = AMPLITUDE_API_KEY, context = applicationContext ) ) val amplitudeDeviceId = amplitude.store.deviceId // Adapty.setIntegrationIdentifier("amplitude_device_id", amplitudeDeviceId) { error -> if (error != null) { // handle the error } } ``` **Configurar amplitudeUserId** ```kotlin showLineNumbers //for Amplitude maintenance SDK (obsolete) val amplitude = Amplitude.getInstance() val amplitudeDeviceId = amplitude.getDeviceId() val amplitudeUserId = amplitude.getUserId() //for actual Amplitude Kotlin SDK val amplitude = Amplitude( Configuration( apiKey = AMPLITUDE_API_KEY, context = applicationContext ) ) val amplitudeUserId = amplitude.store.userId // Adapty.setIntegrationIdentifier("amplitude_user_id", amplitudeUserId) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="Flutter" label="Flutter (Dart)" default> **Configurar amplitudeDeviceId** ```javascript showLineNumbers final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); try { await Adapty().setIntegrationIdentifier( key: "amplitude_device_id", value: amplitude.getDeviceId(), ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` **Configurar amplitudeUserId** ```javascript showLineNumbers final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); try { await Adapty().setIntegrationIdentifier( key: "amplitude_user_id", value: "YOUR_AMPLITUDE_USER_ID", ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="Unity" label="Unity (C#)" default> **Configurar amplitudeDeviceId** ```csharp showLineNumbers using AdaptySDK; Adapty.SetIntegrationIdentifier( "amplitude_device_id", amplitude.getDeviceId(), (error) => { // handle the error }); ``` **Configurar amplitudeUserId** ```csharp showLineNumbers using AdaptySDK; Adapty.SetIntegrationIdentifier( "amplitude_user_id", "YOUR_AMPLITUDE_USER_ID", (error) => { // handle the error }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> **Configurar amplitudeDeviceId** ```typescript showLineNumbers try { await adapty.setIntegrationIdentifier("amplitude_device_id", deviceId); } catch (error) { // handle `AdaptyError` } ``` **Configurar amplitudeUserId** ```typescript showLineNumbers try { await adapty.setIntegrationIdentifier("amplitude_user_id", userId); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ## Estructura del evento de Amplitude \{#amplitude-event-structure\} Adapty envía eventos a Amplitude a través de la HTTP API v2. Cada evento tiene la siguiente estructura: ```json { "api_key": "your_amplitude_api_key", "events": [ { "partner_id": "adapty", "event_type": "subscription_renewed", "time": 1709294400000, "insert_id": "123e4567-e89b-12d3-a456-426614174000", "user_id": "user_12345", "device_id": "device_12345", "platform": "iOS", "os_name": "iOS", "productId": "yearly.premium.6999", "revenue": 9.99, "event_properties": { "vendor_product_id": "yearly.premium.6999", "original_transaction_id": "GPA.3383...", "currency": "USD", "environment": "Production", "store": "app_store" }, "user_properties": { "subscription_state": "subscribed", "subscription_product": "yearly.premium.6999" } } ] } ``` Donde: | Parámetro | Tipo | Descripción | |:----------------------------|:-------|:---------------------------------------------------------------------| | `api_key` | String | Tu clave API de Amplitude. | | `events` | Array | Lista de objetos de evento (Adapty envía uno a la vez). | | `events[].partner_id` | String | Siempre "adapty". | | `events[].event_type` | String | El nombre del evento (mapeado desde el evento de Adapty). | | `events[].time` | Long | Marca de tiempo del evento en milisegundos. | | `events[].insert_id` | String | ID único del evento (UUID). | | `events[].user_id` | String | ID de usuario de Amplitude o ID de usuario del cliente. | | `events[].device_id` | String | ID del dispositivo en Amplitude. | | `events[].platform` | String | Plataforma (p. ej., "iOS", "Android"). | | `events[].os_name` | String | Nombre del sistema operativo. | | `events[].productId` | String | El ID del producto en el store. | | `events[].revenue` | Float | Importe de los ingresos. | | `events[].event_properties` | Object | Atributos detallados del evento (contiene todos los [campos de evento](webhook-event-types-and-fields#for-most-event-types) disponibles). | | `events[].user_properties` | Object | Atributos del usuario, como el estado de la suscripción. | --- # File: appmetrica --- --- title: "AppMetrica" description: "Integra AppMetrica con Adapty para un análisis detallado de suscripciones." --- [AppMetrica](https://appmetrica.yandex.com/about) es una herramienta de análisis gratuita que te ayuda a rastrear el comportamiento de los usuarios y analizar el rendimiento de tu app móvil en tiempo real. Al integrar AppMetrica con Adapty, puedes obtener información más detallada sobre tus métricas de suscripción y la interacción de los usuarios. ## Cómo configurar la integración con AppMetrica \{#how-to-set-up-appmetrica-integration\} La configuración de la integración con AppMetrica consta de dos pasos principales: 1. Configurar la integración en el Adapty Dashboard 2. Configurar la integración en el código de tu app ### Configuración en el dashboard \{#dashboard-configuration\} Para configurar la integración con AppMetrica: 1. Abre la [lista de apps de AppMetrica](https://appmetrica.yandex.ru/application/list) 2. Selecciona la app que quieres rastrear 3. Ve a **Settings > Main** y copia el **Application ID** y la **Post API key** <img src="/assets/shared/img/appmetrica.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Ve a [Integrations > AppMetrica](https://app.adapty.io/integrations/appmetrica) en el Adapty Dashboard 5. Pega tus credenciales de AppMetrica. <img src="/assets/shared/img/appmetrica_creds.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Eventos y etiquetas \{#events-and-tags\} Adapty te permite enviar tres grupos de eventos a AppMetrica. Puedes habilitar los eventos que necesitas para rastrear el rendimiento de tu app. Para ver la lista completa de eventos disponibles, consulta nuestra [documentación de eventos](events). :::note AppMetrica sincroniza los eventos cada 4 horas, por lo que puede haber un retraso antes de que los eventos aparezcan en tu dashboard. ::: <img src="/assets/shared/img/6ed2d88-CleanShot_2023-08-18_at_14.59.042x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::tip Recomendamos usar los nombres de eventos predeterminados de Adapty para mantener la coherencia, aunque puedes personalizarlos para que coincidan con tu configuración de análisis existente. ::: ### Configuración de ingresos \{#revenue-settings\} De forma predeterminada, Adapty envía los datos de ingresos como propiedades en los eventos, que aparecen en el informe de Events de AppMetrica. Puedes configurar cómo se calculan y muestran estos datos: - **Revenue calculation**: Elige cómo se calculan los valores de ingresos para que coincidan con tus necesidades de informes financieros: - **Gross revenue**: Muestra los ingresos totales antes de cualquier deducción, útil para rastrear el importe completo que pagan los clientes - **Proceeds after store commission**: Muestra los ingresos después de deducir las comisiones de App Store/Play Store, lo que te ayuda a rastrear los ingresos reales - **Proceeds after store commission and taxes**: Muestra los ingresos netos después de las comisiones del store y los impuestos aplicables, lo que ofrece la imagen más precisa de tus ganancias - **Report user's currency**: Cuando está habilitado, las ventas se informan en la moneda local del usuario, lo que facilita el análisis de ingresos por región. Cuando está deshabilitado, todas las ventas se convierten a USD para mantener informes coherentes en diferentes mercados. - **Send revenue events**: Habilita esta opción para que los datos de ingresos aparezcan no solo en el informe de Events, sino también en el informe [In-app and ad revenue](https://appmetrica.yandex.com/docs/en/mobile-reports/revenue-report) de AppMetrica. Asegúrate de no enviar ingresos desde ningún otro lugar, ya que esto podría generar duplicados. - **Exclude historical events**: Cuando está habilitado, Adapty no enviará eventos que ocurrieron antes de que el usuario instalara la app con el SDK de Adapty. Esto ayuda a evitar la duplicación de datos si ya estabas enviando eventos a analytics antes de integrar Adapty. <img src="/assets/shared/img/appmetrica_revenue.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Configuración del SDK \{#sdk-configuration\} Para habilitar la integración con AppMetrica en tu app, necesitas configurar dos identificadores: 1. `appmetrica_device_id`: Necesario para la integración básica 2. `appmetrica_profile_id`: Opcional, pero recomendado si tu app tiene registro de usuarios Usa el método `setIntegrationIdentifier()` para establecer estos valores. A continuación se muestra cómo implementarlo en cada plataforma: :::note Los SDKs de terceros generan los IDs de usuario de forma asíncrona. Es posible que el ID no esté disponible cuando se ejecuta `Adapty.activate()`. Si tu **Customer User ID** proviene de uno de estos SDKs, llama a `Adapty.activate()` sin él. Una vez que el ID esté disponible, llama a `setIntegrationIdentifier()` y luego a `identify()` con el CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="Swift" label="iOS (Swift)" default> **Setting appmetrica_device_id** ```swift showLineNumbers AppMetrica.requestStartupIdentifiers(on: nil) { ids, error in if let error { // handle AppMetrica error return } guard let deviceIDHash = ids?[.deviceIDHashKey] as? String else { // handle AppMetrica error return } Task { do { try await Adapty.setIntegrationIdentifier( key: "appmetrica_device_id", value: deviceIDHash ) } catch { // handle the error } } } ``` **Setting appmetrica_profile_id** ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "appmetrica_profile_id", value: "YOUR_APPMETRICA_PROFILE_ID" ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> **Setting appmetrica_device_id** ```kotlin showLineNumbers val startupParamsCallback = object: StartupParamsCallback { override fun onReceive(result: StartupParamsCallback.Result?) { val deviceIdHash = result?.deviceIdHash ?: return Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceIdHash) { error -> if (error != null) { // handle the error } } } override fun onRequestError( reason: StartupParamsCallback.Reason, result: StartupParamsCallback.Result? ) { //handle the error } } AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID_HASH)) ``` **Setting appmetrica_profile_id** ```kotlin showLineNumbers val startupParamsCallback = object: StartupParamsCallback { override fun onReceive(result: StartupParamsCallback.Result?) { val deviceIdHash = result?.deviceIdHash ?: return Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceIdHash) { error -> if (error != null) { // handle the error } } Adapty.setIntegrationIdentifier("appmetrica_profile_id", "YOUR_ADAPTY_CUSTOMER_USER_ID") { error -> if (error != null) { // handle the error } } } override fun onRequestError( reason: StartupParamsCallback.Reason, result: StartupParamsCallback.Result? ) { //handle the error } } AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID_HASH)) ``` </TabItem> <TabItem value="Flutter" label="Flutter (Dart)" default> **Setting appmetrica_device_id** ```javascript showLineNumbers final startupParams = await AppMetrica.requestStartupParams([AppMetricaStartupParams.deviceIdHashKey]); final deviceIdHash = startupParams.result?.deviceIdHash; if (deviceIdHash != null) { try { await Adapty().setIntegrationIdentifier( key: "appmetrica_device_id", value: deviceIdHash, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` **Setting appmetrica_profile_id** ```javascript showLineNumbers try { await Adapty().setIntegrationIdentifier( key: "appmetrica_profile_id", value: "YOUR_APPMETRICA_PROFILE_ID", ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="Unity" label="Unity (C#)" default> **Setting appmetrica_device_id** ```csharp showLineNumbers using AdaptySDK; using Io.AppMetrica; AppMetrica.RequestStartupParams( (result, errorReason) => { string deviceIdHash = result.DeviceIdHash; if (deviceIdHash != null) { Adapty.SetIntegrationIdentifier( "appmetrica_device_id", deviceIdHash, (error) => { // handle the error }); } }, new List<string>() { StartupParamsKey.AppMetricaDeviceIDHash } ); ``` **Setting appmetrica_profile_id** ```csharp showLineNumbers Adapty.SetIntegrationIdentifier( "appmetrica_profile_id", "YOUR_APPMETRICA_PROFILE_ID", (error) => { // handle the error }); ``` </TabItem> <TabItem value="RN" label="React Native (TS)" default> **Setting appmetrica_device_id** ```typescript showLineNumbers // ... const startupParamsCallback = async ( params?: StartupParams, reason?: StartupParamsReason ) => { const deviceIdHash = params?.deviceIdHash if (deviceIdHash) { try { await adapty.setIntegrationIdentifier("appmetrica_device_id", deviceIdHash); } catch (error) { // handle `AdaptyError` } } } AppMetrica.requestStartupParams(startupParamsCallback, [DEVICE_ID_HASH_KEY]) ``` **Setting appmetrica_profile_id** ```typescript showLineNumbers try { await adapty.setIntegrationIdentifier("appmetrica_profile_id", 'YOUR_ADAPTY_CUSTOMER_USER_ID'); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ## Estructura de eventos de AppMetrica \{#appmetrica-event-structure\} Adapty envía eventos a AppMetrica mediante solicitudes POST con parámetros pasados como parámetros de consulta. Para cada evento de Adapty, AppMetrica recibe hasta **dos solicitudes separadas**: 1. **Evento de perfil** (siempre enviado): Contiene metadatos del evento 2. **Evento de ingresos** (opcional): Contiene datos de ingresos si la opción "Send revenue events" está habilitada en el Adapty Dashboard ### Solicitud de evento de perfil \{#profile-event-request\} Se envía a: `https://api.appmetrica.yandex.ru/logs/v1/import/events` Ejemplo de URL con parámetros de consulta: ``` POST https://api.appmetrica.yandex.ru/logs/v1/import/events?post_api_key=your_key&application_id=your_app_id&event_name=subscription_renewed&event_timestamp=1709294400&event_json=%7B%22vendor_product_id%22%3A%22yearly.premium%22...%7D&os_name=ios&ios_ifa=00000000-0000-0000-0000-000000000000&ios_ifv=12345678-1234-1234-1234-123456789012&profile_id=user_12345&session_type=foreground ``` Parámetros de consulta: | Parámetro | Tipo | Descripción | |:-----------------------|:-------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `post_api_key` | String | Tu Post API Key de AppMetrica. | | `application_id` | String | Tu Application ID de AppMetrica. | | `event_name` | String | El nombre del evento (mapeado desde el evento de Adapty). | | `event_timestamp` | Long | Marca de tiempo UNIX del evento en segundos. Se limita a los últimos 7 días si es más antiguo. | | `event_json` | String | Cadena JSON codificada en URL que contiene todos los [campos del evento](webhook-event-types-and-fields#for-most-event-types) disponibles. Solo se incluyen los campos no nulos. | | `os_name` | String | "ios" o "android". | | `profile_id` | String | AppMetrica Profile ID (si está configurado), en caso contrario el Customer User ID (si está disponible). | | `appmetrica_device_id` | String | AppMetrica Device ID Hash. Solo se envía si `profile_id` no está disponible. | | `session_type` | String | Siempre "foreground". | | `ios_ifa` | String | **Solo iOS**. ID for Advertisers. | | `ios_ifv` | String | **Solo iOS**. ID for Vendors. | | `google_aid` | String | **Solo Android**. Google Advertising ID. | ### Solicitud de evento de ingresos (opcional) \{#revenue-event-request-optional\} Se envía a: `https://api.appmetrica.yandex.ru/logs/v1/import/revenue` Esta solicitud solo se envía cuando la opción "Send revenue events" está habilitada en la configuración de la integración en tu Adapty Dashboard. Ejemplo de URL con parámetros de consulta: ``` POST https://api.appmetrica.yandex.ru/logs/v1/import/revenue?post_api_key=your_key&application_id=your_app_id&revenue_event_type=subscription_renewed&price=9.99¤cy=USD&product_id=yearly.premium&quantity=1&transaction_id=GPA.3383...&payload=%7B%22vendor_product_id%22%3A%22yearly.premium%22...%7D&os_name=ios&ios_ifa=00000000-0000-0000-0000-000000000000&profile_id=user_12345&session_type=foreground ``` Parámetros de consulta: | Parámetro | Tipo | Descripción | |:-----------------------|:--------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `post_api_key` | String | Tu Post API Key de AppMetrica. | | `application_id` | String | Tu Application ID de AppMetrica. | | `revenue_event_type` | String | El tipo de evento de ingresos (por ejemplo, "subscription_renewed", "refund", "intro_started"). Consulta el [mapeo de eventos de AppMetrica](#revenue-event-type-mapping). | | `price` | Float | Importe de ingresos (según tu configuración de cálculo de ingresos). | | `currency` | String | Código de moneda (por ejemplo, "USD"). | | `product_id` | String | El ID del producto en el store. | | `quantity` | Integer | Siempre 1. | | `transaction_id` | String | ID de transacción del store. | | `payload` | String | Cadena JSON codificada en URL que contiene los detalles del evento. Se recorta automáticamente si supera los 30 KB eliminando campos opcionales por orden de importancia para preservar los datos más críticos. | | `os_name` | String | "ios" o "android". | | `profile_id` | String | AppMetrica Profile ID (si está configurado), en caso contrario el Customer User ID (si está disponible). | | `appmetrica_device_id` | String | AppMetrica Device ID Hash. Solo se envía si `profile_id` no está disponible. | | `session_type` | String | Siempre "foreground". | | `ios_ifa` | String | **Solo iOS**. ID for Advertisers. | | `ios_ifv` | String | **Solo iOS**. ID for Vendors. | | `google_aid` | String | **Solo Android**. Google Advertising ID. | --- # File: firebase-and-google-analytics --- --- title: "Firebase y Google Analytics" description: "Envía eventos de suscripción de Adapty a Firebase y Google Analytics — potencia Audiences, Remote Config, atribución de Google Ads y otras herramientas de Firebase." --- Adapty puede enviar eventos de suscripción — compras, renovaciones, reembolsos, inicios de prueba — a Firebase y Google Analytics, de modo que una sola integración entrega datos a ambos. :::warning Necesitas tanto un proyecto de Firebase como una propiedad de Google Analytics vinculada, incluso si solo usas uno. Firebase y Google Analytics son los mismos datos en dos consolas distintas. ::: Los eventos de compra y reembolso llegan con los datos de ingresos, moneda y producto adjuntos. Esos mismos datos alimentan las herramientas móviles de Firebase (Audiencias, Remote Config, etc.) y los informes de Google Analytics. Esta es una integración de análisis, no una herramienta de atribución de Google Ads; consulta [Limitaciones](#limitations). ## Qué puedes hacer con esta integración \{#what-you-can-do-with-this-integration\} Adapty agrupa a los usuarios por `subscription_state` (`subscribed`, `active_trial`, `never_subscribed`, etc.) y reenvía los eventos del ciclo de vida de la suscripción a Firebase y Google Analytics. - **Audiences**: Crea audiencias de suscriptores en Firebase y Google Analytics para canales externos: retargeting en Google Ads, campañas de FCM y modelado de audiencias similares. - **Conversiones de Google Ads** *(Google Analytics)*: Usa `purchase` y `refund` como objetivos de conversión en Google Ads. - **Firebase Remote Config**: Cambia límites de uso, textos o feature flags sin actualizar la app, condicionándolos al estado de la suscripción. (No confundir con [Adapty Remote Config](customize-paywall-with-remote-config), que configura el contenido de flows y paywalls.) - **Cloud Messaging**: Notificaciones push a suscriptores inactivos cuando la app está cerrada. - **Seguimiento entre dispositivos** *(Google Analytics)*: Adapty envía `customer_user_id` a Google Analytics para que Google Ads pueda rastrear a la misma persona en distintos dispositivos. - **Embudos de conversión** *(Google Analytics)*: Descubre qué hicieron los usuarios en tu app antes de convertir o darse de baja. - **Predicciones**: Predice la pérdida de usuarios y el gasto usando el historial de compras. - **Pruebas A/B**: Prueba funcionalidades de la app en cohortes de suscriptores; por ejemplo, lanza un nuevo patrón de navegación a usuarios en período de prueba y mide la duración de sesión. (Para variantes de paywall, usa [Pruebas A/B de Adapty](ab-tests).) ## Cómo funciona la integración \{#how-the-integration-works\} 1. Cuando un usuario abre tu app por primera vez, el SDK de Firebase crea un identificador único para la instalación: el **Firebase App Instance ID**. Firebase y Google Analytics usan este ID para identificar la instalación responsable de cada evento. 2. Tu app pasa el Firebase App Instance ID al SDK de Adapty. Adapty vincula el perfil del usuario a esta instalación de Firebase. 3. Cuando el usuario realiza una compra, los servidores de Adapty reenvían el evento a Firebase con el Firebase App Instance ID adjunto. El intercambio de datos se produce de servidor a servidor, fuera de la app. 4. Firebase asocia la compra a la instalación, lo que te permite ver las compras junto con todo lo que el usuario hizo en la app. :::note Un Firebase App Instance ID es específico del dispositivo. Cuando el mismo usuario de Adapty abre la app en otro dispositivo, el nuevo Firebase ID sobreescribe el anterior. Usa un [customer user ID](identifying-users) para mantener la identidad del usuario en todos los dispositivos. ::: ### Compras de Stripe \{#stripe-purchases\} Una compra de Stripe llega a Firebase solo si el comprador abrió tu aplicación móvil **primero**. El Firebase App Instance ID debe establecerse **antes** de que se procese la compra de Stripe. En las compras de App Store y Play Store esto ocurre de forma automática. Se originan dentro de tu aplicación móvil, junto con la llamada a [`setIntegrationIdentifier`](#configure-your-app-code). El ID de Firebase ya está presente cuando se efectúa la compra. Las compras de Stripe se originan fuera de la app, en tu servidor. Tu app móvil debe llamar a `setIntegrationIdentifier` al arrancar — antes de que se produzca cualquier compra de Stripe. De lo contrario, Adapty no tiene ningún ID al que asociarla, y la compra de Stripe nunca llega a Firebase. ### Limitaciones \{#limitations\} - **No es una herramienta de atribución de Google Ads.** Esta integración envía eventos de Adapty a Firebase y Google Analytics para análisis. No atribuye instalaciones de la app a campañas de Google Ads (UAC / Universal App Campaigns) ni separa el tráfico de pago del orgánico. Para la atribución de instalaciones, usa la [Atribución de Adapty](adapty-user-acquisition) integrada. - **Sin relleno histórico.** Adapty reenvía eventos desde el momento en que activas la integración: las compras pasadas, renovaciones y reembolsos nunca llegan a Firebase. (Los datos históricos están disponibles en las exportaciones de [S3](s3-exports) / [GCS](google-cloud-storage) de Adapty, pero importarlos a Firebase no forma parte de esta integración.) - **Los compradores solo en web no llegan a Firebase.** Adapty reenvía las compras a Firebase mediante el Firebase App Instance ID que establece tu app móvil. Los compradores que nunca instalaron la app no tienen ID, por lo que sus compras no llegan a Firebase. Consulta [compras de Stripe](#stripe-purchases) más arriba para más detalles, y considera la [integración de Firebase de FunnelFox](https://funnelfox.com/docs/integrations/subscription-management/adapty) o un flujo de datos web de Google Analytics para el seguimiento en web. - **Las compras de Paddle no están incluidas.** Esta integración no es compatible con Paddle actualmente. Las compras de Paddle permanecen en Adapty Analytics y no llegan a Firebase por esta vía. - **Las compras de Stripe heredan las limitaciones propias de Stripe.** Consulta las [limitaciones de la integración con Stripe](stripe#current-limitations). ## Instrucciones de configuración \{#setup-instructions\} ### Configurar Firebase \{#configure-firebase\} 1. Abre [Firebase Console](https://console.firebase.google.com/) y selecciona o crea un proyecto. Para mantener los análisis de producción limpios de eventos sandbox, usa un proyecto de Firebase separado para las builds de desarrollo. 2. Vincula el proyecto a una propiedad de Google Analytics. Firebase te lo solicita durante la creación del proyecto, o puedes añadirlo más tarde en **Project settings** > **Integrations** > **Google Analytics**. 3. En **Project settings** > **General** > **Your apps**, añade una entrada para cada plataforma en la que publiques (iOS / Android / Web). Para Stripe, añade una entrada de app Web — no existe un tipo de app nativa para Stripe. Cada entrada genera un **Firebase App ID** único y un flujo de datos correspondiente en Google Analytics. Pegarás el ID en la configuración de la integración de Firebase de Adapty durante la configuración. ### Configurar Adapty \{#configure-adapty\} 1. Abre [**Integrations** > **Firebase**](https://app.adapty.io/integrations/firebase) en el Adapty Dashboard. 2. Activa el toggle **Firebase integration**. 3. Introduce las credenciales de cada plataforma en la que publiques tu app. Adapty necesita tanto un **Firebase App ID** como un **Google Analytics secret** para cada plataforma — los valores difieren entre iOS, Android y Stripe. | Adapty Dashboard | Google Analytics | Dónde encontrarlo | | --- | --- | --- | | **Firebase App ID** | **App ID** | Firebase Console > **Project settings** > **General** > **Your apps** | | **Google Analytics secret** | **Measurement Protocol API secret** | Google Analytics > **Admin** > **Data streams** > **Measurement Protocol API secrets** > **Create** | 4. Configura cómo Adapty reenvía los datos de ingresos y usuarios. Los cuatro controles comparten una fila en el dashboard: - Desplegable **Revenue definition**: ingresos brutos, ingresos tras la comisión del store o [ingresos tras la comisión del store e impuestos](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). - Interruptor **Send user properties**: cuando está activado, los eventos incluyen `subscription_state` y `subscription_product_id`. Para utilizarlos en informes o audiencias, consulta [Usar datos de suscripción en informes](#use-subscription-data-in-reports-and-audiences). - Interruptor **Report user's currency**: cuando está activado, Adapty convierte la moneda local de cada transacción a la moneda de informes de tu cuenta antes de enviarla a Google Analytics. - Interruptor **Send trial price**: los inicios de prueba son valiosos, ya que la mayoría de los usuarios de pago suelen comenzar con un período de prueba. Sin embargo, la optimización de pujas de Google Ads solo considera rentables los eventos con ingresos asociados. Activa esta opción para asignar un precio de referencia a cada prueba, de modo que Google los trate como conversiones y optimice el gasto publicitario hacia la captación de usuarios que inician pruebas. Al activarlo, aparece el campo **Trial price percentage**. Indica qué porcentaje del precio completo de la suscripción debe considerar Google que vale cada prueba; por ejemplo, `50%` reporta la mitad del precio de la suscripción durante el período de prueba. 5. Asigna los eventos de Adapty a los nombres de eventos de Firebase/Google Analytics. Adapty ofrece mapas de eventos independientes para **iOS** y **Android**, de modo que puedes usar nombres distintos por plataforma. **Las compras de Stripe usan el mapa de eventos de iOS** — no existe un mapa separado para Stripe. Google Analytics aplica límites estrictos del Measurement Protocol: nombres de eventos de 40 caracteres, nombres de propiedades de usuario de 24 caracteres y valores de 36 caracteres. Google Analytics descarta silenciosamente los eventos personalizados que superan estos límites. :::warning Algunos eventos utilizan el vocabulario de ecommerce reservado en Firebase y Google Analytics: `purchase` y `refund`. La importación de conversiones de Google Ads, los informes de ingresos de Google Analytics y las audiencias predictivas dependen de estas cadenas exactas. Sobrescribe los valores predeterminados solo si no necesitas esas funciones. ::: 6. Haz clic en **Save**. Adapty comienza a reenviar eventos a Firebase en pocos minutos. ### Configura el código de tu app \{#configure-your-app-code\} :::tip Asegúrate de que tu app incluye el <InlineTooltip tooltip="Firebase SDK">[iOS](https://firebase.google.com/docs/ios/setup), [Android](https://firebase.google.com/docs/android/setup), [Flutter](https://firebase.google.com/docs/flutter/setup), [Unity](https://firebase.google.com/docs/unity/setup), [React Native](https://rnfirebase.io/), y [Capacitor](https://github.com/capawesome-team/capacitor-firebase)</InlineTooltip>. ::: Adapty necesita incluir el **Firebase App Instance ID** con cada evento; de lo contrario, nada llegará a Firebase (`MISSING_INTEGRATION_ID`). Después de `FirebaseApp.configure()` y `Adapty.activate()`, solicita el App Instance ID al SDK de Firebase. Pásalo a Adapty mediante `setIntegrationIdentifier`. Ejecuta esto una vez por lanzamiento de la app, antes de cualquier flow de compra. :::note Los SDKs de terceros generan los IDs de usuario de forma asíncrona. Es posible que el ID no esté disponible cuando se ejecuta `Adapty.activate()`. Si tu **Customer User ID** proviene de uno de estos SDKs, llama a `Adapty.activate()` sin él. Una vez que el ID esté disponible, llama a `setIntegrationIdentifier()` y luego a `identify()` con el CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> <Tabs groupId="sdk-version" queryString> <TabItem value="v4" label="Adapty SDK v4+"> ```swift showLineNumbers FirebaseApp.configure() if let appInstanceId = Analytics.appInstanceID() { do { try await Adapty.setIntegrationIdentifier(.firebaseAppInstanceId(appInstanceId)) } catch { // handle the error } } ``` </TabItem> <TabItem value="v3" label="Adapty SDK v3" default> ```swift showLineNumbers FirebaseApp.configure() if let appInstanceId = Analytics.appInstanceID() { do { try await Adapty.setIntegrationIdentifier( key: "firebase_app_instance_id", value: appInstanceId ) } catch { // handle the error } } ``` </TabItem> </Tabs> </TabItem> <TabItem value="kotlin" label="Android (Kotlin)"> ```kotlin showLineNumbers // after Adapty.activate() FirebaseAnalytics.getInstance(context).appInstanceId.addOnSuccessListener { appInstanceId -> Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId) { error -> if (error != null) { // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)"> ```java showLineNumbers // after Adapty.activate() FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId, error -> { if (error != null) { // handle the error } }); }); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)"> ```dart showLineNumbers final appInstanceId = await FirebaseAnalytics.instance.appInstanceId; if (appInstanceId != null) { try { await Adapty().setIntegrationIdentifier( key: "firebase_app_instance_id", value: appInstanceId, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` </TabItem> <TabItem value="unity" label="Unity (C#)"> ```csharp showLineNumbers using AdaptySDK; using Firebase.Analytics; FirebaseAnalytics .GetAnalyticsInstanceIdAsync() .ContinueWithOnMainThread((task) => { if (!task.IsCompletedSuccessfully) { // handle the error return; } Adapty.SetIntegrationIdentifier( "firebase_app_instance_id", task.Result, (error) => { // handle the error } ); }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)"> ```typescript showLineNumbers try { const appInstanceId = await analytics().getAppInstanceId(); if (appInstanceId) { await adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId); } } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ### Verificar la integración \{#verify-the-integration\} La forma más rápida de confirmar que los eventos están llegando es Firebase DebugView: 1. En un dispositivo de prueba, ejecuta tu app con el [modo debug de Firebase habilitado](https://firebase.google.com/docs/analytics/debugview#enable_debug_mode). 2. Realiza una compra en sandbox o cualquier evento que hayas habilitado en el Adapty Dashboard. 3. Abre Firebase Console > **Analytics** > **DebugView**. Los eventos aparecen en segundos, con todos sus parámetros. Los informes estándar —Realtime, Reports, audiencias— se actualizan en minutos o hasta 24 horas, según el informe. DebugView es el único lugar donde puedes confirmarlo en tiempo real. ## Usar datos de suscripción en informes y audiencias \{#use-subscription-data-in-reports-and-audiences\} Activa **Send user properties** en el Adapty Dashboard ([Configurar Adapty](#configure-adapty), paso 4). Sin esto, Adapty no enviará `subscription_state` ni `subscription_product_id`, y el resto de esta sección no tendrá efecto. De forma predeterminada, Firebase y Google Analytics no exponen las propiedades de usuario. Regístralas cada una como dimensión personalizada. Esto hace que `subscription_state` y `subscription_product_id` estén disponibles en informes, Exploraciones y audiencias. Configura las dimensiones en Google Analytics Admin. Después podrás consultarlas tanto en Firebase como en Google Analytics, ya que comparten el mismo backend. Útil para crear audiencias de Google Ads con usuarios de pago o para alimentar modelos predictivos. Una vez completada la configuración, Adapty rellenará estas propiedades en los eventos futuros. Los eventos existentes no se actualizarán. 1. En Google Analytics, abre **Admin** > **Custom definitions**. 2. Haz clic en **Create custom dimensions**. 3. Para cada propiedad, configura: - **Dimension name**: Cualquier nombre legible, por ejemplo "Subscription state". - **Scope**: **User**. - **User property**: `subscription_state` o `subscription_product_id`. El nombre debe coincidir exactamente, ya que Google Analytics distingue entre mayúsculas y minúsculas. ## Solución de problemas \{#troubleshooting\} ### Los eventos no aparecen en Firebase \{#events-dont-appear-in-firebase\} - Asegúrate de que el Firebase App Instance ID esté configurado **antes** de que se realice la primera compra. Los eventos sin un Firebase ID no llegan a Firebase y generan un error. - Asegúrate de que la propiedad de Google Analytics vinculada en la Firebase Console coincida con el flujo de datos. - Asegúrate de que las credenciales (ID + secreto) configuradas en Adapty correspondan a la plataforma. ### `access_level_updated` aparece como fallido en el Event Feed `access_level_updated` es un **evento exclusivo de webhook**. Adapty nunca intenta enviarlo a Firebase, pero el Event Feed lo muestra igualmente como entrega fallida. Ignora esa línea. Tu integración funciona correctamente. Para usar este evento, configura la [integración con webhook](webhook). ### Los eventos de sandbox contaminan los datos de producción \{#sandbox-events-pollute-production-data\} Adapty reenvía las transacciones de sandbox y producción al mismo proyecto de Firebase. Consulta [Configurar Firebase](#configure-firebase) — usar un proyecto de Firebase separado para las builds de desarrollo evita este problema por completo. ### Firebase infravalora los ingresos en apps con StoreKit 2 \{#firebase-undercounts-revenue-for-storekit-2-apps\} Firebase registra automáticamente un evento `in_app_purchase` por cada compra de StoreKit 1, sin necesidad de código. StoreKit 2 usa una API distinta. Firebase nunca ve esas transacciones. Las consecuencias: las apps que dependen principalmente de SK2 sin un pipeline de ingresos separado pueden registrar la mitad o menos de sus ingresos reales, tanto en Firebase como en Google Analytics y en todas las campañas de Google Ads que dependen de esos datos. La optimización de pujas se basa en cifras incorrectas. Los informes de ingresos muestran solo la mitad del panorama real. La solución: pasa `firebase_app_instance_id` a Adapty (consulta [Configura el código de tu app](#configure-your-app-code)). Adapty reenvía cada compra a través del Measurement Protocol — ingresos, moneda y producto incluidos. ### Los números de Adapty Analytics y Firebase no coinciden \{#adapty-analytics-and-firebase-numbers-diverge\} - **StoreKit 2**: Con diferencia, la causa principal. Consulta [Firebase subestima los ingresos en apps con StoreKit 2](#firebase-undercounts-revenue-for-storekit-2-apps). - **Adopción del SDK**: Firebase solo cuenta eventos de usuarios cuya app envía un Firebase App Instance ID. Las versiones antiguas de la app no realizan esa llamada. Adapty sigue contando a esos usuarios; Firebase no. - **Eventos de sandbox**: Adapty también reenvía las transacciones de sandbox a Firebase. Usa un proyecto de Firebase separado para las builds de desarrollo para mantenerlos separados. - **Muestreo**: Google Analytics Explorations aplica muestreo a conjuntos de datos grandes. Para recuentos sin muestreo, consulta la vista en tiempo real o los informes estándar. ### Los nombres de eventos personalizados son rechazados por Google Analytics \{#custom-event-names-are-rejected-by-google-analytics\} Google Analytics limita los nombres de eventos a 40 caracteres, solo alfanuméricos y guiones bajos, y deben empezar por una letra. Renombra cualquier evento personalizado de Adapty en el dashboard que no cumpla estas restricciones. --- # File: mixpanel --- --- title: "Mixpanel" description: "Conecta Mixpanel con Adapty para un potente análisis de suscripciones." --- [Mixpanel](https://mixpanel.com/home/) es un potente servicio de análisis de productos. Su solución de seguimiento basada en eventos permite a los equipos de producto obtener información valiosa sobre las mejores estrategias de adquisición, conversión y retención de usuarios en distintas plataformas. Esta integración te permite llevar todos los eventos de Adapty a Mixpanel. Como resultado, obtendrás una visión más completa de tu negocio de suscripciones y las acciones de tus clientes. Adapty ofrece un conjunto completo de datos que te permite rastrear [eventos de suscripción](events) de los stores en un solo lugar. Con Adapty, puedes ver fácilmente cómo se comportan tus suscriptores, conocer sus preferencias y usar esa información para comunicarte con ellos de forma dirigida y efectiva. ## Cómo configurar la integración con Mixpanel \{#how-to-set-up-mixpanel-integration\} 1. Abre la página [Integrations -> Mixpanel](https://app.adapty.io/integrations/mixpanel) en el Adapty Dashboard. 2. Activa el interruptor e introduce tu **Mixpanel Token**. Puedes especificar un token para todas las plataformas o limitarlo a plataformas concretas si solo quieres recibir datos de algunas de ellas. 3. Configura **Mixpanel Data Residency** para que coincida con tu proyecto de Mixpanel. Este campo es obligatorio y su valor predeterminado es **US**. Elige **US** para el endpoint `api.mixpanel.com` o **Europe** para `api-eu.mixpanel.com`. :::warning Si tu proyecto de Mixpanel usa residencia de datos en la UE, debes establecer **Mixpanel Data Residency** en **Europe**. Mixpanel descarta los eventos enviados al endpoint de EE. UU. desde proyectos de la UE. ::: <img src="/assets/shared/img/mixpanel.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Cómo encontrar tu token de Mixpanel \{#finding-your-mixpanel-token\} Para obtener tu **Mixpanel Token**: 1. Inicia sesión en tu [Mixpanel Dashboard](https://mixpanel.com/settings/project/). 2. Abre **Settings** y selecciona **Organization Settings**. <img src="/assets/shared/img/mixpanel-settings.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la barra lateral izquierda, ve a **Projects** y selecciona tu proyecto. <img src="/assets/shared/img/mixpanel-project-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Cómo funciona la integración \{#how-the-integration-works\} Adapty mapea automáticamente las propiedades de evento relevantes —como el ID de usuario y los ingresos— a las [propiedades nativas de Mixpanel](https://docs.mixpanel.com/docs/data-structure/user-profiles). Esto garantiza un seguimiento e informes precisos de los eventos relacionados con suscripciones. Además, Adapty acumula datos de ingresos por usuario y actualiza sus [Propiedades de Perfil de Usuario](https://docs.mixpanel.com/docs/data-structure/user-profiles), incluidas `subscription state` y `subscription product ID`. Una vez recibido un evento, Mixpanel actualiza los campos correspondientes en tiempo real. ## Eventos y etiquetas \{#events-and-tags\} Debajo de las credenciales, hay tres grupos de eventos que puedes enviar a Mixpanel desde Adapty. Simplemente activa los que necesites. Consulta la lista completa de eventos que ofrece Adapty [aquí](events). <img src="/assets/shared/img/mixpanel-events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Recomendamos usar los nombres de evento predeterminados que ofrece Adapty. Pero puedes cambiarlos según tus necesidades. ## Configuración del SDK \{#sdk-configuration\} Usa el método `.setIntegrationIdentifier()` para configurar `mixpanelUserId`. Si no se establece, Adapty usa tu ID de usuario (`customerUserId`) o, si es nulo, el ID de Adapty. Asegúrate de que el ID de usuario que usas para enviar datos a Mixpanel desde tu app sea el mismo que envías a Adapty. :::note Los SDKs de terceros generan los IDs de usuario de forma asíncrona. Es posible que el ID no esté disponible cuando se ejecuta `Adapty.activate()`. Si tu **Customer User ID** proviene de uno de estos SDKs, llama a `Adapty.activate()` sin él. Una vez que el ID esté disponible, llama a `setIntegrationIdentifier()` y luego a `identify()` con el CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "mixpanel_user_id", value: Mixpanel.mainInstance().distinctId ) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(mixpanelUserId: Mixpanel.mainInstance().distinctId) Adapty.updateProfile(params: builder.build()) ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelAPI.distinctId) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers final mixpanel = await Mixpanel.init("Your Token", trackAutomaticEvents: true); final distinctId = await mixpanel.getDistinctId(); try { await Adapty().setIntegrationIdentifier( key: "mixpanel_user_id", value: distinctId, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; var distinctId = Mixpanel.DistinctId; if (distinctId != null) { Adapty.SetIntegrationIdentifier( "mixpanel_user_id", distinctId, (error) => { // handle the error }); } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // Si ya tienes una instancia compartida de Mixpanel en tu app, úsala en su lugar. const trackAutomaticEvents = true; const mixpanel = new Mixpanel('YOUR_PROJECT_TOKEN', trackAutomaticEvents); await mixpanel.init(); // Este es el distinct_id actual de Mixpanel (generado automáticamente, o establecido mediante mixpanel.identify(...)) const mixpanelUserId = await mixpanel.getDistinctId(); try { await adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelUserId); } catch (error) { // maneja `AdaptyError` } ``` </TabItem> </Tabs> ## Estructura de eventos de Mixpanel \{#mixpanel-event-structure\} Adapty envía eventos a Mixpanel usando el método `track`. Las propiedades del evento tienen esta estructura: ```json { "event": "subscription_renewed", "properties": { "ip": 0, "time": 1709294400, "$insert_id": "123e4567-e89b-12d3-a456-426614174000", "vendor_product_id": "yearly.premium.6999", "original_transaction_id": "GPA.3383...", "currency": "USD", "environment": "Production", "store": "app_store", "purchase_date": "2024-03-01T12:00:00.000000+0000" } } ``` Donde: | Parámetro | Tipo | Descripción | |:-------------------------------------|:--------|:-------------------------------------------------------------| | `event` | String | Nombre del evento (mapeado desde el evento de Adapty). | | `properties` | Object | Propiedades del evento. | | `properties.ip` | Integer | Dirección IP (enviada como 0 en server-to-server). | | `properties.time` | Long | Timestamp UNIX del evento en segundos. | | `properties.$insert_id` | String | ID único del evento (UUID) para deduplicación. | | `properties.vendor_product_id` | String | ID del producto en el store. | | `properties.original_transaction_id` | String | ID de transacción original. | | `properties.currency` | String | Código de moneda. | | `properties.store` | String | Nombre del store (por ejemplo, "app_store"). | | `properties.environment` | String | Entorno ("Sandbox" o "Production"). | ### Actualizaciones del perfil de usuario \{#user-profile-updates\} Adapty también actualiza el Perfil de Usuario de Mixpanel usando `people_set` con las siguientes propiedades: | Parámetro | Tipo | Descripción | |:--------------------------|:-------|:--------------------------------------------------------------------| | `subscription_state` | String | Estado actual de la suscripción (p. ej., "subscribed"). | | `subscription_product_id` | String | ID del producto de suscripción activo. | --- # File: posthog --- --- title: "PostHog" description: "" --- PostHog es una plataforma de analítica que ofrece herramientas para rastrear el comportamiento de los usuarios, visualizar el uso del producto y analizar la retención. Con funcionalidades como el seguimiento de eventos, los flujos de usuario y los feature flags, está diseñada para ayudarte a entender mejor tu producto y mejorarlo. Integrar PostHog con Adapty permite hacer un seguimiento fluido de los eventos relacionados con suscripciones, como inicios de prueba, renovaciones y cancelaciones. Al enviar estos eventos a PostHog, puedes analizar cómo los cambios en las suscripciones afectan al comportamiento de los usuarios, evaluar el rendimiento de los paywalls y obtener información más detallada sobre tus estrategias de monetización, todo dentro de tu flujo de trabajo de analítica habitual. ## Características de la integración \{#integration-characteristics\} | Característica de la integración | Descripción | | -------------------------------- | ------------------------------------------------------------ | | Frecuencia | Tiempo real; es posible que los eventos no aparezcan de inmediato en el dashboard de PostHog. | | Dirección de los datos | Los eventos de Adapty se envían desde el servidor de Adapty al servidor de PostHog. | | Punto de integración de Adapty | <ul><li> Los SDK de PostHog y Adapty en el código de la aplicación móvil</li><li> El servidor de Adapty</li></ul> | ## Estructura de eventos de PostHog \{#posthog-event-structure\} Adapty envía los eventos seleccionados a PostHog según lo configurado en la sección **Events names** de la [página de integración de PostHog](https://app.adapty.io/integrations/posthog). Cada evento tiene esta estructura: ```json showLineNumbers { "distinct_id": "john.doe@example.com", "timestamp": "2025-01-08T11:06:12+00:00", "event": "subscription_started", "properties": { "$set": { "email": "user@example.com", "first_name": "John", "last_name": "Doe", "birthday": "1990-01-01", "gender": "male", "os": "iOS" }, "timezone": "America/New_York", "ip_address": "10.168.1.1", "*": "{{other_event_properties}}" } } ``` Donde | **Parámetro** | **Tipo** | **Descripción** | | --------------- | -------------------- | ------------------------------------------------------------ | | **distinct_id** | String | Identificador único del usuario (p. ej., `profile.posthog_distinct_user_id`, `customer_user_id` o `profile_id`). | | **timestamp** | ISO 8601 fecha y hora | La fecha y hora del evento. | | **event** | String | El nombre del evento tal como lo definiste en la sección Events names de la [configuración de PostHog](https://app.adapty.io/integrations/posthog). | | **properties** | Object | Contiene [properties.$set](posthog#propertiesset-parameters) y todas las [propiedades específicas del evento](messaging#event-properties). Cada propiedad es opcional y no se enviará a PostHog si no está presente. | ### Parámetros de properties.$set Cada parámetro del objeto `properties.$set` es opcional y no se enviará a PostHog si no está presente. | **Parámetro** | **Tipo** | **Descripción** | | --------------- | -------------------- | ------------------------------------------------------------ | | **email** | String | Dirección de correo electrónico del usuario. | | **first_name** | String | Nombre del usuario. | | **last_name** | String | Apellido del usuario. | | **birthday** | String (Date) | Fecha de nacimiento del usuario. | | **gender** | String | Género del usuario. | | **os** | String | Sistema operativo del dispositivo del usuario. | ## Configuración de la integración con PostHog \{#setting-up-posthog-integration\} 1. Abre la página [Integrations -> PostHog](https://app.adapty.io/integrations/posthog) en el Adapty Dashboard y activa el botón. <img src="/assets/shared/img/posthog-on.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Inicia sesión en el [PostHog Dashboard](https://posthog.com/). 3. Ve a **Settings -> Project**. 4. En la ventana **Project**, desplázate hacia abajo hasta la sección **Project ID** y copia la **Project API key**. 5. Pega la API key en el campo **Project API key** del Adapty Dashboard. PostHog no tiene un modo Sandbox específico para la integración servidor a servidor. 6. Elige tu **PostHog Deployment**: | Opción | Descripción | | ------ | ----------- | | us/eu | Despliegues de PostHog alojados por defecto. | | Custom | Para instancias autoalojadas. Introduce la URL de tu instancia en el campo **PostHog Instance URL**. | 7. (Opcional) Si usas un despliegue de PostHog autoalojado, introduce la dirección de tu despliegue en el campo **PostHog Instance URL**. 8. (opcional) Ajusta opciones como **Reporting Proceeds**, **Exclude Historical Events**, **Report User's Currency** y **Send Trial Price**. Consulta [Configuración de la integración](configuration#integration-settings) para más detalles sobre estas opciones. 9. (opcional) También puedes personalizar qué eventos se envían a PostHog en la sección **Events names**. Desactiva los eventos que no necesites o cámbiales el nombre según convenga. 10. Haz clic en **Save** para finalizar la configuración. ## Configuración del SDK \{#sdk-configuration\} Para habilitar la recepción de datos de atribución desde PostHog, pasa el valor `distinctId` a Adapty tal como se muestra a continuación: :::note Los SDKs de terceros generan los IDs de usuario de forma asíncrona. Es posible que el ID no esté disponible cuando se ejecuta `Adapty.activate()`. Si tu **Customer User ID** proviene de uno de estos SDKs, llama a `Adapty.activate()` sin él. Una vez que el ID esté disponible, llama a `setIntegrationIdentifier()` y luego a `identify()` con el CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let distinctId = PostHogSDK.shared.getDistinctId() try await Adapty.setIntegrationIdentifier( key: "posthog_distinct_user_id", value: distinctId ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("posthog_distinct_user_id", PostHog.distinctId()) { error -> if (error != null) { // handle the error } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers Adapty.setIntegrationIdentifier("posthog_distinct_user_id", PostHog.distinctId(), error -> { if (error != null) { // handle the error } }); ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```javascript showLineNumbers try { final distinctId = await Posthog().getDistinctId(); await Adapty().setIntegrationIdentifier( key: "posthog_distinct_user_id", value: distinctId, ); } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity" default> No hay un SDK oficial de PostHog para Unity. </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // ... const posthog = usePostHog(); // ... try { await adapty.setIntegrationIdentifier("posthog_distinct_user_id", posthog.getDistinctId()); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> Adapty enviará ahora eventos a PostHog y recibirá atribución de él. --- # File: splitmetrics --- --- title: "SplitMetrics Acquire" description: "Usa SplitMetrics con Adapty para pruebas A/B de suscripciones y optimización." --- Con la integración de [SplitMetrics Acquire](https://splitmetrics.com/acquire/), puedes ver exactamente cuánto dinero generan tus Apple Search Ads a partir de suscripciones. Además, puedes hacer seguimiento de tus usuarios durante meses para saber cuánto dinero producen tus anuncios a lo largo del tiempo. Además, Adapty envía [eventos de suscripción](events) a SplitMetrics Acquire para que puedas crear dashboards personalizados y automatizaciones allí, basados en la atribución de Apple Search Ads. No añade ningún dato de atribución a Adapty, ya que nosotros ya obtenemos todo lo necesario directamente desde ASA. ## Cómo configurar la integración con SplitMetrics Acquire \{#how-to-set-up-splitmetrics-acquire-integration\} Para integrar SplitMetrics Acquire, ve a [Integrations > SplitMetrics Acquire](https://app.adapty.io/integrations/splitmetrics) e introduce las credenciales. <img src="/assets/shared/img/8255349-CleanShot_2023-08-14_at_17.39.422x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Abre tu cuenta de SplitMetrics Acquire, pasa el cursor sobre uno de los logos de MMP y haz clic en el botón **Settings**. Encuentra tu Client ID en el diálogo, en el punto **5**, cópialo y pégalo en Adapty como **Client ID**. <img src="/assets/shared/img/4d0b2b6-Adapty.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/4f8d0b8-AdaptyGuide.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> También tendrás que introducir el Apple App ID para usar la integración. Para encontrarlo, abre la página de tu app en App Store Connect, ve a la **App Information page** en la sección **General** y localiza el **Apple ID** en la parte inferior izquierda de la pantalla. <img src="/assets/shared/img/61578ee-CleanShot_2022-04-20_at_17.55.03.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Eventos y etiquetas \{#events-and-tags\} Debajo de las credenciales encontrarás tres grupos de eventos que puedes enviar a SplitMetrics Acquire desde Adapty. Activa simplemente los que necesites. Consulta la lista completa de eventos que ofrece Adapty [aquí](events). <img src="/assets/shared/img/1b0c777-CleanShot_2023-08-11_at_14.56.362x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Te recomendamos usar los nombres de evento predeterminados que proporciona Adapty. Sin embargo, puedes cambiarlos según tus necesidades. Adapty enviará los eventos de suscripción a SplitMetrics Acquire mediante una integración servidor a servidor, lo que te permitirá ver todos los eventos de suscripción en tu dashboard de SplitMetrics. ## Configuración del SDK \{#sdk-configuration\} No es necesario configurar nada en el SDK, aunque te recomendamos enviar `customerUserId` a Adapty para mayor precisión. :::warning Asegúrate de haber configurado [Apple Search Ads](apple-search-ads) en Adapty y de haber [subido las credenciales](https://app.adapty.io/settings/apple-search-ads); sin ellas, SplitMetrics Acquire no funcionará. ::: ## Solución de problemas \{#troubleshooting\} Si la integración con SplitMetrics Acquire no funciona a pesar de estar correctamente configurada: - Asegúrate de haber activado el interruptor **Receive Apple Search Ads attribution in Adapty** en [App Settings -> Apple Search Ads tab](https://app.adapty.io/settings/apple-search-ads), de haber configurado [Apple Search Ads](apple-search-ads) en Adapty y de haber [subido las credenciales](https://app.adapty.io/settings/apple-search-ads); sin ellas, SplitMetric no funcionará. - Comprueba que los perfiles tengan atribución ASA no orgánica. Solo los perfiles con atribución ASA detallada y no orgánica enviarán sus eventos a Adapty. ## Estructura de eventos de SplitMetrics Acquire \{#splitmetrics-acquire-event-structure\} Adapty envía eventos a SplitMetrics Acquire mediante una solicitud GET usando parámetros de consulta. Cada evento tiene la siguiente estructura: ```json { "source": "Apple Search Ads", "app_id": "123456789", "name": "subscription_renewed", "type": "subscription_renewed", "revenue": 9.99, "currency": "USD", "tap_time": "2024-03-01 12:00:00", "open_time": "2024-03-01 12:05:00", "event_time": "2024-03-02 12:00:00", "adaccount_id": "123456", "campaign_id": "123456789", "adgroup_id": "123456789", "keyword_id": "123456789", "creative_set_id": "123456789", "Ad_id": "123456789", "country_or_region": "US", "conversion_type": "Download", "user_id": "user_12345", "att_status": "3", "device_type": "iphone", "app_version": "1.2.3", "sdk_version": "2.10.0", "ios_version": "17.2", "event_value": "{\"vendor_product_id\":\"yearly.premium.6999\",\"original_transaction_id\":\"GPA.3383...\"}", "event_id": "123e4567-e89b-12d3-a456-426614174000" } ``` Donde: | Parámetro | Tipo | Descripción | |:--------------------|:-------|:----------------------------------------------------------------------------------------------------------------------------------| | `source` | String | Siempre "Apple Search Ads". | | `app_id` | String | Apple App ID. | | `name` | String | Nombre del evento (mapeado desde el evento de Adapty). | | `type` | String | Tipo de evento (igual que `name`). | | `revenue` | Float | Importe de ingresos. | | `currency` | String | Código de moneda. | | `tap_time` | String | Fecha y hora del tap en el anuncio. | | `open_time` | String | Fecha y hora de la apertura de la app (instalación). | | `event_time` | String | Fecha y hora del evento. | | `adaccount_id` | String | ID de organización de ASA. | | `campaign_id` | String | ID de campaña de ASA. | | `adgroup_id` | String | ID de grupo de anuncios de ASA. | | `keyword_id` | String | ID de palabra clave de ASA. | | `creative_set_id` | String | ID de conjunto creativo de ASA. | | `Ad_id` | String | ID de anuncio de ASA. | | `country_or_region` | String | País o región del store. | | `conversion_type` | String | Tipo de conversión (p. ej., "Download"). | | `user_id` | String | Customer User ID o Adapty Profile ID. | | `att_status` | String | Estado de uso de seguimiento (0-3). | | `device_type` | String | Tipo de dispositivo (p. ej., "iphone", "ipad"). | | `app_version` | String | Versión de la aplicación. | | `sdk_version` | String | Versión del SDK de Adapty. | | `ios_version` | String | Versión de iOS. | | `event_value` | String | Cadena JSON con todos los [detalles del evento](webhook-event-types-and-fields#for-most-event-types) disponibles. | | `event_id` | String | ID de evento único (UUID). | --- # File: braze --- --- title: "Braze" description: "Integra Braze con Adapty para una mejor interacción con clientes y notificaciones push." --- Como una de las principales soluciones de captación de clientes, [Braze](https://www.braze.com/) ofrece una amplia gama de herramientas para notificaciones push, email, SMS y mensajería in-app. Al integrar Adapty con Braze, puedes acceder fácilmente a todos tus eventos de suscripción en un solo lugar, lo que te permite activar comunicaciones automatizadas basadas en esos eventos. Adapty proporciona un conjunto completo de datos que te permite rastrear [eventos de suscripción](events) de todas las stores en un solo lugar y puede usarse para actualizar los perfiles de tus usuarios en Braze. Con Adapty, puedes ver fácilmente el comportamiento de tus suscriptores, conocer sus preferencias y utilizar esa información para comunicarte con ellos de forma dirigida y efectiva. Por tanto, esta integración te permite rastrear eventos de suscripción en tu dashboard de Braze y relacionarlos con tus [campañas de adquisición.](https://www.braze.com/product/journey-orchestration) Adapty envía eventos de suscripción, propiedades de usuario y compras a Braze, para que puedas crear comunicaciones dirigidas a clientes mediante notificaciones push de Braze tras una integración sencilla y rápida, tal como se describe a continuación. ## Cómo configurar la integración con Braze \{#how-to-set-up-braze-integration\} Para integrar Braze, ve a [Integrations -> Braze](https://app.adapty.io/integrations/braze), activa el interruptor y rellena los campos. El primer paso del proceso de integración es proporcionar las credenciales necesarias para establecer una conexión entre tus perfiles de Braze y Adapty. Necesitarás la **REST API Key**, tu **Braze Instance ID** y los **App IDs** para iOS y Android para que la integración funcione correctamente: <img src="/assets/shared/img/5f1e62c-adapty_braze.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. La **REST API Key** se puede crear en **Braze Dashboard** → **Settings** → **API Keys**. Asegúrate de que tu clave tenga el permiso `users.track` al crearla: <img src="/assets/shared/img/b5fdf16-adapty_braze_create_api_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/1e5b4b8-adapty_braze_api_key_users_track.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Para obtener el **Braze Instance ID**, fíjate en la URL de tu Braze Dashboard y accede a la sección de [Braze Docs](https://www.braze.com/docs/api/basics/#endpoints) donde se especifica el ID de instancia. Tendrá un formato regional como US-03, EU-01, etc. 3. Los App IDs de iOS y Android también se encuentran en Braze Dashboard → Settings → API Keys. Cópialos desde aquí: <img src="/assets/shared/img/1e6d21b-adapty_braze_app_ids.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Eventos, atributos de usuario y compras \{#events-user-attributes-and-purchases\} Justo debajo de las credenciales hay tres grupos de eventos que puedes enviar a Braze desde Adapty. Activa simplemente los que necesites. También puedes cambiar los nombres de los eventos según lo que necesites enviar a Braze. Consulta la lista completa de eventos que ofrece Adapty [aquí](events): <img src="/assets/shared/img/702e628-adapty_braze_events_names.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty enviará eventos de suscripción y atributos de usuario a Braze mediante una integración servidor a servidor, lo que te permitirá verlos en tu Braze Dashboard y configurar campañas basándote en ellos. Para los eventos que generan ingresos, como las conversiones de prueba y las renovaciones, Adapty enviará esta información a Braze como compras. [Aquí](messaging#event-properties) encontrarás las especificaciones completas de las propiedades de los eventos que se envían a Braze. :::note Atributos de usuario útiles Adapty envía algunos atributos de usuario para la integración con Braze de forma predeterminada. Puedes consultar la lista que se muestra a continuación para determinar cuáles se adaptan mejor a tus necesidades. ::: | Atributo de usuario | Tipo | Valor | |--------------|----|-----| | `adapty_customer_user_id` | String | Contiene el valor del identificador único del usuario definido por el cliente. Se puede encontrar tanto en el [Dashboard](profiles-crm) de Adapty como en Braze. | | `adapty_profile_id` | String | Contiene el valor del identificador único del Perfil de Usuario de Adapty, que se puede encontrar en el [Dashboard](profiles-crm) de Adapty. | | `environment` | String | <p>Indica si el usuario opera en un entorno sandbox o de producción.</p><p></p><p>Los valores son `Sandbox` o `Production`</p> | | `store` | String | <p>Contiene el nombre de la Store utilizada para realizar la compra.</p><p></p><p>Valores posibles:</p><p>`app_store` o `play_store`.</p> | | `vendor_product_id` | String | <p>Contiene el valor del ID de producto en la store de Apple/Google.</p><p></p><p>p. ej., org.locals.12345</p> | | `subscription_expires_at` | String | <p>Contiene la fecha de vencimiento de la suscripción más reciente.</p><p></p><p>El formato del valor es:</p><p>YYYY-MM-DDTHH:mm:ss.SSS+TZ</p><p>p. ej., 2023-02-15T17:22:03.000+0000</p> | | `active_subscription` | String | El valor se establecerá en `true` en cualquier evento de compra o renovación, o en `false` si la suscripción ha expirado. | | `period_type` | String | <p>Indica el tipo de período más reciente para la compra o renovación.</p><p></p><p>Los valores posibles son</p><p>`trial` para un período de prueba o `normal` para el resto.</p> | Todos los valores float se redondearán a int. Los strings permanecen igual. Además de la lista predefinida de etiquetas disponibles, es posible enviar [atributos personalizados](segments#custom-attributes) mediante etiquetas. Esto permite mayor flexibilidad en el tipo de datos que se pueden incluir con la etiqueta y puede resultar útil para rastrear información específica relacionada con un producto o servicio. Todos los atributos de usuario personalizados se envían automáticamente a Braze si el usuario marca la casilla **Send user attributes** en [la página de integración](https://app.adapty.io/integrations/braze). ## Configuración del SDK \{#sdk-configuration\} Para vincular perfiles de usuario en Adapty y Braze, debes configurar el SDK de Braze con el mismo ID de usuario que Adapty o usar su método `.changeUser()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers let braze = Braze(configuration: configuration) braze.changeUser(userId: "adapty_customer_user_id") ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Braze.getInstance(context).changeUser("adapty_customer_user_id") ``` </TabItem> </Tabs> --- # File: onesignal --- --- title: "OneSignal" description: "Integra OneSignal con Adapty para mejorar la interacción basada en notificaciones push." --- [OneSignal](https://onesignal.com/) es una plataforma líder de engagement con clientes que ofrece notificaciones push, correo electrónico, SMS y mensajería in-app. Integrar Adapty con OneSignal te permite acceder a todos tus eventos de suscripción en un solo lugar, lo que te permite activar comunicaciones automatizadas basadas en esos eventos. Con Adapty, puedes rastrear [eventos de suscripción](events) en múltiples stores, analizar el comportamiento de los usuarios y utilizar esos datos para una comunicación más personalizada. Esta integración te permite monitorear eventos de suscripción en tu dashboard de OneSignal y asociarlos con tus [campañas de adquisición](https://documentation.onesignal.com/docs/en/automated-messages). Adapty actualiza las etiquetas de OneSignal en función de los eventos de suscripción, lo que te permite enviar notificaciones push personalizadas con una configuración mínima. **Características de la integración** | Característica de integración | Descripción | | :---------------------------- | :----------------------------------------------------------- | | Frecuencia | Actualizaciones en tiempo real | | Dirección de datos | Unidireccional: de Adapty al servidor de OneSignal | | Punto de integración de Adapty | <ul><li>SDKs de OneSignal y Adapty en el código de la app móvil</li><li>Servidor de Adapty</li></ul>| ## Configurar la integración con One Signal \{#setting-up-one-signal-integration\} Para configurar la integración: 1. Abre [Integrations → OneSignal](https://app.adapty.io/integrations/onesignal) en tu Adapty Dashboard. <img src="/assets/shared/img/onesignal-on.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Activa el interruptor de la integración. 3. Introduce tu **OneSignal App ID**. Para configurar la integración con OneSignal, ve a [Integrations -> OneSignal](https://app.adapty.io/integrations/onesignal) en tu Adapty Dashboard, activa el interruptor y configura las credenciales de la integración. ## Obtén tu OneSignal App ID \{#retrieving-your-onesignal-app-id\} Encuentra tu **OneSignal App ID** en tu [OneSignal Dashboard](https://dashboard.onesignal.com/login): 1. Ve a **Settings** → **Keys & IDs**. <img src="/assets/shared/img/onesignal-dashboard.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Copia tu **OneSignal App ID** y pégalo en el campo **App ID** del Adapty Dashboard. <img src="/assets/shared/img/onesignal-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Puedes encontrar más información sobre el OneSignal ID en la [siguiente documentación.](https://documentation.onesignal.com/docs/en/keys-and-ids) ### Configuración de eventos \{#configuring-events\} Adapty te permite enviar tres grupos de eventos a OneSignal. Activa los que necesites en el Adapty Dashboard. Puedes consultar la lista completa de eventos disponibles con su descripción detallada [aquí](events). <img src="/assets/shared/img/onesignal.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty envía eventos de suscripción a OneSignal mediante una integración servidor a servidor, lo que te permite rastrear toda la actividad relacionada con suscripciones en OneSignal. :::warning A partir del 17 de abril de 2023, el plan gratuito de OneSignal ya no admite esta integración. Solo está disponible en los planes **Growth**, **Professional** y superiores. Para más información, consulta [Precios de OneSignal](https://onesignal.com/pricing). ::: ## Etiquetas personalizadas \{#custom-tags\} Esta integración actualiza y asigna diversas propiedades a tus usuarios de Adapty como etiquetas, que luego se envían a OneSignal. Consulta la lista de etiquetas a continuación para encontrar las que mejor se adapten a tus necesidades. :::warning OneSignal tiene un límite de etiquetas. Esto incluye tanto las etiquetas generadas por Adapty como cualquier etiqueta existente en OneSignal. Superar el límite puede causar errores al enviar eventos. ::: | Etiqueta | Tipo | Descripción | |---|----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `adapty_customer_user_id` | String | El identificador único del usuario en tu app. Debe ser coherente en tu sistema, Adapty y OneSignal. | | `adapty_profile_id` | String | El ID del perfil de usuario de Adapty, disponible en tu [Adapty Dashboard](profiles-crm). | | `environment` | String | `Sandbox` o `Production`, indica el entorno actual del usuario. | | `store` | String | Store donde se compró el producto. Opciones: **app_store**, **play_store**, **stripe** o el nombre de tu [store personalizada](custom-store). | | `vendor_product_id` | String | El ID del producto en el store (p. ej., `org.locals.12345`). | | `subscription_expires_at` | String | Fecha de expiración de la última suscripción (`YYYY-MM-DDTHH:MM:SS+0000`, p. ej., `2023-02-10T17:22:03.000000+0000`). | | `last_event_type` | String | El tipo de evento más reciente de la [lista de eventos de Adapty](events).<br/> Ten en cuenta lo siguiente:<br/>- Para el evento **Subscription expired**, Adapty envía la propiedad `last_event_type` como `subscription_cancelled`.<br/>- Para **Trial renew canceled** – como `auto_renew_off`<br/>- Para **Subscription renew canceled** – como `auto_renew_off_subscription` | | `purchase_date` | String | Fecha de la última transacción (`YYYY-MM-DDTHH:MM:SS+0000`, p. ej., `2023-02-10T17:22:03.000000+0000`). | | `active_subscription` | String | `true` si el usuario tiene una suscripción activa y `false` si la suscripción ha expirado. | | `period_type` | String | Indica el tipo de período más reciente para la compra o renovación. Valores posibles: `trial` para un período de prueba o `normal` para el resto de casos. | Todos los valores float se redondean a enteros. Los strings no cambian. Además de las etiquetas predefinidas, puedes enviar [atributos personalizados](segments#custom-attributes) como etiquetas, lo que da mayor flexibilidad en los datos que incluyes. Esto es útil para rastrear detalles específicos relacionados con tu producto o servicio. Los atributos de usuario personalizados se envían automáticamente a OneSignal si la casilla **Send user attributes** está marcada en la [página de integración](https://app.adapty.io/integrations/onesignal). Si está desmarcada, Adapty envía exactamente 10 etiquetas. Si está marcada, se pueden enviar más de 10 etiquetas, lo que permite una captura de datos más completa. ## Configuración del SDK \{#sdk-configuration\} Hay dos formas de integrar OneSignal con Adapty: 1. **Legacy (anterior a v5):** Usa `playerId` (obsoleto en [OneSignal SDK v5](https://github.com/OneSignal/OneSignal-iOS-SDK/releases/tag/5.0.0)). 2. **Actual (v5+):** Usa `subscriptionId`. :::warning Asegúrate de enviar `playerId` (para OneSignal SDK anterior a v5) o `subscriptionId` (para OneSignal SDK v5+) a Adapty. Sin esto, las etiquetas de OneSignal no se actualizarán y la integración no funcionará correctamente. ::: <Tabs groupId="current-version" queryString> <TabItem value="v5+" label="OneSignal SDK v5+ (actual)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers // SubscriptionID OneSignal.Notifications.requestPermission({ accepted in Task { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.oneSignalSubscriptionId(OneSignal.User.pushSubscription.id)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "one_signal_subscription_id", value: OneSignal.User.pushSubscription.id ) } }, fallbackToSettings: true) ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers // SubscriptionID val oneSignalSubscriptionObserver = object: IPushSubscriptionObserver { override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) { Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.current.id) { error -> if (error != null) { // handle the error } } } } ``` </TabItem> <TabItem value="java" label="(Android) Java" default> ```java showLineNumbers // SubscriptionID IPushSubscriptionObserver oneSignalSubscriptionObserver = state -> { Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.getCurrent().getId(), error -> { if (error != null) { // handle the error } }); }; ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers // 1. Since OneSignal.User.pushSubscription.id may return null if called too early, // OneSignal suggests to listen for the updates: OneSignal.User.pushSubscription.addObserver((state) { if (state.current.optedIn) { // now you can try to retrieve subscriptionId } }); // 2. Then you can push subscriptionId to Adapty: final subscriptionId = OneSignal.User.pushSubscription.id; if (subscriptionId != null) { await Adapty().setIntegrationIdentifier(key: "one_signal_subscription_id", value: subscriptionId); } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; using OneSignalSDK; var pushUserId = OneSignal.Default.PushSubscriptionState.userId; Adapty.SetIntegrationIdentifier( "one_signal_player_id", pushUserId, (error) => { // handle the error }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers OneSignal.User.pushSubscription.addEventListener('change', (subscription) => { const subscriptionId = subscription.current.id; if (subscriptionId) { adapty.setIntegrationIdentifier("one_signal_subscription_id", subscriptionId); } }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="pre-v5" label="OneSignal SDK v. up to 4.x (legacy)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers // PlayerID // in your OSSubscriptionObserver implementation func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges) { if let playerId = stateChanges.to.userId { Task { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.oneSignalPlayerId(playerId)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "one_signal_player_id", value: playerId ) } } } ``` ```java showLineNumbers // PlayerID OSSubscriptionObserver osSubscriptionObserver = stateChanges -> { if (stateChanges != null && stateChanges.to != null && stateChanges.to.userId != null) { String playerId = stateChanges.to.userId; Adapty.setIntegrationIdentifier("one_signal_player_id", playerId, error -> { if (error != null) { // handle the error } }); } }; ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers // PlayerID OSSubscriptionObserver osSubscriptionObserver = stateChanges -> { OSSubscriptionState to = stateChanges != null ? stateChanges.getTo() : null; String playerId = to != null ? to.getUserId() : null; if (playerId != null) { Adapty.setIntegrationIdentifier("one_signal_player_id", playerId, error -> { if (error != null) { // handle the error } }); } }; ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers // PlayerID (pre-v5 OneSignal SDK) // in your OSSubscriptionObserver implementation func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges) { if let playerId = stateChanges.to.userId { Task { try await Adapty.setIntegrationIdentifier( key: "one_signal_player_id", value: playerId ) } } } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers OneSignal.addSubscriptionObserver(event => { const playerId = event.to.userId; adapty.setIntegrationIdentifier("one_signal_player_id", playerId); }); ``` </TabItem> </Tabs> </TabItem> </Tabs> Lee más en la documentación de OneSignal: - [ID de suscripción push](https://documentation.onesignal.com/docs/en/mobile-sdk-reference#user-pushsubscription-id) - [Cambios en la suscripción push](https://documentation.onesignal.com/docs/en/mobile-sdk-reference#addobserver-push-subscription-changes) ## Gestión de múltiples dispositivos \{#dealing-with-multiple-devices\} Si un usuario tiene varios dispositivos, rastrear los eventos de compra y las suscripciones puede resultar complicado. OneSignal ofrece una forma de gestionar esto mediante [IDs de usuario externas](https://documentation.onesignal.com/docs/en/users). Para mantener la coherencia de los datos del usuario entre dispositivos: 1. Relaciona los distintos dispositivos en tu **servidor** y envía esos datos a OneSignal. 2. Usa el [customer_user_id](identifying-users) de Adapty como [externalUserId](https://documentation.onesignal.com/docs/en/users#external-id) en OneSignal. Si tu app no tiene un sistema de registro, considera usar otro identificador único que se mantenga consistente en todos los dispositivos del usuario. Es importante mantener la coherencia en el identificador de usuario en todos los dispositivos y actualizar OneSignal siempre que cambie el ID de un usuario. Esto simplifica el seguimiento de la actividad y las suscripciones de los usuarios, garantiza una mensajería coherente y permite un análisis más preciso y una mejor experiencia de usuario. Para más detalles, consulta la [documentación de ID de usuario externo](https://documentation.onesignal.com/docs/en/users) de OneSignal. --- # File: pushwoosh --- --- title: "Pushwoosh" description: "Integra Pushwoosh con Adapty para un seguimiento de notificaciones push sin fricciones." --- Adapty utiliza eventos de suscripción para actualizar las etiquetas de perfil de [Pushwoosh](https://www.pushwoosh.com/), de modo que puedas crear comunicaciones dirigidas con tus clientes mediante notificaciones push tras una configuración de integración rápida y sencilla como la que se describe a continuación. ## Cómo configurar la integración con Pushwoosh \{#how-to-set-up-pushwoosh-integration\} Para integrar Pushwoosh, ve a [**Integrations** -> **Pushwoosh**](https://app.adapty.io/integrations/pushwoosh), activa el interruptor y rellena los campos. En primer lugar, introduce las credenciales para establecer la conexión entre tus perfiles de Pushwoosh y Adapty. Se necesitan el App ID y el auth token de Pushwoosh. <img src="/assets/shared/img/64e48a1-CleanShot_2023-08-18_at_11.13.212x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. El **App ID** se encuentra en tu dashboard de Pushwoosh. <img src="/assets/shared/img/ee27687-CleanShot_2023-08-18_at_14.37.442x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. El **Auth token** se encuentra en la sección API Access dentro de la configuración de Pushwoosh. <img src="/assets/shared/img/50e634b-CleanShot_2023-08-18_at_14.35.022x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Eventos y etiquetas \{#events-and-tags\} Debajo de las credenciales encontrarás tres grupos de eventos que puedes enviar a Pushwoosh desde Adapty. Activa simplemente los que necesites. También puedes cambiar los nombres de los eventos antes de enviarlos a Pushwoosh. Consulta la lista completa de eventos que ofrece Adapty [aquí](events). <img src="/assets/shared/img/392dc31-screencapture-app-adapty-io-integrations-pushwoosh-2023-08-22-13_31_07.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty enviará los eventos de suscripción a Pushwoosh mediante una integración server-to-server, lo que te permitirá ver todos los eventos de suscripción en tu Pushwoosh Dashboard. :::note Etiquetas personalizadas Con Adapty también puedes usar tus propias etiquetas personalizadas para la integración con Pushwoosh. Consulta la lista de etiquetas que se muestra a continuación para determinar cuál se adapta mejor a tus necesidades. ::: | Etiqueta | Tipo | Valor | |---|----|-----| | `adapty_customer_user_id` | String | Contiene el valor del identificador único del usuario, que puede encontrarse en el lado de Pushwoosh. | | `adapty_profile_id` | String | Contiene el valor del identificador único del perfil de usuario de Adapty, que puede encontrarse en tu [dashboard](profiles-crm) de Adapty. | | `environment` | String | <p>Indica si el usuario opera en un entorno sandbox o de producción.</p><p></p><p>Los valores son `Sandbox` o `Production`.</p> | | `store` | String | <p>Contiene el nombre del store utilizado para realizar la compra.</p><p></p><p>Valores posibles:</p><p>`app_store` o `play_store`.</p> | | `vendor_product_id` | String | <p>Contiene el valor del Product ID en la store de Apple o Google.</p><p></p><p>p. ej., org.locals.12345</p> | | `subscription_expires_at` | String | <p>Contiene la fecha de expiración de la última suscripción.</p><p></p><p>El formato del valor es:</p><p>año-mes díaTHhora:minuto:segundo</p><p>p. ej., 2023-02-10T17:22:03.000000+0000</p> | | `last_event_type` | String | Indica el tipo del último evento recibido de la lista de [eventos estándar de Adapty](events) que has habilitado para la integración. | | `purchase_date` | String | <p>Contiene la fecha de la última transacción (compra original o renovación).</p><p></p><p>El formato del valor es:</p><p>año-mes díaTHhora:minuto:segundo</p><p>p. ej., 2023-02-10T17:22:03.000000+0000</p> | | `original_purchase_date` | String | <p>Contiene la fecha de la primera compra según la transacción.</p><p></p><p>El formato del valor es:</p><p>año-mes díaTHhora:minuto:segundo</p><p>p. ej., 2023-02-10T17:22:03.000000+0000</p> | | `active_subscription` | String | El valor se establecerá en `true` ante cualquier evento de compra o renovación, o en `false` si la suscripción ha expirado. | | `period_type` | String | <p>Indica el tipo de período más reciente para la compra o renovación.</p><p></p><p>Los valores posibles son</p><p>`trial` para un período de prueba o `normal` para el resto.</p> | Todos los valores float se redondearán a int. Las cadenas de texto permanecen igual. Además de la lista predefinida de etiquetas disponibles, es posible enviar [atributos personalizados](segments#custom-attributes) mediante etiquetas. Esto ofrece mayor flexibilidad en el tipo de datos que se pueden incluir y resulta útil para rastrear información específica relacionada con un producto o servicio. Todos los atributos personalizados de usuario se envían automáticamente a Pushwoosh si el usuario marca la casilla **Send user custom attributes** en [la página de integración](https://app.adapty.io/integrations/pushwoosh). ## Configuración del SDK \{#sdk-configuration\} Para vincular Adapty con Pushwoosh, necesitas enviarnos el valor `HWID`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "pushwoosh_hwid", value: Pushwoosh.sharedInstance().getHWID() ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().hwid) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().getHwid(), error -> { if (error != null) { // handle the error } }); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers final hwid = await Pushwoosh.getInstance.getHWID; try { await Adapty().setIntegrationIdentifier( key: "pushwoosh_hwid", value: hwid, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; Adapty.SetIntegrationIdentifier( "pushwoosh_hwid", Pushwoosh.Instance.HWID, (error) => { // handle the error }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // ... try { await adapty.setIntegrationIdentifier("pushwoosh_hwid", hwid); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> --- # File: slack --- --- title: "Slack" description: "Integra Slack con Adapty para recibir notificaciones en tiempo real sobre eventos de suscripción." --- [Slack](https://slack.com/) es una plataforma de mensajería y productividad para equipos que seguramente no necesita presentación. Con esta integración, recibirás notificaciones en Slack cada vez que Adapty registre un evento de ingresos. Esto es muy útil si quieres celebrar cada subida de tu MRR o si quieres estar al tanto de cancelaciones de pruebas, problemas de facturación, reembolsos y más. ## Cómo configurar la integración con Slack \{#how-to-set-up-slack-integration\} Necesitarás: - crear una app en tu workspace de Slack - darle permiso para publicar mensajes - y luego proporcionar la información necesaria a Adapty en [Integrations → Slack](https://app.adapty.io/integrations/slack). ### 1\. Crear una app en Slack \{#1-create-an-app-in-slack\} 1. Ve al [panel de Slack API](https://api.slack.com/apps) y crea una app así: <img src="/assets/shared/img/f43aedc-CleanShot_2024-01-04_at_18.27.412x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/08fa9e6-CleanShot_2024-01-04_at_18.28.142x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Dale cualquier nombre (`Adapty`, por ejemplo) y agrégala a tu workspace: <img src="/assets/shared/img/5002bb1-CleanShot_2024-01-04_at_18.29.132x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 2\. Dar permiso para publicar y obtener un token para tu app \{#2-give-permission-to-post-and-get-a-token-for-your-app\} Serás redirigido a la página de tu app en Slack. 1. Desplázate hacia abajo y haz clic en **Permissions**: <img src="/assets/shared/img/9750451-CleanShot_2024-01-04_at_18.48.072x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Tras la redirección, desplázate hacia abajo hasta **Scopes** y haz clic en **Add an OAuth Scope**: <img src="/assets/shared/img/db5b5f4-CleanShot_2024-01-04_at_18.50.262x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Otorga los permisos `chat:write`, `chat:write.public` y `chat:write.customize`. Estos son necesarios para publicar en tus canales y personalizar los mensajes: <img src="/assets/shared/img/d97ccb9-CleanShot_2024-01-04_at_18.51.572x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate de nuevo hasta la parte superior de la página y haz clic en **Install to Workspace**: <img src="/assets/shared/img/14608e3-CleanShot_2024-01-04_at_19.17.58.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en **Allow**: <img src="/assets/shared/img/143967e-CleanShot_2024-01-04_at_18.53.292x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Después de esto, serás redirigido a la misma página, pero ahora tendrás disponible un OAuth Token (`xoxb-...`). Esto es exactamente lo que necesitas para completar la configuración: <img src="/assets/shared/img/59b33ee-CleanShot_2024-01-04_at_18.55.222x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 3\. Configurar la integración en Adapty \{#3-configure-the-integration-in-adapty\} 1. Ve a [**Integrations** → **Slack**](https://app.adapty.io/integrations/slack): <img src="/assets/shared/img/b4ffd71-CleanShot_2024-01-04_at_19.05.222x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Pega el token `xoxb-...` del paso anterior y elige en qué canales publicará la app. Puedes configurar la integración para recibir eventos solo en producción, en sandbox o en ambos. También puedes elegir en qué moneda se mostrarán los mensajes (la original o convertida a USD). :::note Ten en cuenta que si quieres publicar mensajes de Adapty en un canal privado, deberás añadir manualmente la app `Adapty` que creaste en Slack a ese canal. De lo contrario, no funcionará. ::: 3. Por último, puedes elegir qué eventos quieres recibir en **Events**: <img src="/assets/shared/img/970a7bb-CleanShot_2024-01-04_at_19.09.472x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ¡Listo! Los eventos se enviarán a los canales que hayas especificado. Podrás ver los ingresos cuando corresponda y consultar el perfil del cliente en Adapty: <img src="/assets/shared/img/852b8c8-CleanShot_2024-01-04_at_19.11.332x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: s3-exports --- --- title: "Amazon S3" description: "Exporta datos de suscripción a S3 para análisis avanzados e informes." --- La integración de Adapty con Amazon S3 te permite almacenar de forma segura los datos de eventos y visitas a paywalls en un único lugar centralizado. Podrás guardar tus [eventos de suscripción](events) en tu bucket de Amazon S3 como archivos .csv. Para configurar esta integración, deberás seguir unos sencillos pasos en la consola de AWS y en el Adapty Dashboard. :::note Programación Adapty envía tus datos cada **24h** a las 4:00 UTC. Cada archivo contendrá los datos de los eventos creados durante el día calendario anterior completo en UTC. Por ejemplo, los datos exportados automáticamente a las 4:00 UTC del 8 de marzo contendrán todos los eventos creados el 7 de marzo desde las 00:00:00 hasta las 23:59:59 UTC. ::: ## Cómo configurar la integración con Amazon S3 \{#how-to-set-up-amazon-s3-integration\} Para empezar a recibir datos, necesitarás las siguientes credenciales: 1. Access key ID 2. Secret access key 3. S3 bucket name 4. Folder name inside the S3 bucket :::note Directorios anidados Puedes especificar directorios anidados en el campo S3 bucket name, por ejemplo: adapty-events/com.sample-app ::: Para integrar Amazon S3, ve a [**Integrations** -> **Amazon S3**](https://app.adapty.io/integrations/s3), activa el interruptor y rellena los campos. En primer lugar, introduce las credenciales para establecer la conexión entre Amazon S3 y los perfiles de Adapty. <img src="/assets/shared/img/2b1a6e3-CleanShot_2023-03-24_at_14.51.272x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> En el Adapty Dashboard, los siguientes campos son necesarios para configurar la conexión: | Campo | Descripción | | :--- | :--- | | **Access Key ID** | Identificador único que se usa para autenticar el acceso de un usuario o aplicación a un servicio de AWS. Encuéntralo en el [archivo csv](s3-exports#how-to-create-amazon-s3-credentials) descargado. | | **Secret Access Key** | Clave privada que se usa junto con el Access Key ID para autenticar el acceso de un usuario o aplicación a un servicio de AWS. Encuéntrala en el [archivo csv](s3-exports#how-to-create-amazon-s3-credentials) descargado. | | **S3 Bucket Name** | Nombre único a nivel global que identifica un bucket de S3 específico dentro de la nube de AWS. Los buckets de S3 son un servicio de almacenamiento simple que permite a los usuarios guardar y recuperar objetos de datos, como archivos e imágenes, en la nube. | | **Folder Inside the Bucker** | El nombre de la carpeta que quieres crear dentro del bucket de S3 seleccionado. Ten en cuenta que S3 simula carpetas mediante prefijos de clave de objeto, que son esencialmente nombres de carpetas. | ## Cómo crear credenciales de Amazon S3 \{#how-to-create-amazon-s3-credentials\} Esta guía te ayudará a crear las credenciales necesarias en tu consola de AWS. ### 1\. Crear política de acceso \{#create-access-policy\} Primero, ve al [Panel de políticas de IAM](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/policies) en tu consola de AWS y selecciona la opción **Create Policy**. <img src="/assets/shared/img/7af075c-CleanShot_2023-03-21_at_10.52.002x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> En el editor de políticas, pega el siguiente JSON y cambia `adapty-s3-integration-test` por el nombre de tu bucket: ```json showLineNumbers title="Json" { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowListObjectsInBucket", "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::adapty-s3-integration-test" }, { "Sid": "AllowAllObjectActions", "Effect": "Allow", "Action": "s3:*Object", "Resource": [ "arn:aws:s3:::adapty-s3-integration-test/*", "arn:aws:s3:::adapty-s3-integration-test" ] }, { "Sid": "AllowBucketLocation", "Effect": "Allow", "Action": "s3:GetBucketLocation", "Resource": "arn:aws:s3:::adapty-s3-integration-test" } ] } ``` <img src="/assets/shared/img/d4e474a-CleanShot_2023-03-21_at_10.56.212x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Una vez completada la configuración de la política, puedes añadir etiquetas (opcional) y hacer clic en **Next** para continuar con el paso final. En este paso, deberás asignar un nombre a tu política y simplemente hacer clic en el botón **Create policy** para finalizar el proceso de creación. <img src="/assets/shared/img/7dcb02f-CleanShot_2023-03-21_at_11.03.372x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 2\. Crear un usuario IAM \{#2-create-iam-user\} Para que Adapty pueda subir informes de datos brutos a tu bucket, deberás proporcionarles el Access Key ID y el Secret Access Key de un usuario con acceso de escritura al bucket específico. Para ello, ve a la consola de IAM y selecciona la [sección Users](https://console.aws.amazon.com/iamv2/home#/users). Desde allí, haz clic en el botón **Add users**. <img src="/assets/shared/img/bb612c8-CleanShot_2023-03-21_at_11.12.392x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Dale un nombre al usuario, elige **Access key – Programmatic access** y continúa con los permisos. <img src="/assets/shared/img/467ee4d-j6aoX.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Para el siguiente paso, selecciona la opción **Add user to group** y luego haz clic en el botón **Create group**. <img src="/assets/shared/img/bfd0e80-CleanShot_2023-03-21_at_11.24.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> A continuación, asigna un nombre a tu Grupo de Usuarios y selecciona la política que creaste anteriormente. Una vez seleccionada, haz clic en el botón **Create group** para completar el proceso. <img src="/assets/shared/img/df29c12-CleanShot_2023-03-21_at_11.28.052x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Una vez creado el grupo con éxito, **selecciónalo** y continúa con el siguiente paso. <img src="/assets/shared/img/1f3722e-CleanShot_2023-03-21_at_11.36.192x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Como este es el último paso de esta sección, puedes continuar haciendo clic en el botón **Create User**. <img src="/assets/shared/img/ea43722-CleanShot_2023-03-21_at_11.40.462x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Por último, puedes **descargar las credenciales en formato .csv** o copiarlas y pegarlas directamente desde el dashboard. <img src="/assets/shared/img/bcf35e1-S3created.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Exportación manual de datos \{#manual-data-export\} Además de la exportación automática de datos de eventos a Amazon S3, Adapty también ofrece una funcionalidad de exportación manual de archivos. Con esta función, puedes seleccionar un intervalo de tiempo específico para los datos de eventos y exportarlos a tu bucket de S3 de forma manual. Esto te da mayor control sobre los datos que exportas y cuándo los exportas. El rango de fechas especificado se usará para exportar los eventos creados desde la Fecha A 00:00:00 UTC hasta la Fecha B 23:59:59 UTC. <img src="/assets/shared/img/466bd29-CleanShot_2023-03-21_at_12.35.252x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Estructura de la tabla \{#table-structure\} En la integración con AWS S3, Adapty proporciona una tabla para almacenar datos históricos de eventos de transacciones y visitas a paywalls. La tabla contiene información sobre el perfil del usuario, los ingresos y beneficios, y el store de origen, entre otros datos. En esencia, estas tablas registran todas las transacciones generadas por una app durante un período de tiempo determinado. :::warning Ten en cuenta que esta estructura puede crecer con el tiempo, con nuevos datos introducidos por nosotros o por los terceros con los que trabajamos. Asegúrate de que tu código que la procesa sea lo suficientemente robusto y se base en los campos específicos, pero no en la estructura en su conjunto. ::: Aquí está la estructura de la tabla para los eventos: :::note Adapty convierte otras divisas a USD según el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo de cambio se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: | Columna | Descripción | |---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **profile_id** | ID de usuario de Adapty. | | **event_type** | Nombre del evento en minúsculas. Consulta la sección [Eventos](events) para conocer los tipos de eventos. | | **event_datetime** | Fecha en formato ISO 8601. | | **transaction_id** | Identificador único de una transacción, como una compra o renovación. | | **original_transaction_id** | Identificador de la transacción de compra original. | | **subscription_expires_at** | Fecha de vencimiento de la suscripción. Normalmente en el futuro. | | **environment** | Puede ser Sandbox o Production. | | **revenue_usd** | Ingresos en USD. Puede estar vacío. | | **proceeds_usd** | Ingresos netos en USD. Puede estar vacío. | | **net_revenue_usd** | Ingresos netos (tras impuestos) en USD. Puede estar vacío. | | **tax_amount_usd** | Importe deducido en concepto de impuestos en USD. Puede estar vacío. | | **revenue_local** | Ingresos en moneda local. Puede estar vacío. | | **proceeds_local** | Ingresos netos en moneda local. Puede estar vacío. | | **net_revenue_local** | Ingresos netos (tras impuestos) en moneda local. Puede estar vacío. | | **tax_amount_local** | Importe deducido en concepto de impuestos en moneda local. Puede estar vacío. | | **customer_user_id** | ID de usuario del desarrollador. Por ejemplo, puede ser tu UUID de usuario, email u otro identificador. Null si no lo has configurado. | | **store** | Puede ser _app_store_ o _play_store_. | | **product_id** | ID del producto en Apple App Store, Google Play Store o Stripe. | | **base_plan_id** | [ID del plan base](https://support.google.com/googleplay/android-developer/answer/12154973) en Google Play Store o [ID de precio](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) en Stripe. | | **developer_id** | ID del desarrollador (SDK) del paywall donde se originó la transacción. | | **ab_test_name** | Nombre de la prueba A/B donde se originó la transacción. | | **ab_test_revision** | Revisión de la prueba A/B donde se originó la transacción. | | **paywall_name** | Nombre del paywall donde se originó la transacción. | | **paywall_revision** | Revisión del paywall donde se originó la transacción. | | **profile_county** | País del perfil determinado por Adapty a partir de la IP. | | **install_date** | Fecha de instalación en formato ISO 8601. | | **idfv** | [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor) en dispositivos iOS | | **idfa** | [advertisingIdentifier](https://developer.apple.com/documentation/adsupport/asidentifiermanager/advertisingidentifier) en dispositivos iOS | | **advertising_id** | El Advertising ID es un código único asignado por el sistema operativo Android que los anunciantes pueden usar para identificar de forma única el dispositivo de un usuario. | | **ip_address** | IP del dispositivo (puede ser IPv4 o IPv6, con preferencia por IPv4 cuando esté disponible). Se actualiza cada vez que cambia la IP del dispositivo. | | **cancellation_reason** | <p>Motivo por el que el usuario canceló una suscripción.</p><p></p><p>Puede ser:</p><p>**iOS & Android** _voluntarily_cancelled_, _billing_error_, _refund_</p><p>**iOS** _price_increase_, _product_was_not_available_, _unknown_, _upgraded_</p><p>**Android** _new_subscription_replace_, _cancelled_by_developer_</p> | | **android_app_set_id** | Un [AppSetId](https://developer.android.com/design-for-safety/privacy-sandbox/reference/adservices/appsetid/AppSetId): ID único por dispositivo y por cuenta de desarrollador, restablecible por el usuario, para casos de uso publicitario sin monetización. | | **android_id** | En Android 8.0 (nivel de API 26) y versiones superiores, un número de 64 bits (expresado como cadena hexadecimal), único para cada combinación de clave de firma de la app, usuario y dispositivo. Para más detalles, consulta la [documentación para desarrolladores de Android](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID). | | **device** | Nombre del modelo de dispositivo visible para el usuario final. | | **currency** | Código de moneda de 3 letras (ISO-4217) de la transacción. | | **store_country** | País del perfil determinado por la store de Apple/Google. | | **attribution_source** | Fuente de atribución. | | **attribution_network_user_id** | ID asignado al usuario por la fuente de atribución. | | **attribution_status** | Puede ser organic, non_organic o unknown. | | **attribution_channel** | Nombre del canal de marketing. | | **attribution_campaign** | Nombre de la campaña de marketing. | | **attribution_ad_group** | Grupo de anuncios de atribución. | | **attribution_ad_set** | Conjunto de anuncios de atribución. | | **attribution_creative** | Palabra clave creativa de atribución. | | **attributes** | JSON de [atributos de usuario personalizados](setting-user-attributes#custom-user-attributes). Incluye los atributos personalizados que hayas configurado para enviar desde tu app móvil. Para enviarlo, activa la opción **Send User Attributes** en la página [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). | | **integration_ids** | Todos los IDs de integración asociados a un perfil. Diccionario. Ejemplo: {'mixpanel_user_id': 'mixpanelUserId-test', 'facebook_anonymous_id': 'facebookAnonymousId-test'} | Here is the table structure for the paywall visits: | Columna | Descripción | | :-------------------- | :------------------------------------------------------------------------------------------------------------------ | | **profile_id** | ID de usuario de Adapty. | | **customer_user_id** | ID de usuario del desarrollador. Por ejemplo, puede ser tu UUID de usuario, email o cualquier otro ID. Null si no lo configuraste. | | **profile_country** | País del perfil determinado por la store de Apple/Google. | | **install_date** | Fecha ISO 8601 en que se produjo la instalación. | | **store** | Puede ser _app_store_ o _play_store_. | | **paywall_showed_at** | La fecha en que el paywall se mostró al cliente. | | **developer_id** | ID de desarrollador (SDK) del paywall donde se originó la transacción. | | **ab_test_name** | Nombre de la prueba A/B donde se originó la transacción. | | **ab_test_revision** | Revisión de la prueba A/B donde se originó la transacción. | | **paywall_name** | Nombre del paywall donde se originó la transacción. | | **paywall_revision** | Revisión del paywall donde se originó la transacción. | ## Eventos y etiquetas \{#events-and-tags\} Puedes gestionar qué datos comunica la integración. La integración ofrece las siguientes opciones de configuración: | Parámetro | Descripción | | :--------------------------------- | :----------------------------------------------------------- | | **Exclude Historical Events** | Elige excluir los eventos que ocurrieron antes de que el usuario instalara la app con el SDK de Adapty. Esto evita la duplicación de eventos y garantiza informes precisos. Por ejemplo, si un usuario activó una suscripción mensual el 10 de enero y actualizó la app con el SDK de Adapty el 6 de marzo, Adapty omitirá los eventos anteriores al 6 de marzo y conservará los posteriores. | | **Include events without profile** | Elige incluir las transacciones que no están vinculadas a un perfil de usuario en Adapty. Esto puede incluir compras realizadas antes de instalar el SDK de Adapty o transacciones recibidas desde las notificaciones del servidor del store que no pueden asociarse inmediatamente a un usuario concreto. | | **Send User Attributes** | Si deseas enviar atributos específicos del usuario, como preferencias de idioma, y tu plan de OneSignal admite más de 10 etiquetas, selecciona esta opción. Al activarla, se permite incluir información adicional más allá de las 10 etiquetas predeterminadas. Ten en cuenta que superar los límites de etiquetas puede provocar errores. | <img src="/assets/shared/img/s3-settings.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Debajo de los ajustes de integración, hay tres grupos de eventos que puedes exportar, enviar y almacenar en Amazon S3 desde Adapty. Activa los que necesites. Consulta la lista completa de eventos que ofrece Adapty [aquí](events). <img src="/assets/shared/img/fd5ccb9-CleanShot_2023-08-17_at_14.49.282x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: google-cloud-storage --- --- title: "Google Cloud Storage" description: "Integra Google Cloud Storage con Adapty para almacenamiento seguro de datos." --- Activa la integración con Google Cloud Storage para almacenar de forma segura los [eventos de suscripción](events) y los [datos de visitas a paywalls](paywall-metrics) en un único lugar centralizado: tu bucket de Google Cloud Storage. Cada día a las 4AM UTC, Adapty subirá archivos .csv con los datos del día anterior a tus buckets. Puedes elegir si quieres recibir datos de **eventos**, datos de **visitas a paywalls**, o **ambos**. También puedes exportar estos datos [manualmente](#manual-data-export) en cualquier momento y para cualquier período de tiempo. Para configurar la integración, [genera una clave de acceso al bucket](#create-google-cloud-storage-credentials) en tu consola de Google Cloud y [agrégala a tu configuración de Adapty](#set-up-google-cloud-storage-integration). ## Programación y duración de las subidas \{#upload-schedule-and-duration\} Adapty sube datos a Google Cloud Storage cada 24 horas, a las 04:00 UTC. Los archivos contienen datos de los eventos creados durante el día natural anterior (UTC). El archivo subido el 8 de marzo incluirá todos los eventos creados el 7 de marzo, de 00:00:00 a 23:59:59 UTC. El proceso puede tardar varias horas, dependiendo del número total de archivos en cola y del volumen de datos que hayas solicitado. Si Adapty incluye datos históricos en tu primera subida, tardará más que las subidas diarias posteriores. ## Configurar la integración con Google Cloud Storage \{#set-up-google-cloud-storage-integration\} Necesitas una clave de cuenta de servicio de Google Cloud válida con **acceso de escritura**. Para generarla, sigue los pasos de la sección [crear credenciales](#create-google-cloud-storage-credentials). :::warning Puedes usar distintos buckets con diferentes credenciales para eventos y visitas a paywalls. Sin embargo, si **cualquiera** de las credenciales es inválida, [**ambas subidas fallarán**](#troubleshooting). ::: Ve a [**Integrations** -> **Google Cloud Storage**](https://app.adapty.io/integrations/google-cloud-storage) y abre la pestaña correspondiente (**Events** o **Paywall visits**). Activa la integración. Sube el archivo con tu **clave de cuenta de servicio de Google Cloud**. Especifica el **bucket** y la **carpeta** de destino. Guarda los cambios. ### Configuración opcional para datos de eventos \{#optional-settings-for-event-data\} Puedes especificar qué eventos incluir en el informe y definir nombres personalizados para ellos. Consulta el artículo de [eventos](events) para ver la lista completa de eventos disponibles. | Nombre | Valor predeterminado | Descripción | | ------------------------------ | ----------------- | ----------- | | Exclude historical events | true | Excluye información sobre eventos ocurridos antes de integrar el SDK de Adapty en tu app. <br /> <br />Si tu plataforma de analítica recibió eventos de suscripción **antes** de que empezaras a usar Adapty, esta opción evita que reciba eventos duplicados. <Details summary="Ejemplo práctico"><p>Un usuario compró una suscripción mensual el 10 de enero. La actualización del 1 de marzo de tu aplicación fue la primera en incluir el SDK de Adapty. <br /> <br /> Si este ajuste está **activado**, el informe no incluirá el evento "subscription started" de enero ni el evento "subscription renewed" de febrero. **Sí** incluirá el evento "subscription renewed" del 10 de marzo.</p> </Details> | | Include events without profile | false | Incluye transacciones que no están vinculadas a un perfil de usuario o que no pueden asociarse de inmediato a un usuario concreto. Pueden ser compras realizadas antes de instalar el SDK de Adapty, o transacciones recibidas mediante notificaciones del servidor. | | Send user attributes | false | Incluye [atributos personalizados del usuario](setting-user-attributes), como datos del usuario y de uso de la app. Selecciona esta opción si tu plan de OneSignal admite más de 10 etiquetas. Ten en cuenta que superar los límites de etiquetas puede generar errores. | ## Crear credenciales de Google Cloud Storage \{#create-google-cloud-storage-credentials\} Esta guía te ayudará a crear las credenciales necesarias en Google Cloud Platform Console. Para que Adapty pueda subir informes de datos sin procesar a tu bucket, se necesita la clave de cuenta de servicio y acceso de escritura al bucket correspondiente. Al proporcionar la clave de cuenta de servicio y conceder acceso de escritura al bucket, permites que Adapty transfiera de forma segura y eficiente los informes de datos desde su plataforma a tu entorno de almacenamiento. :::warning Ten en cuenta que solo admitimos la autorización mediante clave HMAC de cuenta de servicio, por lo que es fundamental asegurarse de que tu clave HMAC de cuenta de servicio tenga los roles "Storage Object Viewer", "Storage Legacy Bucket Writer" y "Storage Object Creator" asignados para habilitar el acceso correcto a Google Cloud Storage. ::: 1. En el primer paso, ve a la sección [IAM](https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts) de tu cuenta de Google Cloud y elige el proyecto correspondiente o crea uno nuevo. 1. A continuación, crea una nueva cuenta de servicio para Adapty haciendo clic en el botón "+ CREATE SERVICE ACCOUNT". 2. Rellena los campos del primer paso, ya que el acceso se concederá en una etapa posterior. Para obtener más detalles sobre esta página, consulta la documentación [aquí](https://docs.cloud.google.com/iam/docs/service-accounts-create). 3. Para crear y descargar una [clave JSON privada](https://docs.cloud.google.com/iam/docs/keys-create-delete), ve a la sección KEYS y haz clic en el botón "ADD KEY". 4. En la sección DETAILS, localiza el valor Email vinculado a la cuenta de servicio recién creada y cópialo. Esta información será necesaria en los pasos siguientes para autorizar la cuenta y permitirle escribir en el bucket. 5. A continuación, ve a la página de [Buckets](https://console.cloud.google.com/storage/browser) de Google Cloud Storage y selecciona un bucket existente o crea uno nuevo para almacenar los informes de datos de eventos o visitas de Adapty. Luego ve a la sección PERMISSIONS y selecciona la opción [GRANT ACCESS](https://docs.cloud.google.com/identity/docs/how-to?hl=en). 6. En la sección PERMISSIONS, introduce el Email de la cuenta de servicio obtenido en el quinto paso, selecciona el rol Storage Object Creator y haz clic en SAVE para aplicar los cambios. Recuerda guardar el nombre del bucket para consultarlo más adelante. ## Exportación manual de datos \{#manual-data-export\} Además de la exportación automática de datos de eventos a Google Cloud Storage, Adapty también ofrece una función de exportación manual de archivos. Con esta función, puedes seleccionar un intervalo de tiempo específico para los datos de eventos y exportarlos manualmente a tu bucket de GCS. Esto te da un mayor control sobre los datos que exportas y cuándo los exportas. El rango de fechas especificado se usará para exportar los eventos creados desde la Fecha A 00:00:00 UTC hasta la Fecha B 23:59:59 UTC. ## Estructura de datos \{#data-structure\} Adapty usa archivos `.csv` para exportar datos en formato tabular. :::warning El contenido de los eventos puede aumentar con el tiempo, con nuevos datos introducidos por nosotros o por terceros con los que trabajamos. Asegúrate de que el código que los procesa sea lo suficientemente robusto y se base en campos concretos, no en la estructura como un todo. ::: ### Eventos \{#events\} Puedes [modificar](#optional-settings-for-event-data) la lista de eventos que se incluyen en tus informes. :::note Adapty convierte otras divisas a USD según el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo de cambio se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: | Columna | Descripción | |------|-----------| | **profile_id** | ID de usuario de Adapty. | | **event_type** | Nombre del evento en minúsculas. Consulta la sección [Eventos](events) para conocer los tipos de eventos. | | **event_datetime** | Fecha en formato ISO 8601. | | **transaction_id** | Identificador único de una transacción, como una compra o renovación. | | **original_transaction_id** | Identificador de la transacción de compra original. | | **subscription_expires_at** | Fecha de expiración de la suscripción. Generalmente en el futuro. | | **environment** | Puede ser Sandbox o Production. | | **revenue_usd** | Ingresos en USD. Puede estar vacío. | | **proceeds_usd** | Ganancias en USD. Puede estar vacío. | | **net_revenue_usd** | Ingresos netos (después de impuestos) en USD. Puede estar vacío. | | **tax_amount_usd** | Importe descontado por impuestos en USD. Puede estar vacío. | | **revenue_local** | Ingresos en moneda local. Puede estar vacío. | | **proceeds_local** | Ganancias en moneda local. Puede estar vacío. | | **net_revenue_local** | Ingresos netos (después de impuestos) en moneda local. Puede estar vacío. | | **tax_amount_local** | Importe descontado por impuestos en moneda local. Puede estar vacío. | | **customer_user_id** | ID de usuario del desarrollador. Por ejemplo, puede ser tu UUID de usuario, email u otro identificador. Nulo si no lo has definido. | | **store** | Puede ser *app_store* o *play_store*. | | **product_id** | ID del producto en Apple App Store, Google Play Store o Stripe. | | **base_plan_id** | [ID del plan base](https://support.google.com/googleplay/android-developer/answer/12154973) en Google Play Store o [ID de precio](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) en Stripe. | | **developer_id** | ID de desarrollador (SDK) del paywall donde se originó la transacción. | | **ab_test_name** | Nombre de la prueba A/B donde se originó la transacción. | | **ab_test_revision** | Revisión de la prueba A/B donde se originó la transacción. | | **paywall_name** | Nombre del paywall donde se originó la transacción. | | **paywall_revision** | Revisión del paywall donde se originó la transacción. | | **profile_country** | País del perfil determinado por Adapty, basado en la IP. | | **install_date** | Fecha en formato ISO 8601 de cuando ocurrió la instalación. | | **idfv** | [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor) en dispositivos iOS. | | **idfa** | [advertisingIdentifier](https://developer.apple.com/documentation/adsupport/asidentifiermanager/advertisingidentifier) en dispositivos iOS. | | **advertising_id** | El Advertising ID es un código único asignado por el sistema operativo Android que los anunciantes pueden usar para identificar de forma única el dispositivo de un usuario. | | **ip_address** | IP del dispositivo (puede ser IPv4 o IPv6, con preferencia por IPv4 cuando esté disponible). Se actualiza cada vez que cambia la IP del dispositivo. | | **cancellation_reason** | <p>El motivo por el que el usuario canceló una suscripción.</p><p></p><p>Valores posibles:</p><p>**iOS y Android** — *voluntarily_cancelled*, *billing_error*, *refund*</p><p>**Solo iOS** — *price_increase*, *product_was_not_available*, *unknown*, *upgraded*</p><p>**Solo Android** — *new_subscription_replace*, *cancelled_by_developer*</p> | | **android_app_set_id** | Un [AppSetId](https://developer.android.com/design-for-safety/privacy-sandbox/reference/adservices/appsetid/AppSetId): ID único por dispositivo y cuenta de desarrollador, restablecible por el usuario, para casos de uso publicitario sin monetización. | | **android_id** | En Android 8.0 (nivel de API 26) y versiones superiores, un número de 64 bits (expresado como cadena hexadecimal), único para cada combinación de clave de firma de app, usuario y dispositivo. Para más detalles, consulta la [documentación para desarrolladores de Android](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID). | | **device** | Nombre del modelo de dispositivo visible para el usuario final. | | **currency** | Código de divisa de 3 letras (ISO-4217) de la transacción. | | **store_country** | País del perfil determinado por Apple/Google store. | | **attribution_source** | Fuente de atribución. | | **attribution_network_user_id** | ID asignado al usuario por la fuente de atribución. | | **attribution_status** | Puede ser organic, non_organic o unknown. | | **attribution_channel** | Nombre del canal de marketing. | | **attribution_campaign** | Nombre de la campaña de marketing. | | **attribution_ad_group** | Grupo de anuncios de atribución. | | **attribution_ad_set** | Conjunto de anuncios de atribución. | | **attribution_creative** | Palabra clave creativa de atribución. | | **attributes** | JSON con los [atributos personalizados del usuario](setting-user-attributes#custom-user-attributes). Incluirá todos los atributos personalizados que hayas configurado para enviar desde tu app móvil. Para enviarlo, activa la opción **Send User Attributes** en la página [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). | | **integration_ids** | Todos los IDs de integración asociados a un perfil. Diccionario. Ejemplo: {'mixpanel_user_id': 'mixpanelUserId-test', 'facebook_anonymous_id': 'facebookAnonymousId-test'} | ### Visitas a paywalls \{#paywall-visits\} | Columna | Descripción | | :-------------------- | :----------------------------------------------------------------------------------------------------------- | | **profile_id** | ID de usuario de Adapty. | | **customer_user_id** | ID de usuario del desarrollador. Por ejemplo, puede ser tu UUID de usuario, email u otro identificador. Nulo si no lo has definido. | | **profile_country** | País del perfil determinado por Apple/Google store. | | **install_date** | Fecha en formato ISO 8601 de cuando ocurrió la instalación. | | **store** | Puede ser *app_store* o *play_store*. | | **paywall_showed_at** | La fecha en que se mostró el paywall al cliente. | | **developer_id** | ID de desarrollador (SDK) del paywall donde se originó la transacción. | | **ab_test_name** | Nombre de la prueba A/B donde se originó la transacción. | | **ab_test_revision** | Revisión de la prueba A/B donde se originó la transacción. | | **paywall_name** | Nombre del paywall donde se originó la transacción. | | **paywall_revision** | Revisión del paywall donde se originó la transacción. | ## Solución de problemas \{#troubleshooting\} Adapty comprueba la validez de tus claves de acceso **antes** de comenzar la subida. Aunque solo una de tus claves de Google Cloud Storage sea inválida, Adapty **cancela la subida** y genera un error. Para garantizar subidas ininterrumpidas, reemplaza tus claves antes de que expiren. Si actualizas la clave de **eventos**, no olvides actualizar también la clave de **visitas a paywalls**, y viceversa. --- # File: webhook-event-types-and-fields --- --- title: "Tipos de eventos y campos de webhook" description: "" --- Adapty envía webhooks en respuesta a eventos de suscripción. Esta sección define estos tipos de eventos y los datos que contiene cada webhook. ## Tipos de eventos de webhook \{#webhook-event-types\} Puedes enviar todos los tipos de eventos a tu webhook o elegir solo algunos. Consulta nuestros [Flujos de eventos](event-flows) para saber qué tipo de datos entrantes esperar y cómo construir tu lógica de negocio en torno a ellos. Puedes desactivar los tipos de eventos que no necesites cuando [configures tu integración de Webhook](set-up-webhook-integration#configure-webhook-integration-in-the-adapty-dashboard). También puedes reemplazar los IDs de eventos predeterminados de Adapty por los tuyos propios si es necesario. | Nombre del evento | Descripción | |:-----------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | subscription_started | Se activa cuando un usuario activa una suscripción de pago sin período de prueba, es decir, se le cobra de inmediato. | | subscription_renewed | Ocurre cuando se renueva una suscripción y se cobra al usuario. Este evento comienza a partir de la segunda facturación, tanto en suscripciones con prueba como sin ella. | | subscription_renewal_cancelled | El usuario ha desactivado la renovación automática de la suscripción. El usuario conserva el acceso a las funciones premium hasta el final del período de suscripción pagado. | | subscription_renewal_reactivated | Se activa cuando un usuario reactiva la renovación automática de la suscripción. | | subscription_expired | Se activa cuando una suscripción finaliza por completo tras ser cancelada. Por ejemplo, si un usuario cancela una suscripción el 12 de diciembre pero esta permanece activa hasta el 31 de diciembre, el evento se registra el 31 de diciembre cuando la suscripción expira. | | subscription_paused | Ocurre cuando un usuario activa la [pausa de suscripción](https://developer.android.com/google/play/billing/lifecycle/subscriptions#pause) (solo Android). | | subscription_deferred | Se activa cuando una compra de suscripción se [aplaza](https://adapty.io/glossary/subscription-purchase-deferral/), lo que permite a los usuarios retrasar el pago manteniendo el acceso a las funciones premium. Esta función está disponible a través de la Google Play Developer API y puede usarse para pruebas gratuitas o para ayudar a usuarios con dificultades económicas. | | non_subscription_purchase | Cualquier compra que no sea una suscripción, como el acceso de por vida o productos consumibles como monedas del juego. | | trial_started | Se activa cuando un usuario activa una suscripción de prueba. | | trial_converted | Ocurre cuando finaliza una prueba y se cobra al usuario (primera compra). Por ejemplo, si un usuario tiene una prueba hasta el 14 de enero pero se le cobra el 7 de enero, este evento se registra el 7 de enero. | | trial_renewal_cancelled | El usuario desactivó la renovación automática de la suscripción durante el período de prueba. El usuario conserva el acceso a las funciones premium hasta que finalice la prueba, pero no se le cobrará ni comenzará una suscripción. | | trial_renewal_reactivated | Ocurre cuando un usuario reactiva la renovación automática de la suscripción durante el período de prueba. | | trial_expired | Se activa cuando finaliza una prueba sin convertirse en suscripción. | | entered_grace_period | Ocurre cuando falla un intento de pago y el usuario entra en un período de gracia (si está habilitado). El usuario conserva el acceso premium durante este tiempo. | | billing_issue_detected | Se activa cuando ocurre un problema de facturación durante un intento de cobro (p. ej., saldo insuficiente en la tarjeta). | | subscription_refunded | Se activa cuando se reembolsa una suscripción (p. ej., por parte del soporte de Apple). | | non_subscription_purchase_refunded | Se activa cuando se reembolsa una compra que no es una suscripción. | | access_level_updated | Ocurre cuando se actualiza el nivel de acceso de un usuario. | :::note `subscription_renewal_reactivated` lleva el ID del producto **anterior** — el que estaba activo cuando el usuario canceló — incluso si el usuario reactivó la suscripción comprando un producto diferente. Apple mantiene el mismo `original_transaction_id` a lo largo de la cadena cancelación → reactivación, por lo que este evento refleja el producto original. El nuevo producto aparece en el siguiente evento `subscription_renewed`, cuando comienza la facturación del nuevo producto. ::: ## Estructura de los eventos de webhook \{#webhook-event-structure\} Adapty solo te enviará los eventos que hayas seleccionado en la sección **Events names** de la página [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). Los eventos del webhook se serializan en JSON. El cuerpo de la solicitud `POST` enviada a tu servidor contendrá el evento serializado dentro de la estructura que se muestra a continuación. Todos los eventos siguen la misma estructura, aunque sus campos varían según el tipo de evento, el store y tu configuración específica. Los atributos de usuario son los [atributos de usuario personalizados](setting-user-attributes#custom-user-attributes) que hayas configurado, por lo que contienen lo que tú hayas definido. Los campos de datos de atribución también son iguales para todos los tipos de eventos; sin embargo, la lista de atribuciones dependerá de las fuentes de atribución que uses en tu app. A continuación puedes ver un ejemplo de evento: ```json title="Json" showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem", "idfv": "00000000-0000-0000-0000-000000000000", "idfa": "00000000-0000-0000-0000-000000000000", "advertising_id": "00000000-0000-0000-0000-000000000000", "profile_install_datetime": "2000-01-31T00:00:00.000000+0000", "user_agent": "ExampleUserAgent/1.0 (Device; OS Version) Browser/Engine", "email": "john.doe@company.com", "event_type": "subscription_started", "event_datetime": "2000-01-31T00:00:00.000000+0000", "event_properties": { "store": "play_store", "currency": "USD", "price_usd": 4.99, "profile_id": "00000000-0000-0000-0000-000000000000", "cohort_name": "All Users", "environment": "Production", "price_local": 4.99, "original_price_usd": 4.99, "original_price_local": 4.99, "discount_amount_usd": 0, "discount_amount_local": 0, "base_plan_id": "b1", "developer_id": "onboarding_placement", "ab_test_name": "onboarding_ab_test", "ab_test_revision": 1, "paywall_name": "UsedPaywall", "proceeds_usd": 4.2315, "variation_id": "00000000-0000-0000-0000-000000000000", "purchase_date": "2024-11-15T10:45:36.181000+0000", "store_country": "AR", "event_datetime": "2000-01-31T00:00:00.000000+0000", "proceeds_local": 4.2415, "tax_amount_usd": 0, "transaction_id": "0000000000000000", "net_revenue_usd": 4.2415, "profile_country": "AR", "paywall_revision": "1", "profile_event_id": "00000000-0000-0000-0000-000000000000", "tax_amount_local": 0, "net_revenue_local": 4.2415, "vendor_product_id": "onemonth_no_trial", "profile_ip_address": "10.10.1.1", "consecutive_payments": 1, "rate_after_first_year": false, "original_purchase_date": "2000-01-31T00:00:00.000000+0000", "original_transaction_id": "0000000000000000", "subscription_expires_at": "2000-01-31T00:00:00.000000+0000", "profile_has_access_level": true, "profile_total_revenue_usd": 4.99, "promotional_offer_id": null, "store_offer_category": null, "store_offer_discount_type": null }, "event_api_version": 1, "profiles_sharing_access_level": [{"profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem"}], "attributions": { "appsflyer": { "ad_set": "Keywords 1.12", "status": "non_organic", "channel": "Google Ads", "ad_group": null, "campaign": "Social media influencers - Rest of the world", "creative": null, "created_at": "2000-01-31T00:00:00.000000+0000" } }, "user_attributes": {"Favourite_color": "Violet", "Pet_name": "Fluffy"}, "integration_ids": {"firebase_app_instance_id": "val1", "branch_id": "val2", "one_signal_player_id": "val3"}, "play_store_purchase_token": { "product_id": "product_123", "purchase_token": "token_abc_123", "is_subscription": true } } ``` ### Campos del evento \{#event-fields\} Los parámetros de evento son los mismos para todos los tipos de evento. | **Campo** | **Tipo** | **Descripción** | |---|---|---| | **advertising_id** | UUID | ID de publicidad (solo Android). | | **attributions** | JSON | [Datos de atribución](webhook-event-types-and-fields#attributions). Se incluye si **Send Attribution** está habilitado en los [ajustes del Webhook](https://app.adapty.io/integrations/customwebhook). | | **customer_user_id** | String | ID de usuario de tu app (UUID, email u otro ID) si lo has definido en el código de tu app al [identificar usuarios](ios-quickstart-identify). Si no identificas usuarios en el código de la app o este usuario en concreto es anónimo (no ha iniciado sesión), este campo es `null`. | | **email** | String | Email del usuario si lo has definido mediante el método [`updateProfile`](setting-user-attributes) del SDK de Adapty o al crear/actualizar perfiles a través de la API del servidor. Si no pasas el valor `email` al SDK o al método de la API, este campo es `null`. | | **event_api_version** | Integer | Versión de la API de Adapty (actual: `1`). | | **event_datetime** | ISO 8601 | La hora efectiva (de negocio) del evento, como la fecha de compra para una compra o la fecha de expiración para una expiración — no cuando Adapty recibió o envió el evento. Formato [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) (p. ej., `2020-07-10T15:00:00.000000+0000`). Consulta la nota a continuación sobre el orden. | | **event_properties** | JSON | [Propiedades del evento](webhook-event-types-and-fields#event-properties). | | **event_type** | String | Nombre del evento en formato Adapty. Consulta [Tipos de eventos de Webhook](webhook-event-types-and-fields#webhook-event-types) para ver la lista completa. | | **idfa** | UUID | ID de publicidad (solo Apple). **IDFA** en el perfil en el [Adapty Dashboard](https://app.adapty.io/profiles/users). Puede ser `null` si no está disponible debido a restricciones de seguimiento, modo infantil o ajustes de privacidad. | | **idfv** | UUID | Identificador para proveedores (IDFV), único por desarrollador. **IDFV** en el perfil en el [Adapty Dashboard](https://app.adapty.io/profiles/users). | | **integration_ids** | JSON | IDs de integración del usuario si los has definido mediante el método `setIntegrationIdentifier` del SDK de Adapty o al crear/actualizar perfiles a través de la API del servidor. `null` si no están disponibles o las integraciones están deshabilitadas. | | **play_store_purchase_token** | JSON | [Token de compra de Play Store](webhook-event-types-and-fields#play-store-purchase-token), incluido si **Send Play Store purchase token** está habilitado en los [ajustes del Webhook](https://app.adapty.io/integrations/customwebhook). | | **profile_id** | UUID | ID de perfil generado automáticamente por Adapty para cada perfil. Un mismo ID de Apple/Google puede estar asociado a diferentes IDs de perfil si no identificas usuarios o permites compras antes del inicio de sesión. Más información sobre [cómo funciona Adapty con perfiles padre/heredero](how-profiles-work#parent-and-inheritor-profiles). | | **profile_install_datetime** | ISO 8601 | Marca de tiempo de instalación en formato [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) (p. ej., `2020-07-10T15:00:00.000000+0000`). | | **profiles_sharing_access_level** | JSON | Lista de usuarios que [comparten el nivel de acceso](general#6-sharing-paid-access-between-user-accounts) excluyendo el perfil de usuario actual. Si el uso compartido de niveles de acceso está habilitado en tu app, esta lista incluye otros perfiles que se han usado con el mismo ID de Apple/Google.<br/>Formato: <ul><li>**profile_id**: (UUID) ID de Adapty</li><li>**customer_user_id**: (String) Customer User ID si se ha proporcionado</li></ul> | | **user_agent** | String | User-agent del navegador del dispositivo. | | **user_attributes** | JSON | Datos personalizados que puedes definir para enriquecer los perfiles de usuario con información específica de la app. Se usan habitualmente para registrar preferencias del usuario (p. ej., tema, idioma) o indicadores de comportamiento (onboarding completado, uso de funcionalidades). <br/>Con formato de pares clave-valor donde las claves son cadenas y los valores pueden ser cadenas o números (p. ej., `{"Favourite_color": "Violet", "Pet_name": "Fluffy"}`). <br/>Puedes definir atributos personalizados manualmente en el Adapty Dashboard para perfiles individuales, de forma programática mediante el método `updateProfile` del SDK de Adapty, o a través de la API del servidor al crear/actualizar perfiles. <br/>Se incluye si **Send User Attributes** está habilitado en los [ajustes del Webhook](https://app.adapty.io/integrations/customwebhook). <p>Aunque los valores de atributos personalizados en el código de la app para móvil pueden definirse como floats o strings, los atributos recibidos a través de la API del servidor o importación histórica pueden llegar en distintos formatos. En ese caso, los valores booleanos y enteros se convertirán a floats.</p> | :::note `event_datetime` refleja cuándo ocurrió un evento en el ciclo de vida de la suscripción, no cuándo Adapty lo procesó o entregó. Por este motivo, varios eventos pueden compartir el mismo `event_datetime` o llegar fuera de orden cronológico. Por ejemplo, un evento `subscription_expired` puede tener un `event_datetime` anterior al de un evento `subscription_renewal_cancelled` que Adapty entrega antes que él. No uses `event_datetime` para ordenar eventos. En su lugar, ordénalos por tu propio tiempo de recepción y elimina duplicados usando `profile_event_id` o los IDs de transacción. ::: ### Atribuciones \{#attributions\} Para enviar los datos de atribución, activa la opción **Send Attribution** en la página [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). Si has activado el envío de datos de atribución y tienes configuradas las [integraciones de atribución](attribution-integration), los datos a continuación se enviarán con el evento para cada fuente. Los mismos datos de atribución se envían a todos los tipos de eventos. ```json title="Json" showLineNumbers { "attributions": { "appsflyer": { "ad_set": "sample_ad_set_123", "status": "non_organic", "channel": "sample_channel", "ad_group": "sample_ad_group_456", "campaign": "sample_ios_campaign", "creative": "sample_creative_789", "created_at": "2000-01-31T00:00:00.000000+0000", "network_user_id": "0000000000000-0000000" } } } ``` | Nombre del campo | Tipo de campo | Descripción | | :------------------ | :------------ | :------------------------------------------------- | | **ad_set** | String | Conjunto de anuncios de atribución. | | **status** | String | Puede ser `organic`, `non_organic,` o `unknown`. | | **channel** | String | Nombre del canal de marketing. | | **ad_group** | String | Grupo de anuncios de atribución. | | **campaign** | String | Nombre de la campaña de marketing. | | **creative** | String | Palabra clave creativa de atribución. | | **created_at** | Fecha ISO 8601 | Fecha y hora de creación del registro de atribución. | | **network_user_id** | String | ID asignado al usuario por la fuente de atribución. | ### IDs de integración \{#integration-ids\} Los siguientes IDs de integración se utilizan actualmente en los eventos: - `adjust_device_id` - `airbridge_device_id` - `amplitude_device_id` - `amplitude_user_id` - `appmetrica_device_id` - `appmetrica_profile_id` - `appsflyer_id` - `branch_id` - `facebook_anonymous_id` - `firebase_app_instance_id` - `mixpanel_user_id` - `pushwoosh_hwid` - `one_signal_player_id` - `one_signal_subscription_id` - `tenjin_analytics_installation_id` - `posthog_distinct_user_id` ### Token de compra de Play Store \{#play-store-purchase-token\} Este campo incluye todos los datos necesarios para revalidar una compra si es preciso. Solo se envía si la opción **Send Play Store purchase token** está habilitada en la [configuración de la integración de Webhook](https://app.adapty.io/integrations/customwebhook). | Campo | Tipo | Descripción | | :------------------ | :------ | :----------------------------------------------------------- | | **product_id** | String | El identificador único del producto (SKU) comprado en Play Store. | | **purchase_token** | String | Token generado por Google Play para identificar de forma única esta transacción de compra. | | **is_subscription** | Boolean | Indica si el producto comprado es una suscripción (`true`) o una compra única (`false`). | ### Propiedades de eventos \{#event-properties\} Las propiedades de los eventos pueden variar según el tipo de evento e incluso entre eventos del mismo tipo. Por ejemplo, un evento originado en el App Store no incluirá propiedades específicas de Android como `base_plan_id`. El evento [Nivel de acceso actualizado](webhook-event-types-and-fields#for-access-level-updated-event) tiene propiedades específicas, por lo que le hemos dedicado una sección aparte. Del mismo modo, hemos separado las [Propiedades adicionales de eventos fiscales y de ingresos](webhook-event-types-and-fields#additional-tax-and-revenue-event-properties), ya que son exclusivas de ciertos tipos de eventos. #### Para la mayoría de los tipos de eventos \{#for-most-event-types\} Las propiedades de los eventos son consistentes para la mayoría de los tipos de eventos (excepto para el evento **Access Level Updated**, que se describe en su propia sección). A continuación se muestra una tabla completa con las propiedades e indicaciones sobre si pertenecen a eventos específicos. :::note Adapty convierte otras divisas a USD según el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo de cambio se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: | Campo | Tipo | Descripción | |:------------------------------|:--------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **ab_test_name** | String | Nombre de la [prueba A/B de Adapty](ab-tests) en la que se originó la transacción. | | **ab_test_revision** | Integer | Revisión de la prueba A/B en la que se originó la transacción. | | **base_plan_id** | String | [ID del plan base](https://support.google.com/googleplay/android-developer/answer/12154973) en Google Play Store o [ID de precio](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) en Stripe. | | **cancellation_reason** | String | <p>Posibles motivos de cancelación: `voluntarily_cancelled`, `billing_error`, `price_increase`, `product_was_not_available`, `refund`, `cancelled_by_developer`, `new_subscription_replace`, `upgraded`, `unknown`, `adapty_revoked`.</p><p>Presente en los siguientes tipos de evento:</p>`subscription_cancelled`, `subscription_refunded` y `trial_cancelled`. | | **cohort_name** | String | El nombre de la [audiencia](audience) que determinó qué paywall se mostró al usuario. | | **consecutive_payments** | Integer | El número de períodos que un usuario lleva suscrito sin interrupciones. Incluye el período actual. | | **currency** | String | Moneda local. | | **developer_id** | String | El ID del [placement](placements) en el que se originó la transacción. | | **discount_amount_local** | Float | El descuento aplicado a la transacción: el precio estándar menos el importe realmente cobrado, antes de la comisión de Apple/Google, en moneda local. `0` para una compra a precio completo. En una prueba gratuita, equivale al precio estándar completo (`original_price_local`), ya que no se cobra nada. `null` cuando se aplicó una oferta pero el precio estándar es desconocido (véase `original_price_local`). Siempre `null` para las ofertas de pago anticipado de App Store: el cargo único cubre varios períodos de facturación, por lo que no puede compararse con el precio estándar por período. | | **discount_amount_usd** | Float | El valor de `discount_amount_local` en USD. | | **environment** | String | Los valores posibles son `Sandbox` o `Production`. | | **event_datetime** | ISO 8601 date | La fecha y hora del evento. Igual que en el nivel raíz del evento. | | **original_price_local** | Float | El precio estándar del producto sin descuento antes de la comisión de Apple/Google, en moneda local. En las suscripciones, este es el precio de renovación. Equivale a `price_local` en una compra a precio completo y siempre equivale a `price_local` en compras únicas, ya que las stores no reportan un precio estándar separado para ellas. `null` para una compra con descuento cuando la store no reporta un precio estándar fiable (por ejemplo, la renovación automática está desactivada, la renovación aún lleva una oferta, o hay un cambio de producto pendiente). | | **original_price_usd** | Float | Igual que `original_price_local`, en USD. | | **original_purchase_date** | ISO 8601 date | En las suscripciones recurrentes, la compra original es la primera transacción de la cadena; su ID, denominado ID de transacción original, vincula la cadena de renovaciones. Las transacciones posteriores son extensiones de esta. La fecha de compra original es la fecha y hora de esa primera transacción. | | **original_transaction_id** | String | <p>En las suscripciones recurrentes, este es el ID de transacción original que vincula la cadena de renovaciones. La transacción original es la primera de la cadena; las posteriores son extensiones de ella.</p><p>Si no hay extensiones, `original_transaction_id` coincide con store_transaction_id.</p> | | **paywall_name** | String | Nombre del paywall en el que se originó la transacción. | | **paywall_revision** | String | Revisión del paywall en el que se originó la transacción. El valor predeterminado es 1. | | **price_local** | Float | El importe cobrado por la transacción antes de la comisión de Apple/Google, en moneda local. `null` para las pruebas gratuitas, ya que no se cobra nada. | | **price_usd** | Float | El importe cobrado por la transacción antes de la comisión de Apple/Google, en USD. `null` para las pruebas gratuitas, ya que no se cobra nada. | | **profile_country** | String | Determinado por Adapty a partir de la IP del perfil. | | **profile_event_id** | UUID | ID de evento único que puede usarse para deduplicación. | | **profile_has_access_level** | Boolean | Booleano que indica si el perfil tiene un nivel de acceso activo. | | **profile_id** | UUID | ID de perfil generado por Adapty. Igual que en el nivel raíz del evento. | | **profile_ip_address** | String | IP del perfil (puede ser IPv4 o IPv6; se prefiere IPv4 cuando está disponible). `null` si **Collect users' IP addresses** está desactivado en los [ajustes de la app](https://app.adapty.io/settings/general). | | **profile_total_revenue_usd** | Float | Ingresos totales del perfil con los reembolsos descontados. | | **promotional_offer_id** | String | El ID de Adapty de la [oferta promocional](offers) utilizada. Este ID se establece al crear una oferta en el dashboard. | | **purchase_date** | ISO 8601 date | La fecha y hora de la compra del producto. | | **rate_after_first_year** | Boolean | Booleano que indica que la suscripción cumple los requisitos para una tasa de comisión reducida (habitualmente el 15%) tras un año de renovación continua. Las tasas de comisión varían según la elegibilidad al programa y el país. Consulta [Comisión de la store e impuestos](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue) para más detalles. | | **store** | String | Store donde se realizó la compra. Valores estándar: **app_store**, **play_store**, **stripe**, **paddle**. <br/>Si configuras [transacciones de store personalizadas](api-adapty/operations/setTransaction) mediante la API del servidor, se utiliza el valor del parámetro **store**. | | **store_country** | String | El país que nos envía la app store. | | **store_offer_category** | String | Categoría de oferta aplicada. Los valores posibles son `introductory`, `promotional`, `winback`. | | **store_offer_discount_type** | String | Tipo de oferta aplicada. Los valores posibles son `free_trial`, `pay_as_you_go` y `pay_up_front`. | | **store_offer_number_of_periods** | Integer | Número de períodos de facturación base que cubre la oferta (1 o más). Solo está presente cuando se aplica una oferta. `null` para las ofertas de pago anticipado de App Store y cuando la store no informa la duración de la oferta. | | **subscription_expires_at** | ISO 8601 date | Fecha de expiración de la suscripción. Normalmente en el futuro. | | **transaction_id** | String | Identificador único de una transacción. | | **trial_duration** | String | Duración del período de prueba en días. Se envía con el formato "{} days", por ejemplo, "7 days". Solo está presente en los tipos de evento relacionados con pruebas: `trial_started`, `trial_converted`, `trial_cancelled`. | | **variation_id** | UUID | ID único del paywall en el que se realizó la compra. | | **vendor_product_id** | String | <p>ID del producto en Apple App Store, Google Play Store o Stripe.</p><p>Si se concedió acceso sin una transacción real en la store, `vendor_product_id` será uno de los siguientes:</p><ul><li>`adapty_server_side_product` — concedido mediante la [API del servidor](api-adapty/operations/grantAccessLevel).</li><li>`adapty_dashboard_product` — [concedido manualmente](give-access-level-to-specific-customer) en el Adapty Dashboard.</li><li>`adapty_promotion` — heredado.</li></ul> | #### Propiedades adicionales de impuestos e ingresos en los eventos \{#additional-tax-and-revenue-event-properties\} Las propiedades de evento relacionadas con impuestos e ingresos que se muestran a continuación son campos adicionales que solo se aplican a determinados tipos de evento. Esto significa que los tipos de evento indicados incluyen las [Propiedades de evento para la mayoría de los tipos de evento](webhook-event-types-and-fields#for-most-event-types), junto con los campos adicionales que se listan a continuación. Tipos de evento que tienen las propiedades de impuestos e ingresos: - `subscription_renewed` - `subscription_initial_purchase` (también conocido como `subscription_started` — es el mismo evento) - `subscription_refunded` - `non_subscription_purchase` | Campo | Tipo | Descripción | | :-------------------- | :---- | :----------------------------------------------------------- | | **net_revenue_local** | Float | Ingresos netos (ingresos tras la comisión de Apple/Google e impuestos) en moneda local. | | **net_revenue_usd** | Float | Ingresos netos (ingresos tras la comisión de Apple/Google e impuestos) en USD. | | **proceeds_local** | Float | Precio del producto tras la comisión de Apple/Google en moneda local. | | **proceeds_usd** | Float | Precio del producto tras la comisión de Apple/Google. | | **tax_amount_local** | Float | Importe de impuestos deducido en moneda local. | | **tax_amount_usd** | Float | Importe de impuestos deducido en USD. | #### Ejemplo de payload de `non_subscription_purchase` \{#non_subscription_purchase-example-payload\} `non_subscription_purchase` sigue la misma estructura que los eventos de suscripción, pero refleja una compra única o consumible. Los campos exclusivos de suscripción no aplican: `cancellation_reason`, `will_renew`, `is_in_grace_period`, `is_refund`, `is_lifetime` y `trial_duration` están ausentes. `subscription_expires_at` está presente pero con valor `null`. Los campos de impuestos e ingresos (`net_revenue_*`, `proceeds_*`, `tax_amount_*`) sí se incluyen. <details> <summary>Ejemplo de payload (haz clic para expandir)</summary> ```json title="Json" showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem", "event_type": "non_subscription_purchase", "event_datetime": "2000-01-31T00:00:00.000000+0000", "event_properties": { "store": "app_store", "currency": "USD", "price_usd": 4.99, "price_local": 4.99, "original_price_usd": 4.99, "original_price_local": 4.99, "discount_amount_usd": 0, "discount_amount_local": 0, "proceeds_usd": 4.2415, "proceeds_local": 4.2415, "net_revenue_usd": 4.2415, "net_revenue_local": 4.2415, "tax_amount_usd": 0, "tax_amount_local": 0, "profile_id": "00000000-0000-0000-0000-000000000000", "environment": "Production", "vendor_product_id": "100coins", "transaction_id": "0000000000000000", "original_transaction_id": "0000000000000000", "purchase_date": "2024-11-15T10:45:36.181000+0000", "original_purchase_date": "2024-11-15T10:45:36.181000+0000", "subscription_expires_at": null, "store_country": "US", "profile_country": "US", "profile_ip_address": "10.10.1.1", "profile_has_access_level": false, "profile_total_revenue_usd": 4.99, "consecutive_payments": 1, "rate_after_first_year": false, "profile_event_id": "00000000-0000-0000-0000-000000000000" }, "event_api_version": 1 } ``` </details> #### Para el evento Access Level Updated \{#for-access-level-updated-event\} El evento **Access Level Updated** es un evento de webhook específico que se genera únicamente cuando la integración de Webhook está activa y este tipo de evento está habilitado. Si está habilitado, se envía al Webhook configurado y aparece en el **Event Feed**. Si no está habilitado, el evento no se creará. Si has habilitado el [uso compartido de niveles de acceso](general#6-sharing-paid-access-between-user-accounts), el evento **access level updated** se enviará para todos los perfiles que compartan el nivel de acceso. :::tip Usa este evento para actualizar el nivel de acceso del usuario en tu base de datos, conceder o revocar funciones premium en tu backend y mantener el acceso sincronizado entre dispositivos o plataformas. ::: | Propiedad | Tipo | Descripción | | ---------------------------------- | ------------- | ------------------------------------------------------------ | | **ab_test_name** | String | Nombre de la prueba A/B en la que se originó la transacción. | | **access_level_id** | String | El ID del nivel de acceso. | | **activated_at** | ISO 8601 date | Fecha y hora en que se activó el acceso por última vez. | | **active_introductory_offer_type** | String | Tipo de oferta introductoria aplicada. Los valores posibles son `free_trial`, `pay_as_you_go` y `pay_up_front`. | | **active_promotional_offer_id** | String | ID de la oferta promocional según se indica en la sección de productos del Adapty Dashboard. | | **active_promotional_offer_type** | String | Tipo de oferta promocional aplicada. Los valores posibles son `free_trial`, `pay_as_you_go` y `pay_up_front`. | | **base_plan_id** | String | [ID del plan base](https://support.google.com/googleplay/android-developer/answer/12154973) en Google Play Store o [ID de precio](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) en Stripe. | | **billing_issue_detected_at** | ISO 8601 date | Fecha y hora del problema de facturación. | | **cancellation_reason** | String | Posibles motivos de cancelación: `voluntarily_cancelled`, `billing_error`, `price_increase`, `product_was_not_available`, `refund`, `cancelled_by_developer`, `new_subscription_replace`, `upgraded`, `unknown`, `adapty_revoked`. | | **cohort_name** | String | Nombre de la audiencia a la que pertenece el perfil. | | **currency** | String | Moneda local (por defecto USD). | | **developer_id** | String | El ID del placement donde se originó la transacción. | | **environment** | String | Los valores posibles son `Sandbox` o `Production`. | | **event_datetime** | ISO 8601 date | La fecha y hora del evento. | | **expires_at** | ISO 8601 date | Fecha y hora en que expirará el acceso. | | **is_active** | Boolean | Booleano que indica si el nivel de acceso está activo. | | **is_in_grace_period** | Boolean | Booleano que indica si el perfil está en el período de gracia. | | **is_lifetime** | Boolean | Booleano que indica si el nivel de acceso es de por vida. | | **is_refund** | Boolean | Booleano que indica si la transacción es un reembolso. | | **original_purchase_date** | ISO 8601 date | En las suscripciones recurrentes, la compra original es la primera transacción de la cadena; su ID, denominado ID de transacción original, vincula la cadena de renovaciones. Las transacciones posteriores son extensiones de esta. La fecha de compra original es la fecha y hora de esta primera transacción. | | **original_transaction_id** | String | <p>En las suscripciones recurrentes, es el ID de transacción original que vincula la cadena de renovaciones. La transacción original es la primera de la cadena; las transacciones posteriores son extensiones de esta.</p><p>Si no hay extensiones, `original_transaction_id` coincide con store_transaction_id.</p>El identificador de transacción de la compra original. | | **paywall_name** | String | Nombre del paywall donde se originó la transacción. | | **paywall_revision** | String | Revisión del paywall donde se originó la transacción. El valor predeterminado es 1. | | **profile_country** | String | Determinado por Adapty a partir de la IP del perfil. | | **profile_event_id** | UUID | ID único del evento que puede usarse para deduplicación. | | **profile_has_access_level** | Boolean | Booleano que indica si el perfil tiene un nivel de acceso activo. | | **profile_id** | UUID | ID interno del perfil de usuario de Adapty. | | **profile_ip_address** | String | IP del perfil (puede ser IPv4 o IPv6, con preferencia por IPv4 cuando esté disponible). `null` si **Collect users' IP addresses** está desactivado en los [ajustes de la app](https://app.adapty.io/settings/general). | | **profile_total_revenue_usd** | Float | Ingresos totales del perfil, incluidos los reembolsos. | | **purchase_date** | ISO 8601 date | La fecha y hora de compra del producto. | | **renewed_at** | ISO 8601 date | Fecha y hora en que se renovará el acceso. | | **starts_at** | ISO 8601 date | Fecha y hora en que comienza el nivel de acceso. | | **store** | String | Store donde se adquirió el producto. Valores estándar: **app_store**, **play_store**, **stripe**, **paddle**. <br/>Si configuras [transacciones de store personalizadas](api-adapty/operations/setTransaction) mediante la API del servidor, se usa el valor del parámetro **store**. | | **store_country** | String | País enviado a Adapty por el store de aplicaciones. | | **subscription_expires_at** | ISO 8601 date | Fecha de expiración de la suscripción. | | **transaction_id** | String | Identificador único de una transacción. | | **trial_duration** | String | Duración del período de prueba en días (p. ej., "7 days"). | | **variation_id** | UUID | Identificador de una variante, utilizado para atribuir compras a este paywall. | | **vendor_product_id** | String | <p>ID del producto en el store (Apple/Google/Stripe).</p><p>Si el acceso se otorgó sin una transacción real en el store, `vendor_product_id` será uno de los siguientes:</p><ul><li>`adapty_server_side_product` — otorgado a través de la [API del servidor](api-adapty/operations/grantAccessLevel).</li><li>`adapty_dashboard_product` — [otorgado manualmente](give-access-level-to-specific-customer) en el Adapty Dashboard.</li><li>`adapty_promotion` — heredado.</li></ul> | | **will_renew** | Boolean | Indica si el nivel de acceso de pago se renovará. | :::warning Ten en cuenta que esta estructura puede crecer con el tiempo, ya que nosotros o los terceros con los que trabajamos podemos añadir nuevos datos. Asegúrate de que el código que la procesa sea lo suficientemente robusto y se base en campos específicos en lugar de depender de la estructura completa. ::: --- # File: set-up-webhook-integration --- --- title: "Configurar la integración de webhook" description: "Configura la integración de webhook en Adapty para automatizar el seguimiento de eventos." --- La [integración de webhook](webhook) de Adapty consta de los siguientes pasos: <img src="/assets/shared/img/webhook-setup.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> 1. **Configuras tu endpoint:** 1. Asegúrate de que tu servidor pueda procesar las solicitudes de Adapty con el encabezado **Content-Type** configurado como `application/json`. 2. Configura tu servidor para recibir la solicitud de verificación de Adapty y responder con cualquier estado `2xx` y un cuerpo JSON. 3. [Gestiona los eventos de suscripción](#subscription-events) una vez verificada la conexión. 2. **Configuras y activas la integración del webhook** en el [Adapty Dashboard](#configure-webhook-integration-in-the-adapty-dashboard). También puedes [mapear los eventos de Adapty a nombres de eventos personalizados](#configure-webhook-integration-in-the-adapty-dashboard). Te recomendamos probar en el entorno **Sandbox** antes de pasar a producción. 3. **Adapty envía una solicitud de verificación** a tu servidor. 4. **Tu servidor responde** con un estado `2XX` y un cuerpo JSON. 5. **Una vez que Adapty recibe una respuesta válida, comienza a enviar eventos de suscripción.** ## Configura tu servidor para procesar las solicitudes de Adapty \{#set-up-your-server-to-process-adapty-requests\} Adapty enviará a tu endpoint de webhook 2 tipos de solicitudes: 1. [Solicitud de verificación](#verification-request): la solicitud inicial para verificar que la conexión está configurada correctamente. Esta solicitud no contendrá ningún evento y se enviará en el momento en que hagas clic en el botón **Save** en la integración de Webhook del Adapty Dashboard. Para confirmar que tu endpoint recibió correctamente la solicitud de verificación, tu endpoint debe responder con la respuesta de verificación. 2. [Evento de suscripción](#subscription-events): una solicitud estándar que el servidor de Adapty envía cada vez que se crea un evento en él. Tu servidor no necesita responder con ninguna respuesta específica. Lo único que necesita el servidor de Adapty es recibir una respuesta HTTP estándar con código 200 si recibe el mensaje correctamente. ### Solicitud de verificación Después de habilitar la integración de webhook en el Adapty Dashboard, Adapty enviará una solicitud POST de verificación que contiene un objeto JSON vacío `{}` como cuerpo. Configura tu endpoint para que tenga el **encabezado Content-Type** como `application/json`, es decir, el endpoint de tu servidor debe esperar que la solicitud webhook entrante tenga su payload en formato JSON. Tu servidor debe responder con un código de estado 2xx y enviar cualquier respuesta JSON válida, por ejemplo: ```json title="Json" {} ``` Una vez que Adapty recibe la respuesta de verificación en el formato correcto y con un código de estado 2xx, tu integración de webhook con Adapty está completamente configurada. ### Eventos de suscripción \{#subscription-events\} Los eventos de suscripción se envían con la cabecera **Content-Type** establecida en `application/json` y contienen datos del evento en formato JSON. Para conocer los posibles tipos de eventos y las estructuras de solicitud, consulta [Tipos de eventos y campos del webhook](webhook-event-types-and-fields). ## Configurar la integración de webhook en el Adapty Dashboard \{#configure-webhook-integration-in-the-adapty-dashboard\} Dentro de Adapty, puedes configurar flujos separados para los eventos de producción y los eventos de prueba recibidos desde el entorno sandbox de Apple o Stripe, o desde la cuenta de prueba de Google. :::tip Adapty admite una URL de webhook por entorno (producción y sandbox). Para enviar eventos a varios servicios, apunta el webhook a tu propio backend y distribúyelos desde allí. ::: Para los eventos de producción, usa el campo **Production endpoint URL** para especificar la URL a la que se enviarán los callbacks. Además, configura el campo **Authorization header value for production endpoint** — la cabecera que usará tu servidor para autenticar los eventos de Adapty. Ten en cuenta que el valor especificado en el campo **Authorization header value for production endpoint** se usará como cabecera `Authorization` exactamente tal como se proporciona, sin ningún cambio ni añadido. Para los eventos de prueba, utiliza los campos **Sandbox endpoint URL** y **Authorization header value for sandbox endpoint** según corresponda. Para configurar la integración con webhook: 1. Abre [Integrations -> Webhook](https://app.adapty.io/integrations/customwebhook) en tu Adapty Dashboard. <img src="/assets/shared/img/webhook_integration.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Activa el interruptor para iniciar la integración. 4. Rellena los campos de la integración: | Campo | Descripción | | ------------------------------------------------------ | ------------------------------------------------------------ | | **Production endpoint URL** | La URL que usa Adapty para enviar solicitudes HTTP POST de eventos en producción. | | **Authorization header value for production endpoint** | <p>El encabezado que tu servidor usará para autenticar las solicitudes de Adapty en producción. Ten en cuenta que utilizaremos el valor especificado en este campo como encabezado `Authorization` exactamente como se proporcione, sin ningún cambio ni adición.</p><p></p><p>Aunque no es obligatorio, se recomienda encarecidamente para mayor seguridad.</p> | Además, para tus necesidades de pruebas en el entorno sandbox, hay otros dos campos disponibles: | Campo de prueba | Descripción | | ----------------------------------------------------- | ------------------------------------------------------------ | | **Sandbox endpoint URL** | La URL que usa Adapty para enviar solicitudes HTTP POST de eventos en el entorno sandbox. | | **Authorization header value for sandbox endpoint** | <p>El encabezado que usará tu servidor para autenticar las solicitudes de Adapty durante las pruebas en el entorno sandbox. Ten en cuenta que usaremos el valor especificado en este campo como encabezado `Authorization` exactamente como se proporcionó, sin ningún cambio ni adición.</p><p></p><p>Aunque no es obligatorio, se recomienda encarecidamente para mayor seguridad.</p> | 4. (opcional) Elige los eventos que quieres recibir y asigna sus nombres. Consulta nuestros [Flujos de eventos](event-flows) para ver qué eventos se disparan en cada situación. Si los IDs de tus eventos son distintos a los que usa Adapty, mantenlos tal como están en tu sistema y sustituye los IDs de eventos predeterminados de Adapty por los tuyos en la sección **Events names** de la página [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). El ID de evento puede ser cualquier cadena; simplemente asegúrate de que el ID de evento en tu servidor de procesamiento de webhooks coincida con el que introdujiste en el Adapty Dashboard. No puedes dejar el ID de evento vacío para los eventos habilitados. <img src="/assets/shared/img/86942b8-event_names_renaming.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Los campos y opciones adicionales no son obligatorios; úsalos según necesites: | Ajuste | Descripción | | :------------------------------------ | :----------------------------------------------------------- | | **Send Trial Price** | Cuando está activado, Adapty incluirá el precio de la suscripción en los campos `price_local` y `price_usd` para el evento **Trial Started**. | | **Exclude Historical Events** | Permite excluir eventos ocurridos antes de que el usuario instalara la app con el SDK de Adapty. Esto evita la duplicación de eventos y garantiza informes precisos. Por ejemplo, si un usuario activó una suscripción mensual el 10 de enero y actualizó la app con el SDK de Adapty el 6 de marzo, Adapty omitirá los eventos anteriores al 6 de marzo y conservará los posteriores. | | **Send user attributes** | Activa esta opción para enviar atributos específicos del usuario, como preferencias de idioma. Estos atributos aparecerán en el campo `user_attributes`. Consulta [Campos del evento](webhook-event-types-and-fields#event-fields) para más información. | | **Send attribution** | Activa esta opción para incluir información de atribución (p. ej., datos de AppsFlyer) en el campo `attributions`. Consulta la sección [Datos de atribución](webhook-event-types-and-fields#attributions) para más detalles. | | **Send Play Store purchase token** | Activa esta opción para recibir el token de Play Store necesario para la revalidación de compras, si fuera necesario. Al activarla, se añadirá el parámetro `play_store_purchase_token` al evento. Para más detalles sobre su contenido, consulta la sección [Token de compra de Play Store](webhook-event-types-and-fields#play-store-purchase-token). | 6. Recuerda hacer clic en el botón **Save** para confirmar los cambios. En el momento en que hagas clic en el botón **Save**, Adapty enviará una solicitud de verificación y esperará la respuesta de verificación de tu servidor. ### Elige los eventos a enviar y mapea los nombres de eventos \{#choose-events-to-send-and-map-event-names\} Elige los eventos que quieres recibir en tu servidor activando el interruptor correspondiente. Si los nombres de tus eventos son distintos a los que usa Adapty y necesitas mantener los tuyos, puedes configurar el mapeo reemplazando los nombres de eventos predeterminados de Adapty por los tuyos en la sección **Events names** de la página [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). <img src="/assets/shared/img/86942b8-event_names_renaming.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> El nombre del evento puede ser cualquier cadena de texto. No puedes dejar los campos vacíos para los eventos habilitados. Si borraste accidentalmente el nombre de un evento de Adapty, siempre puedes copiarlo desde el artículo [Eventos para enviar a integraciones de terceros](events). ## Manejar eventos de webhook \{#handle-webhook-events\} Los webhooks generalmente se entregan entre 5 y 60 segundos después de que ocurre el evento. Sin embargo, los eventos de cancelación pueden tardar hasta 2 horas en entregarse tras la cancelación de una suscripción por parte del usuario. Si el código de estado de respuesta de tu servidor está fuera del rango 200-404, Adapty reintenta la entrega con retroceso exponencial. El primer reintento ocurre aproximadamente **1 minuto** después del fallo inicial, duplicándose en cada intento sucesivo — hasta 9 reintentos distribuidos a lo largo de 24 horas. Te recomendamos que configures tu webhook para realizar solo una validación básica del cuerpo del evento de Adapty antes de responder. Si tu servidor no puede procesar el evento y no quieres que Adapty reintente, usa un código de estado dentro del rango 200-404. Además, gestiona cualquier tarea que consuma tiempo de forma asíncrona y responde a Adapty con rapidez. Si Adapty no recibe respuesta en 10 segundos, considerará el intento como fallido y lo reintentará. --- # File: test-webhook --- --- title: "Probar la integración de webhook" description: "Prueba las integraciones de webhook en Adapty para automatizar el seguimiento de eventos de suscripción." --- Una vez que hayas configurado tu integración, es momento de probarla. Puedes probar tanto la integración en sandbox como la de producción. Te recomendamos empezar con la de sandbox y validar al máximo en ella: - Los eventos se envían y se entregan correctamente. - Configuraste correctamente las opciones para eventos históricos, el precio de la suscripción para el evento **Trial started**, la atribución, los atributos de usuario y el token de compra de Google Play Store para que se envíen o no con un evento. - Mapeaste los nombres de los eventos correctamente y tu servidor puede procesarlos. ## Cómo probar \{#how-to-test\} Antes de empezar a probar una integración, asegúrate de haber: 1. Configurado la integración de webhook según se describe en el tema [Configurar la integración de webhook](set-up-webhook-integration). 2. Configurado el entorno según se describe en los temas [Probar compras in-app en Apple App Store](test-purchases-in-sandbox) y [Probar compras in-app en Google Play Store](testing-on-android). Asegúrate de haber compilado tu app de prueba en el entorno sandbox y no en producción. 3. Realizado una compra / iniciado una prueba / solicitado un reembolso que genere el evento que elegiste enviar al webhook. Por ejemplo, para obtener el evento **Subscription started**, realiza una nueva suscripción. ## Validación del resultado \{#validation-of-the-result\} ### Resultado exitoso al enviar eventos \{#successful-sending-events-result\} En caso de una integración exitosa, el evento aparecerá en la sección **Last sent events** de la integración y tendrá el estado **Success**. <img src="/assets/shared/img/6ccc3bb-webhook_integration_success.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Resultado fallido al enviar eventos \{#unsuccessful-sending-events-result\} | Problema | Solución | |-----|--------| | El evento no apareció | Tu compra no se realizó y, por lo tanto, el evento no se creó. Consulta el tema [Solución de problemas con compras de prueba](troubleshooting-test-purchases) para encontrar la solución. | | El evento apareció y tiene el estado **Sending failed** | <p>Determinamos la entregabilidad en función del estado HTTP y consideramos todo lo que esté **fuera del rango 200-399** como un fallo.</p><p>Para obtener más información sobre el problema, pasa el cursor sobre el estado **Sending failed** de tu evento fallido como se muestra a continuación.</p> | <img src="/assets/shared/img/12ff189-hover_sending_failed.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: handle-integration-errors --- --- title: "Gestionar errores en integraciones" description: "Gestionar errores en integraciones" --- Al usar cualquier integración de atribución, mensajería o analíticas, puede que encuentres algunos errores comunes. Consulta esta guía para los casos de solución de problemas. ## Discrepancia de datos \{#data-discrepancy\} **Motivo**: Esto puede ocurrir porque no todos tus usuarios usan la versión de la app que tiene el SDK de Adapty. **Solución**: Para garantizar la consistencia de los datos, puedes obligar a tus usuarios a actualizar la app a una versión con el SDK de Adapty. ## Errores de red \{#network-errors\} **Motivo**: Lo más probable es que no haya habido conexión a internet entre el servidor de Adapty y el servidor de la integración. **Solución**: Estos problemas normalmente no duran mucho y solo afectan a un pequeño número de eventos. ## El servidor de integración no pudo procesar el evento \{#integration-server-failed-to-process-the-event\} **Motivo**: La integración está configurada incorrectamente. **Solución**: Consulta el artículo sobre la integración en nuestra documentación. Asegúrate de haber completado todos los pasos de configuración tanto en el Adapty Dashboard, como en el lado de la herramienta de terceros y en el código de tu app. ## Datos de integración faltantes \{#missing-integration-data\} **Motivo**: Al perfil le falta algún ID específico de la integración. Esto puede ocurrir cuando la integración no está configurada correctamente en el código de la app. **Solución**: Consulta el artículo sobre la integración en nuestra documentación. Asegúrate de haber implementado los métodos de los fragmentos de código en el código de tu app, y de que estos métodos interactúen realmente con los perfiles de tus usuarios. ## Credenciales de integración faltantes \{#missing-integration-credentials\} **Motivo**: Faltan algunas credenciales de integración o son incorrectas. **Solución**: Comprueba todas las credenciales de esa integración en el Adapty Dashboard. El problema puede deberse a un desajuste de versión o de entorno. ## El evento ha expirado \{#the-event-has-expired\} **Motivo**: La opción **Exclude historical events** está habilitada en la configuración de la integración, y la fecha de creación del evento es anterior a la fecha de creación del perfil en nuestro sistema. Esto puede ocurrir si una cadena de transacciones que comenzó hace muchos años llega a Adapty mediante la validación de recibos para un perfil creado recientemente. **Solución**: Asegúrate de que esto no ocurra con nuevos eventos. Si quieres enviar eventos históricos a la integración, desactiva **Exclude historical events**. ## Tipo de evento desactivado/no compatible \{#disabledunsupported-event-type\} **Motivo**: El evento no está soportado por esta integración, o lo desactivaste al configurarla. Por ejemplo, los eventos `access_level_updated` no están soportados por la mayoría de las integraciones. **Solución**: Consulta en la documentación de la integración si esta soporta ese tipo de evento. Si es así, en el Adapty Dashboard, asegúrate de que ese tipo de evento esté habilitado en la configuración de la integración. --- # File: manage-adapty-with-ai --- --- title: "Gestiona Adapty con agentes de IA y herramientas de codificación" description: "Todas las formas de usar Adapty con IA: integra el SDK con un agente de codificación, consulta analíticas con un LLM y dale a tu herramienta de IA la documentación de Adapty." --- Adapty funciona con herramientas de codificación y agentes de IA. Úsalos para integrar el SDK, consultar tus analíticas o buscar documentación de Adapty sin salir de tu editor. Esta página lista lo que está disponible y para quién es cada herramienta. ## Integra el SDK de Adapty con IA \{#integrate-the-adapty-sdk-with-ai\} Dos formas de añadir el SDK de Adapty a tu app con una herramienta de codificación con IA. Ambas funcionan con Cursor, Claude y otros asistentes de IA. ### Integración basada en skills \{#skill-based-integration\} El skill de integración del SDK de Adapty ejecuta toda la integración desde tu herramienta de codificación con IA con un solo comando. Úsalo cuando quieras una configuración guiada y automatizada. Elige tu plataforma: [iOS](adapty-sdk-integration-skill) · [Android](adapty-sdk-integration-skill-android) · [React Native](adapty-sdk-integration-skill-react-native) · [Flutter](adapty-sdk-integration-skill-flutter) · [Unity](adapty-sdk-integration-skill-unity) · [Kotlin Multiplatform](adapty-sdk-integration-skill-kmp) · [Capacitor](adapty-sdk-integration-skill-capacitor) ### Integración paso a paso \{#step-by-step-integration\} Guía a tu herramienta de IA por la integración etapa a etapa, dándole la documentación adecuada en orden. Úsalo cuando quieras revisar cada paso a medida que avanzas. Elige tu plataforma: [iOS](adapty-cursor) · [Android](adapty-cursor-android) · [React Native](adapty-cursor-react-native) · [Flutter](adapty-cursor-flutter) · [Unity](adapty-cursor-unity) · [Kotlin Multiplatform](adapty-cursor-kmp) · [Capacitor](adapty-cursor-capacitor) ## Gestiona Adapty desde la línea de comandos \{#manage-adapty-from-the-command-line\} La [CLI para desarrolladores de Adapty](developer-cli-quickstart) te permite gestionar tus entidades de Adapty — apps, niveles de acceso, productos, paywalls y placements — desde el terminal, sin abrir el dashboard. Al ser una herramienta de línea de comandos, tu agente de codificación con IA puede ejecutarla directamente. ## Consulta tus datos \{#ask-about-your-data\} Apunta un agente de codificación con IA a la API de exportación de analíticas para consultar tus métricas en lenguaje natural: ingresos, retención, LTV y más. No se necesita servidor MCP. [Consulta a la IA sobre tus datos analíticos](export-analytics-with-ai) ## Dale a tu herramienta de IA la documentación de Adapty \{#give-your-ai-tool-the-adapty-docs\} ### Documentación en texto plano \{#plain-text-docs\} Toda la documentación de Adapty está disponible en Markdown: añade `.md` a la URL de la página o haz clic en **Copy for LLM** debajo del título. Para un contexto más amplio, dale a tu herramienta el índice [`llms.txt`](https://adapty.io/docs/es/llms.txt) o un subconjunto específico de plataforma como [`ios-llms.txt`](https://adapty.io/docs/es/ios-llms.txt). ### Context7 \{#context7\} [Context7](https://context7.com/adaptyteam/adapty-docs) es un servidor MCP que sirve la documentación de Adapty a tu herramienta de IA, pero solo indexa fragmentos de código, no el texto completo. Úsalo para obtener ejemplos de código rápidos; para una guía completa, dale a tu herramienta la documentación en texto plano mencionada arriba. Context7 funciona con Cursor, Claude Code, Windsurf y otras herramientas compatibles con MCP. --- # File: export-analytics-with-ai --- --- title: "Pregunta a la IA sobre tus datos de analíticas" description: "Consulta tus analíticas de Adapty en lenguaje natural con un agente de codificación de IA, usando la API de Export Analytics." --- Pregunta a un agente de codificación con IA sobre tu analítica de Adapty en lenguaje natural — ingresos, conversiones, retención, LTV — y deja que obtenga los datos por ti. Apunta una herramienta que pueda hacer llamadas a la API hacia la [API de exportación de analítica](https://adapty.io/docs/es/export-analytics-api.md), y consultará tus métricas bajo demanda. ## Qué puedes consultar \{#what-you-can-ask-about\} La Export Analytics API devuelve las mismas métricas que ves en los gráficos del Adapty Dashboard. Cada métrica tiene su propia operación: | Métrica | Qué cubre | Operación | | --- | --- | --- | | Ingresos, MRR, ARR, ARPU | Dinero generado a lo largo del tiempo, agrupado por período, país o campaña | [retrieveAnalyticsData](https://adapty.io/docs/es/api-export-analytics/operations/retrieveAnalyticsData.md) | | Retención de cohortes | Cuánto tiempo siguen pagando los suscriptores de una cohorte determinada | [retrieveCohortData](https://adapty.io/docs/es/api-export-analytics/operations/retrieveCohortData.md) | | Tasas de conversión | Cuántos usuarios avanzan de un paso o canal al siguiente | [retrieveConversionData](https://adapty.io/docs/es/api-export-analytics/operations/retrieveConversionData.md) | | Churn y embudo | Dónde abandonan los usuarios y con qué rapidez se dan de baja | [retrieveFunnelData](https://adapty.io/docs/es/api-export-analytics/operations/retrieveFunnelData.md) | | Valor del ciclo de vida (LTV) | Ingresos medios por segmento de usuarios a lo largo del tiempo | [retrieveLTVData](https://adapty.io/docs/es/api-export-analytics/operations/retrieveLTVData.md) | | Retención | Porcentaje de usuarios que siguen activos tras un número determinado de días | [retrieveRetentionData](https://adapty.io/docs/es/api-export-analytics/operations/retrieveRetentionData.md) | Para ver la lista completa de parámetros y filtros, consulta la [referencia de la API](https://adapty.io/docs/es/api-export-analytics.md). ## Antes de empezar \{#before-you-start\} Necesitas tres cosas: - **Una cuenta de Adapty con datos**: La API devuelve las mismas métricas que los gráficos del dashboard, así que tu app ya debe recopilar analíticas. - **Una clave de API secreta**: Encuéntrala en [App settings → General](https://app.adapty.io/settings/general), en el campo **Secret key**. Las claves son específicas por app, así que usa una clave distinta para cada una. Guárdala en una variable de entorno (por ejemplo, `ADAPTY_SECRET_KEY`) para que tu agente pueda leerla sin que tengas que pegarla en el chat. - **Una herramienta de IA capaz de llamar APIs**: Por ejemplo, Claude Code, Cursor o Claude Desktop con una herramienta de fetch. Las herramientas de chat básicas como claude.ai o ChatGPT no pueden llamar a la API directamente. ## Dale a tu agente la especificación de la API \{#give-your-agent-the-api-spec\} La [especificación OpenAPI](https://adapty.io/docs/es/api-specs/export-analytics-api.yaml) describe cada endpoint, la cabecera de autenticación, el cuerpo de la solicitud y ejemplos de respuesta. Una vez que tu agente tenga la especificación, construye solicitudes correctas sin que tengas que escribir ningún código. Dale a tu agente la especificación mediante la URL: - **Pega la URL**: Si tu agente puede obtener URLs, dale `https://adapty.io/docs/es/api-specs/export-analytics-api.yaml` y pídele que lea la especificación. - **Usa una herramienta de fetch**: Si tu agente tiene una herramienta para recuperar URLs (por ejemplo, un servidor MCP de fetch), apúntala a la misma URL. La especificación establece la URL base en `https://api-admin.adapty.io`, así que tu agente tiene todo lo que necesita una vez que tu clave esté en el entorno. ## Pregunta sobre tus datos \{#ask-about-your-data\} Con la especificación cargada y tu clave en una variable de entorno, describe en lenguaje natural la métrica que necesitas. Ejemplos de prompts: ``` What was my MRR at the end of each month this year, and how does it compare to last year? Show my trial-to-paid conversion rate for the last 90 days, broken down by product. Which countries drive the most revenue from my yearly subscription? Top 10. How is week-1 retention trending for subscribers who started in the last 6 months? What's the refund rate on my annual plan since launch, by month? Compare LTV for paid-campaign users vs. organic over the last year, and export it as CSV. ``` El agente asigna tu solicitud a la operación correcta, lee la clave del entorno y devuelve los datos. Las respuestas son JSON por defecto. Pide CSV cuando quieras un archivo listo para hoja de cálculo — el agente establece `format` a `csv` en el cuerpo de la solicitud. :::warning Guarda tu clave secreta en una variable de entorno, no la pegues en el chat ni la incluyas en un archivo de reglas. Las claves son específicas de la app, así que rótala en **Settings → General** si se filtra. Consulta [rotar las claves de API](https://adapty.io/docs/es/export-analytics-api-authorization.md). ::: ## Configura una vez para reutilizar \{#set-up-once-for-repeated-use\} Para no repetir la configuración en cada sesión, guarda la especificación y la clave donde tu agente pueda reutilizarlas: - **Guarda el enlace de la spec**: Añade la URL de la spec a las reglas o al archivo de memoria de tu agente (por ejemplo, un archivo `CLAUDE.md` o las reglas de Cursor) para que se cargue en cada sesión. - **Guarda la clave en tu entorno**: Mantén `ADAPTY_SECRET_KEY` en el perfil de tu shell o en el almacén de secretos de la herramienta para no tener que pegarla nunca más. - **Guarda prompts reutilizables o crea una skill personalizada**: Conserva tus preguntas habituales como prompts guardados, o envuélvelas en una skill personalizada o en un slash command para que tu agente genere un informe cuando lo necesites. ## Límites \{#limits\} Ten en cuenta estas restricciones: - **Límite de velocidad**: La API permite 2 solicitudes por segundo por clave de API. Si se supera, devuelve un error `429 Too Many Requests`. Indica a tu agente que espere y reintente al recibir un `429`. - **Claves por aplicación**: Cada clave funciona para una sola app. Para obtener datos de varias apps, proporciona la clave correspondiente a cada una. - **Formato de salida**: Las respuestas son JSON por defecto. Establece `format` como `csv` en el cuerpo de la solicitud para exportar en CSV. Para conocer todas las reglas de autenticación y formato de solicitud, consulta [Autorización y formato de solicitud](https://adapty.io/docs/es/export-analytics-api-authorization.md). --- # File: handle-webhooks-with-ai --- --- title: "Gestiona eventos de suscripción de Adapty con webhooks" description: "Recibe y gestiona eventos de suscripción de Adapty en tu servidor con webhooks — configuración del endpoint, autenticación, payload y pruebas en una sola página." --- Los webhooks permiten que tu servidor reciba eventos de suscripción de Adapty en tiempo real — compras, renovaciones, cancelaciones, problemas de facturación y reembolsos — para que puedas otorgar acceso, sincronizar tu backend o activar flujos de trabajo. Esta guía te lleva desde el endpoint hasta una integración verificada y probada en una sola página, y muestra cómo hacer que un agente de codificación con IA escriba el handler para tu stack. :::tip ¿Usando un agente de codificación con IA? Haz clic en **Copy for LLM** bajo el título y pega toda esta página en tu agente — tiene la configuración, el payload y la lógica del handler que necesita. ::: ## Cómo funcionan los webhooks de Adapty \{#how-adapty-webhooks-work\} - **Unidireccional y en tiempo real**: Adapty envía un `POST` HTTP a tu servidor cuando ocurre un evento — sin polling. - **Dos tipos de solicitud**: Una solicitud de verificación única (enviada al guardar la integración) y los eventos de suscripción continuos. - **Una URL por entorno**: Configuras un endpoint independiente para producción y para el sandbox. - **Debes confirmar cada solicitud**: Responde con un estado `2xx` rápidamente; Adapty reintentará en caso de fallo. ## Crea tu endpoint \{#build-your-endpoint\} Crea un endpoint HTTPS público que gestione dos tipos de solicitudes: - **Solicitud de verificación**: Se envía una vez cuando guardas la integración. Tiene un cuerpo JSON vacío (`{}`). Responde con un estado `2xx` y un cuerpo JSON. - **Eventos de suscripción**: Solicitudes `POST` continuas con el evento en el cuerpo. Responde `200` en menos de 10 segundos y realiza cualquier tarea pesada de forma asíncrona. Elige una cadena secreta y guárdala como variable de entorno (por ejemplo, `ADAPTY_WEBHOOK_SECRET`). En cada petición, comprueba que el encabezado `Authorization` coincide con ella y rechaza la petición si no coincide — el mismo secreto lo introducirás en el dashboard a continuación. ```javascript title="webhook.js" const app = express(); app.use(express.json()); const WEBHOOK_SECRET = process.env.ADAPTY_WEBHOOK_SECRET; app.post("/adapty/webhook", (req, res) => { // 1. Verify the shared secret Adapty echoes back. if (req.get("Authorization") !== WEBHOOK_SECRET) { return res.sendStatus(401); } // 2. Acknowledge fast, then process asynchronously. res.status(200).json({}); // 3. The verification request has an empty body — nothing to handle. const event = req.body; if (!event.event_type) return; switch (event.event_type) { case "subscription_started": case "subscription_renewed": case "trial_converted": // Grant or extend access. break; case "subscription_expired": case "subscription_refunded": // Revoke access. break; default: break; } }); app.listen(3000); ``` Despliega el endpoint en una URL HTTPS pública antes de configurar la integración: Adapty envía la solicitud de verificación en el momento en que guardas. ### Eventos clave y el payload \{#key-events-and-the-payload\} Todos los eventos comparten el mismo sobre. Los campos varían según el tipo de evento, el store y las opciones que hayas activado. Aquí tienes un ejemplo reducido del evento `subscription_started`: ```json title="Example event" { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem", "event_type": "subscription_started", "event_datetime": "2024-11-15T10:45:36.181000+0000", "event_properties": { "store": "play_store", "currency": "USD", "price_usd": 4.99, "vendor_product_id": "onemonth_no_trial", "transaction_id": "0000000000000000", "original_transaction_id": "0000000000000000", "subscription_expires_at": "2024-12-15T10:45:36.181000+0000", "profile_event_id": "00000000-0000-0000-0000-000000000000" }, "event_api_version": 1 } ``` Los eventos que gestionarás con más frecuencia: | Tipo de evento | Se activa cuando | | --- | --- | | `subscription_started` | Un usuario inicia una suscripción de pago | | `subscription_renewed` | Una suscripción se renueva y se cobra con éxito | | `subscription_renewal_cancelled` | Un usuario desactiva la renovación automática (el acceso dura hasta la fecha de expiración) | | `subscription_expired` | El acceso termina después de que una suscripción no renovada caduca | | `trial_started` | Un usuario inicia una prueba gratuita | | `trial_converted` | Una prueba se convierte en una suscripción de pago | | `billing_issue_detected` | Falla el pago de una renovación | | `subscription_refunded` | Se reembolsa una compra de suscripción | Para la lista completa de eventos y cada campo, consulta [Tipos de eventos y campos de webhook](https://adapty.io/docs/es/webhook-event-types-and-fields.md). :::warning No ordenes los eventos por `event_datetime` — es el momento de negocio del evento, por lo que los eventos pueden llegar desordenados o compartir el mismo timestamp. Ordénalos por tu propio tiempo de recepción y deduplícalos usando `profile_event_id` o los IDs de transacción. ::: ## Configura el webhook en Adapty \{#configure-the-webhook-in-adapty\} 1. Abre [Integrations → Webhook](https://app.adapty.io/integrations/customwebhook) en el Adapty Dashboard. 2. Activa la integración. 3. En **Production endpoint URL**, introduce la URL HTTPS del endpoint que has desplegado. 4. En **Authorization header value for production endpoint**, introduce el mismo secreto que comprueba tu endpoint. Adapty envía este valor en el header `Authorization` en cada petición. Es opcional, pero muy recomendable. 5. Para probar primero en sandbox, rellena también **Sandbox endpoint URL** y su **Authorization header value**. 6. Haz clic en **Save**. Adapty envía inmediatamente la solicitud de verificación a tu endpoint, que responde con un `2xx` para completar la configuración. Para elegir qué eventos enviar, asignar nombres de eventos o habilitar campos opcionales (precio de prueba, eventos históricos, atribución, atributos de usuario, token de Play Store), consulta [Configurar la integración de webhook](https://adapty.io/docs/es/set-up-webhook-integration.md). ## Constrúyelo con tu agente de programación IA \{#build-it-with-your-ai-coding-agent\} Dale a tu agente de programación IA esta guía y la documentación de referencia en Markdown (añade `.md` a cualquier URL de página), indícale tu stack y deja que genere el handler: - [Tipos de eventos y campos del webhook](https://adapty.io/docs/es/webhook-event-types-and-fields.md) - [Configurar la integración de webhook](https://adapty.io/docs/es/set-up-webhook-integration.md) Ejemplo de prompt: ``` Read these Adapty webhook docs, then write a webhook handler for my Express app: verify the Authorization header against ADAPTY_WEBHOOK_SECRET, answer the verification request, acknowledge events with 200, and grant or revoke access based on event_type. ``` The agent writes the handler code, but it can't deploy your endpoint or configure the dashboard — host the endpoint yourself and set the URL and secret in **Integrations → Webhook**. ## Prueba tu webhook \{#test-your-webhook\} Prueba en el sandbox antes de pasar a producción: 1. Configura el endpoint de sandbox y el secreto tal como se describe arriba. 2. En tu app de sandbox, realiza una compra, inicia una prueba o emite un reembolso para activar un evento. 3. Abre la sección **Last sent events** de la integración. Un evento entregado correctamente muestra el estado **Success**. Si un evento muestra **Sending failed**, tu servidor devolvió un estado fuera del rango 200–399 — pasa el cursor sobre el estado para ver los detalles. Para ver el proceso de prueba completo, consulta [Probar la integración de webhook](https://adapty.io/docs/es/test-webhook.md). ## Límites \{#limits\} - **Responde en menos de 10 segundos**: Si Adapty no recibe respuesta a tiempo, considera el intento como fallido y reintenta. - **Reintentos**: Si tu código de estado está fuera del rango 200–404, Adapty reintenta con retroceso exponencial — hasta 9 reintentos en 24 horas. - **Retraso en cancelaciones**: Los eventos de cancelación pueden tardar hasta 2 horas en llegar. - **Una URL por entorno**: Para enviar eventos a varios servicios, apunta el webhook a tu propio backend y distribúyelos desde allí. --- # File: server-side-api-with-ai --- --- title: "Verificar y otorgar acceso a suscripciones desde tu backend" description: "Usa la API de servidor de Adapty para comprobar si un usuario tiene una suscripción activa y otorgar acceso manualmente, con ayuda de un agente de codificación con IA." --- Desde tu backend, usa la API de servidor de Adapty para comprobar si un usuario tiene una suscripción activa y para conceder acceso manualmente. Esta guía cubre las dos llamadas más comunes — `getProfile` y `grantAccessLevel` — y muestra cómo hacer que un agente de codificación con IA escriba la integración para tu stack. :::tip ¿Usas un agente de codificación con IA? Haz clic en **Copy for LLM** debajo del título y pega toda esta página en tu agente — contiene las llamadas, los campos y los detalles que necesita. ::: ## Antes de empezar \{#before-you-start\} - **Una clave de API secreta**: Encuéntrala en [App settings → General](https://app.adapty.io/settings/general), en el campo **Secret key**. Las claves son específicas de cada app. Guárdala en una variable de entorno (por ejemplo, `ADAPTY_SECRET_KEY`) y envíala como `Authorization: Api-Key {key}`. - **La URL base**: Todas las solicitudes van a `https://api.adapty.io`. - **Una forma de identificar al usuario**: Envía `adapty-customer-user-id` (tu propio ID de usuario — solo funciona si identificas a los usuarios en la app) o `adapty-profile-id` (el ID de perfil de Adapty). Son intercambiables; usa uno. ## Comprobar una suscripción \{#check-a-subscription\} Para comprobar el estado, llama a `getProfile` con `GET` y pasa el identificador de usuario como encabezado — no hay cuerpo de solicitud. ```javascript title="check-access.js" const res = await fetch("https://api.adapty.io/api/v2/server-side-api/profile/", { headers: { "Authorization": `Api-Key ${process.env.ADAPTY_SECRET_KEY}`, "adapty-customer-user-id": userId, }, }); const { data } = await res.json(); function hasActiveAccess(profile, accessLevelId = "premium") { const level = profile.access_levels?.find(a => a.access_level_id === accessLevelId); if (!level) return false; if (level.is_in_grace_period) return true; if (!level.expires_at) return true; // lifetime / non-expiring return new Date(level.expires_at) > new Date(); // not expired yet } if (hasActiveAccess(data)) { // unlock premium features } ``` A diferencia del perfil del SDK, la respuesta del servidor **no tiene campo `is_active`**. Determina el estado tú mismo a partir de `access_levels[].expires_at`: `null` significa acceso de por vida, una fecha futura significa activo y una fecha pasada significa expirado. Trata `is_in_grace_period` como aún activo. Para ver todos los campos de perfil y nivel de acceso, consulta [getProfile](https://adapty.io/docs/es/api-adapty/operations/getProfile.md). ## Conceder acceso manualmente \{#grant-access-manually\} Para desbloquear funciones de pago sin una compra —códigos promocionales, acceso para inversores o beta testers, casos de soporte— llama a `grantAccessLevel` con `POST`. ```javascript title="grant-access.js" await fetch("https://api.adapty.io/api/v2/server-side-api/purchase/profile/grant/access-level/", { method: "POST", headers: { "Authorization": `Api-Key ${process.env.ADAPTY_SECRET_KEY}`, "adapty-customer-user-id": userId, "Content-Type": "application/json", }, body: JSON.stringify({ access_level_id: "premium" }), // add "expires_at" for temporary access }); ``` Dos cosas a tener en cuenta: - **El nivel de acceso debe existir previamente** en tu dashboard (**Access levels**) — `access_level_id` es su identificador, no un nombre nuevo. - **Las concesiones manuales no aparecen en los análisis**. Solo se entregan a tu integración de webhook y al Event Feed, por lo que los gráficos de ingresos y conversión no las reflejarán. Para los detalles de solicitud y respuesta, consulta [grantAccessLevel](https://adapty.io/docs/es/api-adapty/operations/grantAccessLevel.md). ## Constrúyelo con tu agente de código IA \{#build-it-with-your-ai-coding-agent\} Dale a tu agente de código IA esta guía y la especificación de la API en Markdown (añade `.md` a cualquier URL de página), indícale tu stack y deja que escriba las llamadas: - [Especificación OpenAPI](https://adapty.io/docs/es/api-specs/adapty-api.yaml) - [getProfile](https://adapty.io/docs/es/api-adapty/operations/getProfile.md) - [grantAccessLevel](https://adapty.io/docs/es/api-adapty/operations/grantAccessLevel.md) Ejemplo de prompt: ``` Using the Adapty server-side API spec, write backend functions to check whether a user has an active "premium" access level (GET /profile/, derive status from expires_at — there's no is_active field) and to grant it (grantAccessLevel). Authenticate with ADAPTY_SECRET_KEY and identify users by adapty-customer-user-id. ``` The agent writes the code, but it can't run your backend or set your keys — you provide the secret key and the user identifiers. ## Límites \{#limits\} - **Límite de velocidad**: Hasta 40.000 solicitudes por minuto por app. - **Claves específicas por app**: Cada clave funciona para una sola app; usa la clave correspondiente para cada app. - **Se requiere un identificador**: Cada solicitud necesita `adapty-customer-user-id` o `adapty-profile-id`. --- # File: test-purchases-in-sandbox --- --- title: "Pruebas en sandbox" description: "Prueba compras en el entorno sandbox para garantizar transacciones sin problemas." --- Una vez que hayas configurado todo en el Adapty Dashboard y en tu aplicación móvil, es momento de realizar pruebas de compras in-app. **Nota:** ninguna de las herramientas de prueba cobra a los usuarios cuando prueban comprar un producto. La App Store no envía correos electrónicos por compras ni reembolsos realizados en los entornos de prueba. :::note **Las transacciones en sandbox se excluyen de todos los gráficos de análisis.** Siguen apareciendo en las páginas de perfil individuales y en el feed de eventos. ::: :::info Para proceder con las pruebas de compras in-app, asegúrate de que: - Has completado las guías de [inicio rápido](quickstart) sobre integración con la store, añadir productos e integración del SDK de Adapty. - Tu producto está marcado como [**Ready to submit**](InvalidProductIdentifiers#step-2-check-products) en App Store Connect. ::: ## Pruebas en sandbox \{#sandbox-testing\} <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/hq4PRU-vuik?si=m5F5Sj6iLEJ-2q6n" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> :::info Recomendamos probar las compras in-app con un dispositivo real. Aunque las compras en sandbox pueden ejecutarse en simuladores, los dispositivos reales son necesarios para probar todos los flows por completo, incluidos los diálogos de pago y las solicitudes biométricas. ::: Tienes dos formas principales de probar las compras in-app: - **Compilar en Xcode y ejecutar en un dispositivo de prueba**: Ideal para desarrolladores y QA engineers. - **Usar una cuenta de prueba en sandbox con TestFlight**: Adecuado para cualquier otra persona. Ambas opciones se explican en la guía a continuación. ### Paso 1. Crear una cuenta de prueba Sandbox en App Store Connect \{#step-1-create-sandbox-test-account-in-app-store-connect\} :::warning Crea una cuenta de prueba Sandbox nueva para asegurarte de que tu historial de compras esté limpio. Si reutilizas una cuenta existente, los productos que hayas comprado anteriormente seguirán disponibles y no podrás probar volver a comprarlos. ::: Puedes crear una nueva cuenta de prueba Sandbox en unos pocos clics: 1. Ve a [**Users and Access** > **Sandbox** > **Test Accounts**](https://appstoreconnect.apple.com/access/users/sandbox) en App Store Connect y haz clic en **+**. <img src="/assets/shared/img/add-sandbox-user.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Introduce los datos del usuario de prueba. Asegúrate de definir el **Country or Region** que quieres probar, ya que afecta a la disponibilidad de productos en esa región y a la moneda de compra. :::tip - Si usas Gmail o iCloud, puedes reutilizar tu dirección de correo existente con [subdireccionamiento con signo más](https://www.wikihow.com/Use-Plus-Addressing-in-Gmail). - Puedes usar una dirección de correo aleatoria que ni siquiera exista, pero asegúrate de rechazar la autenticación de dos factores (2FA) cuando inicies sesión en un dispositivo de prueba más adelante. ::: <img src="/assets/shared/img/57c3a7c-apple_new_test_account.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Create**. ### Paso 2. Activar el modo de desarrollador \{#step-2-enable-the-developer-mode\} :::note Omite este paso si el modo de desarrollador ya está **activado** en tu dispositivo de prueba o si **no tienes un Mac**. ::: Necesitarás un Mac con Xcode instalado y el cable de tu dispositivo de prueba: 1. Abre Xcode en tu Mac. Si vas a probar compras in-app con TestFlight, solo necesitas tener Xcode instalado; no hace falta tener ninguna app abierta. 2. Conecta tu dispositivo de prueba al Mac con el cable. 3. Ve a **Settings > Privacy & Security > Developer Mode** en tu dispositivo de prueba y activa el **Developer Mode**. ### Paso 3. Descarga la app desde TestFlight \{#step-3-download-the-app-from-testflight\} :::info Este paso solo aplica si estás probando con TestFlight. Si estás compilando la app en Xcode, omite este paso. ::: Para más información sobre cómo enviar tu app a TestFlight, consulta la [documentación de Apple](https://developer.apple.com/documentation/StoreKit/testing-in-app-purchases-with-sandbox#Prepare-for-sandbox-testing). Antes de descargar la app de TestFlight, asegúrate de que en tu dispositivo de prueba estás conectado con tu Apple Account de producción. Luego descarga desde TestFlight la app que quieres probar. :::danger No abras la aplicación una vez descargada. Continúa directamente con los siguientes pasos. Si la abriste por accidente, elimínala de tu dispositivo de prueba y descárgala de nuevo. De lo contrario, tu historial de compras podría no estar limpio y las pruebas de compras in-app producirán errores. ::: ### Paso 4. Cambia a la cuenta de prueba Sandbox \{#step-4-switch-to-sandbox-test-account\} <Details> <summary>¿No usas Mac? Aquí tienes una alternativa</summary> Si no trabajas en macOS, no puedes cambiar a una cuenta sandbox desde Xcode. Sin embargo, puedes hacerlo directamente en tu dispositivo de prueba: 1. Ve a **Settings > Your Apple Account > Media & Purchases** en tu dispositivo de prueba. 2. Selecciona **Sign Out** en el menú emergente. 3. Abre la app descargada desde TestFlight e intenta comprar un producto. 4. Cuando se te pida que inicies sesión, introduce las credenciales de tu cuenta sandbox para cambiar al entorno sandbox. </Details> Para cambiar a tu cuenta sandbox: 1. Ve a **Settings > Your Apple Account > Media & Purchases** en tu dispositivo de prueba. 2. Selecciona **Sign Out** en el menú emergente. 3. Ve a **Settings > Developer**. Si la opción **Developer** no está disponible, asegúrate de haberla [activado en el paso 2](#step-2-enable-the-developer-mode). <img src="/assets/shared/img/devmode.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hacia abajo hasta la sección **Sandbox Apple Account** y pulsa **Sign In**. <img src="/assets/shared/img/sandbox-acc.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Inicia sesión con las credenciales de tu Sandbox Apple Account. ### Paso 5. Borrar el historial de compras \{#step-5-clear-purchase-history\} Si acabas de crear una nueva cuenta de prueba de Sandbox y has cambiado a ella, puedes saltarte este paso, ya que solo aplica cuando repites pruebas con la misma cuenta de Sandbox. 1. Ve a **Settings > Developer > Sandbox Apple Account** en tu dispositivo de prueba. 2. Selecciona **Manage** en el menú emergente. 3. Ve a **Account Settings** y pulsa **Clear Purchase History**. :::danger Este paso es obligatorio cada vez que repitas las pruebas con la misma cuenta de prueba de Sandbox. En ese caso, también tendrás que [cerrar sesión en tu cuenta de prueba de Sandbox](#step-4-switch-to-sandbox-test-account) y volver a iniciar sesión para borrar la caché del historial de compras en el dispositivo de prueba. ::: ### Paso 6. Compilar en Xcode y ejecutar \{#step-6-build-in-xcode-and-run\} :::info Este paso solo aplica si estás probando con una compilación de Xcode. Si usas TestFlight, omite este paso. ::: 1. Conecta tu dispositivo de prueba al Mac. 2. Abre Xcode. 3. Haz clic en **Run** en la barra de herramientas o selecciona **Product > Run** para compilar y ejecutar la app en el dispositivo conectado. Si la compilación es correcta, Xcode lanzará la app en tu dispositivo y abrirá una sesión de depuración en el área de debug. Tu app ya está lista para pruebas en el dispositivo. ### Paso 7. Realiza una compra de prueba \{#step-7-make-test-purchase\} Abre la app y realiza tu compra de prueba a través de un paywall. Una vez hecho, consulta el artículo sobre [validación de compras de prueba](validate-test-purchases) para revisar los resultados. ### Paso 8. Sigue probando \{#step-8-keep-testing\} Tu entorno de pruebas ya está listo. Si quieres volver a probarlo, [borra el historial de compras de la cuenta sandbox](https://developer.apple.com/help/app-store-connect/test-in-app-purchases/manage-sandbox-apple-account-settings/). ## Problemas de prueba \{#testing-issues\} A continuación se describen los problemas más comunes que puedes encontrar al probar una app. ### Problemas con TestFlight \{#testflight-issues\} No puedes borrar tu historial de compras **si usas TestFlight sin una cuenta de prueba de Sandbox**, lo que genera varios problemas y resultados de prueba incorrectos. Si olvidaste accidentalmente [cambiar a la cuenta de prueba de Sandbox](#step-4-switch-to-sandbox-test-account) y abriste la app aunque sea una vez, TestFlight asocia tu historial de compras con tu cuenta de Apple de producción, lo que provoca problemas inesperados. Para solucionarlo, sigue estos pasos: 1. Elimina la app del dispositivo de prueba. 2. Sigue los pasos para las [pruebas en Sandbox](#sandbox-testing). :::note Es importante no solo reinstalar la aplicación, sino también cambiar a la cuenta de prueba Sandbox, borrar el historial de compras e iniciarla con la cuenta de prueba Sandbox. ::: ### Problemas con los niveles de acceso compartidos \{#shared-access-levels-issues\} Si repites las pruebas con la misma cuenta de prueba de Sandbox, es posible que encuentres un comportamiento inesperado con los [niveles de acceso compartidos](sharing-paid-access-between-user-accounts) para el usuario de prueba. Para comprobar si el usuario tiene un nivel de acceso heredado, ve a [Profiles & Segments](https://app.adapty.io/profiles/users) desde el Adapty Dashboard y abre el perfil del usuario. <img src="/assets/shared/img/profile-access-level-origin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Si el usuario tiene un nivel de acceso heredado, sigue estos pasos para obtener resultados de prueba precisos: 1. Elimina el perfil principal. 2. Elimina la app del dispositivo de prueba. 3. [Descarga la app desde TestFlight](#step-3-download-the-app-from-testflight). 4. [Cambia a la cuenta de prueba de Sandbox](#step-4-switch-to-sandbox-test-account). 5. [Borra el historial de compras](#step-5-clear-purchase-history). 6. [Abre la app y realiza tu compra de prueba](#step-6-make-test-purchase). :::note Borrar el historial de compras es lo que restablece la compra en el lado del store. Eliminar el perfil principal solo borra el registro en el lado de Adapty. Para entender por qué una cuenta reutilizada mantiene el acceso y qué acciones de restablecimiento funcionan realmente, consulta [Restablecer la suscripción de un tester](#resetting-a-testers-subscription). ::: ### Actualización de la app en TestFlight \{#updating-app-in-testflight\} Si la app de TestFlight se ha actualizado: 1. Elimina la app del dispositivo de prueba. 2. [Descarga la app desde TestFlight](#step-3-download-the-app-from-testflight). 3. [Cambia a la cuenta de prueba Sandbox](#step-4-switch-to-sandbox-test-account). 4. [Borra el historial de compras](#step-5-clear-purchase-history). 5. [Abre la app y realiza tu compra de prueba](#step-6-make-test-purchase). ## Restablecer la suscripción de un tester \{#resetting-a-testers-subscription\} En el entorno sandbox, una compra pertenece a la **cuenta sandbox de Apple**, no al perfil de Adapty. Las acciones que realices en el perfil —eliminarlo o editar su nivel de acceso— no eliminan la compra de la cuenta del store. En el siguiente reinstalado o sincronización, el SDK vuelve a asociar la misma transacción y el tester recupera el acceso. La siguiente tabla muestra qué cambia con cada acción de restablecimiento y qué ve el tester después. | Acción | Perfil de Adapty | Cuenta sandbox de Apple | Acceso del tester después | | :-------------------------------------------------------------------------------- | :------------------------------------------------------ | :---------------------- | :------------------------------------------------------------------------------------------------- | | Eliminar el perfil en el Adapty Dashboard | Eliminado | Sin cambios | **Regresa** — al reinstalar, un nuevo perfil vuelve a enlazar la misma cadena de transacciones | | Eliminar el perfil mediante la [API de eliminación de perfil](api-adapty/operations/deleteProfile) | Eliminado | Sin cambios | **Regresa** — igual que eliminarlo en el Dashboard | | Añadir una fecha de expiración pasada mediante **Add access level** | Sobreescrito en la siguiente sincronización | Sin cambios | **Regresa** en la siguiente renovación — la suscripción activa vuelve a aplicar una fecha de expiración futura | | Llamar a la [API de revocación de nivel de acceso](api-adapty/operations/revokeAccessLevel) | Expira ahora, dispara `access_level_updated` (`is_active=false`) | Sin cambios | **Regresa** en la siguiente renovación o reinstalación — no es un reinicio de sandbox fiable | | Cancelar la suscripción en la cuenta sandbox | Sin cambio directo | Suscripción cancelada | Las renovaciones se detienen, el acceso termina cuando expira el período actual y el tester puede volver a comprar el producto | | Iniciar sesión con una cuenta sandbox de Apple nueva | Perfil nuevo | Cuenta nueva y vacía | **Limpio** — recomendado para pruebas repetidas | ### Restablecer un tester a un estado limpio \{#reset-a-tester-to-a-clean-state\} Para probar el flujo de compra varias veces, usa una cuenta sandbox de Apple nueva para cada prueba en lugar de restablecer el perfil. Sigue el [Paso 1](#step-1-create-sandbox-test-account-in-app-store-connect) para crear la cuenta y el [Paso 4](#step-4-switch-to-sandbox-test-account) para cambiar a ella en el dispositivo. Si reutilizas una cuenta sandbox existente, [borra su historial de compras](#step-5-clear-purchase-history) primero — eliminar el perfil de Adapty no lo borra. ### Eliminar el acceso a un tester existente \{#remove-access-from-an-existing-tester\} Para quitar el acceso a un tester, no retrocedas la fecha de expiración ni llames a la API Revoke access level. En sandbox, la suscripción se renueva automáticamente cada pocos minutos. Cada renovación restaura una fecha de expiración futura en la misma cadena de transacciones, por lo que el acceso se recupera solo. La API Revoke access level sí dispara un evento `access_level_updated` (`is_active=false`), pero la siguiente renovación lo sobreescribe. Para detener el acceso realmente, cancela la suscripción desde el store. En el dispositivo de prueba, ve a **Settings > Developer > Sandbox Apple Account**, selecciona **Manage** y cancela la suscripción. Los renovaciones se detienen y el acceso termina cuando expira el período actual. ### Por qué eliminar el perfil devuelve el acceso \{#why-deleting-the-profile-brings-access-back\} Cuando un tester reinstala la app, Adapty recibe el historial de compras de la cuenta sandbox y vincula la nueva instalación a la compra existente. La compra está asociada a la cuenta del store, no al perfil que eliminaste. - **Perfiles anónimos**: Una reinstalación sin `customer_user_id` siempre hereda el nivel de acceso de la cuenta de la store, independientemente de tu configuración de [compartición de acceso de pago](sharing-paid-access-between-user-accounts). - **Perfiles identificados**: Si el acceso se transfiere a un nuevo `customer_user_id` depende de tu configuración de compartición de acceso de pago. Para entender cómo Adapty enlaza estos perfiles en una cadena, consulta [Cómo funcionan los perfiles](how-profiles-work#parent-and-inheritor-profiles). ## Probar suscripciones \{#test-subscriptions\} Al probar la app con una cuenta de prueba de sandbox, puedes configurar la tasa de renovación de la suscripción para cada tester en sandbox. Consulta más información sobre cómo editar las tasas de renovación en la [documentación oficial de Apple](https://developer.apple.com/help/app-store-connect/test-in-app-purchases/manage-sandbox-apple-account-settings). Por defecto, las suscripciones se renuevan hasta 12 veces antes de detenerse, según el siguiente calendario: | Duración de la suscripción | 1 semana | 1 mes | 2 meses | 3 meses | 6 meses | 1 año | | :---------------------------------- | :--------- | :--------- | :--------- | :--------- | :--------- | :--------- | | Velocidad de renovación | 3 minutos | 5 minutos | 10 minutos | 15 minutos | 30 minutos | 1 hora | | Duración del reintento de cobro | 10 minutos | 10 minutos | 10 minutos | 10 minutos | 10 minutos | 10 minutos | | Duración del período de gracia | 3 minutos | 5 minutos | 5 minutos | 5 minutos | 5 minutos | 5 minutos | :::note Ten en cuenta que las transacciones de prueba pueden tardar hasta 10 minutos en aparecer en el [Event feed](validate-test-purchases). ::: Usa el sandbox para verificar que tu app y tu backend gestionan correctamente las renovaciones, los reintentos de cobro y los períodos de gracia — no para predecir los tiempos de renovación en producción. El calendario acelerado y limitado que se muestra arriba no coincide con el de producción. Para reproducir transacciones en tu servidor con fines de prueba de backend, usa la [API Set transaction](api-adapty/operations/setTransaction). ## Probar ofertas \{#test-offers\} Para que la elegibilidad funcione correctamente al probar ofertas, es necesario eliminar todos los recibos del usuario. La forma más fiable de probar ofertas es usar una [cuenta de prueba Sandbox](#step-1-create-sandbox-test-account-in-app-store-connect) completamente nueva. Repetir las pruebas con la misma cuenta de prueba Sandbox puede provocar comportamientos inesperados. :::danger Si repites las pruebas con la misma cuenta de prueba Sandbox, asegúrate de [borrar el historial de compras](#step-5-clear-purchase-history) para evitar problemas de elegibilidad. ::: --- # File: local-sk-files --- --- title: "Pruebas con StoreKit en Xcode" description: "Prueba compras en el entorno sandbox para garantizar transacciones fluidas." --- Las pruebas con StoreKit en Xcode te permiten probar compras in-app de forma local sin necesidad de configurar una cuenta sandbox. Para este tipo de pruebas, necesitas: 1. [Crear un producto en Adapty](quickstart-products) y asignarle un **App Store product ID**. 2. En Xcode, crea un [archivo de configuración de StoreKit](https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode) local y añade un producto. El ID del producto debe coincidir con el **App Store product ID** en Adapty. 3. Añade el archivo de configuración de StoreKit a tu esquema de compilación y compila la app. Ejecútala en el emulador o en tu dispositivo. ## ¿Debería usar las pruebas con StoreKit en Xcode? \{#should-i-use-storekit-testing-in-xcode\} Esta forma de probar es la más cómoda si eres desarrollador de la app y quieres probar la compilación sobre la marcha o reproducir distintos escenarios de compra usando las herramientas de Xcode. Sin embargo, ten en cuenta que este tipo de pruebas es local, por lo que ningún cambio aparecerá en el Adapty Dashboard. Antes de lanzar tu app en producción, te recomendamos que pruebes el [trabajo con perfiles](ios-quickstart-identify) usando el [entorno sandbox](test-purchases-in-sandbox). **Deberías** usar las pruebas con StoreKit si quieres: - Probar la lógica de compra - Reproducir distintos escenarios de compra con las herramientas de Xcode (por ejemplo, pago cancelado o reembolso) - Probar usando el emulador **No deberías** usar las pruebas con StoreKit si quieres: - Probar la lógica relacionada con perfiles - Verificar que tus acciones en la app aparecen en el Adapty Dashboard - Compartir tu app con equipos que no sean de desarrollo para pruebas ## Paso 1. Crea un archivo de configuración de StoreKit \{#step-1-create-a-storekit-configuration-file\} Para crear un archivo de configuración de StoreKit en Xcode: 1. Haz clic en **File > New > File from template**. Luego selecciona **StoreKit Configuration File** y haz clic en **Next**. 2. Ponle un nombre. Luego, dependiendo de si ya tienes los productos en App Store Connect: - Selecciona **Sync this file with an app in App Store Connect**: para crear un archivo de configuración que contendrá todos tus productos de App Store Connect y poder probarlos localmente. - No selecciones **Sync this file with an app in App Store Connect**: para crear un archivo de configuración vacío donde tendrás que añadir los productos manualmente. Haz clic en **Next**. 3. No añadas tu app como destino. Continúa. Si estás trabajando con productos sincronizados desde App Store Connect, ve al [Paso 2](#step-2-add-the-configuration-file-to-the-build-scheme). 4. Si tus productos no están sincronizados desde App Store Connect, haz clic en **+** en la parte inferior izquierda y selecciona un tipo de producto. 5. Introduce un nombre para el grupo de suscripción y haz clic en **Next**. 6. Introduce un nombre de referencia. En el campo **Product ID**, introduce el **App Store product ID** de tu producto en Adapty. 7. Configura el precio, las ofertas y otros ajustes del producto en el archivo de configuración. O añade más productos. ## Paso 2. Añade el archivo de configuración al esquema de compilación \{#step-2-add-the-configuration-file-to-the-build-scheme\} Para compilar la app usando este archivo de configuración, necesitas añadirlo a un esquema de compilación. La buena práctica es separar los esquemas de prueba y de producción, así que te sugerimos crear un nuevo esquema para pruebas: 1. En la parte superior, haz clic en el nombre de tu app y selecciona **New scheme**. 2. Introduce un nombre para el esquema y haz clic en **OK**. 3. Haz clic de nuevo en el nombre de la app y selecciona **Edit scheme**. En **StoreKit configuration**, selecciona tu archivo de configuración local para que se use al compilar. ## Paso 3. Compila y prueba \{#step-3-build--test\} Ahora puedes compilar la app y probar compras in-app sin conectarte al backend de App Store. Puedes realizar compras y obtener niveles de acceso de forma local. Estos cambios no se reflejarán en el Adapty Dashboard, pero aun así puedes probar el desbloqueo de funciones de pago localmente. [Lee más](https://developer.apple.com/documentation/xcode/testing-in-app-purchases-with-storekit-transaction-manager-in-code) sobre otras funciones disponibles con las pruebas de StoreKit en Xcode. --- # File: testing-on-android --- --- title: "Probar compras in-app en Google Play Store" description: "Prueba compras de suscripción en Android usando Adapty." --- Probar las compras in-app (IAPs) en tu app Android es un paso fundamental antes de publicarla. Las pruebas en sandbox son una forma segura y eficiente de probar IAPs sin cobrar dinero real a tus usuarios. En esta guía, te explicamos el proceso de pruebas en sandbox de IAPs en Google Play Store para Android. :::note **Las transacciones en sandbox se excluyen de todos los gráficos de análisis.** Siguen apareciendo en las páginas de perfil individuales y en el feed de eventos. ::: ## Entorno de pruebas \{#testing-environment\} Para garantizar el rendimiento óptimo de tu app Android, se recomienda probarla en un dispositivo real en lugar de un emulador. Aunque hemos probado con éxito en emuladores, Google recomienda usar un dispositivo real. Si decides usar un emulador, asegúrate de que tenga Google Play instalado. Esto ayudará a garantizar que tu app funcione correctamente. ## 1. Configura una cuenta de prueba para probar la app \{#1-set-up-test-account-for-app-testing\} Para facilitar las pruebas en fases posteriores del desarrollo, necesitarás configurar un usuario de prueba para las pruebas de compras in-app. Este usuario será la primera cuenta con la que inicies sesión en tu dispositivo Android de pruebas. Ten en cuenta que la cuenta principal de un dispositivo Android solo puede cambiarse realizando un restablecimiento de fábrica, lo que borra todos tus datos. Por eso es importante configurar correctamente tu cuenta de usuario de prueba para evitar tener que hacer un restablecimiento. :::important La forma de configurar una cuenta de prueba dependerá del dispositivo que uses: - Si tienes un dispositivo dedicado para pruebas, crea una **cuenta de prueba separada (una nueva cuenta de Gmail)**. - Si no tienes un dispositivo dedicado para pruebas, puedes usar tu propia **cuenta personal** y activar temporalmente las **License testing** para ella. - Si no tienes ningún dispositivo Android, puedes **crear una cuenta de prueba separada y usarla con un emulador**. Sin embargo, este enfoque no se recomienda ya que no permite detectar todos los posibles problemas de dispositivos reales. ::: ## 2. Activa License testing \{#2-enable-license-testing\} Una vez que hayas configurado una cuenta de usuario de prueba, deberás configurar las pruebas de licencias para tu app. Para hacerlo, sigue estos pasos: 1. En la barra lateral de Google Play Console, ve a **Settings** y selecciona **License testing** en la sección **Monetization**. <img src="/assets/shared/img/android-license-testing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Selecciona una lista de probadores de licencias existente o crea una nueva. <img src="/assets/shared/img/android-testers.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Añade la cuenta que usarás para las pruebas a la lista y guarda los cambios. Si los miembros de tu equipo también necesitan probar la app, puedes añadir sus correos electrónicos a la lista para que todo el grupo tenga acceso. <img src="/assets/shared/img/android-list.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 3. Crea una pista cerrada y añade la cuenta de prueba \{#3-create-closed-track-and-add-test-account-to-it\} Para empezar a probar, necesitas publicar una versión firmada de tu app en una pista cerrada: 1. Abre tu app y selecciona **Test and release > Testing > Closed testing** en el menú. Allí, haz clic en **Create track**. <img src="/assets/shared/img/android-closed-testing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Introduce el nombre de la pista de pruebas cerrada y haz clic en **Create track**. 3. Añade una lista de probadores a la pista. 4. En la sección **How testers join your test**, copia el enlace y envíalo al dispositivo que ha iniciado sesión con la cuenta de prueba. Abre el enlace en tu dispositivo de pruebas para convertir al usuario en probador. <img src="/assets/shared/img/android-link.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning Ten en cuenta lo siguiente para garantizar que las pruebas funcionen correctamente: - Abrir la URL de participación marca tu cuenta de Play para pruebas. Si no completas este paso, los productos no se cargarán. - Con frecuencia, los desarrolladores usan un ID de aplicación diferente para sus versiones de prueba. Esto puede causarte problemas, ya que Google Play Services utiliza el ID de aplicación para encontrar tus compras in-app. - Hay casos en los que un usuario de prueba puede estar autorizado a comprar consumibles pero no suscripciones si el dispositivo de prueba no tiene PIN. Esto puede manifestarse con un críptico mensaje de "Something went wrong". Asegúrate de que el dispositivo de prueba tenga un PIN y de que esté conectado a Google Play Store. ::: ## 4. Sube un APK firmado a la pista cerrada \{#4-upload-a-signed-apk-to-the-closed-track\} Genera un APK firmado o usa Android App Bundle para subir un APK firmado a la pista cerrada que acabas de crear. Ni siquiera necesitas lanzar el release. Solo sube el APK. Puedes encontrar más información al respecto en [este](https://support.google.com/googleplay/android-developer/answer/9859348?visit_id=638929100639477968-3849460621&rd=1) artículo de soporte. :::important Si tu app es nueva, es posible que tengas que hacerla disponible en tu país o región. Para ello, ve a **Testing > Closed testing**, haz clic en tu pista de pruebas y ve a **Countries/regions** para añadir los países y regiones deseados. ::: ## 5. Prueba las compras in-app \{#5-test-in-app-purchases\} Después de subir el APK, espera unos minutos para que se procese el release. Luego, abre tu dispositivo de pruebas e inicia sesión con la cuenta de correo electrónico que añadiste a la lista de probadores. A continuación, podrás probar las compras in-app como lo harías en una app de producción. <img src="/assets/shared/img/a8d2da9-image.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Más información \{#read-more\} Consulta los siguientes recursos para saber más sobre cómo probar compras in-app en apps Android: - [Períodos de renovación en sandbox](https://developer.android.com/google/play/billing/test#subs) - [Probar compras únicas](https://developer.android.com/google/play/billing/test#one-time) --- # File: validate-test-purchases --- --- title: "Validar compras de prueba" description: "Valida las compras de prueba en Adapty para garantizar transacciones sin problemas." --- Antes de publicar tu app en producción, es fundamental probar las compras in-app a fondo. Consulta nuestros artículos [Probar compras in-app en Apple App Store](test-purchases-in-sandbox) y [Probar compras in-app en Google Play Store](testing-on-android) para obtener una guía detallada sobre cómo hacerlo. Una vez que empieces a probar, necesitas verificar que las compras de prueba se hayan completado correctamente. Cada vez que realices una compra de prueba en tu dispositivo móvil, comprueba la transacción correspondiente en el [**Event Feed**](https://app.adapty.io/event-feed) del Adapty Dashboard. Si la compra no aparece en el **Event Feed**, significa que Adapty no la está registrando. ## La compra de prueba es exitosa \{#test-purchase-is-successful\} Si la compra de prueba se realiza correctamente, el evento de transacción aparecerá en el **Event Feed**: <img src="/assets/shared/img/9ade2d5-event_feed_sandbox.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Si las transacciones funcionan como se espera, pasa al [Release checklist](release-checklist) y continúa con la publicación de la app. ## La compra de prueba no es exitosa \{#test-purchase-is-not-successful\} Si no ves ningún evento de transacción en 10 minutos o encuentras un error en la app, consulta el artículo de [Solución de problemas](troubleshooting-test-purchases) y los artículos sobre manejo de errores [para iOS](ios-sdk-error-handling), [para Android](android-sdk-error-handling), [para React Native](react-native-handle-errors), [para Flutter](error-handling-on-flutter-react-native-unity), [para Unity](unity-handle-errors) y [Kotlin Multiplatform](kmp-handle-errors) para encontrar posibles soluciones. <img src="/assets/shared/img/31a79b2-no_events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: troubleshooting-test-purchases --- --- title: "Solución de problemas con compras de prueba" description: "Soluciona problemas con compras de prueba en Adapty y resuelve incidencias comunes en transacciones in-app." --- Si encuentras problemas con las transacciones, asegúrate primero de haber completado todos los pasos del [checklist de lanzamiento](release-checklist). Si ya los completaste y sigues teniendo problemas, sigue las indicaciones a continuación para resolverlos: ## Se devuelve un error en la app móvil \{#an-error-is-returned-in-the-mobile-app\} Consulta la lista de errores de tu plataforma: [para iOS](ios-sdk-error-handling), [para Android](android-sdk-error-handling), [para React Native](react-native-troubleshoot-purchases), [Flutter](error-handling-on-flutter-react-native-unity) y [Unity](unity-troubleshoot-purchases), y sigue nuestras recomendaciones para resolver el problema. ## La transacción no aparece en el Event Feed aunque no se devuelve ningún error en la app móvil \{#transaction-is-absent-from-the-event-feed-although-no-error-is-returned-in-the-mobile-app\} Para resolver este problema, verifica lo siguiente: 1. **Para iOS**: Asegúrate de usar un dispositivo real y no un simulador. 2. Asegúrate de que el `Bundle ID`/`Package name` de tu app coincida con el que figura en [**App settings**](https://app.adapty.io/settings/general). 3. Asegúrate de que el `PUBLIC_SDK_KEY` de tu app coincida con la **Public SDK key** del Adapty Dashboard: [**App settings** -> pestaña **General** -> subsección **API keys**](https://app.adapty.io/settings/general). 4. Asegúrate de estar usando una cuenta sandbox y no un [archivo de configuración local de StoreKit](local-sk-files). Si antes usaste un archivo de configuración local de StoreKit para pruebas, verifica que no lo estés usando en la compilación actual. ## No hay ningún evento en mi perfil de prueba \{#no-event-is-present-in-my-testing-profile\} Esto es un comportamiento normal. En Adapty se crea automáticamente un nuevo registro de perfil de usuario cuando: - Un usuario ejecuta tu app por primera vez - Un usuario cierra sesión en tu app **Por qué ocurre:** Todas las transacciones y eventos están vinculados al perfil que generó la primera transacción. Esto mantiene todo el historial de transacciones (pruebas, compras, renovaciones) asociado al mismo perfil. **Lo que verás:** Puede que aparezcan nuevos registros de perfil (llamados "perfiles no originales") sin eventos, pero conservarán los niveles de acceso. Es posible que veas eventos `access_level_updated`. Esto es un comportamiento esperado. **Para pruebas:** Para evitar la creación de múltiples perfiles, crea una nueva cuenta de prueba (Sandbox Apple ID) cada vez que reinstales la app. Para más detalles, consulta [Creación de perfiles](how-profiles-work#profile-creation). A continuación se muestra un ejemplo de perfil no original. Observa la ausencia de eventos en **User history** y la presencia de un nivel de acceso. <img src="/assets/shared/img/98d0dad-non-original_profile.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Los precios no reflejan los precios reales configurados en App Store Connect \{#prices-do-not-reflect-the-actual-prices-set-in-app-store-connect\} Tanto en Sandbox como en TestFlight, que usa el entorno sandbox para las compras in-app, lo importante es verificar que el flujo de compra funcione correctamente, no que los precios sean exactos. Cabe destacar que la API de Apple puede proporcionar datos inexactos en ocasiones, especialmente cuando los dispositivos o cuentas tienen distintas regiones configuradas. Como los precios provienen directamente del Store y el backend de Adapty no afecta en ningún modo a los precios de compra, puedes ignorar cualquier inexactitud en los precios durante las pruebas de compras a través de Adapty. Por tanto, prioriza la prueba del flujo de compra en sí sobre la exactitud de los precios para asegurarte de que funciona como se espera. ## La hora de la transacción en el Event Feed es incorrecta \{#the-transaction-time-in-the-event-feed-is-incorrect\} El **Event Feed** utiliza la zona horaria configurada en **App Settings**. Para alinear la zona horaria de los eventos con tu hora local, ajusta la **Reporting timezone** en [**App settings** -> pestaña **General**](https://app.adapty.io/settings/general). ## Los paywalls y los productos tardan mucho en cargarse \{#paywalls-and-products-take-a-long-time-to-load\} Este problema puede ocurrir si tu cuenta de prueba tiene un historial de transacciones muy largo. Te recomendamos encarecidamente crear una nueva cuenta de prueba cada vez, tal como se describe en la sección [Crear una cuenta de prueba en Sandbox (Sandbox Apple ID) en App Store Connect](test-purchases-in-sandbox#step-1-create-sandbox-test-account-in-app-store-connect). Si no puedes crear una cuenta nueva, puedes borrar el historial de transacciones de tu cuenta actual siguiendo estos pasos en tu dispositivo iOS: 1. Abre **Configuración** y toca **App Store**. 2. Toca tu **Sandbox Apple ID**. 3. En el popup, selecciona **Manage**. 4. En la página **Account Settings**, toca **Clear Purchase History**. Para más detalles, consulta la [documentación para desarrolladores de Apple](https://developer.apple.com/documentation/storekit/testing-in-app-purchases-with-sandbox). --- # File: test-devices --- --- title: "Dispositivos de prueba" description: "Aprende a gestionar los dispositivos de prueba en Adapty para un testing eficiente." --- Para hacer pruebas, puedes marcar tu dispositivo como dispositivo de prueba, lo que desactiva el caché y garantiza que los cambios se reflejen de inmediato. :::note Los dispositivos de prueba están disponibles a partir de las siguientes versiones del SDK: - iOS: 2.11.1 - Android: 2.11.3 - React Native: 2.11.1 La compatibilidad con Flutter y Unity se añadirá más adelante. ::: ## Marca tu dispositivo como dispositivo de prueba \{#mark-your-device-as-test\} 1. Abre **[App settings](https://app.adapty.io/settings/general)** en el Adapty Dashboard. 2. Desplázate hacia abajo hasta la sección **Test devices** en la pestaña **General**. <img src="/assets/shared/img/14c581d-test_device_add.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en el botón **Add test device**. <img src="/assets/shared/img/f86d5e2-test_users_add_device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. En la ventana **Add test device**, introduce: | Campo | Descripción | |:-----------------------------------------| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Test device name** | Nombre del dispositivo o dispositivos de prueba para tu referencia. | | **ID used to identify this test device** | Elige el tipo de identificador que usarás para identificar el dispositivo o dispositivos de prueba. Consulta nuestras recomendaciones en la sección [Qué identificador deberías usar](test-devices#which-identifier-you-should-use) para elegir la mejor opción. | | **ID value** | Introduce el valor del identificador. | 5. Recuerda hacer clic en el botón **Add test device** para guardar los cambios. ## Qué identificador deberías usar \{#which-identifier-you-should-use\} Para identificar un dispositivo puedes usar varios identificadores. Recomendamos los siguientes: - **Customer User ID** tanto para dispositivos iOS como Android si <InlineTooltip tooltip="identificas a tus usuarios en Adapty">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) y [Unity](unity-identifying-users)</InlineTooltip>. Es la mejor opción, especialmente si tienes más de un dispositivo de prueba para una misma cuenta en tu app. Si se usa el Customer User ID como **ID used to identify this test device**, todos los dispositivos vinculados a esa cuenta se marcarán como dispositivos de prueba. - **IDFA (iOS)** y **Advertising ID (Android)**: Estos identificadores publicitarios son la opción ideal para dispositivos iOS y Android respectivamente si ya solicitas el consentimiento de tus usuarios para acceder a ellos. Aunque tengas un Customer User ID, puede que prefieras usar identificadores publicitarios si cambias de cuenta en tu app durante las pruebas. Además, estos identificadores son útiles cuando la misma cuenta tiene tanto dispositivos de prueba como personales y no quieres que los dispositivos personales se marquen como dispositivos de prueba. Existen otras opciones, como el Adapty Profile ID, el IDFV y el Android ID, que son menos cómodas pero pueden usarse si no puedes utilizar el Customer User ID, el IDFA ni el Advertising ID. A continuación, revisamos todas las opciones posibles en detalle. ### Identificadores para todas las plataformas \{#identifiers-for-all-platforms\} | Identificador | Uso | |----------|-----| | Customer User ID | <p>Un identificador único que tú asignas para identificar a tus usuarios en tu sistema. Puede ser el correo electrónico del usuario, tu ID interno o cualquier otra cadena de texto. Para usar esta opción, debes <InlineTooltip tooltip="identificar a tus usuarios en Adapty">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) y [Unity](unity-identifying-users)</InlineTooltip>.</p><p></p><p>Es la mejor opción para identificar un dispositivo de prueba, especialmente si usas varios dispositivos para la misma cuenta. Todos los dispositivos con esa cuenta se considerarán dispositivos de prueba.</p> | | Adapty profile ID | <p>Un identificador único para el [perfil de usuario](profiles-crm) en Adapty.</p><p></p><p>Úsalo si no puedes usar el Customer User ID, el IDFA para iOS ni el Advertising ID para Android. Ten en cuenta que el Adapty Profile ID puede cambiar si reinstalar la app o vuelves a iniciar sesión.</p> | #### Cómo obtener el Customer User ID y el Adapty profile ID \{#how-to-obtain-customer-user-id-and-adapty-profile-id\} Ambos identificadores se pueden obtener en los detalles del **Profile** en el Adapty Dashboard: 1. Busca el perfil del usuario en la pestaña [**Adapty Profiles** -> **Event feed**](https://app.adapty.io/event-feed). :::note Para encontrar el perfil exacto, realiza un tipo de transacción poco frecuente. Cuando la transacción aparezca en el [**Event Feed**](https://app.adapty.io/event-feed), podrás identificarla fácilmente. ::: 2. Copia los valores de los campos **Customer user ID** y **Adapty ID** en los detalles del perfil: <img src="/assets/shared/img/345d308-test_users_CUID_adapty_ID.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Identificadores de Apple \{#apple-identifiers\} | Identificador | Uso | |----------|-----| | IDFA | <p>El Identifier for Advertisers (IDFA) es un identificador único de dispositivo que Apple asigna al dispositivo del usuario.</p><p></p><p>Es ideal para dispositivos iOS, ya que no cambia por sí solo, aunque puedes restablecerlo manualmente.</p><p>**Nota**: Desde la introducción de iOS 14.5, los anunciantes deben solicitar el consentimiento del usuario para acceder al IDFA. Asegúrate de pedirlo en tu app y de haberlo concedido en tu dispositivo de prueba.</p> | | IDFV | El Identifier for Vendors (IDFV) es un identificador alfanumérico único que Apple asigna a todas las apps de un mismo dispositivo pertenecientes al mismo editor o proveedor. Puede cambiar si reinstalar o actualizas tu app. | #### Cómo obtener el IDFA \{#how-to-obtain-the-idfa\} Apple no proporciona el IDFA por defecto. Obtenlo a través de la atribución del perfil en el Adapty Dashboard: 1. Busca el perfil del usuario en la pestaña [**Adapty Profiles** -> **Event feed**](https://app.adapty.io/event-feed). :::note Para encontrar el perfil exacto, realiza un tipo de transacción poco frecuente. Cuando la transacción aparezca en el [**Event Feed**](https://app.adapty.io/event-feed), podrás identificarla fácilmente. ::: 2. Abre los detalles del perfil y copia el valor del campo **IDFA** en la sección **Attributes**: <img src="/assets/shared/img/ce4a63f-test_users_idfa.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> También puedes [buscar en el App Store una app que te muestre tu IDFA](https://www.apple.com/us/search/idfa?src=globalnav). #### Cómo obtener el Identifier for Vendors (IDFV) \{#how-to-obtain-the-identifier-for-vendors-idfv\} Para obtener el IDFV, pide a tu desarrollador que lo solicite usando el siguiente método en tu app y que muestre el identificador recibido en los logs o en el panel de depuración. ```swift showLineNumbers title="Swift" UIDevice.current.identifierForVendor ``` ### Identificadores de Google \{#google-identifiers\} | Identificador | Uso | |----------|-----| | Advertising ID | <p>El Advertising ID es un identificador único de dispositivo que Google asigna al dispositivo del usuario.</p><p>Es ideal para dispositivos Android, ya que no cambia por sí solo, aunque puedes restablecerlo manualmente.</p><p> **Nota**: Para usarlo, desactiva la opción **Opt out of Ads Personalization** en la configuración de **Ads** si usas Android 12 o superior.</p>| | Android ID | El Android ID es un identificador único para cada combinación de clave de firma de la app, usuario y dispositivo. Está disponible en Android 8.0 y versiones superiores. | #### Cómo obtener el Advertising ID \{#how-to-obtain-advertising-id\} Para encontrar el Advertising ID de tu dispositivo: 1. Abre la app **Settings** en tu dispositivo Android. 2. Toca **Google**. 3. Selecciona **Ads** en **Services**. Tu Advertising ID aparecerá en la parte inferior de la pantalla. #### Cómo obtener el Android ID \{#how-to-obtain-android-id\} Para obtener el Android ID, pide a tu desarrollador que solicite el [ANDROID_ID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID) usando el siguiente método en tu app y que muestre el identificador recibido en los logs o en el panel de depuración. ```kotlin showLineNumbers title="Kotlin/Java" android.provider.Settings.Secure.getString(contentResolver, android.provider.Settings.Secure.ANDROID_ID); ``` --- # File: release-checklist --- --- title: "Lista de verificación para el lanzamiento" description: "Sigue la lista de verificación de Adapty para garantizar un proceso de actualización de tu app sin problemas." --- ¡Nos alegra que hayas elegido Adapty! Esperamos que la implementación haya ido bien. Esta guía te llevará paso a paso para asegurarte de que tu app esté lista para publicarse en los stores y de que el flujo de monetización funcione correctamente. ## Elementos esenciales antes del lanzamiento \{#pre-flight-essentials\} Lo que necesitas antes de empezar la validación: - Un dispositivo real con una cuenta sandbox - Acceso al Adapty Dashboard - Acceso a App Store Connect / Google Play Console :::note Aunque las compras sandbox pueden ejecutarse en simuladores, necesitas dispositivos reales para probar todos los flujos, incluidos los diálogos de pago y las solicitudes biométricas. ::: <Button id="test-purchases-in-sandbox"> Guía de pruebas para App Store </Button> <Button id="testing-on-android"> Guía de pruebas para Google Play </Button> ## Validaciones universales \{#universal-validations\} - [ ] **Conexión con el store**: Asegúrate de haber conectado Adapty a App Store y/o Google Play: - [ ] [App Store](initial_ios) - [ ] [Google Play](initial-android) - [ ] **Entrega de eventos de suscripción**: Confirma que las notificaciones del servidor están configuradas: - [ ] [Notificaciones del servidor de App Store](enable-app-store-server-notifications) - [ ] [Notificaciones en tiempo real para desarrolladores (RTDN)](enable-real-time-developer-notifications-rtdn) - [ ] **Identificación de perfiles**: Valida la lógica de identificación de usuarios y asegúrate de que las compras se asocien al perfil correcto: - [ ] [Comprueba que la lógica de identificación en el código de tu app coincide con tu caso de uso](ios-quickstart-identify) - [ ] [Asegúrate de entender la lógica de padre/heredero para compartir el acceso de pago entre perfiles de usuario](sharing-paid-access-between-user-accounts) - [ ] **Ofertas**: Si tienes ofertas promocionales de App Store en la app, asegúrate de haber [añadido tu clave de compra in-app](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers) tanto en el campo principal como en la sección **App Store promotional offers**. - [ ] **Recopilación de datos**: Garantiza el cumplimiento de la privacidad: - [ ] Si necesitas cumplir con normativas de privacidad como GDPR o CCPA, o tu app está destinada a niños, controla si [habilitas la recopilación y el uso compartido del IDFA e IP](sdk-installation-ios#data-policies). - [ ] Si tu app usa AppTrackingTransparency, asegúrate de [enviar el estado de autorización a Adapty](ios-deal-with-att). - [ ] **Etiquetas de privacidad**: [Más información](apple-app-privacy) sobre los datos que recopila Adapty y qué indicadores tendrás que configurar para la revisión. ## Validación de compras \{#purchase-validations\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Antes de publicar tu app, asegúrate de que las compras funcionan correctamente y de que tu paywall está listo para la revisión de la store. La forma de validar las compras in-app depende de cómo las hayas implementado: - Muestras un paywall creado en el Adapty Paywall Builder - Has implementado tu propio paywall y usas el método `makePurchase` dentro de él para gestionar las compras - Usas Adapty en modo observador (ya sea con el Adapty Paywall Builder o con tu paywall personalizado) <Tabs groupId="paywall" queryString> <TabItem value="builder" label="Adapty Paywall Builder" default> **Objetivo**: Adapty renderiza el paywall, los usuarios pueden comprar productos, el acceso se desbloquea y el flow de restauración funciona. - [ ] Tu app [muestra el paywall](ios-present-paywalls) desde el mismo placement que vas a publicar. - [ ] El paywall se muestra en pantalla. Si la carga tarda demasiado (por ejemplo, si tú o tus usuarios tenéis una conexión inestable), considera [ajustar tu fetch policy](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder). - [ ] El paywall coincide con la variante esperada (audiencia/idioma si aplica). Puedes [cambiar la prioridad de la audiencia](change-audience-priority) si es necesario. - [ ] Los productos y precios aparecen en el paywall. Ten en cuenta que la API de Apple puede proporcionar precios incorrectos durante las pruebas (especialmente con configuraciones de distintas regiones), así que prioriza probar el flujo de compra en sí sobre la exactitud de los precios, ya que Adapty no afecta a los precios del Store. - [ ] La compra en sandbox se completa correctamente. Se recibe el callback de compra exitosa. - [ ] El acceso se desbloquea y se mantiene. Comprueba que [el acceso de pago se concede según el perfil de Adapty actual](ios-check-subscription-status#connect-profile-with-paywall-logic). - [ ] Tras la compra, el perfil de Adapty tiene un nivel de acceso activo. - [ ] Las funciones de pago se desbloquean cuando el perfil contiene ese nivel de acceso (no solo en el callback de compra). - [ ] La restauración de compras funciona. Cuando reinstales la app o la instales en un dispositivo nuevo, la restauración automática de compras funciona según la configuración de [Compartir acceso de pago](sharing-paid-access-between-user-accounts). Si no tienes autenticación de backend, las compras se restauran automáticamente independientemente de la configuración. En otros casos, asegúrate de que los usuarios puedan restaurar sus compras tras reinstalar la app. - [ ] Requisitos de revisión del Store: - [ ] El botón **Restore purchases** está en el paywall. Puedes añadirlo en el Paywall Builder y procesará las restauraciones de compras automáticamente al pulsarlo. - [ ] Los Términos de Uso y la Política de Privacidad son accesibles desde la pantalla del paywall, y al hacer clic en estos enlaces se abren en un navegador. </TabItem> <TabItem value="makepurchase" label="Paywall personalizado (makePurchase)" default> **Objetivo**: Tú renderizas la UI; Adapty gestiona las compras, actualizaciones de perfil y restauraciones. - [ ] Los IDs de productos no están hardcodeados en el código de tu app. Solo hardcodeas IDs de [placement](placements). - [ ] Tu app [obtiene los productos](fetch-paywalls-and-products) desde el mismo placement que vas a publicar. - [ ] La lista de productos carga correctamente. Si la carga tarda demasiado (por ejemplo, si tú o tus usuarios tenéis una conexión inestable), considera [ajustar tu fetch policy](fetch-paywalls-and-products#fetch-paywall-information). - [ ] Los productos obtenidos coinciden con la variante esperada (audiencia/idioma, si aplica). Puedes [cambiar la prioridad de la audiencia](change-audience-priority) si es necesario. - [ ] Los productos y precios aparecen en el paywall. Ten en cuenta que la API de Apple puede mostrar precios incorrectos durante las pruebas (especialmente con distintas configuraciones de región), así que prioriza probar el flujo de compra en sí y no la exactitud de los precios, ya que Adapty no influye en los precios del Store. - [ ] La compra en sandbox con [makePurchase](making-purchases) se completa correctamente: - [ ] El resultado de compra exitosa se gestiona correctamente. - [ ] Los resultados pendientes, fallidos o cancelados se manejan sin errores. - [ ] Si [usas un Remote Config](present-remote-config-paywalls), sus valores se cargan correctamente en tu paywall. - [ ] Cuando se muestra un paywall, se llama al método [`logShowFlow` (iOS SDK v4+) / `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events). - [ ] La compra en sandbox se completa correctamente. Se recibe el callback de compra exitosa. - [ ] El acceso se desbloquea y persiste. Confirma que [el acceso de pago se concede según el perfil de Adapty actual](ios-check-subscription-status#connect-profile-with-paywall-logic). - [ ] Tras la compra, el perfil de Adapty tiene un nivel de acceso activo. - [ ] Las funciones de pago se desbloquean cuando el perfil contiene ese nivel de acceso (no solo en el callback de compra). - [ ] La restauración de compras funciona. Al reinstalar la app o instalarla en un nuevo dispositivo, la restauración automática de compras funciona según la configuración de [Compartir acceso de pago](sharing-paid-access-between-user-accounts). Si no tienes autenticación en el backend, las compras se restauran automáticamente independientemente de la configuración. En otros casos, asegúrate de que los usuarios puedan restaurar sus compras después de reinstalar la app. - [ ] Requisitos para la revisión del Store: - [ ] El botón **Restore purchases** es accesible y [gestiona las restauraciones](restore-purchase). - [ ] Los Términos de uso y la Política de privacidad son accesibles desde la pantalla del paywall, y al hacer clic en esos enlaces se abren en el navegador. </TabItem> <TabItem value="observer" label="Modo observador"> **Objetivo**: Tú gestionas las compras, actualizaciones de perfil y restauraciones; Adapty recibe el reporte de transacciones. - [ ] **Tu app completa las compras usando tu propio flujo de compra** (StoreKit / BillingClient / backend): - [ ] La compra en sandbox se completa correctamente en la interfaz de la store. - [ ] Los resultados pendientes, fallidos o cancelados se gestionan correctamente en tu app. - [ ] **Las transacciones se reportan a Adapty**. - [ ] El modo observador está [habilitado en el código de tu app](implement-observer-mode). - [ ] La compra aparece en el Event Feed de Adapty. - [ ] Las renovaciones, cancelaciones y reembolsos se reflejan con el tiempo (según corresponda). - [ ] **Se registran las vistas de paywall**. El método [`logShowFlow` (iOS SDK v4+) / `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events) se llama cuando se muestra un paywall. - [ ] **La restauración de compras funciona con tu implementación**. Al reinstalar la app o cambiar de dispositivo, el acceso se restaura correctamente. - [ ] **Requisitos de revisión de la store**: - [ ] La acción **Restaurar compras** es accesible y activa tu flujo de restauración. - [ ] Los Términos de uso y la Política de privacidad son accesibles desde el paywall o la pantalla de compra y se abren en un navegador. </TabItem> </Tabs> Si tienes alguna pregunta sobre la integración del SDK de Adapty, usa el chatbot de IA en la parte inferior derecha o contáctanos en [support@adapty.io](mailto:support@adapty.io). --- # File: submit-app-to-app-store --- --- title: "Envía tu app de iOS a la App Store" description: "Sube tu build a App Store Connect y envía tu app de iOS con suscripciones para revisión de Apple." --- Una vez que tu integración con Adapty esté probada y funcionando, ya puedes subir tu build a App Store Connect y enviar tu app para revisión de Apple. :::tip Antes de enviar, asegúrate de haber completado la [Lista de verificación para el lanzamiento](release-checklist) para verificar tu integración con Adapty, los flujos de compra y los requisitos de revisión de la tienda. ::: ## Sube tu build a App Store Connect \{#upload-your-build-to-app-store-connect\} ### Paso 1. Archiva tu app en Xcode y súbela a App Store Connect \{#step-1-archive-your-app-in-xcode-and-upload-it-to-app-store-connect\} 1. En Xcode, establece el destino de compilación en **Any iOS Device (arm64)**. <img src="/assets/shared/img/build-target.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 2. Selecciona **Product** > **Archive** en la barra de menú superior. <img src="/assets/shared/img/xcode-archive.webp" style={{ border: '1px solid #727272', width: '500px', display: 'block', margin: '0 auto' }} /> 3. Espera a que finalice el proceso de archivado. La ventana **Organizer** se abre automáticamente. Selecciona tu archivo y haz clic en **Distribute App**. <img src="/assets/shared/img/distribute-app.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 4. Elige **App Store Connect** como método de distribución. Sigue los pasos para completar la subida. :::note La subida puede fallar si faltan recursos obligatorios, como el icono de la app o la pantalla de inicio. Consulta el registro de errores de Xcode para más detalles. ::: <img src="/assets/shared/img/distribution-method.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> ### Paso 2. Comprueba el build en App Store Connect \{#step-2-check-the-build-in-app-store-connect\} 1. Ve a [App Store Connect](https://appstoreconnect.apple.com) y abre tu app. 2. Desplázate hasta la sección **Build**. Asegúrate de que el build que acabas de subir aparece ahí. :::note Puede tardar unos minutos en aparecer el build en App Store Connect tras la subida. ::: <img src="/assets/shared/img/app-store-build.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> ## Envía tu app y productos a revisión \{#submit-your-app-and-products-for-review\} Una vez que el build aparezca en la sección **Build**, adjunta tus suscripciones in-app y envía la app para revisión de Apple. ### Paso 1. Adjunta los productos al envío \{#step-1-attach-products-to-the-submission\} Cada suscripción debe tener el estado **Ready to Submit** en App Store Connect antes de poder adjuntarla. Si una suscripción está en borrador o le falta información, no aparecerá en la lista. 1. En la misma página, desplázate hasta la sección **In-App Purchases and Subscriptions**. 2. Haz clic en **Select in-app purchases or subscriptions**. <img src="/assets/shared/img/app-store-select-products.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 3. Selecciona todos los productos que quieras incluir en este envío y haz clic en **Done**. ### Paso 2. Envía para revisión \{#step-2-submit-for-review\} 1. Completa todos los campos obligatorios de la página (descripción, capturas de pantalla, palabras clave, etc.). 2. En la sección **App Store Version Release**, selecciona si quieres publicar tu app automáticamente, manualmente o de forma programada tras su aprobación. 3. Haz clic en **Add for Review** y luego en **Submit to App Review**. Apple revisa las apps en un plazo de 1 a 2 días, aunque los tiempos pueden variar. ## Verifica tu app en producción \{#verify-your-app-in-production\} Tras la aprobación de Apple: 1. Realiza una compra real (o espera a que tu primer usuario compre). 2. Abre el [**Event Feed**](https://app.adapty.io/event-feed) en el Adapty Dashboard y confirma que aparecen los eventos de transacciones en producción. 3. Comprueba que los eventos de suscripción (renovaciones, cancelaciones) llegan correctamente; esto depende de que las [notificaciones del servidor de App Store](enable-app-store-server-notifications) estén configuradas. Si los eventos de producción no aparecen, verifica la [configuración de tu conexión con App Store](app-store-connection-configuration). ## Próximos pasos \{#next-steps\} Tu app ya está en producción. Empieza a hacer crecer tus ingresos por suscripción: - **[Pruebas A/B](ab-tests)**: Experimenta con distintos paywalls para encontrar el que mejor convierte. - **[Analíticas](charts)**: Monitoriza métricas de suscripción como MRR, churn y conversión. - **Integraciones**: Envía eventos de suscripción a plataformas de [analíticas](analytics-integration) y de [atribución](attribution-integration). --- # File: general --- --- title: "Configuración de la app" description: "Explora la configuración general de Adapty para un uso sin complicaciones." --- Puedes navegar a la pestaña General de la página App Settings para gestionar el comportamiento, la apariencia y el reparto de ingresos de tu app. Aquí puedes personalizar el nombre e icono de tu app, gestionar las claves del SDK y la API de Adapty, configurar tu estado en el Small Business Program y elegir la zona horaria para los análisis y gráficos de tu app. ## 1. Detalles de la app \{#1-app-details\} <img src="/assets/shared/img/8fa2929-CleanShot_2023-04-21_at_15.16.222x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Elige un nombre e icono únicos que representen tu app en la interfaz de Adapty. Ten en cuenta que el nombre y el icono de la app no afectarán al nombre ni al icono en el App Store o Google Play. Además, asegúrate de seleccionar una categoría de app adecuada que refleje con precisión el propósito y el contenido de tu app. Esto ayudará a los usuarios a descubrirla y garantizará que aparezca en las categorías correctas de la store. ## 2\. Miembro del Small Business Program y tarifa de servicio reducida \{#2-member-of-small-business-program-and-reduced-service-fee\} <img src="/assets/shared/img/825e2be-CleanShot_2023-04-19_at_13.43.292x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Si tu organización está inscrita en el [Small Business Program](app-store-small-business-program) de Apple o en el programa de [tarifa de servicio reducida](google-reduced-service-fee) de Google, tus apps están sujetas a una comisión reducida de la store. Notifica a Adapty si tu app está inscrita en un programa de comisión reducida. Para garantizar cálculos correctos, especifica el estado de estos programas en la sección "Reduced Store Fee". La configuración de tarifa reducida solo se aplica a las transacciones futuras. Cambia tu estado **antes** de que entre en vigor y Adapty ajustará la tasa de comisión. :::warning * Si extiendes tu participación en un programa de tarifa reducida, **añade un período de elegibilidad adicional**. * Si pierdes la membresía en el programa, **cambia la fecha de vencimiento** de tu período de elegibilidad actual. ::: Los siguientes artículos profundizan en este tema: * [App Store Small Business Program](app-store-small-business-program) * [Google Reduced Service Fee](google-reduced-service-fee) ## 3\. Zona horaria de informes \{#3-reporting-timezone\} <img src="/assets/shared/img/47227f9-CleanShot_2023-04-19_at_13.45.302x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Elige la zona horaria que corresponda a tu ubicación o a la zona donde los análisis y gráficos de tu app sean más relevantes. Recomendamos usar la misma zona horaria que tu cuenta de App Store Connect o Google Play Console para mantener la coherencia. Ten en cuenta que esta configuración de zona horaria no afecta a las integraciones de terceros en el sistema de Adapty, que utilizan la zona horaria UTC. Puedes acceder a la configuración de zona horaria en la sección "Reported timezone" de la pestaña General en la página App Settings. También puedes aplicar la misma zona horaria a todas las apps de tu cuenta de Adapty marcando la casilla correspondiente. ## 4\. Definición de instalaciones para análisis \{#4-installs-definition-for-analytics\} Elige qué se considera un nuevo evento de instalación en los análisis: | Base | Descripción | |------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Nuevos device_ids | <p>(Recomendado) Cada instalación de la app desde la store en un dispositivo se cuenta como una nueva instalación. Esto incluye tanto las instalaciones por primera vez como las reinstalaciones.</p><p>Las instalaciones se cuentan por ID de dispositivo y no se ven afectadas por la autenticación del usuario. Crear un perfil (al activar el SDK o al cerrar sesión), iniciar sesión o actualizar la app no genera eventos de instalación adicionales.</p><p>Por ejemplo, si la misma app está instalada en 5 dispositivos diferentes, verás 5 instalaciones en los análisis.</p> | | Nuevos customer_user_ids | <p>Esta opción está pensada para apps que <InlineTooltip tooltip="identifican usuarios en Adapty">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Unity](unity-identifying-users), [Kotlin Multiplatform](kmp-quickstart-identify), [Capacitor](capacitor-quickstart-identify)</InlineTooltip>. </p><p>Para los usuarios con sesión iniciada, solo la primera instalación asociada a un customer user ID se cuenta como instalación. Las instalaciones en dispositivos adicionales no se contabilizan como nuevas instalaciones. </p><p>Los usuarios anónimos (usuarios que no han iniciado sesión) no se contabilizan en los análisis. </p><p>Reinstalar la app o volver a iniciar sesión no crea instalaciones adicionales.</p> <p>Las stores y las plataformas de atribución (como App Store Connect, Google Play Console y AppsFlyer) utilizan un enfoque basado en dispositivos para contar instalaciones. Si cuentas instalaciones por customer user IDs en Adapty, los números pueden diferir de los de estos servicios externos.</p><p>⚠️ Si no identificas usuarios en Adapty, no se contabilizará ninguna instalación con esta opción activada.</p> | | Nuevos perfiles en Adapty | (Heredado) Cada instalación, reinstalación de la app y los perfiles anónimos creados durante los cierres de sesión se cuentan como nuevas instalaciones. | Ten en cuenta que esta opción solo afecta a la página [**Analytics**](https://app.adapty.io/analytics) y no tiene impacto en la página [**Overview**](https://app.adapty.io/overview), donde puedes configurar la vista por separado. ## 5. Lógica de aumento de precio en el App Store \{#5-app-store-price-increase-logic\} Para mantener datos precisos y evitar discrepancias entre los análisis de Adapty y los resultados de App Store Connect, es importante seleccionar la opción adecuada al ajustar las configuraciones relacionadas con los aumentos de precio en App Store Connect. Así puedes elegir la lógica que se aplicará a los aumentos de precio de las suscripciones en Adapty: <img src="/assets/shared/img/b766c8b-CleanShot_2023-07-18_at_19.28.18_22x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **El precio de la suscripción para los usuarios existentes se mantiene:** Al seleccionar esta opción, el precio actual se conservará para tus suscriptores existentes, aunque realices cambios en el precio en App Store Connect. Esto significa que los suscriptores existentes seguirán siendo facturados al precio original de su suscripción. - **Cuando el precio de la suscripción cambia en App Store Connect, también cambia para los suscriptores existentes:** Si eliges esta opción, cualquier cambio de precio realizado en App Store Connect se aplicará también a tus suscriptores existentes. Esto significa que los suscriptores existentes serán cobrados al nuevo precio que refleja la actualización establecida en App Store Connect. :::warning Es importante tener en cuenta que la opción seleccionada no solo afecta a los análisis en Adapty, sino que también impacta en las integraciones y en el comportamiento general del procesamiento de transacciones. ::: Asegúrate de seleccionar la opción adecuada que se alinee con tu enfoque deseado para gestionar los precios de las suscripciones para los suscriptores existentes. Esto ayudará a mantener datos precisos y la sincronización entre los análisis de Adapty y los resultados obtenidos de App Store Connect. ## 6. Compartir el acceso de pago entre cuentas de usuario \{#6-sharing-paid-access-between-user-accounts\} :::link Artículo principal: [Compartir el acceso de pago entre cuentas de usuario](sharing-paid-access-between-user-accounts) ::: La configuración **Sharing paid access between user accounts** determina qué hace Adapty cuando más de un [perfil de usuario](identifying-users) intenta acceder a la misma compra. Puedes especificar una configuración de compartición de acceso independiente para el [entorno sandbox](test-purchases-in-sandbox). **Activado (predeterminado)** Los usuarios identificados (aquellos con un [Customer User ID](identifying-users#set-customer-user-id-on-configuration)) pueden compartir el mismo [nivel de acceso](access-level) proporcionado por Adapty si su dispositivo está conectado al mismo Apple/Google ID. Esto es útil cuando un usuario reinstala la app e inicia sesión con un correo diferente: seguirá teniendo acceso a su compra anterior. Con esta opción, varios usuarios identificados pueden compartir el mismo nivel de acceso. Aunque el nivel de acceso se comparte, todas las transacciones pasadas y futuras se registran como eventos en el Customer User ID original para mantener una analítica coherente y conservar un historial completo de transacciones — incluidos períodos de prueba, compras de suscripciones, renovaciones y más, vinculadas al mismo perfil. **Transferir acceso al nuevo usuario** Los usuarios identificados pueden seguir accediendo al [nivel de acceso](access-level) proporcionado por Adapty, incluso si inician sesión con un [Customer User ID](identifying-users#set-customer-user-id-on-configuration) diferente o reinstalan la app, siempre que el dispositivo esté conectado al mismo Apple/Google ID. A diferencia de la opción anterior, Adapty transfiere la compra entre usuarios identificados. Esto garantiza que el contenido adquirido esté disponible, pero solo un usuario puede tener acceso a la vez. Por ejemplo, si UserA compra una suscripción y UserB inicia sesión en el mismo dispositivo y restaura las transacciones, UserB obtendrá acceso a la suscripción y se le revocará a UserA. Si uno de los usuarios (ya sea el nuevo o el antiguo) no está identificado, el nivel de acceso seguirá compartiéndose entre esos perfiles en Adapty. Aunque el nivel de acceso se transfiere, todas las transacciones pasadas y futuras se registran como eventos en el Customer User ID original para mantener una analítica coherente y conservar un historial completo de transacciones — incluidos períodos de prueba, compras de suscripciones, renovaciones y más, vinculadas al mismo perfil. Tras activar **Transferir acceso al nuevo usuario**, los niveles de acceso no se transferirán entre perfiles de forma inmediata. El proceso de transferencia para cada nivel de acceso específico solo se activa cuando Adapty recibe un evento del store, como una renovación de suscripción, una restauración o al validar una transacción. **Desactivado** El primer perfil de usuario identificado que obtenga un nivel de acceso lo conservará de forma permanente. Esta es la mejor opción si tu lógica de negocio requiere que las compras estén vinculadas a un único Customer User ID. Ten en cuenta que los niveles de acceso siguen compartiéndose entre usuarios anónimos. Puedes "desvincular" una compra [eliminando el perfil del usuario propietario](https://adapty.io/docs/es/api-adapty/operations/deleteProfile). Tras la eliminación, el nivel de acceso queda disponible para el primer perfil de usuario que lo reclame, ya sea anónimo o identificado. Desactivar el uso compartido solo afecta a los nuevos usuarios. Las suscripciones que ya se comparten entre usuarios seguirán compartiéndose aunque se desactive esta opción. :::warning Apple y Google exigen que las compras in-app se compartan o transfieran entre usuarios porque se basan en el Apple/Google ID para asociar la compra. Sin el uso compartido, restaurar las compras podría no funcionar en reinstalaciones posteriores. Desactivar el uso compartido puede impedir que los usuarios recuperen el acceso después de iniciar sesión. Recomendamos desactivar el uso compartido solo si tus usuarios **están obligados a iniciar sesión** antes de realizar una compra. De lo contrario, un usuario identificado podría comprar una suscripción, iniciar sesión en otra cuenta y perder el acceso de forma permanente. ::: ### ¿Qué opción debo elegir? \{#which-setting-should-i-choose\} | Mi app... | Opción a elegir | | ------------------------------------------------------------ | ------------------------------------------------------------ | | No tiene sistema de inicio de sesión y solo utiliza los IDs de perfil anónimos de Adapty. | Usa la opción predeterminada, ya que los niveles de acceso siempre se comparten entre IDs de perfil anónimos en las tres opciones. | | Tiene un sistema de inicio de sesión opcional y permite a los clientes realizar compras antes de crear una cuenta. | Elige **Transferir acceso al nuevo usuario** para garantizar que los clientes que compren sin cuenta puedan restaurar sus transacciones más adelante. | | Requiere que los clientes creen una cuenta antes de comprar, pero permite que las compras estén vinculadas a varios Customer User IDs. | Elige **Transferir acceso al nuevo usuario** para garantizar que solo un Customer User ID tenga acceso a la vez, permitiendo además que los usuarios inicien sesión con un Customer User ID diferente sin perder su acceso de pago. | | Requiere que los clientes creen una cuenta antes de comprar, con reglas estrictas que vinculan las compras a un único Customer User ID. | Elige **Desactivado** para garantizar que las transacciones nunca se transfieran entre cuentas. | ## 7. Claves del SDK y la API \{#7-sdk-and-api-keys\} Usa una clave SDK pública para integrar los SDK de Adapty en tu app, y una clave secreta para acceder a la API del servidor de Adapty. Puedes generar nuevas claves o revocar las existentes según sea necesario. Para crear tokens para el Developer CLI, ve a **Settings → Developer API**. Consulta [Authentication](developer-cli-authentication). ## 8. Dispositivos de prueba \{#8-test-devices\} Especifica los dispositivos que se usarán para pruebas para asegurarte de que reciben actualizaciones instantáneas de los cambios en paywalls o placements, sin demoras de caché. Para más información, consulta [Testing devices](test-devices). ## 9. Persistencia de variante entre placements \{#9-cross-placement-variation-stickiness\} Define cuánto tiempo después de finalizar una prueba un usuario sigue viendo las variantes de esa prueba. Esto afecta a la precisión de los análisis y a la experiencia del usuario, ya que mostrarle una oferta diferente a la que vio anteriormente puede influir en su decisión de compra. El período máximo y predeterminado de persistencia es de 90 días. :::warning Ten en cuenta lo siguiente: - Cambiar esta configuración afectará a todos los usuarios que previamente recibieron una variante. Inmediatamente podrán ver un nuevo paywall cuando accedan a un placement, lo que puede distorsionar los resultados de tus pruebas A/B en curso. - Si el período de persistencia ha expirado para un usuario, puede recibir un nuevo paywall o prueba A/B. Sin embargo, incluso en ese caso, no podrá formar parte de ninguna otra prueba entre placements en ningún momento futuro. ::: ## 10. Eliminar la app \{#10-delete-the-app\} Si ya no necesitas una app, puedes eliminarla de Adapty. :::warning Ten en cuenta que esta acción es irreversible y no podrás restaurar la app ni sus datos. ::: --- # File: ios-settings --- --- title: "Credenciales de Apple App Store" description: "Configura los ajustes de iOS en Adapty para una gestión fluida de suscripciones." --- Para configurar las credenciales del App Store y garantizar el funcionamiento óptimo del SDK de Adapty para iOS, ve a la pestaña [iOS SDK](https://app.adapty.io/settings/ios-sdk) dentro de la página App Settings del Adapty Dashboard. A continuación, configura los siguientes parámetros: <img src="/assets/shared/img/3d4087e-CleanShot_2023-06-26_at_13.27.042x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> | Campo | Descripción | |----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Bundle ID** | El [bundle ID de tu app](app-store-connection-configuration#step-1-provide-bundle-id-and-apple-app-id). | | **In-app purchase API (StoreKit 2)** | [Claves](app-store-connection-configuration#step-2-provide-issuer-id-and-key-id) para habilitar la autenticación segura y la validación del historial de transacciones de compras in-app. | | **App Store Server Notifications** | URL que se utiliza para habilitar las [notificaciones server-to-server](enable-app-store-server-notifications) del App Store para monitorizar y responder a los cambios de estado de las suscripciones de los usuarios. | | **App Store Promotional Offers** | Claves de suscripción para crear [ofertas promocionales](generate-in-app-purchase-key) en Adapty para productos específicos. | | **Apple app ID** | El ID de tu app en el App Store. Para encontrarlo, abre la página de tu app en App Store Connect, abre la página **App Information** desde el menú izquierdo y copia el **Apple ID**. | | **App Store Connect shared secret (LEGACY)** | <p>**Clave heredada para el SDK de Adapty anterior a v2.9.0**</p><p></p><p>[Una clave](app-store-connection-configuration#step-5-enter-app-store-shared-secret) para la validación de recibos y la prevención de fraude en tu app.</p> | --- # File: google-play-store-connection-configuration --- --- title: "Configurar la integración con Google Play Store" description: "Configura la conexión con Google Play Store en Adapty para gestionar las compras in-app sin problemas." --- Esta sección describe el proceso de integración de tu aplicación móvil distribuida a través de Google Play con Adapty. Tendrás que introducir los datos de configuración de tu app desde la Play Store en el Adapty Dashboard. Este paso es fundamental para validar las compras y recibir actualizaciones de suscripciones desde la Play Store dentro de Adapty. Puedes completar este proceso durante el onboarding inicial o realizar cambios posteriormente en los **App Settings** del Adapty Dashboard. :::danger Los cambios de configuración solo son válidos antes de publicar tu aplicación móvil con los paywalls de Adapty integrados. Modificar la configuración tras el lanzamiento romperá la integración y los paywalls dejarán de mostrarse en tu aplicación. ::: ## Paso 1. Proporciona el Package name \{#step-1-provide-package-name\} El Package name es el identificador único de tu app en Google Play Store. Es necesario para el funcionamiento básico de Adapty, como el procesamiento de suscripciones. 1. Abre la [Google Play Developer Console](https://play.google.com/console/u/0/developers). 2. Selecciona la app cuyo ID necesitas. Se abrirá la ventana **Dashboard**. <img src="/assets/shared/img/7889edb-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Busca el ID del producto bajo el nombre de la aplicación y cópialo. 4. Abre los [**App settings**](https://app.adapty.io/settings/android-sdk) desde el menú superior de Adapty. <img src="/assets/shared/img/b00066c-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. En la pestaña **Android SDK** de la ventana **App settings**, pega el **Package name** copiado. ## Paso 2. Sube el archivo de clave de cuenta \{#step-2-upload-the-account-key-file\} 1. Sube el archivo de clave privada de cuenta de servicio en formato JSON que creaste en el paso [Crear archivo de clave de cuenta de servicio](create-service-account) en el área **Service account key file**. <img src="/assets/shared/img/20fdba1-service_key_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> No olvides hacer clic en el botón **Save** para confirmar los cambios. **Próximos pasos** - [Activar las notificaciones en tiempo real para desarrolladores (RTDN) en la Google Play Console](enable-real-time-developer-notifications-rtdn) --- # File: enable-real-time-developer-notifications-rtdn --- --- title: "Habilitar notificaciones en tiempo real para desarrolladores (RTDN) en Google Play Console" description: "Mantente informado sobre eventos críticos y garantiza la exactitud de los datos habilitando las Notificaciones en Tiempo Real para Desarrolladores (RTDN) en Google Play Console para Adapty. Aprende a configurar RTDN para recibir actualizaciones instantáneas sobre reembolsos y otros eventos importantes de la Play Store" --- Configurar las notificaciones en tiempo real para desarrolladores (RTDN) es fundamental para garantizar la exactitud de los datos, ya que te permite recibir actualizaciones al instante desde la Play Store, incluyendo información sobre reembolsos y otros eventos. ## Habilitar notificaciones \{#enable-notifications\} 1. Asegúrate de tener **Google Cloud Pub/Sub** habilitado. Abre [este enlace](https://console.cloud.google.com/flows/enableapi?apiid=pubsub) y selecciona el proyecto de tu app. Si todavía no has habilitado **Google Cloud Pub/Sub**, debes hacerlo aquí. <img src="/assets/shared/img/pubsub.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Ve a [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) desde el menú superior de Adapty y copia el contenido del campo **Enable Pub/Sub API** que aparece junto al título **Google Play RTDN topic name**. <img src="/assets/shared/img/a72ff2d-copy_topic.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note Si el contenido del campo **Enable Pub/Sub API** tiene un formato incorrecto (el formato correcto empieza por `projects/...`), consulta la sección [Corregir el formato incorrecto en el campo Enable Pub/Sub API](enable-real-time-developer-notifications-rtdn#fixing-incorrect-format-in-enable-pubsub-api-field) para obtener ayuda. ::: 3. Abre la [Google Play Console](https://play.google.com/console/), elige tu app y ve a **Monetize with Play** -> **Monetization setup**. En la sección **Google Play Billing**, marca la casilla **Enable real-time notifications**. 4. Pega el contenido del campo **Enable Pub/Sub API** que copiaste en los **App Settings** de Adapty en el campo **Topic name**. 5. Haz clic en **Save changes** en la Google Play Console. <img src="/assets/shared/img/e55ba0e-paste_topic_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Probar las notificaciones \{#test-notifications\} Para comprobar si te has suscrito correctamente a las notificaciones en tiempo real para desarrolladores: 1. Guarda los cambios en la configuración de Google Play Console. 2. Debajo del campo **Topic name** en Google Play Console, haz clic en **Send test notification**. <img src="/assets/shared/img/rtdn-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Ve a [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) en Adapty. Si se ha enviado una notificación de prueba, verás su estado encima del nombre del topic. <img src="/assets/shared/img/rtdn-adapty-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Corregir el formato incorrecto en el campo Enable Pub/Sub API \{#fixing-incorrect-format-in-enable-pubsub-api-field\} Si el contenido del campo **Enable Pub/Sub API** tiene un formato incorrecto (el formato correcto empieza por `projects/...`), sigue estos pasos para solucionar el problema: ### 1. Verificar la habilitación de la API y los permisos \{#1-verify-api-enablement-and-permissions\} Comprueba detenidamente que todas las APIs necesarias estén habilitadas y que los permisos estén correctamente concedidos a la cuenta de servicio. Aunque ya hayas completado estos pasos, es importante revisarlos de nuevo para asegurarte de que no se omitió ninguno. Repite los pasos de las siguientes secciones: 1. [Habilitar las APIs de desarrollador en Google Play Console](enabling-of-devepoler-api) 2. [Crear una cuenta de servicio en Google Cloud Console](create-service-account) 3. [Conceder permisos a la cuenta de servicio en Google Play Console](grant-permissions-to-service-account) 4. [Generar el archivo de clave de la cuenta de servicio en Google Play Console](create-service-account-key-file) 5. [Configurar la integración con Google Play Store](google-play-store-connection-configuration) ### 2. Ajustar las políticas de dominio \{#2-adjust-domain-policies\} Cambia las políticas **Domain restricted contacts** y **Domain restricted sharing**: 1. Abre la [Google Cloud Console](https://console.cloud.google.com/) y selecciona el proyecto donde creaste la cuenta de servicio para gestionar tu app. 2. En la sección **Quick Access**, elige **IAM & Admin**. <img src="/assets/shared/img/google-cloud-IAM-and-Admin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En el panel izquierdo, elige **Organization Policies**. 4. Busca la política **Domain restricted contacts**. <img src="/assets/shared/img/google-cloud-policy-action.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en el botón de puntos suspensivos en la columna **Actions** y elige **Edit policy**. 6. En la ventana de edición de la política: 1. En **Policy source**, selecciona el botón de opción **Override parent's policy**. 2. En **Policy enforcement**, selecciona el botón de opción **Replace**. 3. En **Rules**, haz clic en el botón **ADD A RULE**. <img src="/assets/shared/img/google-cloud-edit-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. En **New rule** -> **Policy values**, elige **Allow All**. <img src="/assets/shared/img/google-cloud-allow-all-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en **SET POLICY**. 7. Repite los pasos 4-6 para la política **Domain restricted sharing**. Por último, vuelve a generar el contenido del campo **Enable Pub/Sub API** situado junto al título **Google Play RTDN topic name**. El campo tendrá ahora el formato correcto. Asegúrate de cambiar **Policy source** de vuelta a **Inherit parent's policy** para las políticas actualizadas una vez que hayas habilitado correctamente las Notificaciones en Tiempo Real para Desarrolladores (RTDN). ## Reenvío de eventos sin procesar \{#raw-events-forwarding\} En algunos casos, puede que quieras seguir recibiendo eventos S2S sin procesar de Google. Para continuar recibiéndolos mientras usas Adapty, simplemente añade tu endpoint en el campo **URL for forwarding raw Google events** y enviaremos los eventos tal cual los recibimos de Google. <img src="/assets/shared/img/e388892-001774-September-22-GhkjOFbT.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- **Próximos pasos** Configura el SDK de Adapty para: - [Android](sdk-installation-android) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) --- # File: apple-search-ads --- --- title: "Apple Ads" description: "Integra Apple Ads con Adapty para optimizar las conversiones de suscripciones." --- :::important La integración de Apple Ads en **App settings** se utiliza únicamente para análisis básico y para las integraciones con SplitMetrics Acquire y Asapty. [Adapty Ads Manager](adapty-ads-manager) utiliza una conexión independiente. Conecta tu cuenta de Apple Ads en [Adapty Ads Manager](adapty-ads-manager-get-started). ::: Adapty puede ayudarte a obtener datos de atribución de Apple Ads y analizar tus métricas con segmentación por campaña y palabra clave. Adapty recopila los datos de atribución de Apple Ads automáticamente a través de su SDK y el AdServices Framework. Una vez que hayas configurado la integración con Apple Ads, Adapty comenzará a recibir datos de atribución de Apple Ads. Puedes acceder a estos datos y consultarlos fácilmente en la página de perfiles. <img src="/assets/shared/img/ba4a3e9-CleanShot_2023-08-21_at_15.14.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Configurar la integración \{#set-up-integration\} ### Conectar Adapty con el framework AdServices \{#connect-adapty-to-the-adservices-framework\} Apple Ads a través de [AdServices](https://developer.apple.com/documentation/adservices) requiere cierta configuración en el Adapty Dashboard, y también necesitarás habilitarlo en el lado de la app. Para configurar Apple Ads usando el framework AdServices a través de Adapty, sigue estos pasos: #### Paso 1: Obtener la clave pública \{#step-1-obtain-public-key\} En el Adapty Dashboard, ve a [Settings -> Apple Ads.](https://app.adapty.io/settings/apple-search-ads) Localiza la clave pública pregenerada (Adapty te proporciona un par de claves) y cópiala. <img src="/assets/shared/img/baa5998-CleanShot_2023-08-21_at_14.55.542x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Si utilizas un servicio alternativo o tu propia solución para la atribución de Apple Ads, puedes subir tu propia clave privada. ::: #### Paso 2: Configura la gestión de usuarios en Apple Ads \{#step-2-configure-user-management-on-apple-ads\} En tu [cuenta de Apple Ads](https://ads.apple.com/app-store), ve a la página **Settings > User Management**. Para que Adapty pueda obtener datos de atribución, necesitas invitar otra cuenta de Apple ID y concederle acceso como API Account Manager. Puedes usar cualquier cuenta a la que tengas acceso o crear una nueva exclusivamente para este fin. Lo importante es que debas poder iniciar sesión en Apple Ads con ese Apple ID. <img src="/assets/shared/img/ec183b2-kdjsfldsfjkdsfdfd.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### Paso 3: Generar credenciales de API \{#step-3-generate-api-credentials\} Como siguiente paso, inicia sesión en la cuenta recién añadida en Apple Ads. Ve a Settings -> API en la interfaz de Apple Ads. Pega la clave pública copiada anteriormente en el campo correspondiente. Genera nuevas credenciales de API. #### Paso 4: Configurar Adapty con las credenciales de Apple Ads \{#step-4-configure-adapty-with-apple-ads-credentials\} Copia los campos Client ID, Team ID y Key ID de la configuración de Apple Ads. En el Adapty Dashboard, pega estas credenciales en los campos correspondientes. <img src="/assets/shared/img/7356113-CleanShot_2023-08-21_at_15.08.512x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Conectar tu app a la red AdServices \{#connect-your-app-to-the-adservices-network\} Una vez que completes [la configuración del framework AdServices](#connect-the-adservices-framework), Adapty empieza a recopilar automáticamente los datos de atribución de Apple Search Ads. No necesitas añadir ningún código al SDK. En aplicaciones iOS, estos datos de atribución **siempre** tendrán prioridad sobre los datos de otras fuentes. Si este comportamiento no es el deseado, *desactiva* la atribución de ASA siguiendo las instrucciones a continuación. ## Desactivar la integración \{#disable-integration\} Para desactivar la atribución de Apple Search Ads, abre la pestaña [**App Settings** -> **Apple Search Ads**](https://app.adapty.io/settings/apple-search-ads) y desactiva el interruptor **Receive Apple Search Ads attribution**. <img src="/assets/shared/img/asa-disable.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning Ten en cuenta que desactivar esto detendrá por completo la recepción de datos de análisis de ASA. Como resultado, ASA dejará de utilizarse en el análisis y no se enviará a las integraciones. Además, SplitMetrics Acquire y Asapty dejarán de funcionar, ya que dependen de la atribución de ASA para operar correctamente. La atribución recibida antes de este cambio no se verá afectada. ::: ## Subir tus propias claves \{#uploading-your-own-keys\} :::note Opcional Estos pasos no son necesarios para la atribución de Apple Ads, solo para trabajar con otros servicios como Asapty o tu propia solución. ::: Puedes usar tu propio par de claves pública-privada si estás utilizando otros servicios o una solución propia para la atribución de ASA. ### Paso 1 \{#step-1\} Genera la clave privada en el Terminal ```text showLineNumbers title="Text" openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem ``` Súbela en Adapty Settings -> Apple Ads (botón Upload private key) ### Paso 2 \{#step-2\} Genera la clave pública en el Terminal ```text showLineNumbers title="Text" openssl ec -in private-key.pem -pubout -out public-key.pem ``` Puedes usar esta clave pública en los ajustes de Apple Ads de la cuenta con el rol API Account Manager. Así podrás usar los valores generados de Client ID, Team ID y Key ID tanto en Adapty como en otros servicios. --- # File: account --- --- title: "Detalles de la cuenta y facturación" description: "Gestiona tu cuenta de Adapty y optimiza la configuración para un mejor seguimiento de suscripciones." --- La página **Account** te permite gestionar tu perfil, los miembros del equipo y la facturación. La página tiene tres pestañas: - [General](#general-settings) - [Subscription & Billing](#billing-info) - [Members](#members) Para acceder a la configuración de tu cuenta, haz clic en **Account** en la parte superior derecha o ve a [app.adapty.io/account](https://app.adapty.io/account). <img src="/assets/shared/img/account-info.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Configuración general \{#general-settings\} La pestaña General contiene tu perfil, la configuración de la cuenta, las preferencias de visualización y la configuración de informes. - **Profile**: Introduce tu nombre, apellidos y nombre de empresa. El nombre de empresa puede tener hasta 256 caracteres. - **Account settings**: Consulta tu dirección de correo electrónico registrada y cambia tu contraseña. - **Date & Time formats**: Elige cómo se muestran las fechas y horas en Adapty: - **American format**: January 31, 2022 y hora en formato de 12 horas (AM/PM) - **European format**: 31 January, 2022 y hora en formato de 24 horas (16:00) - **Email reports**: Configura informes diarios, semanales o mensuales para una o todas tus apps. Recibe informes resumidos de todas las apps a la vez, o un informe detallado de cada app seleccionada. <img src="/assets/shared/img/account-info.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Suscripción y facturación \{#billing-info\} La pestaña **Subscription & Billing** te permite gestionar tu información de pago y el acceso a funciones: - Añadir o actualizar los datos de pago - Revisar la información de facturación - Adquirir funciones adicionales de pago Más información sobre [funciones y precios](https://adapty.io/pricing). ## Miembros \{#members\} Puedes gestionar los miembros de tu equipo desde la configuración de la cuenta. Para añadir miembros, invítalos por su correo electrónico y asígnales un rol. Lee más sobre cómo gestionar los miembros del equipo y sus permisos de acceso [aquí](members-settings). --- # File: members-settings --- --- title: "Members" description: "Gestiona la configuración y los permisos de los miembros en el dashboard de Adapty." --- :::note Esta página trata sobre los miembros del Adapty Dashboard Si quieres otorgar distintos niveles de acceso a los usuarios de tu app, consulta [Nivel de acceso](access-level). ::: El sistema de miembros del Adapty Dashboard te permite conceder diferentes niveles de acceso a Adapty y especificar las aplicaciones para cada miembro. ## Roles \{#roles\} Los siguientes roles están disponibles para los miembros del Adapty Dashboard: | Rol | Acceso a Facturación | Añadir nuevos miembros | Cambiar cualquier cosa | Acceso a todas las secciones | |-------------|----------------------|------------------------|------------------------|------------------------------| | Owner | ✅ | ✅ | ✅ | ✅ | | Admin | ❌ | ✅ | ✅ | ✅ | | Developer | ❌ | ❌ | ✅ | ❌ | | Viewer | ❌ | ❌ | ❌ | ✅ | | Support | ❌ | ❌ | ❌ | ❌ | | ASA manager | ❌ | ❌ | ❌ | ❌ | - **Owner:** El Owner es el creador original de la cuenta de Adapty y tiene el mayor nivel de acceso y control. Los Owners tienen acceso completo a la facturación de Adapty, lo que les permite gestionar la información de pago y los planes de suscripción. Además, solo los Owners y Admins pueden especificar el acceso a las aplicaciones para los nuevos miembros. Solo puede haber un Owner por cuenta de Adapty. - **Admin:** Los miembros con el rol Admin tienen acceso completo a las aplicaciones seleccionadas. Pueden realizar diversas tareas de gestión, como crear y modificar paywalls, realizar pruebas A/B, analizar métricas y gestionar miembros dentro de esas aplicaciones. - **Developer**: Los miembros con el rol Developer tienen acceso completo a todas las entidades, excepto a las analíticas y los miembros de la cuenta. No pueden acceder a ninguna configuración de facturación. Este rol está pensado para quienes configuran paywalls, pruebas A/B y otras entidades e integran Adapty en tu app, pero no deben ver datos financieros. - **Viewer:** Los miembros con el rol Viewer tienen acceso de solo lectura a las aplicaciones seleccionadas. Pueden ver la información, pero no pueden crear ni modificar paywalls, pruebas A/B ni otras funciones, invitar a nuevos usuarios, crear nuevas apps ni cambiar la configuración de la app. - **Support:** Los miembros con el rol Support solo tienen acceso a los perfiles de usuario en las aplicaciones seleccionadas. Sin embargo, no pueden realizar acciones como añadir nuevos miembros ni acceder a ninguna otra sección de Adapty. Este rol es especialmente adecuado para equipos de soporte o personas que necesitan ayudar a los clientes con consultas o problemas relacionados con suscripciones. - **ASA manager**: Los miembros con el rol ASA manager solo tienen acceso al dashboard de [Adapty Ads Manager](adapty-ads-manager). ## Añadir un miembro \{#add-a-member\} En Adapty, puedes invitar hasta 256 miembros al equipo. Añadir nuevos miembros es gratuito. :::note Solo puedes invitar direcciones de correo que aún no estén registradas en Adapty. Si tu compañero o compañera tiene una cuenta independiente, invita a otra dirección de correo o contacta con el soporte de Adapty para eliminar su cuenta existente. ::: Para añadir un miembro al equipo: 1. Haz clic en **Account** en la parte superior derecha y abre la pestaña **Members**. 2. Haz clic en **Invite member**. <img src="/assets/shared/img/invite-member.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Introduce la dirección de correo electrónico del miembro. 4. Selecciona un [rol](#roles) de la lista. 5. Selecciona las apps a las que quieres dar acceso. 6. (Opcional) Activa **Always allow access to new apps** para conceder acceso automáticamente a futuras apps. 7. Haz clic en **Save**. <img src="/assets/shared/img/add-member.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Transferir la propiedad de la cuenta \{#transfer-account-ownership\} Si necesitas transferir la **propiedad completa de la cuenta**, contacta con nuestro equipo de soporte en [support@adapty.io](mailto:support@adapty.io). Si necesitas transferir la **propiedad de la app**, consulta la [guía específica](transfer-apps) para más información. --- # File: set-up-app-store-connect --- --- title: "Configurar App Store Connect" description: "Guía para desarrolladores primerizos sobre cómo inscribirse en el Apple Developer Program y configurar App Store Connect para las compras in-app." --- Si estás **creando tu primera app para iOS**, debes configurar tu cuenta de Apple Developer y App Store Connect antes de integrar Adapty. :::note Si ya tienes una cuenta de Apple Developer y una app registrada en App Store Connect, puedes saltarte esta guía e ir directamente a [Integración inicial con el App Store](initial_ios). ::: ## Paso 1. Inscríbete en el Apple Developer Program \{#step-1-enroll-in-apple-developer-program\} Para distribuir apps en el App Store y vender compras in-app, debes unirte al [Apple Developer Program](https://developer.apple.com/programs/). ### Elige el tipo de inscripción \{#choose-enrollment-type\} Apple ofrece dos tipos de inscripción: | | Individual | Organización | |----------------------------------------|------------------------|------------------------------------| | **Para quién es** | Desarrolladores solos | Empresas, equipos, organizaciones sin ánimo de lucro | | **Requiere número D-U-N-S** | No | Sí | | **Apps publicadas bajo** | Tu nombre personal | El nombre de tu organización | | **Gestión de equipo** | No disponible | Disponible | :::tip Si te inscribes como organización, necesitas un **número D-U-N-S** — un identificador único de nueve dígitos emitido por Dun & Bradstreet. Puedes [comprobar si tu organización ya tiene uno](https://developer.apple.com/enroll/duns-lookup/) o solicitar uno nuevo — el enlace está al final de la página de búsqueda. Un número D-U-N-S puede tardar hasta 5 días hábiles en llegar. ::: ### Inscríbete \{#enroll\} 1. Ve a la [página de inscripción del Apple Developer Program](https://developer.apple.com/programs/enroll/). 2. Inicia sesión con tu Apple ID. Si no tienes uno, créalo primero. 3. Sigue los pasos según tu tipo de inscripción (individual u organización). 4. Paga la cuota anual. Una vez que Apple procese tu inscripción, tendrás acceso a [App Store Connect](https://appstoreconnect.apple.com). La inscripción suele tardar hasta 48 horas. Para las organizaciones, puede tardar más si se requiere verificación del D-U-N-S. ## Paso 2. Configura tu app en App Store Connect \{#step-2-set-up-your-app-in-app-store-connect\} Antes de poder vender compras in-app, completa la configuración inicial en App Store Connect. Esto incluye firmar acuerdos, añadir datos de pago y registrar tu app. ### Firma el Paid Applications Agreement \{#sign-the-paid-applications-agreement\} Apple requiere que firmes el Paid Applications Agreement antes de poder vender en el App Store. Esto aplica tanto a apps de pago como a compras in-app en apps gratuitas. 1. Ve a la página **Business** en [App Store Connect](https://appstoreconnect.apple.com/business). 2. Encuentra el acuerdo **Paid Apps** y haz clic en **Review and Agree**. 3. Completa la información requerida: - **Banking information**: Añade una cuenta bancaria donde Apple enviará tus ingresos. - **Tax information**: Rellena los formularios fiscales de los países donde quieres vender. - **Contact information**: Proporciona tus datos de contacto. :::important Debes completar las tres secciones (banking, tax, contact) para que el acuerdo entre en vigor. Hasta que el acuerdo esté activo, no podrás vender compras in-app. ::: ### Crea un Bundle ID \{#create-a-bundle-id\} Un Bundle ID identifica tu app de forma única en todo el ecosistema de Apple. Lo necesitas para registrar tu app en App Store Connect y para configurar la integración con Adapty. 1. Abre el [portal de Apple Developer](https://developer.apple.com/account). 2. Ve a **Certificates, Identifiers & Profiles** → **Identifiers**. 3. Haz clic en **+** para registrar un nuevo identificador. 4. Selecciona **App IDs** y haz clic en **Continue**. 5. Selecciona **App** como tipo y haz clic en **Continue**. 6. Rellena los campos: - **Description**: Un nombre para identificar este Bundle ID (p. ej., "My Subscription App"). - **Bundle ID**: Elige **Explicit** e introduce un identificador único en formato de dominio invertido (p. ej., `com.yourcompany.yourapp`). 7. En la sección **Capabilities**, desplázate hacia abajo y marca **In-App Purchase**. 8. Haz clic en **Continue** y luego en **Register**. ### Registra tu app en App Store Connect \{#register-your-app-in-app-store-connect\} 1. Ve a la página **Apps** en [App Store Connect](https://appstoreconnect.apple.com/apps). 2. Haz clic en **+** → **New App**. 3. Rellena los campos obligatorios: - **Platforms**: Selecciona **iOS**. - **Name**: El nombre de tu app tal como aparecerá en el App Store. - **Primary language**: El idioma predeterminado para los metadatos de tu app. - **Bundle ID**: Selecciona el Bundle ID que creaste en el paso anterior. - **SKU**: Un identificador único para tu app (no visible para los usuarios). Por ejemplo, `my_subscription_app_2025`. 4. Haz clic en **Create**. Tu app ya está registrada en App Store Connect y lista para la integración con Adapty. ## Qué hacer a continuación \{#whats-next\} - [Integración inicial con el App Store](initial_ios): Conecta tu app del App Store a Adapty - [Integración del SDK](quickstart-sdk): Integra el SDK de Adapty en el código de tu app - [Pruebas en sandbox](test-purchases-in-sandbox): Prueba tus compras in-app antes del lanzamiento - [Envía tu app iOS al App Store](submit-app-to-app-store): Sube tu build y envíala para revisión de Apple - [App Store Small Business Program](app-store-small-business-program): Reduce tu comisión del App Store del 30% al 15% --- # File: app-store-products --- --- title: "Producto en App Store" description: "Gestiona los productos de App Store de forma eficiente con las herramientas de suscripción de Adapty." --- Esta página explica cómo crear un producto en App Store Connect. Aunque esta información no está directamente relacionada con la funcionalidad de Adapty, puede servirte de ayuda si tienes problemas al crear productos en tu cuenta de App Store Connect. Para crear un producto que se vinculará a Adapty: 1. Abre **App Store Connect**. Ve a la sección [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) en el menú lateral izquierdo. <img src="/assets/shared/img/148c3b5-subscriptions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Si aún no has creado un grupo de suscripciones, haz clic en el botón **Create** bajo el título **Subscription Groups** para iniciar el proceso. Los [Subscription Groups](https://developer.apple.com/help/app-store-connect/manage-subscriptions/offer-auto-renewable-subscriptions) en App Store Connect organizan y gestionan tus productos, lo que permite a los usuarios cambiar entre diferentes ofertas sin problemas. Ten en cuenta que no es posible crear una suscripción fuera de un grupo. 3. En la ventana **Create Subscription Group** que se abre, introduce un nombre para el nuevo grupo de suscripciones en el campo **Reference Name**. El nombre de referencia es una etiqueta o identificador que tú defines para distinguir y gestionar los distintos grupos de suscripciones dentro de tu app. El nombre de referencia no es visible para los usuarios; es exclusivamente para tu uso interno y organización. Te permite identificar y referirte fácilmente a grupos de suscripciones específicos al gestionarlos en la interfaz de App Store Connect. Esto resulta especialmente útil si tienes varias ofertas de suscripción o quieres categorizarlas de una forma que tenga sentido para la estructura de tu app. <img src="/assets/shared/img/3f93c44-create_subscription_group.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Haz clic en el botón **Create** para confirmar la creación del grupo de suscripciones. 5. El grupo de suscripciones se crea y se abre. Ahora puedes crear suscripciones dentro del grupo. Haz clic en el botón **Create** bajo el título **Subscriptions**. Si añades una nueva suscripción a un grupo existente, haz clic en el botón **Plus** junto al título **Subscriptions**. <img src="/assets/shared/img/22fc643-add_subscription.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. En la ventana **Create Subscription** que se abre, introduce el nombre en el campo **Reference Name** y el código único de la suscripción en el campo **Product ID**. El Reference Name actúa como identificador exclusivo dentro de App Store Connect para tu suscripción in-app. No es visible para los usuarios en el App Store. Recomendamos usar una descripción clara y legible que represente con precisión la suscripción que quieres crear. Ten en cuenta que este nombre no puede superar los 64 caracteres. El Product ID es un identificador alfanumérico único imprescindible para acceder a tu producto durante la fase de desarrollo y para sincronizarlo con Adapty. En el Product ID solo se permiten caracteres alfanuméricos, puntos y guiones bajos. <img src="/assets/shared/img/04aca55-create_subscription.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Haz clic en el botón **Create** para confirmar la creación de la suscripción. 8. La suscripción se crea y se abre. Ahora selecciona la duración de la suscripción en la lista **Subscription Duration**. Aunque la duración ya esté indicada en el nombre de la suscripción, recuerda completar el campo **Subscription Duration**. <img src="/assets/shared/img/f56cf0f-subscription_duration.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. Ahora toca configurar el precio de la suscripción. Para ello, haz clic en el botón **Add Subscription Price** bajo el título Subscription Prices. Es posible que tengas que desplazarte hacia abajo para encontrarlo. 10. En la ventana **Subscription Price** que se abre, selecciona el país base en la lista **Country or Region** y la moneda base en la lista **Price**. Más adelante, Apple calculará automáticamente los precios para los 175 países o regiones basándose en este precio base y los tipos de cambio más recientes. <img src="/assets/shared/img/de1cec8-subscription_price.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 11. Haz clic en el botón **Next**. En la ventana **Price by Country or Region** que se abre, verás los precios recalculados automáticamente para todos los países. Puedes modificarlos si lo deseas. <img src="/assets/shared/img/2a047a6-price_by_country.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 12. Tras actualizar los precios regionales, continúa haciendo clic en el botón **Next** en la parte inferior de la ventana. 13. En la ventana **Confirm Subscription Price?** que se abre, revisa detenidamente los precios finales. Si necesitas corregirlos, puedes hacer clic en el botón **Back** para volver a la ventana **Price by Country or Region** y actualizarlos. Cuando estés conforme con los precios, haz clic en el botón **Confirm**. <img src="/assets/shared/img/d2b2031-confirm_prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 14. Después de cerrar la ventana **Confirm Subscription Price?**, recuerda hacer clic en el botón **Save** en la ventana de tu suscripción. Sin este paso, la suscripción no se creará y todos los datos introducidos se perderán. Ten en cuenta que los pasos descritos hasta ahora se centran en configurar una suscripción de renovación automática. Sin embargo, si quieres configurar otros tipos de compras in-app, puedes hacer clic en la pestaña **In-App Purchases** en la barra lateral, en lugar de "Subscriptions". Esto te llevará a la sección donde puedes gestionar y crear distintos tipos de compras in-app. <img src="/assets/shared/img/5663d85-in-app_purchases.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Añadir productos a Adapty \{#add-products-to-adapty\} Una vez que hayas terminado de añadir tus compras in-app, suscripciones y ofertas en App Store Connect, el siguiente paso es [añadir estos productos a Adapty](create-product). --- # File: apple-app-privacy --- --- title: "Privacidad de aplicaciones de Apple" description: "Entiende las políticas de privacidad de aplicaciones de Apple y su impacto en tu app de suscripciones." --- Apple exige una declaración de privacidad para todas las apps nuevas y actualizaciones tanto en la sección **App Privacy** de App Store Connect como en el archivo de manifiesto de la app. Adapty es una dependencia de terceros en tu app, por lo que debes declarar cómo usas Adapty en relación con los datos de usuario. ## Manifiesto de privacidad de aplicaciones de Apple \{#apple-app-privacy-manifest\} El [archivo de manifiesto de privacidad](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests), llamado `PrivacyInfo.xcprivacy`, describe qué datos privados utiliza tu app y por qué. Como propietario de la app, debes crear un archivo de manifiesto para ella. Además, si integras SDKs adicionales, asegúrate de que los archivos de manifiesto de aquellos incluidos en la lista de [SDKs que requieren manifiesto de privacidad y firma](https://developer.apple.com/support/third-party-SDK-requirements/) estén incluidos. Al compilar la app, Xcode tomará todos estos archivos de manifiesto y los fusionará en uno solo. Aunque Adapty no figura en la lista de [SDKs que requieren manifiesto de privacidad y firma](https://developer.apple.com/support/third-party-SDK-requirements/), las versiones 2.10.2 y superiores del SDK de Adapty lo incluyen para tu comodidad. Asegúrate de actualizar el SDK para obtener el manifiesto. Si bien Adapty no requiere que se incluya ningún dato en el archivo de manifiesto (también llamado informe de privacidad de la app), si usas el `customerUserId` de Adapty para el seguimiento, es necesario especificarlo en tu archivo de manifiesto de la siguiente manera: 1. Añade un diccionario al array `NSPrivacyCollectedDataTypes` en tu archivo de información de privacidad. 2. Añade las claves `NSPrivacyCollectedDataType`, `NSPrivacyCollectedDataTypeLinked` y `NSPrivacyCollectedDataTypeTracking` al diccionario. 3. Añade la cadena `NSPrivacyCollectedDataTypeUserID` (identificador del tipo de dato `UserID` en la [lista de categorías y tipos de datos que se deben declarar en el archivo de manifiesto](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests#Describe-the-data-your-app-or-third-party-SDK-collects)) para la clave `NSPrivacyCollectedDataType` en tu diccionario `NSPrivacyCollectedDataTypes`. 4. Añade `true` para las claves `NSPrivacyCollectedDataTypeTracking` y `NSPrivacyCollectedDataTypeLinked` en tu diccionario `NSPrivacyCollectedDataTypes`. 5. Usa la cadena `NSPrivacyCollectedDataTypePurposeProductPersonalization` como valor para la clave `NSPrivacyCollectedDataTypePurposes` en tu diccionario `NSPrivacyCollectedDataTypes`. Si segmentas tus paywalls a audiencias con atributos personalizados, considera detenidamente qué atributos personalizados usas y si coinciden con las [categorías y tipos de datos que se deben declarar en el archivo de manifiesto](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests). En ese caso, repite los pasos anteriores para cada tipo de dato. Una vez que hayas declarado todos los tipos y categorías de datos que recopilas, crea el informe de privacidad de tu app tal como se describe en la [documentación de Apple](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests#Create-your-apps-privacy-report). ## Declaración de privacidad de aplicaciones de Apple en App Store Connect \{#apple-app-privacy-disclosure-in-app-store-connect\} 1. En [App Store Connect](https://appstoreconnect.apple.com/), abre tu app y ve a **App Privacy**. Haz clic en **Get Started**. <img src="/assets/shared/img/app-privacy-get-started.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Selecciona **Yes, we collect data from this app** y haz clic en **Next**. <img src="/assets/shared/img/app-privacy-data-collection.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Tipos de datos \{#data-types\} La tabla siguiente lista los tipos de datos que Apple exige declarar e indica cuáles necesita Adapty. **Esto solo cubre Adapty.** Si tu app recopila datos adicionales a través de otros SDKs o tu propio código, selecciona también esos tipos de datos. ✅ = Requerido por Adapty 👀 = Puede ser requerido \(consulta los detalles a continuación\) ❌ = No requerido por Adapty — selecciona si tu app recopila estos datos por otros medios | Tipo de dato | Requerido | Nota | |-----------------------------------------------------------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Identifiers | ✅ | <p>Si identificas usuarios con un customerUserId, selecciona 'User ID'.</p><p></p><p>Adapty recopila IDFA, por lo que debes seleccionar 'Device ID'.</p> | | Purchases | ✅ | Adapty recopila el historial de compras de los usuarios. | | Contact Info, incluyendo nombre, número de teléfono o dirección email | 👀 | Requerido si pasas datos personales como nombre, número de teléfono o dirección de email usando el método **`updateProfile`**. | | Usage Data | 👀 | Si usas SDKs de analítica como Amplitude, Mixpanel, AppMetrica o Firebase, puede ser necesario. | | Location | ❌ | Adapty no recopila datos de ubicación precisa. Selecciona si tu app los recopila. | | Health & Fitness | ❌ | Adapty no recopila datos de salud ni actividad física. Selecciona si tu app los recopila. | | Sensitive Info | ❌ | Adapty no recopila información sensible. Selecciona si tu app la recopila. | | User Content | ❌ | Adapty no recopila contenido de usuario. Selecciona si tu app lo recopila. | | Diagnostics | ❌ | Adapty no recopila datos de diagnóstico. Selecciona si tu app los recopila. | | Browsing History | ❌ | Adapty no recopila el historial de navegación. Selecciona si tu app lo recopila. | | Search History | ❌ | Adapty no recopila el historial de búsqueda. Selecciona si tu app lo recopila. | | Contacts | ❌ | Adapty no recopila listas de contactos. Selecciona si tu app las recopila. | | Financial Info | ❌ | Adapty no recopila información financiera. Selecciona si tu app la recopila. | ### Tipos de datos requeridos \{#required-data-types\} #### Purchases \{#purchases\} Al usar Adapty, debes declarar que tu app recopila **Purchase History**. <img src="/assets/shared/img/feb3b9f-CleanShot_2023-08-25_at_12.32.552x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### Identifiers \{#identifiers\} Al usar Adapty, debes declarar los siguientes identificadores: - **Device ID** — Adapty recopila IDFA. - **User ID** — requerido si identificas usuarios con **`customerUserId`**. <img src="/assets/shared/img/93f3daa-CleanShot_2023-08-25_at_12.35.272x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Uso de datos \{#data-usage\} Después de guardar los **Data types**, deberás indicar cómo se usan los datos: 1. Haz clic en **Set up purchase history** dentro del bloque **Purchases**. <img src="/assets/shared/img/purchase-privacy.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Cuando Apple pregunte cómo se usan los datos del historial de compras, selecciona lo siguiente para Adapty: - **Analytics** — Adapty usa el historial de compras para analíticas de ingresos, cohortes y métricas. - **Product Personalization** — Adapty usa los datos de compras para la segmentación de audiencias y la segmentación de paywalls. - **App Functionality** — Adapty valida las compras, gestiona los niveles de acceso y hace seguimiento del estado de la suscripción. Selecciona propósitos adicionales si tu app usa los datos de compras de otras formas (por ejemplo, si envías eventos de compra a plataformas publicitarias mediante integraciones de Adapty). <img src="/assets/shared/img/purchase-history.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Next**. 4. Para **Device ID** y **User ID** (si se usa): 1. Haz clic en **Set up user/device ID** dentro del bloque **User/Device ID**. 2. Cuando Apple pregunte cómo se usan los datos de identificadores, selecciona lo siguiente para Adapty: - **App Functionality** — Adapty usa los identificadores para gestionar perfiles de usuario, vincular compras y hacer seguimiento de los niveles de acceso. Si envías datos de atribución a plataformas de terceros mediante integraciones de Adapty (como AppsFlyer o Adjust), selecciona también **Third-Party Advertising**. Selecciona propósitos adicionales si tu app usa los identificadores de otras formas. <img src="/assets/shared/img/user-id-privacy.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en **Next**. --- # File: apple-family-sharing --- --- title: "Apple Family Sharing" description: "Habilita Apple Family Sharing en Adapty para admitir suscripciones compartidas." --- La función de Family Sharing de Apple permite distribuir compras in-app entre los miembros de una familia, lo que ofrece a los usuarios de apps orientadas a grupos —como servicios de streaming de vídeo o apps para niños— una forma cómoda de compartir suscripciones sin tener que compartir su Apple ID. Al permitir que hasta cinco miembros de la familia usen una suscripción, [Family Sharing](https://developer.apple.com/documentation/storekit/supporting-family-sharing-in-your-app) puede mejorar la fidelización y el engagement de los usuarios en tu app. En esta guía, explicamos cómo activar el uso compartido familiar para suscripciones y cómo Adapty gestiona las compras compartidas dentro de una familia. Para empezar a habilitar Family Sharing para un producto concreto, ve a [App Store Connect](https://appstoreconnect.apple.com/). Family Sharing está desactivado por defecto tanto para las compras in-app nuevas como para las existentes, por lo que es necesario habilitarlo individualmente para cada compra in-app. Puedes hacerlo fácilmente accediendo a la **página de tu app**, navegando a la página de la compra in-app correspondiente y seleccionando la opción **Turn On** en la sección Family Sharing. Ten en cuenta que una vez que actives Family Sharing para un producto, **no podrás desactivarlo**, ya que esto interrumpiría la experiencia de los usuarios que ya han compartido la suscripción con sus familiares. Además, ten en cuenta que solo los productos no consumibles y las suscripciones pueden compartirse. <img src="/assets/shared/img/6db165a-CleanShot_2023-03-28_at_17.15.342x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> En el modal que aparece, haz clic en el botón **Confirm** para finalizar el proceso. Tras hacerlo, la sección Family Sharing debería actualizarse y mostrar el mensaje "This subscription can be shared by everyone in a family group." Esto confirma que la suscripción ya está habilitada para Family Sharing y puede compartirse con hasta cinco miembros de la familia. Adapty facilita la compatibilidad con Family Sharing sin ningún esfuerzo adicional. Solo tienes que [configurar tus productos](app-store-products) desde App Store y, una vez que lo **actives** desde App Store Connect, **Family Sharing** estará disponible automáticamente en **Adapty** y se recibirá como un evento en el webhook. :::note Ten en cuenta que Family Sharing no es compatible con el entorno sandbox. ::: Ten en cuenta que cuando un usuario adquiere una suscripción y la comparte con sus familiares, puede haber un **retraso de hasta una hora** antes de que esté disponible para ellos. Apple diseñó este retraso para dar al usuario tiempo de cambiar de opinión y retirar el acceso si lo desea. Sin embargo, si la suscripción se renueva, los familiares reciben acceso sin ningún retraso. Cuando un usuario compra un producto in-app de Family Sharing, la transacción aparecerá en su recibo como de costumbre, pero con un nuevo campo llamado `in_app_ownership_type` con el valor `PURCHASED.` Además, se creará una nueva transacción para todos los miembros de la familia, que tendrá un `web_order_line_item_id` y un `original_transaction_id` diferentes a los de la compra original, así como un campo `in_app_ownership_type` con el valor `FAMILY_SHARED.` Para garantizar un cálculo de ingresos preciso, solo se contabilizan en los análisis de Adapty las transacciones con un `in_app_ownership_type` de `PURCHASED`. Las transacciones `FAMILY_SHARED` quedan excluidas de las métricas de ingresos y conversión. **Eventos enviados para transacciones de Family Sharing.** Las transacciones `FAMILY_SHARED` solo activan el evento **Access level updated**. Los eventos de suscripción por producto no se activan para los miembros de la familia. | Evento | `FAMILY_SHARED` | `PURCHASED` | | --- | --- | --- | | **Nivel de acceso actualizado** | Sí | Sí | | **Suscripción iniciada** | No | Sí | | **Prueba iniciada** | No | Sí | | **Suscripción renovada** | No | Sí | | **Suscripción expirada** | No | Sí | | **Suscripción reembolsada** | No | Sí | | **Problema de facturación detectado** | No | Sí | Si tu analítica downstream se basa en **Suscripción iniciada**, los miembros familiares no aparecerán ahí. Usa **Nivel de acceso actualizado** para detectar miembros familiares activos. Para identificar a los demás miembros de la familia en Adapty, puedes encontrarlos en los detalles del evento. Primero, localiza la transacción de compra familiar original. Luego, examina los detalles del evento para esa transacción, buscando específicamente el mismo producto, fecha de compra y fecha de vencimiento. Analizando los detalles del evento, puedes identificar otras transacciones de membresía familiar asociadas a la compra original. --- # File: app-store-small-business-program --- --- title: "App Store Small Business Program" description: "Comprende el Small Business Program de Apple, su impacto en tus ingresos y los análisis de Adapty" --- :::link Para el programa equivalente en Play Store, consulta [Google Reduced Service Fee](google-reduced-service-fee). ::: Las organizaciones que reciben hasta 1 millón de USD en ingresos anuales del App Store pueden participar en el [programa Small Business](https://developer.apple.com/app-store/small-business-program/) de Apple. Si te inscribes, la comisión estándar del 30% se reduce al **15%**. Los miembros del programa deben **cambiar su configuración en Adapty** para garantizar cálculos de ingresos correctos y el manejo adecuado de los eventos de integración. Este artículo describe: * [Cómo configurar Adapty](#configure-adapty) si tu app está inscrita en el Small Business Program * [Cómo inscribirte en el programa](#apply-for-the-program) si quieres reducir tu comisión del store ## Configurar Adapty \{#configure-adapty\} Adapty puede aplicar la comisión reducida a tus [análisis](analytics) y [eventos de integración](analytics-integration). Para activarlo, especifica tu estado en el Small Business Program por app. :::warning Configura tu estado en el SBP en Adapty **en cuanto recibas la aprobación**. Los cambios tardíos no pueden reescribir los eventos de webhook ya entregados ([detalles](#retroactive-setting-changes)). ::: 1. Abre [**App Settings** → **General**](https://app.adapty.io/account) 2. Busca la sección **Small Business Program**. 3. Haz clic en **Add period**. 4. Selecciona la fecha de inicio de la membresía. 5. Selecciona una fecha de fin, o activa la casilla **At the current moment** para extender este estado indefinidamente. Si en el futuro [pierdes la elegibilidad](#losing-eligibility), puedes modificar la fecha de fin. 6. Haz clic en **Apply**. Si tu organización sigue siendo elegible para el programa, la membresía se renueva automáticamente al siguiente año natural. Sin embargo, el estado de membresía solo se aplica **al rango de fechas que especifiques**. * Haz clic en **Add period** para añadir un nuevo período de membresía. * Para extender este estado indefinidamente, activa la casilla **At the current moment**. Para verificar tu configuración, abre el [gráfico de Revenue](revenue) y selecciona **Proceeds after store commission**. Confirma que los ingresos mostrados reflejan la comisión reducida. ## Inscribirse en el programa \{#apply-for-the-program\} ### Requisitos de elegibilidad \{#eligibility-requirements\} Apple determina la elegibilidad para el SBP en función de tus **ingresos anuales** — las ventas del año natural anterior **después** de la comisión del store e impuestos. Para ser elegible, los ingresos anuales de tu organización y sus <InlineTooltip tooltip="Cuentas de desarrollador asociadas">Cuentas en las que tú o tu organización tenéis una participación mayoritaria (>50%) o autoridad de toma de decisiones.</InlineTooltip> deben sumar 1 millón de USD o menos. Las organizaciones recién creadas son automáticamente elegibles para solicitar el programa. ### Antes de solicitar \{#before-you-apply\} Asegúrate de que: - Eres el titular de la cuenta en el Apple Developer Program - Has aceptado el último contrato de Paid Applications en App Store Connect - Puedes listar todas tus cuentas de desarrollador asociadas ### Inscripción \{#enrollment\} 1. Ve a la [página de inscripción del App Store Small Business Program](https://developer.apple.com/app-store/small-business-program/). 2. Haz clic en **Enroll** e inicia sesión con tu cuenta de Apple Developer. 3. Revisa la información precargada (nombre, correo electrónico, Team ID) y envíala. ### Revisión \{#review\} El proceso de revisión puede tardar más de un mes. Si cumples los requisitos, recibirás un correo de aprobación de Apple. Tras la aprobación, hay un período de espera. La comisión reducida entra en vigor el día 15 del [siguiente período fiscal de Apple](https://adapty.io/apple-fiscal-calendar/). No se aplica a transacciones anteriores. ### Pérdida de elegibilidad \{#losing-eligibility\} Cuando tus ingresos totales del año natural superan 1 millón de USD, pierdes la membresía del programa y Apple comienza a aplicar la comisión estándar del 30%. :::important Si tu negocio abandona el Small Business Program, **cambia inmediatamente la fecha de salida** en tu configuración. De lo contrario, Adapty seguirá calculando la comisión con la tasa reducida. ::: Puedes volver a calificar para el programa **el año siguiente** a que tus ingresos anuales caigan por debajo de 1 millón de USD. Lee los [términos oficiales del programa](https://developer.apple.com/app-store/small-business-program/) para más detalles. ## Cambios retroactivos en la configuración \{#retroactive-setting-changes\} Cuando cambias el estado de comisión reducida en Adapty con una fecha de efecto retroactiva, la nueva tasa de comisión aparece en los datos de Adapty según distintos calendarios: | Dónde aparece la tasa | Qué ocurre después de cambiar la tasa | | --- | --- | | Dashboard de analíticas (Revenue, Proceeds, MRR, ARR) | Adapty aplica la nueva tasa en un plazo de 24 horas, cuando se ejecuta el recálculo diario. | | Exportaciones a S3, GCS y BigQuery | Adapty aplica la nueva tasa en la siguiente exportación programada. | | Eventos de webhook ya entregados | Adapty no puede modificar los eventos de webhook tras su entrega. Conservan la tasa anterior. | Si tu almacén de datos guarda ingresos procedentes de eventos de webhook, esos registros mantienen la tasa de comisión antigua. Para reconciliarlos, recupera el período afectado desde el dashboard de analíticas o genera una exportación nueva a S3, GCS o BigQuery. --- # File: android-products --- --- title: "Producto en Play Store" description: "Gestiona productos Android con Adapty, simplifica las compras in-app y optimiza las estrategias de monetización." --- Esta página proporciona orientación para crear un producto en Play Store. Aunque esta información puede no estar directamente relacionada con la funcionalidad de Adapty, resulta un recurso valioso si encuentras dificultades al crear productos en Google Play Console. Un producto hace referencia a un artículo o servicio digital que ofreces dentro de tu app en Play Store, normalmente disponible para su compra. Puede incluir productos in-app como compras únicas, suscripciones u otros bienes digitales que los usuarios pueden adquirir mientras usan tu aplicación. En el [sistema de facturación de Google](https://developer.android.com/google/play/billing/compatibility), las suscripciones pueden incorporar múltiples planes base, cada uno con distintos descuentos u ofertas. Esta estructura está compuesta por tres componentes principales: - **Suscripciones:** Representan conjuntos de beneficios que los usuarios pueden disfrutar durante un período específico (los artículos que se venden). Por ejemplo, un "nivel Gold" que ofrece funciones premium a los suscriptores. - **Planes base:** Representan configuraciones específicas de períodos de facturación, tipos de renovación y precios (cómo se venden los artículos). Por ejemplo, "anual con renovación automática" o "mensual prepago". - **Ofertas:** Implican descuentos disponibles para usuarios elegibles que modifican el precio del plan base. Por ejemplo, "prueba gratuita de 14 días para nuevos usuarios". ## ¿Cómo crear un producto en Play Store? \{#how-to-create-a-product-in-play-store\} Un producto hace referencia a un artículo o servicio digital que ofreces dentro de tu app, normalmente disponible para su compra. Puede incluir productos in-app como compras únicas, suscripciones u otros bienes digitales que los usuarios pueden adquirir mientras usan tu aplicación. Para configurar un producto para dispositivos Android: 1. Abre la sección [**Monetize** -> **Subscriptions**](https://console.cloud.google.com/iam-admin/serviceaccounts) o [**Monetize** -> **In-app products**](https://console.cloud.google.com/iam-admin/serviceaccounts) en el menú izquierdo de Google Play Console. <img src="/assets/shared/img/6eff1d1-subscription_GP.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el botón **Create subscription**. <img src="/assets/shared/img/af7fe02-create_subscription_GP.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la ventana **Create subscription** que se abre, introduce el ID de la suscripción en el campo **Product ID** y el nombre de la suscripción en el campo **Name**. El ID del producto debe ser único, comenzar con un número o letra minúscula, y puede contener guiones bajos (\_) y puntos (.). Se utiliza para acceder a tu producto durante el desarrollo y sincronizarlo con Adapty. Una vez que se asigna un Product ID a un producto en Google Play Console, no puede reutilizarse en ninguna otra app, incluso si el producto se elimina. Al elegir el ID del producto, es recomendable seguir un formato estandarizado. Te recomendamos usar un enfoque más conciso y nombrar el producto `<nombre de suscripción>.<nivel de acceso>`. Así puedes controlar la duración y la frecuencia de facturación mediante planes base como semanal, mensual, etc. El nombre es solo para tu referencia; aparecerá en tu ficha de Google Play Store, así que puedes usar cualquier nombre descriptivo que necesites. Tiene un límite de 55 caracteres. 4. Haz clic en el botón **Create** para confirmar la creación de la suscripción. :::note Productos de suscripción de Google Play en Adapty Los productos de Adapty corresponden a los planes base de las suscripciones de Google Play, ya que esos son los productos disponibles para que los clientes compren. Adapty gestiona sin problemas la migración de las suscripciones existentes de Google Play junto con sus planes base correspondientes en los productos, sin que tengas que hacer nada adicional. Sin embargo, cuando añades un nuevo producto en Adapty, deberás proporcionar tanto el ID del plan base como el ID del producto. ::: ### Crear un plan base \{#create-a-base-plan\} Para los productos de suscripción, necesitarás añadir un plan base. Los planes base determinan el período de facturación, el precio y el tipo de renovación para que los clientes adquieran tu suscripción. Ten en cuenta que los clientes no compran directamente un producto de suscripción, sino que siempre adquieren un plan base dentro de una suscripción. Para crear un plan base: 1. Abre la sección [**Monetize** -> **Subscriptions**](https://console.cloud.google.com/iam-admin/serviceaccounts) en el menú izquierdo de Google Play Console. Una vez allí, localiza la suscripción a la que deseas añadir un plan base. 2. Haz clic en el botón **View subscription** junto a la suscripción. <img src="/assets/shared/img/4072a2a-subscriptions_GP.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Una vez que se abran los detalles de la suscripción, haz clic en el botón **Add base plan** que aparece bajo el título **Base plans and offers**. Es posible que tengas que desplazarte hacia abajo para encontrarlo. <img src="/assets/shared/img/b493b60-add_base_plan.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. En la ventana **Add base plan** que se abre, introduce un identificador único para el plan base en el campo **Plan ID**. Debe comenzar con un número o letra minúscula, y puede contener números (0-9), letras minúsculas (a-z) y guiones (-). Completa también los campos obligatorios. <img src="/assets/shared/img/8146763-CleanShot_2023-07-20_at_16.51.412x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Especifica los precios por región. <img src="/assets/shared/img/8b26e1d-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Haz clic en el botón **Save** para finalizar la configuración. 7. Haz clic en el botón **Activate** para activar el plan base. Ten en cuenta que los productos de suscripción solo pueden tener un único plan base con duración y tipo de renovación consistentes en Adapty. ### Productos de respaldo \{#fallback-products\} :::warning Compatibilidad con planes base no retrocompatibles Las versiones antiguas de los SDK de Adapty no son compatibles con las funciones de Google Billing Library v5+, concretamente con múltiples planes base por producto de suscripción y ofertas. Solo los planes base marcados como **[backwards compatible](https://support.google.com/googleplay/android-developer/answer/12124625?hl=en#backwards_compatible)** en Google Play Console son accesibles con estas versiones del SDK. Ten en cuenta que solo un plan base por suscripción puede marcarse como retrocompatible. ::: <img src="/assets/shared/img/b5e70cb-CleanShot_2023-07-20_at_17.03.252x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Para aprovechar al máximo las configuraciones y funciones mejoradas de suscripciones de Google en Adapty, ofrecemos la posibilidad de configurar un producto de respaldo retrocompatible. Este producto de respaldo se utiliza exclusivamente para apps que usan versiones antiguas del SDK de Adapty. Al crear productos de Google Play, ahora tienes la opción de indicar si el producto debe marcarse como retrocompatible en Play Console. Adapty utiliza esta información para determinar si el producto puede ser comprado por versiones antiguas del SDK (versiones 2.5 e inferiores). Supongamos que tienes una suscripción llamada `subscription.premium` que ofrece dos planes base: semanal (retrocompatible) y mensual. Si añades el producto `subscription.premium:weekly` a Adapty, no necesitas indicar un producto retrocompatible. Sin embargo, en el caso del producto `subscription.premium:monthly`, deberás especificar un producto retrocompatible. No hacerlo podría provocar una compra no deseada del producto `subscription.premium:weekly` en la biblioteca de facturación 4 de Google. Para resolver este escenario, debes crear un producto separado donde el plan base también sea mensual y esté marcado como retrocompatible. Esto garantiza que los usuarios que seleccionen la opción `subscription.premium:monthly` sean facturados correctamente con la frecuencia prevista. ## Añadir productos a Adapty \{#add-products-to-adapty\} Una vez que hayas completado la incorporación de tus compras in-app, suscripciones y ofertas en App Store Connect, el siguiente paso es [añadir estos productos a Adapty](create-product). --- # File: google-play-data-safety --- --- title: "Seguridad de datos de Google Play" description: "Garantiza el cumplimiento de las políticas de seguridad de datos de Google Play en Adapty." --- La sección de seguridad de datos disponible en Google Play ofrece a los desarrolladores un método sencillo para informar a los usuarios sobre los datos que su app recopila o comparte, así como destacar las medidas críticas de privacidad y seguridad. Esta información permite a los usuarios tomar decisiones más informadas al elegir qué apps descargar y usar. Aquí tienes una guía breve sobre los datos que Adapty recopila para ayudarte a proporcionar la información requerida a Google Play. ## Recopilación y seguridad de datos \{#data-collection-and-security\} <img src="/assets/shared/img/3508c24-image4.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **¿Tu app recopila o comparte alguno de los tipos de datos de usuario requeridos?** Selecciona 'Sí', ya que Adapty recopila el historial de compras del cliente. **¿Todos los datos de usuario recopilados por tu app están cifrados en tránsito?** Selecciona 'Sí', ya que Adapty cifra los datos en tránsito. **¿Ofreces a los usuarios una forma de solicitar la eliminación de sus datos?** Si seleccionas 'Sí', asegúrate de que tus clientes tengan una forma de contactar con tu equipo de soporte para solicitar la eliminación de sus datos. Podrás eliminar al cliente directamente desde el Adapty Dashboard o mediante la API REST. ## Tipos de datos \{#data-types\} A continuación encontrarás la lista de tipos de datos que Google requiere para el reporte, junto con la especificación de si Adapty recopila cada tipo de dato en particular. | Tipo de dato | Detalles | | :----------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Ubicación | Adapty no lo recopila | | Salud y fitness | Adapty no lo recopila | | Fotos y vídeos | Adapty no lo recopila | | Archivos y documentos | Adapty no lo recopila | | Calendario | Adapty no lo recopila | | Contactos | Adapty no lo recopila | | Contenido del usuario | Adapty no lo recopila | | Historial de navegación | Adapty no lo recopila | | Historial de búsqueda | Adapty no lo recopila | | Información y rendimiento de la app | Adapty no lo recopila | | Navegación web | Adapty no lo recopila | | Información de contacto | Adapty no lo recopila | | Información financiera | Adapty recopila el historial de compras de los usuarios | | Información personal e identificadores | Adapty recopila el ID de usuario y otra información de contacto identificable, como nombre, dirección de correo electrónico, número de teléfono, etc., si los pasas explícitamente al SDK de Adapty. | | Identificadores de dispositivo y otros | Adapty recopila datos sobre el ID del dispositivo. | ## Uso y tratamiento de datos \{#data-usage-and-handling\} ### IDs de usuario \{#user-ids\} **1. ¿Estos datos se recopilan, se comparten o ambas cosas?** Adapty recopila estos datos. Si usas integraciones entre Adapty y terceros que no se consideran proveedores de servicios, puede que también tengas que indicar "Compartido" aquí. **2. ¿Estos datos se procesan de forma efímera?** Selecciona 'No'. **3. ¿Estos datos son obligatorios para tu app o los usuarios pueden elegir si se recopilan?** La recopilación de estos datos es obligatoria y no se puede desactivar. <img src="/assets/shared/img/2c60161-image5.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **4. ¿Por qué se recopilan estos datos de usuario? / ¿Por qué se comparten estos datos de usuario?** Marca las casillas 'Funcionalidad de la app' y 'Análisis'. <img src="/assets/shared/img/07a3c9e-image2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Información financiera \{#financial-info\} Si usas Adapty, debes declarar que tu app recopila información sobre el 'Historial de compras' en la sección de tipos de datos de Google Play Console. <img src="/assets/shared/img/1057870-image7.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Identificadores de dispositivo u otros \{#device-or-other-ids\} <img src="/assets/shared/img/d10f132-CleanShot_2023-03-01_at_17.55.312x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/ccb1a2a-image5.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Pasos siguientes \{#next-steps\} Una vez que hayas completado las selecciones de seguridad de datos, Google mostrará una vista previa de la sección de privacidad de tu app. Si has seleccionado "Información financiera" e "Identificadores de dispositivo u otros" como se mencionó anteriormente, la información de privacidad debería aparecer de forma similar al siguiente ejemplo. <img src="/assets/shared/img/e8d9b73-image3.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Si estás listo para enviar tu app a revisión, consulta nuestro documento [Lista de verificación para el lanzamiento](release-checklist) para obtener más orientación sobre cómo preparar tu app para el envío. --- # File: google-reduced-service-fee --- --- title: "Tarifa de Servicio Reducida de Google" description: "Comprende la Tarifa de Servicio Reducida de Google, su impacto en tus ingresos y los análisis de Adapty" --- :::link Para el programa equivalente en App Store, consulta el [Programa para Pequeñas Empresas de App Store](app-store-small-business-program). ::: El [programa de Tarifa de Servicio Reducida](https://support.google.com/googleplay/android-developer/answer/112622?hl=en) de Google Play reduce la comisión sobre tu primer millón de USD en ganancias anuales del 30% al **15%**. Las ganancias que superen el millón de USD en el mismo año calendario se cobran a la tarifa estándar del 30%. :::note Desde el 1 de enero de 2022, Google cobra el 15% en todas las suscripciones de renovación automática independientemente de este programa. La Tarifa de Servicio Reducida beneficia principalmente a las compras in-app que no son suscripciones y a las apps de pago. ::: Los miembros del programa deben **cambiar su configuración en Adapty** para garantizar cálculos de ingresos correctos y el manejo adecuado de los eventos de integración. Este artículo describe: * [Cómo configurar Adapty](#configure-adapty) si tu app está inscrita en el programa de Tarifa de Servicio Reducida * [Cómo inscribirse en el programa](#enroll-in-the-program) si quieres reducir tu comisión del store ## Configurar Adapty \{#configure-adapty\} Adapty puede aplicar la tarifa de comisión reducida a tus [análisis](analytics) y [eventos de integración](analytics-integration). Para habilitarlo, especifica tu estado en la Tarifa de Servicio Reducida para cada app por separado. :::warning Configura tu estado de Tarifa de Servicio Reducida en Adapty **en cuanto te inscribas**. Los cambios posteriores no pueden reescribir los eventos de webhook ya entregados ([detalles](#retroactive-setting-changes)). ::: 1. Abre [**App Settings** → **General**](https://app.adapty.io/account). 2. Busca la sección **Reduced Service Fee**. 3. Haz clic en **Add period**. 4. Selecciona la fecha de inicio de la membresía. 5. Selecciona una fecha de fin, o activa la casilla **At the current moment** para extender este estado indefinidamente. Si tus [ganancias anuales superan el millón de USD](#exceeding-the-threshold), puedes modificar la fecha de fin. 6. Haz clic en **Apply**. El estado de membresía solo se aplica **al rango de fechas que especifiques**. El programa se reinicia cada año calendario. * Haz clic en **Add period** para añadir un nuevo período de membresía. * Para extender este estado indefinidamente, activa la casilla **At the current moment**. Para verificar tu configuración, abre el [gráfico de Ingresos](revenue) y selecciona **Proceeds after store commission**. Confirma que los ingresos mostrados reflejan la tarifa de comisión reducida. ## Inscribirse en el programa \{#enroll-in-the-program\} ### Requisitos de elegibilidad \{#eligibility-requirements\} Google determina la elegibilidad en función de tus **ganancias anuales** en todas las cuentas de tu <InlineTooltip tooltip="Grupo de Cuentas">Un grupo de cuentas de desarrollador cuyas ganancias se contabilizan conjuntamente. Debes designar tu cuenta de desarrollador como la cuenta de desarrollador principal y vincular las cuentas asociadas al grupo.</InlineTooltip>. La tarifa del 15% se aplica al primer millón de USD en ganancias anuales combinadas. Cualquier ganancia por encima de ese umbral se cobra al 30%. ### Antes de inscribirte \{#before-you-enroll\} Asegúrate de que: - Tienes un [perfil de pago](https://support.google.com/googleplay/android-developer/answer/10632485) configurado - Puedes listar todas tus cuentas de desarrollador asociadas ### Inscripción \{#enrollment\} 1. Ve a la [Google Play Console](https://play.google.com/console/). 2. Crea un Grupo de Cuentas y establece tu cuenta de desarrollador como la cuenta de desarrollador principal. 3. Vincula las cuentas de desarrollador asociadas al grupo. 4. Acepta los términos del programa de Tarifa de Servicio Reducida. Tras completar estos pasos, Google te inscribe automáticamente. No hay revisión manual ni correo de aprobación. Para instrucciones detalladas, consulta la [guía de inscripción](https://support.google.com/googleplay/android-developer/answer/10632485) de Google. ### Superar el umbral \{#exceeding-the-threshold\} Cuando tus ganancias anuales combinadas superen el millón de USD, Google cobra el 30% sobre la parte que excede el umbral durante el resto de ese año calendario. :::important Si tus ganancias anuales superan el millón de USD, **cambia inmediatamente la fecha de salida** en tu configuración de Adapty. De lo contrario, Adapty seguirá calculando la comisión a la tarifa reducida. ::: El programa se reinicia cada año calendario. Si tus ganancias superan el millón de USD en un año, la tarifa del 15% se aplica automáticamente de nuevo a tu primer millón de USD el año siguiente. No es necesario volver a inscribirse. Lee los [términos oficiales del programa](https://support.google.com/googleplay/android-developer/answer/112622?hl=en) para más detalles. ## Cambios retroactivos en la configuración \{#retroactive-setting-changes\} Cuando cambias el estado de comisión reducida en Adapty con una fecha de efecto retroactiva, la nueva tasa de comisión aparece en los datos de Adapty según distintos calendarios: | Dónde aparece la tasa | Qué ocurre después de cambiar la tasa | | --- | --- | | Dashboard de analíticas (Revenue, Proceeds, MRR, ARR) | Adapty aplica la nueva tasa en un plazo de 24 horas, cuando se ejecuta el recálculo diario. | | Exportaciones a S3, GCS y BigQuery | Adapty aplica la nueva tasa en la siguiente exportación programada. | | Eventos de webhook ya entregados | Adapty no puede modificar los eventos de webhook tras su entrega. Conservan la tasa anterior. | Si tu almacén de datos guarda ingresos procedentes de eventos de webhook, esos registros mantienen la tasa de comisión antigua. Para reconciliarlos, recupera el período afectado desde el dashboard de analíticas o genera una exportación nueva a S3, GCS o BigQuery. --- # File: google-play-quota-increase --- --- title: "Solicitar aumento de cuota de la Google Play Developer API" description: "Solicita un aumento de cuota para la Google Play Developer API si superas el límite predeterminado durante importaciones históricas o con una base de suscriptores grande." --- Adapty utiliza la [Google Play Developer API](https://developers.google.com/android-publisher) para validar compras y sincronizar datos de suscripciones. La cuota predeterminada para esta API es de 3.000 consultas por minuto. Si tu app supera este límite, Google te envía una notificación por correo electrónico. Esto ocurre habitualmente durante las [importaciones de datos históricos](importing-historical-data-to-adapty) o en apps con un gran número de suscriptores activos. Para evitar interrupciones, solicita un aumento de cuota a Google antes de realizar una importación grande o si recibiste una notificación de cuota superada. ## Antes de empezar \{#before-you-start\} Activa las [notificaciones en tiempo real para desarrolladores (RTDN)](enable-real-time-developer-notifications-rtdn) si aún no lo has hecho. Las RTDN entregan actualizaciones de suscripciones mediante notificaciones push en lugar de sondeo, lo que reduce el consumo de la API. Google puede rechazar las solicitudes de aumento de cuota si las RTDN no están habilitadas. ## Reúne la información necesaria \{#gather-required-information\} Antes de abrir el formulario de solicitud, recopila los siguientes datos: - **ID de cuenta de desarrollador**: Para encontrarlo, en la [Google Play Console](https://play.google.com/console/), ve a **Settings > Developer account > Account details**. El ID aparece en la parte superior de la página. - **Nombre del paquete de la app**: El nombre del paquete de tu app Android (por ejemplo, `com.example.app`). Encuéntralo en la Google Play Console en la página **Dashboard** de tu app. - **Número de proyecto de Google Cloud**: Para encontrarlo, en la [Google Cloud Console](https://console.cloud.google.com/), selecciona tu proyecto. El número de proyecto aparece en la página **Dashboard**. ## Solicita el aumento de cuota \{#request-the-quota-increase\} 1. Abre el [formulario de solicitud de aumento de cuota de la Google Play Developer API](https://support.google.com/googleplay/android-developer/contact/apiqr). 2. Introduce tu ID de cuenta de desarrollador, el nombre del paquete de la app y el número de proyecto de Google Cloud. 3. Selecciona la API y el bloque de cuota que necesita un aumento. Si recibiste un correo de Google sobre el exceso de cuota, en él se especifica qué bloque es. 4. En el campo de justificación, explica que utilizas un servicio de gestión de suscripciones de terceros que requiere acceso a la API para validar compras y sincronizar datos de suscripciones. 5. Para la cuota solicitada, introduce la cantidad que necesitas. Si no estás seguro de cuánto solicitar, revisa tu uso actual en la [Google Cloud Console](https://console.cloud.google.com/) en **IAM & Admin > Quotas** (filtra por "Google Play Android Developer API"); luego envía tus datos de uso y el número de entradas históricas que planeas importar a [support@adapty.io](mailto:support@adapty.io) para que podamos ayudarte a determinar la cantidad correcta. 6. Envía el formulario. Google normalmente procesa las solicitudes de aumento de cuota en unos pocos días hábiles. --- # File: prepare-your-app-for-store-review --- --- title: "Prepara tu app para la revisión en la store" description: "Consejos para que tu aplicación sea aprobada en App Store y Google Play Store" --- Este artículo describe el proceso que siguen las stores al revisar las apps enviadas y ofrece consejos para conseguir la aprobación más rápido. La información proviene de las guías oficiales de envío: * [Directrices de envío de App Store](https://developer.apple.com/app-store/review/guidelines/) * [Directrices de envío de Google Play Store](https://play.google/developer-content-policy/) :::important Ambas stores siguen un proceso de revisión similar. Cuando una política solo aplica a una de las stores, el artículo lo indica por su nombre. ::: Los usuarios de Adapty deben prestar especial atención a los problemas de cumplimiento relacionados con [paywalls y compras in-app](#iap-related-requirements). Son uno de los motivos más frecuentes de rechazo. ## Antes de empezar \{#before-you-begin\} Confirma que tu aplicación está lista para el envío. Adapty ofrece una [lista de verificación para el lanzamiento](release-checklist) para preparar tu app para su publicación. Google Play Store exige a los editores nuevos que [prueben la app](https://support.google.com/googleplay/android-developer/answer/14151465?hl=en) antes de enviarla. La prueba debe contar con al menos 12 personas y durar un mínimo de 14 días consecutivos. Este requisito se introdujo en 2025 para reducir el número de apps con errores que llegan al equipo de revisión de Google. ## Resumen del proceso de revisión \{#review-process-overview\} #### Paso 1: La revisión automatizada \{#step-1-the-automated-screening\} Tanto App Store como Google Play Store siguen un proceso de revisión similar en dos pasos. Inmediatamente después del envío, tu app pasa por un análisis automatizado que puede tardar varias horas. Ambas tiendas analizan tu app en busca de malware, y Google hace especial hincapié en este proceso. Busca indicadores de comportamiento malicioso, como el contacto con servidores sospechosos o el acceso injustificado a datos del usuario. Si tu aplicación se considera potencialmente dañina, se marca y se pasa a un analista de seguridad humano. La [documentación de Google Play Protect](https://developers.google.com/android/play-protect/cloud-based-protections#machine-learning) contiene una lista aproximada de las comprobaciones que se realizan en este paso. Las stores también verifican la presencia de los metadatos necesarios, la ausencia de dependencias dañinas o gravemente desactualizadas, y la integridad de tu build. #### Paso 2: La revisión humana \{#step-2-the-human-review\} Una vez que tu app supera el cribado automático, un revisor humano la examina. Este paso puede llevar varios días, dependiendo de la complejidad de tu app y la cola de revisión en ese momento. Las apps que procesan datos sensibles tardan más en revisarse. ## Requisitos generales \{#general-requirements\} ### Estabilidad \{#stability\} Las aplicaciones que se cierran inesperadamente durante la revisión son rechazadas. Los revisores pueden simular intencionalmente condiciones de red poco fiables, por lo que la app debe ser capaz de gestionarlas correctamente. ### Completitud \{#completeness\} Tanto Apple como Google imponen un requisito de *completitud* ("funcionalidad mínima") sobre el contenido de la store. * Los marcadores de posición, las pantallas "próximamente" y las funcionalidades rotas son motivo de rechazo en iOS. * Google es [más flexible](https://support.google.com/googleplay/android-developer/answer/9898783?hl=en), especialmente si tu aplicación está en [Early Access](https://knowledge.workspace.google.com/admin/users/access/turn-early-access-apps-on-or-off-for-users). * Ambos stores **rechazan aplicaciones** con poca o ninguna funcionalidad. Esto incluye apps que muestran una sola imagen, un PDF o una página web. El contenido faltante entra en la misma categoría. * Si la app no hace lo que anuncias, será rechazada. * Si configuras una compra in-app en tu dashboard, pero no la incluyes en la build, la app será rechazada. ### Precisión de los metadatos \{#metadata-accuracy\} La información engañosa, inexacta o inconsistente en la descripción, capturas de pantalla y otros metadatos puede llevar al rechazo. No uses tu ficha en la store para anunciar funcionalidades futuras de la app. Si la app no está diseñada para el público en general, el revisor buscará documentación adicional que explique sus flujos de trabajo. Incluye instrucciones claras en los metadatos de la app. ### Clasificación de contenido \{#content-rating\} El contenido de tu app debe coincidir con la clasificación que has declarado. ### Aspectos legales \{#legal-aspects\} * La política de privacidad de tu app debe ser accesible desde dentro de la app. Puedes usar el [botón de enlace](paywall-buttons#links) del Paywall Builder. * Exige a los usuarios que lean y acepten cualquier acuerdo legal **antes** de que entre en vigor. * Indica la presencia de publicidad en tu app. No hacerlo puede resultar en un rechazo. * Si tu app de iOS incluye compras in-app, debes aceptar el **Paid Apps Agreement** en tu dashboard de App Store Connect. ### Autenticación \{#authentication\} Si parte del contenido de tu app solo está disponible tras autenticarse, proporciona credenciales de acceso válidas al revisor de la store. No poder acceder al contenido de forma completa justifica un rechazo. Si tu app permite a los usuarios crear una cuenta, también debe permitirles eliminarla. Dirigir a los usuarios a soporte por correo electrónico o a un sitio web no cumple este requisito. ### Acceso y privacidad \{#access-and-privacy\} Los metadatos de la app deben indicar claramente el motivo de cada permiso solicitado. Los permisos más sensibles (por ejemplo, acceso a mensajes de texto y registros de llamadas) pueden requerir una demostración en vídeo. El mismo principio aplica a los datos sensibles del usuario: si los solicitas, explica por qué. ## Requisitos relacionados con las compras in-app \{#iap-related-requirements\} Las infracciones de política de negocio son uno de los motivos de rechazo más habituales. Si los principales métodos de monetización de tu aplicación son las suscripciones y las compras in-app, estará sujeta a un escrutinio mayor. ### Requisitos del paywall \{#paywall-requirements\} Los revisores de aplicaciones esperan paywalls sencillos y fáciles de entender. Si se sospecha que estás manipulando a los usuarios, la aplicación es rechazada. Si en varias revisiones se encuentran pruebas de prácticas engañosas, tu cuenta puede ser desactivada y tu aplicación [suspendida](https://support.google.com/googleplay/android-developer/community-guide/287283557/app-suspended-for-repeated-rejections?hl=en). Google Play utiliza un [sistema de penalizaciones](https://support.google.com/googleplay/android-developer/answer/9899234?hl=en) que puede llevar a la eliminación de todas tus aplicaciones. Sigue estas prácticas en el diseño de tus paywalls: - **Sé transparente y claro desde el principio.** Muestra el precio exacto del producto, la frecuencia de cobro, los beneficios y las condiciones de cancelación antes de pedir al usuario que realice la compra. Diferencia claramente entre compras únicas y productos que requieren pagos recurrentes. Si un producto incluye un período de prueba gratuito, indica claramente su duración y condiciones. No uses un lenguaje deliberadamente confuso para engañar al usuario. - **Sé coherente.** Los precios de los productos deben coincidir en el listing de App Store, las pantallas in-app, las pantallas de gestión de suscripciones y el contenido de marketing. Cualquier discrepancia en los precios, por pequeña que sea, es motivo de rechazo. El Paywall Builder de Adapty sincroniza automáticamente los precios entre tu paywall y tu producto en App Store Connect. Si tu paywall está codificado manualmente, debes [obtener el precio de cada producto](fetch-paywalls-and-products) desde su array de datos. - **Muestra todos los niveles por igual.** No preselecciones la opción más cara ni ocultes las más baratas. - **Evita los "patrones oscuros".** No crees una falsa sensación de urgencia o escasez. No obligues a los usuarios a hacer compras haciendo que las funciones gratuitas sean intencionalmente incómodas o difíciles de encontrar. ### Garantía de acceso \{#access-guarantee\} La aplicación debe garantizar el derecho de los usuarios a acceder a sus compras. * **Acceso inmediato** Una compra exitosa debe desbloquear el acceso al producto de forma inmediata, sin demoras visibles. Los estados intermedios de autorización de pago no deben generar errores ni arruinar la experiencia del usuario. Una compra exitosa debe ocultar el paywall de inmediato. Si continúas mostrando el paywall después de una compra, impides que el usuario acceda al contenido que pagó. * **Restauración del acceso** Un usuario debe poder restaurar el acceso al producto desde un nuevo dispositivo. Coloca el botón de restauración en un lugar visible. Si creaste tu paywall con el [Flow Builder](adapty-flow-builder), el botón de restauración activa automáticamente el proceso de restauración. Si [implementaste un paywall manualmente](ios-implement-paywalls-manually), añade el código que llama al método [restorePurchases](restore-purchase). Adapty restaurará el nivel de acceso del usuario, **a menos que** uses el SDK en [modo observer](observer-vs-full-mode). El app debe ser capaz de reconocer las compras in-app realizadas desde la página del producto en el store, o desde cualquier otro lugar en el store. ### Métodos de pago apropiados \{#appropriate-payment-methods\} Ambas stores prohíben la venta de bienes físicos mediante compras in-app y exigen el uso de su sistema de pago para la mayoría de los bienes digitales. El requisito de facturación de la store no se aplica en algunas jurisdicciones geográficas, como Estados Unidos y la UE. Dependiendo del país, es posible que puedas [evitar por completo la facturación de la store](https://support.google.com/googleplay/android-developer/answer/16497028), o [presentar al usuario una elección](https://support.google.com/googleplay/android-developer/answer/13821247) entre la facturación de la app store y una alternativa. Algunas categorías de apps (como lectores de libros electrónicos o apps de citas) pueden ser elegibles para métodos de pago alternativos incluso fuera de estas regiones. Consulta las directrices oficiales de los stores para más detalles. :::tip A diferencia de [Google](https://support.google.com/googleplay/android-developer/answer/13821247), Apple no ofrece una lista definitiva de países que permiten métodos de facturación alternativos. A medida que nuevas jurisdicciones aprueben leyes similares, la disponibilidad irá aumentando. Lee la documentación correspondiente a tu país concreto antes de continuar. ::: Ten en cuenta que ambas stores aplican directrices para las integraciones con proveedores de pago y siguen cobrando comisiones por las transacciones que usan estos servicios. ## Manejo de rechazos \{#handling-rejection\} Si tu aplicación es rechazada, el revisor indicará qué directrices ha infringido. Lee la directriz completa y corrígelo: * [Directrices de envío a la App Store](https://developer.apple.com/app-store/review/guidelines/) * [Directrices de envío a la Google Play Store](https://play.google/developer-content-policy/) Si consideras que el rechazo fue injusto, tienes derecho a apelar. Proporciona evidencia de cumplimiento y contacta con el store. * No actualices la app mientras está en proceso de revisión. * Cada vez que envíes tu aplicación a revisión, puede que te toque un revisor diferente. Esto puede jugar a tu favor o en tu contra. * No corrijas los problemas uno a uno. Envía la app a revisión solo cuando hayas aplicado todas las correcciones. * Si Google Play rechazó tu aplicación por incumplimiento de políticas, actualiza los datos en cuestión en todas las pistas, aunque estén pausadas o inactivas. * Las revisiones posteriores suelen tardar menos que la primera. * La revisión urgente puede estar disponible para errores críticos y fechas límite — úsala con moderación. ## Tras la revisión: monitoreo continuo \{#after-the-review-continuous-monitoring\} Ambas tiendas siguen monitoreando tu aplicación incluso después de que supere el proceso de revisión. Si la funcionalidad de tu app cambia tras la aprobación (por ejemplo, debido a código cargado dinámicamente), será marcada y eliminada del listado. Una avalancha de opiniones negativas de usuarios también puede motivar un escrutinio adicional. Entre 2024 y 2025, Google [eliminó el 47% de sus apps de Play Store](https://techcrunch.com/2025/04/29/google-play-sees-47-decline-in-apps-since-start-of-last-year/) para mejorar su calidad media. Abandonar tu app también conlleva riesgos. Tanto [Google](https://www.cnet.com/tech/mobile/google-play-store-will-hide-apps-that-havent-been-updated-in-years/) como [Apple](https://developer.apple.com/support/app-store-improvements/#:~:text=Developers%20of%20apps%20that%20have,launch%20will%20be%20removed%20immediately.) eliminan las apps que no reciben actualizaciones ni descargas. ## Ver también \{#see-also\} * [Pruebas en sandbox](test-purchases-in-sandbox) * [Lista de verificación para el lanzamiento](release-checklist) --- # File: firebase-apps --- --- title: "Aplicaciones Firebase" description: "Integra Firebase con Adapty para mejorar el análisis de usuarios y el seguimiento de suscripciones en tu app móvil." --- Esta página explica cómo integrar Adapty en tu app si funciona con Firebase. :::note Primeros pasos Estos no son todos los pasos necesarios para que Adapty funcione, solo algunos consejos útiles para la integración con Firebase. Si quieres integrar Adapty en tu app, lee primero la [Guía de inicio rápido](quickstart). ::: ## Identificación de usuarios \{#user-identification\} Si usas Firebase Auth, este fragmento de código puede ayudarte a mantener tus usuarios sincronizados entre Firebase y Adapty. Ten en cuenta que es solo un ejemplo y debes considerar las particularidades de autenticación de tu app. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS with Firebase" default> ```swift showLineNumbers @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Configure Adapty before Firebase Adapty.activate("YOUR_API_KEY") Adapty.delegate = self // Configure Firebase FirebaseApp.configure() // Add state change listener for Firebase Authentication Auth.auth().addStateDidChangeListener { (auth, user) in if let uid = user?.uid { // identify Adapty SDK with new Firebase user Adapty.identify(uid) { error in if let e = error { print("Sign in error: \(e.localizedDescription)") } else { print("User \(uid) signed in") } } } } return true } } extension AppDelegate: AdaptyDelegate { // MARK: - Adapty delegate func didReceiveUpdatedPurchaserInfo(_ purchaserInfo: PurchaserInfoModel) { // You can optionally post to the notification center whenever // purchaser info changes. // You can subscribe to this notification throughout your app // to refresh tableViews or change the UI based on the user's // subscription status NotificationCenter.default.post(name: NSNotification.Name(rawValue: "com.Adapty.PurchaserInfoUpdatedNotification"), object: purchaserInfo) } } ``` </TabItem> <TabItem value="kotlin" label="Android with Firebase" default> ```kotlin showLineNumbers class App : Application() { override fun onCreate() { super.onCreate() // Configure Adapty Adapty.activate(this, "YOUR_API_KEY") Adapty.setOnPurchaserInfoUpdatedListener(object : OnPurchaserInfoUpdatedListener { override fun onPurchaserInfoReceived(purchaserInfo: PurchaserInfoModel) { // handle any changes to subscription state } }) // Add state change listener for Firebase Authentication FirebaseAuth.getInstance().addAuthStateListener { auth -> val currentUserId = auth.currentUser?.uid if (currentUserId != null) { // identify Adapty SDK with new Firebase user Adapty.identify(currentUserId) { error -> if (error == null) { //success } } } else { Adapty.logout { } } } } } ``` </TabItem> </Tabs> --- # File: refund-saver --- --- title: "Refund Saver" description: "Usa Adapty Refund Saver para minimizar los reembolsos y maximizar los ingresos." --- Cuando un usuario solicita un reembolso, Apple lleva a cabo una investigación. Para decidir **si el reembolso está justificado**, solicita al desarrollador información sobre la actividad de ese usuario. Sin esta evidencia, incluso una suscripción con un uso intensivo tiene muchas probabilidades de ser reembolsada. El **Refund Saver** responde automáticamente a las [solicitudes de consumo de Apple](https://developer.apple.com/documentation/appstoreserverapi/send-consumption-information-v1), protegiendo tus ingresos y **aumentando la probabilidad de rechazo** de solicitudes injustas. Funciona con todos los tipos de compras in-app de Apple: suscripciones de renovación automática, suscripciones únicas, consumibles y no consumibles (incluidos los productos de por vida). ## Cómo funciona Refund Saver \{#how-refund-saver-works\} 1. Cuando un usuario inicia una solicitud de reembolso, App Store envía una notificación solicitando los detalles de la transacción y el uso. Si **ignoras** o **demoras** la respuesta, Apple probablemente **aprobará el reembolso**. 2. El Adapty Refund Saver procesa estas notificaciones automáticamente, proporcionando a Apple los datos necesarios. Esta automatización reduce la posibilidad de reembolsos innecesarios, ahorrando tiempo y protegiendo tus ingresos. 3. Adapty registra cada resultado: reembolsado o rechazado. Esos datos alimentan los análisis de Refund Saver en el Dashboard. :::info Con Refund Saver, puedes recuperar hasta el 40% de los ingresos procedentes de solicitudes de reembolso. ::: ## Requisitos para usar Refund Saver \{#requirements-to-use-refund-saver\} Para usar esta función, asegúrate de cumplir los siguientes requisitos previos: 1. **Actualiza tu Política de Privacidad en App Store Connect:** La Política de Privacidad de tu app debe indicar la recopilación y el uso de datos de consumo. Esto garantiza que los usuarios entiendan las prácticas de privacidad de tu app antes de descargarla. Consulta los [Detalles de privacidad de apps de Apple](https://developer.apple.com/app-store/app-privacy-details/) para más información. 2. **Obtén el consentimiento del usuario para compartir datos en tu app**: Apple exige que obtengas el consentimiento válido del usuario antes de compartir sus datos personales con Apple. Como desarrollador, eres responsable de obtener ese consentimiento, ya que serás tú quien comparta los datos del usuario con Apple. Consulta las [directrices](https://developer.apple.com/documentation/appstoreserverapi/send-consumption-information) de Apple para más detalles. 3. **Activa las Notificaciones del Servidor V2:** Asegúrate de que las Notificaciones del Servidor V2 estén activadas en tu cuenta de Apple Developer y correctamente configuradas en Adapty, ya que las notificaciones V1 no son compatibles. Si aún no las has activado, sigue los pasos de la guía [Activar las notificaciones del servidor de App Store](enable-app-store-server-notifications). ## Activar Refund Saver \{#turn-on-refund-saver\} 1. Abre la sección [Refund Saver](https://app.adapty.io/refund-saver) en el Adapty Dashboard. 2. Haz clic en **Turn on Refund Saver** para activar la función. ## Establecer un comportamiento predeterminado para los reembolsos \{#set-a-default-refund-behavior\} Apple permite a los desarrolladores especificar un resultado preferencial para cada solicitud de reembolso al responder a ella. El propósito de este ajuste es encontrar el equilibrio adecuado entre rechazar y aceptar solicitudes de reembolso, de modo que solo se concedan reembolsos justificados. Ten en cuenta que este ajuste solo sirve para influir en el resultado, pero la decisión final sigue siendo de Apple. Adapty permite configurar esta preferencia, aunque utilizaremos el mismo valor para todas las solicitudes de reembolso. 1. Para cambiar tu preferencia, haz clic en **Edit refund preference**. 2. En la ventana **Edit refund preference**, elige tu opción de **Default refund request preference**: | Opción | Descripción | | -------------------------------------------- | ------------------------------------------------------------ | | Always decline | (por defecto) Es la opción predeterminada y generalmente ofrece los mejores resultados para minimizar los reembolsos. | | Decline first refund request, grant all next | Para cada transacción que encuentre Refund Saver, primero solicitará a Apple que rechace el reembolso. Sin embargo, si la misma transacción vuelve a aparecer, Refund Saver siempre recomendará conceder el reembolso. Este enfoque ayuda a reducir la frustración de los usuarios ante rechazos injustos: pueden volver a solicitar el reembolso y es probable que lo reciban. | | Always refund | Sugiere a Apple que apruebe todas las solicitudes de reembolso. | | No preference | No proporciona ninguna recomendación a Apple. En este caso, Apple determinará el resultado del reembolso según sus políticas internas y el historial del usuario, sin ninguna influencia de tu configuración. Esta opción ofrece el enfoque más neutral. | ## Establecer el comportamiento de Refund Saver para un usuario específico en el dashboard \{#set-refund-behavior-for-a-specific-user-in-the-dashboard\} Aunque hayas configurado el comportamiento predeterminado de Refund Saver para toda la app, puedes establecer preferencias individuales para usuarios concretos. En el Adapty Dashboard, puedes hacerlo desde el perfil del usuario. Usa la sección **Refund Saver Preferences** situada en la parte inferior izquierda. :::note Las preferencias por usuario anulan el valor predeterminado de la app, incluido el comportamiento "Decline first refund request, grant all next". ::: ## Establecer el comportamiento de reembolso para un usuario específico en el SDK \{#set-refund-behavior-for-a-specific-user-in-the-sdk\} Puedes definir la preferencia de reembolso en el código de tu app de forma individual para cada instalación según las acciones del usuario. Usa el siguiente fragmento para establecer la preferencia: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (3.4.1+)" default> ```swift showLineNumbers code do { try await Adapty.updateRefundPreference(<PREFERENCE_VALUE>) // possible values: .noPreference, .grant, .decline } catch { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter (3.4.0+)" default> ```javascript showLineNumbers code try { // possible values: AdaptyRefundPreference.noPreference, AdaptyRefundPreference.grant, AdaptyRefundPreference.decline await Adapty().updateRefundPreference(<PREFERENCE_VALUE>); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native (3.4.0+)" default> ```typescript showLineNumbers try { await adapty.updateRefundPreference(<PREFERENCE_VALUE>); // possible values: RefundPreference.NoPreference, RefundPreference.Grant, RefundPreference.Decline } catch (error) { // handle the `AdaptyError` } ``` </TabItem> <TabItem value="unity" label="Unity (3.3.0+)" default> ```csharp showLineNumbers Adapty.UpdateAppStoreRefundPreference(<PREFERENCE_VALUE>, (error) => { if (error != null) { // handle the error return; } }); ``` </TabItem> </Tabs> :::note También puedes usar la API del servidor para [establecer una preferencia de reembolso individual](api-adapty/operations/setRefundSaverSettings): - Usa el SDK cuando la configuración de preferencias esté directamente vinculada a interacciones del cliente, como cuando los usuarios pulsan un botón para configurar su preferencia. - Usa la API cuando necesites realizar procesamiento en el servidor o cuando se adapte mejor a la arquitectura de tu aplicación. ::: ## Obtener el consentimiento del usuario \{#obtain-user-consent\} La forma en que recopilas el consentimiento del usuario para compartir datos depende de ti, pero Apple exige un consentimiento válido antes de compartir cualquier dato personal con ellos. Apple recomienda usar un **enfoque de opt-in**, que consiste en mensajes dentro de la app que explican cómo se utilizarán los datos y requieren una acción explícita del usuario para otorgar el consentimiento. Si un usuario ignora o rechaza el mensaje, no se considera que haya dado su consentimiento. Para más detalles, consulta las [directrices](https://developer.apple.com/documentation/appstoreserverapi/send-consumption-information) de Apple. Si el consentimiento explícito no es práctico para tu app, puedes considerar un **enfoque de exclusión voluntaria (opt-out)**. Esto implica incluir una cláusula de uso compartido de datos en tus Términos de Servicio, explicando que los usuarios aceptan el intercambio de datos al aceptar los términos. Asegúrate de indicar claramente cómo pueden revocar su consentimiento. A continuación se muestra un ejemplo de cláusula para el enfoque de exclusión voluntaria, que incluye los tipos de datos que podrías compartir. Es solo un ejemplo orientativo para que redactes tu propio texto. Eres responsable de asegurarte de que tu versión final cumpla con todas las leyes aplicables y los requisitos de Apple. *"Si recibimos una solicitud de reembolso por una compra in-app, podemos proporcionar a Apple información sobre la actividad de compras in-app del usuario. Esto puede incluir detalles como el tiempo transcurrido desde la instalación de la app, el tiempo total de uso de la app, un identificador de cuenta anónimo, si la compra in-app fue consumida por completo, si incluía un período de prueba, el importe total gastado y el importe total reembolsado."* Dependiendo del enfoque elegido, establece la opción **Default consent policy** en el menú **Edit refund preferences**: <p> </p> | Opción | Descripción | | ------- | ------------------------------------------------------------ | | Opt-out | (predeterminado) Si Adapty no conoce el estado de consentimiento del usuario, asume que el consentimiento **fue otorgado** y Refund Saver **compartirá** los datos relacionados con reembolsos con Apple. | | Opt-in | Si Adapty no conoce el estado de consentimiento del usuario, asume que el consentimiento **no fue otorgado** y Refund Saver **no compartirá** ningún dato con Apple. Este es el enfoque recomendado por Apple. | ## Actualizar el consentimiento del usuario en el SDK \{#update-user-consent-in-the-sdk\} Para indicarle a Adapty si un usuario específico ha dado su consentimiento, usa el método `updateCollectingRefundDataConsent`. El valor persiste en el servidor por perfil, por lo que solo necesitas llamarlo cuando el consentimiento cambie. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (3.4.1+)" default> ```swift showLineNumbers do { try await Adapty.updateCollectingRefundDataConsent(<CONSENT_VALUE>) // true = consent is explicitly provided, false = consent is explicitly revoked } catch { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter (3.4.0+)" default> ```dart showLineNumbers try { // true = user gave consent, false = user revoked consent await Adapty().updateCollectingRefundDataConsent(<CONSENT_VALUE>); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native (3.4.0+)" default> ```typescript showLineNumbers try { await adapty.updateCollectingRefundDataConsent(<CONSENT_VALUE>); // true = consent is explicitly provided, false = consent is explicitly revoked } catch (error) { // handle the `AdaptyError` } ``` </TabItem> <TabItem value="unity" label="Unity (3.3.0+)" default> ```csharp showLineNumbers Adapty.UpdateAppStoreCollectingRefundDataConsent(<CONSENT_VALUE>, (error) => { if (error != null) { // handle the error return; } }); ``` </TabItem> </Tabs> :::note También puedes usar la API del servidor para [establecer una preferencia individual de uso compartido de datos](api-adapty/operations/setRefundSaverSettings): - Usa el SDK cuando la configuración de la preferencia está directamente vinculada a interacciones del cliente, como cuando los usuarios hacen clic en un botón para configurar su preferencia. - Usa la API cuando necesitas realizar procesamiento en el servidor o cuando se adapta mejor a la arquitectura de tu aplicación. ::: ## Comprobar el consentimiento del usuario \{#check-user-consent\} Puedes consultar el estado de consentimiento actual de un usuario en cualquier momento. En el Adapty Dashboard, abre el perfil del usuario y busca el ajuste **Allow data sharing** en la sección **Refund Saver Preferences**, en la parte inferior izquierda. :::note También puedes usar la API del lado del servidor para [obtener las preferencias individuales de reembolso y compartición de datos](api-adapty/operations/getRefundSaverSettings). ::: ## Limitaciones \{#limitations\} - **Solo para App Store de Apple:** Refund Saver solo está disponible para solicitudes de reembolso realizadas en el App Store de Apple. Google Play no ofrece análisis de datos de consumo para reembolsos. Las decisiones de reembolso en Google Play se basan únicamente en las políticas de Google y en la información proporcionada por el usuario. - **Requiere Server Notifications V2:** Refund Saver no es compatible con App Store Server Notifications V1. Si actualmente usas V1 en Adapty, debes cambiar a V2; consulta la guía [Envío de notificaciones del servidor de App Store a Adapty](enable-app-store-server-notifications) para más detalles. Cambiar a V2 también mejorará tu analítica en Adapty al proporcionar datos más precisos y completos. --- # File: meta-create-campaign --- --- title: "Publicita tu app en Meta Ads" --- En esta guía paso a paso aprenderás cómo crear y configurar anuncios para tu app en Meta, para optimizarlos y hacer un seguimiento de su rendimiento fácilmente. ## Cómo se estructuran los anuncios en Meta \{#how-ads-in-meta-are-structured\} Al anunciarte en Meta Ads, necesitas configurar tres niveles jerárquicos: - **Campaign**: Las campañas definen tus objetivos publicitarios. - **Ad set**: Los conjuntos de anuncios especifican tu audiencia objetivo y los placements, determinando dónde y a quién se mostrarán tus anuncios. Cada campaña puede contener varios conjuntos de anuncios. - **Ads**: Los anuncios son los creativos reales que los usuarios ven e interactúan con ellos. Cada conjunto de anuncios puede contener varios anuncios; sin embargo, se recomienda limitarlo a no más de cinco anuncios por conjunto para un rendimiento óptimo. ## Paso 1. Crea una cuenta en Meta Ads Manager \{#step-1-create-meta-ads-manager-account\} Para empezar con Meta Ads, necesitas tener una página de Facebook Business, ya que no puedes publicar anuncios desde tu perfil personal. Por tanto, tienes que vincular tu página de empresa a tu portfolio de negocios de Meta Ads: 1. Ve a [business.facebook.com](https://business.facebook.com/). Si todavía no tienes una página de empresa en el portfolio de negocios, deberás añadirla. Haz clic en **Go to settings**. 2. Ve a **Account > Pages** en la barra lateral izquierda. Haz clic en **Add** y selecciona **Add an existing Facebook page** o **Create a new Facebook page**. Consulta la [guía para crear una página de empresa](https://www.facebook.com/business/help/473994396650734) si todavía no tienes una. 3. Opcionalmente, vincula tu cuenta de Instagram en la página **Account > Instagram accounts** de la configuración. Una vez conectada tu página de empresa, ya puedes continuar. ## Paso 2. Añadir el píxel de Meta \{#step-2-add-meta-pixel\} Necesitarás un píxel de Meta para conectar los datos de tu campaña con los ingresos y obtener mejores resultados. Antes de conectar datos y crear un píxel, necesitarás: - Una página de empresa – añádela a tu portfolio empresarial en [**Settings > Accounts > Pages**](https://business.facebook.com/latest/settings/pages) - Una cuenta de Business Manager – debes tener control total sobre el portfolio empresarial - Un email empresarial – configúralo en [**Settings > Business info**](https://business.facebook.com/latest/settings/business_info) - Una cuenta publicitaria – añádela a tu portfolio empresarial en [**Settings > Accounts > Ad accounts**](https://business.facebook.com/latest/settings/ad_accounts) Cuando estés listo, crea un píxel: 1. Ve a [**Events Manager**](https://www.facebook.com/events_manager2). Haz clic en **Connect data**. 2. Selecciona **Web** como tipo de fuente de datos. 3. Ponle un nombre a tu conjunto de datos y haz clic en **Create**. 4. Para la [atribución de Adapty](adapty-user-acquisition), no necesitas completar la instalación completa del píxel. Cuando te pregunten sobre la integración, puedes hacer clic en **x** en la ventana de configuración, y el píxel seguirá apareciendo en la lista después de actualizar la página. 5. Cuando tu conjunto de datos aparezca en la lista, puedes continuar con la creación de una campaña. ## Paso 3. Crear una campaña \{#step-3-create-campaign\} Para crear una campaña en Meta Ads Manager: 1. Ve a [Meta Ads Manager](https://adsmanager.facebook.com/adsmanager/manage). En la pestaña **Campaign**, haz clic en **Create**. 2. Selecciona **Sales** como objetivo de la campaña y haz clic en **Continue**. 3. Asigna un nombre a tu campaña en la sección **Campaign name**. 4. En la sección **Budget**, en **Budget strategy**, selecciona cómo quieres controlar el presupuesto: - **Campaign budget**: La opción más sencilla si no tienes claro qué oportunidades funcionarán mejor. Si la seleccionas, Meta Ads detectará automáticamente los mejores resultados y asignará más presupuesto a los conjuntos de anuncios con mejor rendimiento. Luego, selecciona si necesitas un presupuesto **Daily** o **Lifetime** e introduce el límite en tu moneda. El presupuesto **Daily** te da más flexibilidad mientras aprendes, así que puedes empezar con cantidades pequeñas e ir ajustándolas sobre la marcha. También puedes seleccionar **Schedule budget increase** y configurar reglas para aumentar el presupuesto automáticamente por un valor fijo o un porcentaje. - **Ad set budget**: Selecciona esta opción si quieres definir manualmente qué audiencias recibirán más o menos presupuesto de la campaña. Si no estás del todo seguro, puedes seleccionar **Share some of your budget with other ad sets** para que Meta ajuste automáticamente los presupuestos de los ad sets hasta un 20% si eso beneficia el rendimiento del anuncio. 5. En **Campaign bid strategy**, selecciona la mejor opción para tus objetivos: - **Highest volume (default)**: La opción más sencilla para empezar. Si la seleccionas, dejas que Meta optimice el coste por clic para obtener los mejores resultados con tu presupuesto. - **Cost per result goal**: Apunta a un coste por resultado concreto si conoces tus referencias de rendimiento. - **Bid cap**: Establece el coste máximo que estás dispuesto a pujar. 6. Adapty te permite realizar [pruebas A/B](ab-tests) exhaustivas. Sin embargo, también puedes habilitar pruebas A/B en Meta Ads si lo necesitas. Obtén más información sobre las pruebas A/B en Meta Ads Manager [aquí](https://www.facebook.com/business/help/1159714227408868). 7. Ahora es el momento de añadir el primer ad set a tu campaña. Haz clic en **Next** para continuar. ## Paso 4. Crear un conjunto de anuncios \{#step-4-create-ad-set\} Para crear un conjunto de anuncios: 1. Escribe el nombre en el campo **Ad set name**. 2. En el desplegable **Conversion location**, selecciona **Website**. 3. En el campo **Performance goal**, selecciona **Maximize number of landing page views** si tienes una landing page, o **Maximize number of link clicks** si usas un smart link que lleva a los usuarios directamente al store. 4. En el campo **Dataset**, selecciona el dataset que creaste en el [Paso 2](#step-2-add-meta-pixel). 5. Selecciona un **Conversion event**. En nuestro caso, probablemente será **Purchase** o **Start trial**. No te preocupes si ves una advertencia de que tu dataset no tiene eventos todavía: simplemente significa que tu dataset es nuevo. 6. Si al configurar la campaña seleccionaste **Ad set budget**, elige si necesitas un presupuesto **Daily** o **Lifetime** e introduce el límite en tu moneda. El presupuesto **Daily** te da más flexibilidad mientras estás aprendiendo, ya que puedes empezar con importes pequeños e irlos ajustando sobre la marcha. Establece las fechas de inicio y, si procede, de fin del conjunto de anuncios. Por ejemplo, si quieres anunciar una oferta promocional en tu app, es fundamental que el período del conjunto de anuncios coincida con el de la oferta. 7. En la sección **Audience controls**, configura los ajustes de audiencia: - **Location**: Las ubicaciones pueden ser tan amplias o específicas como necesites. Puedes limitar las **Locations** en el conjunto de anuncios para adaptarlas a las particularidades regionales de tus anuncios. - **Minimum age**: Selecciona la edad mínima de los usuarios que verán tu anuncio. En algunos casos puede ser un requisito legal. No puedes establecer una edad mínima inferior a 18 años a nivel global, ni a 20 años en Tailandia. - **Language**: Configura **Language** solo si no es el idioma más común en los países seleccionados. Por ejemplo, no necesitarás seleccionar **English** en Estados Unidos, pero si tu objetivo son hispanohablantes que viven allí, puede que quieras seleccionar **Spanish**. 8. De forma predeterminada, Meta encuentra automáticamente grupos más pequeños de personas a las que tu anuncio será relevante. Sin embargo, si añades una sugerencia de audiencia, puedes orientar a Meta hacia las personas que crees que tienen más probabilidades de responder. En la sección **Advantage+ audience**, puedes ajustar: - **Age**: Establece un rango de edad específico para orientar el anuncio y así adaptarlo mejor a distintos grupos de edad. - **Gender**: Muestra tu anuncio a todos los usuarios o segméntalo por género. - **Detailed targeting**: Esta opción te ofrece el control más preciso sobre la audiencia de tu anuncio o app. Aquí puedes formar grupos basándote en **Demographics**, **Interests** o **Behaviors**. Dependiendo de lo que haga tu app, por ejemplo, puedes centrarte en distintas profesiones, seguidores de bandas de música concretas, padres de recién nacidos o personas que suelen comprar mucho en línea. :::note La configuración de **Detailed targeting** se aplica con el operador **Or**. Si quieres aplicar condiciones con el operador **And**, haz clic en **Define further** y selecciona nuevas condiciones. ::: 9. En la sección **Placements**, puedes seleccionar dónde aparecerá tu anuncio. De forma predeterminada, se selecciona la opción **Advantage+**, que permite a Meta distribuir el presupuesto de tu conjunto de anuncios entre varios placements según dónde sea más probable que funcionen mejor. Te recomendamos usar esta opción si no sabes dónde colocar tu anuncio. Si quieres seleccionar placements específicos de forma manual, selecciona **Manual placements** y personalízalos. Lee más [aquí](https://www.facebook.com/business/help/965529646866485). 10. **Recomendado**: La segmentación por dispositivo te ayuda a optimizar tu gasto. En la sección **Placements**, haz clic en **Show more settings**. En el apartado **Devices and operating system**, selecciona qué dispositivos, sistemas operativos y versiones de SO deben incluirse en tu audiencia. Esto garantiza que tus anuncios solo se muestren a los usuarios relevantes. Por ejemplo, los usuarios de escritorio no verán tu anuncio, y los usuarios con versiones antiguas del SO que tu app no soporta quedarán excluidos. 11. Cuando estés listo, haz clic en **Next** para continuar. ## Paso 5. Crear anuncios \{#step-5-create-ads\} Para crear un anuncio en Meta Ads Manager: 1. Asigna un nombre al anuncio en el campo **Ad name**. 2. En la sección **Identity**, selecciona la página de Facebook que se usará para publicar los anuncios. Si tienes una cuenta de Instagram específica para tu app y la has conectado en Meta Business Suite en el [Paso 1](#step-1-create-meta-ads-manager-account), selecciónala en el desplegable **Instagram account**. Si no, selecciona **Use Facebook page** para que los anuncios de Instagram se publiquen usando la página de Facebook. 3. En **Ad setup**, elige cómo quieres publicar tu anuncio. Para promocionar apps, te recomendamos seleccionar **Create ad**, para que la publicación redirija a los usuarios a tu app en lugar de a la página de Facebook. En el campo **Format**, elige una opción según el número de creatividades que tengas y cómo quieras mostrarlas. 4. En la sección **Destination**, mantén **Website** seleccionado como **Main destination**. En el campo **Website URL**, pega `https://api-ua.adapty.io/api/v1/attribution/click`. En [Atribución de Adapty](adapty-user-acquisition), [crea una campaña web](ua-facebook) y pega el contenido de **Click link** después de `https://api-ua.adapty.io/api/v1/attribution/click` en el campo **URL parameters** de la sección **Tracking**. 5. En la sección **Ad creative**, haz clic en **Set up creative** y selecciona **Image ad** o **Video ad**. Se abrirá una nueva ventana donde podrás subir archivos multimedia, recortarlos y añadir textos. 6. Si quieres traducir automáticamente los textos del anuncio, en la sección **Languages**, haz clic en **Add languages**. Añade un idioma principal: tomará los textos de tu creatividad automáticamente. Luego, añade los idiomas de traducción para la traducción automática. 7. Cuando estés listo, haz clic en **Publish** para lanzar tu anuncio. ## Qué viene después \{#whats-next\} Para activar tu anuncio, necesitarás añadir un método de pago si todavía no lo has hecho. Después, puedes [explorar cómo la campaña afecta a los ingresos de tu app en el dashboard de Adapty User Acquisition](adapty-user-acquisition). ¿Todavía no usas Adapty User Acquisition? [Reserva una llamada con nosotros](https://calendly.com/tnurutdinov-adapty/30min) para descubrir cómo puede ayudarte a hacer seguimiento y optimizar tus campañas publicitarias. --- # File: tiktok-create-campaign --- --- title: "Anuncia tu app en TikTok for Business" --- En esta guía paso a paso aprenderás a crear y configurar anuncios para tu app en TikTok for Business, para que puedas optimizarlos y hacer seguimiento de su rendimiento fácilmente. ## Paso 1. Añade la información de tu negocio \{#step-1-add-business-info\} Si estás empezando con TikTok for Business, primero tienes que añadir la información de tu negocio: 1. Ve a [https://ads.tiktok.com](https://ads.tiktok.com/business/) y haz clic en **Get started**. 2. Regístrate con tu correo electrónico o tu cuenta de TikTok. 3. Introduce los datos de tu negocio y sigue las instrucciones en pantalla. Una vez aprobada tu cuenta de negocio, serás redirigido a la creación de tu primera campaña. ## Paso 2. Crear un píxel \{#step-2-create-a-pixel\} Necesitarás un píxel de TikTok para conectar los datos de tu campaña con los ingresos y obtener mejores resultados: 1. Ve a [**Events Manager**](https://ads.tiktok.com/i18n/events_manager/home). Haz clic en **Connect data source**. 2. Selecciona **Web** como tipo de fuente de datos. 3. En la ventana **Add your website**, haz clic en **Skip**. 4. Selecciona **Manual setup** y haz clic en **Next**. 5. Selecciona **TikTok pixel + Events API** y haz clic en **Next**. 6. Ponle un nombre a tu píxel y haz clic en **Create**. 7. Para [Adapty Attribution](adapty-user-acquisition), no necesitarás completar la instalación completa del píxel. Puedes cerrar la ventana de configuración y tu píxel aparecerá en la lista. 8. Para que este píxel esté disponible en las campañas, debes enviarle un evento de prueba desde [Adapty Attribution](adapty-user-acquisition): 1. [Crea una nueva campaña de TikTok](ua-tiktok). 2. Expande una sección específica de plataforma, por ejemplo, iOS. 3. Selecciona un píxel en el menú desplegable. 4. Haz clic en **Send test event**. 5. En el menú desplegable, selecciona el evento que usarás para la optimización en el anuncio. 6. En TikTok for Business, abre tu píxel y ve a la pestaña **Test events**. Copia el `test_event_code`. 7. Pégalo en el campo **Test event code** en Adapty y haz clic en **Send**. 9. El evento de prueba aparecerá en TikTok en varios minutos. Cuando lo veas en los detalles de tu píxel, podrás continuar con la configuración de la campaña en TikTok Ads Manager. ## Paso 3. Selecciona el objetivo de la campaña \{#step-3-select-the-campaign-objective\} :::important Este tutorial usa la vista Quick setup de TikTok Ads Manager. Algunas configuraciones recomendadas solo aparecen en la vista Full, lo cual indicamos en los pasos correspondientes. ::: Ve a la [página de creación de anuncios](https://ads.tiktok.com/i18n/nb_creation/create/objectives) en el Ads Manager. En la primera pantalla, selecciona el objetivo publicitario y haz clic en **Continue**. Selecciona **Sales > Website conversion**. ## Paso 4. Completa la información de la campaña \{#step-4-fill-in-the-campaign-info\} A continuación, completa la información de la campaña: 1. Asigna un nombre a tu campaña en el campo **Campaign name**. 2. En el campo **Optimization goal**, selecciona **Conversion**. 3. Selecciona tu píxel activo en el menú desplegable y elige un **Optimization event**. Ten en cuenta que solo están disponibles los eventos activos. Si el evento que necesitas no está disponible, envía un evento de prueba siguiendo las instrucciones del [Paso 2](#step-2-create-a-pixel). 4. Tu anuncio se mostrará en el feed y la búsqueda de TikTok. Para una configuración adicional, haz clic en **Advanced settings**. En **Placements**, configura los ajustes de placement: - **User comment**: Selecciona esta opción si quieres mostrar tu anuncio también en la sección de comentarios. TikTok recomienda mantener los comentarios de usuarios activados para ayudar a que tus anuncios consigan más impresiones. - **Allow video download**: Permite que los espectadores descarguen tu anuncio. - **Allow video sharing**: Permite que los espectadores compartan tu anuncio. 5. Haz clic en **Continue**. ## Paso 5. Añadir contenido de anuncio \{#step-5-add-ad-content\} Ahora es el momento de configurar tus creatividades y la URL de destino: 1. En el campo **TikTok account**, selecciona la cuenta que se usará para publicar. 2. En [Adapty Attribution](adapty-user-acquisition), [crea una campaña web](ua-tiktok) y pega el **Click link** en el campo **Destination URL**. 3. En la sección **Creatives**, haz clic en **+ Videos and images**. 4. Si quieres usar tus publicaciones de TikTok como creatividades, selecciónalas en la pestaña **TikTok post**. Si no, cambia a la pestaña **Creative library** y haz clic en **Upload**. Los archivos que subas ahí estarán disponibles desde esa pestaña más adelante, para reutilizarlos en otras campañas. 5. Recorta las creatividades para adaptarlas al formato de TikTok y elige si se usarán como anuncios individuales o como un carrusel. 6. Despliega la creatividad subida y haz clic en **+** junto a **No music selected**. Allí puedes subir tus propios archivos mp3. Añadir música es obligatorio. 7. En el campo **Add text**, escribe el texto que se usará como descripción. 8. Selecciona **Place the ads on this TikTok account as a post** si quieres publicar este anuncio en tu cuenta de TikTok. 9. En el campo **Call to action**, selecciona o elimina las llamadas a la acción relevantes para tu anuncio. TikTok las añadirá automáticamente. 10. Haz clic en **Continue**. ## Paso 6. Configura la segmentación y el presupuesto \{#step-6-configure-targeting-and-budget\} Por último, define quién verá tu anuncio y cuánto estás dispuesto a pagar por él: 1. En la sección **Targeting**, selecciona **Automatic** o **Custom**. La opción **Automatic** es la más sencilla si aún no conoces bien tu audiencia. Sin embargo, si seleccionas **Custom**, puedes optimizar tu gasto eligiendo los grupos de usuarios que tienen más probabilidades de responder a tu anuncio. 2. Si has seleccionado **Custom**, configura: - **Location**: La ubicación predeterminada es la de tu cuenta publicitaria. Si seleccionas más de un país o región de segmentación, los resultados de revisión de anuncios se devolverán por separado para cada ubicación. La entrega real de los anuncios también puede variar según las ubicaciones compatibles de los distintos placements. - **Languages**: De forma predeterminada, se seleccionan todos los idiomas. Elige el idioma de segmentación según el que se use con más frecuencia en la ubicación seleccionada. - **Gender**: De forma predeterminada, se seleccionan todos los géneros. :::tip Si cambias al modo completo, encontrarás una sección adicional **Device** dentro de **Targeting**. Aquí puedes limitar tu audiencia por tipo de dispositivo, sistema operativo y versión del SO, lo que resulta útil si tu app requiere una versión mínima. ::: 3. En la sección **Budget**, selecciona una de las opciones sugeridas o elige **Custom**. 4. Si has seleccionado **Custom**, elige si necesitas un presupuesto **Daily** o **Lifetime** e introduce el límite en tu moneda. El presupuesto **Daily** te da más flexibilidad mientras aún estás aprendiendo, así que puedes empezar con cantidades pequeñas e irlas ajustando sobre la marcha. 5. En la sección **Schedule**, selecciona **Continue for at least 7 days** o **Custom**. Te recomendamos configurar una programación **Custom** si tu anuncio es sensible al tiempo, para no perder el momento en que necesites detenerlo. 6. Si has seleccionado **Custom**, establece la fecha de inicio o las fechas de inicio y fin de los anuncios. Ten en cuenta que se usará la zona horaria de tu cuenta. 7. Haz clic en **Publish**. Cuando termines, se creará una nueva campaña con un grupo de anuncios. El grupo de anuncios contendrá un anuncio si has configurado un carrusel o varios anuncios si has añadido creatividades como anuncios separados. ## Paso 7. Introduce los datos de pago \{#step-7-enter-payment-details\} Para empezar a publicar el anuncio, tras configurar la segmentación y el presupuesto, introduce tus datos de pago. ¡Y ya está todo listo! ## Qué hacer a continuación \{#whats-next\} Ahora puedes [explorar cómo la campaña afecta a los ingresos de tu app en el dashboard de Adapty User Acquisition](adapty-user-acquisition). ¿Todavía no usas Adapty User Acquisition? [Reserva una llamada con nosotros](https://calendly.com/tnurutdinov-adapty/30min) para conocer cómo puede ayudarte a hacer seguimiento y optimizar tus campañas publicitarias. --- # File: getting-started-with-server-side-api --- --- title: "API del lado del servidor" description: "Empieza a usar la API del lado del servidor de Adapty para la gestión de suscripciones." --- :::tip ¿Usas un agente de programación con IA? Consulta [Verificar y conceder acceso a suscripciones desde tu backend](server-side-api-with-ai) para ver una guía completa en una sola página. ::: Con la API puedes: 1. Comprobar el estado de la suscripción de un usuario. 2. Activar la suscripción de un usuario con un nivel de acceso. 3. Obtener los atributos del usuario. 4. Establecer los atributos del usuario. 5. Obtener y actualizar configuraciones de paywall. <img src="/assets/shared/img/server.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note Para registrar eventos de suscripción, usa la integración [Webhook](webhook) en Adapty o intégrala directamente con tu servicio existente. ::: ## Caso 1: Sincronizar suscriptores entre web y móvil \{#case-1-sync-subscribers-between-web-and-mobile\} Si utilizas proveedores de pago web como Stripe, ChargeBee u otros, puedes sincronizar a tus suscriptores fácilmente. Así es como funciona: 1. <InlineTooltip tooltip="Asigna un ID único a cada usuario">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) y [Unity](unity-identifying-users)</InlineTooltip>. 2. [Comprueba su estado de suscripción](api-adapty/operations/getProfile) mediante la API. 3. Si el usuario tiene un plan freemium, muéstrale un paywall en tu sitio web. 4. Tras un pago exitoso, [actualiza el estado de la suscripción](api-adapty/operations/setTransaction) en Adapty mediante la API. 5. Tus suscriptores se mantendrán automáticamente sincronizados con tu app móvil. ## Caso 2: Conceder una suscripción \{#case-2-grant-a-subscription\} :::note Por razones de seguridad, no puedes conceder una suscripción a través del SDK. ::: Si vendes a través de tu propia tienda online, Amazon Appstore, Microsoft Store o cualquier otra plataforma que no sea Google Play ni App Store, tendrás que sincronizar esas transacciones con Adapty para proporcionar acceso y registrar la transacción en los análisis. 1. <InlineTooltip tooltip="Asigna un ID único a cada usuario">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), y [Unity](unity-identifying-users)</InlineTooltip>. 2. [Configura una store personalizada para tus productos en el Adapty Dashboard](custom-store). 3. Sincroniza la transacción con Adapty usando la solicitud de API [Set transaction](api-adapty/operations/setTransaction). ## Caso 3: Otorgar un nivel de acceso \{#case-3-grant-an-access-level\} Supongamos que estás ejecutando una promoción que ofrece una prueba gratuita de 7 días y quieres que la experiencia sea coherente en todas las plataformas. Para sincronizarlo con la app móvil: 1. <InlineTooltip tooltip="Asigna un ID único a cada usuario">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), y [Unity](unity-identifying-users)</InlineTooltip>. 2. Usa la API para [otorgar acceso premium](api-adapty/operations/grantAccessLevel) durante 7 días. Después de los 7 días, los usuarios que no se suscriban pasarán al nivel gratuito. ## Caso 4: Sincronizar propiedades y atributos personalizados de los usuarios \{#case-4-sync-users-properties-and-custom-attributes\} Si tienes atributos personalizados para tus usuarios —como el número de palabras aprendidas en una app de aprendizaje de idiomas—, también puedes sincronizarlos. 1. <InlineTooltip tooltip="Asigna un ID único a cada usuario">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), y [Unity](unity-identifying-users)</InlineTooltip>. 2. [Actualiza el atributo](api-adapty/operations/updateProfile) mediante la API o el SDK. Estos atributos personalizados se pueden usar para crear segmentos y ejecutar pruebas A/B. ## Caso 5: Gestionar configuraciones de paywall \{#case-5-manage-paywall-configurations\} Puedes [actualizar Remote Configs en paywalls](api-adapty/operations/updatePaywall) para ajustar dinámicamente el aspecto y comportamiento de tu paywall sin necesidad de redesplegar tu app. --- **Próximos pasos:** - Continúa con la [autorización para la API del lado del servidor](ss-authorization) - Solicitudes: - [Obtener perfil](api-adapty/operations/getProfile) - [Crear perfil](api-adapty/operations/createProfile) - [Actualizar perfil](api-adapty/operations/updateProfile) - [Eliminar perfil](api-adapty/operations/deleteProfile) - [Conceder nivel de acceso](api-adapty/operations/grantAccessLevel) - [Revocar nivel de acceso](api-adapty/operations/revokeAccessLevel) - [Establecer transacción](api-adapty/operations/setTransaction) - [Validar compra, proporcionar nivel de acceso al cliente e importar su historial de transacciones](api-adapty/operations/validateStripePurchase) - [Añadir identificadores de integración](api-adapty/operations/setIntegrationIdentifiers) - [Obtener paywall](api-adapty/operations/getPaywall) - [Listar paywalls](api-adapty/operations/listPaywalls) - [Actualizar paywall](api-adapty/operations/updatePaywall) --- # File: onboardings --- --- title: "Onboardings" --- :::warning El editor de onboarding sin código es completamente funcional, pero Adapty ya no añade funcionalidades ni lanza actualizaciones para él. Para proyectos nuevos, considera usar el [Adapty Flow Builder](adapty-flow-builder) — un editor visual sin código para paywalls de una sola pantalla y flows de onboarding multipantalla que se renderizan de forma nativa en el dispositivo: - **Cualquier tipo de flow**: Crea paywalls de una sola pantalla, onboardings de varios pasos que incluyen un paywall, y cualquier combinación entre ambos. - **Renderizado nativo**: Los flows se renderizan a través del SDK de Adapty, sin web views. - **Actualiza sin redesplegar**: Cambia textos, diseño o lógica en cualquier momento, y las actualizaciones llegan a los usuarios sin necesidad de publicar una nueva versión de la app. ::: Los onboardings de Adapty permiten a equipos no técnicos crear flows de onboarding sin escribir código. El editor sin código genera una serie de pantallas que presentan la app a los usuarios. Puedes personalizar las pantallas con preguntas interactivas y variables, y luego ejecutar pruebas A/B para encontrar el flow con mejor rendimiento. Los onboardings están disponibles para apps que usen el SDK de Adapty v3.8.0+ (iOS, Android, React Native, Flutter), v3.14.0+ (Unity) o v3.15.0+ (Kotlin Multiplatform, Capacitor). ## Cómo funciona \{#how-it-works\} 1. [Diseña un onboarding en el editor sin código.](design-onboarding) 2. [Crea un placement para el onboarding.](create-onboarding#step-2-create-a-placement-for-your-onboarding) 3. Integra el onboarding en tu proyecto con el SDK de Adapty: - [iOS](ios-onboardings) - [Android](android-onboardings) - [React Native](react-native-onboardings) - [Flutter](flutter-onboardings) - [Unity](unity-onboardings) - [Kotlin Multiplatform](kmp-onboardings) - [Capacitor](capacitor-onboardings) 4. Prueba el onboarding y publícalo para tus usuarios. --- # File: create-onboarding --- --- title: "Crear onboarding" --- Los [onboardings](onboardings) presentan a los nuevos usuarios el valor, las funciones y los consejos de uso de tu aplicación móvil. ## Paso 1. Crear un onboarding \{#step-1-create-an-onboarding\} Para crear un nuevo onboarding en el Adapty Dashboard: 1. Ve a **Onboardings** desde el menú principal de Adapty. Esta página muestra una vista general de todos los onboardings que has configurado, junto con sus métricas. Haz clic en **Create onboarding**. <img src="/assets/shared/img/create-onboarding1.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Crea un nombre descriptivo para tu onboarding y haz clic en **Proceed to build onboarding**. <img src="/assets/shared/img/create-onboarding2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Serás redirigido al constructor de onboarding. Contiene una plantilla de demostración predeterminada que puedes estudiar para entender cómo los onboardings recopilan datos y cómo puedes personalizarlos usando variables y cuestionarios. Siéntete libre de eliminar las pantallas que no necesites y [diseña tu propia experiencia de onboarding](design-onboarding) allí. <img src="/assets/shared/img/create-onboarding3.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Cuando estés listo, haz clic en el botón **Preview** en la parte superior derecha. Completa tu onboarding flow tú mismo para asegurarte de que todo funciona correctamente. <img src="/assets/shared/img/create-onboarding4.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Si todo funciona bien, haz clic en **Publish** en la parte superior derecha. Espera a que se publique antes de volver a Adapty. De lo contrario, perderás tu progreso. :::danger Si no haces clic en **Publish**, el SDK no podrá obtener el onboarding que has creado. ::: <img src="/assets/shared/img/create-onboarding5.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Una vez publicado el onboarding, haz clic en **Back to Adapty**. Tu onboarding está creado y puedes añadirlo a un placement para empezar a usarlo. ## Paso 2. Crea un placement para tu onboarding \{#step-2-create-a-placement-for-your-onboarding\} 1. Ve a **Placements** desde el menú principal y cambia a la pestaña **Onboardings**. Haz clic en **Create placement**. <img src="/assets/shared/img/create-onboarding6.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Introduce el nombre y el ID del placement. Luego, haz clic en **Run onboarding** y selecciona el onboarding que verán todos los usuarios. 3. Si tienes un onboarding preparado para un grupo específico de usuarios, [añade más audiencias](audience) y selecciona un onboarding diferente para ellas. ## Paso 3. Integra el onboarding en tu app \{#step-3-integrate-the-onboarding-into-your-app\} :::important Los onboardings están disponibles para apps que usen el SDK de Adapty v3.8.0+ (iOS, Android, React Native, Flutter), v3.14.0+ (Unity) o v3.15.0+ (Kotlin Multiplatform, Capacitor). ::: Para empezar a mostrar onboardings en tu app, intégralos mediante el SDK de Adapty: - [iOS](ios-onboardings) - [Android](android-onboardings) - [React Native](react-native-onboardings) - [Flutter](flutter-onboardings) - [Unity](unity-onboardings) - [Kotlin Multiplatform](kmp-onboardings) - [Capacitor](capacitor-onboardings) Para entender qué onboarding funciona mejor, también puedes ejecutar [pruebas A/B](ab-tests). --- # File: design-onboarding --- --- title: "Diseñar onboardings" description: "Crea onboardings significativos." --- El onboarding builder sin código para aplicaciones móviles es una herramienta potente y personalizable que te ayudará a ofrecer a tus usuarios la mejor experiencia de onboarding. No necesitas ser desarrollador ni diseñador para obtener un gran resultado. ## Pantallas del onboarding \{#onboarding-screens\} El flow de onboarding consta de varias pantallas que añades y diseñas. Los usuarios navegarán entre ellas tocando el botón. :::tip Si algunos de tus usuarios necesitan un flow ligeramente diferente (por ejemplo, en una app de fitness podrías querer mostrar distintas imágenes de 'objetivo' según el género del usuario), no hace falta que crees onboardings separados. En su lugar, puedes ocultar algunas pantallas por defecto y mostrarlas solo en determinados escenarios. ::: ## Elementos del onboarding \{#onboarding-elements\} Los elementos del onboarding se muestran a la izquierda en el orden en que aparecen. Haz clic en **Add** arriba a la derecha para añadir un nuevo elemento. Estos son los grupos de elementos disponibles: - **Containers**: Los contenedores permiten configurar un diseño flexible. Por ejemplo, si quieres añadir texto en dos columnas, añade **Columns** y arrastra dos bloques de texto dentro de **Columns** en el panel izquierdo. O, si añades un carrusel, tendrás que agregar imágenes a los elementos **Media** que contiene. - **Typography**: Añade bloques de texto preformateados y configura su apariencia según tus necesidades. - **Media & Display**: Además de imágenes y vídeos, puedes añadir gráficos animados que muestren el valor de tu app y animen a los usuarios. Los **formatos de vídeo compatibles** son MP4 y WebM. El **tamaño máximo de archivo multimedia** es de 15 MB. Si quieres añadir un elemento animado no compatible (como Lottie), puedes convertirlo a vídeo (por ejemplo, con [esta herramienta](https://www.lottielab.com/lottie/lottie-to-video)) e insertarlo como vídeo. - **Quiz**: Crea cuestionarios breves con opciones de texto e imagen para personalizar la experiencia de onboarding y conocer mejor a tus usuarios. - **Inputs**: Recoge datos de tus usuarios. - **Buttons**: Los botones permiten a los usuarios navegar entre pantallas, cerrar el onboarding o ir al paywall. También puedes añadir botones brillantes o animados para captar la atención del usuario y convertir su instalación en una compra. - **Loaders**: Los loaders animados mantienen a los usuarios entretenidos durante el proceso. - **User engagement**: Añade testimonios, listas de correos de usuarios y cuenta atrás. :::note Como parte del grupo **Media & Display**, también puedes añadir código HTML personalizado si las opciones de personalización disponibles no son suficientes. Sin embargo, los elementos HTML personalizados no se precargan ni se cachean, por lo que se recomienda usar **Raw HTML** solo para elementos pequeños y ligeros. ::: <img src="/assets/shared/img/design-onboarding4.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### ID de elemento e ID de acción \{#element-id-and-action-id\} Si quieres usar un botón para acciones personalizadas, asígnale un **action ID** y úsalo en tu código fuente. Los action ID permiten gestionar de la misma manera distintos botones que comparten el mismo ID. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Si quieres procesar la entrada del usuario en un campo concreto (por ejemplo, guardar su edad o correo electrónico), asígnale un **element ID** y úsalo en tu código fuente para asociar preguntas con respuestas. Los element ID solo pueden usarse una vez en tu onboarding. <img src="/assets/shared/img/design-onboarding5.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Opciones de personalización \{#customization-options\} El builder ofrece las siguientes opciones de personalización: - Pestaña **Styles**: Ajusta la apariencia del elemento. <img src="/assets/shared/img/design-onboarding1.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - Pestaña **Element**: Configura los atributos del elemento, como la visibilidad, las acciones al pulsar botones u otras propiedades no relacionadas con su apariencia. <img src="/assets/shared/img/design-onboarding2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - Pestaña **Screen**: Configura los ajustes generales de la pantalla, como una cabecera o la visualización de un contador de pantallas. <img src="/assets/shared/img/design-onboarding3.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Copiar pantallas y elementos \{#copy-screens-and-elements\} Si has creado un onboarding y quieres reutilizar partes del mismo, o si deseas hacer pequeños cambios y ejecutar pruebas A/B, puedes copiar una o más pantallas de un onboarding a otro. Para copiar pantallas, abre el onboarding builder y realiza una de estas acciones: - Haz clic derecho en una pantalla y selecciona **Copy** - Selecciona la pantalla que quieres y pulsa `Ctrl+C` (Windows) o `⌘+C` (Mac) También puedes copiar elementos individuales o bloques de texto, ya sea dentro del mismo onboarding o entre onboardings distintos. ## Copiar pantallas desde funnels web-to-app \{#copy-screens-from-web-to-app-funnels\} Si usas funnels web-to-app creados en [FunnelFox](https://funnelfox.com/) y quieres utilizar pantallas de esos funnels en onboardings, puedes hacerlo fácilmente copiando las pantallas en el funnel builder y pegándolas en el onboarding builder: 1. En el funnel builder de FunnelFox, haz clic derecho en una pantalla y selecciona **Copy**, o selecciónala y pulsa `Ctrl+C`/`⌘+C`. 2. Abre el onboarding builder. 3. Haz clic derecho en la pantalla donde quieres insertar la pantalla copiada y selecciona **Paste**, o selecciónala y pulsa `Ctrl+V`/`⌘+V`. La pantalla copiada se insertará debajo de la pantalla seleccionada. <img src="/assets/shared/img/funnel-to-onboarding.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: adapty-paywall-builder --- --- title: "Adapty Paywall Builder (Legacy)" description: "Crea paywalls y flows de onboarding con el constructor visual sin código." --- :::warning El Paywall Builder es completamente funcional, pero Adapty ya no añade nuevas funciones ni publica actualizaciones para él. Para nuevos proyectos, considera el [Adapty Flow Builder](adapty-flow-builder) — un editor visual sin código para paywalls de una sola pantalla y flows de onboarding multipantalla que se renderizan de forma nativa en el dispositivo: - **Cualquier tipo de flow**: Crea paywalls de una sola pantalla, onboardings de varios pasos que incluyen un paywall, y cualquier cosa intermedia. - **Renderizado nativo**: Los flows se renderizan a través del SDK de Adapty, sin web views. - **Actualiza sin redesplegar**: Cambia textos, diseño o lógica en cualquier momento, y los cambios llegan a los usuarios sin necesidad de publicar una nueva versión de la app. ::: El **Paywall Builder** de Adapty es una herramienta visual sin código para diseñar paywalls personalizados. Puedes empezar desde una plantilla, personalizar el diseño y añadir elementos como carruseles, tarjetas, listas de productos y pies de página. El builder también admite fuentes personalizadas, etiquetas de productos y localización. El Paywall Builder requiere el SDK de Adapty v3.0 o posterior. Una vez que hayas diseñado un paywall, [añádelo a un placement](add-audience-paywall-ab-test) y muéstralo en tu app: - [iOS](ios-quickstart-paywalls) - [Android](android-quickstart-paywalls) - [React Native](react-native-quickstart-paywalls) - [Flutter](flutter-quickstart-paywalls) - [Unity](unity-quickstart-paywalls) - [Capacitor](capacitor-quickstart-paywalls) - [Kotlin Multiplatform](kmp-quickstart-paywalls) --- # File: flutterflow --- --- title: "Plugin de Adapty para FlutterFlow" description: "Integra FlutterFlow con Adapty para una gestión de suscripciones mejorada." --- Adapty es una plataforma versátil diseñada para ayudar a las apps móviles a crecer. Tanto si estás empezando como si ya tienes miles de usuarios, Adapty te permite ahorrar meses en la integración de compras in-app y duplicar los ingresos por suscripciones con la gestión de paywalls. El plugin de Adapty para FlutterFlow te permite aprovechar todas las funciones de Adapty sin escribir ni una línea de código. Puedes diseñar páginas de paywall en FlutterFlow, habilitar las compras en ellas y luego controlar de forma remota qué productos se muestran, incluyendo la segmentación por grupos de usuarios o las pruebas A/B. Y una vez que publiques tu app, tendrás acceso inmediato a analíticas detalladas de las compras de tus clientes directamente en nuestro dashboard. ¿Quieres actualizar los productos disponibles en tu paywall? ¡Es muy sencillo! Haz los cambios en unos pocos clics dentro del Adapty Dashboard y tus clientes verán los nuevos productos de inmediato, sin necesidad de publicar una nueva versión de la app. Qué más te ofrece Adapty: - **Suscripciones y compras in-app**: Adapty se encarga de la validación de recibos en el servidor y sincroniza a tus clientes en todas las plataformas, incluida la web. - **Pruebas A/B para paywalls**: Prueba diferentes precios, duraciones, períodos de prueba y elementos visuales para optimizar tus ofertas de suscripción y de compra única. - **Analíticas potentes**: Accede a métricas detalladas para entender mejor la monetización de tu app y mejorarla. - **Integraciones**: Adapty se conecta sin problemas con herramientas de analítica de terceros como Amplitude, AppsFlyer, Adjust, Branch, Mixpanel, Facebook Ads, AppMetrica, Webhooks personalizados y mucho más. --- # File: ff-getting-started --- --- title: "Primeros pasos" description: "Empieza con los Feature Flags de Adapty para personalizar los flujos de suscripción." --- Con Adapty puedes crear y ejecutar paywalls y pruebas A/B en distintos momentos del recorrido del usuario en tu app móvil, como en el onboarding, en los ajustes, etc. Estos puntos se llaman [Placements](placements). Un placement en tu app puede gestionar varios paywalls o [pruebas A/B](ab-tests) a la vez, cada uno diseñado para un grupo concreto de usuarios, lo que denominamos [Audiencias](audience). Además, puedes experimentar con los paywalls, sustituyendo uno por otro a lo largo del tiempo sin publicar una nueva versión de la app. Lo único que tienes que escribir directamente en el código de la app es el ID del placement. <img src="/assets/shared/img/audience.jpg" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> La biblioteca de Adapty mantiene tu paywall actualizado con los últimos productos de tu Adapty Dashboard. [Obtiene los datos de los productos](ff-action-flow) y [los muestra en tu paywall](ff-add-variables-to-paywalls), [gestiona las compras](ff-make-purchase) y [comprueba el nivel de acceso del usuario](ff-check-subscription-status) para determinar si debe recibir contenido de pago. Para empezar, solo tienes que [añadir la biblioteca de Adapty](ff-getting-started#add-the-adapty-library-as-a-dependency) a tu proyecto de FlutterFlow e [inicializarla](ff-getting-started#initiate-adapty-plugin) como se muestra a continuación. :::warning Antes de comenzar, ten en cuenta las siguientes limitaciones: - La biblioteca de Adapty para FlutterFlow no es compatible con aplicaciones web. Evita compilar aplicaciones web con ella. - La biblioteca de Adapty para FlutterFlow no es compatible con paywalls creados con el Paywall Builder de Adapty. Tienes que diseñar tu propio paywall en FlutterFlow antes de habilitar las compras con Adapty. ::: ## Añadir la biblioteca de Adapty como dependencia \{#add-the-adapty-library-as-a-dependency\} 1. En el [FlutterFlow Dashboard](https://app.flutterflow.io/dashboard), abre tu proyecto y haz clic en **Settings and Integrations** en el menú de la izquierda. En la sección **Project setup** a la izquierda, selecciona **Project dependencies**. <img src="/assets/shared/img/main_settings.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. En la sección **FlutterFlow Libraries**, haz clic en **Add Library** e introduce `adapty-xtuel0`. Haz clic en **Add**. 3. Ahora debes asociar tu clave SDK con la biblioteca. Haz clic en **View details** junto a la biblioteca. <img src="/assets/shared/img/ff_view_details.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Copia la **Public SDK key** desde la pestaña [**App Settings** -> **General**](https://app.adapty.io/settings/general) en el Adapty Dashboard. <img src="/assets/shared/FF_img/adaptyapikey.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Pega la clave en **AdaptyApiKey** en FlutterFlow. <img src="/assets/shared/img/ff_apikey.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> La biblioteca de Adapty FF se añadirá como dependencia a tu proyecto. En la ventana de la biblioteca **Adapty** FF encontrarás todos los recursos de Adapty que se han importado a tu proyecto. ## Llamar a la nueva acción de activación al iniciar la aplicación \{#call-the-new-activation-action-at-application-launch\} 1. Ve a la sección **Custom Code** en el menú de la izquierda y abre `main.dart`. <img src="/assets/shared/img/ff_dartmain.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en **+** y selecciona `activate (Adapty)`. <img src="/assets/shared/img/ff_activate.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Save**. ## Inicializar el plugin de Adapty \{#initiate-adapty-plugin\} Para que el Adapty Dashboard reconozca tu app, deberás proporcionar una clave especial en FlutterFlow. 1. En tu proyecto de FlutterFlow, ve a **Settings and Integrations > Permissions** en el menú de la izquierda. 2. En la ventana **Permissions** que se abre, haz clic en el botón **Add Permission**. 3. En los campos **iOS Permission Key** y **Android Permission Key**, pega `AdaptyPublicSdkKey`. 4. Para el campo **Permission Message**, copia la **Public SDK key** desde la pestaña [**App Settings** -> **General**](https://app.adapty.io/settings/general) en el Adapty Dashboard. Cada app tiene su propia clave SDK, así que si tienes varias apps, asegúrate de coger la correcta. <img src="/assets/shared/img/ff_permissions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Tras completar estos pasos, podrás mostrar tu paywall en tu app de FlutterFlow y habilitar las compras a través de él. ## ¿Qué sigue? \{#whats-next\} 1. [Crea un flujo de acción](ff-action-flow) para gestionar los productos del paywall de Adapty y sus datos en FlutterFlow. 2. [Mapea los datos recibidos en el paywall](ff-add-variables-to-paywalls) que diseñaste en FlutterFlow. 3. [Configura el botón de compra](ff-make-purchase) en tu paywall para procesar las transacciones a través de Adapty cuando se pulse. 4. Por último, [añade comprobaciones del estado de la suscripción](ff-check-subscription-status) para determinar si mostrar contenido de pago al usuario. --- # File: ff-action-flow --- --- title: "Paso 1. Crear el flujo para mostrar los datos del paywall" description: "Configura los flujos de acción de feature flags en Adapty para personalizar los journeys de suscripción de los usuarios." --- :::important Al usar el plugin de FlutterFlow, no puedes usar paywalls creados en el Paywall Builder de Adapty. Debes implementar tu propia página de paywall en FlutterFlow y conectarla a Adapty. ::: Después de añadir la librería de Adapty como dependencia a tu proyecto de FlutterFlow, es momento de construir el flujo que **recupera los datos del paywall y los productos de Adapty, y los muestra en el paywall que has diseñado en FlutterFlow**. Primero necesitamos recibir los datos del paywall desde Adapty. Empezaremos solicitando el paywall de Adapty, luego sus productos asociados, y finalmente comprobaremos si los datos se recibieron correctamente. Si es así, mostraremos el título y el precio del producto en la página del paywall. En caso contrario, mostraremos un mensaje de error. Antes de continuar, asegúrate de haber hecho lo siguiente: 1. [Crear al menos un paywall y añadir al menos un producto](create-paywall) en el Adapty Dashboard. 2. [Crear al menos un placement](create-placement) y [añadir tu paywall](add-audience-paywall-ab-test) en el Adapty Dashboard. ¡Empecemos! ## Paso 1.1. Solicitar el paywall de Adapty \{#step-11-request-adapty-paywall\} Como se mencionó, para mostrar datos en tu paywall de FlutterFlow, primero necesitamos recuperarlos desde Adapty. El primer paso es obtener el paywall de Adapty. Así se hace: 1. Abre tu pantalla de paywall y cambia a la sección **Actions** en el panel derecho. Ahí, abre el **Action Flow Editor**. <img src="/assets/shared/img/ff_action_flow.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. En la ventana **Select Action Trigger**, selecciona **On Page Load**. <img src="/assets/shared/img/ff_action_trigger.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Add Action**. Luego, busca la acción personalizada `getPaywall` y selecciónala. <img src="/assets/shared/img/ff_getpaywall.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. En la sección **Set Actions Arguments**, introduce el ID real del [placement que has creado](create-placement) en el Adapty Dashboard que incluye el paywall. En este ejemplo es `monthly`. ¡Asegúrate de usar tu ID de placement real! <img src="/assets/shared/img/ff_placementid.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Si has [localizado](localizations-and-locale-codes) tu paywall en el dashboard de Adapty, también puedes configurar el argumento **locale**. 6. En **Action Output Variable Name**, crea una nueva variable y nómbrala `getPaywallResult`. La usaremos en el siguiente paso para referenciar el paywall de Adapty y solicitar sus productos. <img src="/assets/shared/img/ff_getpaywallresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 1.2. Solicitar los productos del paywall de Adapty \{#step-12-request-adapty-paywall-products\} ¡Genial! Ya hemos recuperado el paywall de Adapty. Ahora, obtengamos los productos asociados a este paywall: 1. Haz clic en **+** bajo la acción creada y selecciona **Add Action**. Esta acción recibirá los productos del paywall de Adapty. Para ello, busca y selecciona `getPaywallProducts`. 2. En la sección **Set Actions Arguments**, selecciona la variable `getPaywallResult` creada anteriormente. <img src="/assets/shared/img/ff_getpaywallproduct.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Rellena los demás campos de la siguiente manera: - **Available Options**: Data Structured Field - **Select Field**: value - **Available Options**: No further changes <img src="/assets/shared/img/ff_getpaywallresult2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Haz clic en **Confirm**. 5. En **Action Output Variable Name**, crea una nueva variable y nómbrala `getPaywallProductsResult`. La usaremos para vincular el paywall que diseñaste en FlutterFlow con los datos del paywall de Adapty. <img src="/assets/shared/img/ff_getpaywallproductsresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 1.3. Añadir comprobación de si el paywall se cargó correctamente \{#step-13-add-check-if-the-paywall-uploaded-successfully\} Antes de continuar, verifiquemos que el paywall de Adapty se recibió correctamente. Si es así, podemos actualizar el paywall con los datos de los productos. Si no, gestionaremos el error. Así se añade la comprobación: 1. Haz clic en **+** y luego en **Add Conditional**. <img src="/assets/shared/img/ff-add-conditional.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. En la sección **Action Output**, selecciona la variable de salida de acción creada anteriormente (`getPaywallResult` en nuestro ejemplo). <img src="/assets/shared/img/ff-getpaywallresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Para verificar que el paywall de Adapty se recibió, comprueba la presencia de un campo con valor. Rellena los campos de la siguiente manera: - **Available Options**: Has Field - **Field (AdaptyGetPaywallResult)**: value 4. Haz clic en **Confirm** para finalizar la condición. ## Paso 1.4. Registrar la visualización del paywall \{#step-14-log-the-paywall-review\} Para asegurarte de que los análisis de Adapty registran la visualización del paywall, necesitamos registrar este evento. Sin este paso, la visualización no se contabilizará en los análisis. Así se hace: 1. Haz clic en **+** bajo la etiqueta **TRUE** y haz clic en **Add Action**. 2. En el campo **Select Action**, busca y elige **logShowPaywall**. <img src="/assets/shared/img/ff-logshowpaywall.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Value** en el área **Set Action Arguments** y elige la variable `getPaywallResult` que hemos creado. Esta variable contiene los datos del paywall. 4. Rellena los campos de la siguiente manera: - **Available Options**: Data Structured Field - **Select Field**: value 5. Haz clic en **Confirm**. <img src="/assets/shared/img/ff-lohsgowpaywallresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 1.5. Mostrar error si el paywall no se recibe \{#step-15-show-error-if-paywall-not-received\} Si el paywall de Adapty no se recibe, necesitas [gestionar el error](error-handling-on-flutter-react-native-unity#system-storekit-codes). En este ejemplo, simplemente mostraremos un mensaje de alerta. 1. Añade una acción **Informational Dialog** a la etiqueta **FALSE**. 2. En el campo **Title**, añade el texto que quieras ver como título del diálogo. En este ejemplo, es **Error**. 3. Haz clic en **Value** en el cuadro **Message**. 4. Rellena los campos de la siguiente manera: - **Set Variable**: variable `getPaywallProductResult` que hemos creado - **Available Options**: Data Structure Field - **Select Field**: error - **Available Options**: Data Structure Field - **Select Field**: errorMessage <img src="/assets/shared/img/ff-error.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en **Confirm**. 6. Añade una acción **Terminate action** al flujo **FALSE**. <img src="/assets/shared/img/ff-terminate.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Haz clic en **Close** en la esquina superior derecha. ¡Felicidades! Has recibido correctamente los datos del producto. Ahora, [vincúlalos al paywall que has diseñado en FlutterFlow](ff-add-variables-to-paywalls). --- # File: ff-add-variables-to-paywalls --- --- title: "Paso 2. Añadir datos a la página del paywall" description: "Añade variables de Feature Flag a los paywalls en Adapty." --- Una vez que hayas [recibido todos los datos de producto necesarios](ff-action-flow), es hora de mapearlos al bonito paywall que diseñaste en FlutterFlow. En este ejemplo, mapearemos el título del producto y su precio. ## Paso 2.1. Añadir el título del producto a la página del paywall \{#step-21-add-product-title-to-paywall-page\} 1. Haz doble clic en el texto del producto en tu página del paywall. En la ventana **Set from Variable**, busca la variable `getPaywallProductResult` y selecciónala. <img src="/assets/shared/img/ff-paywall-text.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Rellena los campos de la siguiente manera: - **Available Options**: Data Structured Field - **Select Field**: value - **Available Options**: Item at Index - **List Index Options**: First - **Available Options**: Data Structured Field - **Select Field**: localizedTitle - **Default Variable Value**: null - **UI Builder Display Value**: Cualquier valor; en el ejemplo es `product.title` <img src="/assets/shared/img/ff-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Confirm** para guardar los cambios. ## Paso 2.2. Añadir el texto del precio a la página del paywall \{#step-22-add-price-text-to-paywall-page\} Repite los pasos del Paso 2.1 para el texto del precio como se muestra a continuación: 1. Haz doble clic en el texto del precio en tu página del paywall. En la ventana **Set from Variable**, busca la variable `getPaywallProductResult` y selecciónala. <img src="/assets/shared/img/ff-price.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Rellena los campos de la siguiente manera: - **Available Options**: Data Structured Field - **Select Field**: value - **Available Options**: Item at Index - **List Index Options**: First - **Available Options**: Data Structured Field - **Select Field**: price - **Default Variable Value**: null - **UI Builder Display Value**: Cualquier valor; en el ejemplo es `product.price` 3. Haz clic en el botón **Confirm** para guardar los cambios. ### Añadir el precio en moneda local a la página del paywall \{#add-price-in-local-currency-to-paywall-page\} 1. Haz doble clic en el precio en tu página del paywall. En la ventana **Set from Variable**, busca la variable `getPaywallProductResult` y selecciónala. 2. Rellena los campos de la siguiente manera: - **Available Options**: Data Structured Field - **Select Field**: value - **Available Options**: Item at Index - **List Index Options**: First - **Available Options**: Data Structured Field - **Select Field**: price - **Available Options**: Data Structured Field - **Select Field**: amount - **Available Options**: Decimal - **Decimal Type**: Automatic - **Default Variable Value**: null - **UI Builder Display Value**: Cualquier valor; en el ejemplo es `price.amount` 3. Haz clic en **Confirm** para guardar los cambios. ¡Y voilà! Ahora, al lanzar tu app, mostrará los datos del producto del paywall de Adapty directamente en tu página del paywall. Es hora de [permitir que tus usuarios compren este producto](ff-make-purchase). --- # File: ff-make-purchase --- --- title: "Paso 3. Habilitar la compra" description: "Aprende a realizar compras usando el sistema de Feature Flags de Adapty." --- ¡Enhorabuena! Ya has [configurado tu paywall para mostrar datos de productos de Adapty](ff-add-variables-to-paywalls), incluyendo el título y el precio del producto. Ahora vamos al paso final: permitir que los usuarios realicen una compra a través del paywall. ## Paso 3.1. Permitir que los usuarios realicen compras \{#step-31-enable-users-to-make-purchases\} 1. Haz doble clic en el botón de compra de tu página del paywall. En el panel derecho, abre la sección **Actions** si no está ya abierta. 2. Abre el **Action Flow Editor**. <img src="/assets/shared/img/ff-action-flow-editor.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la ventana **Select Action Trigger**, elige **On Tap**. 4. En la ventana **No Actions Created**, haz clic en **Add Action**. Busca la acción `makePurchase` y selecciónala. <img src="/assets/shared/img/ff-makepurchase.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. En la sección **Set Actions Arguments**, selecciona la variable `getPaywallProductsResult` creada anteriormente. 6. Rellena los campos de la siguiente manera: - **Available Options**: Data Structure Field - **Select Field**: value - **Available Options**: Item at Index - **List Index Options**: First <img src="/assets/shared/img/ff-makepurchase-value.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Haz clic en `subscriptionUpdateParameters`, busca `AdaptySubscriptionUpdateParameters` y selecciónalo. Haz clic en **Confirm**. :::info Por defecto, puedes dejar todos los campos del objeto vacíos. Necesitarás rellenarlos para reemplazar una suscripción por otra en apps de Android. Lee más [aquí](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/). ::: <img src="/assets/shared/img/ff-subupdate.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. Haz clic en **Confirm**. 9. En **Action Output Variable Name**, crea una nueva variable y nómbrala `makePurchaseResult`; se usará más adelante para confirmar que la compra fue exitosa. <img src="/assets/shared/img/ff-makepurchaseresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 3.2. Comprobar si la compra fue exitosa \{#step-32-check-if-the-purchase-was-successful\} Ahora configuremos una comprobación para ver si la compra se realizó correctamente. 1. Haz clic en **+** y luego en **Add Conditional**. 2. En **Set Condition for Action**, selecciona la variable `makePurchaseResult`. 3. En la ventana **Set Variable**, rellena los campos de la siguiente manera: - **Available Options**: Has Field - **Select Field**: profile <img src="/assets/shared/img/ff-makepurchaseresult-conditional.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Haz clic en **Confirm**. ## Paso 3.3. Abrir el contenido de pago \{#step-33-open-paid-content\} Si la compra es exitosa, puedes desbloquear el contenido de pago. Aquí te explicamos cómo configurarlo: 1. Haz clic en **+** bajo la etiqueta **TRUE** y haz clic en **Add Action**. 2. En el campo **Define Action**, busca y selecciona la página que quieres abrir en la lista **Navigate To**. En este ejemplo, la página es **Questions**. <img src="/assets/shared/img/ff-questions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 3.4. Mostrar un mensaje de error si la compra falla \{#step-34-show-error-message-if-purchase-failed\} Si la compra falla, vamos a mostrar una alerta al usuario. 1. Añade una acción **Informational Dialog** a la etiqueta **FALSE**. 2. En el campo **Title**, introduce el texto que quieras para el título del diálogo, como **Purchase Failed**. <img src="/assets/shared/img/ff-purchase-fail.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Haz clic en **Value** en el cuadro **Message**. En la ventana **Set from Variable**, busca `makePurchaseResult` y selecciónalo. Rellena los campos de la siguiente manera: - **Available Options**: Data Structure Field - **Select Field**: error - **Available Options**: Data Structure Field - **Select Field**: errorMessage <img src="/assets/shared/img/ff-fail-message.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Haz clic en **Confirm**. 5. Añade una acción **Terminate** al flujo **FALSE**. <img src="/assets/shared/img/ff-terminate-purchase.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Por último, haz clic en **Close** en la esquina superior derecha. ¡Enhorabuena! Tus usuarios ya pueden comprar tus productos. Como paso adicional, vamos a [configurar una comprobación del acceso del usuario al contenido de pago](ff-check-subscription-status) en otros puntos de la app para decidir si mostrarles el contenido de pago o el paywall. --- # File: ff-check-subscription-status --- --- title: "Paso 4. Verificar el acceso al contenido de pago" description: "Aprende a verificar el estado de la suscripción usando los feature flags de Adapty para una mejor segmentación de usuarios." --- Para determinar si un usuario tiene acceso a contenido de pago específico, debes verificar su nivel de acceso. Esto implica comprobar si el usuario tiene al menos un nivel de acceso y si ese nivel es el requerido. Puedes hacerlo consultando el perfil del usuario, que contiene todos los niveles de acceso disponibles. Ahora, vamos a permitir que los usuarios compren tu producto: 1. Haz doble clic en el botón que debe mostrar el contenido de pago y abre la sección **Actions** en el panel derecho si no está ya abierta. 2. Abre el **Action Flow Editor**. <img src="/assets/shared/img/ff-open-paid-content.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. En la ventana **Select Action Trigger**, elige **On Tap**. 4. En la ventana **No Actions Created**, haz clic en el botón **Add Conditional Action**. 5. Haz clic en **UNSET** para establecer los argumentos de la acción y elige la variable `currentProfile`. Esta es la variable de Adapty que almacena los datos del perfil del usuario actual. <img src="/assets/shared/img/ff-currentprofile.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Rellena los campos de la siguiente manera: - **Available Options**: Data Structure Field - **Select Field**: accessLevels - **Available Options**: Filter List Items - **Filter Conditions**: 1. Selecciona **Conditions -> Single Condition** y haz clic en **UNSET**. 2. En el campo **First value**, selecciona **Item in list** como **Source** y rellena los campos así: - **Available Options**: Data Structure Field - **Select Field**: accessLevelIdentifier 3. Establece el operador de filtro en **Equal to**. 4. Haz clic en **UNSET** junto a **Second value** y en el campo **Value**, introduce el ID de tu nivel de acceso; en nuestro ejemplo usamos `premium`. <img src="/assets/shared/img/ff-filter.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Haz clic en **Confirm** y continúa rellenando los demás campos. - **Available Options**: Item at Index - **List Index Options**: First - **Available Options**: Data Structure Field - **Select Field**: accessLevel - **Available Options**: Data Structure Field - **Select Field**: isActive 7. Haz clic en **Confirm**. Ahora, añade las acciones para lo que ocurre a continuación: si el usuario tiene la suscripción correcta o no. Llévalo a la página disponible para suscriptores premium o abre la página del paywall para que pueda comprar el acceso. --- # File: ff-resources --- --- title: "Acciones y tipos de datos del plugin FlutterFlow de Adapty" description: "Accede a los recursos de indicadores de funciones de Adapty para agilizar las funciones basadas en suscripciones." --- ## Acciones personalizadas \{#custom-actions\} A continuación se muestran los métodos de Adapty disponibles en FlutterFlow con el plugin de Adapty. Pueden usarse como acciones personalizadas en FlutterFlow. | Custom Action | Description | Action Arguments | Adapty Data Types - Action Output Variable | |---|----|--------|----| | activate | Inicializa el SDK de Adapty | None || | <p id="getPaywall">getPaywall</p> | Obtiene un paywall. No devuelve los productos del paywall. Usa la acción `getPaywallProducts` para obtener los productos reales | <ul><li>[Placement_ID](placements)</li><li>[Locale](localizations-and-locale-codes)</li></ul> | [AdaptyGetPaywallResult](ff-resources#adaptygetpaywallresult)| | <p id="getPaywallProducts">getPaywallProducts</p> | Devuelve una lista de los productos reales del paywall | [AdaptyPaywall](ff-resources#adaptypaywall) | [AdaptyGetProductsResult](ff-resources#adaptygetproductsresult) | | <p id="getproductsintroductoryoffereligibility">getProductsIntroductoryOfferEligibility</p> | Comprueba si el usuario cumple los requisitos para una oferta introductoria de suscripción iOS | [AdaptyPaywallProduct](product) | [AdaptyGetIntroEligibilitiesResult](ff-resources#adaptygetintroeligibilitiesresult) | | <p id="makePurchase">makePurchase</p> | Completa una compra y desbloquea el contenido. Si el paywall tiene una oferta promocional, Adapty la aplica automáticamente en el proceso de pago | <ul><li> **product**: un objeto AdaptyPaywallProduct obtenido del paywall.</li><li> **subscriptionUpdateParams**: un objeto [`AdaptySubscriptionUpdateParameters`](ff-resources#adaptysubscriptionupdateparameters) para actualizar o rebajar una suscripción (usar en Android).</li><li>**isOfferPersonalized**: indica si la oferta está personalizada para el comprador (usar en Android).</li></ul> | [AdaptyMakePurchaseResult](ff-resources#adaptymakepurchaseresult) | | <p id="getprofile">getProfile</p> | <p>Obtiene el perfil del usuario actual de la app. Esto te permite configurar niveles de acceso y otros parámetros</p><p>Si falla (por ejemplo, por falta de conexión), se devolverán los datos en caché. Adapty actualiza regularmente la caché del perfil para mantener la información lo más actualizada posible</p> | None| [AdaptyGetProfileResult](ff-resources#adaptygetprofileresult) | | updateProfile | Modifica atributos opcionales del perfil del usuario actual, como correo electrónico, número de teléfono, etc. Posteriormente puedes usar estos atributos para crear [segmentos](segments) de usuarios o simplemente consultarlos en el CRM | El ID y cualquier parámetro que deba actualizarse en el [AdaptyProfile](ff-resources#adaptyprofile) | [AdaptyError](ff-resources#adaptyerror) (Optional) | | restorePurchases | Restaura todas las compras realizadas por el usuario | None | [AdaptyGetProfileResult](ff-resources#adaptygetprofileresult) | | logShowPaywall | Registra cuándo se muestra un paywall concreto al usuario | [AdaptyPaywall](ff-resources#adaptypaywall) | [AdaptyError](ff-resources#adaptyerror) (Optional) | | identify | Identifica al usuario mediante el `customerUserId` de tu sistema | customerUserId | [AdaptyError](ff-resources#adaptyerror) (Optional) | | logout | Cierra la sesión del usuario actual en tu app | None | [AdaptyError](ff-resources#adaptyerror) (Optional)| | presentCodeRedemptionSheet | Muestra una hoja que permite a los usuarios canjear códigos (solo iOS) | None | None | ## Tipos de datos \{#data-types\} Tipos de datos de Adapty (colecciones de valores de datos) entregados a FlutterFlow con el plugin de Adapty. ### AdaptyAccessLevel Información sobre el [nivel de acceso](access-level) del usuario. | Nombre del campo | Tipo | Descripción | |--------------------------|----------|-------------| | activatedAt | DateTime | La hora en que este nivel de acceso fue activado | | activeIntroductoryOfferType | String | El tipo de oferta introductoria activa. Si está definido, significa que se aplicó una oferta durante este período de suscripción | | activePromotionalOfferId | String | El ID de una oferta promocional activa (comprada desde iOS) | | activePromotionalOfferType | String | El tipo de oferta promocional activa (comprada desde iOS). Si está definido, significa que se aplicó una oferta durante este período de suscripción | | billingIssueDetectedAt | DateTime | La hora en que se detectó un problema de facturación. La suscripción puede seguir activa. Se establece en null si el pago se procesa correctamente | | cancellationReason | String | El motivo por el que se canceló la suscripción | | expiresAt | DateTime | La hora de expiración del nivel de acceso (puede ser en el pasado o no estar definida para el acceso de por vida) | | id | String | El identificador del nivel de acceso | | isActive | Boolean | True si este nivel de acceso está activo. En general, puedes consultar esta propiedad para determinar si un usuario tiene acceso a las funciones premium | | isInGracePeriod | Boolean | True si esta suscripción de renovación automática está en el [período de gracia](https://developer.apple.com/help/app-store-connect/manage-subscriptions/enable-billing-grace-period-for-auto-renewable-subscriptions) | | isLifetime | Boolean | True si este nivel de acceso está activo de por vida (sin fecha de expiración) | | isRefund | Boolean | True si esta compra fue reembolsada | | offerId | String | El ID de una oferta promocional activa (comprada desde Android) | | renewedAt | DateTime | La hora en que el nivel de acceso fue renovado por última vez | | startsAt | DateTime | La hora de inicio de este nivel de acceso (puede ser en el futuro) | | store | String | El store donde se realizó la compra | | unsubscribedAt | DateTime | La hora en que se desactivó la renovación automática de la suscripción. La suscripción puede seguir activa. Si no está definido, el usuario reactivó la suscripción | | vendorProductId | String | El ID del producto en el store que desbloqueó este nivel de acceso | | willRenew | Boolean | True si esta suscripción de renovación automática está configurada para renovarse | ### AdaptyAccessLevelIdentifiers Esta estructura está diseñada para reemplazar el par clave-valor de `Map<String, AdaptyAccessLevel` [AdaptyAccessLevel](ff-resources#adaptyaccesslevel). | Campo | Tipo | Descripción | |-------|------|-------------| | accessLevelIdentifier | String | El ID del nivel de acceso | | accessLevel | Data ([AdaptyAccessLevel](ff-resources#adaptyaccesslevel)) | El [AdaptyAccessLevel](ff-resources#adaptyaccesslevel) asociado | ### AdaptyCustomDoubleAttribute Información sobre los atributos double personalizados definidos para el [usuario](ff-resources#adaptyprofile). | Field Name | Type | Description | |------------|------|-------------| | key | String | El ID del atributo double personalizado | | value | Double | El valor del atributo double personalizado | ### AdaptyCustomStringAttribute Información sobre los atributos de cadena personalizados definidos para el [usuario](ff-resources#adaptyprofile). | Field Name | Type | Description | |------------|------|-------------| | key | String | El ID del atributo de cadena personalizado | | value | String | El valor del atributo de cadena personalizado | ### AdaptyError Contiene detalles sobre un error. Para ver la lista completa de códigos de error, consulta [React Native, Flutter, Unity - Gestión de errores](error-handling-on-flutter-react-native-unity). | Nombre del campo | Tipo | Descripción | |--------------------------|----------|-------------| | errorMessage | String | Descripción legible del error | | errorCode | Integer | Código numérico que identifica el error | ### AdaptyGetIntroEligibilitiesResult Contiene el resultado de la acción personalizada `getProductsIntroductoryOfferEligibility`. | Nombre del campo | Tipo | Descripción | |--------------------------|----------|-------------| | value | List < Data ([AdaptyProductIntroEligibility](ff-resources#adaptyproductintroeligibility)) > | Lista de la elegibilidad del usuario para ofertas promocionales | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | Contiene detalles sobre el error mediante [`AdaptyError`](ff-resources#adaptyerror) | ### AdaptyGetPaywallResult Contiene el resultado de la acción personalizada `getPaywall`. | Nombre del campo | Tipo | Descripción | |--------------------------|----------|-------------| | value | Data ([AdaptyPaywall](ff-resources#adaptypaywall)) | Contiene una lista de objetos [AdaptyPaywall](ff-resources#adaptypaywall) | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | Contiene información del error a través de [AdaptyError](ff-resources#adaptyerror) | ### AdaptyGetProductsResult Contiene el resultado de la acción personalizada `getPaywallProducts`. | Nombre del campo | Tipo | Descripción | |--------------------------|----------|-------------| | value | List < Data ([AdaptyPaywallProduct](product)) > | Contiene una lista de [AdaptyPaywallProducts](product) | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | Contiene información del error a través de [AdaptyError](ff-resources#adaptyerror) | ### AdaptyGetProfileResult Contiene el resultado de la acción personalizada `getProfile`. | Field Name | Type | Description | |--------------------------|----------|-------------| | value | Data ([AdaptyProfile](ff-resources#adaptyprofile)) | Contiene el perfil del usuario como un [AdaptyProfile](ff-resources#adaptyprofile) | | error | Data (AdaptyError) | Contiene información del error mediante [AdaptyError](ff-resources#adaptyerror) | ### AdaptyMakePurchaseResult Contiene el resultado de la acción personalizada `makePurchase`. | Nombre del campo | Tipo | Descripción | |--------------------------|----------|-------------| | value | Data ([AdaptyProfile](ff-resources#adaptyprofile)) | Contiene el perfil del usuario como [AdaptyProfile](ff-resources#adaptyprofile) | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | Contiene información del error mediante [AdaptyError](ff-resources#adaptyerror) | ### AdaptyNonSubscription Información sobre compras que no son suscripciones. Pueden ser productos de una sola compra (consumibles), desbloqueos (como desbloquear un nuevo mapa en el juego), etc. | Campo | Tipo | Descripción | |--------------------------|----------|-------------| | isConsumable | Boolean | Indica si el producto es consumible | | isOneTime | Boolean | Indica si el producto es una compra única (p. ej., si es `true`, la compra se procesa solo una vez) | | isRefund | Boolean | Indica si el producto ha sido reembolsado | | isSandbox | Boolean | Indica si el producto se compró en un entorno sandbox | | purchasedAt | DateTime | La hora en que se compró el producto | | purchaseId | String | El ID de la compra en Adapty. Se puede usar para rastrear productos de compra única | | store | String | El store donde se compró el producto (p. ej., App Store, Google Play) | | vendorProductId | String | ID del producto en el sistema del proveedor | | vendorTransactionId | String | ID de la transacción en el sistema del proveedor | ### AdaptyPaywall Información sobre un [paywall](paywalls). | Nombre del campo | Tipo | Descripción | |----------------------|----------|-------------| | abTestName | String | El nombre de la prueba A/B padre | | hasViewConfiguration | Boolean | Indica si existe una configuración de vista para el paywall | | locale | String | El ID de configuración regional del paywall | | name | String | Nombre del paywall | | placement.id | String | El ID del placement padre | | remoteConfigString | String | Un diccionario personalizado del Adapty Dashboard asociado a este paywall | | placement.revision | Integer | La revisión/versión actual del paywall. Cada cambio genera una nueva revisión | | variationId | String | El ID de variante usado para atribuir las compras a este paywall | | vendorProductIds | String | Array de IDs de productos relacionados con el paywall | ### AdaptyPaywallProduct Información sobre el [producto](product). | Field Name | Type | Description | | -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | vendorProductId | String | El ID de un producto del store | | localizedDescription | String | Una descripción del producto en el idioma del usuario | | localizedTitle | String | El nombre del producto en el idioma del usuario | | regionCode | String | El código de región de la configuración regional usada para formatear el precio del producto (usar en iOS) | | isFamilyShareable | Boolean | Un valor booleano que indica si el producto está disponible para compartir en familia en App Store Connect. Siempre será FALSE en versiones de iOS anteriores a 14.0 y de macOS anteriores a 11.0 (usar en iOS) | | paywallVariationId | String | El ID de una variante, usado para atribuir las compras a este paywall | | paywallABTestName | String | Nombre de la prueba A/B principal | | paywallName | String | Nombre del paywall principal | | price | Data ([AdaptyPriceData](#adaptyprice) | El precio del producto | | subscriptionDetails | Data ([AdaptySubscriptionDetails](#adaptysubscriptiondetails)) | Información sobre la suscripción | ### AdaptyPrice Información sobre el precio del producto. | Field Name | Type | Description | | --------------- | ------ | ---------------------------------------------------- | | amount | Double | El valor numérico del precio | | currencyCode | String | El código de la moneda del precio | | currencySymbol | String | El símbolo utilizado para la moneda | | localizedString | String | El precio mostrado en el idioma del usuario | ### AdaptyProductIntroEligibility Define si el usuario es elegible para una oferta introductoria de una suscripción de iOS. | Field Name | Type | Description | | --------------- | ----------------------------------------------------------- | ------------------------------------------------------------ | | vendorProductId | String | El ID de un producto en una store | | eligibility | [AdaptyEligibilityEnum](ff-resources#adaptyeligibilityenum) | Indica si el usuario es elegible para una oferta introductoria en una suscripción de iOS | ### AdaptyProductNonsubscriptions Detalles de la compra única activa vinculada a este producto. | Field Name | Type | Description | | ---------------- | ----------------------------------------------------------- | ------------------------------------------------------------ | | productId | String | El ID del producto en el store | | nonsubscriptions | [AdaptyNonSubscription](ff-resources#adaptynonsubscription) | Información sobre compras que no son suscripciones. Pueden ser productos de compra única (consumibles), desbloqueos (como desbloquear un mapa nuevo en un juego), etc. | ### AdaptyProductSubscriptions Detalles de la suscripción activa vinculada a este producto. | Field Name | Type | Description | | ------------ | ----------------------------------------------------- | ---------------------------------------- | | productId | String | El ID del producto en un store | | subscription | [AdaptySubscription](ff-resources#adaptysubscription) | Información sobre las compras de suscripción | ### AdaptyProfile Información sobre el perfil del usuario | Field Name | Type | Description | | ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | accessLevels | List < Data ([AdaptyAccessLevelIdentifiers](ff-resources#adaptyaccesslevelidentifiers)) > | Lista de todos los niveles de acceso que pertenecen al usuario | | profileId | String | El ID del perfil del usuario | | customerUserId | String | El ID del usuario en el sistema del proveedor | | subscriptions | List < Data ([MapKeySubscriptions](#mapkeysubscriptions)) > | La lista de todas las suscripciones compradas por el usuario | | nonSubscriptions | List < Data ([MapKeyNonSubscriptions](#mapkeynonsubscriptions)) > | La lista de todos los productos que no son suscripciones comprados por el usuario | ### AdaptyProfileParameters Información sobre el usuario. | Field Name | Type | Description | | ----------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | firstName | String | El nombre del usuario | | lastName | String | El apellido del usuario | | gender | [AdaptyGenderEnum](#adaptygenderenum) | El género del usuario | | birthday | String | La fecha de nacimiento del usuario | | email | String | El correo electrónico del usuario | | phoneNumber | String | El número de teléfono del usuario | | facebookAnonymousId | String | El ID del usuario en la [integración con Facebook Ads](facebook-ads) | | amplitudeUserId | String | El ID del usuario en la [integración con Amplitude](amplitude) | | amplitudeDeviceId | String | El ID del dispositivo del usuario en la [integración con Amplitude](amplitude) | | mixpanelUserId | String | El ID del usuario en la [integración con Mixpanel](mixpanel) | | appmetricaProfileId | String | El ID del usuario en la [integración con AppMetrica](appmetrica) | | appmetricaDeviceId | String | El ID del dispositivo del usuario en la [integración con AppMetrica](appmetrica) | | oneSignalPlayerId | String | El ID del usuario en la [integración con OneSignal](onesignal) | | pushwooshHWID | String | El ID del dispositivo del usuario en la [integración con Pushwoosh](pushwoosh) | | firebaseAppInstanceId | String | El ID del usuario en la [integración con Firebase](firebase-and-google-analytics) | | airbridgeDeviceId | String | El ID del dispositivo del usuario en la [integración con Airbridge](airbridge) | | appTrackingTransparencyStatus | AdaptyATTStatus | El estado del acceso a IDFA (usar en iOS) | | analyticsDisabled | Boolean | Indica si el [analytics externo está desactivado para el usuario](analytics-integration#disabling-external-analytics-for-a-specific-customer) | | customStringAttributes | List < Data ([AdaptyCustomStringAttribute](ff-resources#adaptycustomstringattribute)) > | Lista de atributos de cadena personalizados del usuario | | customDoubleAttributes | List < Data ([AdaptyCustomDoubleAttribute](ff-resources#adaptycustomdoubleattribute)) > | Lista de atributos double personalizados del usuario | ### AdaptySubscription Información sobre la suscripción existente del usuario. | Field Name | Type | Description | | --------------------------- | -------- | ------------------------------------------------------------ | | activatedAt | DateTime | El momento en que se activó esta suscripción | | activeIntroductoryOfferType | String | El tipo de oferta introductoria activa. Si está definido, significa que se aplicó una oferta durante este período de suscripción | | activePromotionalOfferId | String | El ID de una oferta promocional activa (usar en iOS) | | activePromotionalOfferType | String | El tipo de oferta promocional activa (usar en iOS). Si está definido, significa que se aplicó una oferta durante este período de suscripción | | cancellationReason | String | El motivo por el que se canceló la suscripción | | expiresAt | DateTime | El momento de expiración de la suscripción | | renewedAt | DateTime | El momento en que se renovó por última vez la suscripción | | unsubscribedAt | DateTime | El momento en que se desactivó la renovación automática de la suscripción. La suscripción puede seguir activa. Si no está definido, el usuario reactivó la suscripción | | billingIssueDetectedAt | DateTime | El momento en que se detectó un problema de facturación. La suscripción puede seguir activa. Se establece en null si el pago se procesa correctamente | | isActive | Boolean | True si esta suscripción está activa. En general, puedes comprobar esta propiedad para determinar si un usuario tiene acceso a las funciones premium | | isInGracePeriod | Boolean | True si esta suscripción de renovación automática está en el [período de gracia](https://developer.apple.com/help/app-store-connect/manage-subscriptions/enable-billing-grace-period-for-auto-renewable-subscriptions) | | isLifetime | Boolean | True si esta suscripción tiene acceso de por vida (sin fecha de expiración) | | isRefund | Boolean | True si esta compra fue reembolsada | | isSandbox | Boolean | Indica si el producto se compró en un entorno sandbox | | offerId | String | El ID de una oferta promocional activa (usar en Android) | | startsAt | DateTime | El momento de inicio de este nivel de acceso (puede ser en el futuro) | | store | String | El store donde se compró el producto (p. ej., App Store, Google Play) | | vendorOriginalTransactionId | String | ID de la suscripción inicial en el sistema del proveedor | | vendorProductId | String | ID del producto en el sistema del proveedor | | vendorTransactionId | String | ID de la transacción en el sistema del proveedor | | willRenew | Boolean | True si esta suscripción de renovación automática está configurada para renovarse | ### AdaptySubscriptionDetails Esquema de un objeto Subscription como parte del [AdaptyPaywallProduct](product). | Field Name | Type | Description | | ----------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | androidBasePlanId | String | [ID del plan base](https://support.google.com/googleplay/android-developer/answer/12154973) en Google Play Store o [ID de precio](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) en Stripe. | | androidIntroductoryOfferEligibility | [AdaptyEligibilityEnum](ff-resources#adaptyeligibilityenum) | Indica si el usuario cumple los requisitos para una oferta introductoria en una suscripción de iOS | | androidOfferId | String | El ID de una oferta promocional activa (usar en Android) | | androidOfferTags | List < String > | Lista de [etiquetas personalizadas](https://developers.google.com/android-publisher/api-ref/rest/v3/OfferTag) especificadas para planes base y ofertas de suscripción. | | introductoryOffer | List < Data ([AdaptySubscriptionPhase](ff-resources#adaptysubscriptionphase)) > | El ID de una oferta introductoria (usar en iOS) | | localizedSubscriptionPeriod | String | El período de la suscripción en el idioma del usuario | | promotionalOffer | Data ([AdaptySubscriptionPhase](ff-resources#adaptysubscriptionphase)) | Los detalles de la oferta promocional (usar en iOS) | | promotionalOfferEligibility | Boolean | Indica si el usuario cumple los requisitos para una oferta promocional en una suscripción de iOS | | promotionalOfferId | String | El ID de la oferta promocional (usar en iOS) | | renewalType | [AdaptyRenewalTypeEnum](#adaptyrenewaltypeenum) | Define si la suscripción es de renovación automática o no mediante [AdaptyRenewalTypeEnum](ff-resources#adaptyrenewaltypeenum) | | subscriptionGroupIdentifier | String | El ID del grupo de productos al que pertenece el producto (usar en iOS) | | subscriptionPeriod | Data ([AdaptySubscriptionPeriod](#adaptysubscriptionperiod)) | La duración de la suscripción | ### AdaptySubscriptionPeriod La duración de la suscripción. | Field Name | Type | Description | | ------------- | --------------------------------------------- | ----------------------------------------------------------- | | numberOfUnits | Integer | Número de días/semanas/meses/años que dura la suscripción. | | unit | [AdaptyPeriodUnitEnum](#adaptyperiodunitenum) | Unidad de medida del período: días, semanas, meses, años. | ### AdaptySubscriptionPhase Representa una fase de suscripción, como un período de prueba gratuita o una oferta introductoria. | Field Name | Type | Description | | --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | identifier | String | El ID de la fase | | localizedNumberOfPeriods | String | La duración de la fase. Por ejemplo, una oferta de 6 meses se mostraría como `6 months` en el idioma del usuario. | | localizedSubscriptionPeriod | String | La duración de la suscripción en el idioma del usuario, como `3 months`. | | numberOfPeriods | Integer | El número de períodos de suscripción en esta fase. Por ejemplo, una oferta de 6 meses tendría dos períodos de 3 meses. | | paymentMode | [AdaptyPaymentModeEnum](#adaptypaymentmodeenum) | El modelo de pago utilizado para esta fase. | | price | Data ([AdaptyPrice](#adaptyprice)) | El precio de esta fase. | | subscriptionPeriod | Data ([AdaptySubscriptionPeriod](#adaptysubscriptionperiod)) | El período de suscripción en el que se basa esta fase. | ### AdaptySubscriptionUpdateParameters (*Solo Android*) Parámetros para reemplazar una suscripción por otra. | Field Name | Type | Description | | ---------- | ------------------------------------------------------------ | ---------- | | oldSubVendorProductId | String | El ID de la suscripción actual en Play Store que deseas reemplazar. | | replacementMode | [AdaptySubscriptionUpdateReplacementMode](ff-resources#adaptysubscriptionupdatereplacementmode) | Enum que corresponde a los valores de [`BillingFlowParams.ProrationMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode). | ### MapKeyNonSubscriptions Reemplazo de un diccionario para [AdaptyNonSubscription](ff-resources#adaptynonsubscription). | Field Name | Tipo | | ---------- | ------------------------------------------------------------ | | key | String | | value | List < Data ([AdaptyNonSubscription](ff-resources#adaptynonsubscription)) > | ### MapKeySubscriptions Reemplazo de un diccionario para [AdaptySubscription](ff-resources#adaptysubscription). | Field Name | Type | | ---------- | ------------------------------------------------------------ | | key | String | | value | List < Data ([AdaptySubscription](ff-resources#adaptysubscription)) > | ## Enumeraciones \{#enums\} Enumeraciones de Adapty (variables que representan conjuntos de constantes predefinidas) disponibles en FlutterFlow a través del plugin de Adapty. ### AdaptyEligibilityEnum Define si el usuario puede beneficiarse de una oferta introductoria para una suscripción de iOS. | Field Name | Description | |--------------------------|-------------| | eligible | El usuario es elegible para una oferta introductoria; puedes mostrar esta información en tu interfaz | | ineligible | El usuario no es elegible para ninguna oferta; no deberías presentarla en tu interfaz | | notApplicable | Este producto no está configurado para tener una oferta | ### AdaptyGenderEnum Define el género del usuario. | Field Name | Description | | ---------- | -------------------------------------------- | | none | El género no está definido | | female | El género del usuario es femenino | | male | El género del usuario es masculino | | Other | El usuario ha definido su género como "otro" | ### AdaptyPaymentModeEnum Define el modelo de pago. | Campo | Descripción | | ---------- | ------------------------------------------------------------ | | payAsYouGo | Modelo de precios en el que se factura al cliente según el uso o consumo real del producto/servicio, en lugar de pagar una tarifa fija por adelantado | | payUpFront | Modelo de precios en el que se factura al cliente antes de que reciba el producto/servicio. | | freeTrial | El usuario está en un período de prueba gratuita | | unknown | El modelo de precios no está definido | ### AdaptyPeriodUnitEnum Define las unidades en las que se miden los períodos. | Field Name | Description | | ---------- | ------------- | | day | En días | | week | En semanas | | month | En meses | | year | En años | | unknown | No definido | ### AdaptyRenewalTypeEnum Define si la suscripción es de renovación automática o no. | Field Name | Description | | ------------- | -------------------------------------------------------- | | prepaid | La suscripción es de prepago y no se renueva automáticamente. | | autorenewable | La suscripción se renueva automáticamente. | ### AdaptySubscriptionUpdateReplacementMode Define el modo de actualización de suscripción para Android. | Field Name | Descripción | | ------------- | --------------------------------------------------- | | withTimeProration | (predeterminado) El nuevo plan entra en vigor de inmediato y el tiempo restante se prorrateará y acreditará al usuario. | | chargeProratedPrice | El nuevo plan entra en vigor de inmediato y el ciclo de facturación permanece igual. Se cobrará el precio por el período restante. Esta opción solo está disponible para actualizaciones de suscripción. | | withoutProration | El nuevo plan entra en vigor de inmediato y el nuevo precio se cobrará en la siguiente fecha de renovación. El ciclo de facturación permanece igual. | | deferred | La nueva compra entra en vigor de inmediato y el nuevo plan se activará cuando el artículo anterior expire. | | chargeFullPrice | El nuevo plan entra en vigor de inmediato y el ciclo de facturación permanece igual. Se cobrará el precio completo por el período restante. Esta opción solo está disponible para actualizaciones de suscripción. | ### Estados de la app \{#app-states\} Las variables de estado de la app son variables específicas que almacenan el estado actual de una aplicación. Se puede acceder a ellas y modificarlas en toda la aplicación, en todas las páginas y componentes. Este tipo de variable resulta útil para almacenar datos que deben compartirse entre distintas partes de la app, como preferencias de usuario y tokens de autenticación. | Nombre del campo | Tipo de dato | Persistido | Descripción | | -------------- | -------------------------------------------------- | --------- | ------------------------------------------------------------ | | currentProfile | Data ([AdaptyProfile](ff-resources#adaptyprofile)) | False | La variable que contiene la información del perfil del usuario actual. Mantenla actualizada. | --- # End of Documentation _Generated on: 2026-07-24T13:01:55.947Z_ _Successfully processed: 277/277 files_ # UNITY - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-07-24T13:01:55.957Z Total files: 41 --- # File: sdk-installation-unity --- --- title: "Instalar y configurar el SDK de Unity" description: "Guía paso a paso para instalar el SDK de Adapty en Unity para apps con suscripciones." --- El SDK de Adapty incluye dos módulos clave para una integración fluida en tu app de Unity: - **Core Adapty**: Este SDK esencial es necesario para que Adapty funcione correctamente en tu app. - **AdaptyUI**: Este módulo es necesario si usas el [Paywall Builder de Adapty](adapty-paywall-builder), una herramienta visual sin código para crear paywalls multiplataforma de forma sencilla. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestra [app de ejemplo](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets), que muestra la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funciones básicas. ::: ## Requisitos \{#requirements\} El SDK de Adapty es compatible con iOS 13.0+, pero requiere iOS 15.0+ para funcionar con paywalls creados en el Paywall Builder. :::info Adapty es compatible con Google Play Billing Library hasta la versión 8.x. Por defecto, Adapty utiliza Google Play Billing Library v7.0.0. Para usar una versión más reciente, [sobreescribe la dependencia de Billing](https://developer.android.com/google/play/billing/integrate#dependency) en tu build de Android. ::: :::info Instalar el SDK es el paso 5 de la configuración de Adapty. Para que las compras funcionen en tu app, también necesitas conectar tu app a los stores, y luego crear productos, un paywall y un placement en el Adapty Dashboard. La [guía de inicio rápido](quickstart) explica todos los pasos necesarios. ::: ## Instalar el SDK de Adapty \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Unity.svg?style=flat&logo=unity)](https://github.com/adaptyteam/AdaptySDK-Unity/releases) Elige el método de instalación que prefieras: <Tabs groupId="unity-install-method"> <TabItem value="git-url" label="Git URL"> Instala el SDK de Adapty mediante Unity Package Manager usando una Git URL: 1. En Unity, abre **Window → Package Manager**. 2. Haz clic en **+** en la esquina superior izquierda y selecciona **Add package from git URL...**. 3. Introduce la siguiente URL y haz clic en **Add**: ``` https://github.com/adaptyteam/AdaptySDK-Unity.git?path=Packages/com.adapty.unity-sdk#upm ``` Para más detalles, consulta la guía de Unity sobre [cómo instalar un paquete UPM desde una URL de Git](https://docs.unity3d.com/Manual/upm-ui-giturl.html). </TabItem> <TabItem value="unity-package" label="Unity package" default> Descarga el [`adapty-unity-plugin-*.unitypackage`](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Releases) de GitHub e impórtalo en tu proyecto. <img src="/assets/shared/img/456bd98-adapty-unity-plugin.webp" style={{ border: 'none', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </TabItem> </Tabs> Después de instalar el SDK, completa los siguientes pasos: 1. Instala el [plugin External Dependency Manager (EDM)](https://github.com/googlesamples/unity-jar-resolver#getting-started). El SDK de Adapty lo utiliza para gestionar las dependencias de iOS Cocoapods y las dependencias de Android gradle. 2. Tras instalar EDM, puede que necesites invocar el gestor de dependencias: `Assets -> External Dependency Manager -> Android Resolver -> Force Resolve` y `Assets -> External Dependency Manager -> iOS Resolver -> Install Cocoapods` 3. Al compilar tu proyecto de Unity para iOS, obtendrás el archivo `Unity-iPhone.xcworkspace`, que debes abrir en lugar de `Unity-iPhone.xcodeproj`; de lo contrario, no se usarán las dependencias de Cocoapods. ## Activar el módulo Adapty del SDK \{#activate-adapty-module-of-adapty-sdk\} Activa el SDK en el código de tu aplicación. :::note El SDK solo necesita activarse una vez en tu aplicación. ::: Para obtener tu **Public SDK Key**: 1. Ve al Adapty Dashboard y navega a [**App settings → General**](https://app.adapty.io/settings/general). 2. En la sección **Api keys**, copia la **Public SDK Key** (NO la Secret Key). 3. Reemplaza `"YOUR_PUBLIC_SDK_KEY"` en el código. O bien, obtenla de forma programática usando el [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` O directamente: ``` npx adapty auth login adapty apps list ``` - Asegúrate de usar la **Public SDK key** para inicializar Adapty; la **Secret key** solo debe usarse para la [API del lado del servidor](getting-started-with-server-side-api). - Las **SDK keys** son únicas para cada app, así que si tienes varias apps asegúrate de elegir la correcta. ```csharp showLineNumbers title="C#" using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, AdaptyEventListener { void Start() { DontDestroyOnLoad(this.gameObject); Adapty.SetEventListener(this); var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY"); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); } public void OnLoadLatestProfile(AdaptyProfile profile) { } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } } ``` :::important Espera el callback de finalización de `Activate` antes de llamar a cualquier otro método del SDK de Adapty. Consulta [Orden de llamadas en el SDK de Unity](unity-sdk-call-order) para ver la secuencia completa. ::: ## Configurar la escucha de eventos \{#set-up-event-listening\} Crea un script para escuchar los eventos de Adapty. Nómbralo `AdaptyListener` en tu escena. Te recomendamos usar el método `DontDestroyOnLoad` en este objeto para asegurarte de que persista durante toda la vida útil de la aplicación. <img src="/assets/shared/img/2ccd564-create_adapty_listener.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty usa el espacio de nombres `AdaptySDK`. Al principio de los archivos de script que usen el SDK de Adapty, puedes añadir: ```csharp showLineNumbers title="C#" using AdaptySDK; ``` Suscríbete a los eventos de Adapty: ```csharp showLineNumbers title="C#" using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, AdaptyEventListener { public void OnLoadLatestProfile(AdaptyProfile profile) { // handle updated profile data } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } } ``` Recomendamos ajustar el orden de ejecución de scripts (Script Execution Order) para colocar el AdaptyListener antes del tiempo predeterminado (Default Time). Esto garantiza que Adapty se inicialice lo antes posible. <img src="/assets/shared/img/activate_unity.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Ahora configura los paywalls en tu app: - Si usas el [Paywall Builder de Adapty](adapty-paywall-builder), primero [activa el módulo AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) a continuación y luego sigue la [guía de inicio rápido del Paywall Builder](unity-quickstart-paywalls). - Si construyes tu propia interfaz de paywall, consulta la [guía de inicio rápido para paywalls personalizados](unity-quickstart-manual). ## Activar el módulo AdaptyUI del SDK \{#activate-adaptyui-module-of-adapty-sdk\} Si planeas usar [Paywall Builder](adapty-paywall-builder) y has instalado el módulo AdaptyUI, necesitas que AdaptyUI esté activo. Puedes activarlo durante la configuración: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetActivateUI(true); ``` ## Configuración opcional \{#optional-setup\} ### Registro #### Configura el sistema de registro \{#set-up-the-logging-system\} Adapty registra errores y otra información importante para ayudarte a entender qué está pasando. Están disponibles los siguientes niveles: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | Solo se registrarán errores | | `warn` | Se registrarán errores y mensajes del SDK que no causan errores críticos, pero que merecen atención | | `info` | Se registrarán errores, advertencias y varios mensajes informativos | | `verbose` | Se registrará cualquier información adicional que pueda ser útil durante la depuración, como llamadas a funciones, consultas a la API, etc. | Puedes establecer el nivel de log en tu app durante la configuración de Adapty: ```csharp showLineNumbers title="C#" // 'verbose' is recommended for development and the first production release var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY"); builder.LogLevel = AdaptyLogLevel.Verbose; ``` También puedes cambiar el nivel de log en tiempo de ejecución: ```csharp showLineNumbers title="C#" Adapty.SetLogLevel(AdaptyLogLevel.Verbose, (error) => { // handle result }); ``` ### Políticas de datos \{#data-policies\} Adapty no almacena datos personales de tus usuarios a menos que los envíes explícitamente, pero puedes implementar políticas de seguridad de datos adicionales para cumplir con las directrices del store o del país. #### Deshabilitar la recopilación y el uso compartido de direcciones IP \{#disable-ip-address-collection-and-sharing\} Al activar el módulo de Adapty, establece `SetIPAddressCollectionDisabled` en `true` para deshabilitar la recopilación y el uso compartido de la dirección IP del usuario. El valor predeterminado es `false`. Usa este parámetro para mejorar la privacidad del usuario, cumplir con normativas regionales de protección de datos (como GDPR o CCPA) o reducir la recopilación de datos innecesaria cuando las funciones basadas en IP no son necesarias para tu app. ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetIPAddressCollectionDisabled(true); ``` #### Desactivar la recopilación y el uso compartido del ID publicitario \{#disable-advertising-id-collection-and-sharing\} Al activar el módulo de Adapty, establece `SetAppleIDFACollectionDisabled` y/o `SetGoogleAdvertisingIdCollectionDisabled` en `true` para desactivar la recopilación de identificadores publicitarios. El valor predeterminado es `false`. Usa este parámetro para cumplir con las políticas de App Store/Google Play, evitar que aparezca el aviso de App Tracking Transparency, o si tu aplicación no necesita atribución publicitaria ni análisis basado en IDs publicitarios. ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAppleIDFACollectionDisabled(true) .SetGoogleAdvertisingIdCollectionDisabled(true); ``` #### Configurar la caché de medios para AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} De forma predeterminada, AdaptyUI almacena en caché los medios (como imágenes y vídeos) para mejorar el rendimiento y reducir el uso de red. Puedes personalizar la configuración de la caché proporcionando una configuración personalizada. Usa `SetAdaptyUIMediaCache` para sobreescribir la configuración de caché predeterminada: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAdaptyUIMediaCache( 100 * 1024 * 1024, // MemoryStorageTotalCostLimit 100MB null, // MemoryStorageCountLimit 100 * 1024 * 1024 // DiskStorageSizeLimit 100MB ); ``` Parámetros: | Parámetro | Obligatorio | Descripción | |-----------------------------|-------------|--------------------------------------------------------------------------------------------------| | memoryStorageTotalCostLimit | opcional | Tamaño total de la caché en memoria en bytes. Por defecto, usa el valor específico de la plataforma. | | memoryStorageCountLimit | opcional | Límite del número de elementos en el almacenamiento en memoria. Por defecto, usa el valor específico de la plataforma. | | diskStorageSizeLimit | opcional | Límite del tamaño de archivo en disco en bytes. Por defecto, usa el valor específico de la plataforma. | ### Habilitar niveles de acceso locales (Android) \{#enable-local-access-levels-android\} Por defecto, los [niveles de acceso locales](local-access-levels) están habilitados en iOS y deshabilitados en Android. Para habilitarlos también en Android, establece `SetGoogleLocalAccessLevelAllowed` en `true`: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetGoogleLocalAccessLevelAllowed(true); ``` ### Borrar datos al restaurar desde copia de seguridad \{#clear-data-on-backup-restore\} Cuando `SetAppleClearDataOnBackup` está configurado en `true`, el SDK detecta cuándo la app se restaura desde una copia de seguridad de iCloud y elimina todos los datos almacenados localmente por el SDK, incluida la información de perfil en caché, los detalles de productos y los paywalls. El SDK se inicializa entonces con un estado limpio. El valor predeterminado es `false`. :::note Solo se elimina la caché local del SDK. El historial de transacciones con Apple y los datos de usuario en los servidores de Adapty permanecen sin cambios. ::: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAppleClearDataOnBackup(true); ``` ## Solución de problemas \{#troubleshooting\} #### Reglas de copia de seguridad de Android (configuración de Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Algunos SDKs (incluido Adapty) incluyen su propia configuración de Android Auto Backup. Si utilizas varios SDKs que definen reglas de copia de seguridad, el fusionador de manifiestos de Android puede fallar con un error relacionado con `android:fullBackupContent`, `android:dataExtractionRules` o `android:allowBackup`. Síntomas típicos del error: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Estos cambios deben realizarse en el directorio de la plataforma Android (normalmente en la carpeta `android/` de tu proyecto). ::: Para resolverlo, necesitas: - Indicar al fusionador de manifiestos que use los valores de tu app para los atributos relacionados con la copia de seguridad. - Crear archivos de reglas de copia de seguridad que combinen las reglas de Adapty con las de otros SDKs. #### 1. Añade el namespace `tools` a tu manifiesto \{#1-add-the-tools-namespace-to-your-manifest\} En tu archivo `AndroidManifest.xml`, asegúrate de que la etiqueta raíz `<manifest>` incluya tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. Sobreescribe los atributos de copia de seguridad en `<application>` \{#2-override-backup-attributes-in-application\} En el mismo archivo `AndroidManifest.xml`, actualiza la etiqueta `<application>` para que tu app proporcione los valores definitivos e indique al fusionador de manifiestos que reemplace los valores de las librerías: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` Si algún SDK también define `android:allowBackup`, inclúyelo en `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Crea los archivos de reglas de copia de seguridad combinadas \{#3-create-merged-backup-rules-files\} Crea archivos XML en el directorio `res/xml/` de tu proyecto Android que combinen las reglas de Adapty con las de otros SDKs. Android utiliza distintos formatos de reglas de copia de seguridad según la versión del sistema operativo, por lo que crear ambos archivos garantiza la compatibilidad con todas las versiones de Android que admite tu app. :::note Los ejemplos a continuación usan AppsFlyer como SDK de terceros de muestra. Reemplaza o añade reglas para cualquier otro SDK que uses en tu app. ::: **Para Android 12 y superior** (usa el nuevo formato de reglas de extracción de datos): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Para Android 11 e inferior** (usa el formato legado de contenido de copia de seguridad completa): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> :::important En Unity, aplica estos cambios en `Assets/Plugins/Android/AndroidManifest.xml` y crea los archivos de reglas de copia de seguridad en `Assets/Plugins/Android/res/xml/`. ::: #### Las compras fallan al volver desde otra app en Android \{#purchases-fail-after-returning-from-another-app-in-android\} Si la Activity que inicia el flujo de compra usa un `launchMode` no predeterminado, Android puede recrearla o reutilizarla incorrectamente cuando el usuario regresa desde Google Play, una app bancaria o un navegador. Esto puede provocar que el resultado de la compra se pierda o se trate como cancelado. Para garantizar que las compras funcionen correctamente, utiliza solo los modos de inicio `standard` o `singleTop` para la Activity que inicia el flujo de compra, y evita cualquier otro modo. En tu `AndroidManifest.xml`, asegúrate de que la Activity que inicia el flujo de compra esté configurada como `standard` o `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### La app se bloquea al mostrar un paywall en Android \{#app-crashes-when-a-paywall-is-displayed-on-android\} Si tu app se bloquea en Android al mostrar un paywall, es posible que falte el plugin de Kotlin en la configuración de Gradle. Para añadirlo: 1. En **Player Settings**, asegúrate de que las opciones **Custom Launcher Gradle Template** y **Custom Base Gradle Template** estén seleccionadas. <img src="/assets/shared/img/kotlin-plugin1.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Añade la siguiente línea a `/Assets/Plugins/Android/launcherTemplate.gradle`: ```groovy showLineNumbers apply plugin: 'com.android.application' // highlight-next-line apply plugin: 'kotlin-android' apply from: 'setupSymbols.gradle' apply from: '../shared/keepUnitySymbols.gradle' ``` 3. Añade la siguiente línea a `/Assets/Plugins/Android/baseProjectTemplate.gradle`: ```groovy showLineNumbers plugins { // If you are changing the Android Gradle Plugin version, make sure it is compatible with the Gradle version preinstalled with Unity // See which Gradle version is preinstalled with Unity here https://docs.unity3d.com/Manual/android-gradle-overview.html // See official Gradle and Android Gradle Plugin compatibility table here https://developer.android.com/studio/releases/gradle-plugin#updating-gradle // To specify a custom Gradle version in Unity, go do "Preferences > External Tools", uncheck "Gradle Installed with Unity (recommended)" and specify a path to a custom Gradle version id 'com.android.application' version '8.3.0' apply false id 'com.android.library' version '8.3.0' apply false // highlight-next-line id 'org.jetbrains.kotlin.android' version '1.8.0' apply false **BUILD_SCRIPT_DEPS** } ``` --- # File: unity-quickstart-paywalls --- --- title: "Habilitar compras usando paywalls en Unity SDK" description: "Aprende cómo presentar paywalls en tu aplicación Unity con el SDK de Adapty." --- Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – cualquier cosa que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - Los [**paywalls**](paywalls) son configuraciones que definen qué productos ofrecer. En Adapty, los paywalls son la única forma de recuperar productos, pero este diseño te permite modificar ofertas, precios y combinaciones de productos sin tocar el código de tu app. - Los [**placements**](placements) – dónde y cuándo muestras los paywalls en tu app (como `main`, `onboarding`, `settings`). Configuras los paywalls para los placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita la ejecución de pruebas A/B y mostrar diferentes paywalls a distintos usuarios. Adapty te ofrece tres formas de habilitar compras en tu app. Selecciona una según los requisitos de tu aplicación: | Implementación | Complejidad | Cuándo usarla | |---------------------------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Adapty Paywall Builder | ✅ Fácil | [Creas un paywall completo y listo para compras en el editor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona todo el flujo de compra, la validación de recibos y la gestión de suscripciones entre bastidores. | | Paywalls creados manualmente | 🟡 Medio | Implementas la UI de tu paywall en el código de tu app, pero igualmente obtienes el objeto paywall de Adapty para mantener flexibilidad en las ofertas de productos. Consulta la [guía](unity-quickstart-manual). | | Modo observador | 🔴 Difícil | Ya tienes tu propia infraestructura de gestión de compras y quieres seguir usándola. Ten en cuenta que el modo observador tiene sus limitaciones en Adapty. Consulta el [artículo](observer-vs-full-mode). | :::important **Los pasos a continuación muestran cómo implementar un paywall creado en el Adapty Paywall Builder.** Si no quieres usar el Paywall Builder, consulta la [guía para gestionar compras en paywalls creados manualmente](unity-making-purchases). ::: Para mostrar un paywall creado en el Adapty Paywall Builder, en el código de tu app solo necesitas: 1. **Obtener el paywall**: Obtener el paywall de Adapty. 2. **Mostrar el paywall y Adapty gestionará las compras por ti**: Muestra el contenedor del paywall que obtuviste en tu app. 3. **Gestionar las acciones de los botones**: Asocia las interacciones del usuario con el paywall con la respuesta de tu app a ellas. Por ejemplo, abrir enlaces o cerrar el paywall cuando los usuarios pulsan botones. ## Antes de empezar \{#before-you-start\} Antes de empezar, completa estos pasos: 1. Conecta tu app al [App Store](initial_ios) y/o [Google Play](initial-android) en el Adapty Dashboard. 2. [Crea tus productos](create-product) en Adapty. 3. [Crea un paywall y añade productos](create-paywall). 4. [Crea un placement y añade tu paywall](create-placement). 5. [Instala y activa el SDK de Adapty](sdk-installation-unity) en el código de tu app. :::tip La forma más rápida de completar estos pasos es seguir la [guía de inicio rápido](quickstart) o crear paywalls y placements usando el [CLI para desarrolladores](developer-cli-quickstart). ::: ## 1. Obtener el paywall \{#1-get-the-paywall\} Tus paywalls están asociados a placements configurados en el dashboard. Los placements te permiten ejecutar distintos paywalls para diferentes audiencias o realizar [pruebas A/B](ab-tests). Para obtener un paywall creado en el Adapty Paywall Builder, necesitas: 1. Obtener el objeto `paywall` por el ID del [placement](placements) usando el método `GetPaywall` y comprobar si fue creado en el builder mediante la propiedad `HasViewConfiguration`. 2. Crear la vista del paywall usando el método `CreatePaywallView`. La vista contiene los elementos de UI y el estilo necesarios para mostrar el paywall. :::important Para obtener la configuración de la vista, debes activar el toggle **Show on device** en el Paywall Builder. De lo contrario, obtendrás una configuración de vista vacía y el paywall no se mostrará. ::: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if(error != null) { // handle the error return; } // Create paywall view parameters var parameters = new AdaptyUICreatePaywallViewParameters(); // Create the paywall view AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { if(error != null) { // handle the error return; } // view - the paywall view ready to be presented }); }); ``` :::info Este inicio rápido proporciona la configuración mínima necesaria para mostrar un paywall. Para detalles de configuración avanzada, consulta nuestra [guía sobre cómo obtener paywalls](unity-get-pb-paywalls). ::: ## 2. Mostrar el paywall \{#2-display-the-paywall\} Ahora que tienes la configuración del paywall, basta con añadir unas pocas líneas para mostrarlo. Para mostrar el paywall, usa el método `view.Present()` en el `view` creado por el método `CreatePaywallView`. Cada `view` solo puede usarse una vez. Si necesitas mostrar el paywall de nuevo, llama a `CreatePaywallView` otra vez para crear una nueva instancia de `view`. ```csharp showLineNumbers title="Unity" view.Present((error) => { // handle the error }); ``` :::info Para más detalles sobre cómo mostrar un paywall, consulta nuestra [guía](unity-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#3-handle-button-actions\} Cuando los usuarios pulsan botones en el paywall, el SDK de Unity gestiona automáticamente las compras y la restauración. Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren gestionar las acciones en tu código. Por ejemplo, tu paywall probablemente tenga un botón de cerrar y URLs que abrir (p. ej., términos de uso y política de privacidad). Para gestionar estas acciones, tu clase debe implementar la interfaz `AdaptyPaywallsEventsListener` y registrarse como listener. :::tip Lee nuestras guías sobre cómo gestionar [acciones](unity-handle-paywall-actions) y [eventos](unity-handling-events) de botones. ::: ```csharp showLineNumbers title="Unity" public class YourClass : MonoBehaviour, AdaptyPaywallsEventsListener { void Start() { // Register this class as the paywall events listener Adapty.SetPaywallsEventsListener(this); } // AdaptyPaywallsEventsListener method - handles button actions public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: view.Dismiss(null); break; case AdaptyUIUserActionType.OpenUrl: Application.OpenURL(action.Value); break; default: break; } } } ``` ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox del App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. Ahora necesitas [comprobar el nivel de acceso de los usuarios](unity-check-subscription-status) para asegurarte de que muestras un paywall o das acceso a las funciones de pago a los usuarios correctos. ## Ejemplo completo \{#full-example\} Así es como todos esos pasos pueden integrarse juntos en tu app. ```csharp showLineNumbers using System; using UnityEngine; using AdaptySDK; public class PaywallManager : MonoBehaviour, AdaptyPaywallsEventsListener { [SerializeField] private string placementId = "YOUR_PLACEMENT_ID"; private AdaptyUIPaywallView currentPaywallView; void Start() { // Register for paywall events Adapty.SetPaywallsEventsListener(this); GetAndDisplayPaywall(); } private void GetAndDisplayPaywall() { Adapty.GetPaywall(placementId, (paywall, error) => { if (error != null) { Debug.LogError("Error getting paywall: " + error.Message); return; } if (paywall.HasViewConfiguration) { CreateAndPresentPaywallView(paywall); } else { Debug.LogWarning("Paywall was not created using the builder"); } }); } private void CreateAndPresentPaywallView(AdaptyPaywall paywall) { var parameters = new AdaptyUICreatePaywallViewParameters(); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { if (error != null) { Debug.LogError("Error creating paywall view: " + error.Message); return; } currentPaywallView = view; view.Present((presentError) => { if (presentError != null) { Debug.LogError("Error presenting paywall: " + presentError.Message); return; } Debug.Log("Paywall presented successfully"); }); }); } // AdaptyPaywallsEventsListener implementation public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: Debug.Log("Close button pressed"); view.Dismiss(null); break; case AdaptyUIUserActionType.OpenUrl: Application.OpenURL(action.Value); break; default: break; } } // Required interface methods (implement as needed) public void PaywallViewDidAppear(AdaptyUIPaywallView view) { } public void PaywallViewDidDisappear(AdaptyUIPaywallView view) { } public void PaywallViewDidSelectProduct(AdaptyUIPaywallView view, string productId) { } public void PaywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { } public void PaywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult) { } public void PaywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } public void PaywallViewDidStartRestore(AdaptyUIPaywallView view) { } public void PaywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { } public void PaywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { } public void PaywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { } public void PaywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { } public void PaywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } public void ShowPaywall() { GetAndDisplayPaywall(); } void OnDestroy() { if (currentPaywallView != null) { currentPaywallView.Dismiss(null); } } } ``` --- # File: unity-check-subscription-status --- --- title: "Comprobar el estado de la suscripción en el SDK de Unity" description: "Aprende cómo comprobar el estado de la suscripción en tu app de Unity con Adapty." --- Para decidir si los usuarios pueden acceder al contenido de pago o ver un paywall, necesitas comprobar su [nivel de acceso](access-level) en el perfil. Este artículo te muestra cómo acceder al estado del perfil para decidir qué necesitan ver los usuarios: si mostrarles un paywall o darles acceso a las funciones de pago. ## Obtener el estado de la suscripción \{#get-subscription-status\} Cuando decides si mostrar un paywall o contenido de pago a un usuario, compruebas su [nivel de acceso](access-level) en su perfil. Tienes dos opciones: - Llama a `GetProfile` si necesitas los datos más recientes del perfil de inmediato (como al iniciar la app) o quieres forzar una actualización. - Configura **actualizaciones automáticas del perfil** para mantener una copia local que se actualiza automáticamente cada vez que cambia el estado de la suscripción. ### Obtener el perfil \{#get-profile\} La forma más sencilla de obtener el estado de la suscripción es usar el método `GetProfile` para acceder al perfil: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access }); ``` ### Escuchar actualizaciones de la suscripción \{#listen-to-subscription-updates\} Para recibir actualizaciones del perfil automáticamente en tu app: 1. Extiende `AdaptyEventListener` e implementa el método `OnLoadLatestProfile`: Adapty llamará a este método automáticamente cada vez que cambie el estado de la suscripción del usuario. 2. Almacena los datos del perfil actualizado cuando se llame a este método, para poder usarlos en toda la app sin realizar peticiones de red adicionales. ```csharp public class SubscriptionManager : MonoBehaviour, AdaptyEventListener { private AdaptyProfile currentProfile; void Start() { // Register this object as an Adapty event listener Adapty.SetEventListener(this); } // Store the profile when it updates public void OnLoadLatestProfile(AdaptyProfile profile) { currentProfile = profile; // Update UI, unlock content, etc. } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } // Use stored profile instead of calling getProfile() public bool HasAccess() { if (currentProfile?.AccessLevels != null && currentProfile.AccessLevels.ContainsKey("premium")) { return currentProfile.AccessLevels["premium"].IsActive; } return false; } } ``` :::note Adapty llama automáticamente a `OnLoadLatestProfile` cuando se inicia tu app, proporcionando datos de suscripción en caché incluso si el dispositivo está sin conexión. ::: ## Conectar el perfil con la lógica del paywall \{#connect-profile-with-paywall-logic\} Cuando necesitas tomar decisiones inmediatas sobre mostrar paywalls o dar acceso a funciones de pago, puedes comprobar el perfil del usuario directamente. Este enfoque es útil en situaciones como el inicio de la app, al entrar en secciones premium o antes de mostrar contenido específico. ```csharp private void CheckAccessLevel() { Adapty.GetProfile((profile, error) => { if (error != null) { Debug.LogError("Error checking access level: " + error.Message); // Show paywall if access check fails return; } var accessLevel = profile.AccessLevels["YOUR_ACCESS_LEVEL"]; if (accessLevel == null || !accessLevel.IsActive) { // Show paywall if no access } }); } private void InitializePaywall() { LoadPaywall(); CheckAccessLevel(); } ``` ## Próximos pasos \{#next-steps\} Ahora que sabes cómo rastrear el estado de la suscripción, aprende a [trabajar con perfiles de usuario](unity-quickstart-identify) para asegurarte de que pueden acceder a lo que han pagado. --- # File: unity-quickstart-identify --- --- title: "Identificar usuarios en el SDK de Unity" description: "Guía de inicio rápido para configurar Adapty en la gestión de suscripciones in-app en Unity." --- :::important Esta guía es para ti si tienes tu propio sistema de autenticación. Aquí aprenderás a trabajar con perfiles de usuario en Adapty para que se integre con tu sistema de autenticación existente. ::: La forma en que gestionas las compras de los usuarios depende del modelo de autenticación de tu app: - Si tu app no utiliza autenticación de backend y no almacena datos de usuario, consulta la [sección sobre usuarios anónimos](#anonymous-users). - Si tu app tiene (o tendrá) autenticación de backend, consulta la [sección sobre usuarios identificados](#identified-users). **Conceptos clave**: - Los **perfiles** son las entidades necesarias para que funcione el SDK. Adapty los crea automáticamente. - Pueden ser anónimos **(sin customer user ID)** o identificados **(con customer user ID)**. - Proporcionas el **customer user ID** para cruzar los perfiles de Adapty con tu sistema de autenticación interno. Estas son las diferencias entre usuarios anónimos e identificados: | | Usuarios anónimos | Usuarios identificados | |-------------------------------|----------------------------------------------------------|-------------------------------------------------------------------------------------| | **Gestión de compras** | Restauración de compras a nivel de store | Mantienen el historial de compras en todos los dispositivos mediante su customer user ID | | **Gestión de perfiles** | Nuevos perfiles en cada reinstalación | El mismo perfil en todas las sesiones y dispositivos | | **Persistencia de datos** | Los datos de usuarios anónimos están vinculados a la instalación de la app | Los datos de usuarios identificados persisten entre instalaciones | ## Usuarios anónimos \{#anonymous-users\} Si no tienes autenticación de backend, **no necesitas gestionar la autenticación en el código de la app**: 1. Cuando el SDK se activa en el primer arranque de la app, Adapty **crea un nuevo perfil para el usuario**. 2. Cuando el usuario realiza una compra en la app, esta compra queda **asociada a su perfil de Adapty y a su cuenta del store**. 3. Cuando el usuario **reinstala** la app o la instala en un **nuevo dispositivo**, Adapty **crea un nuevo perfil anónimo en la activación**. 4. Si el usuario ya había realizado compras en tu app, por defecto, sus compras se sincronizan automáticamente desde el App Store al activar el SDK. Con usuarios anónimos se crearán nuevos perfiles en cada instalación, pero eso no es un problema porque, en las analíticas de Adapty, puedes [configurar qué se considerará una nueva instalación](general#4-installs-definition-for-analytics). Para los usuarios anónimos, debes contar las instalaciones por **IDs de dispositivo**. En este caso, cada instalación de la app en un dispositivo se cuenta como una instalación, incluidas las reinstalaciones. ## Usuarios identificados \{#identified-users\} Tienes dos opciones para identificar a los usuarios en la app: - [**Durante el inicio de sesión/registro:**](#during-loginsignup) Si los usuarios inician sesión después de que arranca tu app, llama a `identify()` con un customer user ID cuando se autentiquen. - [**Durante la activación del SDK:**](#during-the-sdk-activation) Si ya tienes un customer user ID almacenado cuando arranca la app, envíalo al llamar a `activate()`. :::important Por defecto, cuando Adapty recibe una compra de un Customer User ID que actualmente está asociado a otro Customer User ID, el nivel de acceso se comparte, de modo que ambos perfiles tienen acceso de pago. Puedes configurar este ajuste para transferir el acceso de pago de un perfil a otro o deshabilitar el uso compartido por completo. Consulta el [artículo](general#6-sharing-paid-access-between-user-accounts) para más detalles. ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Durante el inicio de sesión/registro \{#during-loginsignup\} Si identificas a los usuarios después del arranque de la app (por ejemplo, después de que inicien sesión o se registren), usa el método `identify` para establecer su customer user ID. - Si **no has usado este customer user ID antes**, Adapty lo vinculará automáticamente al perfil actual. - Si **ya has usado este customer user ID para identificar al usuario**, Adapty cambiará al perfil asociado a ese customer user ID. :::important Los customer user IDs deben ser únicos para cada usuario. Si hardcodeas el valor del parámetro, todos los usuarios se considerarán como uno solo. ::: Espera el callback de finalización de `Identify` antes de llamar a otros métodos del SDK. Las llamadas concurrentes producen `#3006 profileWasChanged` o aterrizan en el perfil anónimo. Consulta [Orden de llamadas en el SDK de Unity](unity-sdk-call-order). ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Único para cada usuario if(error == null) { // identificación correcta } }); ``` ### Durante la activación del SDK \{#during-the-sdk-activation\} Si ya conoces un customer user ID cuando activas el SDK, puedes enviarlo en el método `activate` en lugar de llamar a `identify` por separado. Si conoces un customer user ID pero lo estableces solo después de la activación, eso significará que, al activarse, Adapty creará un nuevo perfil anónimo y cambiará al existente solo después de que llames a `identify`. Puedes pasar un customer user ID existente (uno que hayas usado antes) o uno nuevo. Si pasas uno nuevo, el nuevo perfil creado al activarse se vinculará automáticamente al customer user ID. :::note Por defecto, la creación de perfiles anónimos no afecta a los dashboards de analíticas, ya que las instalaciones se cuentan por IDs de dispositivo. Un ID de dispositivo representa una única instalación de la app desde el store en un dispositivo y solo se regenera tras reinstalar la app. No depende de si es una primera instalación o una reinstalación, ni de si se usa un customer user ID existente. Crear un perfil (al activar el SDK o al cerrar sesión), iniciar sesión o actualizar la app sin reinstalarla no genera eventos de instalación adicionales. Si quieres contar las instalaciones por usuarios únicos en lugar de dispositivos, ve a **App settings** y configura [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```csharp showLineNumbers using UnityEngine; using AdaptySDK; var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID"); // Los customer user IDs deben ser únicos para cada usuario. Si hardcodeas el valor del parámetro, todos los usuarios se considerarán como uno solo. Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` ### Cerrar sesión de usuarios \{#log-users-out\} Si tienes un botón para cerrar la sesión de los usuarios, usa el método `logout`. :::important Cerrar la sesión de los usuarios crea un nuevo perfil anónimo para el usuario. ::: ```csharp showLineNumbers Adapty.Logout((error) => { if(error == null) { // cierre de sesión correcto } }); ``` :::info Para volver a iniciar sesión en la app, usa el método `identify`. ::: ### Permitir compras sin inicio de sesión \{#allow-purchases-without-login\} Si tus usuarios pueden realizar compras tanto antes como después de iniciar sesión en tu app, debes asegurarte de que mantengan el acceso tras iniciar sesión: 1. Cuando un usuario sin sesión iniciada realiza una compra, Adapty la vincula a su ID de perfil anónimo. 2. Cuando el usuario inicia sesión en su cuenta, Adapty cambia al perfil identificado. - Si es un nuevo customer user ID (por ejemplo, la compra se realizó antes del registro), Adapty asigna el customer user ID al perfil actual, por lo que se mantiene todo el historial de compras. - Si es un customer user ID existente (el customer user ID ya está vinculado a un perfil), necesitas obtener el nivel de acceso actual tras el cambio de perfil. Puedes llamar a [`getProfile`](unity-check-subscription-status) justo después de la identificación, o [escuchar las actualizaciones del perfil](unity-check-subscription-status) para que los datos se sincronicen automáticamente. ## Próximos pasos \{#next-steps\} ¡Enhorabuena! Has implementado la lógica de pago in-app en tu app. ¡Te deseamos todo el éxito con la monetización de tu app! Para sacar aún más partido a Adapty, puedes explorar estos temas: - [**Pruebas**](troubleshooting-test-purchases): Asegúrate de que todo funciona como se espera - [**Onboardings**](onboardings): Engancha a los usuarios con onboardings e impulsa la retención - [**Integraciones**](configuration): Intégrate con servicios de atribución de marketing y analíticas con solo una línea de código - [**Establecer atributos de perfil personalizados**](unity-setting-user-attributes): Añade atributos personalizados a los perfiles de usuario y crea segmentos para lanzar pruebas A/B o mostrar diferentes paywalls a distintos usuarios --- # File: adapty-sdk-integration-skill-unity --- --- title: "Integra Adapty en tu app de Unity con la habilidad de integración del SDK" description: "Usa la habilidad adapty-sdk-integration para integrar el SDK de Adapty en tu app de Unity de principio a fin con tu herramienta de codificación con IA." --- La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. :::important La habilidad está en beta. Si se detiene o se comporta de forma inesperada, sigue la [guía de integración paso a paso](adapty-cursor-unity) — te lleva a través de cada etapa con la documentación adecuada. ::: La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de Adapty de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación de Adapty relevante en cada etapa. **Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Para instalarla, elige el formulario para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex u otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que las skills instaladas de esta forma no se actualizan automáticamente): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` También puedes clonar el repositorio y copiar `skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta. Tras la instalación, ejecuta la skill en tu proyecto: ``` /adapty-sdk-integration ``` La skill hace algunas preguntas de configuración y luego te guía por la configuración del dashboard, la instalación del SDK, el paywall y la verificación. --- # File: adapty-cursor-unity --- --- title: "Integra Adapty en tu app de Unity con ayuda de IA" description: "Una guía paso a paso para integrar Adapty en tu app de Unity usando Cursor, Context7, ChatGPT, Claude u otras herramientas de IA." --- Esta guía te lleva paso a paso por la integración de Adapty en tu app de Unity con una herramienta de IA — solo tienes que darle los documentos correctos de Adapty en el orden correcto. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Antes de empezar: configuración en el dashboard \{#before-you-start-dashboard-setup\} Adapty requiere cierta configuración en el dashboard antes de escribir código con el SDK. Puedes hacerlo con una skill interactiva de LLM o manualmente desde el Dashboard. ### Enfoque mediante skill (recomendado) \{#skill-approach-recommended\} El skill de Adapty CLI permite que tu LLM configure tu app, productos, niveles de acceso, paywalls y placements directamente, sin necesidad de abrir el Dashboard en cada paso. Solo tienes que [conectar tus stores](integrate-payments) en el Dashboard. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` Una vez añadido el skill, ejecuta `/adapty-cli` en tu agente. Te guiará paso a paso, incluyendo el momento en que debas abrir el Dashboard para conectar tus stores. ### Enfoque desde el dashboard Si prefieres configurarlo todo manualmente, esto es lo que necesitas antes de escribir cualquier código. Tu LLM no puede consultar los valores del dashboard por ti, tendrás que proporcionarlos. 1. **Conecta tus stores**: En el Adapty Dashboard, ve a **App settings → General**. Conecta tanto App Store como Google Play si tu app de Unity apunta a ambas plataformas. Esto es necesario para que las compras funcionen. [Conecta los stores](integrate-payments) 2. **Copia tu clave SDK pública**: En el Adapty Dashboard, ve a **App settings → General** y localiza la sección **API keys**. En el código, es la cadena que pasas al builder de configuración de Adapty. 3. **Crea al menos un producto**: En el Adapty Dashboard, ve a la página **Products**. No referenciarás los productos directamente en el código — Adapty los entrega a través de paywalls. [Añadir productos](quickstart-products) 4. **Crea un paywall y un placement**: En el Adapty Dashboard, crea un paywall en la página **Paywalls** y asígnalo a un placement en la página **Placements**. En el código, el ID del placement es la cadena que pasas a `Adapty.GetPaywall("YOUR_PLACEMENT_ID")`. [Crear paywall](quickstart-paywalls) 5. **Configura los niveles de acceso**: En el Adapty Dashboard, configúralos por producto en la página **Products**. En el código, la cadena que se comprueba es `profile.AccessLevels["premium"]?.IsActive`. El nivel de acceso `premium` predeterminado funciona para la mayoría de las aplicaciones. Si los usuarios de pago tienen acceso a distintas funcionalidades según el producto (por ejemplo, un plan `basic` frente a un plan `pro`), [crea niveles de acceso adicionales](assigning-access-level-to-a-product) antes de empezar a programar. :::tip Una vez que tengas los cinco, estás listo para escribir código. Dile a tu LLM: "Mi clave SDK pública es X, mi ID de placement es Y" para que pueda generar el código de inicialización y obtención de paywalls correcto. ::: ### Configura cuando estés listo \{#set-up-when-ready\} No son necesarias para empezar a programar, pero las querrás a medida que tu integración madure: - **Pruebas A/B**: Configúralas en la página **Placements**. No se requieren cambios de código. [Pruebas A/B](ab-tests) - **Paywalls y placements adicionales**: Añade más llamadas `GetPaywall` con diferentes IDs de placement. - **Integraciones de analítica**: Configúralas en la página **Integrations**. La configuración varía según la integración. Consulta [integraciones de analítica](analytics-integration) y [integraciones de atribución](attribution-integration). ## Proporciona documentación de Adapty a tu LLM \{#feed-adapty-docs-to-your-llm\} ### Usa Context7 (recomendado) [Context7](https://context7.com) es un servidor MCP que da a tu LLM acceso directo a la documentación actualizada de Adapty. Tu LLM obtiene automáticamente la documentación adecuada según lo que preguntes, sin necesidad de pegar URLs manualmente. Context7 funciona con **Cursor**, **Claude Code**, **Windsurf** y otras herramientas compatibles con MCP. Para configurarlo, ejecuta: ``` npx ctx7 setup ``` Esto detecta tu editor y configura el servidor Context7. Para la configuración manual, consulta el [repositorio de Context7 en GitHub](https://github.com/upstash/context7). Una vez configurado, referencia la librería de Adapty en tus prompts: ``` Use the adaptyteam/adapty-docs library to look up how to install the Unity SDK ``` :::warning Aunque Context7 elimina la necesidad de pegar enlaces a la documentación manualmente, el orden de implementación es importante. Sigue el [recorrido de implementación](#implementation-walkthrough) paso a paso para asegurarte de que todo funciona. ::: ### Usar documentación en texto plano Puedes acceder a cualquier artículo de Adapty en formato Markdown. Añade `.md` al final de su URL o haz clic en **Copy for LLM** bajo el título del artículo. Por ejemplo: [adapty-cursor-unity.md](https://adapty.io/docs/es/adapty-cursor-unity.md). Cada paso del [resumen de implementación](#implementation-walkthrough) incluye un bloque "Envía esto a tu LLM" con enlaces `.md` para copiar. Para acceder a más documentación de una vez, consulta los [archivos de índice y subconjuntos por plataforma](#plain-text-doc-index-files) más abajo. ## Guía de implementación paso a paso \{#implementation-walkthrough\} El resto de esta guía recorre la integración de Adapty en el orden de implementación. Cada etapa incluye la documentación que debes enviar a tu LLM, qué deberías ver al terminar y los problemas más comunes. ### Planifica tu integración \{#plan-your-integration\} Antes de escribir código, pídele a tu LLM que analice tu proyecto y cree un plan de implementación. Si tu herramienta de IA admite un modo de planificación (como el modo plan de Cursor o Claude Code), úsalo para que el LLM pueda leer tanto la estructura de tu proyecto como la documentación de Adapty antes de escribir cualquier código. Indícale a tu LLM qué enfoque usas para las compras, ya que esto afecta a las guías que debe seguir: - [**Adapty Paywall Builder**](adapty-paywall-builder): Creas los paywalls en el editor no-code de Adapty y el SDK los renderiza automáticamente. - [**Paywalls creados manualmente**](unity-making-purchases): Construyes tu propia interfaz de paywall en código, pero sigues usando Adapty para obtener productos y gestionar compras. - [**Modo Observer**](observer-vs-full-mode): Mantienes tu infraestructura de compras existente y usas Adapty solo para analíticas e integraciones. ¿No sabes cuál elegir? Consulta la [tabla comparativa en la guía de inicio rápido](unity-quickstart-paywalls). ### Instalar y configurar el SDK Añade el paquete del SDK de Adapty a través de Unity Package Manager y actívalo con tu clave pública del SDK. Esta es la base: sin esto, nada más funcionará. **Guía:** [Instalar y configurar el SDK de Adapty](sdk-installation-unity) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/sdk-installation-unity.md ``` :::tip[Checkpoint] - **Esperado:** El proyecto compila y se ejecuta. La consola de Unity muestra el log de activación de Adapty. - **Problema frecuente:** "Public API key is missing" → verifica que hayas reemplazado el marcador con tu clave real de **App settings**. ::: ### Mostrar paywalls y gestionar compras \{#show-paywalls-and-handle-purchases\} Obtén un paywall por ID de placement, muéstralo y gestiona los eventos de compra. Las guías que necesitas dependen de cómo gestionas las compras. Prueba cada compra en el sandbox a medida que avances — no esperes hasta el final. Consulta [Probar compras en sandbox](test-purchases-in-sandbox) para las instrucciones de configuración. <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Paywall Builder" default> **Guías:** - [Habilitar compras con paywalls (inicio rápido)](unity-quickstart-paywalls) - [Obtener paywalls del Paywall Builder y su configuración](unity-get-pb-paywalls) - [Mostrar paywalls](unity-present-paywalls) - [Gestionar eventos del paywall](unity-handling-events) - [Responder a las acciones de los botones](unity-handle-paywall-actions) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/unity-quickstart-paywalls.md - https://adapty.io/docs/es/unity-get-pb-paywalls.md - https://adapty.io/docs/es/unity-present-paywalls.md - https://adapty.io/docs/es/unity-handling-events.md - https://adapty.io/docs/es/unity-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Esperado:** El paywall aparece con los productos configurados. Al pulsar un producto se activa el diálogo de compra en sandbox. - **Problema frecuente:** Paywall vacío o error en `GetPaywall` → verifica que el ID del placement coincide exactamente con el del dashboard y que el placement tiene una audiencia asignada. ::: </TabItem> <TabItem value="manual" label="Paywalls manuales"> **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](unity-quickstart-manual) - [Obtener paywalls y productos](fetch-paywalls-and-products-unity) - [Renderizar paywall diseñado con Remote Config](present-remote-config-paywalls-unity) - [Realizar compras](unity-making-purchases) - [Restaurar compras](unity-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/es/unity-quickstart-manual.md - https://adapty.io/docs/es/fetch-paywalls-and-products-unity.md - https://adapty.io/docs/es/present-remote-config-paywalls-unity.md - https://adapty.io/docs/es/unity-making-purchases.md - https://adapty.io/docs/es/unity-restore-purchase.md :::tip[Checkpoint] - **Esperado:** Tu paywall personalizado muestra los productos obtenidos de Adapty. Al pulsar un producto, aparece el diálogo de compra en sandbox. - **Problema frecuente:** Array de productos vacío → verifica que el paywall tenga productos asignados en el dashboard y que el placement tenga una audiencia. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Guías:** - [Resumen de Observer mode](observer-vs-full-mode) - [Implementar Observer mode](implement-observer-mode-unity) - [Reportar transacciones en Observer mode](report-transactions-observer-mode-unity) :::tip[Checkpoint] - **Esperado:** Tras una compra en sandbox usando tu flujo de compra existente, la transacción aparece en el **Event Feed** del Adapty Dashboard. - **Problema frecuente:** Si no aparecen eventos → verifica que estás reportando transacciones a Adapty y que las notificaciones del servidor están configuradas para ambas stores. ::: </TabItem> </Tabs> ### Comprobar el estado de la suscripción \{#check-subscription-status\} Tras una compra, consulta el perfil del usuario para verificar si hay un nivel de acceso activo y así controlar el acceso al contenido premium. **Guía:** [Comprobar el estado de la suscripción](unity-check-subscription-status) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/unity-check-subscription-status.md ``` :::tip[Checkpoint] - **Resultado esperado:** Tras una compra en sandbox, `profile.AccessLevels["premium"]?.IsActive` devuelve `true`. - **Problema frecuente:** `AccessLevels` vacío tras la compra → comprueba que el producto tiene un nivel de acceso asignado en el dashboard. ::: ### Identificar usuarios \{#identify-users\} Vincula las cuentas de usuario de tu app con los perfiles de Adapty para que las compras persistan entre dispositivos. :::important Omite este paso si tu app no tiene autenticación. ::: **Guía:** [Identificar usuarios](unity-quickstart-identify) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/unity-quickstart-identify.md ``` :::tip[Checkpoint] - **Esperado:** Tras llamar a `Adapty.Identify("your-user-id")`, la sección **Profiles** del dashboard muestra tu ID de usuario personalizado. - **Cuidado:** Llama a `Identify` después de la activación pero antes de obtener los paywalls para evitar una atribución de perfil anónima. ::: ### Preparación para el lanzamiento \{#prepare-for-release\} Una vez que tu integración funcione en el sandbox, repasa el checklist de lanzamiento para asegurarte de que todo está listo para producción. **Guía:** [Checklist de lanzamiento](release-checklist) Envía esto a tu LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/es/release-checklist.md ``` :::tip[Checkpoint] - **Esperado:** Todos los elementos de la lista confirmados: conexiones con la store, notificaciones del servidor, flujo de compra, verificación del nivel de acceso y requisitos de privacidad. - **Problema frecuente:** Notificaciones del servidor ausentes → configura las App Store Server Notifications en **App settings → iOS SDK** y las Google Play Real-Time Developer Notifications en **App settings → Android SDK**. ::: ## Archivos de índice de documentación en texto plano \{#plain-text-doc-index-files\} Si necesitas dar a tu LLM un contexto más amplio más allá de páginas individuales, disponemos de archivos de índice que listan o combinan toda la documentación de Adapty: - [`llms.txt`](https://adapty.io/docs/es/llms.txt): Lista todas las páginas con enlaces `.md`. Es un [estándar emergente](https://llmstxt.org/) para hacer los sitios web accesibles a los LLMs. Ten en cuenta que para algunos agentes de IA (p. ej., ChatGPT) tendrás que descargar `llms.txt` y subirlo al chat como archivo. - [`llms-full.txt`](https://adapty.io/docs/es/llms-full.txt): Toda la documentación de Adapty combinada en un único archivo. Es muy grande: úsalo solo cuando necesites una visión completa. - [`unity-llms.txt`](https://adapty.io/docs/es/unity-llms.txt) y [`unity-llms-full.txt`](https://adapty.io/docs/es/unity-llms-full.txt) específicos de Unity: subconjuntos por plataforma que ahorran tokens en comparación con el sitio completo. --- # File: unity-get-pb-paywalls --- --- title: "Obtener paywalls del Paywall Builder y su configuración en el SDK de Unity" description: "Aprende cómo recuperar paywalls de PB en Adapty para un mejor control de suscripciones en tu app de Unity." --- Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el nuevo Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu app móvil. El primer paso es obtener el paywall asociado al placement y su configuración de vista, tal como se describe a continuación. :::warning El nuevo Paywall Builder funciona con la versión 3.3.0 o superior del SDK de Unity. ::: Ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Si estás implementando tus paywalls de forma manual, consulta el tema [Obtener paywalls y productos para paywalls de Remote Config en tu app móvil](fetch-paywalls-and-products-unity). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a mostrar paywalls en tu app móvil (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall](create-placement) en el Adapty Dashboard. 4. Instala el [SDK de Adapty](sdk-installation-unity) en tu app móvil. </details> ## Obtener el paywall diseñado con Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Si has [diseñado un paywall con el Paywall Builder](adapty-paywall-builder), no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese tipo de paywall contiene tanto lo que se debe mostrar como la forma en que debe mostrarse. Aun así, necesitas obtener su ID a través del placement, su configuración de vista, y luego presentarlo en tu app móvil. Para garantizar un rendimiento óptimo, es fundamental recuperar el paywall y su [configuración de vista](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) lo antes posible, dando suficiente tiempo para que las imágenes se descarguen antes de presentarlas al usuario. Para obtener un paywall, usa el método `GetPaywall`: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-paywall-locale-in-adapty-paywall-builder). Se espera que este parámetro sea un código de idioma compuesto de uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta es para el idioma y la segunda para la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios puede que no obtengan los datos más recientes, pero disfrutarán de tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se desinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché actualizada regularmente descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el respaldo local.</p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede consistir en diferentes solicitudes internamente.</p> | Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Un objeto [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) con una lista de IDs de productos, el identificador del paywall, el Remote Config y otras propiedades. | ## Obtener la configuración de vista del paywall diseñado con Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Asegúrate de activar el botón **Show on device** en el Paywall Builder. Si esta opción no está activada, la configuración de vista no estará disponible para recuperar. ::: Después de obtener el paywall, comprueba si incluye un `ViewConfiguration`, lo que indica que fue creado con el Paywall Builder. Esto te guiará sobre cómo mostrar el paywall. Si el `ViewConfiguration` está presente, trátalo como un paywall del Paywall Builder; si no, [trátalo como un paywall de Remote Config](present-remote-config-paywalls-unity). En el SDK de Unity, llama directamente al método `CreatePaywallView` sin necesidad de obtener primero la configuración de vista de forma manual. :::warning El resultado del método `CreatePaywallView` solo puede usarse una vez. Si necesitas usarlo de nuevo, vuelve a llamar al método `CreatePaywallView`. Llamarlo dos veces sin recrearlo puede provocar el error `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers var parameters = new AdaptyUICreatePaywallViewParameters() .SetPreloadProducts(preloadProducts) .SetLoadTimeout(new TimeSpan(0, 0, 3)); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { // handle the result }); ``` Parámetros: | Parámetro | Presencia | Descripción | | :------------------ | :------------- | :----------------------------------------------------------- | | **paywall** | obligatorio | Un objeto `AdaptyPaywall` para obtener un controlador del paywall deseado. | | **loadTimeout** | por defecto: 5 seg | Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el respaldo local. Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede consistir en diferentes solicitudes internamente. | | **PreloadProducts** | opcional | Proporciona un array de `AdaptyPaywallProducts` para optimizar el tiempo de visualización de los productos en pantalla. Si se pasa `nil`, AdaptyUI obtendrá automáticamente los productos necesarios. | | **CustomTags** | opcional | Define un diccionario de etiquetas personalizadas y sus valores resueltos. Las etiquetas personalizadas actúan como marcadores de posición en el contenido del paywall, reemplazados dinámicamente por cadenas específicas para personalizar el contenido. Consulta el tema sobre etiquetas personalizadas en el Paywall Builder para más detalles. | | **CustomTimers** | opcional | Define un diccionario de temporizadores personalizados y sus fechas de finalización. Los temporizadores personalizados te permiten mostrar cuentas atrás en tu paywall. | :::note Si usas varios idiomas, aprende cómo añadir una [localización al Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) y cómo usar los códigos de idioma correctamente [aquí](localizations-and-locale-codes). ::: Una vez que tengas la vista, [presenta el paywall](unity-present-paywalls). ## Personalizar recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu paywall, implementa los recursos personalizados. Las imágenes y vídeos destacados tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizados, apuntas a estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el dashboard de Adapty. Por ejemplo, puedes: - Mostrar una imagen o vídeo diferente a algunos usuarios. - Mostrar una imagen de vista previa local mientras se carga la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. :::important Para usar esta función, actualiza el SDK de Adapty para Unity a la versión 3.8.0 o superior. ::: Aquí tienes un ejemplo de cómo proporcionar recursos personalizados mediante un diccionario simple: ```csharp showLineNumbers var customAssets = new Dictionary<string, AdaptyCustomAsset> { { "custom_image", AdaptyCustomAsset.LocalImageFile("custom_assets/images/custom_image.png") }, { "hero_video", AdaptyCustomAsset.LocalVideoFile("custom_assets/videos/custom_video.mp4") } }; var parameters = new AdaptyUICreatePaywallViewParameters() .SetCustomAssets(customAssets) .SetLoadTimeout(new TimeSpan(0, 0, 3)); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { // handle the result }); ``` :::note Si no se encuentra un recurso, el paywall usará su apariencia predeterminada. ::: ## Configurar temporizadores definidos por el desarrollador \{#set-up-developer-defined-timers\} Para usar temporizadores personalizados en tu app de Unity, puedes pasar un diccionario de IDs de temporizadores y sus fechas de finalización directamente al método `SetCustomTimers`. Aquí tienes un ejemplo: ```csharp showLineNumbers var customTimers = new Dictionary<string, DateTime> { { "CUSTOM_TIMER_6H", DateTime.Now.AddHours(6) }, { "CUSTOM_TIMER_NY", new DateTime(2025, 1, 1) } }; var parameters = new AdaptyUICreatePaywallViewParameters() .SetCustomTimers(customTimers) .SetLoadTimeout(new TimeSpan(0, 0, 3)); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { // handle the result }); ``` En este ejemplo, `CUSTOM_TIMER_NY` y `CUSTOM_TIMER_6H` son los **Timer ID** de los temporizadores definidos por el desarrollador que configuraste en el Adapty Dashboard. El resolvedor de temporizadores garantiza que tu app actualice dinámicamente cada temporizador con el valor correcto. Por ejemplo: - `CUSTOM_TIMER_NY`: El tiempo restante hasta que finalice el temporizador, como el día de Año Nuevo. - `CUSTOM_TIMER_6H`: El tiempo restante en un período de 6 horas que comenzó cuando el usuario abrió el paywall. ## Acelerar la obtención del paywall con el paywall de la audiencia predeterminada \{#speed-up-paywall-fetching-with-default-audience-paywall\} Normalmente, los paywalls se obtienen casi al instante, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, en casos en que tienes muchas audiencias y paywalls, y tus usuarios tienen una conexión a internet lenta, obtener un paywall puede tardar más de lo deseable. En esas situaciones, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ningún paywall. Para solucionar esto, puedes usar el método `GetPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, como se detalla en la sección [Obtener el paywall](#fetch-paywall) anterior. :::warning Considera usar `GetPaywall` en lugar de `GetPaywallForDefaultAudience`, ya que este último tiene limitaciones importantes: - **Problemas de compatibilidad**: Puede generar problemas al dar soporte a varias versiones de la app, requiriendo diseños compatibles con versiones anteriores o aceptando que las versiones más antiguas puedan mostrarse incorrectamente. - **Sin personalización**: Solo muestra contenido para la audiencia "All Users", eliminando la segmentación por país, atribución o atributos personalizados. Si la mayor velocidad de obtención compensa estos inconvenientes en tu caso de uso, usa `GetPaywallForDefaultAudience` como se muestra a continuación. De lo contrario, usa `GetPaywall` como se describe [arriba](#fetch-paywall). ::: ```csharp showLineNumbers Adapty.GetPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del paywall. Se espera que este parámetro sea un código de idioma compuesto de uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta es para el idioma y la segunda para la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios puede que no obtengan los datos más recientes, pero disfrutarán de tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se desinstala la app o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché actualizada regularmente descrita anteriormente y los paywalls de respaldo. También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | --- # File: unity-present-paywalls --- --- title: "Mostrar paywalls" description: "Aprende cómo mostrar paywalls en tu app de Unity con el SDK de Adapty." --- Si has personalizado un paywall con el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese paywall contiene tanto lo que debe mostrarse como la forma en que debe hacerse. :::warning Esta guía cubre el **nuevo Paywall Builder**, que requiere el SDK de Adapty 3.3.0 o posterior. Para mostrar paywalls con Remote Config, consulta [Renderizar paywalls diseñados con remote config](present-remote-config-paywalls). ::: Para mostrar un paywall, usa el método `view.Present()` sobre el `view` creado por el método [`CreatePaywallView`](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Cada `view` solo puede usarse una vez. Si necesitas mostrar el paywall de nuevo, llama a `CreatePaywallView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo puede provocar el error `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers title="Unity" view.Present((error) => { // handle the error }); ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Mostrar diálogo \{#show-dialog\} Usa este método en lugar de los diálogos de alerta nativos cuando hay un paywall visible en Android. En Android, las alertas normales aparecen detrás del paywall y el usuario no puede verlas. Este método garantiza que el diálogo se muestre correctamente por encima del paywall en todas las plataformas. ```csharp showLineNumbers title="Unity" var dialog = new AdaptyUIDialogConfiguration() .SetTitle("Close paywall?") .SetContent("You will lose access to exclusive offers.") .SetDefaultActionTitle("Stay") .SetSecondaryActionTitle("Close"); AdaptyUI.ShowDialog(view, dialog, (action, error) => { if (error == null) { if (action == AdaptyUIDialogActionType.Secondary) { // User confirmed - close the paywall view.Dismiss(); } // If primary - do nothing, user stays } }); ``` ## Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el paywall en iOS pasando el parámetro `iosPresentationStyle` al método `Present()`. El parámetro acepta los valores `AdaptyUIIOSPresentationStyle.FullScreen` (predeterminado) o `AdaptyUIIOSPresentationStyle.PageSheet`. ```csharp showLineNumbers title="Unity" view.Present(AdaptyUIIOSPresentationStyle.PageSheet, (error) => { // handle the error }); ``` --- # File: unity-handle-paywall-actions --- --- title: "Responder a acciones de botones en el SDK de Unity" description: "Gestiona las acciones de botones de paywall en Unity usando Adapty para una mejor monetización de la app." --- Si estás creando paywalls con el Paywall Builder de Adapty, es fundamental configurar los botones correctamente: 1. Añade un [botón en el Paywall Builder](paywall-buttons) y asígnale una acción existente o crea un ID de acción personalizado. 2. Escribe código en tu app para gestionar cada acción que hayas asignado. Esta guía muestra cómo gestionar acciones personalizadas y predefinidas en tu código. :::warning **Solo las compras y restauraciones se gestionan automáticamente.** El resto de acciones de botones, como cerrar paywalls o abrir enlaces, requieren implementar respuestas específicas en el código de la app. ::: ## Cerrar paywalls \{#close-paywalls\} Para añadir un botón que cierre tu paywall: 1. En el Paywall Builder, añade un botón y asígnale la acción **Close**. 2. En el código de tu app, implementa un handler para la acción `close` que descarte el paywall. ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: view.Dismiss(null); break; default: // handle other events break; } } ``` ## Abrir URLs desde paywalls \{#open-urls-from-paywalls\} :::tip Si quieres añadir un grupo de enlaces (p. ej., términos de uso y restauración de compras), añade un elemento **Link** en el Paywall Builder y gestíonalo igual que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu paywall (p. ej., **Terms of use** o **Privacy policy**): 1. En el Paywall Builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieres abrir. 2. En el código de tu app, implementa un handler para la acción `openUrl` que abra la URL recibida en un navegador. ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.OpenUrl: var urlString = action.Value; if(!string.IsNullOrWhiteSpace(urlString)) { Application.OpenURL(urlString); } break; default: // handle other events break; } } ``` ## Iniciar sesión en la app \{#log-into-the-app\} Para añadir un botón que permita a los usuarios iniciar sesión en tu app: 1. En el Paywall Builder, añade un botón y asígnale la acción **Custom** con el ID `login`. 2. En el código de tu app, implementa un handler para la acción personalizada `login` que identifique a tu usuario. ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Custom: if (action.Value == "login") { // Navigate to login scene SceneManager.LoadScene("LoginScene"); } break; default: // handle other events break; } } ``` ## Gestionar acciones personalizadas \{#handle-custom-actions\} Para añadir un botón que gestione cualquier otra acción: 1. En el Paywall Builder, añade un botón, asígnale la acción **Custom** y asígnale un ID. 2. En el código de tu app, implementa un handler para el ID de acción que hayas creado. Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro paywall: ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Custom: if (action.Value == "openNewPaywall") { // Display another paywall ShowAlternativePaywall(); } break; default: // handle other events break; } } private void ShowAlternativePaywall() { // Implement your logic to show alternative paywall } ``` --- # File: unity-handling-events --- --- title: "Gestionar eventos del paywall" description: "Aprende a gestionar eventos del paywall en tu aplicación Unity con el SDK de Adapty." --- :::important Esta guía cubre la gestión de eventos para compras, restauraciones, selección de productos y renderizado del paywall. También debes implementar el manejo de botones (cerrar paywall, abrir enlaces, etc.). Consulta nuestra [guía sobre el manejo de acciones de botones](unity-handle-paywall-actions) para más detalles. ::: Los paywalls configurados con el [Paywall Builder](adapty-paywall-builder) no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan ciertos eventos a los que tu aplicación puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el paywall. A continuación aprenderás cómo responder a estos eventos. :::warning Esta guía es **exclusivamente para paywalls del nuevo Paywall Builder**, que requieren el SDK de Adapty v3.3.0 o posterior. ::: :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Gestión de eventos \{#handling-events\} Para controlar o monitorear los procesos que ocurren en la pantalla del paywall dentro de tu aplicación móvil, implementa la interfaz `AdaptyPaywallsEventsListener`: ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; public class PaywallEventsHandler : MonoBehaviour, AdaptyPaywallsEventsListener { void Start() { Adapty.SetPaywallsEventsListener(this); } // Implement all required interface methods below } ``` ### Eventos generados por el usuario \{#user-generated-events\} #### Paywall mostrado \{#paywall-appeared\} Se invoca cuando la vista del paywall aparece en pantalla. :::note En iOS, también se invoca cuando el usuario pulsa el [botón del web paywall](web-paywall#step-2a-add-a-web-purchase-button) dentro de un paywall y el web paywall se abre en un navegador integrado. ::: ```csharp showLineNumbers title="Unity" public void PaywallViewDidAppear(AdaptyUIPaywallView view) { } ``` #### Paywall ocultado \{#paywall-disappeared\} Se invoca cuando la vista del paywall desaparece de la pantalla. :::note En iOS, también se invoca cuando un [web paywall](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un paywall en un navegador integrado desaparece de la pantalla. ::: ```csharp showLineNumbers title="Unity" public void PaywallViewDidDisappear(AdaptyUIPaywallView view) { } ``` #### Selección de producto \{#product-selection\} Se invoca cuando se selecciona un producto para comprar (por el usuario o por el sistema). ```csharp showLineNumbers title="Unity" public void PaywallViewDidSelectProduct( AdaptyUIPaywallView view, string productId ) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "productId": "premium_monthly" } ``` </Details> #### Compra iniciada \{#started-purchase\} Se invoca cuando el usuario inicia el proceso de compra. ```csharp showLineNumbers title="Unity" public void PaywallViewDidStartPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product ) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### Compra exitosa, cancelada o pendiente \{#successful-canceled-or-pending-purchase\} Si la compra se completa correctamente, el usuario la cancela, o queda en estado pendiente, se invocará este método. Las cancelaciones del usuario y los pagos pendientes (como los que requieren aprobación parental) activan este método, no `PaywallViewDidFailPurchase`. ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult ) { } ``` <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```javascript // Successful purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } } } // Cancelled purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "UserCancelled" } } // Pending purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Pending" } } ``` </Details> Recomendamos cerrar la pantalla en ese caso. #### Compra fallida \{#failed-purchase\} Si una compra falla debido a un error, se invocará este método. Esto incluye errores de StoreKit/Google Play Billing (restricciones de pago, productos inválidos, fallos de red), errores de verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones del usuario activan `PaywallViewDidFinishPurchase` con un resultado de cancelación, y los pagos pendientes no activan este método. ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error ) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } ``` </Details> #### Restauración iniciada \{#started-restore\} Se invoca cuando el usuario inicia el proceso de restauración: ```csharp showLineNumbers title="Unity" public void PaywallViewDidStartRestore(AdaptyUIPaywallView view) { } ``` #### Restauración exitosa \{#successful-restore\} Se invoca cuando la restauración de compras se completa correctamente: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishRestore( AdaptyUIPaywallView view, AdaptyProfile profile ) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ``` </Details> Recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de la suscripción](unity-listen-subscription-changes) para aprender cómo verificarlo. #### Restauración fallida \{#failed-restore\} Se invoca cuando la restauración de compras falla: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailRestore( AdaptyUIPaywallView view, AdaptyError error ) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> #### Navegación web de pago finalizada \{#finished-web-payment-navigation\} Después de intentar abrir un [web paywall](web-paywall) para realizar una compra (tanto si tuvo éxito como si falló), se invocará este método: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishWebPaymentNavigation( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error ) { } ``` **Parámetros:** - `product`: El producto para el que se abrió (o intentó abrir) el web paywall - `error`: `null` si el web paywall se abrió correctamente, o un `AdaptyError` si falló <Details> <summary>Ejemplos de eventos (Haz clic para expandir)</summary> ```javascript // Successful navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "wrong_param", "message": "Current method is not available for this product", "details": { "underlyingError": "Product not configured for web purchases" } } } ``` </Details> ### Obtención de datos y renderizado \{#data-fetching-and-rendering\} #### Errores de carga de productos \{#product-loading-errors\} Se invoca cuando falla la carga de productos y proporciona un `AdaptyError`. Si no pasaste el array de productos durante la inicialización, AdaptyUI recuperará los objetos necesarios del servidor por sí solo. Esta operación puede fallar, y AdaptyUI reportará el error invocando este método: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailLoadingProducts( AdaptyUIPaywallView view, AdaptyError error ) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> #### Errores de renderizado \{#rendering-errors\} Se invoca cuando ocurre un error durante el renderizado de la interfaz y proporciona un `AdaptyError`: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailRendering( AdaptyUIPaywallView view, AdaptyError error ) { } ``` <Details> <summary>Ejemplo de evento (Haz clic para expandir)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ``` </Details> En condiciones normales, estos errores no deberían producirse, por lo que si encuentras alguno, por favor haznos saber. --- # File: unity-web-paywalls --- --- title: "Implementar web paywalls en Unity SDK" description: "Configura un web paywall para cobrar sin las comisiones y auditorías del App Store." --- :::important Antes de comenzar, asegúrate de haber [configurado tu web paywall en el dashboard](web-paywall) y de tener instalada la versión 3.14 o posterior del SDK de Adapty. ::: ## Paywalls web abiertos \{#open-web-paywalls\} Si trabajas con un paywall desarrollado por ti mismo, debes gestionar los paywalls web mediante el método del SDK. El método `Adapty.OpenWebPaywall`: 1. Genera una URL única que permite a Adapty vincular un paywall concreto mostrado a un usuario específico con la página web a la que es redirigido. 2. Detecta cuándo tus usuarios vuelven a la app y, a continuación, solicita `Adapty.GetProfile` a intervalos cortos para determinar si los derechos de acceso del perfil se han actualizado. De esta forma, si el pago fue exitoso y los derechos de acceso se actualizaron, la suscripción se activa en la app casi de inmediato. ```csharp showLineNumbers title="Unity" Adapty.OpenWebPaywall( product, (error) => { if (error != null) { Debug.LogError($"Failed to open web paywall: {error.Message}"); } else { Debug.Log("Web paywall opened successfully"); } } ); ``` :::note Hay dos versiones del método `OpenWebPaywall`: 1. `OpenWebPaywall(product)` que genera URLs por paywall y también añade los datos del producto a las URLs. 2. `OpenWebPaywall(paywall)` que genera URLs por paywall sin añadir los datos del producto a las URLs. Úsala cuando tus productos en el paywall de Adapty sean distintos de los del paywall web. ::: #### Gestión de errores \{#handle-errors\} | Código de error | Descripción | Acción recomendada | |-----------|--------------------------------------------------------|---------------------------------------------------------------------------| | `AdaptyErrorCode.WrongParam` | El paywall o producto no tiene configurada una URL de compra web, o no se pudo abrir la URL en el navegador | Consulta el mensaje de error para más detalles. Verifica la configuración del paywall/producto en el Adapty Dashboard, o comprueba la configuración del dispositivo. | | `AdaptyErrorCode.DecodingFailed` | No se pudieron codificar correctamente los parámetros en la URL | Verifica que los parámetros de la URL sean válidos y estén correctamente formateados | :::note Comprueba la propiedad `Message` del error para obtener detalles específicos sobre qué salió mal, ya que `WrongParam` puede indicar varios problemas (URL de compra faltante, fallo al abrir el navegador, etc.). ::: ## Abre web paywalls en un navegador in-app \{#open-web-paywalls-in-an-in-app-browser\} :::important La apertura de web paywalls en un navegador in-app está disponible a partir de la versión 3.15 del SDK de Adapty. ::: Por defecto, los web paywalls se abren en el navegador externo, lo que saca a los usuarios de tu aplicación. Para ofrecer una experiencia más fluida, puedes abrirlos en un navegador in-app. Así, la página de compra web se muestra dentro de tu aplicación y los usuarios pueden completar las transacciones sin cambiar de app. Para habilitarlo, pasa `AdaptyWebPresentation.InAppBrowser` al método `OpenWebPaywall`: ```csharp showLineNumbers title="Unity" Adapty.OpenWebPaywall( product, AdaptyWebPresentation.InAppBrowser, // default — ExternalBrowser (error) => { if (error != null) { Debug.LogError($"Failed to open web paywall: {error.Message}"); } else { Debug.Log("Web paywall opened successfully"); } } ); ``` --- # File: unity-use-fallback-paywalls --- --- title: "Unity - Usar paywalls de respaldo" description: "Gestiona los casos en los que los usuarios están sin conexión o los servidores de Adapty no están disponibles" --- :::warning Los paywalls de respaldo son compatibles con Unity SDK v2.11 y versiones posteriores. ::: Para mantener una experiencia de usuario fluida, es importante configurar [respaldos](/fallback-paywalls) para tus flows, [paywalls](paywalls) y [onboardings](onboardings). Esta precaución amplía las capacidades de la aplicación en caso de pérdida parcial o total de la conexión a internet. * **Si la aplicación no puede acceder a los servidores de Adapty:** Podrá mostrar un flow o paywall de respaldo, y acceder a la configuración local del onboarding. * **Si la aplicación no puede acceder a internet:** Podrá mostrar un flow o paywall de respaldo. Los onboardings incluyen contenido remoto y requieren conexión a internet para funcionar. :::important Antes de seguir los pasos de esta guía, [descarga](/local-fallback-paywalls) los archivos de configuración de respaldo desde Adapty. ::: ## Configuración \{#configuration\} 1. Añade los archivos de configuración de respaldo al directorio común `Assets/StreamingAssets` de tu proyecto. 2. Llama al método `.setFallback` **antes** de obtener el paywall o el onboarding de destino. ```csharp using UnityEngine; using AdaptySDK; #if UNITY_IOS string fileName = "ios_fallback.json"; #elif UNITY_ANDROID string fileName = "android_fallback.json"; #else // Optional: handle Editor or other platforms string fileName = "fallback.json"; #endif Adapty.SetFallback(fileName, (error) => { if (error != null) { Debug.LogError($"Failed to set fallback: {error}"); return; } // Fallback set successfully }); ``` Parámetros: | Parámetro | Descripción | |:-------------|:-----------------------------------------------------| | **fileName** | La cadena con el nombre del archivo de configuración de respaldo. | --- # File: unity-localizations-and-locale-codes --- --- title: "Usar localizaciones y códigos de idioma en Unity SDK" description: "Aprende a localizar paywalls en tu app de Unity con el SDK de Adapty." --- ## Por qué esto es importante \{#why-this-is-important\} Hay algunos escenarios en los que los códigos de idioma entran en juego; por ejemplo, cuando intentas obtener el paywall correcto para la localización actual de tu app. Como los códigos de idioma son complejos y pueden variar de una plataforma a otra, nos apoyamos en un estándar interno para todas las plataformas que soportamos. Sin embargo, precisamente por esa complejidad, es muy importante que entiendas exactamente qué estás enviando a nuestro servidor para obtener la localización correcta y qué ocurre después, de modo que siempre recibas lo que esperas. ## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\} Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código se compone de subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués (Brasil)), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de configuración regional \{#locale-code-matching\} Cuando Adapty recibe una llamada desde el SDK con el código de configuración regional y comienza a buscar la localización correspondiente de un paywall, ocurre lo siguiente: 1. La cadena de configuración regional entrante se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. A continuación, se busca la localización cuyo código de configuración regional coincida exactamente 3. Si no se encuentra ninguna coincidencia, se toma la subcadena anterior al primer guion (`pt` para `pt-br`) y se busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, se devuelve la localización predeterminada `en` De este modo, un dispositivo iOS que envió `'pt_BR'`, un dispositivo Android que envió `pt-BR` y otro dispositivo que envió `pt-br` obtendrán el mismo resultado. ## Implementación de localizaciones: forma recomendada \{#implementing-localizations-recommended-way\} Si te estás preguntando cómo gestionar las localizaciones, probablemente ya estés trabajando con archivos de cadenas localizadas en tu proyecto. En ese caso, te recomendamos añadir un par clave-valor con el código de idioma de Adapty correspondiente en cada uno de esos archivos. Luego, extrae el valor de esa clave al llamar a nuestro SDK, así: ```csharp showLineNumbers // 1. Modify your localization files (e.g., using Unity's Localization package) /* en.json */ { "adapty_paywalls_locale": "en" } /* es.json */ { "adapty_paywalls_locale": "es" } /* pt-BR.json */ { "adapty_paywalls_locale": "pt-br" } // 2. Extract and use the locale code using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Settings; using AdaptySDK; public class PaywallManager : MonoBehaviour { public async void FetchPaywall() { // Get the current locale from Unity's Localization system var locale = LocalizationSettings.SelectedLocale; var localeCode = GetAdaptyLocaleCode(locale); // Pass locale code to Adapty.GetPaywall or Adapty.GetPaywallForDefaultAudience method Adapty.GetPaywall("placement_id", localeCode, (paywall, error) => { if (error != null) { // handle the error return; } // Use the paywall }); } private string GetAdaptyLocaleCode(Locale locale) { // Convert Unity locale to Adapty format var localeIdentifier = locale.Identifier.Code; return localeIdentifier.ToLower().Replace('_', '-'); } } ``` Así te aseguras de tener control total sobre qué localización se recuperará para cada usuario de tu app. ## Implementar localizaciones: otra forma \{#implementing-localizations-the-other-way\} Puedes obtener resultados similares (aunque no idénticos) sin definir explícitamente códigos de idioma para cada localización. Esto implicaría extraer un código de idioma de otros objetos que proporciona tu plataforma, así: ```csharp showLineNumbers using UnityEngine; using System.Globalization; using AdaptySDK; public class PaywallManager : MonoBehaviour { public void FetchPaywall() { var localeCode = GetSystemLocaleCode(); // Pass locale code to Adapty.GetPaywall or Adapty.GetPaywallForDefaultAudience method Adapty.GetPaywall("placement_id", localeCode, (paywall, error) => { if (error != null) { // handle the error return; } // Use the paywall }); } private string GetSystemLocaleCode() { // Get the system's current culture var culture = CultureInfo.CurrentCulture; var languageCode = culture.TwoLetterISOLanguageName; var regionCode = culture.Name.Contains('-') ? culture.Name.Split('-')[1] : null; if (!string.IsNullOrEmpty(regionCode)) { return $"{languageCode}-{regionCode.ToLower()}"; } return languageCode; } } ``` Ten en cuenta que no recomendamos este enfoque por varias razones: 1. En iOS, los idiomas preferidos y la configuración regional actual no son idénticos. Si quieres que la localización se seleccione correctamente, tendrás que apoyarte en la lógica de Apple, que funciona de forma nativa si usas el enfoque recomendado con archivos de cadenas localizadas, o recrearla manualmente. 2. Es difícil predecir exactamente qué recibirá el servidor de Adapty. Por ejemplo, en iOS es posible obtener una configuración regional como `ar_OM@numbers='latn'` en un dispositivo y enviarla a nuestro servidor. En ese caso, en lugar de la localización `ar-om` que buscabas, recibirás `ar`, lo cual probablemente no es lo esperado. Aun así, si decides optar por este enfoque, asegúrate de cubrir todos los casos de uso relevantes. --- # File: unity-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del Paywall Builder en el SDK de Unity" description: "Solucionar problemas del Paywall Builder en el SDK de Unity" --- Esta guía te ayuda a resolver los problemas más comunes al usar paywalls diseñados en el Adapty Paywall Builder con el SDK de Unity. ## Falla al obtener la configuración de un paywall \{#getting-a-paywall-configuration-fails\} **Problema**: El método `CreateView` no puede obtener la configuración del paywall. **Causa**: El paywall no está habilitado para mostrarse en el dispositivo desde el Paywall Builder. **Solución**: Activa el interruptor **Show on device** en el Paywall Builder. <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## El número de vistas del paywall es demasiado alto \{#the-paywall-view-number-is-too-big\} **Problema**: El contador de vistas del paywall muestra el doble del número esperado. **Causa**: Es posible que estés llamando a `LogShowPaywall` en tu código, lo que duplica el contador de vistas cuando se usa el Paywall Builder. Para los paywalls diseñados con el Paywall Builder, el seguimiento de análisis es automático, por lo que no es necesario usar este método. **Solución**: Asegúrate de no llamar a `LogShowPaywall` en tu código si estás usando el Paywall Builder. ## Otros problemas \{#other-issues\} **Problema**: Estás experimentando otros problemas relacionados con el Paywall Builder que no se han cubierto anteriormente. **Solución**: Si es necesario, migra el SDK a la versión más reciente usando las [guías de migración](unity-sdk-migration-guides). Muchos problemas se resuelven en versiones más nuevas del SDK. --- # File: unity-quickstart-manual --- --- title: "Habilitar compras en tu paywall personalizado con Unity SDK" description: "Integra el SDK de Adapty en tus paywalls personalizados de Unity para habilitar compras in-app." --- Esta guía describe cómo integrar Adapty en tus paywalls personalizados. Mantén el control total sobre la implementación del paywall, mientras el SDK de Adapty obtiene los productos, gestiona las nuevas compras y restaura las anteriores. :::important **Esta guía está dirigida a desarrolladores que implementan paywalls personalizados.** Si quieres la forma más sencilla de habilitar compras, usa el [Adapty Paywall Builder](unity-quickstart-paywalls). Con Paywall Builder, creas paywalls en un editor visual sin código, Adapty gestiona toda la lógica de compras automáticamente y puedes probar distintos diseños sin volver a publicar tu app. ::: ## Antes de empezar \{#before-you-start\} ### Configura los productos \{#set-up-products\} Para habilitar las compras in-app, necesitas entender tres conceptos clave: - [**Productos**](product) – cualquier cosa que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls**](paywalls) – configuraciones que definen qué productos ofrecer. En Adapty, los paywalls son la única forma de obtener productos, pero este diseño te permite modificar productos, precios y ofertas sin tocar el código de tu app. - [**Placements**](placements) – dónde y cuándo muestras los paywalls en tu app (como `main`, `onboarding`, `settings`). Configuras los paywalls para los placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar distintos paywalls a diferentes usuarios. Asegúrate de entender estos conceptos incluso si trabajas con tu paywall personalizado. Básicamente, son solo tu forma de gestionar los productos que vendes en tu app. Para implementar tu paywall personalizado, necesitarás crear un **paywall** y añadirlo a un **placement**. Esta configuración te permite obtener tus productos. Para saber qué debes hacer en el dashboard, sigue la guía de inicio rápido [aquí](quickstart). ### Gestiona usuarios \{#manage-users\} Puedes trabajar con o sin autenticación de backend en tu lado. Sin embargo, el SDK de Adapty gestiona de forma diferente a los usuarios anónimos e identificados. Lee la [guía de inicio rápido de identificación](unity-quickstart-identify) para entender las particularidades y asegurarte de que trabajas correctamente con los usuarios. ## Paso 1. Obtén los productos \{#step-1-get-products\} Para obtener los productos de tu paywall personalizado, necesitas: 1. Obtener el objeto `paywall` pasando el ID del [placement](placements) al método `getPaywall`. 2. Obtener el array de productos para este paywall usando el método `getPaywallProducts`. ```csharp showLineNumbers using AdaptySDK; void LoadPaywall() { Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if (error != null) { // Handle the error return; } Adapty.GetPaywallProducts(paywall, (products, productsError) => { if (productsError != null) { // Handle the error return; } // Use products to build your custom paywall UI }); }); } ``` ## Paso 2. Acepta compras \{#step-2-accept-purchases\} Cuando un usuario toca un producto en tu paywall personalizado, llama al método `makePurchase` con el producto seleccionado. Esto gestionará el flujo de compra y devolverá el perfil actualizado. ```csharp showLineNumbers using AdaptySDK; void PurchaseProduct(AdaptyPaywallProduct product) { Adapty.MakePurchase(product, (result, error) => { if (error != null) { // Handle the error return; } switch (result.Type) { case AdaptyPurchaseResultType.Success: var profile = result.Profile; // Purchase successful, profile updated break; case AdaptyPurchaseResultType.UserCancelled: // User canceled the purchase break; case AdaptyPurchaseResultType.Pending: // Purchase is pending (e.g., user will pay offline with cash) break; } }); } ``` ## Paso 3. Restaura compras \{#step-3-restore-purchases\} Los app stores requieren que todas las apps con suscripciones ofrezcan una forma de que los usuarios puedan restaurar sus compras. Llama al método `restorePurchases` cuando el usuario toque el botón de restaurar. Esto sincronizará su historial de compras con Adapty y devolverá el perfil actualizado. ```csharp showLineNumbers using AdaptySDK; void RestorePurchases() { Adapty.RestorePurchases((profile, error) => { if (error != null) { // Handle the error return; } // Restore successful, profile updated }); } ``` ## Próximos pasos \{#next-steps\} :::tip ¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte! ::: Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox de App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. A continuación, [comprueba si los usuarios han completado su compra](unity-check-subscription-status) para determinar si mostrar el paywall o dar acceso a las funciones de pago. --- # File: fetch-paywalls-and-products-unity --- --- title: "Obtener paywalls y productos para paywalls con Remote Config en Unity SDK" description: "Obtén paywalls y productos en el SDK de Unity de Adapty para mejorar la monetización de los usuarios." --- Antes de mostrar paywalls con Remote Config y personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls con Remote Config y personalizados. Para obtener orientación sobre cómo recuperar paywalls creados con Paywall Builder, consulta [Obtener paywalls de Paywall Builder y su configuración](unity-get-pb-paywalls). :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: <details> <summary>Antes de empezar a obtener paywalls y productos en tu app (haz clic para expandir)</summary> 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall en el placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-unity) en tu app. </details> ## Obtener información del paywall \{#fetch-paywall-information\} En Adapty, un [producto](product) es una combinación de productos del App Store y Google Play. Estos productos multiplataforma se integran en paywalls, lo que te permite mostrarlos en placements específicos de tu app. Para mostrar los productos, necesitas obtener un [Paywall](paywalls) de uno de tus [placements](placements) con el método `getPaywall`. :::important **No escribas los IDs de producto en el código.** El único ID que debes incluir en el código es el ID del placement. Los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si hoy un paywall devuelve dos productos y mañana tres, muéstralos todos sin cambiar el código. ::: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p><p>Consulta [Localizaciones y códigos de idioma](unity-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen conexiones a internet inestables, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls en dos capas: la caché actualizada regularmente descrita anteriormente y los [paywalls de respaldo](unity-use-fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el respaldo local.</p><p></p><p>Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede constar de diferentes solicitudes internamente.</p> | ¡No escribas los IDs de producto en el código! Dado que los paywalls se configuran de forma remota, los productos disponibles, el número de productos y las ofertas especiales (como pruebas gratuitas) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios. Por ejemplo, si inicialmente obtienes 2 productos, tu app debería mostrar esos 2 productos. Sin embargo, si más adelante obtienes 3 productos, tu app debería mostrar los 3 sin requerir cambios en el código. Lo único que tienes que incluir en el código es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Un objeto [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) con: una lista de IDs de producto, el identificador del paywall, el Remote Config y varias otras propiedades. | ## Obtener productos \{#fetch-products\} Una vez que tienes el paywall, puedes consultar el array de productos correspondiente: ```csharp showLineNumbers Adapty.GetPaywallProducts(paywall, (products, error) => { if(error != null) { // handle the error return; } // products - the requested products array }); ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Lista de objetos [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y varias otras propiedades. | Al implementar tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html). A continuación se muestran las propiedades más utilizadas, pero consulta el documento enlazado para obtener todos los detalles de las propiedades disponibles. | Propiedad | Descripción | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Para mostrar el título del producto, usa `product.LocalizedTitle`. Ten en cuenta que la localización se basa en el país del store seleccionado por el usuario, no en el idioma del propio dispositivo. | | **Price** | Para mostrar una versión localizada del precio, usa `product.Price.LocalizedString`. Esta localización se basa en la configuración regional del dispositivo. También puedes acceder al precio como número usando `product.Price.Amount`. El valor se proporcionará en la moneda local. Para obtener el símbolo de moneda asociado, usa `product.Price.CurrencySymbol`. | | **Subscription Period** | Para mostrar el período (p. ej., semana, mes, año, etc.), usa `product.Subscription?.LocalizedPeriod`. Esta localización se basa en la configuración regional del dispositivo. Para obtener el período de suscripción mediante programación, usa `product.Subscription?.Period`. Desde ahí puedes acceder al enum `Unit` para obtener la duración (es decir, `AdaptySubscriptionPeriodUnit.Day`, `AdaptySubscriptionPeriodUnit.Week`, `AdaptySubscriptionPeriodUnit.Month`, `AdaptySubscriptionPeriodUnit.Year` o `AdaptySubscriptionPeriodUnit.Unknown`). El valor `NumberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral, verías `AdaptySubscriptionPeriodUnit.Month` en la propiedad Unit y `3` en la propiedad NumberOfUnits. | | **Introductory Offer** | Para mostrar un distintivo u otro indicador de que una suscripción incluye una oferta introductoria, revisa la propiedad `product.Subscription?.Offer?.Phases`. Esta es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase están las siguientes propiedades útiles:<br/>• `PaymentMode`: un enum con los valores `AdaptyPaymentMode.FreeTrial`, `AdaptyPaymentMode.PayAsYouGo`, `AdaptyPaymentMode.PayUpFront` y `AdaptyPaymentMode.Unknown`. Las pruebas gratuitas serán del tipo `AdaptyPaymentMode.FreeTrial`.<br/>• `Price`: El precio con descuento como número. Para las pruebas gratuitas, busca `0` aquí.<br/>• `LocalizedNumberOfPeriods`: una cadena localizada según el idioma del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días muestra `"3 days"` en este campo.<br/>• `SubscriptionPeriod`: Alternativamente, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma manera para las ofertas que la sección anterior describe.<br/>• `LocalizedSubscriptionPeriod`: Un período de suscripción formateado del descuento para la configuración regional del usuario. | ## Acelerar la obtención de paywalls con el paywall de audiencia predeterminada \{#speed-up-paywall-fetching-with-default-audience-paywall\} Normalmente, los paywalls se obtienen casi al instante, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, en casos donde tienes numerosas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, obtener un paywall puede tardar más de lo deseado. En estas situaciones, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ningún paywall. Para resolver esto, puedes usar el método `GetPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, como se detalla en la sección [Obtener información del paywall](#fetch-paywall-information) anterior. :::warning Considera usar `GetPaywall` en lugar de `GetPaywallForDefaultAudience`, ya que este último tiene limitaciones importantes: - **Problemas de compatibilidad**: Puede crear problemas al dar soporte a múltiples versiones de la app, requiriendo diseños retrocompatibles o aceptando que las versiones más antiguas puedan mostrarse incorrectamente. - **Sin personalización**: Solo muestra contenido para la audiencia "All Users", eliminando la segmentación basada en país, atribución o atributos personalizados. Si una obtención más rápida compensa estos inconvenientes para tu caso de uso, usa `GetPaywallForDefaultAudience` como se muestra a continuación. De lo contrario, usa `GetPaywall` como se describe [anteriormente](#fetch-paywall-information). ::: ```csharp showLineNumbers Adapty.GetPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del paywall. Se espera que este parámetro sea un código de idioma compuesto por una o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen conexiones a internet inestables, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante limpieza manual.</p><p></p><p>El SDK de Adapty almacena los paywalls localmente en dos capas: la caché actualizada regularmente descrita anteriormente y los paywalls de respaldo. También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | --- # File: present-remote-config-paywalls-unity --- --- title: "Renderizar paywall diseñado con Remote Config en Unity SDK" description: "Descubre cómo presentar paywalls con Remote Config en Adapty Unity SDK para personalizar la experiencia de usuario." --- Si has personalizado un paywall usando Remote Config, necesitarás implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú decides qué incluir y cómo se ve tu paywall. Proporcionamos un método para obtener la configuración remota, dándote la autonomía de mostrar tu paywall personalizado configurado a través de Remote Config. ## Obtener el Remote Config del paywall y presentarlo \{#get-paywall-remote-config-and-present-it\} Para obtener el Remote Config de un paywall, accede a la propiedad `remoteConfig` y extrae los valores que necesites. ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if (error != null) { // handle the error return; } // Access remote config dictionary var dictionary = paywall.RemoteConfig?.Dictionary; var headerText = dictionary?["header_text"] as string; // Or access raw JSON data var jsonData = paywall.RemoteConfig?.Data; }); ``` En este punto, una vez que hayas recibido todos los valores necesarios, es momento de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a distintos tamaños de pantalla y orientaciones de dispositivos móviles, garantizando una experiencia fluida y amigable en todos los dispositivos. :::warning Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls-unity#track-paywall-view-events) tal como se describe a continuación, para que los análisis de Adapty puedan recopilar información para los embudos y las pruebas A/B. ::: Una vez que hayas terminado de mostrar el paywall, continúa configurando el flujo de compra. Cuando el usuario realice una compra, simplemente llama a `.MakePurchase()` con el producto de tu paywall. Para más detalles sobre el método `.MakePurchase()`, consulta [Realizar compras](unity-making-purchases). Te recomendamos [crear un paywall de respaldo denominado paywall de respaldo](unity-use-fallback-paywalls). Este respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones. ## Registrar eventos de visualización del paywall \{#track-paywall-view-events\} Adapty te ayuda a medir el rendimiento de tus paywalls. Aunque recopilamos datos de compras automáticamente, registrar las visualizaciones del paywall requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un paywall. Para registrar un evento de visualización del paywall, simplemente llama a `.LogShowPaywall(paywall)` y se reflejará en las métricas de tu paywall en los embudos y las pruebas A/B. :::important No es necesario llamar a `.LogShowPaywall(paywall)` si estás mostrando paywalls creados en el [Paywall Builder](adapty-paywall-builder). ::: ```csharp showLineNumbers Adapty.LogShowPaywall(paywall, (error) => { // handle the error }); ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :-------- |:------------------------------------------------------------------| | **paywall** | obligatorio | Un objeto [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). | --- # File: unity-making-purchases --- --- title: "Realizar compras in-app en Unity SDK" description: "Guía sobre cómo gestionar compras in-app y suscripciones con Adapty." --- Mostrar paywalls dentro de tu aplicación móvil es un paso esencial para ofrecer a los usuarios acceso a contenido o servicios premium. Sin embargo, simplemente mostrar estos paywalls solo es suficiente para gestionar las compras si usas [Paywall Builder](adapty-paywall-builder) para personalizar tus paywalls. Si no usas el Paywall Builder, debes usar un método independiente llamado `.makePurchase()` para completar una compra y desbloquear el contenido deseado. Este método es la puerta de entrada para que los usuarios interactúen con los paywalls y realicen sus transacciones. Si tu paywall tiene una oferta promocional activa para el producto que el usuario intenta comprar, Adapty la aplicará automáticamente en el momento de la compra. :::warning Ten en cuenta que la oferta introductoria solo se aplicará de forma automática si usas paywalls configurados con el Paywall Builder. En otros casos, necesitarás [verificar la elegibilidad del usuario para una oferta introductoria en iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Omitir este paso puede provocar que tu app sea rechazada durante la revisión. Además, podría suponer cobrar el precio completo a usuarios que son elegibles para una oferta introductoria. ::: Asegúrate de haber completado la [configuración inicial](quickstart) sin saltarte ningún paso. Sin ella, no podemos validar las compras. ## Realizar una compra \{#make-purchase\} :::note **¿Usas [Paywall Builder](adapty-paywall-builder)?** Las compras se procesan automáticamente; puedes saltarte este paso. **¿Buscas una guía paso a paso?** Consulta la [guía de inicio rápido](unity-implement-paywalls-manually) para obtener instrucciones de implementación completas con todo el contexto. ::: ```csharp showLineNumbers using AdaptySDK; void MakePurchase(AdaptyPaywallProduct product) { Adapty.MakePurchase(product, (result, error) => { switch (result.Type) { case AdaptyPurchaseResultType.Pending: // handle pending purchase break; case AdaptyPurchaseResultType.UserCancelled: // handle purchase cancellation break; case AdaptyPurchaseResultType.Success: var profile = result.Profile; // handle successfull purchase break; default: break; } }); } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :-------- |:------------------------------------------------------------------------------------------------------| | **Product** | requerido | Un objeto [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html) obtenido del paywall. | Parámetros de respuesta: | Parámetro | Descripción | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Si la solicitud se ha realizado correctamente, la respuesta contiene este objeto. Un objeto [AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html) proporciona información completa sobre los niveles de acceso, suscripciones y compras únicas de un usuario dentro de la app.</p><p>Comprueba el estado del nivel de acceso para determinar si el usuario tiene el acceso necesario a la app.</p> | :::warning **Nota:** si todavía usas una versión de StoreKit de Apple inferior a v2.0 y una versión del SDK de Adapty inferior a v2.9.0, debes proporcionar el [secreto compartido de Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret) en su lugar. Este método está actualmente obsoleto por Apple. ::: ## Cambiar la suscripción al realizar una compra \{#change-subscription-when-making-a-purchase\} Cuando un usuario elige una suscripción nueva en lugar de renovar la actual, el funcionamiento depende del store: - En el App Store, la suscripción se actualiza automáticamente dentro del grupo de suscripciones. Si un usuario compra una suscripción de un grupo mientras ya tiene activa otra de un grupo distinto, ambas suscripciones estarán activas al mismo tiempo. - En Google Play, la suscripción no se actualiza automáticamente. Tendrás que gestionar el cambio en el código de tu app tal como se describe a continuación. Para reemplazar la suscripción por otra en Android, llama al método `.makePurchase()` con el parámetro adicional: ```csharp showLineNumbers // Create subscription update parameters var subscriptionUpdateParams = new AdaptySubscriptionUpdateParameters( "old_product_id", // Product ID of the current subscription AdaptySubscriptionUpdateReplacementMode.WithTimeProration ); Adapty.MakePurchase(product, subscriptionUpdateParams, (profile, error) => { if(error != null) { // Handle the error return; } // successful cross-grade }); ``` Parámetro de solicitud adicional: | Parámetro | Presencia | Descripción | | :--------------------------- | :-------- |:-------------------------------------------------------------------------------------------------------| | **subscriptionUpdateParams** | obligatorio | un objeto [`AdaptySubscriptionUpdateParameters`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_subscription_update_parameters.html). | Puedes leer más sobre suscripciones y modos de reemplazo en la documentación para desarrolladores de Google: - [Acerca de los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Recomendaciones de Google para los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Modo de reemplazo [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Nota: este método solo está disponible para actualizaciones de suscripción. No se admiten cambios a un plan inferior. - Modo de reemplazo [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Nota: el cambio real de suscripción solo se producirá cuando finalice el período de facturación actual. ## Canjear códigos de oferta en iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Sobre los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios concretos. A diferencia de las ofertas habituales, que se aplican de forma automática, los códigos de oferta se distribuyen fuera de la app — por email, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, accediendo a una URL de canje o a través de un diálogo dentro de la app. Para configurar códigos de oferta, abre una suscripción en App Store Connect y ve a su sección **Offer Codes**. Puedes crear [tres tipos](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) de códigos de oferta: - **Free** — la suscripción es gratuita durante un período determinado y la siguiente renovación se cobra al precio completo. - **Pay as you go** — el usuario paga un precio reducido en cada ciclo de facturación durante un período determinado y, después, la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único reducido por toda la duración de la oferta y, después, la suscripción se renueva al precio completo. No es necesario añadir los códigos de oferta a Adapty. Apple etiqueta cada transacción durante el período de la oferta con la categoría del código de oferta. Esto incluye el canje inicial y todas las renovaciones con descuento posteriores. Adapty detecta la etiqueta y registra cada transacción con la categoría de oferta `offer_code`. Cuando termina el período de oferta y la suscripción se renueva al precio completo, la etiqueta deja de estar presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Resolución de discrepancias en los ingresos \{#revenue-discrepancy-troubleshooting\} Si observas que una transacción con código de oferta aparece en Adapty al precio completo del producto en lugar del precio reducido, verifica lo siguiente en App Store Connect: - El código de oferta tiene los precios correctos configurados para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está configurado para el país o región específica del usuario. Apple envía el precio regional en la transacción. Si no hay ningún precio regional configurado para la oferta, Apple puede enviar el precio completo del producto. Puedes filtrar y verificar las transacciones con código de oferta en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds) mediante los filtros de tipo de oferta **Offer Code** y **Offer Discount Type**. #### Códigos promocionales heredados (obsoletos) \{#legacy-promo-codes-deprecated\} :::warning Apple dejó obsoletos los códigos promocionales para compras in-app en marzo de 2026. Los códigos de oferta los sustituyen con más funcionalidades: elegibilidad configurable, fechas de expiración y hasta 1 millón de códigos por trimestre. Si antes usabas códigos promocionales para compras in-app, migra a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app y versión) daban acceso gratuito a una suscripción. A diferencia de los códigos de oferta, Apple no incluía información de descuento en las transacciones con código promocional — enviaba el precio completo del producto en el recibo. Por ello, Adapty registraba estas transacciones al precio completo, lo que generaba discrepancias entre los análisis de Adapty y App Store Connect. Si ves transacciones históricas al precio completo que deberían haber sido gratuitas, es probable que provengan de códigos promocionales heredados. Como estos códigos ya están obsoletos, migra a los códigos de oferta para un seguimiento preciso de los ingresos. </Details> Para mostrar la hoja de canje de códigos en tu app: ```csharp showLineNumbers Adapty.PresentCodeRedemptionSheet((error) => { // handle the error }); ``` :::danger Según nuestras observaciones, la hoja de canje de códigos de oferta puede no funcionar de forma fiable en algunas apps. Recomendamos redirigir al usuario directamente al App Store. Para ello, debes abrir la URL con el siguiente formato: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ## Gestionar planes de prepago (Android) \{#manage-prepaid-plans-android\} Si los usuarios de tu app pueden comprar [planes de prepago](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (por ejemplo, adquirir una suscripción no renovable durante varios meses), puedes activar las [transacciones pendientes](https://developer.android.com/google/play/billing/subscriptions#pending) para planes de prepago. ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetGoogleEnablePendingPrepaidPlans(true); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` --- # File: unity-restore-purchase --- --- title: "Restaurar compras en la app móvil con Unity SDK" description: "Aprende cómo restaurar compras en Adapty para garantizar una experiencia de usuario sin interrupciones." --- Restaurar compras tanto en iOS como en Android es una funcionalidad que permite a los usuarios recuperar el acceso a contenido comprado previamente, como suscripciones o compras in-app, sin que se les vuelva a cobrar. Esta funcionalidad es especialmente útil para usuarios que hayan desinstalado y reinstalado la app, o que hayan cambiado de dispositivo y quieran acceder a su contenido ya adquirido sin pagar de nuevo. :::note En los paywalls creados con [Paywall Builder](adapty-paywall-builder), las compras se restauran automáticamente sin que tengas que añadir código adicional. Si ese es tu caso, puedes saltarte este paso. ::: Para restaurar una compra cuando no usas [Paywall Builder](adapty-paywall-builder) para personalizar el paywall, llama al método `.restorePurchases()`: ```csharp showLineNumbers Adapty.RestorePurchases((profile, error) => { if (error != null) { // handle the error return; } var accessLevel = profile.AccessLevels["YOUR_ACCESS_LEVEL"]; if (accessLevel != null && accessLevel.IsActive) { // restore access } }); ``` Parámetros de respuesta: | Parámetro | Descripción | |---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Un objeto [`AdaptyProfile`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html). Este modelo contiene información sobre los niveles de acceso, suscripciones y compras únicas.</p><p>Comprueba el **estado del nivel de acceso** para determinar si el usuario tiene acceso a la app.</p> | :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: --- # File: implement-observer-mode-unity --- --- title: "Implementar el modo Observer en el SDK de Unity" description: "Implementa el modo observer en Adapty para registrar eventos de suscripción de usuarios en el SDK de Unity." --- Si ya tienes tu propia infraestructura de compras y no estás listo para migrar completamente a Adapty, puedes explorar el [modo Observer](observer-vs-full-mode). En su forma básica, el modo Observer ofrece analíticas avanzadas e integración fluida con sistemas de atribución y analíticas. Si esto cubre tus necesidades, solo tienes que: 1. Activarlo al configurar el SDK de Adapty estableciendo el parámetro `observerMode` en `true`. Sigue las instrucciones de configuración para [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 2. [Reportar transacciones](report-transactions-observer-mode-unity) desde tu infraestructura de compras existente a Adapty. ### Configuración del modo Observer \{#observer-mode-setup\} Activa el modo Observer si gestionas las compras y el estado de suscripción por tu cuenta y utilizas Adapty para enviar eventos de suscripción y analíticas. :::important Cuando se ejecuta en modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlo tú mismo. ::: ```csharp showLineNumbers title="C#" using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, AdaptyEventListener { void Start() { DontDestroyOnLoad(this.gameObject); Adapty.SetEventListener(this); var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetObserverMode(true); // Enable observer mode Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); } public void OnLoadLatestProfile(AdaptyProfile profile) { } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } } ``` Parámetros: | Parámetro | Descripción | |--------------|----------------------------------------------------------------------------------------------------------------| | observerMode | Un valor booleano que controla el [modo Observer](observer-vs-full-mode). El valor por defecto es `false`. | ## Usar los paywalls de Adapty en el modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los paywalls y las funciones de pruebas A/B de Adapty, puedes hacerlo, pero requiere algo de configuración adicional en el modo Observer. Esto es lo que necesitarás hacer además de los pasos anteriores: 1. Muestra los paywalls de la forma habitual para [paywalls con Remote Config](present-remote-config-paywalls-unity). 3. [Asocia los paywalls](report-transactions-observer-mode-unity) con las transacciones de compra. --- # File: report-transactions-observer-mode-unity --- --- title: "Reportar transacciones en Observer Mode en el SDK de Unity" description: "Reporta transacciones de compras en el Observer Mode de Adapty para obtener información sobre usuarios y seguimiento de ingresos en el SDK de Unity." --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+ (current)" default> En Observer Mode, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Necesitas reportar las transacciones desde tu app store. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` para reportar explícitamente cada transacción y que Adapty la reconozca. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `ReportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el paywall que la originó, garantizando análisis precisos del paywall. ```csharp showLineNumbers Adapty.ReportTransaction( "YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID", // optional (error) => { // handle the error }); ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | requerido | <ul><li> Para iOS: Identificador de la transacción.</li><li> Para Android: Identificador de cadena `purchase.getOrderId` de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación.</li></ul> | | variationId | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x (legacy)" default> En Observer Mode, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Necesitas reportar las transacciones desde tu app store o restaurarlas. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` en ambas plataformas para reportar explícitamente cada transacción, y usa `restorePurchases` en Android como paso adicional para asegurarte de que Adapty la reconozca. :::warning **¡No omitas el reporte de transacciones ni la restauración de compras!** Si no llamas a estos métodos, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: Si usas paywalls de Adapty, incluye el `PAYWALL_VARIATION_ID` al reportar una transacción. Esto vincula la compra con el paywall que la originó, garantizando análisis precisos del paywall. ```csharp showLineNumbers // every time when calling transasction.finish() #if UNITY_ANDROID && !UNITY_EDITOR Adapty.RestorePurchases((profile, error) => { // handle the error }); #endif Adapty.ReportTransaction( "YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID", // optional (error) => { // handle the error }); ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | requerido | <ul><li> Para iOS, StoreKit 1: un objeto [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</li><li> Para iOS, StoreKit 2: objeto [Transaction](https://developer.apple.com/documentation/storekit/transaction).</li><li> Para Android: Identificador de cadena (`purchase.getOrderId`) de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación.</li></ul> | | variationId | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). | </TabItem> <TabItem value="old2" label="Adapty SDK up to 3.2.x (legacy)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> **Reporte de transacciones** - Las versiones hasta la 3.1.x escuchan automáticamente las transacciones en la App Store, por lo que no es necesario reportarlas manualmente. - La versión 3.2 no admite Observer Mode. </TabItem> <TabItem value="kotlin" label="Android and Android-based cross-platforms" default> **Reporte de transacciones** Usa `restorePurchases` para reportar una transacción a Adapty en Observer Mode, tal como se explica en la página [Restaurar compras en código móvil](unity-restore-purchase). :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `restorePurchases`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: </TabItem> </Tabs> **Asociar paywalls a transacciones** El SDK de Adapty no puede determinar el origen de las compras, ya que eres tú quien las procesa. Por lo tanto, si tienes pensado usar paywalls y/o pruebas A/B en Observer Mode, necesitas asociar la transacción proveniente de tu app store con el paywall correspondiente en el código de tu app. Es importante hacerlo correctamente antes de publicar tu app; de lo contrario, generará errores en los análisis. ```csharp Adapty.SetVariationForTransaction("<variationId>", "<transactionId>", (error) => { if(error != null) { // handle the error return; } // successful binding }); ``` | Parámetro | Presencia | Descripción | | ------------------------------------------------------ | --------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | requerido | <p>Para iOS, StoreKit 1: un objeto [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</p><p>Para iOS, StoreKit 2: objeto [Transaction](https://developer.apple.com/documentation/storekit/transaction).</p><p>Para Android: Identificador de cadena (purchase.getOrderId de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación).</p> | | variationId | requerido | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). | </TabItem> </Tabs> --- # File: unity-troubleshoot-purchases --- --- title: "Solucionar problemas de compras en Unity SDK" description: "Solucionar problemas de compras en Unity SDK" --- Esta guía te ayuda a resolver problemas comunes al implementar compras manualmente en el SDK de Unity. ## makePurchase se llama correctamente, pero el perfil no se actualiza \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Problema**: El método `makePurchase` se completa correctamente, pero el perfil del usuario y el estado de la suscripción no se actualizan en Adapty. **Causa**: Esto suele indicar una configuración incompleta de Google Play Store o problemas de configuración. **Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android). ## makePurchase se invoca dos veces \{#makepurchase-is-invoked-twice\} **Problema**: El método `makePurchase` se está llamando varias veces para la misma compra. **Causa**: Esto ocurre normalmente cuando el flujo de compra se activa varias veces debido a problemas de gestión del estado de la interfaz o a interacciones rápidas del usuario. **Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android). ## AdaptyError.cantMakePayments en modo observador \{#adaptyerror-cantmakepayments-in-observer-mode\} **Problema**: Estás obteniendo `AdaptyError.cantMakePayments` al usar `makePurchase` en modo observador. **Causa**: En el modo observador, debes gestionar las compras por tu cuenta, no usar el método `makePurchase` de Adapty. **Solución**: Si usas `makePurchase` para las compras, desactiva el modo observador. Debes elegir entre usar `makePurchase` o gestionar las compras por tu cuenta en el modo observador. Consulta [Implementar el modo observador](implement-observer-mode-unity) para más detalles. ## Error de Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Problema**: Estás recibiendo un error de facturación no disponible de Google Play Store. **Causa**: Este error no está relacionado con Adapty. Es un error de la biblioteca de facturación de Google Play que indica que la facturación no está disponible en el dispositivo. **Solución**: Este error no está relacionado con Adapty. Puedes consultar más información en la documentación de Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## No se encuentran makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Problema**: Estás encontrando problemas porque no se encuentran los `makePurchasesCompletionHandlers`. **Causa**: Esto suele estar relacionado con problemas en las pruebas de sandbox. **Solución**: Crea un nuevo usuario de sandbox e inténtalo de nuevo. Esto generalmente resuelve los problemas con los manejadores de finalización de compra en sandbox. ## Otros problemas \{#other-issues\} **Problema**: Estás experimentando otros problemas relacionados con las compras que no se tratan más arriba. **Solución**: Migra el SDK a la última versión siguiendo las [guías de migración](unity-sdk-migration-guides) si es necesario. Muchos problemas se resuelven en versiones más recientes del SDK. --- # File: unity-identifying-users --- --- title: "Identificar usuarios en Unity SDK" description: "Aprende cómo identificar usuarios en tu app de Unity con el SDK de Adapty." --- Adapty crea un ID de perfil interno para cada usuario. Sin embargo, si tienes tu propio sistema de autenticación, deberías establecer tu propio Customer User ID. Puedes encontrar usuarios por su Customer User ID en la sección [Perfiles](profiles-crm) y utilizarlo en la [API del lado del servidor](getting-started-with-server-side-api), que se enviará a todas las integraciones. ### Configurar el ID de usuario en la inicialización \{#setting-customer-user-id-on-configuration\} Si ya tienes un ID de usuario durante la configuración, pásalo como parámetro `customerUserId` al método `.activate()`: ```csharp showLineNumbers using UnityEngine; using AdaptySDK; var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID"); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Establecer el ID de usuario después de la configuración \{#setting-customer-user-id-after-configuration\} Si no tienes un ID de usuario en la configuración del SDK, puedes establecerlo en cualquier momento con el método `.identify()`. Los casos más comunes para usar este método son tras el registro o la autenticación, cuando el usuario pasa de ser anónimo a estar autenticado. ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { if(error == null) { // successful identify } }); ``` Parámetros de la solicitud: - **Customer User ID** (obligatorio): un identificador de usuario de tipo string. :::warning Reenvío de datos significativos del usuario En algunos casos, como cuando un usuario inicia sesión de nuevo en su cuenta, los servidores de Adapty ya tienen información sobre ese usuario. En estos escenarios, el SDK de Adapty cambiará automáticamente para trabajar con el nuevo usuario. Si enviaste algún dato al usuario anónimo, como atributos personalizados o atribuciones de redes de terceros, deberás reenviar esos datos para el usuario identificado. También es importante tener en cuenta que debes volver a solicitar todos los paywalls y productos después de identificar al usuario, ya que los datos del nuevo usuario pueden ser diferentes. ::: ### Cerrar sesión e iniciar sesión \{#logging-out-and-logging-in\} Puedes cerrar la sesión del usuario en cualquier momento llamando al método `.logout()`: ```csharp showLineNumbers Adapty.Logout((error) => { if(error == null) { // successful logout } }); ``` Luego puedes iniciar sesión con el usuario usando el método `.identify()`. ## Asignar `appAccountToken` (iOS) [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) es un **UUID** que te permite vincular las transacciones del App Store con la identidad interna de tu usuario. StoreKit asocia este token con cada transacción, de modo que tu backend puede relacionar los datos del App Store con tus usuarios. Usa un UUID estable generado por usuario y reutilízalo para la misma cuenta en todos los dispositivos. Así te aseguras de que las compras y las notificaciones del App Store permanezcan correctamente vinculadas. Puedes configurar el token de dos formas: durante la activación del SDK o al identificar al usuario. :::important Siempre debes pasar `appAccountToken` junto con `customerUserId`. Si solo pasas el token, no se incluirá en la transacción. ::: ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; using System; // During configuration: var appAccountToken = new Guid("YOUR_APP_ACCOUNT_TOKEN"); var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID", appAccountToken); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); // Or when identifying users Adapty.Identify("YOUR_USER_ID", appAccountToken, (error) => { if (error == null) { // successful identify } }); ``` ## Establecer IDs de cuenta ofuscados (Android) \{#set-obfuscated-account-ids-android\} Google Play requiere IDs de cuenta ofuscados en ciertos casos de uso para mejorar la privacidad y seguridad de los usuarios. Estos IDs ayudan a Google Play a identificar compras sin exponer información del usuario, lo que es especialmente importante para la prevención de fraude y los análisis. Es posible que necesites establecer estos IDs si tu app maneja datos sensibles de usuarios o si debes cumplir con normativas de privacidad específicas. Los IDs ofuscados permiten a Google Play rastrear compras sin revelar los identificadores reales de los usuarios. ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; // Durante la configuración: var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID", null, "YOUR_OBFUSCATED_ACCOUNT_ID"); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // manejar el error return; } }); // O al identificar usuarios Adapty.Identify("YOUR_USER_ID", null, "YOUR_OBFUSCATED_ACCOUNT_ID", (error) => { if (error == null) { // identificación exitosa } }); ``` ## Detectar usuarios en múltiples dispositivos \{#detect-users-across-devices\} Cuando el SDK se activa, lee automáticamente los derechos existentes del usuario desde StoreKit (iOS) o Google Play Billing (Android) y los sincroniza con el backend de Adapty. Una suscripción activa aparece en el perfil de Adapty sin que la app llame a `restorePurchases`. Lo que **no** ocurre automáticamente es reconocer que un perfil en un dispositivo nuevo pertenece al mismo usuario que el perfil en el dispositivo original. Adapty relaciona perfiles por Customer User ID, así que la continuidad de identidad depende de lo que uses como CUID. **Lo que Adapty puede detectar entre dispositivos** | Tu configuración | Lo que Adapty detecta | Lo que debes hacer | | --- | --- | --- | | Customer User ID = `device_id` (sin login en la app) | El nuevo dispositivo obtiene un CUID diferente y, por tanto, un perfil diferente. La suscripción se sincroniza con el nuevo perfil mediante un evento **Access level updated**, pero `subscription_started` no se dispara — el nuevo perfil se trata como heredero de la compra original. Los análisis basados en `subscription_started` contarán de menos a los usuarios que vuelven. | Usa un ID de cuenta estable como Customer User ID para que un usuario que regresa coincida con el perfil existente en todos los dispositivos. | | Customer User ID = ID de cuenta estable (login en cada dispositivo) | El SDK sincroniza automáticamente la suscripción en `activate()`, e `identify()` relaciona el perfil existente por CUID. | No se necesita configuración adicional — tanto la identidad como la suscripción se resuelven automáticamente. | | Heredero de Apple Family Sharing | El miembro de la familia recibe la suscripción solo a través de un evento **Access level updated** — `subscription_started` no se dispara. | Escucha el evento **Access level updated**. Consulta [Apple Family Sharing](apple-family-sharing) para ver la matriz de eventos completa. | | Misma cuenta de Apple/Google, distintos usuarios dentro de la app | El primer perfil que registra la compra se convierte en el principal. Los perfiles posteriores ven la suscripción a través de una cadena de herederos, con un evento **Access level updated**. | Exige login y elige un [modo de compartición](sharing-paid-access-between-user-accounts) que se adapte a tu modelo. | **Restaurar compras en un dispositivo nuevo** Muestra un botón "Restaurar compras" iniciado por el usuario en tu paywall. Apple App Review (directriz 3.1.1) lo exige, y actúa como alternativa cuando la sincronización automática no cubre algún caso límite. El botón debe llamar a `restorePurchases` en tu SDK. No es necesario llamar a `restorePurchases` de forma programática al primer inicio para el uso normal — el SDK ya ejecuta el equivalente en `activate()`. Reserva las llamadas programáticas para forzar una verificación de recibo actualizada, por ejemplo al depurar un acceso que falta después de que `activate()` haya completado. --- # File: unity-setting-user-attributes --- --- title: "Establecer atributos de usuario en el SDK de Unity" description: "Aprende a actualizar atributos de usuario y datos de perfil en tu app de Unity con el SDK de Adapty." --- Puedes añadir atributos opcionales como email, número de teléfono, etc., al usuario de tu app. Luego puedes usarlos para crear [segmentos](segments) de usuarios o simplemente consultarlos en el CRM. ### Establecer atributos de usuario \{#setting-user-attributes\} Para establecer atributos de usuario, llama al método `.updateProfile()`: ```csharp showLineNumbers var builder = new Adapty.ProfileParameters.Builder() .SetFirstName("John") .SetLastName("Appleseed") .SetBirthday(new DateTime(1970, 1, 3)) .SetGender(ProfileGender.Female) .SetEmail("example@adapty.io"); Adapty.UpdateProfile(builder.Build(), (error) => { if(error != nil) { // handle the error } }); ``` Ten en cuenta que los atributos que hayas establecido previamente con el método `updateProfile` no se restablecerán. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ### Lista de claves permitidas \{#the-allowed-keys-list\} Las claves permitidas `<Key>` de `AdaptyProfileParameters.Builder` y sus valores `<Value>` se listan a continuación: | Key | Value | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | Enum, los valores permitidos son: `female`, `male`, `other` | | birthday | Date | ### Atributos de usuario personalizados \{#custom-user-attributes\} Puedes definir tus propios atributos personalizados. Normalmente están relacionados con el uso de tu app. Por ejemplo, en apps de fitness podrían ser el número de ejercicios por semana; en apps de aprendizaje de idiomas, el nivel de conocimiento del usuario, etc. Puedes utilizarlos en segmentos para crear paywalls y ofertas segmentadas, y también en análisis para identificar qué métricas de producto influyen más en los ingresos. ```csharp showLineNumbers try { builder = builder.SetCustomStringAttribute("string_key", "string_value"); builder = builder.SetCustomDoubleAttribute("double_key", 123.0f); } catch (Exception e) { // handle the exception } ``` Para eliminar una clave existente, usa el método `.withRemoved(customAttributeForKey:)`: ```csharp showLineNumbers try { builder = builder.RemoveCustomAttribute("key_to_remove"); } catch (Exception e) { // handle the exception } ``` A veces necesitas saber qué atributos personalizados ya se han establecido. Para ello, usa el campo `customAttributes` del objeto `AdaptyProfile`. :::warning Ten en cuenta que el valor de `customAttributes` puede estar desactualizado, ya que los atributos de usuario pueden enviarse desde distintos dispositivos en cualquier momento, por lo que los atributos en el servidor pueden haber cambiado desde la última sincronización. ::: ### Límites \{#limits\} - Hasta 30 atributos personalizados por usuario - Los nombres de clave pueden tener hasta 30 caracteres. El nombre de la clave puede incluir caracteres alfanuméricos y cualquiera de los siguientes: `_` `-` `.` - El valor puede ser una cadena de texto o un número flotante con un máximo de 50 caracteres. --- # File: unity-listen-subscription-changes --- --- title: "Comprobar el estado de la suscripción en Unity SDK" description: "Rastrea y gestiona el estado de la suscripción del usuario en Adapty para mejorar la retención de clientes en tu aplicación Unity." --- Con Adapty, hacer seguimiento del estado de la suscripción es muy sencillo. No tienes que insertar manualmente los IDs de producto en tu código. En cambio, puedes confirmar fácilmente el estado de suscripción de un usuario comprobando si tiene un [nivel de acceso](access-level) activo. <details> <summary>Antes de empezar a comprobar el estado de la suscripción (haz clic para ampliar)</summary> - Para iOS, configura las [Notificaciones del servidor de App Store](enable-app-store-server-notifications) - Para Android, configura las [Notificaciones en tiempo real para desarrolladores (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## El nivel de acceso y el objeto AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Los niveles de acceso son propiedades del objeto [AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html). Te recomendamos obtener el perfil cuando tu app arranca, por ejemplo al [identificar a un usuario](unity-identifying-users#setting-customer-user-id-on-configuration), y actualizarlo cada vez que se produzcan cambios. Así podrás usar el objeto de perfil sin necesidad de solicitarlo repetidamente. Para recibir notificaciones sobre actualizaciones del perfil, suscríbete a los cambios de perfil como se describe en la sección [Escuchar actualizaciones del estado de la suscripción](#listening-for-subscription-status-updates) más abajo. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas. ::: ## Obtener el nivel de acceso desde el servidor \{#retrieving-the-access-level-from-the-server\} Para obtener el nivel de acceso desde el servidor, usa el método `.GetProfile()`: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access }); ``` Parámetros de respuesta: | Parámetro | Descripción | | --------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Profile | <p>Un objeto [AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html). En general, solo tienes que comprobar el estado del nivel de acceso del perfil para determinar si el usuario tiene acceso premium a la app.</p><p></p><p>El método `.getProfile` proporciona el resultado más actualizado, ya que siempre intenta consultar la API. Si por algún motivo (por ejemplo, sin conexión a internet) el SDK de Adapty no puede obtener información del servidor, se devuelven los datos de la caché. También es importante tener en cuenta que el SDK de Adapty actualiza la caché de `AdaptyProfile` de forma periódica para mantener esta información lo más actualizada posible.</p> | El método `.getProfile()` te proporciona el perfil del usuario a partir del cual puedes obtener el estado del nivel de acceso. Puedes tener múltiples niveles de acceso por app. Por ejemplo, si tienes una app de noticias y vendes suscripciones a diferentes temáticas de forma independiente, puedes crear los niveles de acceso "sports" y "science". Sin embargo, la mayoría de las veces solo necesitarás un nivel de acceso; en ese caso, puedes usar simplemente el nivel de acceso "premium" predeterminado. Aquí tienes un ejemplo para comprobar el nivel de acceso "premium" predeterminado: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // "premium" is an identifier of default access level var accessLevel = profile.AccessLevels["premium"]; if (accessLevel != null && accessLevel.IsActive) { // grant access to premium features } }); ``` ### Escuchar actualizaciones del estado de la suscripción \{#listening-for-subscription-status-updates\} Cada vez que la suscripción del usuario cambia, Adapty lanza un evento. Para recibir mensajes de Adapty, necesitas hacer una configuración adicional: ```csharp showLineNumbers // Extend `AdaptyEventListener ` with `OnLoadLatestProfile ` method: public class AdaptyListener : MonoBehaviour, AdaptyEventListener { public void OnLoadLatestProfile(AdaptyProfile profile) { // handle any changes to subscription state } } ``` Adapty también lanza un evento al inicio de la aplicación. En ese caso, se pasará el estado de suscripción almacenado en caché. ### Caché del estado de la suscripción \{#subscription-status-cache\} La caché implementada en el SDK de Adapty almacena el estado de suscripción del perfil. Esto significa que, incluso si el servidor no está disponible, se puede acceder a los datos en caché para obtener información sobre el estado de suscripción del perfil. No obstante, hay que tener en cuenta que no es posible solicitar datos directamente desde la caché. El SDK consulta periódicamente el servidor cada minuto para comprobar si hay actualizaciones o cambios relacionados con el perfil. Si hay modificaciones, como nuevas transacciones u otras actualizaciones, se enviarán a los datos en caché para mantenerlos sincronizados con el servidor. --- # File: unity-deal-with-att --- --- title: "Gestionar ATT en el SDK de Unity" description: "Comienza con Adapty en Unity para simplificar la configuración y gestión de suscripciones." --- Si tu aplicación utiliza el framework AppTrackingTransparency y presenta al usuario una solicitud de autorización de seguimiento de la app, debes enviar el [estado de autorización](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) a Adapty. ```csharp showLineNumbers var builder = new Adapty.ProfileParameters.Builder() .SetAppTrackingTransparencyStatus(IOSAppTrackingTransparencyStatus.Authorized); Adapty.UpdateProfile(builder.Build(), (error) => { if(error != null) { // handle the error } }); ``` :::warning Te recomendamos encarecidamente que envíes este valor lo antes posible cuando cambie; solo así los datos se enviarán a tiempo a las integraciones que hayas configurado. ::: --- # File: kids-mode-unity --- --- title: "Kids Mode en Unity SDK" description: "Activa fácilmente el Modo Niños para cumplir con las políticas de Apple y Google. No se recopilan IDFA, GAID ni datos publicitarios en Unity SDK." --- Si tu aplicación Unity está destinada a niños, debes seguir las políticas de [Apple](https://developer.apple.com/kids/) y [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Si usas el SDK de Adapty, unos pocos pasos sencillos te ayudarán a configurarlo para cumplir con estas políticas y superar las revisiones de las tiendas. ## ¿Qué se necesita? \{#whats-required\} Debes configurar el SDK de Adapty para desactivar la recopilación de: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [Dirección IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Además, te recomendamos usar el ID de usuario del cliente con cuidado. Un ID en formato `<NombreApellido>` se considerará claramente como recopilación de datos personales, al igual que usar un correo electrónico. En el Modo Niños, la mejor práctica es usar identificadores aleatorios o anonimizados (por ejemplo, IDs hasheados o UUIDs generados por el dispositivo) para garantizar el cumplimiento normativo. ## Activar el Modo Niños \{#enabling-kids-mode\} ### Cambios en el Adapty Dashboard \{#updates-in-the-adapty-dashboard\} En el Adapty Dashboard, debes desactivar la recopilación de direcciones IP. Para ello, ve a [App settings](https://app.adapty.io/settings/general) y haz clic en **Disable IP address collection** en **Collect users' IP address**. ### Cambios en el código de tu app \{#updates-in-your-mobile-app-code\} ¡La compatibilidad con el Modo Niños en Unity estará disponible próximamente! Por ahora, puedes seguir las guías de plataformas nativas: - [Kids Mode en iOS SDK](kids-mode) para la configuración en iOS - [Kids Mode en Android SDK](kids-mode-android) para la configuración en Android --- # File: unity-get-onboardings --- --- title: "Obtener onboardings en el SDK de Unity" description: "Aprende cómo recuperar onboardings en Adapty para Unity." --- Después de [diseñar la parte visual de tu onboarding](design-onboarding) con el editor en el Adapty Dashboard, puedes mostrarlo en tu app de Unity. El primer paso es obtener el onboarding asociado al placement y su configuración de vista, tal como se describe a continuación. Antes de empezar, asegúrate de que: 1. Has instalado el [SDK de Unity de Adapty](sdk-installation-unity) en su versión 3.14.0 o superior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). ## Obtener el onboarding y crear la vista \{#fetch-onboarding-and-create-view\} Cuando creas un [onboarding](onboardings) con nuestro editor sin código, se almacena como un contenedor con una configuración que tu app necesita obtener y mostrar. Este contenedor gestiona toda la experiencia: qué contenido aparece, cómo se presenta y cómo se procesan las interacciones del usuario (como respuestas a cuestionarios o entradas de formularios). El contenedor también registra automáticamente los eventos de analíticas, por lo que no necesitas implementar un seguimiento de vistas por separado. Para obtener el mejor rendimiento, obtén la configuración del onboarding con antelación para dar tiempo suficiente a que las imágenes se descarguen antes de mostrárselas a los usuarios. Para obtener un onboarding, usa el método `GetOnboarding`: ```csharp showLineNumbers Adapty.GetOnboarding("YOUR_PLACEMENT_ID", (onboarding, error) => { if (error != null) { // handle the error return; } // the requested onboarding }); ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto de uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p><p>Consulta [Localizaciones y códigos de localización](flutter-localizations-and-locale-codes) para más información sobre los códigos de localización y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante limpieza manual.</p><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché actualizada periódicamente descrita anteriormente y los onboardings de respaldo. También usamos CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus onboardings, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo de espera, se devolverán los datos en caché o el respaldo local.</p><p>Ten en cuenta que, en casos excepcionales, este método puede agotar el tiempo de espera un poco más tarde de lo especificado en `loadTimeout`, ya que la operación puede consistir en diferentes solicitudes internas.</p> | Parámetros de respuesta: | Parámetro | Descripción | |:----------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Un objeto [`AdaptyOnboarding`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_onboarding.html) con: el identificador y la configuración del onboarding, Remote Config y otras propiedades. | Tras obtener el onboarding, llama al método `CreateOnboardingView`. :::warning El resultado del método `CreateOnboardingView` solo puede usarse una vez. Si necesitas usarlo de nuevo, llama al método `CreateOnboardingView` otra vez. Llamarlo dos veces sin volver a crearlo puede provocar el error `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers AdaptyUI.CreateOnboardingView(onboarding, (view, error) => { // handle the result }); ``` Parámetros: | Parámetro | Presencia | Descripción | |:---------------| :------------- |:-----------------------------------------------------------------------------| | **onboarding** | obligatorio | Un objeto `AdaptyOnboarding` para obtener una vista del onboarding deseado. | | **externalUrlsPresentation** | <p>opcional</p><p>por defecto: `InAppBrowser`</p> | <p>Controla cómo se abren los enlaces en el onboarding. Opciones disponibles:</p><p>- `AdaptyWebPresentation.InAppBrowser` - Abre los enlaces en un navegador dentro de la app (por defecto)</p><p>- `AdaptyWebPresentation.ExternalBrowser` - Abre los enlaces en el navegador externo del dispositivo</p><p>Consulta [Personalizar cómo se abren los enlaces en los onboardings](unity-present-onboardings#customize-how-links-open-in-onboardings) para ver ejemplos de uso.</p> | Una vez que hayas cargado correctamente el onboarding y su configuración de vista, puedes [mostrarlo en tu app móvil](unity-present-onboardings). ## Acelerar la obtención del onboarding con el onboarding de audiencia por defecto \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Normalmente, los onboardings se obtienen casi de inmediato, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, en los casos en que tienes numerosas audiencias y onboardings, y tus usuarios tienen una conexión a internet débil, obtener un onboarding puede tardar más de lo que te gustaría. En estas situaciones, puede que quieras mostrar un onboarding por defecto para garantizar una experiencia de usuario fluida en lugar de no mostrar ninguno. Para resolver esto, puedes usar el método `GetOnboardingForDefaultAudience`, que obtiene el onboarding del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el onboarding con el método `getOnboarding`, tal como se detalla en la sección [Obtener el onboarding](#fetch-onboarding) anterior. :::warning Considera usar `GetOnboarding` en lugar de `GetOnboardingForDefaultAudience`, ya que este último tiene limitaciones importantes: - **Problemas de compatibilidad**: Puede generar problemas al admitir varias versiones de la app, lo que requiere diseños compatibles con versiones anteriores o aceptar que las versiones más antiguas puedan mostrarse incorrectamente. - **Sin personalización**: Solo muestra contenido para la audiencia "All Users", eliminando la segmentación basada en país, atribución o atributos personalizados. Si una obtención más rápida supera estos inconvenientes para tu caso de uso, usa `GetOnboardingForDefaultAudience` como se muestra a continuación. De lo contrario, usa `GetOnboarding` como se describe [arriba](#fetch-onboarding). ::: ```csharp showLineNumbers Adapty.GetOnboardingForDefaultAudience("YOUR_PLACEMENT_ID", (onboarding, error) => { if (error != null) { // handle the error return; } // the requested onboarding }); ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. | | **locale** | <p>opcional</p><p>por defecto: `en`</p> | <p>El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto de uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.</p><p></p><p>Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.</p> | | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | <p>Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, es posible que los usuarios no obtengan los datos absolutamente más recientes, pero experimentarán tiempos de carga más rápidos, independientemente de lo inestable que sea su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante limpieza manual.</p><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché actualizada periódicamente descrita anteriormente y los onboardings de respaldo. También usamos CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus onboardings, asegurando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | --- # File: unity-present-onboardings --- --- title: "Presentar onboardings en Unity SDK" description: "Aprende cómo presentar onboardings de forma efectiva para conseguir más conversiones." --- Si has personalizado un onboarding con el builder, no tienes que preocuparte por renderizarlo en el código de tu app Unity para mostrárselo al usuario. Ese onboarding ya contiene tanto lo que se debe mostrar como la forma en que debe mostrarse. Antes de empezar, asegúrate de que: 1. Has instalado [Adapty Unity SDK](sdk-installation-unity) 3.14.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). Para mostrar un onboarding, usa el método `view.Present()` en el `view` creado por el método `CreateOnboardingView`. Cada `view` solo puede usarse una vez. Si necesitas mostrar el paywall de nuevo, llama a `CreateOnboardingView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar el mismo `view` sin recrearlo puede producir un error `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers title="Unity" view.Present((presentError) => { if (presentError != null) { // handle the error } }; ``` ## Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\} Configura cómo se presenta el onboarding en iOS pasando el parámetro `iosPresentationStyle` al método `Present()`. El parámetro acepta los valores `AdaptyUIIOSPresentationStyle.FullScreen` (predeterminado) o `AdaptyUIIOSPresentationStyle.PageSheet`. ```csharp showLineNumbers title="Unity" view.Present(AdaptyUIIOSPresentationStyle.PageSheet, (error) => { // handle the error }); ``` ## Personaliza cómo se abren los enlaces en los onboardings \{#customize-how-links-open-in-onboardings\} :::important La personalización de cómo se abren los enlaces en los onboardings es compatible a partir de Adapty SDK v3.15. ::: Por defecto, los enlaces en los onboardings se abren en un navegador integrado en la app, lo que ofrece una experiencia fluida al mostrar páginas web dentro de tu aplicación sin cambiar de app. Para abrir los enlaces en un navegador externo, pasa `AdaptyWebPresentation.ExternalBrowser` al método `CreateOnboardingView`: ```csharp showLineNumbers title="Unity" AdaptyUI.CreateOnboardingView( onboarding, AdaptyWebPresentation.ExternalBrowser, // default — InAppBrowser (view, error) => { if (error != null) { // handle the error return; } // present the onboarding view view.Present((presentError) => { if (presentError != null) { // handle the error } }); } ); ``` Opciones disponibles: - `AdaptyWebPresentation.InAppBrowser` - Abre los enlaces en un navegador integrado (por defecto) - `AdaptyWebPresentation.ExternalBrowser` - Abre los enlaces en el navegador externo del dispositivo --- # File: unity-handling-onboarding-events --- --- title: "Manejar eventos de onboarding en Unity SDK" description: "Maneja eventos relacionados con onboarding en Unity usando Adapty." --- Antes de empezar, asegúrate de que: 1. Has instalado el [SDK de Adapty para Unity](sdk-installation-unity) 3.14.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). Los onboardings configurados con el builder generan eventos a los que tu app puede responder. A continuación se explica cómo hacerlo. Para controlar o monitorizar los procesos que ocurren en la pantalla de onboarding dentro de tu app de Unity, implementa la interfaz `AdaptyOnboardingsEventsListener`. ## Acciones personalizadas \{#custom-actions\} En el builder, puedes añadir una acción **personalizada** a un botón y asignarle un ID. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Luego, puedes usar este ID en tu código y gestionarlo como una acción personalizada. Por ejemplo, si un usuario pulsa un botón personalizado, como **Login** o **Allow notifications**, el método `OnboardingViewOnCustomAction` se activará con el parámetro `actionId` siendo el **Action ID** del builder. Puedes crear tus propios IDs, como "allowNotifications". Para gestionar los eventos del onboarding, implementa la interfaz `AdaptyOnboardingsEventsListener`: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { void Start() { Adapty.SetOnboardingsEventsListener(this); } public void OnboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string actionId ) { if (actionId == "allowNotifications") { // request notification permissions } } public void OnboardingViewDidFailWithError( AdaptyUIOnboardingView view, AdaptyError error ) { // handle errors } // Implement other required interface methods (see examples below) } ``` <Details> <summary>Ejemplo de evento (Haz clic para ampliar)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ## Cerrar el onboarding \{#closing-onboarding\} El onboarding se considera cerrado cuando el usuario pulsa un botón con la acción **Close** asignada. <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Ten en cuenta que debes gestionar qué ocurre cuando el usuario cierra el onboarding. Por ejemplo, debes dejar de mostrar el onboarding en sí. ::: Implementa el método `OnboardingViewOnCloseAction` en tu clase: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string actionId ) { view.Dismiss((error) => { if (error != null) { // handle the error } }); } // ... other interface methods } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ## Abrir un paywall \{#opening-a-paywall\} :::tip Gestiona este evento para abrir un paywall si quieres abrirlo dentro del onboarding. Si prefieres abrirlo después de que se cierre, hay una forma más directa: gestiona [`OnboardingViewOnCloseAction`](#closing-onboarding) y abre el paywall sin depender de los datos del evento. ::: La forma más fluida de trabajar con paywalls en onboardings es hacer que el ID de acción sea igual al ID del placement del paywall. Así, tras el evento `OnboardingViewOnPaywallAction`, puedes usar el ID del placement para obtener y abrir el paywall de inmediato. Ten en cuenta que, en iOS, solo se puede mostrar una vista (paywall u onboarding) en pantalla al mismo tiempo. Si presentas un paywall encima de un onboarding, no podrás controlar el onboarding en segundo plano mediante código. Si intentas cerrar el onboarding, se cerrará el paywall en su lugar, dejando el onboarding visible. Para evitar esto, cierra siempre la vista del onboarding antes de presentar el paywall. ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string actionId ) { // Dismiss onboarding before presenting paywall view.Dismiss((dismissError) => { if (dismissError != null) { // handle the error return; } Adapty.GetPaywall(actionId, (paywall, error) => { if (error != null) { // handle the error return; } AdaptyUI.CreatePaywallView(paywall, (paywallView, createError) => { if (createError != null) { // handle the error return; } paywallView.Present((presentError) => { if (presentError != null) { // handle the error } }); }); }); }); } // ... other interface methods } ``` <Details> <summary>Ejemplo de evento (haz clic para expandir)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ## Finalización de la carga del onboarding \{#finishing-loading-onboarding\} Cuando un onboarding termina de cargarse, implementa el método `OnboardingViewDidFinishLoading`: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta ) { // handle loading completion } // ... other interface methods } ``` <Details> <summary>Ejemplo de evento (haz clic para ampliar)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ## Seguimiento de navegación \{#tracking-navigation\} El método `OnboardingViewOnAnalyticsEvent` se invoca cuando ocurren distintos eventos de analítica durante el flow de onboarding. El objeto `analyticsEvent` puede ser de uno de los siguientes tipos: |Tipo | Descripción | |------------|-------------| | `AdaptyOnboardingsAnalyticsEventOnboardingStarted` | Cuando el onboarding se ha cargado | | `AdaptyOnboardingsAnalyticsEventScreenPresented` | Cuando se muestra cualquier pantalla | | `AdaptyOnboardingsAnalyticsEventScreenCompleted` | Cuando se completa una pantalla. Incluye un `ElementId` opcional (identificador del elemento completado) y un `Reply` opcional (respuesta del usuario). Se activa cuando el usuario realiza cualquier acción para salir de la pantalla. | | `AdaptyOnboardingsAnalyticsEventSecondScreenPresented` | Cuando se muestra la segunda pantalla | | `AdaptyOnboardingsAnalyticsEventUserEmailCollected` | Se activa cuando se recoge el correo electrónico del usuario a través del campo de entrada | | `AdaptyOnboardingsAnalyticsEventOnboardingCompleted` | Se activa cuando un usuario llega a una pantalla con el ID `final`. Si necesitas este evento, [asigna el ID `final` a la última pantalla](design-onboarding). | | `AdaptyOnboardingsAnalyticsEventUnknown` | Para cualquier tipo de evento no reconocido. Incluye `Name` (el nombre del evento desconocido) y `meta` (metadatos adicionales) | Cada evento incluye información `meta` con los siguientes campos: | Campo | Descripción | |------------|-------------| | `OnboardingId` | Identificador único del flow de onboarding | | `ScreenClientId` | Identificador de la pantalla actual | | `ScreenIndex` | Posición de la pantalla actual en el flow | | `ScreensTotal` | Número total de pantallas en el flow | A continuación se muestra un ejemplo de cómo usar los eventos de analítica para el seguimiento: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent analyticsEvent ) { switch (analyticsEvent) { case AdaptyOnboardingsAnalyticsEventOnboardingStarted: // track onboarding start TrackEvent("onboarding_started", meta); break; case AdaptyOnboardingsAnalyticsEventScreenPresented: // track screen presentation TrackEvent("screen_presented", meta); break; case AdaptyOnboardingsAnalyticsEventScreenCompleted screenCompleted: // track screen completion with user response TrackEvent("screen_completed", meta, screenCompleted.ElementId, screenCompleted.Reply); break; case AdaptyOnboardingsAnalyticsEventOnboardingCompleted: // track successful onboarding completion TrackEvent("onboarding_completed", meta); break; case AdaptyOnboardingsAnalyticsEventUnknown unknownEvent: // handle unknown events TrackEvent(unknownEvent.Name, meta); break; // handle other cases as needed } } // ... other interface methods } ``` :::note El método `TrackEvent` es un marcador de posición que debes implementar tú mismo para enviar analíticas a tu servicio de analíticas preferido. ::: <Details> <summary>Ejemplos de eventos (haz clic para expandir)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: unity-onboarding-input --- --- title: "Procesar datos de onboardings en Unity SDK" description: "Guarda y usa datos de onboardings en tu app Unity con Adapty SDK." --- Cuando tus usuarios responden a una pregunta de quiz o introducen datos en un campo de texto, se invocará el método `OnboardingViewOnStateUpdatedAction`. Puedes guardar o procesar el tipo de campo en tu código. Implementa el método `OnboardingViewOnStateUpdatedAction` en tu clase: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string elementId, AdaptyOnboardingsStateUpdatedParams @params ) { switch (@params) { case AdaptyOnboardingsSelectParams selectParams: // handle single selection break; case AdaptyOnboardingsMultiSelectParams multiSelectParams: // handle multiple selections break; case AdaptyOnboardingsInputParams inputParams: // handle text input break; case AdaptyOnboardingsDatePickerParams datePickerParams: // handle date selection break; } } // ... other interface methods } ``` Los parámetros incluyen: | Parámetro | Descripción | |----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `elementId` | Un identificador único para el elemento de entrada. Puedes usarlo para asociar preguntas con respuestas al guardarlas. | | `@params` | El objeto con los datos de entrada del usuario. Puede ser uno de los siguientes tipos. | | `AdaptyOnboardingsSelectParams` | Selección única entre opciones. Contiene `Id`, `Value`, `Label` | | `AdaptyOnboardingsMultiSelectParams` | Selecciones múltiples entre opciones. Contiene una lista de `Params` (cada uno con `Id`, `Value`, `Label`)<br/>• `input`: Objeto con `type`, `value`<br/>• `datePicker`: Objeto con `day`, `month`, `year` | | `AdaptyOnboardingsInputParams` | Campo de entrada de texto. Contiene `Input`, que puede ser `AdaptyOnboardingsTextInput`, `AdaptyOnboardingsEmailInput` o `AdaptyOnboardingsNumberInput` | | `AdaptyOnboardingsDatePickerParams` | Selección de fecha. Contiene `Day`, `Month`, `Year` opcionales | <Details> <summary>Ejemplos de datos guardados (pueden variar según tu implementación)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## Casos de uso \{#use-cases\} ### Enriquecer perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Si quieres vincular inmediatamente los datos de entrada con el perfil del usuario y evitar preguntarle dos veces por la misma información, necesitas [actualizar el perfil del usuario](unity-setting-user-attributes) con los datos de entrada al gestionar la acción. Por ejemplo, pides a los usuarios que introduzcan su nombre en el campo de texto con el ID `name` y quieres establecer el valor de ese campo como su nombre de pila. También les pides que introduzcan su correo electrónico en el campo `email`. En el código de tu app, puede verse así: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string elementId, AdaptyOnboardingsStateUpdatedParams @params ) { if (@params is AdaptyOnboardingsInputParams inputParams) { var builder = new AdaptyProfileParameters.Builder(); switch (elementId) { case "name": if (inputParams.Input is AdaptyOnboardingsTextInput textInput) { builder.SetFirstName(textInput.Value); } break; case "email": if (inputParams.Input is AdaptyOnboardingsEmailInput emailInput) { builder.SetEmail(emailInput.Value); } break; } Adapty.UpdateProfile(builder.Build(), (error) => { if (error != null) { // handle the error } }); } } // ... other interface methods } ``` ### Personalizar paywalls según las respuestas \{#customize-paywalls-based-on-answers\} Usando quizzes en los onboardings, también puedes personalizar los paywalls que muestras a los usuarios después de que completen el onboarding. Por ejemplo, puedes preguntarles sobre su experiencia con el deporte y mostrar diferentes CTAs y productos a distintos grupos de usuarios. 1. [Añade un quiz](onboarding-quizzes) en el editor de onboarding y asigna IDs significativos a sus opciones. 2. Gestiona las respuestas del quiz según sus IDs y [establece atributos personalizados](unity-setting-user-attributes) para los usuarios. ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string elementId, AdaptyOnboardingsStateUpdatedParams @params ) { if (@params is AdaptyOnboardingsSelectParams selectParams) { var builder = new AdaptyProfileParameters.Builder(); switch (elementId) { case "experience": // set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.SetCustomStringAttribute("experience", selectParams.Value); break; } Adapty.UpdateProfile(builder.Build(), (error) => { if (error != null) { // handle the error } }); } } // ... other interface methods } ``` 3. [Crea segmentos](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade [audiencias](audience) para cada segmento que hayas creado. 5. [Muestra un paywall](unity-paywalls) para el placement en el código de tu app. Si tu onboarding tiene un botón que abre un paywall, implementa el código del paywall como [respuesta a la acción de ese botón](unity-handling-onboarding-events#opening-a-paywall). --- # File: unity-sdk-call-order --- --- title: "Orden de llamadas en Unity SDK" description: "Evita perder acceso premium, atribución faltante y errores intermitentes #2002 llamando a los métodos del SDK de Adapty en el orden correcto." --- `Adapty.Activate()` debe completarse antes de llamar a cualquier otro método del SDK de Adapty. Hasta que se dispare su callback de finalización, el SDK no tiene estado. Cualquier llamada realizada antes o en paralelo con `Activate()` falla con [`#2002 notActivated`](unity-handle-errors#custom-network-codes). Si tu app autentica usuarios y obtienes un customer user ID después del lanzamiento, llama a `Adapty.Identify()` en ese momento. No llames a métodos de acción del usuario hasta que se dispare el callback de `Identify`. Las llamadas que compiten con él o bien fallan con [`#3006 profileWasChanged`](unity-handle-errors#custom-network-codes), o aterrizan en el perfil anónimo creado durante la activación. Cuando esto ocurre, la atribución, los IDs de MMP como `appsflyer_id` y la propiedad de instalación no siempre se transfieren al perfil identificado. Si tu app no autentica usuarios, omite `Identify` y sigue trabajando con el perfil anónimo. Los SDKs de MMP y analíticas (AppsFlyer, Adjust, Branch, PostHog) siguen la misma regla. Inicialízalos primero y espera a sus callbacks de UID antes de llamar a `Adapty.Activate`. De lo contrario, el ID del MMP aterriza en un perfil anónimo temporal y no siempre se transfiere al identificado. Para los detalles específicos de AppsFlyer, consulta [AppsFlyer](appsflyer). ## El orden correcto \{#the-correct-order\} Tu ruta depende de dos cosas: cuándo conoces el customer user ID y si usas un SDK de MMP o analíticas. - **Pasos 2 y 5**: Obligatorios para toda app. Activa el SDK y luego llama a los métodos del SDK. - **Pasos 1 y 3**: Necesarios solo si integras un SDK de MMP o analíticas (AppsFlyer, Adjust, Branch, PostHog). - **Paso 4**: Necesario solo si tu app autentica usuarios y obtiene el customer user ID después del lanzamiento. Si tienes el customer user ID al iniciar la app, pásalo directamente a `Activate()` (paso 2a). Esta ruta nunca crea un perfil anónimo, por lo que el paso 4 es innecesario. | Paso | Llamada | Cuándo | Notas | |------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Inicializa tu SDK de MMP o analíticas (AppsFlyer, Adjust, PostHog, Branch) | Al lanzar la app, lo primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerId`. | | 2a | `Adapty.Activate(builder.Build(), ...)` con `SetCustomerUserId` configurado en el builder | Al lanzar la app, después del paso 1, si tienes el customer user ID | Recomendado. Nunca se crea un perfil anónimo. | | 2b | `Adapty.Activate(builder.Build(), ...)` sin `SetCustomerUserId` | Al lanzar la app, después del paso 1, si no tienes el customer user ID (o nunca lo obtienes) | Adapty crea un perfil anónimo. | | 3 | `Adapty.SetIntegrationIdentifier(key, value, callback)` para cada MMP | Después del paso 2, antes de cualquier llamada de acción del usuario | Necesario para que los IDs del MMP aterricen en el perfil correcto. | | 4 | `Adapty.Identify("YOUR_USER_ID", callback)` | Después del paso 3 (o paso 2 si no hay MMP), antes del paso 5 — solo en la ruta 2b con autenticación | Espera el callback de finalización. Las llamadas concurrentes durante `Identify` producen `#3006 profileWasChanged`. | | 5 | `GetPaywall`, `GetPaywallProducts`, `RestorePurchases`, `MakePurchase`, `UpdateAttribution`, `UpdateProfile` | Después del paso 4 si llamas a `Identify`; en caso contrario, después del paso 3 (o paso 2 si no hay MMP) | Estas llamadas necesitan un perfil estable. | :::important Saltarse estos pasos provoca pérdida de acceso premium para usuarios recurrentes, `appsflyer_id` ausente en los perfiles y paywalls devueltos para la audiencia incorrecta. ::: ## Instalaciones web2app y web-funnel \{#web2app-and-web-funnel-installs\} Si los usuarios compran en un checkout web (Stripe, Paddle) y luego instalan la app nativa, el primer `Activate()` del dispositivo crea un nuevo perfil anónimo. Este perfil no está vinculado al perfil web. Si puedes resolver el customer user ID antes del lanzamiento de la app (desde tu flujo de autenticación o el referrer de instalación), pásalo directamente a `Activate()`. De lo contrario, la compra web es invisible en el dispositivo hasta que llames a `Identify("YOUR_USER_ID")` y luego a `RestorePurchases`. Para los metadatos que debes enviar con cada checkout web, consulta: - [Stripe](stripe) - [Paddle](paddle) --- # File: unity-optimize-paywall-fetching --- --- title: "Optimizar la carga de paywalls en Unity SDK" description: "Carga paywalls de Adapty de forma fiable: timing, caché y patrones de respaldo para Unity." --- Una carga de paywall fiable en Unity hace tres cosas: renderiza rápido, devuelve el paywall dirigido a la audiencia correcta y recurre al respaldo cuando la red es lenta. Las reglas a continuación cubren el timing, la caché y los patrones de respaldo para conseguirlo. :::tip Las reglas asumen que `Adapty.Activate()` y `Adapty.Identify()` ya han finalizado. Consulta [Orden de llamadas en Unity SDK](unity-sdk-call-order). ::: ## Reglas y errores comunes \{#rules-and-pitfalls\} | Haz esto | No hagas esto | Por qué | |---|---|---| | Carga el placement que vas a mostrar. | Precarga todos los placements de forma concurrente al iniciar. | La precarga masiva bloquea el hilo principal y produce una pantalla en negro durante la ráfaga. | | Llama a `GetPaywall` después de que la atribución haya tenido tiempo de resolverse — por ejemplo, 1–2 segundos después de `Activate` o cuando se dispare `OnLoadLatestProfile`. | Llama a `GetPaywall` en `Awake()`. | La atribución aún no ha llegado. El paywall se resuelve contra la audiencia predeterminada y omite silenciosamente los segmentos y la personalización de ASA. | | Establece un `loadTimeout` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | Esperes indefinidamente a que `GetPaywall` responda. | Sin un timeout, los usuarios con conectividad deficiente ven una pantalla en blanco hasta que la red responde — o cierran la app. | Consulta [Cargar paywalls y productos](fetch-paywalls-and-products-unity) para la referencia de los parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado. ## Ajustar para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad sistemáticamente deficiente (zonas rurales, transporte, regiones con problemas de enrutamiento): - Establece `fetchPolicy` a `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad` en cada carga excepto la primera. - Configura un [paywall de respaldo](fallback-paywalls) para cada placement en el Adapty Dashboard. - Establece `loadTimeout` entre 3 y 5 segundos y acepta el paywall de respaldo cuando se agote el tiempo. - No condicionales la visualización del paywall a `GetProfile`. Llama a `GetPaywall` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: unity-test --- --- title: "Prueba y lanzamiento con Unity SDK" description: "Aprende a probar y lanzar tu app Unity con el SDK de Adapty." --- Si ya has implementado el SDK de Adapty en tu app de Unity, querrás verificar que todo está configurado correctamente y que las compras funcionan como se espera en las plataformas iOS y Android. Esto implica probar tanto la integración del SDK como el flujo de compra real con el entorno sandbox de Apple y el entorno de pruebas de Google Play. ## Prueba tu app \{#test-your-app\} Para realizar pruebas completas de tus compras in-app, consulta nuestras guías de pruebas específicas para cada plataforma: [guía de pruebas para iOS](test-purchases-in-sandbox) y [guía de pruebas para Android](testing-on-android). ## Prepárate para el lanzamiento \{#prepare-for-release\} Antes de enviar tu app al store, sigue el [checklist de lanzamiento](release-checklist) para confirmar: - La conexión con el store y las notificaciones del servidor están configuradas - Las compras se completan y se reportan a Adapty - El acceso se desbloquea y se restaura correctamente - Se cumplen los requisitos de privacidad y revisión --- # File: InvalidProductIdentifiers-unity --- --- title: "Solución para el error Code-1000 noProductIDsFound en Unity SDK" description: "Resuelve errores de identificador de producto no válido al gestionar suscripciones en Adapty." --- El error con código 1000, `noProductIDsFound`, indica que ninguno de los productos que solicitaste en el paywall está disponible para comprar en el App Store, aunque aparezcan listados allí. A veces este error va acompañado de una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin el error, puedes ignorarla sin problema. Si estás viendo el error `noProductIDsFound`, sigue estos pasos para resolverlo: ## Paso 1. Comprueba el bundle ID \{#step-2-check-bundle-id\} 1. Abre [App Store Connect](https://appstoreconnect.apple.com/apps). Selecciona tu app y ve a la sección **General** → **App Information**. 2. Copia el **Bundle ID** en la subsección **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Abre la pestaña [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) desde el menú superior de Adapty y pega el valor copiado en el campo **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Vuelve a la página **App information** en App Store Connect y copia el **Apple ID** desde allí. 5. En la página [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) del Adapty Dashboard, pega el ID en el campo **Apple app ID**. ## Paso 2. Comprueba los productos \{#step-3-check-products\} 1. Ve a **App Store Connect** y navega a [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) en el menú de la izquierda. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripción. Verás tus productos listados en la sección **Subscriptions**. 3. Asegúrate de que el producto que estás probando aparece como **Ready to Submit**. <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Compara el ID del producto de la tabla con el que aparece en la pestaña [**Products**](https://app.adapty.io/products) del Adapty Dashboard. Si los IDs no coinciden, copia el ID del producto de la tabla y [crea un producto](create-product) con ese ID en el Adapty Dashboard. <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 3. Comprueba la disponibilidad del producto \{#step-4-check-product-availability\} 1. Vuelve a **App Store Connect** y abre la misma sección **Subscriptions**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripción para ver tus productos. 3. Selecciona el producto que estás probando. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hasta la sección **Availability** y comprueba que todos los países y regiones requeridos están en la lista. <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 4. Comprueba los precios del producto \{#step-5-check-product-prices\} 1. De nuevo, ve a la sección **Monetization** → **Subscriptions** en **App Store Connect**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Haz clic en el nombre del grupo de suscripción. 3. Selecciona el producto que estás probando. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Desplázate hacia abajo hasta **Subscription Pricing** y despliega la sección **Current Pricing for New Subscribers**. <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Asegúrate de que todos los precios requeridos están en la lista. <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Paso 5. Comprueba que el estado de pago de la app, la cuenta bancaria y los formularios fiscales están activos \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. En la página de inicio de [**App Store Connect**](https://appstoreconnect.apple.com/), haz clic en **Business**. <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Selecciona el nombre de tu empresa. <img src="/assets/shared/img/business-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Desplázate hacia abajo y comprueba que tu **Paid Apps Agreement**, **Bank Account** y **Tax forms** aparecen todos como **Active**. <img src="/assets/shared/img/appstore-connect-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Siguiendo estos pasos deberías poder resolver la advertencia `InvalidProductIdentifiers` y publicar tus productos en el store. ## Paso 6. Vuelve a crear el producto si está bloqueado \{#step-6-recreate-the-product-if-its-stuck\} Puede que los pasos 1 a 5 pasen todos correctamente — estado `Approved`, Bundle ID correcto, API key válida — y aun así el SDK siga devolviendo `1000 noProductIDsFound`. En ese caso, es posible que el producto esté bloqueado en el registro de Apple. El registro de productos de Apple puede entrar en un estado en el que un producto existe en la interfaz de App Store Connect pero no está expuesto en la ruta de búsqueda de StoreKit. Elimina el producto en App Store Connect y vuelve a crearlo con el mismo ID de producto. Espera hasta 24 horas después de volver a crearlo para que los cambios se propaguen. --- # File: cantMakePayments-unity --- --- title: "Solución para el error Code-1003 cantMakePayment en el SDK de Unity" description: "Resuelve el error de pagos al gestionar suscripciones en Adapty." --- El error 1003, `cantMakePayments`, indica que no es posible realizar compras in-app en este dispositivo. Si encuentras el error `cantMakePayments`, normalmente se debe a una de estas razones: - Restricciones del dispositivo: El error no está relacionado con Adapty. Consulta las soluciones más abajo. - Configuración del modo Observer: El método `makePurchase` y el modo Observer no pueden usarse al mismo tiempo. Consulta la sección más abajo. ## Problema: Restricciones del dispositivo \{#issue-device-restrictions\} | Problema | Solución | |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| | Restricciones de Screen Time | Desactiva las restricciones de compras in-app en [Screen Time](https://support.apple.com/en-us/102470) | | Cuenta suspendida | Contacta con el soporte de Apple para resolver problemas con la cuenta | | Restricciones regionales | Usa una cuenta de App Store de una región compatible | ## Problema: Usar el modo Observer y makePurchase a la vez \{#issue-using-both-observer-mode-and-makepurchase\} Si usas `makePurchases` para gestionar las compras, no necesitas el modo Observer. El [modo Observer](observer-vs-full-mode) solo es necesario si implementas la lógica de compra tú mismo. Por lo tanto, si usas `makePurchase`, puedes eliminar sin problema la activación del modo Observer del código de inicialización del SDK. --- # File: migration-to-unity-sdk-314 --- --- title: "Migrar el SDK de Adapty para Unity a v3.14" description: "Migra al SDK de Adapty para Unity v3.14 para obtener mejor rendimiento y nuevas funcionalidades de monetización." --- El SDK de Adapty 3.14.0 es una versión mayor que incluye mejoras que, sin embargo, pueden requerir algunos pasos de migración por tu parte: 1. Listener de eventos separado para eventos de paywall. 2. Cambiar el nombre de `AdaptyUI.CreateView` a `AdaptyUI.CreatePaywallView` y métodos relacionados. 3. Actualizar el método `MakePurchase` para usar `AdaptyPurchaseParameters` en lugar de parámetros individuales. 4. Reemplazar `SetFallbackPaywalls` con el método `SetFallback`. 5. Actualizar el acceso a propiedades del paywall para usar `AdaptyPlacement`. 6. Actualizar el acceso a la configuración remota para usar el objeto `AdaptyRemoteConfig`. 7. Reemplazar `VendorProductIds` con `ProductIdentifiers` en el modelo `AdaptyPaywall`. 8. Actualizar la política de fetch de `GetPaywall` para usar `AdaptyFetchPolicy`. ## Receptor de eventos separado para eventos de paywall \{#separate-event-listener-for-paywall-events\} Si muestras paywalls diseñados con el [Paywall Builder](adapty-paywall-builder), los eventos de vista de paywall ahora utilizan la interfaz `AdaptyPaywallsEventsListener` y el método `SetPaywallsEventsListener` dedicados. La interfaz principal `AdaptyEventListener` se mantiene para las actualizaciones de perfil y los detalles de instalación. ```diff showLineNumbers using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, - AdaptyEventListener { + AdaptyEventListener, + AdaptyPaywallsEventsListener { void Start() { Adapty.SetEventListener(this); + Adapty.SetPaywallsEventsListener(this); } // AdaptyEventListener methods public void OnLoadLatestProfile(AdaptyProfile profile) { } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } + // AdaptyPaywallsEventsListener methods + // Implement paywall event handlers here } ``` [Más información sobre el manejo de eventos de paywall](unity-handling-events). ## Renombrar los métodos de creación y presentación de vistas \{#rename-view-creation-and-presentation-methods\} Los métodos de creación y presentación de vistas han sido renombrados: ```diff showLineNumbers using AdaptySDK; - AdaptyUI.CreateView(paywall, parameters, (view, error) => { + AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { if (error != null) { // handle the error return; } - AdaptyUI.PresentView(view, (error) => { + AdaptyUI.PresentPaywallView(view, (error) => { // handle the error }); }); } ``` Del mismo modo, el método de cierre ha sido renombrado: ```diff showLineNumbers - AdaptyUI.DismissView(view, (error) => { + AdaptyUI.DismissPaywallView(view, (error) => { // handle the error }); ``` ## Actualizar el método MakePurchase \{#update-makepurchase-method\} El método `MakePurchase` ahora usa `AdaptyPurchaseParameters` en lugar de los argumentos individuales `subscriptionUpdateParams` e `isOfferPersonalized`. Esto proporciona mayor seguridad de tipos y permite ampliar los parámetros de compra en el futuro. ```diff showLineNumbers using AdaptySDK; void MakePurchase( AdaptyPaywallProduct product, AdaptySubscriptionUpdateParameters subscriptionUpdate, bool? isOfferPersonalized ) { - Adapty.MakePurchase(product, subscriptionUpdate, isOfferPersonalized, (result, error) => { + var parameters = new AdaptyPurchaseParametersBuilder() + .SetSubscriptionUpdateParams(subscriptionUpdate) + .SetIsOfferPersonalized(isOfferPersonalized) + .Build(); + + Adapty.MakePurchase(product, parameters, (result, error) => { switch (result.Type) { case AdaptyPurchaseResultType.Pending: // handle pending purchase break; case AdaptyPurchaseResultType.UserCancelled: // handle purchase cancellation break; case AdaptyPurchaseResultType.Success: var profile = result.Profile; // handle successful purchase break; default: break; } }); } ``` Si no se necesitan parámetros adicionales, puedes usar simplemente: ```csharp showLineNumbers using AdaptySDK; void MakePurchase(AdaptyPaywallProduct product) { Adapty.MakePurchase(product, (result, error) => { // handle purchase result }); } ``` ## Actualizar el método de respaldo \{#update-fallback-method\} :::important Al actualizar al SDK de Unity 3.14, deberás descargar los nuevos archivos de respaldo desde el Adapty Dashboard y reemplazar los existentes en tu proyecto. ::: El método para configurar los respaldos ha sido actualizado. El método `SetFallbackPaywalls` ha sido renombrado a `SetFallback`: ```diff showLineNumbers using AdaptySDK; void SetFallBackPaywalls() { #if UNITY_IOS var assetId = "adapty_fallback_ios.json"; #elif UNITY_ANDROID var assetId = "adapty_fallback_android.json"; #else var assetId = ""; #endif - Adapty.SetFallbackPaywalls(assetId, (error) => { + Adapty.SetFallback(assetId, (error) => { // handle the error }); } ``` Consulta el ejemplo de código completo en la página [Usar paywalls de respaldo en Unity](unity-use-fallback-paywalls). ## Actualizar el acceso a propiedades del paywall \{#update-paywall-property-access\} Las siguientes propiedades se han movido de `AdaptyPaywall` a `AdaptyPlacement`: ```diff showLineNumbers using AdaptySDK; void ProcessPaywall(AdaptyPaywall paywall) { - var abTestName = paywall.ABTestName; - var audienceName = paywall.AudienceName; - var revision = paywall.Revision; - var placementId = paywall.PlacementId; + var abTestName = paywall.Placement.ABTestName; + var audienceName = paywall.Placement.AudienceName; + var revision = paywall.Placement.Revision; + var placementId = paywall.Placement.Id; } ``` ## Actualizar el acceso a la configuración remota \{#update-remote-config-access\} Las propiedades de Remote Config se han reestructurado en un objeto `AdaptyRemoteConfig` para una mejor organización: ```diff showLineNumbers using AdaptySDK; void ProcessRemoteConfig(AdaptyPaywall paywall) { - var remoteConfigString = paywall.RemoteConfigString; - var locale = paywall.Locale; - var remoteConfigDict = paywall.RemoteConfig; + var remoteConfigString = paywall.RemoteConfig.Data; + var locale = paywall.RemoteConfig.Locale; + var remoteConfigDict = paywall.RemoteConfig.Dictionary; } ``` ## Actualización del uso del modelo AdaptyPaywall \{#update-adaptypaywall-model-usage\} La propiedad `VendorProductIds` ha quedado obsoleta en favor de `ProductIdentifiers`. La nueva propiedad devuelve objetos `AdaptyProductIdentifier` en lugar de simples cadenas de texto, lo que proporciona información de producto más estructurada. ```diff showLineNumbers using AdaptySDK; void ProcessPaywallProducts(AdaptyPaywall paywall) { - var productIds = paywall.VendorProductIds; - foreach (var vendorId in productIds) { - // use vendorId - } + var productIdentifiers = paywall.ProductIdentifiers; + foreach (var productId in productIdentifiers) { + var vendorId = productId.VendorProductId; + // use vendorId + } } ``` El objeto `AdaptyProductIdentifier` proporciona acceso al ID del producto del proveedor a través de la propiedad `VendorProductId`, manteniendo la misma funcionalidad y ofreciendo una mejor estructura para mejoras futuras. ## Actualizar la política de obtención en GetPaywall \{#update-getpaywall-fetch-policy\} El tipo del parámetro `fetchPolicy` en el método `GetPaywall` ha cambiado de `AdaptyPaywallFetchPolicy` a `AdaptyPlacementFetchPolicy`. Este cambio unifica el uso de la política de obtención en todo el SDK. ```diff showLineNumbers using AdaptySDK; void GetPaywall(string placementId) { - Adapty.GetPaywall(placementId, AdaptyPaywallFetchPolicy.ReloadRevalidatingCacheData, null, (paywall, error) => { + Adapty.GetPaywall(placementId, AdaptyPlacementFetchPolicy.ReloadRevalidatingCacheData, null, (paywall, error) => { // handle the result }); } ``` --- # File: migration-to-unity-sdk-34 --- --- title: "Migrar Adapty Unity SDK a v. 3.4" description: "Migra al Adapty Unity SDK v3.4 para mejorar el rendimiento y acceder a nuevas funciones de monetización." --- Adapty SDK 3.4.0 es una versión mayor que incluye mejoras que requieren pasos de migración por tu parte. ## Actualizar los archivos de paywall de respaldo \{#update-fallback-paywall-files\} Actualiza los archivos de paywall de respaldo para garantizar la compatibilidad con la nueva versión del SDK: 1. [Descarga los archivos de paywall de respaldo actualizados](fallback-paywalls) desde el Adapty Dashboard. 2. [Reemplaza los paywalls de respaldo existentes en tu aplicación móvil](unity-use-fallback-paywalls) con los nuevos archivos. ## Actualiza la implementación del modo Observer \{#update-implementation-of-observer-mode\} Si utilizas el modo Observer, asegúrate de actualizar su implementación. Anteriormente, se usaban distintos métodos para reportar transacciones a Adapty. En la nueva versión, el método `reportTransaction` debe usarse de forma consistente tanto en Android como en iOS. Este método reporta explícitamente cada transacción a Adapty, asegurando que sea reconocida. Si se utilizó un paywall, pasa el ID de variación para vincular la transacción a él. :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: ```diff showLineNumbers - #if UNITY_ANDROID && !UNITY_EDITOR - Adapty.RestorePurchases((profile, error) => { - // handle the error - }); - #endif Adapty.ReportTransaction( "YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID", // optional (error) => { // handle the error }); ``` --- # File: migration-to-unity330 --- --- title: "Migrar el SDK de Adapty para Unity a v3.3" description: "Migra al SDK de Adapty para Unity v3.3 para mejorar el rendimiento y acceder a nuevas funciones de monetización." --- Adapty SDK 3.3.0 es una versión mayor que incluye mejoras que, sin embargo, pueden requerir algunos pasos de migración por tu parte. 1. Actualiza a Adapty SDK v3.3.x. 2. Se han renombrado varias clases, propiedades y métodos en los módulos Adapty y AdaptyUI del SDK de Adapty. 3. A partir de ahora, el método `SetLogLevel` acepta un callback como argumento. 4. A partir de ahora, el método `PresentCodeRedemptionSheet` acepta un callback como argumento. 5. Cambia la forma en que se crea la vista del paywall. 6. Elimina el método `GetProductsIntroductoryOfferEligibility`. 7. Guarda los paywalls de respaldo en archivos separados (uno por plataforma) en `Assets/StreamingAssets/` y pasa los nombres de los archivos al método `SetFallbackPaywalls`. 8. Actualiza la lógica de compra. 9. Actualiza el manejo de eventos del Paywall Builder. 10. Actualiza el manejo de errores del paywall en el Paywall Builder. 11. Actualiza las configuraciones de integración para Adjust, Amplitude, AppMetrica, Appsflyer, Branch, Firebase y Google Analytics, Mixpanel, OneSignal, Pushwoosh. 13. Actualiza la implementación del modo Observer. 14. Actualiza la inicialización del plugin de Unity con una llamada explícita a `Activate`. ## Actualización del SDK de Adapty para Unity a la versión 3.3.x \{#upgrade-adapty-unity-sdk-to-33x\} Hasta esta versión, el SDK de Adapty era el núcleo obligatorio necesario para el correcto funcionamiento de Adapty en tu app, mientras que el SDK de AdaptyUI era opcional y solo se necesitaba si usabas el Paywall Builder de Adapty. A partir de la versión 3.3.0, el SDK de AdaptyUI queda obsoleto y AdaptyUI se integra en el SDK de Adapty como un módulo. Debido a estos cambios, es necesario eliminar AdaptyUI SDK y reinstalar Adapty SDK. 1. Elimina las dependencias de los paquetes **AdaptySDK** y **AdaptyUISDK** de tu proyecto. 2. Borra las carpetas **AdaptySDK** y **AdaptyUISDK**. 3. Importa de nuevo el paquete AdaptySDK como se describe en la página [Instalación y configuración del SDK de Adapty para Unity](sdk-installation-unity). ## Cambios de nombre \{#renamings\} 1. Renombrar en el módulo de Adapty: | Versión antigua | Nueva versión | | ------------------------- | ------------------------ | | Adapty.sdkVersion | Adapty.SDKVersion | | Adapty.LogLevel | AdaptyLogLevel | | Adapty.Paywall | AdaptyPaywall | | Adapty.PaywallFetchPolicy | AdaptyPaywallFetchPolicy | | PaywallProduct | AdaptyPaywallProduct | | Adapty.Profile | AdaptyProfile | | Adapty.ProfileParameters | AdaptyProfileParameters | | ProfileGender | AdaptyProfileGender | | Error | AdaptyError | 2. Renombrar en el módulo AdaptyUI: | Versión anterior | Nueva versión | | ------------------ | ------------------ | | CreatePaywallView | CreateView | | PresentPaywallView | PresentView | | DismissPaywallView | DismissView | | AdaptyUI.View | AdaptyUIView | | AdaptyUI.Action | AdaptyUIUserAction | ## Cambiar el método SetLogLevel \{#change-the-setloglevel-method\} A partir de ahora, el método `SetLogLevel` acepta un callback como argumento. ```diff showLineNumbers - Adapty.SetLogLevel(Adapty.LogLevel.Verbose); + Adapty.SetLogLevel(Adapty.LogLevel.Verbose, null); // or you can pass the callback to handle the possible error ``` ## Cambiar el método PresentCodeRedemptionSheet \{#change-the-presentcoderedemptionsheet-method\} A partir de ahora, el método `PresentCodeRedemptionSheet` acepta un callback como argumento. ```diff showLineNumbers - Adapty.PresentCodeRedemptionSheet(); + Adapty.PresentCodeRedemptionSheet(null); // or you can pass the callback to handle the possible error ``` ## Cambiar la forma en que se crea la vista del paywall \{#change-how-the-paywall-view-is-created\} Para ver el ejemplo de código completo, consulta [Obtener la configuración de vista del paywall diseñado con el Paywall Builder](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). ```diff showLineNumbers + var parameters = new AdaptyUICreateViewParameters() + .SetPreloadProducts(true); - AdaptyUI.CreatePaywallView( + AdaptyUI.CreateView( paywall, - preloadProducts: true, + parameters, (view, error) => { // use the view }); ``` ## Eliminar el método GetProductsIntroductoryOfferEligibility \{#remove-the-getproductsintroductoryoffereligibility-method\} Antes de Adapty iOS SDK 3.3.0, el objeto producto siempre incluía las ofertas, independientemente de si el usuario era elegible. Había que comprobar la elegibilidad manualmente antes de usar la oferta. Ahora, el objeto producto solo incluye una oferta si el usuario es elegible. Esto significa que ya no es necesario comprobar la elegibilidad: si hay una oferta disponible, el usuario es elegible. ## Método actualizado para proporcionar paywalls de respaldo \{#update-method-for-providing-fallback-paywalls\} Hasta esta versión, los paywalls de respaldo se pasaban como JSON serializado. A partir de la v 3.3.0, el mecanismo ha cambiado: 1. Guarda los paywalls de respaldo en archivos dentro de `/Assets/StreamingAssets/`, un archivo para Android y otro para iOS. 2. Pasa los nombres de los archivos al método `SetFallbackPaywalls`. Tu código cambiará de la siguiente manera: ```diff showLineNumbers using AdaptySDK; void SetFallBackPaywalls() { + #if UNITY_IOS + var assetId = "adapty_fallback_ios.json"; + #elif UNITY_ANDROID + var assetId = "adapty_fallback_android.json"; + #else + var assetId = ""; + #endif - Adapty.SetFallbackPaywalls("FALLBACK_PAYWALLS_JSON_STRING", (error) => { + Adapty.SetFallbackPaywalls(assetId, (error) => { // handle the error }); } ``` Consulta el ejemplo de código completo en la página [Usar paywalls de respaldo en Unity](unity-use-fallback-paywalls). ## Actualizar la realización de compras \{#update-making-purchase\} Las compras previamente canceladas y pendientes se consideraban errores y devolvían los códigos `PaymentCancelled` y `PendingPurchase`, respectivamente. Ahora se usa la nueva clase `AdaptyPurchaseResultType` para procesar compras canceladas, exitosas y pendientes. Actualiza el código de compra de la siguiente manera: ```diff showLineNumbers using AdaptySDK; void MakePurchase(AdaptyPaywallProduct product) { - Adapty.MakePurchase(product, (profile, error) => { - // handle successfull purchase + Adapty.MakePurchase(product, (result, error) => { + switch (result.Type) { + case AdaptyPurchaseResultType.Pending: + // handle pending purchase + break; + case AdaptyPurchaseResultType.UserCancelled: + // handle purchase cancellation + break; + case AdaptyPurchaseResultType.Success: + var profile = result.Profile; + // handle successful purchase + break; + default: + break; } }); } ``` Mira el ejemplo de código final en la página [Realizar compras en la app móvil](unity-making-purchases). ## Actualización del manejo de eventos del Paywall Builder \{#update-handling-of-paywall-builder-events\} Las compras canceladas y pendientes ya no se consideran errores; todos estos casos se procesan con el método `PaywallViewDidFinishPurchase`. 1. Elimina el procesamiento del evento de compra cancelada. 2. Actualiza el manejo del evento de compra exitosa de la siguiente manera: ```diff showLineNumbers - public void OnFinishPurchase( - AdaptyUI.View view, - Adapty.PaywallProduct product, - Adapty.Profile profile - ) { } + public void PaywallViewDidFinishPurchase( + AdaptyUIView view, + AdaptyPaywallProduct product, + AdaptyPurchaseResult purchasedResult + ) { } ``` 3. Actualiza el manejo de acciones: ```diff showLineNumbers - public void OnPerformAction( - AdaptyUI.View view, - AdaptyUI.Action action - ) { + public void PaywallViewDidPerformAction( + AdaptyUIView view, + AdaptyUIUserAction action + ) { switch (action.Type) { - case AdaptyUI.ActionType.Close: + case AdaptyUIUserActionType.Close: view.Dismiss(null); break; - case AdaptyUI.ActionType.OpenUrl: + case AdaptyUIUserActionType.OpenUrl: var urlString = action.Value; if (urlString != null { Application.OpenURL(urlString); } default: // handle other events break; } } ``` 4. Actualiza el manejo del inicio de compra: ```diff showLineNumbers - public void OnSelectProduct( - AdaptyUI.View view, - Adapty.PaywallProduct product - ) { } + public void PaywallViewDidSelectProduct( + AdaptyUIView view, + string productId + ) { } ``` 5. Actualiza el manejo de compra fallida: ```diff showLineNumbers - public void OnFailPurchase( - AdaptyUI.View view, - Adapty.PaywallProduct product, - Adapty.Error error - ) { } + public void PaywallViewDidFailPurchase( + AdaptyUIView view, + AdaptyPaywallProduct product, + AdaptyError error + ) { } ``` 6. Actualiza el manejo del evento de restauración exitosa: Check out the final code example in the [Handle paywall events](unity-handling-events) page. ## Actualización del manejo de errores en paywalls del Paywall Builder \{#update-handling-of-paywall-builder-paywall-errors\} El manejo de errores también ha cambiado; actualiza tu código siguiendo las indicaciones a continuación. 1. Actualiza el manejo de errores de carga de productos: ```diff showLineNumbers - public void OnFailLoadingProducts( - AdaptyUI.View view, - Adapty.Error error - ) { } + public void PaywallViewDidFailLoadingProducts( + AdaptyUIView view, + AdaptyError error + ) { } ``` 2. Actualiza el manejo de errores de renderizado: ```diff showLineNumbers - public void OnFailRendering( - AdaptyUI.View view, - Adapty.Error error - ) { } + public void PaywallViewDidFailRendering( + AdaptyUIView view, + AdaptyError error + ) { } ``` ## Actualizar la configuración del SDK de integraciones de terceros \{#update-third-party-integration-sdk-configuration\} A partir de Adapty Unity SDK 3.3.0, hemos actualizado la API pública del método `updateAttribution`. Anteriormente, aceptaba un diccionario `[AnyHashable: Any]`, lo que permitía pasar objetos de atribución directamente desde varios servicios. Ahora requiere un `[String: any Sendable]`, por lo que tendrás que convertir los objetos de atribución antes de pasarlos. Para garantizar que las integraciones funcionen correctamente con Adapty Unity SDK 3.3.0 y versiones posteriores, actualiza las configuraciones de tu SDK para las siguientes integraciones tal como se describe en las secciones a continuación. ### Adjust Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers - using static AdaptySDK.Adapty; using AdaptySDK; Adjust.GetAdid((adid) => { - Adjust.GetAttribution((attribution) => { - Dictionary<String, object> data = new Dictionary<String, object>(); - - data["network"] = attribution.Network; - data["campaign"] = attribution.Campaign; - data["adgroup"] = attribution.Adgroup; - data["creative"] = attribution.Creative; - - String attributionString = JsonUtility.ToJson(data); - Adapty.UpdateAttribution(attributionString, AttributionSource.Adjust, adid, (error) => { - // handle the error - }); + if (adid != null) { + Adapty.SetIntegrationIdentifier( + "adjust_device_id", + adid, + (error) => { + // handle the error + }); } }); Adjust.GetAttribution((attribution) => { Dictionary<String, object> data = new Dictionary<String, object>(); data["network"] = attribution.Network; data["campaign"] = attribution.Campaign; data["adgroup"] = attribution.Adgroup; data["creative"] = attribution.Creative; String attributionString = JsonUtility.ToJson(data); - Adapty.UpdateAttribution(attributionString, AttributionSource.Adjust, adid, (error) => { + Adapty.UpdateAttribution(attributionString, "adjust", (error) => { // handle the error }); }); ``` ### Amplitude Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers using AdaptySDK; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetAmplitudeUserId("YOUR_AMPLITUDE_USER_ID"); - builder.SetAmplitudeDeviceId(amplitude.getDeviceId()); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + Adapty.SetIntegrationIdentifier( + "amplitude_user_id", + "YOUR_AMPLITUDE_USER_ID", + (error) => { + // handle the error + }); + Adapty.SetIntegrationIdentifier( + "amplitude_device_id", + amplitude.getDeviceId(), + (error) => { + // handle the error + }); ``` ### AppMetrica Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers using AdaptySDK; - var deviceId = AppMetrica.GetDeviceId(); - if (deviceId != null { - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID"); - builder.SetAppmetricaDeviceId(deviceId); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); - } + var deviceId = AppMetrica.GetDeviceId(); + if (deviceId != null { + Adapty.SetIntegrationIdentifier( + "appmetrica_device_id", + deviceId, + (error) => { + // handle the error + }); + + Adapty.SetIntegrationIdentifier( + "appmetrica_profile_id", + "YOUR_ADAPTY_CUSTOMER_USER_ID", + (error) => { + // handle the error + }); + } ``` ### AppsFlyer Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers using AppsFlyerSDK; using AdaptySDK; // before SDK initialization AppsFlyer.getConversionData(this.name); // in your IAppsFlyerConversionData void onConversionDataSuccess(string conversionData) { // It's important to include the network user ID - string appsFlyerId = AppsFlyer.getAppsFlyerId(); - Adapty.UpdateAttribution(conversionData, AttributionSource.Appsflyer, appsFlyerId, (error) => { + string appsFlyerId = AppsFlyer.getAppsFlyerId(); + + Adapty.SetIntegrationIdentifier( + "appsflyer_id", + appsFlyerId, + (error) => { // handle the error }); + + Adapty.UpdateAttribution( + conversionData, + "appsflyer", + (error) => { + // handle the error + }); } ``` ### Branch Actualiza el código de tu app para móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers using AdaptySDK; - class YourBranchImplementation { - func initializeBranch() { - Branch.getInstance().initSession(launchOptions: launchOptions) { (data, error) in - if let data { - Adapty.updateAttribution(data, source: .branch) - } - } - } - } + Branch.initSession(delegate(Dictionary<string, object> parameters, string error) { + string attributionString = JsonUtility.ToJson(parameters); + + Adapty.UpdateAttribution( + attributionString, + "branch", + (error) => { + // handle the error + }); + }); ``` ### Firebase y Google Analytics \{#firebase-and-google-analytics\} Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Firebase y Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers // We suppose FirebaseAnalytics Unity Plugin is already installed using AdaptySDK; Firebase.Analytics .FirebaseAnalytics .GetAnalyticsInstanceIdAsync() .ContinueWithOnMainThread((task) => { if (!task.IsCompletedSuccessfully) { // handle error return; } var firebaseId = task.Result var builder = new Adapty.ProfileParameters.Builder(); - builder.SetFirebaseAppInstanceId(firebaseId); - - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error + Adapty.SetIntegrationIdentifier( + "firebase_app_instance_id", + firebaseId, + (error) => { + // handle the error }); }); ``` ### Mixpanel Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers using AdaptySDK; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetMixpanelUserId(Mixpanel.DistinctId); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + var distinctId = Mixpanel.DistinctId; + if (distinctId != null) { + Adapty.SetIntegrationIdentifier( + "mixpanel_user_id", + distinctId, + (error) => { + // handle the error + }); + } ``` ### OneSignal Actualiza el código de tu aplicación móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers using AdaptySDK; - using OneSignalSDK; - var pushUserId = OneSignal.Default.PushSubscriptionState.userId; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetOneSignalPlayerId(pushUserId); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + var distinctId = Mixpanel.DistinctId; + if (distinctId != null) { + Adapty.SetIntegrationIdentifier( + "mixpanel_user_id", + distinctId, + (error) => { + // handle the error + }); + } ``` ### Pushwoosh Actualiza el código de tu app móvil como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [Configuración del SDK para la integración con Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers using AdaptySDK; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetPushwooshHWID(Pushwoosh.Instance.HWID); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + Adapty.SetIntegrationIdentifier( + "pushwoosh_hwid", + Pushwoosh.Instance.HWID, + (error) => { + // handle the error + }); ``` ## Actualiza la implementación del modo Observer \{#update-observer-mode-implementation\} Actualiza la forma en que vinculas los paywalls a las transacciones. Antes, usabas el método `setVariationId` para asignar el `variationId`. Ahora puedes incluir el `variationId` directamente al registrar la transacción mediante el nuevo método `reportTransaction`. Consulta el ejemplo de código completo en [Asociar paywalls con transacciones de compra en modo Observer](report-transactions-observer-mode-unity). ```diff showLineNumbers // every time when calling transaction.finish() - Adapty.SetVariationForTransaction("<variationId>", "<transactionId>", (error) => { - if(error != null) { - // handle the error - return; - } - - // successful binding - }); + Adapty.ReportTransaction( + "YOUR_TRANSACTION_ID", + "PAYWALL_VARIATION_ID", // optional + (error) => { + // handle the error + }); ``` ## Actualizar la inicialización del plugin de Unity \{#update-the-unity-plugin-initialization\} A partir de Adapty Unity SDK 3.3.0, es obligatorio llamar al método `Activate` de forma explícita durante la inicialización del plugin: ```csharp showLineNumbers Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` --- # File: migration-to-unity-sdk-v3 --- --- title: "Migrar el SDK de Unity de Adapty a v3.0" description: "Migra al SDK de Unity de Adapty v3.0 para obtener mejor rendimiento y nuevas funcionalidades de monetización." --- El SDK de Adapty v3.0 incorpora soporte para el nuevo [Adapty Paywall Builder](adapty-paywall-builder), la nueva versión de la herramienta no-code y fácil de usar para crear paywalls. Con su máxima flexibilidad y ricas capacidades de diseño, tus paywalls serán más efectivos y rentables. ## Proceso de actualización \{#upgrade-process\} El proceso de actualización para Unity sigue los mismos pasos que en otras plataformas: 1. Actualiza al SDK de Adapty v3.x 2. Migra tus paywalls existentes al nuevo Paywall Builder Para instrucciones de migración específicas de Unity, consulta la [guía de instalación del SDK de Unity](sdk-installation-unity) y sigue los pasos generales de migración descritos en la guía de migración principal. --- # File: unity-migration-guide --- --- title: "Guía de migración del SDK" description: "Guías de migración para el SDK de Adapty para Unity." --- ## Guías de migración \{#migration-guides\} ### [Guía de migración al SDK de Unity Adapty 3.x](unity-sdk-migration-guides) Aprende a migrar desde versiones anteriores al SDK de Unity Adapty 3.x. ## Novedades \{#whats-new\} ### Versión 3.x \{#version-3x\} - Presentación de paywall mejorada - Gestión de errores mejorada - Mejor compatibilidad con C# - Optimizaciones de rendimiento ### Versión 2.x \{#version-2x\} - Nuevas funciones de onboarding - Analíticas mejoradas - Flujo de compra mejorado - Correcciones de errores y mejoras de estabilidad ## Cambios incompatibles \{#breaking-changes\} ### Versión 3.x \{#version-3x-breaking\} - API de observer actualizada - Métodos de presentación de paywall modificados - Estructura de gestión de errores modificada ### Versión 2.x \{#version-2x-breaking\} - API de onboarding actualizada - Estructura de perfil modificada - Flujo de compra modificado ## Lista de comprobación para la migración \{#migration-checklist\} Al migrar a una nueva versión: - [ ] Revisar los cambios incompatibles - [ ] Actualizar las llamadas a la API - [ ] Probar todas las funcionalidades - [ ] Actualizar la gestión de errores - [ ] Verificar el seguimiento de analíticas - [ ] Probar en todas las plataformas --- # End of Documentation _Generated on: 2026-07-24T13:01:55.981Z_ _Successfully processed: 41/41 files_