# Adapty Documentation (Full Content) > Complete documentation content across all platforms. > **AI agents**: to search Adapty docs faster and with fewer tokens, install the Adapty skills: `npx adapty skills install --yes` Locale: es Generated on: 2026-10-30T13:00:40.025Z --- # ANDROID - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-10-30T13:00:39.409Z Total files: 54 --- # File: android-sdk-overview --- --- title: "Descripción general del SDK de Android" description: "Conoce el SDK de Android de Adapty y sus principales características." --- [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Android.svg?style=flat&logo=android)](https://github.com/adaptyteam/AdaptySDK-Android/releases) ¡Bienvenido! Estamos aquí para que las compras in-app sean pan comido 🚀 Hemos creado el SDK de Adapty para Android para quitarte el dolor de cabeza de las compras in-app y que puedas centrarte en lo que mejor sabes hacer: crear apps increíbles. Esto es lo que gestionamos por ti: - Gestión de compras, validación de recibos y administración de suscripciones desde el primer momento - Crea y prueba flows y 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 adentrarte en el código, necesitarás integrar Adapty con Google Play Console y configurar los productos en el dashboard. Consulta nuestra [guía de inicio rápido](quickstart) para tener todo listo primero. ::: ## Primeros pasos \{#get-started\} For a fully automated integration, use the [adapty-integration skill](https://github.com/adaptyteam/adapty-skills): 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. [Instala y configura el SDK](sdk-installation-android): Añade el SDK como dependencia a tu proyecto y actívalo en el código. 2. [Activa las compras con flows y paywalls](android-quickstart-paywalls): Configura el flujo de compra para que los usuarios puedan adquirir productos. Si prefieres construir tu propia interfaz, consulta [Implementar paywalls personalizados](android-quickstart-manual). 3. [Comprueba el estado de la suscripción](android-check-subscription-status): Verifica automáticamente el estado de la suscripción del usuario y controla su acceso al contenido de pago. 4. [Identifica a los usuarios (opcional)](android-quickstart-identify): Asocia a 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? Te lo mostramos: - **App de ejemplo**: Consulta nuestro [ejemplo completo](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app) que muestra la configuración completa ## Conceptos principales \{#main-concepts\} Antes de entrar en el código, familiarízate con los conceptos clave de Adapty. Lo mejor del enfoque de Adapty es que solo los placements están hardcodeados en tu app. Todo lo demás — productos, diseños de paywall, 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. **Flows y paywalls** - Productos agrupados con configuración, asociados a un placement. Dos formas de crearlos, comparadas en [Flows y paywalls](flows-and-paywalls): - **[Flow & Paywall Builder](adapty-flow-builder)** - Interfaz visual sin código. Adapty renderiza la interfaz y gestiona la compra por ti. - **[Paywalls personalizados](paywalls)** - Sin configuración visual; tú construyes la interfaz en tu propio código y llamas a `makePurchase` tú mismo. Consulta [Implementar paywalls personalizados](android-quickstart-manual). En el código del SDK, ambos se recuperan 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 son el "dónde" y "cuándo" de tu estrategia de monetización. Algunos placements habituales son: - `main` - Tu ubicación principal de 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` para tu primera integración, y luego [piensa en qué otros momentos 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-android --- --- title: "Instalar y configurar el SDK de Android" description: "Guía paso a paso para instalar el SDK de Adapty en Android para apps 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 app. - **AdaptyUI**: Este módulo renderiza flows, además de los paywalls del builder antiguo. 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 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. ::: ## Dependencias \{#dependencies\} El SDK de Adapty es compatible con estas versiones de Google Play Billing Library: | Versión del SDK de Adapty | Versión de Billing Library | |---|---| | 4.0.2 y posteriores | v8 | | 4.0.0–4.0.1 | v7 por defecto, v8 si otra dependencia la eleva | | 3.17.3 y posteriores en la línea 3.x | v8 | | 3.15.0–3.17.2 | v7 por defecto, v8 si otra dependencia la eleva | :::note La compatibilidad con una versión de Billing Library no implica que Adapty sea compatible con todas las funciones que Google introdujo en ella. Antes de adoptar una nueva funcionalidad de facturación de Google Play, consulta [Producto en Play Store](android-products). ::: ## 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) Siempre recomendamos instalar la versión más reciente disponible del SDK — incluye las últimas correcciones de estabilidad y mejoras. ```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() } } ```
## 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` termine antes de llamar a cualquier otro método del SDK de Adapty. Consulta [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 el [Flow & Paywall Builder](adapty-flow-builder), sigue el [quickstart](android-quickstart-paywalls). - Si construyes tu propia interfaz de paywall, consulta el [quickstart para paywalls personalizados](android-quickstart-manual). ## Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Si tienes previsto usar el [Flow & Paywall Builder](adapty-flow-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(); ``` #### Habilitar Adapty Attribution \{#enable-adapty-attribution\} A partir de la versión 4.1 del SDK de Adapty, [Adapty Attribution](user-acquisition) está desactivado por defecto. Al activar el módulo de Adapty, establece `adaptyAttributionEnabled` en `true` para que el SDK registre las instalaciones y envíe los detalles de instalación a tu app. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withAdaptyAttributionEnabled(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withAdaptyAttributionEnabled(true) .build(); ``` En versiones del SDK anteriores a la 4.1, la atribución de Adapty se activa automáticamente — no es necesario ningún cambio en el código. #### 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 caché proporcionando una configuración personalizada. Usa `AdaptyUI.configureMediaCache` para cambiar 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 de tamaño 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(); ``` ### Gestión de mensajes de Google Play \{#google-play-messages-handling\} :::info Este parámetro está disponible a partir de la versión 4.3.0 del SDK. ::: Google Play muestra mensajes in-app, como un aviso de que el pago de una suscripción fue rechazado, solo cuando la app los solicita. Por defecto, el SDK los solicita cada vez que se inicia una `Activity` de tu app. Para elegir cuándo mostrarlos, establece `withStoreMessagesHandling` en `MANUAL`. El valor predeterminado es `AUTO`. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withStoreMessagesHandling(AdaptyConfig.StoreMessagesHandling.MANUAL) // AUTO is the default .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withStoreMessagesHandling(AdaptyConfig.StoreMessagesHandling.MANUAL) // AUTO is the default .build(); ``` En el modo manual, el SDK no solicita mensajes de Google Play por su cuenta, por lo que tu aplicación debe llamar a `showStoreMessages`. Consulta cómo hacerlo en [Controlar mensajes de Google Play](android-store-messages). ## 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 & Paywall Builder en el SDK de Android" description: "Guía de inicio rápido para habilitar compras in-app con el Flow & Paywall Builder 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) - [**Flows y paywalls**](adapty-flow-builder) – pantallas que presentan productos a los usuarios, creadas en el Flow & Paywall Builder sin código. El SDK los recupera mediante `getFlow`. Si prefieres construir la interfaz en tu propio código, consulta [Implementar paywalls personalizados](android-quickstart-manual). - [**Placements**](placements) – dónde y cuándo muestras flows y paywalls en tu app (como `main`, `onboarding`, `settings`). Los vinculas 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 opciones a distintos 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 usarla | |---|---|---| | Adapty Flow & Paywall Builder | ✅ Fácil | [Creas un flow o paywall completo y listo para compras en el editor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y se encarga de toda la lógica de compra, validación de recibos y gestión de suscripciones. | | Paywalls personalizados | 🟡 Media | Implementas la UI 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 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 o paywall creado en el Adapty Flow & Paywall Builder.** Si prefieres construir la UI del paywall tú mismo, consulta [Implementar paywalls personalizados](android-quickstart-manual). ::: Para mostrar un flow o paywall creado en el Adapty Flow & Paywall Builder, en el código de tu app solo necesitas: 1. **Obtén el flow o paywall**: Obtenlo desde Adapty. 2. **Muéstralo y Adapty gestionará las compras por ti**: Muestra la vista en tu app. 3. **Gestiona las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abre enlaces o cierra el paywall cuando el usuario haga clic en los 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 o paywall](paywall-builder-templates) y [añade productos a él](paywall-product-block). 4. [Crea un placement y añade tu flow o paywall](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 o paywall \{#1-get-the-flow-or-paywall\} Tus flows y paywalls están asociados a placements configurados en el dashboard. Los placements te permiten mostrar diferentes flows o paywalls a distintas audiencias o ejecutar [pruebas A/B](ab-tests). Para obtener un flow o paywall creado en el Adapty Flow & Paywall 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. Obtén la configuración de vista con el método `getFlowConfiguration`. La configuración de vista contiene los elementos de UI y los estilos necesarios para mostrarla. :::important Para obtener la configuración de vista, [publica el flow o el paywall](builder-save-publish). Un flow o paywall con cambios sin publicar tiene el estado **Dirty**, y su placement continúa sirviendo la última versión publicada. ::: ```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 o el paywall \{#2-display-the-flow-or-paywall\} Ahora que tienes la configuración del flow, basta con añadir unas pocas líneas para mostrar tu flow o paywall. 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 o un paywall, consulta nuestra [guía](android-present-paywalls). ::: ## 3. Gestionar acciones de botones \{#handle-button-actions\} Cuando los usuarios pulsan botones en el flow o el paywall, el SDK de Android gestiona automáticamente las compras, la restauración, el cierre de pantalla 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 sobrescribir 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 haría 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 flow o 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 flow. Ahora necesitas [verificar el nivel de acceso de los usuarios](android-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 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 la skill de integración del SDK" description: "Usa la skill adapty-integration para integrar el SDK de Adapty en tu app Android de principio a fin con tu herramienta de codificación con IA." --- :::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-android) — te lleva a través de cada etapa con la documentación adecuada. ::: El skill `adapty-integration` automatiza la integración de Adapty de principio a fin: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación relevante de Adapty en cada etapa. Funciona en cualquier agente de programación que soporte skills: Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI y otros. El skill forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener los nuevos skills a medida que se publiquen, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Para iniciar la integración, pide a tu agente: ``` Integrate Adapty into my app ``` El skill hace algunas preguntas de configuración y luego te guía a través de 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-integration skill](https://github.com/adaptyteam/adapty-skills): 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 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ñadido el skill, ejecuta `/adapty-cli` en tu agente. Te guiará paso a paso, incluyendo cuándo abrir el Dashboard para conectar tu store. La CLI de skills no actualiza el skill por sí sola. Para obtener la versión más reciente, ejecuta `npx skills update`. ### 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 referencias los productos directamente en el código — Adapty los entrega a través de 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 UI tú mismo), 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("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 querrá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) - **Flows y placements adicionales**: Añade más llamadas `getFlow` con distintos 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) 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 Flow & Paywall Builder**](adapty-flow-builder): Creas flows en el editor no-code de Adapty y el SDK los renderiza automáticamente. - [**Paywalls personalizadas**](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 analíticas e integraciones. ¿No sabes cuál elegir? Lee 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 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 hasta el final. Consulta [Probar compras en sandbox](test-purchases-in-sandbox) para las instrucciones de configuración. **Guías:** - [Habilitar compras con Flow & Paywall Builder (inicio rápido)](android-quickstart-paywalls) - [Obtener flows y su configuración](android-get-pb-paywalls) - [Mostrar flows](android-present-paywalls) - [Gestionar eventos de flow](android-handling-events) - [Responder a acciones de botones](android-handle-paywall-actions) Lo siento, pero no puedo acceder a URLs externas ni navegar por internet. Sin embargo, puedo ayudarte a traducir la documentación de Adapty si me la proporcionas directamente aquí. Por favor, pega el contenido MDX que deseas traducir y lo procesaré según las instrucciones indicadas. :::tip[Checkpoint] - **Expected:** El paywall aparece con tus productos configurados. Al pulsar un producto se activa el diálogo de compra en sandbox. - **Gotcha:** Flow vacío o error en `getFlow` → verifica que el ID del placement coincida exactamente con el dashboard y que el placement tenga una audiencia asignada. ::: **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](android-quickstart-manual) - [Obtener paywalls y productos personalizados](fetch-paywalls-and-products-android) - [Presentar paywalls personalizados](present-remote-config-paywalls-android) - [Realizar compras](android-making-purchases) - [Restaurar compras](android-restore-purchase) 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) Send this to your 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. - **Atención:** Si no aparecen eventos, verifica que estás notificando las transacciones a Adapty y que las Google Play Real-Time Developer Notifications 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-paywalls --- --- title: "Flows y paywalls - Android" description: "Muestra y gestiona flows y paywalls creados con los editores no-code de Adapty en tu aplicación Android." --- ## Mostrar paywalls \{#display-paywalls\} ### Adapty Flow & Paywall Builder :::tip Para comenzar rápidamente con los flows y paywalls de Adapty, consulta nuestra [guía de inicio rápido](android-quickstart-paywalls). ::: ### Implementar paywalls personalizados Para más guías sobre cómo implementar paywalls personalizados y gestionar compras, consulta [Implementar paywalls personalizados](android-implement-paywalls-manually). ## Funciones útiles \{#useful-features\} --- # File: android-get-pb-paywalls --- --- title: "Obtener flows y paywalls - Android" description: "Obtén flows y paywalls de Adapty en tu aplicación Android." --- Después de [diseñar tu flow](adapty-flow-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. :::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 \{#before-you-start\} Necesitas: - **Productos en el Adapty Dashboard**: [Crea tus productos](create-product). - **Un flow con productos**: Constrúyelo en el [Flow & Paywall Builder](adapty-flow-builder) y [asigna productos](paywall-product-block). - **Un placement**: [Crea un placement](create-placement) y asígnale tu flow. - **SDK instalado**: Consulta la [guía de instalación del SDK para Android](sdk-installation-android). ## Obtener un flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall en el builder, no tienes que preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Un flow o paywall así contiene tanto lo que debe mostrarse como la forma en que debe hacerlo. 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 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 mostrárselas al usuario. :::tip Para precalentar varios placements a la vez, llama a [`preloadFlows`](android-optimize-paywall-fetching#preload-placements) (Android SDK 4.1+). Solo almacena en caché el JSON del placement, por lo que igualmente debes obtener la configuración de vista para cargar el diseño y las imágenes. ::: Para obtener un flow o paywall, usa 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", AdaptyPlacementFetchPolicy.Default, 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 } }); ``` (no translatable prose — no output needed) | 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: `AdaptyPlacementFetchPolicy.Default` |

`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. Recomendamos esta opción porque garantiza que los usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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.

Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché es más reciente que `maxAgeMillis`, y va al servidor una vez que es más antigua.

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.

El SDK de Adapty almacena flows y 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 obtenerlos 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, asegurando 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 timeout, 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 timeout 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 | | :-------- | :---------- | | Flow | Un objeto `AdaptyFlow` que contiene el placement, los identificadores (`id`, `variationId`), el nombre de la variante (`variationName`, opcional, SDK 4.2+), el nombre, sus variantes de paywall (`paywalls`), los Remote Configs y un flag `hasViewConfiguration` que indica si el flow incluye una configuración de vista. Para obtener los productos reales para 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 & Paywall Builder** (un flow) o en el **old Paywall Builder** (un paywall). Adapty renderiza la interfaz por ti. Continúa con los pasos siguientes 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 de Builder. [Renderízalo tú mismo](present-remote-config-paywalls-android). :::important Asegúrate de [publicar el flow](builder-save-publish). Un flow con ediciones sin publicar tiene el estado **Dirty**, y su placement seguirá sirviendo la última versión publicada. ::: 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 | El identificador de la [localización del flow](add-paywall-locale-in-adapty-paywall-builder) con la que se renderizará la vista, expresado como un código de idioma con una o dos subetiquetas separadas por `-` (p. ej., `en`, `pt-br`). Si se omite, la vista se renderiza en `en`, o en la localización predeterminada del flow cuando este no tiene `en`. Consulta [Localizaciones y códigos de idioma](android-localizations-and-locale-codes). | | **customLayoutId** |

opcional

por defecto: `null`

SDK 4.1+

| El ID personalizado de un layout en la configuración de layouts del flow. Pásalo para renderizar ese layout específico en lugar del que el SDK selecciona automáticamente según el tipo de dispositivo y el tamaño de pantalla. Si ningún layout coincide con el ID, la llamada falla con un error de no-view-configuration. El Flow & Paywall Builder aún no asigna IDs de layout personalizados, por lo que conviene dejar este parámetro sin definir. | | **loadTimeout** | por defecto: 5 seg | Este valor limita el tiempo de espera de 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 superar ligeramente el tiempo indicado en `loadTimeout`, ya que la operación puede incluir distintas solicitudes internamente. |
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, null, 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 mediante `Adapty.getFlow`. | | **locale** | opcional | El identificador de la [localización del flow](add-paywall-locale-in-adapty-paywall-builder) con la que se renderizará la vista, expresado como un código de idioma con uno o dos subetiquetas separadas por `-` (p. ej., `en`, `pt-br`). Si se omite, la vista se renderiza en `en`, o en la localización predeterminada del flow cuando este no tiene `en`. Consulta [Localizaciones y códigos de idioma](android-localizations-and-locale-codes). | | **customLayoutId** |

opcional

valor predeterminado: `null`

SDK 4.1+

| El ID personalizado de un layout en la configuración de layouts del flow. Pásalo para renderizar ese layout específico en lugar del que el SDK selecciona automáticamente según el tipo de dispositivo y el tamaño de pantalla. Si ningún layout coincide con el ID, la llamada falla con un error de no-view-configuration. El Flow & Paywall Builder aún no asigna IDs de layout personalizados, así que déjalo sin configurar. | | **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 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 solicitudes internamente. |
:::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** | requerido | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `AdaptyPlacementFetchPolicy.Default` |

`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud 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 inestable, considera `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo consulta el servidor cuando no hay nada almacenado. Es posible que los usuarios no obtengan los datos más recientes, pero la carga será más rápida 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.

Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada tiene menos de `maxAgeMillis`, y consulta el servidor cuando es más antigua.

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

| ## Personalizar 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, 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. - Mostrar contenido multimedia incluido en la app, para que la primera pantalla se renderice sin necesidad de descarga. Consulta [Mostrar el contenido multimedia de la primera pantalla desde el paquete de la app](android-optimize-paywall-fetching#show-first-screen-media-from-the-app-bundle). A continuación tienes un ejemplo de cómo proporcionar assets 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 antiguo 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, como se describe a continuación. :::warning Los paywalls creados con el Paywall Builder de SDK 3.x requieren la versión 3.0 o superior del SDK de Android. ::: Ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Si implementas paywalls personalizados, consulta el tema [Obtener paywalls y productos para paywalls personalizados 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** | obligatorio | 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. 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** | predeterminado: `AdaptyPlacementFetchPolicy.Default` |

`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud 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 conexión inestable, considera `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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 peticiones de red.

Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre ambas: lee primero la caché mientras la copia en caché sea más reciente que `maxAgeMillis`, y va al servidor cuando es más antigua.

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.

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 utilizamos 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.

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

Este valor limita el tiempo de espera de este método. Si se alcanza el timeout, 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 diferentes 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` con una lista de IDs de producto, el identificador del paywall, Remote Config y varias 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 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 `ViewConfiguration`, lo que indica que fue creado con Paywall Builder. Esto te orientará sobre cómo mostrar el paywall. Si la `ViewConfiguration` está presente, trátalo como un paywall del Paywall Builder; si no, [trátalo como un paywall personalizado](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 admites varios idiomas, añade una localización a tu paywall. Para consultar los códigos que debes usar, visita [Localizaciones y códigos de idioma](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** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** |

opcional

por defecto: `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](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.

| | **fetchPolicy** | por defecto: `AdaptyPlacementFetchPolicy.Default` |

`fetchPolicy` determina qué capa lee el SDK primero, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo consulta el servidor cuando no hay nada en caché. Es posible que los usuarios no reciban los datos más recientes, pero experimentarán tiempos de carga más rápidos sin importar 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.

Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que `maxAgeMillis`, y consulta el servidor una vez que sea más antigua.

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.

| ## 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 aplicación Android." --- Si has creado un flow o un paywall en el antiguo Paywall Builder, no tienes que preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Tanto el flow como el paywall ya incluyen qué mostrar y cómo mostrarlo. :::warning Esta guía cubre flows y paywalls renderizados por Adapty. El proceso es diferente para los paywalls personalizados y el [modo Observer](observer-vs-full-mode). - Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](present-remote-config-paywalls-android). - Para presentar **paywalls en modo Observer**, consulta [Android - Presentar flows 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á 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.

Valor por defecto: `Unspecified`, lo que significa que Adapty ajustará los insets automáticamente. Esto funciona bien para flows de borde a borde.

Si tu flow no es de borde a borde, 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 | Un `AdaptyUiTagResolver` que devuelve los valores de tu aplicación para las [etiquetas personalizadas](flow-custom-tags) del flow. Para más detalles, consulta [Pass data to flows](android-flow-custom-tags). | | **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 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). ## Usa etiquetas personalizadas \{#use-custom-tags\} :::link Artículo principal: [Pasar datos a flows](android-flow-custom-tags) ::: Crea un `AdaptyUiTagResolver` que devuelva el valor de tu app para cada [etiqueta personalizada](flow-custom-tags) en el flow: ```kotlin showLineNumbers val customTags = mapOf("username" to "DragonSlayer42") val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] } ``` ```java showLineNumbers Map customTags = new HashMap<>(); customTags.put("username", "DragonSlayer42"); AdaptyUiTagResolver tagResolver = customTags::get; ``` En este ejemplo, `username` es el nombre de una etiqueta personalizada en tu flow. Los usuarios ven "DragonSlayer42" en lugar de la etiqueta. Pasa el resolver en el parámetro `tagResolver` de `AdaptyUI.getFlowView`, `AdaptyFlowView.showFlow` o `AdaptyFlowScreen`. ## 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 cubre únicamente los paywalls creados en el antiguo Paywall Builder para **Adapty SDK 3.x**. Requieren SDK v3.0. Presentar un paywall creado para SDK 2.x o anterior, un paywall personalizado o un paywall en [modo Observer](observer-vs-full-mode) funciona de forma diferente. - Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](present-remote-config-paywalls-android). - Para presentar **paywalls en modo Observer**, consulta [Android - Presentar 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 las acciones de los flows - Android" description: "Gestiona las acciones de los botones de flows y paywalls en tu app de Android." --- Si estás creando flows en el Adapty Flow & Paywall Builder, es fundamental configurar los botones correctamente: 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 creando paywalls con el Adapty Paywall Builder, es fundamental configurar los botones correctamente: 1. En el antiguo Paywall Builder, añade un botón y asígnale una acción predefinida o 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 de forma automática.** 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 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 en el [Flow & Paywall 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, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras. A continuación te explicamos 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 tienen lugar 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" } } ```
Este método no se invocará en el modo Observer. Consulta el tema [Android - Presentar flows 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 con éxito, 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 flows 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 - Presentar flows 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 después de 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 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. |
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 de analítica \{#analytics-events\} ```kotlin showLineNumbers title="Kotlin" public override fun onAnalyticEvent( name: String, params: Map, context: Context, ) {} ``` Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. A partir del SDK de Adapty 4.3.0, los números enteros en `params` llegan como `Long` y los decimales como `Double`. En versiones anteriores, todos los números llegan como `Double`. Para leer un número en cualquier versión del SDK, conviértelo a `Number`, por ejemplo `(params["screen_order"] as? Number)?.toInt()`. Consulta [Registrar vistas de pantallas del flow](android-flow-screen-views) para saber qué hacer con ellos. Un flow también reporta los valores que los usuarios escriben y seleccionan. Consulta [Procesar datos de flows](android-flow-input) para leerlos. ### 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 | |:-------|:------------| | **onShowAppRate** | Reservado para solicitudes de valoración de la app desde un flow. Los flows aún no activan solicitudes de valoración, así que no es necesario 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, así que no es necesario 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. ::: 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. Esos 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 puedes ver cómo responder a estos eventos. :::warning Esta guía cubre únicamente **paywalls creados con el builder antiguo**, que requieren el SDK de Adapty 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 - 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 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 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" } } } ```
#### 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-flow-screen-views --- --- title: "Rastrear vistas de pantallas de flow en el SDK de Android" description: "Recibe un evento cada vez que un usuario abre una pantalla de flow en tu app de Android y reenvíalo a tus analíticas de producto." --- Cada vez que un usuario abre una pantalla en un [flow](adapty-flow-builder), el SDK envía un evento `flow_screen_showed` a tu app. Reenvíalo a Amplitude, Mixpanel o la herramienta de analíticas de producto que uses, para analizar las pantallas del flow junto con el resto de los eventos de tu app. :::tip Para ver cuántos usuarios llegan a cada pantalla y en qué punto abandonan, no necesitas código — el [embudo de flow](flow-funnel) ya lo muestra en el Adapty Dashboard. Usa esta guía para construir el mismo embudo en tu propia herramienta de analítica, o para combinar las pantallas del flow con eventos que Adapty no conoce. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty Android SDK v4 o posterior**: El callback de análisis de flows no existe en versiones anteriores. - **Una versión del flow publicada después de que se lanzara esta funcionalidad**: Adapty genera los eventos de pantalla al publicar un flow, por lo que un flow publicado antes de eso no reportará nada. Si no llegan eventos a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Qué recibes \{#what-you-receive\} Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: Las vistas de pantalla llegan al mismo callback de análisis que cualquier otro evento del flow. Consulta [Gestionar eventos de flow y paywall](android-handling-events) para saber dónde registrarlo. ## Reenvía cada vista de pantalla a tus analíticas \{#forward-every-screen-view-to-your-analytics\} Comprueba el nombre del evento antes de leer cualquier otro dato, ya que un flow reporta otros eventos a través del mismo callback. ```kotlin showLineNumbers title="Kotlin" override fun onAnalyticEvent( name: String, params: Map, context: Context, ) { if (name != "flow_screen_showed") return val screenId = params["instanceId"] val screenOrder = params["screen_order"] // Send screenId and screenOrder to your analytics provider here. } ``` ## Ejecutar código cuando el usuario llega al final \{#run-code-when-a-user-reaches-the-end\} `is_last_screen` es `true` en una pantalla desde la que el flow no puede avanzar. Úsalo para desbloquear contenido, marcar el onboarding como completado o enviar tu propio evento de finalización, sin necesidad de añadir una acción de botón en el builder. ```kotlin showLineNumbers title="Kotlin" override fun onAnalyticEvent( name: String, params: Map, context: Context, ) { if (name != "flow_screen_showed") return if (params["is_last_screen"] != true) return // The user reached the end of the flow. } ``` Llegar a la última pantalla no es lo mismo que cerrar el flow. Un usuario puede ver la pantalla final y descartar el flow sin realizar ninguna acción, y un flow con bifurcaciones puede terminar en varias pantallas diferentes. Para saber que el flow en sí desapareció, usa `onFlowClosed` en su lugar. --- # File: android-flow-input --- --- title: "Procesar datos de flows en el SDK de Android" description: "Guarda y usa los datos que tus usuarios introducen en los flows de tu app Android con el SDK de Adapty." --- Cuando un usuario escribe en un campo de texto, responde un cuestionario o activa un interruptor en un [flow](adapty-flow-builder), el SDK pasa el valor a tu app a través de su callback de análisis. Las apps suelen usar esos datos para: - **Registrar usuarios en su propio backend**: Toma el correo electrónico y el nombre que el usuario introdujo en tu onboarding flow, y crea su cuenta cuando el flow se cierre. - **Guardar respuestas y preferencias**: Registra lo que el usuario eligió para que tu app pueda actuar en consecuencia más adelante — por ejemplo, [escríbelo en su perfil de Adapty](android-setting-user-attributes) como atributos personalizados. - **Personalizar flows futuros**: Guarda las respuestas del cuestionario como atributos personalizados y luego dirige un placement posterior para que cada segmento reciba un flow diferente, o un paywall diferente dentro de él. - **Mostrar valores calculados a partir de las respuestas**: Por ejemplo, calcula el IMC a partir del peso y la altura del usuario, y [pásalo a una etiqueta personalizada en el siguiente flow](android-flow-custom-tags#example-show-a-bmi-from-onboarding-answers). - **Alimentar plataformas de analítica de terceros**: Reenvía las respuestas a Amplitude, Mixpanel o cualquier otra plataforma de analítica de producto que utilices. Las entradas y los grupos seleccionables reportan sus valores automáticamente. Para distinguirlos en tu código, asigna a cada entrada un **Element ID** significativo y a cada grupo seleccionable un **Group ID** en el builder. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: ## Antes de comenzar \{#before-you-start\} Necesitas: - **Adapty SDK v4 o posterior**: Los callbacks de flow no existen en versiones anteriores. - **Un flow creado en el [Flow & Paywall Builder](adapty-flow-builder)**: Solo los flows reportan valores de entrada a través de este callback. - **Una versión de flow publicada recientemente**: Un flow solo reporta valores de entrada si lo publicaste después de que esta función estuviera disponible. Si no llega nada a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Recibir valores de entrada \{#receive-input-values\} Los valores de entrada llegan al mismo callback que el resto de los eventos de análisis de un flow, bajo el nombre de evento `flow_user_input`. Sobreescribe `onAnalyticEvent` en tu listener de eventos del flow: ```kotlin showLineNumbers title="Kotlin" class MyFlowEventListener : AdaptyFlowDefaultEventListener() { override fun onAnalyticEvent( name: String, params: Map, context: Context, ) { handleFlowInput(name, params) } } ``` El callback `onAnalyticEvent` entrega todos los eventos de análisis de un flow, incluidas las [vistas de pantalla](android-flow-screen-views). Para filtrar los eventos de entrada del usuario, compara `name` con `flow_user_input`. El parámetro que almacena el valor depende de `element_type`: | `element_type` | Parámetro que almacena el valor | Tipo en Kotlin | |:---------------|:--------------------------------|:----------| | `text_input`, `email_input`, `number_input`, `phone_input` | `value` | `String` | | `date_picker`, `time_picker`, `date_time_picker` | `value` | `Long` en SDK 4.3.0 y versiones posteriores, `Double` en versiones anteriores. Tiempo Unix en milisegundos. Léelo a través de `Number`. | | `single_choice`, `multi_choice` | `item_ids`, `item_titles` | `List` | | `toggle` | `value` | `Boolean` | La sección [Qué recibes](#what-you-receive) enumera todos los parámetros y lo que reporta cada elemento del builder. Para confirmar que el callback se ejecuta, interactúa con el input en una versión de prueba de tu propia app. Si tu callback handler no recibe ningún evento, revisa los [prerequisitos](#before-you-start). Asegúrate de que el flow fue publicado después de que esta función estuviera disponible. ## Cuando tu app recibe el input \{#when-your-app-receives-the-input\} :::important El valor predeterminado de entrada o selección nunca llega a tu aplicación a través de este callback. Si un usuario acepta la opción marcada como **Set as default** y continúa, no se dispara ningún evento. No interpretes la ausencia del evento como "sin respuesta": el usuario simplemente dejó el valor predeterminado tal como estaba. ::: **Los siguientes elementos disparan este evento:** - Campos de texto, email, número y teléfono - Selectores de fecha, hora y fecha-hora - Grupos seleccionables de opción única y múltiple, y toggles **Los siguientes no lo hacen:** - Campos de contraseña, selecciones de productos y cambios de pestaña - Inputs dentro de un elemento Header, que se comparte entre pantallas - Grupos seleccionables con IDs de opción duplicados o faltantes, o con un ID de grupo reutilizado en otra pantalla. Ese tipo de grupo no envía nada en absoluto, en lugar de enviar parte de la respuesta. **El evento se dispara cuando:** - Un campo pierde el foco. Un campo vaciado reporta una cadena vacía; un campo que el usuario nunca editó no reporta nada. Un marcador de posición no es un valor. Si el usuario vuelve al campo, lo edita y lo abandona de nuevo, se genera un segundo evento. - El usuario cierra un selector tras elegir un nuevo valor. Cerrarlo sin cambios no reporta nada. - El usuario pulsa una opción o un toggle. Un evento de opción múltiple lista todas las opciones seleccionadas, por lo que deseleccionar la última envía dos arrays vacíos. **El evento no se dispara cuando:** - El usuario escribe. No hay flujo de pulsaciones de teclas, solo el valor que tiene el campo cuando pierde el foco. - El usuario envía o cierra el flow. Un valor que aún se está editando en ese momento puede perderse; la sección de [limitaciones de entrega](#delivery-and-limitations) explica cómo diseñar la última pantalla teniendo esto en cuenta. - Se establece un valor sin interacción del usuario. Una opción marcada como **Set as default** se preselecciona al abrirse la pantalla, y una acción **Set Variable** puede seleccionar una opción o rellenar un input a partir de otra interacción. Ninguna de las dos envía un evento; un input prerrellenado solo se reporta una vez que el usuario lo edita. ## Qué recibes \{#what-you-receive\} El callback entrega dos eventos. Filtra por `name` para mostrar únicamente los eventos `flow_user_input`. El payload JSON de respuesta tiene este aspecto: ```json { "name": "flow_user_input", "instanceId": "scr_registration", "isBackendEvent": false, "isCustomerEvent": true, "element_id": "email", "element_type": "email_input", "value": "jane@example.com" } ``` | Parámetro | Descripción | |:------------------|:------------| | `name` | `flow_user_input` para eventos de entrada, `flow_screen_showed` para vistas de pantalla. | | `instanceId` | El ID de la pantalla del input. Los IDs de elemento son únicos dentro de una pantalla, no en todo el flow. Si tu flow tiene inputs en más de una pantalla, combina `instanceId` con `element_id` al filtrar eventos. | | `element_id` | El **Element ID** del [input](builder-inputs-and-forms), o el **Group ID** del [grupo seleccionable](flow-selectable-elements). | | `element_type` | El tipo de elemento que envió el evento. Determina cuál de los parámetros siguientes contiene el valor del input. | | `value` | **Solo campos de texto, selectores y toggles.** El valor del input: una cadena para campos de texto, un entero para selectores, un booleano para toggles. | | `item_ids` | **Solo grupos seleccionables de elección única y múltiple.** Los **Element IDs** de las opciones seleccionadas, en el orden en que aparecen en el builder. Una entrada para un grupo de elección única; cualquier número para un grupo de elección múltiple. | | `item_titles` | **Solo grupos seleccionables de elección única y múltiple.** Los títulos de las opciones listadas en `item_ids`, en el mismo orden. Nunca está vacío: una opción sin título informa su ID en su lugar. | | `isCustomerEvent` | Indicador de utilidad, siempre `true` para este evento. Marca los eventos que el SDK entrega a tu callback. Resulta útil si un mismo handler reenvía todos los eventos del flow a tu sistema de análisis y filtras por este indicador en lugar de por `name`. | | `isBackendEvent` | Indicador de utilidad, siempre `false` para este evento. Marca los eventos que Adapty también registra para su propio análisis. `false` confirma que lo que los usuarios introducen llega a tu app y solo a ella — Adapty no lo recibe ni lo almacena. | Lo que reporta cada elemento: | En el builder | `element_type` | Parámetro que almacena el valor | Qué contiene | |:--------------|:---------------|:--------------------------------|:-------------| | Input de **Texto**, **Número**, **Número de teléfono** | `text_input`, `number_input`, `phone_input` | `value` | La cadena literal que escribió el usuario. Los números llegan como cadenas, no como tipos numéricos. | | Input de **E-mail** | `email_input` | `value` | La cadena literal que escribió el usuario, aunque no haya superado la validación de formato del builder. Valídala en tu lado antes de usarla. | | Input de **Contraseña** | ninguno | ninguno | No envía ningún evento. | | Input de **Fecha** | `date_picker` | `value` | Tiempo Unix en milisegundos, como entero, a medianoche local de la fecha seleccionada. | | Input de **Hora** | `time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Input de **Fecha y hora** | `date_picker` y `time_picker` | `value` | Dos elementos, un selector de fecha y un selector de hora. Cada uno envía su propio evento. | | Input cambiado a **Fecha y hora** en el desplegable **Type** | `date_time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Grupo de **elección única** | `single_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: un array con el Element ID de la opción seleccionada. `item_titles`: un array con el título de esa opción. | | Grupo de **elección múltiple** | `multi_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: los Element IDs de todas las opciones seleccionadas, en el orden en que aparecen en el builder. `item_titles`: sus títulos, en el mismo orden. Ambos arrays están vacíos cuando no hay nada seleccionado. | | Grupo de **Toggle** | `toggle` | `value` | Un booleano. | Para bifurcar según una respuesta, compara `item_ids`, no `item_titles`. El título es derivado: el **Element Title** de la opción si lo definiste, si no su texto en tu idioma predeterminado, y si no su Element ID. Un usuario que vio el flow en otro idioma vio un texto diferente. ## Ejemplos de eventos \{#event-examples\} Estos ejemplos muestran las propiedades disponibles en cada evento, con valores ilustrativos en los comentarios.
Entrada de texto, correo electrónico, número y teléfono (Haz clic para expandir) ```kotlin override fun onAnalyticEvent(name: String, params: Map, context: Context) { name // "flow_user_input" params["name"] // "flow_user_input" params["instanceId"] // "scr_J260KU5q" params["isCustomerEvent"] // true params["isBackendEvent"] // false params["element_id"] // "email" params["element_type"] // "email_input" params["value"] // "jane@example.com" (String) } ```
Selectores de fecha, hora y fecha-hora (Click to expand) ```kotlin override fun onAnalyticEvent(name: String, params: Map, context: Context) { params["element_id"] // "birthday" params["element_type"] // "date_picker" params["value"] // 645408000000 (Long — Unix milliseconds, 1990-06-15, local midnight. Double before SDK 4.3.0) } ```
Opción única (Haz clic para expandir) ```kotlin override fun onAnalyticEvent(name: String, params: Map, context: Context) { params["element_id"] // "experience" params["element_type"] // "single_choice" params["item_ids"] // ["pro"] (List) params["item_titles"] // ["I train professionally"] } ```
Selección múltiple (Haz clic para expandir) ```kotlin override fun onAnalyticEvent(name: String, params: Map, context: Context) { params["element_id"] // "interests" params["element_type"] // "multi_choice" params["item_ids"] // ["sports", "music"] (List) params["item_titles"] // ["Sports", "Music"] } ```
Toggle (Click to expand) ```kotlin override fun onAnalyticEvent(name: String, params: Map, context: Context) { params["element_id"] // "reminders" params["element_type"] // "toggle" params["value"] // true (Boolean) } ```
## Entrega y limitaciones \{#delivery-and-limitations\} :::warning Los flows envían valores sin procesar: direcciones de correo electrónico, números de teléfono y cualquier otra cosa que el usuario escriba. Trata todo lo que entregue el callback de analíticas como datos personales y no los almacenes en ningún lugar donde no guardarías el correo electrónico de un usuario. ::: - **El último valor prevalece**: recibes un evento por campo con el valor con el que el usuario se quedó, no una secuencia tecla a tecla. Si edita un campo, solo llega la última versión. - **Sin garantía de envío**: los valores te llegan a medida que el usuario avanza por el flow, y puede salir en cualquier momento. Espera a que el flow se cierre antes de considerar un conjunto de respuestas como completo. - **Entrega de mejor esfuerzo**: si un usuario cierra el flow o minimiza la app mientras un campo tiene el foco o un selector está abierto, ese valor puede perderse. Para que el valor del último campo sea fiable, termina el flow con una pantalla que no tenga entradas ni selectores, y cierra el flow mediante una acción explícita del usuario en lugar de hacerlo automáticamente al aparecer esa pantalla. Pasar a la pantalla final quita el foco del campo anterior, que es lo que envía su valor. Guarda cada valor de entrada tal como lo recibe el listener y envía el conjunto completo cuando el flow se cierre. Para detectar ese momento, sobrescribe `onFlowClosed` en tu `AdaptyFlowEventListener`. Se ejecuta cuando la vista del flow se descarta, ya sea que el usuario haya completado el flow o lo haya cerrado a medias. ## Casos de uso \{#use-cases\} ### Registrar usuarios en tu backend \{#register-users-on-your-backend\} Recoge los valores conforme lleguen y envíalos una vez que el flow se cierre, para que una sola solicitud incluya un conjunto completo de respuestas. El flow se cierra tanto si el usuario lo completó como si lo abandonó a mitad. Comprueba que los campos que necesitas estén presentes antes de llamar a tu backend. El flow no puede mostrar errores de tu backend. El callback no tiene valor de retorno y el SDK no dispone de ningún método que envíe datos a un flow en ejecución. Si el registro falla, por ejemplo porque el email ya está en uso, muestra el error en tu propia interfaz después de que el flow se cierre. ```kotlin showLineNumbers title="Kotlin" class MyFlowEventListener : AdaptyFlowDefaultEventListener() { private val flowAnswers = mutableMapOf() override fun onAnalyticEvent( name: String, params: Map, context: Context, ) { if (name != "flow_user_input") return val elementId = params["element_id"] as? String ?: return val value = params["value"] as? String ?: return flowAnswers[elementId] = value } override fun onFlowClosed() { if (!flowAnswers.containsKey("email")) return // Send flowAnswers to your backend here to create the account. flowAnswers.clear() } } ``` ### Enrich user profiles with data \{#enrich-user-profiles-with-data\} Para vincular lo que un usuario ha introducido con su perfil y evitar pedirle los mismos datos dos veces, [actualiza el perfil de usuario](android-setting-user-attributes) a medida que lleguen los valores. Por ejemplo, si tu flow tiene un campo de texto con el ID de elemento `name` y un campo de correo electrónico con el ID de elemento `email`: ```kotlin showLineNumbers title="Kotlin" private fun handleFlowInput(name: String, params: Map) { if (name != "flow_user_input") return val elementId = params["element_id"] as? String ?: return val value = params["value"] as? String ?: return val builder = AdaptyProfileParameters.Builder() when (elementId) { "name" -> builder.withFirstName(value) "email" -> builder.withEmail(value) else -> return } Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } } ``` ### Personalizar flows que se muestran más adelante \{#customize-flows-shown-later\} Las respuestas del quiz también pueden decidir qué ve un usuario en un [placement](placements) posterior: un flow diferente, o un paywall distinto dentro de él. Por ejemplo, pregunta a los usuarios sobre su experiencia con el deporte en tu flow de onboarding y, después, muestra a cada grupo su propio flow con distintos productos y textos. 1. Añade un [quiz](onboarding-quizzes) a tu flow. Asigna al [grupo seleccionable](flow-selectable-elements) el Group ID `experience` y a cada opción un Element ID significativo. 2. Gestiona las respuestas y [establece atributos personalizados](android-setting-user-attributes) para el usuario. ```kotlin showLineNumbers title="Kotlin" private fun handleFlowInput(name: String, params: Map) { if (name != "flow_user_input") return if (params["element_id"] != "experience") return val optionId = (params["item_ids"] as? List<*>)?.firstOrNull() as? String ?: return val builder = AdaptyProfileParameters.Builder() // Set the custom attribute 'experience' to the option the user selected // (beginner, amateur, or pro). builder.withCustomAttribute("experience", optionId) Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } } ``` 3. [Crea un segmento](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade una [audiencia](audience) para cada segmento. 5. [Muestra el flow](android-present-paywalls) de ese placement en tu app. --- # File: android-flow-custom-tags --- --- title: "Pasar datos a flows en el SDK de Android" description: "Pasa un valor desde tu app de Android a un flow y muéstralo en un elemento de texto." --- Un [custom tag](flow-custom-tags) es una variable personalizada con un valor proveniente de tu app. Después de seleccionar **Value comes from outside the app** en la configuración de una variable, tu app puede establecer su valor a través del SDK de Adapty. Por ejemplo, un elemento de texto contiene "Coins:" y un custom tag. Si tu app pasa "120" para el tag, los usuarios ven "Coins: 120". ## Antes de empezar \{#before-you-start\} Necesitas el SDK de Adapty v4 o posterior. Las versiones anteriores a v4 no muestran flows. ## Añade etiquetas personalizadas al flow \{#add-custom-tags-to-the-flow\} Para mostrar los valores de tu app en un flow, configura etiquetas personalizadas en el builder: 1. [Crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) para cada valor que tu app envíe. 2. [Inserta cada etiqueta en un elemento de texto](flow-custom-tags#add-a-custom-tag-to-a-text-element). 3. Anota el nombre completo de cada etiqueta, incluido su grupo. El código de tu app pasa cada valor con ese nombre. Por ejemplo, si el grupo `user` contiene una etiqueta `streak`, el nombre completo de la etiqueta es `user.streak`. ## Pasar valores de etiquetas personalizadas \{#pass-custom-tag-values\} Crea un `AdaptyUiTagResolver` y pásalo en el parámetro `tagResolver` de `AdaptyUI.getFlowView`. El SDK llama al resolver con el nombre completo de cada etiqueta personalizada, por ejemplo `user.streak`. El resolver devuelve uno de los siguientes valores: - **Una cadena de texto**: los usuarios la ven en lugar de la etiqueta. Debe ser legible, como "1.250", no "1250.0". Adapty no la formatea ni traduce. - **`null`**: los usuarios ven el valor inicial de la etiqueta como respaldo. - **Una cadena vacía**: los usuarios no ven nada en lugar de la etiqueta. ```kotlin showLineNumbers title="Kotlin" val customTags = mapOf( "username" to "DragonSlayer42", "coins" to "1,250", ) val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] } val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products = null, eventListener = eventListener, tagResolver = tagResolver, ) ``` ```java showLineNumbers title="Java" Map customTags = new HashMap<>(); customTags.put("username", "DragonSlayer42"); customTags.put("coins", "1,250"); AdaptyUiTagResolver tagResolver = customTags::get; AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, null, eventListener, AdaptyFlowInsets.Unspecified, AdaptyCustomAssets.Empty, tagResolver ); ``` Si creas `AdaptyFlowView` manualmente, pasa el resolver a su método `showFlow`. En Jetpack Compose, pásalo a `AdaptyFlowScreen`. Para ver un ejemplo de cada llamada, consulta [Mostrar flows y paywalls](android-present-paywalls). Para probar tus etiquetas personalizadas, abre el flow en tu app después de añadir el código que establece sus valores. El canvas del builder y la [vista previa en dispositivos](paywall-device-compatibility-preview#preview-on-devices) solo muestran los valores iniciales: la app móvil de Adapty no establece los valores de las etiquetas personalizadas. Si tu app ya establece valores de etiquetas personalizadas para paywalls del antiguo Paywall Builder, revisa [en qué se diferencian las etiquetas personalizadas en los flows](migrate-to-flows#custom-tags-from-the-old-paywall-builder). ## Qué ven los usuarios cuando no hay ningún valor \{#what-users-see-without-a-value\} Los usuarios ven el **Valor inicial** de una etiqueta personalizada como valor de respaldo en los siguientes casos: - Tu app no pasa ningún valor para la etiqueta. - El usuario no ha actualizado tu app a una versión que envíe el valor. - El nombre en el código de tu app no coincide con el nombre completo de la etiqueta, incluidas las mayúsculas y minúsculas. - Otra etiqueta personalizada en el mismo elemento de texto no tiene valor. En ese caso, todas las etiquetas personalizadas de ese elemento de texto muestran su valor inicial. Para no mostrar nada en lugar de una etiqueta, pasa una cadena vacía. Adapty trata una cadena vacía como un valor, por lo que el valor de respaldo no aparece. ## Ejemplo: mostrar un IMC a partir de las respuestas del onboarding \{#example-show-a-bmi-from-onboarding-answers\} Un flow puede recoger respuestas de los usuarios, y una etiqueta personalizada puede mostrar un valor de tu app. Combina ambas cosas para mostrar a los usuarios un resultado que tu app calcula a partir de sus respuestas, como una puntuación, un plan recomendado o una métrica de salud: 1. Un flow recoge las respuestas y las envía a tu app. 2. Tu app calcula el resultado. 3. Tu app pasa el resultado al siguiente flow como valor de etiqueta personalizada. En este ejemplo, un flow de onboarding recoge el peso y la altura del usuario. Cuando el usuario cierra el flow de onboarding tras su última pantalla, tu app calcula su índice de masa corporal (IMC). Luego abre un flow de paywall que muestra el IMC. ### Configura los flows \{#set-up-the-flows\} 1. [Crea dos flows](paywall-builder-templates): un flow de onboarding que recoja el peso y la altura del usuario, y un flow de paywall que muestre su IMC. 2. Añade dos campos de tipo [**Number**](builder-inputs-and-forms) al flow de onboarding: - Uno para el peso en kilogramos, con el **Element ID** `weight`. - Uno para la altura en centímetros, con el **Element ID** `height`. En ambos campos, establece el **Format** como **Integer**. Un campo envía su valor como texto, exactamente tal como lo escribe el usuario, y el [código de ejemplo](#pass-the-bmi-to-the-paywall) no interpreta "70.5" y "70,5" de la misma manera. Los números enteros no tienen separador decimal, por lo que el código los lee igual en cualquier idioma. 3. Para asegurarte de que tu app recibe ambos valores, termina el flow de onboarding con una pantalla que no tenga campos. En esa pantalla, añade un botón con la acción [**Close flow**](onboarding-actions#close-flow). Los campos envían sus valores cuando pierden el foco o cuando el usuario pasa a otra pantalla. La acción Close flow no los envía. El código de ejemplo solo abre el paywall si el usuario llegó a la última pantalla. 4. En el flow de paywall, [crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) llamada `bmi`. En el campo **Initial value**, introduce un guion (`—`). Los usuarios verán el guion si tu app no pasa ningún IMC. 5. Añade un elemento de texto con las palabras "Your BMI:" al flow de paywall. Después de esas palabras, [inserta la etiqueta personalizada `bmi`](flow-custom-tags#add-a-custom-tag-to-a-text-element). 6. Añade el flow de onboarding a un [placement](placements), por ejemplo `onboarding`. Añade el flow de paywall a un placement con el ID `bmi_paywall`. El código de [Pass the BMI to the paywall](#pass-the-bmi-to-the-paywall) obtiene el paywall usando este ID. ### Pasa el IMC al paywall Recoge el peso y la altura de los eventos `flow_user_input` del flow de onboarding, como se explica en [Procesar datos de flows](android-flow-input). Para saber cuándo el usuario ha llegado a la última pantalla, espera un evento `flow_screen_showed` con `is_last_screen` a `true`, como se explica en [Rastrear vistas de pantallas del flow](android-flow-screen-views). Cuando el usuario cierre el flow de onboarding tras esa pantalla, calcula el IMC. Luego crea la vista del flow del paywall y pasa el IMC para el tag personalizado `bmi`. Pasa el siguiente listener a la vista del flow de onboarding: ```kotlin showLineNumbers title="Kotlin" class OnboardingEventListener( private val onBmiCalculated: (bmi: String) -> Unit, ) : AdaptyFlowDefaultEventListener() { private val bodyAnswers = mutableMapOf() private var reachedLastScreen = false override fun onAnalyticEvent( name: String, params: Map, context: Context, ) { if (name == "flow_screen_showed" && params["is_last_screen"] == true) { reachedLastScreen = true return } if (name != "flow_user_input") return val elementId = params["element_id"] as? String ?: return val value = params["value"] as? String ?: return bodyAnswers[elementId] = value } override fun onFlowClosed() { // Reset `bodyAnswers` and `reachedLastScreen` on every close, // so that the next run starts without old answers. val answers = bodyAnswers.toMap() val finished = reachedLastScreen bodyAnswers.clear() reachedLastScreen = false // Number inputs send their values as strings. if (!finished) return val weight = answers["weight"]?.toDoubleOrNull() ?: return val height = answers["height"]?.toDoubleOrNull()?.takeIf { it > 0 } ?: return val heightInMeters = height / 100 val bmi = weight / (heightInMeters * heightInMeters) onBmiCalculated(String.format(Locale.getDefault(), "%.1f", bmi)) } } ``` When the user closes the onboarding flow after its last screen, the listener calls `onBmiCalculated` with the BMI. Pass a lambda for `onBmiCalculated` that calls the following function. The function fetches the paywall flow, creates its view, and passes the BMI for the `bmi` custom tag: ```kotlin showLineNumbers title="Kotlin" fun showBmiPaywall(activity: Activity, bmi: String) { Adapty.getFlow("bmi_paywall") { flowResult -> when (flowResult) { is AdaptyResult.Success -> { AdaptyUI.getFlowConfiguration(flowResult.value) { configResult -> when (configResult) { is AdaptyResult.Success -> { val flowView = AdaptyUI.getFlowView( activity, configResult.value, products = null, eventListener = AdaptyFlowDefaultEventListener(), tagResolver = AdaptyUiTagResolver { tag -> if (tag == "bmi") bmi else null }, ) // add flowView to your view hierarchy } is AdaptyResult.Error -> { // handle the error } } } } is AdaptyResult.Error -> { // handle the error } } } } ``` `String.format` con `Locale.getDefault()` utiliza el separador decimal del locale del usuario: "22.7" en inglés, "22,7" en alemán. ### Guardar el IMC en el perfil del usuario \{#save-the-bmi-to-the-users-profile\} Para mostrar a los usuarios distintos flows según su IMC, guárdalo como un [atributo personalizado](android-setting-user-attributes#custom-user-attributes) de su perfil. En el Adapty Dashboard, [crea un atributo personalizado de tipo **Number**](segments#custom-attributes) con la clave `bmi`. Después, muestra cada flow a una [audiencia](audience) definida a partir de ese atributo. Llama al método `updateProfile` dentro de `onFlowClosed`, justo después de la línea que calcula `bmi`. Pasa el valor `Double`, no la cadena formateada, para que un segmento pueda comparar el IMC con un umbral: ```kotlin showLineNumbers title="Kotlin" val params = AdaptyProfileParameters.Builder() .withCustomAttribute("bmi", bmi) .build() Adapty.updateProfile(params) { error -> if (error != null) { // handle the error } } ``` --- # 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 y códigos de idioma de tu app para llegar a una audiencia global (Android)." --- ## Por qué esto es importante \{#why-this-is-important\} Los códigos de idioma entran en juego cuando Adapty elige la localización para un flow o un onboarding, 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 eso Adapty utiliza un único estándar interno en 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 de Brasil), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de idioma \{#locale-code-matching\} En SDK v4, los flows y onboardings hacen coincidir los códigos de idioma de manera diferente: los flows son localizados por el SDK en el dispositivo, los onboardings por el servidor de Adapty. ### Flows y paywalls Un paywall que Adapty renderiza se entrega como un flow en el SDK v4, por lo que la regla siguiente aplica a ambos. La coincidencia es exacta. El SDK compara el código que pasas con los códigos de localización del flow carácter por carácter: no cambia las mayúsculas, no reemplaza guiones bajos (`_`) por guiones (`-`) ni usa como alternativa la etiqueta de idioma. Para un flow con localización `pt-br`, solo `pt-br` coincide: `pt-BR`, `pt_BR` y `pt-PT` no funcionan. Cuando el código no coincide con ninguna localización, el flow se renderiza silenciosamente en su [configuración regional predeterminada](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) — el SDK no devuelve un error ni registra ninguna advertencia. Cuando el código coincide, Adapty combina la localización con la predeterminada: las cadenas de texto y los recursos que no estén definidos en la localización coincidente se obtienen de la localización predeterminada. Omitir el código de idioma no es lo mismo que solicitar la localización predeterminada del flow: el SDK sustituye un `en` fijo. Un flow cuya configuración regional predeterminada es `de` se renderiza igualmente en `en` si tiene una localización `en`, y solo recurre a `de` cuando no la tiene. Esto se aplica al SDK de Android 4.0.1 y versiones posteriores; en la versión 4.0.0, omitir el código renderiza la localización predeterminada del flow. :::warning Pasa el código de idioma exactamente como está configurado en el dashboard: subtags en minúsculas separados por guiones. No uses directamente un identificador de configuración regional del sistema: `Locale.getDefault().toLanguageTag()` devuelve `pt-BR` y `Locale.getDefault().toString()` devuelve `pt_BR`, y ambos recurren a la localización predeterminada. Convierte el valor en tu app antes de pasarlo. ::: ### Onboardings Los onboardings se localizan en el servidor, y las reglas del servidor toleran otros formatos. Cuando pasas un `locale` a [`getOnboarding`](android-get-onboardings): 1. La cadena de locale 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 locale 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 el contenido para el locale predeterminado del onboarding Esta forma, `pt_BR`, `pt-BR`, y `pt-br` se resuelven en la misma localización del onboarding. ## Implementación de localizaciones \{#implementing-localizations\} En SDK v4, no necesitas pasar un código de idioma al obtener un flow — `getFlow` devuelve el flow con todas sus localizaciones. - **Flows creados en el builder**: el SDK no lee la configuración regional del dispositivo, así que resuélvela en tu app y pásala como argumento `locale` de `AdaptyUI.getFlowConfiguration`. El argumento es opcional: omítelo y el flow se renderizará en `en`, o en su [configuración regional predeterminada](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) cuando el flow no tenga localización `en`. - **Paywalls personalizados (Remote Config)**: `getFlow` devuelve todas las localizaciones configuradas en `flow.remoteConfigs`. Cada entrada tiene un código `locale` y el contenido de configuración (`jsonString` o el `dataMap` ya procesado). Selecciona la entrada que corresponda al usuario con tu propia lógica de respaldo: ```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() // read your values from config?.dataMap } is AdaptyResult.Error -> { // handle the error } } } ``` Adapty almacena esos códigos de `locale` en el formato descrito en [Estándar de código de locale en Adapty](#locale-code-standard-at-adapty). El SDK no compara los Remote Configs con un locale, por lo que es tu aplicación la que decide qué entrada aplicar. ## 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 qué estás enviando exactamente a nuestro servidor para obtener la localización correcta y qué ocurre después, para 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ó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, 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 guión (`pt` para `pt-br`) y buscamos la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, devolvemos el contenido del idioma predeterminado del paywall 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 porque es difícil predecir exactamente qué recibirá el servidor de Adapty. Si decides usarlo de todas formas, 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 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. En la versión SDK v4, esto toma un `AdaptyFlowPaywall` — una variación de paywall del flow obtenido. Comprueba que `flow.paywalls` no esté vacío antes de indexarlo, por ejemplo `flow.paywalls[0]`. ::: ## 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 antiguo Paywall Builder en el SDK de Android" description: "Corrige errores al mostrar paywalls creados en el antiguo Paywall Builder en el SDK de Android de Adapty." --- Esta guía te ayuda a resolver problemas frecuentes al usar paywalls diseñados en el Paywall Builder de Adapty en el SDK de Android. Para problemas con flows, consulta [Problemas comunes](flow-common-issues). ## 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 visualizaciones del paywall es demasiado alto \{#the-paywall-view-number-is-too-big\} **Problema**: El contador de visualizaciones del paywall muestra el doble de lo esperado. **Motivo**: Es posible que estés llamando a `logShowFlow` (Android SDK v4+) / `logShowPaywall` en tu código, lo que duplica el contador de visualizaciones de un paywall creado en el antiguo Paywall Builder. Adapty registra esas visualizaciones automáticamente, por lo que no necesitas usar este método. **Solución**: Asegúrate de no estar llamando a `logShowFlow` (Android SDK v4+) / `logShowPaywall` en tu código para un paywall creado en el antiguo Paywall 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-implement-paywalls-manually --- --- title: "Implementar paywalls personalizados en Android SDK" description: "Aprende cómo implementar paywalls personalizados en tu app de Android 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, gestionaremos todos los escenarios del 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). ::: ## Modo observador \{#observer-mode\} Si quieres implementar tu propia lógica de manejo de compras desde cero, pero aun así quieres aprovechar los análisis avanzados de Adapty, puedes usar el modo observador. :::important Consulta las limitaciones del modo observador [aquí](observer-vs-full-mode). ::: --- # 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 quieres la forma más sencilla de activar compras, usa el [Adapty Flow & Paywall Builder](android-quickstart-paywalls). Con el Flow & Paywall 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) – cualquier cosa que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls personalizados**](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 paywalls en tu app (como `main`, `onboarding`, `settings`). Configuras 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. En esencia, son solo tu forma de gestionar los productos que vendes en tu app. Para implementar tu paywall personalizado, tendrás que crear un **custom 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 personalizados en el SDK de Android" description: "Obtén paywalls y productos en el SDK de Android de Adapty para mejorar la monetización de los usuarios." --- Antes de mostrar paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls personalizados. Para obtener orientación sobre cómo obtener flows o paywalls personalizados en el **Flow & Paywall Builder** o el **old 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 (haz clic para expandir) 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-android) en tu aplicación 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** | requerido | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. || **fetchPolicy** | por defecto: `AdaptyPlacementFetchPolicy.Default` |

`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va al servidor primero y devuelve los datos en caché si esa solicitud 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee la caché primero y va al servidor solo cuando no hay nada en caché. 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.

Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee la caché primero mientras la copia en caché sea más reciente que `maxAgeMillis`, y va al servidor cuando es más antigua.

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 flows y 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 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 de 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 de espera especificado en `loadTimeout`, ya que la operación puede estar compuesta por distintas 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 de la variante (`variationName`, opcional, SDK 4.2+), el nombre, sus variaciones de paywall (`paywalls`), 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 versión 4, el parámetro `locale` se ha movido fuera de `getFlow` y se ha trasladado a `getFlowConfiguration` (usado solo al renderizar con AdaptyUI). Para paywalls personalizados, todos los locales disponibles se devuelven juntos en `flow.remoteConfigs` — elige el locale que coincida con el dispositivo del usuario o la configuración de tu app. ::: :::tip Para llenar esa caché antes de que el usuario necesite el flow, llama a [`preloadFlows`](android-optimize-paywall-fetching#preload-placements) (SDK de Android 4.1+) para varios placements a la vez. Pasa `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad` para leer la copia precargada antes de ir a la red. ::: ## 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** | requerido | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. || **fetchPolicy** | predeterminado: `AdaptyPlacementFetchPolicy.Default` |

`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. Es posible que los usuarios no reciban 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 solicitudes de red.

Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché sea más reciente que `maxAgeMillis`, y va al servidor una vez que sea más antigua.

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 paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema se refiere a paywalls personalizados. Para obtener orientación sobre cómo obtener paywalls creados con Paywall Builder, consulta [Obtener paywalls de 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** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **locale** |

opcional

por defecto: `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` representa el 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** | por defecto: `AdaptyPlacementFetchPolicy.Default` |

`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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 solicitudes de red.

Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché sea más reciente que `maxAgeMillis`, y va al servidor una vez que sea más antigua.

Ten en cuenta que la caché se conserva al reiniciar la app 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 periódica 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 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.

| | **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 especificado en `loadTimeout`, ya que la operación puede incluir distintas solicitudes 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` 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** | 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 idioma](android-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.

| | **fetchPolicy** | predeterminado: `AdaptyPlacementFetchPolicy.Default` |

`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada almacenado. 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 solicitudes de red.

Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que `maxAgeMillis`, y va al servidor una vez que sea más antigua.

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.

|
--- # File: present-remote-config-paywalls-android --- --- title: "Mostrar paywalls personalizados en Android SDK" description: "Descubre cómo mostrar paywalls personalizados en el SDK de Android de Adapty para 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** | requerido | Un objeto `AdaptyPaywall`. | --- # File: android-making-purchases --- --- title: "Realizar compras in-app en la aplicación móvil con el SDK de Android" description: "Guía para 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, presentar un paywall admite compras por sí solo únicamente cuando Adapty renderiza la pantalla, es decir, un [flow](adapty-flow-builder) o un paywall del [Paywall Builder antiguo](adapty-paywall-builder). Si renderizas la pantalla en tu propio código, debes usar un método aparte 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 se aplicará automáticamente solo si Adapty renderiza la pantalla. 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 **¿Adapty renderiza tu pantalla?** Para un [flow](adapty-flow-builder) o un paywall de [Paywall Builder](adapty-paywall-builder), las compras se procesan automáticamente; puedes omitir este paso. **¿Buscas instrucciones 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.

| Si el usuario eligió un método de pago diferido, como pago en efectivo en una tienda, `makePurchase` devuelve `AdaptyPurchaseResult.Pending` y el usuario no obtiene acceso todavía. A partir del SDK de Adapty 4.3.0, el SDK valida la compra con Adapty cuando Google Play la aprueba. Si la app está cerrada en ese momento, el SDK valida la compra la próxima vez que el usuario abre la app. Para otorgar acceso en ese momento, [escucha las actualizaciones del perfil](android-listen-subscription-changes#listening-for-subscription-status-updates). :::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: :::warning Pasa `subscriptionUpdateParams` solo cuando el usuario cambia desde una suscripción activa comprada en la misma cuenta de Google. Para una compra nueva, llama a `makePurchase` sin este parámetro — si lo pasas, la compra fallará con el error `CURRENT_SUBSCRIPTION_TO_UPDATE_NOT_FOUND_IN_HISTORY`, cuyo mensaje induce a confusión al apuntar a una suscripción inactiva o a una cuenta de Google diferente. ::: ```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 la app para Android 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 cobre de nuevo. Esta función es especialmente útil para quienes han desinstalado y reinstalado la app, o han cambiado de dispositivo, y quieren acceder a su contenido ya comprado sin volver a pagar. :::note Si construiste la pantalla con un builder, no necesitas restaurar las compras manualmente. Conecta su control de restauración — la acción **Restore purchases** en el Flow & Paywall Builder, o un enlace o botón de restauración en el antiguo — y el SDK completa la restauración. ::: Si construiste la pantalla tú mismo, 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 niveles de acceso, suscripciones y compras únicas.

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

| :::info A partir del SDK de Adapty para Android 3.17.3 y 4.0.2, `restorePurchases()` se completa correctamente incluso cuando no hay nada que restaurar: devuelve el perfil actual, así que comprueba el estado del nivel de acceso en él. En versiones anteriores del SDK, el método falla con el error `NO_PURCHASES_TO_RESTORE` en este caso. ::: ## Qué se puede restaurar \{#what-can-be-restored\} `restorePurchases` recupera suscripciones activas y compras únicas no consumidas de Google Play. A partir del SDK de Adapty para Android 3.17.3 y 4.0.2, que utilizan Google Play Billing Library 8, el Play Store ya no devuelve suscripciones caducadas ni compras únicas consumidas. Para permitir que los usuarios recuperen esas compras en otros dispositivos y reinstalaciones, [identifícalos](android-identifying-users) con `Adapty.identify`. Adapty entonces restaura el historial de compras desde su backend en lugar de desde el store. :::note Google Play solo reporta las compras actualmente vigentes, por lo que `restorePurchases()` no puede encontrar suscripciones que expiraron antes de que integraras Adapty. Para añadirlas a Adapty, [importa tus datos históricos](importing-historical-data-to-adapty). ::: :::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 análisis avanzados e integración fluida con sistemas de atribución y analítica. 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 [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk). 2. [Informar las 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`. | ## Uso de flows y paywalls de Adapty en el Modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los flows, paywalls y 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 personalizados](present-remote-config-paywalls-android) de la forma habitual. Para flows y paywalls creados en el Flow & Paywall Builder, consulta [Presentar flows en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode). 2. [Asocia los flows y paywalls](report-transactions-observer-mode-android) con las transacciones de compra. --- # File: report-transactions-observer-mode-android --- --- title: "Informar transacciones en Observer Mode en el SDK de Android" description: "Informa transacciones de compra en el Observer Mode de Adapty para obtener información sobre usuarios y seguimiento de ingresos en el SDK de Android." --- 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 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 flows y paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el flow o paywall que la originó, garantizando un análisis preciso. ```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`. | ```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`. | 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 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 flows y paywalls de Adapty, vincula tu transacción al flow o paywall que originó la compra mediante el método `setVariationId`. Esto atribuye la compra correctamente para un análisis preciso. ```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`. | ```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`. | **Informar transacciones** Usa `restorePurchases` para informar 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](android-restore-purchase). :::warning **¡No omitas el informe 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 flows y paywalls a transacciones** El SDK de Adapty no puede determinar el origen de las compras, ya que eres tú quien las procesa. Por eso, si tienes intención de usar flows, paywalls y/o pruebas A/B en modo Observer, necesitas asociar la transacción proveniente de tu store con el flow o 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. ```kotlin Adapty.setVariationId(transactionId, variationId) { error -> if (error == null) { // success } } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | required | 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. | | variationId | required | El identificador de tipo String de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto `AdaptyPaywall`. | ```java Adapty.setVariationId(transactionId, variationId, error -> { if (error == null) { // success } }); ``` | Parámetro | Presencia | Descripción | | ------------------------------------------------- | --------- | ------------------------------------------------------------ | | transactionId | required | 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 | required | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto `AdaptyPaywall`. | --- # File: android-present-paywall-builder-paywalls-in-observer-mode --- --- title: "Presentar flows en modo Observer en el SDK de Android" description: "Presenta flows en modo Observer en tu app de Android mientras gestionas las compras con tu propio código." --- Si usas el [modo Observer](observer-vs-full-mode), eres responsable de tu propia lógica de compras. Pero puedes delegar la interfaz del paywall al [Flow & Paywall Builder](adapty-flow-builder). Diseña la pantalla en el builder, y el SDK de Adapty la renderiza de forma nativa. Actualiza su texto, diseño y precios cuando quieras, sin publicar una nueva versión de la app. Cuando el usuario pulsa el botón de compra, el SDK llama a tu código para completar la compra. Esta guía muestra cómo presentar esa pantalla y gestionar la llamada de compra. Si Adapty completa las compras por ti, consulta [Mostrar flows y paywalls](android-present-paywalls). ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty Android SDK v4 o posterior**: Las versiones anteriores no pueden renderizar flows. Para migrar una app existente desde v3, consulta la [guía de migración](migration-to-android-sdk-v4). - **`observerMode` establecido en `true`**: Consulta la [guía de instalación del SDK de Android](sdk-installation-android). - **Integración inicial con la store**: Configura Adapty [con Google Play](initial-android). - **Un flow publicado con productos**: Créalo en el [Flow & Paywall Builder](adapty-flow-builder) y asígnale [productos](create-product). - **Un placement de flow**: [Crea un placement](create-placement) y asígnale tu flow. - **El flow obtenido en tu app**: [Obtén el flow y su configuración](android-get-pb-paywalls) antes de mostrarlo. ## Implementación \{#implementation\} 1. Implementa el `AdaptyUiObserverModeHandler`. El evento `onPurchaseInitiated` te informará de que el usuario ha iniciado una compra. Puedes activar tu flow de compra personalizado en respuesta a este callback: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` Para gestionar las restauraciones en modo Observer, sobreescribe `getRestoreHandler()`. Por defecto devuelve `null`, lo que utiliza el flow integrado de Adapty `Adapty.restorePurchases()`. Para proporcionar tu propia implementación de restauración: ```kotlin showLineNumbers val observerModeHandler = object : AdaptyUiObserverModeHandler { override fun onPurchaseInitiated( product: AdaptyPaywallProduct, onStartPurchase: AdaptyUiObserverModeHandler.PurchaseStartCallback, onFinishPurchase: AdaptyUiObserverModeHandler.PurchaseFinishCallback, ) { // the same body as the purchase example 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() { @Override public void onPurchaseInitiated( AdaptyPaywallProduct product, PurchaseStartCallback onStartPurchase, PurchaseFinishCallback onFinishPurchase ) { // the same body as the purchase example 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 flow funcione correctamente, por ejemplo, para mostrar el indicador de carga: | 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() | 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 crear la vista con éxito, 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` 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á 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. Consulta [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 en tiempo de ejecución. Consulta [Customize assets](android-get-pb-paywalls#customize-assets) para más detalles. | | **tagResolver** | opcional | Un `AdaptyUiTagResolver` que devuelve los valores de tu app para las [etiquetas personalizadas](flow-custom-tags) del flow. Para más detalles, lee [Pass data to flows](android-flow-custom-tags). | | **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 expandir) 1. Configura la integración inicial de Adapty [con Google Play](initial-android) y [con el 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 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 evento `onPurchaseInitiated` te informará de que el usuario ha iniciado una compra. Puedes activar tu flow de compra personalizado en respuesta a este callback: :::note Las versiones del SDK de Adapty para Android anteriores a la 3.15.0 también pasaban el paywall y la vista del paywall a este callback. Elimina esos dos parámetros cuando actualices a la versión 3.15.0 o posterior. ::: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` Para gestionar las restauraciones en el modo Observer, sobreescribe `getRestoreHandler()`. Por defecto devuelve `null`, lo que utiliza el flow integrado de Adapty `Adapty.restorePurchases()`. Para proporcionar tu propia implementación de restauración: ```kotlin showLineNumbers val observerModeHandler = object : AdaptyUiObserverModeHandler { override fun onPurchaseInitiated( product: AdaptyPaywallProduct, onStartPurchase: AdaptyUiObserverModeHandler.PurchaseStartCallback, onFinishPurchase: AdaptyUiObserverModeHandler.PurchaseFinishCallback, ) { // the same body as the purchase example 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() { @Override public void onPurchaseInitiated( AdaptyPaywallProduct product, PurchaseStartCallback onStartPurchase, PurchaseFinishCallback onFinishPurchase ) { // the same body as the purchase example 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 | 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() | 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 hacerlo, 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 ``` Después de crear la vista correctamente, puedes añadirla a la jerarquía de vistas y mostrarla. Para ello, utiliza 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](android-get-pb-paywalls#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 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 mapea `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`. | | **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 expandir) 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 app.
1. Implementa el `AdaptyUiObserverModeHandler`. El callback de `AdaptyUiObserverModeHandler` (`onPurchaseInitiated`) te notifica cuando el usuario inicia una compra. Puedes activar tu flow de compra personalizado en respuesta a este callback de la siguiente manera: ```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 indicador de carga, 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, ha fallado o 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 ``` Después de que la vista se haya creado con éxito, 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 tiempo de visualización de los productos en pantalla. Si se pasa `null`, AdaptyUI obtendrá automáticamente los productos necesarios. | | **ViewConfiguration** | requerido | 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** | requerido | 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 barra de navegación solapan el `AdaptyPaywallView`, pasa `AdaptyPaywallInsets.NONE`. Para el modo pantalla completa donde las barras del sistema solapan parte de tu interfaz, obtén los insets como se muestra 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 [Gestión de eventos del paywall](android-handling-events) para más detalles. | | **PersonalizedOfferResolver** | opcional | Para indicar precios personalizados ([leer más](https://developer.android.com/google/play/billing/integrate#personalized-price)), implementa `AdaptyUiPersonalizedOfferResolver` y pasa tu propia lógica que mapea `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 Etiquetas personalizadas en Paywall Builder para más detalles. | | **ObserverModeHandler** | requerido para el modo Observer | El `AdaptyUiObserverModeHandler` que implementaste en el paso anterior. | | **variationId** | requerido | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto `AdaptyPaywall`. | | **transaction** | requerido |

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.

| Para el modo de pantalla completa en el que 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-user --- --- title: "Usuarios y acceso en el SDK de Android" description: "Aprende a trabajar con usuarios y niveles de acceso en tu app de Android con el SDK de Adapty." --- --- # 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 cómo 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 de los usuarios 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: android-store-messages --- --- title: "Controlar los mensajes de Google Play en el SDK de Android" description: "Elige cuándo tu app de Android muestra los mensajes in-app de Google Play, como avisos de pago rechazado, para que no interrumpan un flow u onboarding." --- Google Play a veces necesita mostrar a tus usuarios un mensaje sobre su suscripción — por ejemplo, que el pago de una suscripción fue rechazado y el método de pago necesita actualizarse. Google Play muestra estos mensajes sobre tu app, únicamente cuando la app los solicita. De forma predeterminada, el SDK de Adapty solicita mensajes de Google Play cada vez que se inicia una `Activity` de tu app. El mensaje aparece sobre lo que muestre esa `Activity`, incluyendo un flow, un onboarding o un checkout. Para elegir el momento tú mismo, cambia el SDK al manejo manual de mensajes y llama a `showStoreMessages` cuando un mensaje no interrumpa al usuario. Google Play decide si un mensaje aplica al usuario, por lo que una solicitud no siempre muestra un mensaje. Si el usuario corrige su método de pago desde el mensaje, el SDK sincroniza las compras del usuario con Adapty, y tu [listener de perfil](android-listen-subscription-changes) recibe el nivel de acceso actualizado. :::warning En el modo manual, el SDK nunca solicita mensajes de Google Play por sí solo. Si tu app nunca llama a `showStoreMessages`, los usuarios con un pago rechazado no verán el aviso en tu app. ::: ## Antes de empezar \{#before-you-start\} Necesitas el SDK de Adapty para Android 4.3.0 o posterior. Las versiones anteriores no solicitan mensajes de Google Play. ## Habilitar la gestión manual \{#enable-manual-handling\} Para solicitar mensajes de Google Play solo cuando tu aplicación llame a `showStoreMessages`, establece `withStoreMessagesHandling` en `MANUAL` cuando [actives el SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk): ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withStoreMessagesHandling(AdaptyConfig.StoreMessagesHandling.MANUAL) // AUTO is the default .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withStoreMessagesHandling(AdaptyConfig.StoreMessagesHandling.MANUAL) // AUTO is the default .build(); ``` ## Mostrar mensajes \{#show-messages\} Cuando tu app llegue a un punto donde un mensaje de Google Play no interrumpa al usuario — por ejemplo, después de que el usuario cierre un flow o termine el onboarding — llama a `showStoreMessages` con la `Activity` que muestra el mensaje: ```kotlin showLineNumbers Adapty.showStoreMessages(activity) ``` ```java showLineNumbers Adapty.showStoreMessages(activity); ``` El método no devuelve ningún resultado. El SDK no solicita ningún mensaje en estos casos: - **El SDK no está activado**: Llama a `showStoreMessages` después de `Adapty.activate`. - **La `Activity` no puede mostrar un mensaje**: La `Activity` está finalizando o destruida, o su ventana aún no está adjunta. Llama al método desde una `Activity` que el usuario esté viendo. En el modo `AUTO` predeterminado, también puedes llamar a `showStoreMessages` —por ejemplo, para solicitar un mensaje de nuevo en una `Activity` que ya está en ejecución. --- # 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-onboardings --- --- title: "Onboardings en el SDK de Android" description: "Aprende a trabajar con onboardings en tu app de Android con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: --- # File: android-get-onboardings --- --- title: "Obtener onboardings en el SDK de Android" description: "Aprende cómo recuperar onboardings en Adapty para Android." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 el SDK de Android" description: "Aprende a presentar onboardings en Android para una interacción efectiva con el usuario." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: Antes de empezar, 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 utiliza datos de onboardings en tu app Android con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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: ```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-best-practices --- --- title: "Mejores prácticas en el SDK de Android" description: "Patrones de referencia para integrar el SDK de Adapty en Android: orden de llamadas, manejo de errores y otras reglas para entornos de producción." --- --- # 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 arrancar la app, lo primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `Adapty.activate(context, AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | Al arrancar la app, después del paso 1, si tienes el ID de usuario | Recomendado. No se crea ningún perfil anónimo. | | 2b | `Adapty.activate(context, AdaptyConfig.Builder("KEY").build())` sin `customerUserId` | Al arrancar la app, después del paso 1, si no tienes el ID de usuario (o nunca lo recoges) | Adapty crea un perfil anónimo. | | 3 | `Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.appsflyerId(uid)) { error -> ... }` | 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. Pasa una lista de identificadores para configurar varios MMPs en una sola llamada. | | 4 | `Adapty.identify("YOUR_USER_ID") { error -> ... }` | Después del paso 3 (o del paso 2 si no hay MMP), antes del paso 5 — solo en el flujo 2b con autenticación | Usa el callback de finalización. Las llamadas concurrentes durante `identify` pueden caer en el perfil anónimo. | | 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateExternalAttribution`, `updateProfile` | Después del paso 4 si llamas a `identify`; si no, 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, que falte `appsflyer_id` en los perfiles, y que se devuelvan paywalls para la audiencia equivocada. ::: ## Obtener un placement justo después de `updateProfile` \{#fetching-a-placement-right-after-updateprofile\} Si llamas a `updateProfile` para establecer atributos personalizados y luego obtienes un placement (`getPaywall`) en la misma sesión, espera a que `updateProfile` termine antes de realizar la obtención. Si se lanzan juntos, el SDK puede calcular el hash del segmento del placement a partir de atributos que aún no han terminado de propagarse, lo que añade un reintento antes de que la obtención se resuelva. Consulta [Gestionar errores en el SDK de Android](android-sdk-error-handling) si el propio reintento falla. ## 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: "Optimiza la carga de flows y paywalls en el SDK de Android" description: "Carga flows y paywalls de Adapty de forma fiable: timing, caché y patrones de respaldo para Android." --- Una carga fiable de un flow o paywall en Android hace tres cosas: renderiza rápido, devuelve la variación orientada a la audiencia y recurre al respaldo de forma elegante cuando la red es lenta. Las reglas que se describen a continuación cubren los patrones de timing, 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, o precalienta la caché con [`preloadFlows`](#preload-placements) (SDK 4.1+). | Lanza tus propias llamadas concurrentes a `getFlow` al iniciar. | Un burst de prefetch manual bloquea el hilo principal y produce una pantalla en negro. `preloadFlows` está diseñado para esto y ejecuta el lote de forma concurrente. | | Llama a `getFlow` después de que la atribución haya tenido tiempo de resolverse — por ejemplo, 1–2 segundos después de `activate` o tras el disparo de `setOnProfileUpdatedListener`. | Llama a `getFlow` en `Application.onCreate()`. | La atribución aún no ha llegado. El flow se resuelve contra la audiencia por defecto y omite silenciosamente los segmentos y la personalización de ASA. | | Configura un `loadTimeout` y un [paywall de respaldo](fallback-paywalls) para cada placement. | Esperes a `getFlow` indefinidamente. | Sin un timeout, los usuarios con mala conectividad ven una pantalla en blanco hasta que la red responde — o cierran la app. | Consulta [Obtener paywalls y productos personalizados](fetch-paywalls-and-products-android) para la referencia de los parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado. ## Precargar placements \{#preload-placements\} :::info `preloadFlows` y `preloadFlowsForDefaultAudience` están disponibles a partir de la versión 4.1 del SDK. ::: `preloadFlows` almacena en caché el JSON del flow con antelación — una solicitud por placement. Luego se usa de forma habitual: `getFlow` para el flow y `getFlowConfiguration` para su configuración de vista. `fetchPolicy` decide qué capa lee primero un `getFlow` posterior, no si puede acceder a la caché en absoluto: - `ReturnCacheDataElseLoad` lee primero la copia precargada y solo va a la red si no hay nada en caché. `ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)` hace lo mismo mientras la copia sea más reciente que `maxAgeMillis`. - El valor predeterminado, `ReloadRevalidatingCacheData`, va primero a la red y usa la copia precargada como respaldo cuando la petición falla o se agota el tiempo de espera. Una precarga resulta útil en ambos casos, pero de forma distinta: una política de caché primero elimina la petición, mientras que el valor predeterminado la mantiene y obtiene una copia en caliente como respaldo. Úsalo cuando sepas qué placements necesitará la sesión pero no quieras mostrarlos todavía — por ejemplo, justo después de que se resuelvan `activate` e `identify`, para el flow detrás de un botón que el usuario aún no ha pulsado. ```kotlin showLineNumbers ... Adapty.preloadFlows(listOf("onboarding", "main_paywall"), loadTimeout = 5.seconds) { error -> if (error != null) { // at least one placement failed to preload } } // Later, when the user reaches the paywall: Adapty.getFlow( "main_paywall", fetchPolicy = AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad, ) { result -> // ... } ``` ```java showLineNumbers ... Adapty.preloadFlows(Arrays.asList("onboarding", "main_paywall"), TimeInterval.seconds(5), error -> { if (error != null) { // at least one placement failed to preload } }); // Later, when the user reaches the paywall: Adapty.getFlow("main_paywall", AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad, result -> { // ... }); ``` **Parámetros:** - `placementIds` (obligatorio): los placements a precargar. Los IDs en blanco o duplicados se ignoran. - `loadTimeout` (opcional): tiempo de espera aplicado a cada placement individualmente, no al lote completo. Por defecto es 5 segundos; los valores inferiores a 1 segundo se elevan a 1 segundo. **Aspectos importantes del comportamiento:** - La llamada de retorno se activa solo después de que se hayan intentado todos los placements, e informa los fallos por placement juntos. Un fallo en un placement no detiene los demás. - Si un placement supera el tiempo de espera, encuentra un error de servidor o falla por un error de red, el SDK recurre a las variaciones de respaldo para ese placement. Los demás fallos se informan tal cual. - La precarga solo calienta la caché. No devuelve contenido: igualmente debes llamar a `getFlow` para mostrarlo. ### Qué cubre una precarga \{#what-a-preload-covers\} Un flow llega a la pantalla en capas. Una precarga cubre la primera, exactamente igual que `getFlow`: | Capa | Obtenida por | Preparada por una precarga | |---|---|---| | JSON del flow — la variación elegida, sus IDs de producto y el Remote Config | `getFlow` | Sí | | Diseño de interfaz — estructura, estilos y textos de la pantalla | `getFlowConfiguration` | No | | Imágenes, incluido el fotograma estático que reemplaza a un elemento de vídeo | `getFlowConfiguration`, en segundo plano | No | | Archivos de vídeo | El reproductor del sistema, al renderizar la pantalla | No almacenado en caché por el SDK | `getFlowConfiguration` espera el layout, por lo que la primera solicitud de un layout determinado implica un viaje de ida y vuelta a la red, incluso después de una precarga. El SDK guarda ese layout en su propio caché de disco, que sobrevive a los reinicios de la app y se lee antes de cualquier llamada a la red, de modo que el coste recae en la primera solicitud y no en cada una. Una vez que el SDK tiene el layout, comienza a cachear las imágenes de forma independiente a la llamada: no bloquea la pantalla, y no hay ningún callback ni error que informe de cuándo termina. ### Identificar qué placement falló \{#find-out-which-placement-failed\} El callback recibe un `AdaptyPreloadPlacementsError` que cubre todo el lote, con el código `REQUEST_FAILED` (2005). Para ver los fallos individuales, lee su propiedad `preloadErrors` — un mapa con clave por ID de placement: ```kotlin showLineNumbers Adapty.preloadFlows(listOf("onboarding", "main_paywall")) { error -> if (error is AdaptyPreloadPlacementsError) { error.preloadErrors.forEach { (placementId, placementError) -> // log or retry the individual placement } } } ``` `preloadErrors` existe solo en `AdaptyPreloadPlacementsError`, así que confirma el tipo primero: cualquier otro `AdaptyError` proviene de algo distinto a un fallo por placement. ### Omitir la segmentación de audiencias \{#skip-audience-segmentation\} Para calentar la caché sin esperar a la segmentación de audiencias, usa la variante de audiencia predeterminada. No recibe `loadTimeout`: ```kotlin showLineNumbers Adapty.preloadFlowsForDefaultAudience(listOf("main_paywall")) { error -> } ``` ## Mostrar el contenido multimedia de la primera pantalla desde el bundle de la app \{#show-first-screen-media-from-the-app-bundle\} Un flow descarga sus imágenes y vídeos desde Adapty. Para mostrar el contenido multimedia de la primera pantalla de forma instantánea, sírvelo desde el bundle de la app. Es una buena forma de reutilizar recursos que ya incluyes en la app, como los visuales de un onboarding nativo existente. 1. En el Flow & Paywall Builder, asigna un [ID de medio personalizado](custom-media#custom-media-id) a la imagen o el vídeo. El archivo que subas allí será el de respaldo. 2. Añade el archivo a la carpeta `res/raw` o `assets` de tu app. 3. Cuando crees la vista del flow con `getFlowView`, pasa el archivo incluido en el bundle para ese ID en `customAssets`: ```kotlin showLineNumbers // "welcome_video" is the custom media ID set in the Flow & Paywall Builder val bundledAssets = AdaptyCustomAssets.of( "welcome_video" to AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.welcome), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.welcome_poster), ), resolution = AdaptyCustomVideoAsset.Resolution(width = 1080, height = 1920), ), ) val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, bundledAssets, ) ``` Los archivos incluidos en el paquete aumentan el tamaño de descarga de tu app, así que incluye solo los archivos multimedia que el usuario ve primero. Los archivos multimedia que no incluyas en el paquete seguirán apareciendo de inmediato: la configuración de la vista lleva una copia de baja resolución de cada imagen, incluido el fotograma estático de los vídeos, y la muestra hasta que se cargue el archivo completo. Para la referencia completa de `customAssets`, consulta [Personalizar recursos](android-get-pb-paywalls#customize-assets). ## Ajusta para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte público, regiones con problemas de enrutamiento): - Establece `fetchPolicy` en `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad` en cada solicitud 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 flow a `getProfile`. Llama a `getFlow` 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-reference --- --- title: "Referencia del SDK de Android" description: "Documentación de referencia para el SDK de Android de Adapty." --- Esta página contiene documentación de referencia para el SDK de Android de Adapty. Elige el tema que necesites: - **[Modelos del SDK](https://android.adapty.io)** - Modelos de datos y estructuras usados por el SDK - **[Gestión de errores](android-sdk-error-handling)** - Gestión de errores y resolución de problemas --- # 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.
Se produce habitualmente cuando una splash screen o un 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 el timing en dispositivos reales es diferente. Espera a que `Adapty.activate` complete antes de programar 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 de 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` complete antes de programar 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. | |

CURRENT_SUBSCRIPTION_TO_UPDATE

\_NOT_FOUND_IN_HISTORY

| La suscripción original que debe reemplazarse no se encuentra entre 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 máximo de espera 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. | | [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 el 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 al servicio de Google Play Store mediante `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 en algún punto entre el dispositivo cliente y los servicios de facturación de Google Play. | | [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 falta o está desactualizada.

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

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

4. Google Play no pudo cobrar 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 con el propio Google Play. | | [ITEM_ALREADY_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_ALREADY_OWNED()) | El producto ya ha sido comprado. | | [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 artículo 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 han añadido al Adapty Dashboard.
  2. Asegúrate de que el **Package name** de tu app coincide con el del Google Play Console.
  3. Verifica que los identificadores de producto de los stores coinciden 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 es **Active** en tu 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 hay una cuenta bancaria vinculada a la app para que pueda ser apta para monetización.
  6. Verifica si los productos están disponibles en tu región.
  7. Asegúrate de que tu app está en alguno 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 usuarios.
| | NO_PURCHASES_TO_RESTORE | Obsoleto en Adapty Android SDK 3.17.3 y 4.0.2: `restorePurchases()` ya no devuelve este error. Cuando no hay nada que restaurar, el método finaliza correctamente con el perfil actual, por lo que debes comprobar el estado del nivel de acceso en él. En versiones anteriores del SDK, este error indica que Google Play no encontró la compra a restaurar. | | AUTHENTICATION_ERROR | Debes [configurar el SDK de Adapty](sdk-installation-android#activate-adapty-module-of-adapty-sdk) correctamente mediante 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 se pudo 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 de API no válida. | | ANALYTICS_DISABLED | No podemos gestionar los eventos de analítica porque has [desactivado esta opción](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.

El SDK también devuelve este error cuando no puede leer un archivo de [respaldo](fallback-paywalls) local, generalmente porque la versión del archivo no coincide con la versión del SDK. Descarga un archivo nuevo desde el dashboard.

| | LOGGING_OUT_UNIDENTIFIED_USER | Se llamó a `Adapty.logout()` para un usuario que nunca fue identificado. Llámalo solo después de [`Adapty.identify`](android-identifying-users). | ## Fetches lentas justo después de `updateProfile` \{#slow-fetches-right-after-updateprofile\} Si estableces atributos personalizados con `updateProfile` y obtienes un placement en la misma sesión, el SDK puede calcular el hash de segmento del placement a partir de atributos que aún no han terminado de propagarse. Reintenta una vez que el hash de segmento se actualiza, por lo que el síntoma es una obtención más lenta en lugar de un error. La obtención falla solo cuando el reintento sigue sin coincidir, lo que significa que la escritura del atributo no se ha completado. Para evitar el reintento, usa `await` en `updateProfile` antes de obtener el placement. Consulta [Orden de llamadas en el SDK de Android](android-sdk-call-order). ## Errores de renderizado del flow \{#flow-rendering-errors\} El SDK de Adapty 4.0 y versiones posteriores notifica estos errores a través de `onError` en tu `AdaptyFlowEventListener`, en lugar de devolverlos desde una llamada a un método. Consulta [Gestionar eventos en el SDK de Android](android-handling-events). | Error | Solución | |-------|----------| | WRONG_ASSET_TYPE | Un elemento de imagen en el flow apunta a un recurso que es un relleno de color o degradado, por lo que el SDK muestra un marcador de posición en su lugar. Asigna un recurso de imagen a ese elemento en el [Flow & Paywall Builder](adapty-flow-builder). | | JS_EXCEPTION | El JavaScript del flow ha lanzado una excepción. Lee el texto de la excepción en el mensaje de error. | | NAVIGATOR_NOT_FOUND | Una acción de apertura de pantalla hace referencia a un navegador que no existe en la configuración de vistas del flow, y el flow no tiene ningún navegador `default` al que recurrir. Revisa el destino de la acción en el [Flow & Paywall Builder](adapty-flow-builder). | | INVALID_ACTION_URL | Una acción de apertura de URL hace referencia a un ID de cadena que no se resuelve en una URL válida. Define una URL válida para esa acción en el [Flow & Paywall Builder](adapty-flow-builder). | ## 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: android-sdk-migration-guides --- --- title: "Guías de migración del SDK de Android" description: "Guías de migración para las versiones del SDK de Android de Adapty." --- Esta página contiene todas las guías de migración para el SDK de Android de Adapty. Elige la versión a la que quieres migrar para ver las instrucciones detalladas: - **[Migrar a v4.1](migration-to-android-sdk-41)** - **[Migrar a v4.0](migration-to-android-sdk-v4)** - **[Migrar a v3.12](migration-to-android-312)** - **[Migrar a v3.10](migration-to-android-310)** - **[Migrar a v3.4](migration-to-android-sdk-34)** - **[Migrar a v3.3](migration-to-android330)** - **[Migrar a v3.0](migration-to-android-sdk-v3)** --- # File: migration-to-android-sdk-41 --- --- title: "Migrar el SDK de Adapty para Android a v4.1" description: "Migra al SDK de Adapty para Android v4.1: activa Adapty Attribution de forma explícita y adopta las APIs de atribución externa renombradas." --- El SDK de Adapty para Android 4.1 cambia la forma en que se activa Adapty Attribution y renombra las APIs de atribución externa. :::warning Las APIs renombradas son un cambio radical. `AdaptyAttributionSource` se elimina por completo — no está deprecada ni existe un typealias para hacer de puente. El código que compila contra 4.0.x no compilará en 4.1 hasta que renombres todos los puntos de llamada indicados a continuación. ::: ## Referencia rápida \{#quick-reference\} | v4.0 | v4.1 | |---|---| | Adapty Attribution habilitada automáticamente | Adapty Attribution deshabilitada por defecto; actívala con `.withAdaptyAttributionEnabled(true)` | | `Adapty.updateAttribution(attribution, source)` | `Adapty.updateExternalAttribution(attribution, provider)` | | `AdaptyAttributionSource` | `AdaptyExternalAttributionProvider`, con un nuevo valor `CUSTOM` | | `AdaptyProfile.appliedAttributionSources` | `AdaptyProfile.appliedExternalAttributionProviders` | | Archivo de respaldo descargado para 4.0 | Nuevo formato de archivo de respaldo; descarga el archivo de nuevo | ## Instalación \{#installation\} Establece la versión de `adapty-bom` en `4.1.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. :::note Si actualizas desde 4.0.0 o 4.0.1, también se aplican dos cambios de la versión 4.0.2: - Adapty trabaja con Google Play Billing Library 8 por defecto, en lugar de 7.0.0. - `restorePurchases` se completa correctamente cuando no hay nada que restaurar y devuelve el perfil actual, en lugar de fallar con `NO_PURCHASES_TO_RESTORE`. Consulta [Restaurar compras](android-restore-purchase). ::: ## La atribución de Adapty está desactivada por defecto \{#adapty-attribution-is-disabled-by-default\} En la versión 4.0 y anteriores, el SDK registraba las instalaciones para [Atribución de Adapty](user-acquisition) de forma automática. A partir de la versión 4.1, esto está desactivado por defecto: el SDK no registra instalaciones, el listener configurado con `setOnInstallationDetailsListener` no se dispara nunca, y `getCurrentInstallationStatus` devuelve `AdaptyInstallationStatus.Determined.NotAvailable`. Si utilizas la atribución de Adapty, actívala al inicializar el SDK: ```diff showLineNumbers val config = AdaptyConfig.Builder("PUBLIC_SDK_KEY") + .withAdaptyAttributionEnabled(true) .build() Adapty.activate(applicationContext, config) ``` Si no usas Adapty Attribution, no es necesario hacer ningún cambio. ## APIs de atribución externa renombradas \{#renamed-external-attribution-apis\} ### updateAttribution → updateExternalAttribution El método que pasa datos de atribución de un proveedor externo (Adjust, AppsFlyer, Branch, Tenjin o uno personalizado) cambia de nombre, y su parámetro `source` pasa a llamarse `provider`: ```diff showLineNumbers - Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST) { error -> /* handle the error */ } + Adapty.updateExternalAttribution(attribution, AdaptyExternalAttributionProvider.ADJUST) { error -> /* handle the error */ } ``` Se conservan ambas sobrecargas: `attribution` puede ser un `Map` o un `String` JSON. El callback se activa una vez que el backend acepta los datos para su procesamiento asíncrono. Un resultado exitoso no significa que los datos ya se hayan aplicado al perfil. ### AdaptyAttributionSource → AdaptyExternalAttributionProvider El tipo ha sido renombrado, y los valores predefinidos conservan sus nombres: `APPLE_ADS`, `ADJUST`, `APPSFLYER`, `BRANCH` y `TENJIN`. Para cualquier otro proveedor, constrúyelo a partir de una cadena: `AdaptyExternalAttributionProvider("your_provider")`. La versión 4.1 también añade un valor predefinido `CUSTOM` para proveedores con los que Adapty no se integra directamente. En la 4.0, ese mismo valor solo estaba disponible como la cadena `"custom"`. ### AdaptyProfile.appliedAttributionSources → appliedExternalAttributionProviders La propiedad del perfil que lista los proveedores de atribución aplicados al perfil cambia de nombre. El tipo de sus elementos cambia en consecuencia: ```diff showLineNumbers - if (profile.appliedAttributionSources.contains(AdaptyAttributionSource.APPLE_ADS)) { + if (profile.appliedExternalAttributionProviders.contains(AdaptyExternalAttributionProvider.APPLE_ADS)) { // Apple Ads attribution has been applied } ``` ## Archivos de respaldo \{#fallback-files\} El formato del [archivo de respaldo](fallback-flows) cambió en la versión 4.1 del SDK. Descarga el archivo de nuevo desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** e inclúyelo en tu app, aunque ya hayas descargado uno para la versión 4.0. :::warning Este paso no genera ningún error de compilación. Si lo omites, se lanzará `WRONG_PARAMETER` en cada llamada de placement que necesite un respaldo — consulta [Gestión de errores en el SDK de Android](android-sdk-error-handling). ::: --- # File: migration-to-android-sdk-v4 --- --- title: "Migrar Adapty Android SDK a v4.0" description: "Migra al Adapty Android SDK v4.0 reemplazando las APIs de paywall con APIs de flow, que renderizan flows además de los paywalls del antiguo builder." --- Adapty Android SDK 4.0 introduce flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan con flows y siguen siendo compatibles con los paywalls del antiguo builder — 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)` | | `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.1` (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 & Paywall Builder](adapty-flow-builder). ## Obtener flows \{#fetching-flows\} ### getPaywall + getViewConfiguration → getFlow + getFlowConfiguration El tipo de retorno del fetch cambia de `AdaptyPaywall` a `AdaptyFlow`, y el cargador de configuración pasa de llamarse `AdaptyUI.getViewConfiguration` a `AdaptyUI.getFlowConfiguration` (devolviendo `AdaptyUI.FlowConfiguration` en lugar de `AdaptyUI.LocalizedViewConfiguration`). El parámetro `locale` sale de la llamada al fetch y pasa a `getFlowConfiguration`: `AdaptyFlow` también expone `paywalls` — las variaciones de paywall del flow, como valores `AdaptyFlowPaywall`. Los métodos de paywall web reciben uno de estos valores. ```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 } } } } ``` `locale` es opcional en `getFlowConfiguration`: si lo omites, la vista se renderiza en `en`, o en la localización predeterminada del flow cuando este no tiene `en`. Consulta [Localizaciones y códigos de idioma](android-localizations-and-locale-codes). ### 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 */ } ``` ### Archivos de respaldo \{#fallback-files\} El formato del archivo de respaldo [cambió en el SDK v4](fallback-flows). Descarga el nuevo archivo desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** y agrégalo a tu app. ## Seguimiento de vistas de flows \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` pasa a llamarse `logShowFlow` y ahora recibe un `AdaptyFlow` en lugar de un `AdaptyPaywall`. El evento se sigue registrando en la misma variación, por lo que las métricas de funnel y pruebas A/B existentes seguirán 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 Adapty — 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, constrúyela a partir de una cadena: `AdaptyAttributionSource("your_source")`. :::note SDK 4.1 renombra este método a `updateExternalAttribution` y el tipo a `AdaptyExternalAttributionProvider`. Los nombres anteriores se eliminan en la versión 4.1 sin haber sido marcados como obsoletos. Si migras directamente a la versión 4.1, consulta [Migrar a v4.1](migration-to-android-sdk-41). ::: ### 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#configure-your-app-code). ```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-10-30T13:00:39.455Z_ _Successfully processed: 54/54 files_ # API - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-10-30T13:00:39.458Z Total files: 32 --- # File: developer-cli --- --- title: "CLI para desarrolladores" description: "Descripción general del CLI para desarrolladores de Adapty." --- La **Adapty Developer CLI** es una herramienta de línea de comandos para gestionar tu cuenta de Adapty sin necesidad de abrir el Dashboard. Ofrece las principales funciones de configuración, accesibles desde tu terminal o entornos automatizados. **Qué puedes hacer con la CLI:** - Crea y configura apps de iOS y Android en tu cuenta de Adapty - Define niveles de acceso — los niveles de suscripción que tu app comprueba en tiempo de ejecución - Configura productos y asígnalos a los IDs de las stores de App Store y Google Play - Vincula ofertas de la store, incluidas las pruebas gratuitas de Google Play, a productos — consulta [Ofertas de productos](developer-cli-products#product-offers) en la referencia de comandos - Crea paywalls y asígnales productos - Crea flows, escribe sus configuraciones de builder y publícalos — consulta [Flows](developer-cli-flows) en la referencia de comandos - Configura placements para obtener paywalls o flows a través del SDK - Lee los análisis de tu app — ingresos, MRR, conversiones, el embudo, retención, LTV y cohortes — a través de [Analytics](developer-cli-analytics) - Crea, inicia, detén y reemplaza pruebas A/B de paywalls y flows a través de [A/B tests](developer-cli-ab-tests) - Gestiona campañas de Apple Ads, palabras clave y reglas de automatización a través de [Ads Manager](developer-cli-ads-manager) - Lee los informes de atribución de Adapty — gasto, instalaciones, ingresos y ROAS por canal y campaña — a través de [Attribution](developer-cli-attribution) - Migra un proyecto de RevenueCat — su catálogo, diseños de paywalls, código de la app e historial de transacciones — consulta [Migración desde RevenueCat](migration-from-revenuecat) - Instala los skills de Adapty en tus agentes de programación con `adapty skills install` — consulta [Skills](developer-cli-reference#skills) en la referencia de comandos --- # File: developer-cli-quickstart --- --- title: "Guía de inicio rápido para el CLI para desarrolladores de Adapty" description: "Configura tu cuenta de Adapty de principio a fin usando el CLI para desarrolladores — desde la creación de la app hasta un placement activo, en unos pocos comandos." --- El CLI de Adapty te permite configurar tu app completamente desde la línea de comandos. Úsalo como alternativa al [inicio rápido del dashboard](integrate-payments) si prefieres herramientas de terminal o [clientes MCP](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli). :::note Conectar Adapty con App Store Connect y Google Play todavía requiere una configuración puntual en el dashboard — esto se explica en el paso 3. ::: Al terminar, tu app, nivel de acceso, producto, flow o paywall y placement estarán visibles en el [Adapty Dashboard](https://app.adapty.io). Si ejecutas la CLI en la aplicación Claude (anteriormente Claude Cowork), configúrala primero en Claude — consulta [Configurar la CLI de Adapty en la aplicación Claude](developer-cli-cowork). ## 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 omitiste 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 productos del store que vincules aquí no se pueden cambiar una vez creados. Para usar IDs de productos 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 ``. Si también vendes este producto en la web a través de Stripe o Paddle, añade esos IDs al mismo comando. Consulta [products create](developer-cli-products#adapty-products-create) en la referencia de comandos. Si el producto tiene prueba gratuita o descuento en Google Play, o una oferta promocional o de recuperación en App Store, vincula la oferta al producto para que Adapty pueda aplicarla. Consulta [product-offers create](developer-cli-products#adapty-product-offers-create) en la referencia de comandos. ## 6. Crea un flow o un paywall \{#6-create-a-flow-or-paywall\} Todo producto debe estar dentro de un flow o un paywall antes de poder aparecer en tu app. Elige una opción: - Un **flow** es una pantalla, o una secuencia de pantallas, que diseñas en el [Flow & Paywall Builder](adapty-flow-builder) sin código y el SDK de Adapty renderiza de forma nativa: una pantalla de paywall, un onboarding, un cuestionario. Elige un flow cuando quieras que Adapty se encargue de renderizar la pantalla de compra. Los flows requieren el SDK de Adapty 4.0 o posterior. - Un **paywall** es un conjunto de productos sin diseño asociado. Elige un paywall cuando renderices la pantalla de compra en el código de tu propia app y solo necesites que Adapty entregue los productos, opcionalmente con un [Remote Config](customize-paywall-with-remote-config). Un placement sirve un único tipo de contenido, así que decide antes del paso 7. ### 6a. Crear un flow \{#6a-create-a-flow\} El CLI crea el registro del flow. Sus pantallas viven en una configuración de builder, un documento JSON que el CLI no genera. Para crear la configuración desde el terminal, usa la [skill flow-generator](flow-generator-skill), que maneja el CLI por ti. Crea el flow: ```bash adapty flows create --app --name "Onboarding" ``` El comando devuelve un ``. Crea sus pantallas con la [skill flow-generator](flow-generator-skill) o en el [Flow & Paywall Builder](adapty-flow-builder). En cualquier caso, el resultado es un borrador guardado que tus usuarios aún no ven. Publica el flow: ```bash adapty flows publish --app ``` El comando pide confirmación. La publicación se ejecuta en segundo plano, así que comprueba el estado con `adapty flows get --app ` hasta que aparezca `published`. Si aparece `publication_failed`, [`flows config get`](developer-cli-flows#adapty-flows-config-get) indica el motivo. Un placement solo acepta un flow publicado. ### 6b. Crea un paywall \{#6b-create-a-paywall\} Un [paywall](paywalls) es el contenedor que agrupa tus productos. :::important Una vez que un paywall está vinculado a un placement, sus productos no se pueden modificar. Para usar productos distintos, 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 flow o paywall. Lo único que escribes directamente en el código de tu app es el ID del placement. Todo lo demás — qué flow o paywall mostrar y a qué usuarios — se gestiona desde el dashboard sin necesidad de 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 preguntes a Adapty qué mostrar en ese punto. Elige algo que describa la ubicación, como `"main"`, `"onboarding"` o `"settings"`. ```bash adapty placements create --app --title "Onboarding" --developer-id "onboarding" --audiences '[{"content_type":"flow","segment_ids":[],"flow_id":"","priority":0}]' ``` ```bash adapty placements create --app --title "Main" --developer-id "main" --audiences '[{"content_type":"paywall","segment_ids":[],"paywall_id":"","priority":0}]' ``` La bandera `--audiences` controla qué flow o paywall se muestra a cada usuario. Los ejemplos anteriores definen una única audiencia por defecto: todos los usuarios en este placement ven el mismo contenido. Un placement sirve un solo tipo de contenido: un placement de paywall admite entradas de tipo `paywall` y un placement de flow admite entradas de tipo `flow`. Para segmentar por segmento, consulta [Forma de audiences](developer-cli-placements#audiences-shape) en la referencia de comandos. ## ¿Qué hacer a continuación? \{#whats-next\} Todas las entidades ya son visibles en el [Adapty Dashboard](https://app.adapty.io). A continuación: - Cambia un flow más adelante — describe el cambio al [skill flow-generator](flow-generator-skill), o abre el flow en el [Flow & Paywall Builder](adapty-flow-builder). - Renderiza un paywall en tu app — [obtén el paywall y sus productos](fetch-paywalls-and-products) con el SDK y construye la pantalla con tu propio código. - [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 flows o paywalls — consulta [`placements update`](developer-cli-placements#adapty-placements-update) y [`segments list`](developer-cli-placements#adapty-segments-list) en la referencia de comandos. --- # File: developer-cli-authentication --- --- title: "Autenticación en el Adapty Developer CLI" description: "Cómo autenticarse con el Adapty Developer CLI." --- El CLI requiere autenticación para llamar a la API de Adapty. Un solo inicio de sesión cubre todo el CLI. Los [comandos del Ads Manager](developer-cli-ads-manager) acceden a un servicio diferente, pero utilizan el mismo token almacenado — no hay un segundo paso de inicio de sesión. ## 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 nombre, un prefijo de token enmascarado y la ruta al archivo de configuración local: ``` User: Jane Doe Token: abcd1234**** Config: ~/.config/adapty/config.json ``` A partir de la CLI 0.9.1, al iniciar sesión se almacena tu nombre pero no tu correo electrónico. La línea `Email:` solo aparece en sesiones guardadas por una versión anterior. En scripts que lean `adapty auth status --json`, usa el campo `name` para identificar al usuario. 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 borrar tus credenciales almacenadas localmente, ejecuta: ```bash adapty auth logout ``` Esto elimina `~/.config/adapty/config.json` y la migración guardada por `adapty migrations create` o `adapty migrations use`. El token sigue siendo válido en el servidor hasta que expire; 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 borrarlo localmente, ejecuta: ```bash adapty auth revoke ``` Si el token tiene una migración guardada, el CLI también la elimina, pero solo después de que el servidor confirme la revocación. Usa esto cuando quieras invalidar completamente un token — por ejemplo, si tus credenciales pudieran haber sido comprometidas. Tras revocar, ejecuta `adapty auth login` para autenticarte de nuevo. ## 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 de comandos del Adapty Developer CLI" description: "Índice de los comandos del Adapty Developer CLI organizados por tema, con las opciones globales, opciones de paginación y el comando para instalar los skills de Adapty." --- El CLI de Adapty agrupa sus comandos por tema, como `products` o `placements`. Cada tema tiene su propia página con sus comandos, argumentos, flags y valores aceptados. Esta página lista los temas, los flags que acepta cualquier comando y el comando para instalar los skills de Adapty. | Tema | Comandos | Qué hacen | |---|---|---| | [Apps y niveles de acceso](developer-cli-apps) | `apps`, `access-levels` | Crear y configurar apps y los niveles de suscripción que comprueba tu app | | [Productos y ofertas](developer-cli-products) | `products`, `product-offers` | Configurar productos, mapearlos a los IDs de producto de App Store y Google Play, y vincular ofertas de la store a ellos | | [Paywalls](developer-cli-paywalls) | `paywalls` | Crear paywalls y asignarles productos | | [Flows](developer-cli-flows) | `flows` | Crear flows, escribir y validar sus configuraciones del builder, publicarlos y leer sus métricas | | [Placements y segmentos](developer-cli-placements) | `placements`, `segments` | Configurar placements, sus audiencias y los segmentos a los que se dirigen las audiencias. Leer las métricas de los placements de flows | | [Analytics](developer-cli-analytics) | `analytics` | Leer gráficos, conversiones, el embudo, retención, LTV y cohortes | | [Pruebas A/B](developer-cli-ab-tests) | `ab-tests` | Crear, iniciar, detener, reemplazar y archivar pruebas A/B de paywalls y flows | | [Atribución](developer-cli-attribution) | `attribution` | Leer gasto, instalaciones, ingresos y ROAS por canal y campaña | | [Ads Manager](developer-cli-ads-manager-reference) | `asa` | Gestionar campañas, palabras clave y reglas de automatización de Apple Ads | | [Migraciones](developer-cli-migrations) | `migrations` | Mover un proyecto de RevenueCat a Adapty | | [Autenticación](developer-cli-authentication) | `auth` | Iniciar sesión, comprobar y revocar tu sesión de CLI | ## Indicadores globales \{#global-flags\} Estos indicadores están disponibles en todos los comandos. | Indicador | Descripción | |---|---| | `--json` | Devuelve la salida en JSON en lugar de texto formateado | | `--help` | Muestra la ayuda del comando | Todos los comandos `list` excepto `adapty product-offers list` también aceptan indicadores de paginación: | Indicador | Valor por defecto | Descripción | |---|---|---| | `--page` | `1` | Número de página | | `--page-size` | `20` | Elementos por página (máx.: 100) | ## Skills \{#skills\} ### adapty skills install Instala los skills de Adapty en los agentes de codificación de esta máquina. ```bash adapty skills install ``` | Flag | Requerido | Descripción | |---|---|---| | `--agent` | No | Agente en el que instalar: `claude-code`, `codex` o `gemini-cli`. Repite la flag para varios agentes. | | `-y`, `--yes` | No | Instala en todos los agentes encontrados sin preguntar. Obligatorio cuando el CLI encuentra varios agentes y no puede preguntar, por ejemplo con `--json`. | La CLI busca Claude Code, Codex y Gemini CLI en el `PATH`. Si no encuentra ninguno, pregunta si ejecutar `npx skills@1.7.0 add adaptyteam/adapty-skills --all --global`, que cubre otros agentes. Reinicia el agente para cargar los skills. Claude Code no actualiza el plugin por su cuenta. Para obtener nuevos skills a medida que se publican, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para activarlo sin usar el menú, añade `"autoUpdate": true` a la entrada `adapty` en `~/.claude/settings.json`: ```json title="~/.claude/settings.json" { "extraKnownMarketplaces": { "adapty": { "source": { "source": "github", "repo": "adaptyteam/adapty-skills" }, "autoUpdate": true } } } ``` Los skills instalados a través del CLI de skills se actualizan con `npx skills update`. Para Codex, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#codex). --- # File: developer-cli-apps --- --- title: "Comandos de apps y niveles de acceso" description: "Comandos de la CLI de Adapty para listar, leer, crear y actualizar apps y niveles de acceso." --- Estos comandos de la CLI de Adapty gestionan tus apps y sus [niveles de acceso](access-level): los niveles de suscripción que tu app comprueba en tiempo de ejecución. ## Apps \{#apps\} Administra las apps de tu cuenta de Adapty. Para la configuración desde el dashboard, consulta [Configuración de la app](general). ### adapty apps list Lista todas las apps de tu cuenta de Adapty. ```bash adapty apps list ``` Acepta [flags de paginación](developer-cli-reference#global-flags). ### 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 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` | Apple bundle ID | | `--google-bundle-id` | Requerido con `--platform android` | Google bundle ID | ### adapty apps update Actualiza una app existente. ```bash adapty apps update --title "New Name" ``` | Argument | Description | |---|---| | `app-id` | App ID (UUID) | | Flag | Description | |---|---| | `--title` | Nuevo título de la app | | `--apple-bundle-id` | Nuevo Apple bundle ID | | `--google-bundle-id` | Nuevo Google bundle ID | 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 Lista todos los [niveles de acceso](access-level) de una aplicación. ```bash adapty access-levels list --app ``` | Flag | Required | Description | |---|---|---| | `--app` | Yes | App ID (UUID) | Acepta [flags de paginación](developer-cli-reference#global-flags). ### 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 Crea un nuevo [nivel de acceso](access-level). ```bash adapty access-levels create --app --sdk-id "pro" --title "Pro" ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--sdk-id` | Sí | Identificador usado 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 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. ## ¿Qué sigue? \{#whats-next\} - [Referencia de comandos](developer-cli-reference) — todos los temas del CLI, los flags globales y los flags de paginación. --- # File: developer-cli-products --- --- title: "Comandos de productos y ofertas de productos" description: "Comandos de la CLI de Adapty para listar, leer, crear y actualizar productos, asignarles IDs de App Store y Google Play, y vincular ofertas de la store." --- Estos comandos de la CLI de Adapty gestionan [productos](product), los asignan a sus IDs de producto en App Store y Google Play, y vinculan ofertas de la store a ellos. ## Productos \{#products\} ### Lista de productos de Adapty \{#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](developer-cli-reference#global-flags). ### 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 Crea un nuevo [producto](product). :::important Los IDs de producto de la store y los IDs de precio no se pueden cambiar después de la creación. Para usar IDs de store diferentes, 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`, `two_months`, `trimonthly`, `semiannual`, `annual`, `lifetime` | | `--ios-product-id` | Al menos un store requerido | ID del producto en App Store Connect | | `--android-product-id` | Al menos un store requerido | ID del producto en Google Play Console | | `--android-base-plan-id` | Requerido con `--android-product-id` salvo que se use `--period lifetime` | ID del plan base en Google Play Console | | `--stripe-product-id` | Al menos un store requerido | ID del producto en Stripe | | `--stripe-price-id` | Requerido con `--stripe-product-id` | ID del precio en Stripe | | `--paddle-product-id` | Al menos un store requerido | ID del producto en Paddle | | `--paddle-price-id` | Requerido con `--paddle-product-id` | ID del precio en Paddle | Cada producto necesita al menos un store: `--ios-product-id`, `--android-product-id`, `--stripe-product-id`, o `--paddle-product-id`. Un mismo producto puede llevar IDs de varios stores a la vez. Para vender un producto en la web a través de Stripe o Paddle, primero conecta el proveedor de pago con Adapty: consulta [Stripe](stripe) y [Paddle](paddle). Para cada uno de estos stores, pasa el ID del producto y el ID del precio juntos. El comando falla si solo pasas uno de los dos. ```bash adapty products create --app --title "Monthly" --access-level-id --period monthly --stripe-product-id prod_xxx --stripe-price-id price_xxx ``` Un producto solo para web es válido: puedes crear un producto con IDs de Stripe o Paddle sin IDs de App Store o Google Play. ### adapty products update Actualiza un [producto](product) existente. Los IDs de producto y precio del store no se pueden cambiar después de la creación y no están disponibles en este comando. Para usar IDs de store diferentes, crea un nuevo producto. ```bash adapty products update --app --title "Monthly" --access-level-id ``` | Argumento | Descripción | |---|---| | `product-id` | ID del producto (UUID) | | Indicador | Obligatorio | 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 | ## Ofertas de producto \{#product-offers\} Vincula las ofertas que creaste en App Store Connect o Google Play Console con un producto de Adapty para que Adapty pueda aplicarlas. Para saber qué hace cada tipo de oferta y cómo crearla en el store, consulta [Añadir ofertas a Adapty](create-offer). Añade estas ofertas: - Ofertas promocionales y de recuperación de App Store. - Todas las ofertas de Google Play, incluidas las pruebas gratuitas. Google Play no tiene un tipo de oferta introductoria, por lo que una prueba es una oferta sobre un plan base y solo se aplica después de vincularla en Adapty. No añadas ofertas introductorias de App Store: se aplican automáticamente a los usuarios que cumplan los requisitos. Una oferta vinculada aún tiene que seleccionarse para el producto en un flow o un paywall antes de que los usuarios la vean. Para seleccionarla en un paywall, usa el Adapty Dashboard: consulta [Mostrar oferta](create-offer#2-display-offer). `adapty products list` y `adapty products get` muestran cuántas ofertas están vinculadas a cada producto en `offers_count`. El recuento incluye solo las ofertas vinculadas en Adapty, no las que existen en los stores. ### adapty product-offers list Lista todas las ofertas vinculadas a un [producto](product). ```bash adapty product-offers list --app --product ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--product` | Sí | ID del producto (UUID) | Devuelve todas las ofertas en una sola página y no acepta [flags de paginación](developer-cli-reference#global-flags). ### adapty product-offers get Obtén los detalles de una oferta específica. ```bash adapty product-offers get --app ``` | Argumento | Descripción | |---|---| | `offer-id` | ID de la oferta (UUID) | | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | ### adapty product-offers create Vincula una oferta de store a un [producto](product). ```bash adapty product-offers create --app --product --title "Spring" --type promotional --ios-offer-id spring_ios --android-offer-id spring-android ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--product` | Sí | ID del producto (UUID) | | `--title` | Sí | Nombre de la oferta, visible solo en el Adapty Dashboard | | `--type` | Sí | Tipo de oferta: `promotional` o `win_back` | | `--ios-offer-id` | Al menos un store obligatorio | ID de oferta de App Store Connect | | `--android-offer-id` | Al menos un store obligatorio | ID de oferta de Google Play Console | Las ofertas de recuperación son exclusivas de App Store, por lo que `--type win_back` solo acepta `--ios-offer-id`. Para cualquier oferta de Google Play, incluida la prueba gratuita, usa `--type promotional`. ```bash adapty product-offers create --app --product --title "Come back" --type win_back --ios-offer-id winback_ios ``` ### adapty product-offers update Actualiza una oferta existente. ```bash adapty product-offers update --app --title "Spring 2" ``` | Argument | Description | |---|---| | `offer-id` | ID de la oferta (UUID) | | Indicador | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | No | Nombre de la oferta | | `--type` | No | Tipo de oferta: `promotional` o `win_back`. Para cambiar una oferta a `win_back`, elimina también su parte de Google Play con `--clear-android-offer-id` | | `--ios-offer-id` | No | Nuevo ID de oferta de App Store | | `--android-offer-id` | No | Nuevo ID de oferta de Google Play | | `--clear-ios-offer-id` | No | Elimina la parte de App Store de la oferta | | `--clear-android-offer-id` | No | Elimina la parte de Google Play de la oferta | Se requiere al menos una opción además de `--app`. Los campos que omitas conservan sus valores actuales. Cada oferta mantiene al menos un ID de oferta de store, por lo que el comando falla si intenta eliminar el último. :::warning El comando lee la oferta y la guarda completa. Si alguien edita la misma oferta en el Adapty Dashboard entre esa lectura y el guardado, su cambio se sobreescribe. ::: Las ofertas introductorias no se pueden actualizar. Una oferta que tenga un store distinto de App Store o Google Play tampoco se puede actualizar desde la CLI: edítala en el Adapty Dashboard. Para eliminar una oferta, utiliza el Adapty Dashboard. La CLI no tiene comando de eliminación. ## ¿Qué sigue? \{#whats-next\} - [Referencia de comandos](developer-cli-reference) — todos los temas de la CLI, los flags globales y los flags de paginación. --- # File: developer-cli-paywalls --- --- title: "Comandos de paywalls" description: "Comandos de la CLI de Adapty para listar, leer, crear y actualizar paywalls, y listar los placements que muestran un paywall." --- Estos comandos de la CLI de Adapty gestionan los [paywalls](paywalls) y los productos que vende cada paywall. ## adapty paywalls list Lista todos los [paywalls](paywalls) de una app. ```bash adapty paywalls list --app ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | Acepta [flags de paginación](developer-cli-reference#global-flags). ## 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 | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | ## adapty paywalls create Crea un nuevo [paywall](paywalls). ```bash adapty paywalls create --app --title "Default Paywall" --product-id ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | Sí | Título del paywall | | `--product-id` | Sí | ID del [producto](product) (UUID). Repite para varios productos: `--product-id --product-id ` | ## 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 pueden modificarse. Para usar productos distintos en un paywall activo, 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 | Required | Description | |---|---|---| | `--app` | Yes | ID de la app (UUID) | | `--title` | Yes | Título del paywall | | `--product-id` | Yes | ID del [producto](product) (UUID). Repite para varios productos: `--product-id --product-id ` | ## 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 intercambiar un paywall para ver qué placements se verían afectados. Los elementos de esta lista no incluyen el campo `is_active`. Para consultar el estado de activación de un placement, usa [`placements list`](developer-cli-placements#adapty-placements-list) o [`placements get`](developer-cli-placements#adapty-placements-get). ## ¿Qué sigue? \{#whats-next\} - [Referencia de comandos](developer-cli-reference) — todos los temas del CLI, los flags globales y los flags de paginación. --- # File: developer-cli-flows --- --- title: "Comandos de flows" description: "Comandos de la CLI de Adapty para crear flows, leer y escribir sus configuraciones del builder, validarlos y previsualizarlos, subir archivos multimedia, publicarlos y leer sus métricas." --- Los [flows](adapty-flow-builder) son los paywalls y onboardings que construyes en el Flow & Paywall Builder. El CLI gestiona el registro del flow, su configuración del builder y la publicación. Una configuración del builder es un documento JSON extenso que normalmente genera el Flow & Paywall Builder; el CLI no la genera por sí solo. Para crear o editar una configuración desde la terminal, usa el [skill flow-generator](flow-generator-skill), que ejecuta estos comandos por ti. Antes de publicar, el [skill flow-audit](flow-audit-skill) comprueba si el flow está listo para producción. Los comandos `flows metrics` leen los datos de la página de [métricas de flows](flow-metrics) y requieren la versión 0.15.0 o posterior del CLI. Los comandos se incluyen aquí como referencia. ## adapty flows list \{#adapty-flows-list\} Lista todos los flows de una aplicación. ```bash adapty flows list --app ``` | Flag | Required | Description | |---|---|---| | `--app` | Yes | App ID (UUID) | Acepta [flags de paginación](developer-cli-reference#global-flags). ## adapty flows get Obtén los detalles de un flow específico. ```bash adapty flows get --app ``` | Argument | Description | |---|---| | `flow-id` | Flow ID (UUID) | | Flag | Required | Description | |---|---|---| | `--app` | Yes | App ID (UUID) | La respuesta contiene `id`, `name`, `status` y `updated_at`. El status es uno de los siguientes: | Estado | Significado | |---|---| | `draft` | El flow nunca ha sido publicado | | `published` | La versión actual está activa | | `dirty` | El flow fue publicado anteriormente y la versión actual tiene cambios sin publicar. Los usuarios siguen viendo la última versión publicada | | `publishing` | La publicación está en curso | | `publication_failed` | La última publicación falló. Corrige la configuración y vuelve a publicar | | `archived` | El flow está archivado | ## adapty flows create Crea un flow. El nuevo flow tiene un nombre y sin configuración. Para añadir una configuración, usa [`flows config update`](#adapty-flows-config-update). ```bash adapty flows create --app --name "Onboarding" ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--name` | Sí | Nombre del flow | ## adapty flows update Renombra un flow. ```bash adapty flows update --app --name "Onboarding v2" ``` | Argument | Description | |---|---| | `flow-id` | ID del flow (UUID) | | Flag | Required | Description | |---|---|---| | `--app` | Yes | ID de la app (UUID) | | `--name` | Yes | Nuevo nombre del flow | ## adapty flows publish Publica la versión actual de un flow para tus usuarios. ```bash adapty flows publish --app ``` | Argumento | Descripción | |---|---| | `flow-id` | ID del flow (UUID) | | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--yes`, `-y` | No | Publica sin pedir confirmación. Obligatorio cuando la salida se redirige o se usa `--json` | Dado que publicar cambia lo que ven tus usuarios, el comando muestra el nombre del flow y pide confirmación. Cualquier respuesta distinta de `y` cancela el comando con código de salida 1. Con `--json` o en una tubería, el comando rechaza con código de salida 2 en lugar de esperar una respuesta, así que usa `--yes` en scripts y sesiones de agentes. La publicación es asíncrona: la respuesta devuelve `status: publishing` y el flow aún no está activo. Consulta [`flows get`](#adapty-flows-get) hasta que el estado pase a `published` o `publication_failed`. Si ocurre `publication_failed`, [`flows config get`](#adapty-flows-config-get) indica el motivo. La publicación falla con HTTP 400 cuando el flow no tiene configuración, cuando la configuración no es publicable o cuando la versión actual ya está activa. Para validar una configuración antes de publicarla, usa [`flows config validate`](#adapty-flows-config-validate). ## adapty flows config get Lee la configuración del builder de un flow. ```bash adapty flows config get --app ``` | Argument | Description | |---|---| | `flow-id` | Flow ID (UUID) | | Flag | Required | Description | |---|---|---| | `--app` | Yes | App ID (UUID) | La respuesta contiene `config`, `remote_configs`, `status` y `updated_at`. `updated_at` es una marca de tiempo en milisegundos del último cambio de configuración. Pásala a [`flows config update`](#adapty-flows-config-update) como `--expected-updated-at` para evitar sobreescribir una edición concurrente. Un flow cuya configuración nunca se ha escrito devuelve 404. Tras un intento de publicación, la respuesta también incluye tres campos que describen cómo fue. Léelos cuando [`flows get`](#adapty-flows-get) indique `publication_failed`: | Campo | Descripción | |---|---| | `publication_status` | El estado de publicación de esta versión del flow: `transforming`, `transformed`, `uploading`, `uploaded`, `published` o `failed` | | `transform_error` | El error de transformación sin procesar: puede ser un payload JSON con los problemas o una cadena de resumen | | `publication_error` | Un mensaje legible para una publicación fallida | ## adapty flows config update Escribe la configuración del builder de un flow. ```bash adapty flows config update --app --config-file config.json ``` | Argument | Description | |---|---| | `flow-id` | ID del flow (UUID) | | Indicador | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--config` | Uno de los dos | Configuración del Builder como cadena JSON | | `--config-file` | Uno de los dos | Ruta a un archivo JSON con la configuración del Builder, o `-` para leer desde stdin | | `--remote-configs` | No | Array JSON de entradas `{locale, data}`, donde `data` es el Remote Config como cadena | | `--expected-updated-at` | No | El valor `updated_at` de un `flows config get` anterior. Si la configuración cambió después de esa lectura, el comando falla en lugar de sobrescribir el cambio. Omítelo para sobrescribir sin condiciones | Al escribir la configuración se guarda un borrador sin publicarlo. En un flow publicado, la escritura crea una nueva versión: el estado del flow pasa a `dirty`, y los usuarios siguen viendo la versión publicada hasta que ejecutes [`flows publish`](#adapty-flows-publish). ## adapty flows config validate Comprueba si una configuración del builder es publicable, sin guardarla. ```bash adapty flows config validate --app --config-file config.json ``` | Argumento | Descripción | |---|---| | `flow-id` | ID del flow (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--config` | Una de las dos | Configuración del builder como cadena JSON | | `--config-file` | Una de las dos | Ruta a un archivo JSON con la configuración del builder, o `-` para leer desde stdin | La respuesta contiene `valid` y una lista de `issues`. Cuando la configuración no es publicable, el comando termina con el código 1, por lo que los scripts pueden usarlo como condición de bloqueo. ## adapty flows config preview Renderiza un archivo de configuración local en el navegador. Este comando no realiza ninguna llamada a la API y no acepta el flag `--app`. ```bash adapty flows config preview ./config.json --screen --device iphone-14 --orientation portrait ``` | Argumento | Descripción | |---|---| | `config-file` | Ruta a un archivo JSON de configuración del builder local | | Flag | Por defecto | Descripción | |---|---|---| | `--screen` | La primera pantalla del flow | ID de pantalla a renderizar | | `--device` | `iphone-14` | Marco de dispositivo en el que renderizar | | `--orientation` | `portrait` | `portrait` o `landscape` | En la terminal, el comando abre la vista previa en tu navegador. Cuando se usa con pipes o con `--json`, imprime la URL en su lugar. La URL contiene toda la configuración y es larga: pásala por un pipe a una herramienta de capturas de pantalla en lugar de imprimirla. Las configuraciones de más de 32 KB aproximadamente se renderizan lentamente. ## adapty flows media upload Sube una imagen para usarla en la configuración de un flow. ```bash adapty flows media upload --app ./hero.png ``` | Argumento | Descripción | |---|---| | `file` | Ruta al archivo de imagen | | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | Formatos aceptados: GIF, HEIC, JPEG, PNG, SVG y WebP. La respuesta contiene el `id`, el `name` y la `url` del CDN de la imagen para referenciarla desde la configuración. ## adapty flows metrics list \{#adapty-flows-metrics-list\} Muestra las métricas de por vida de varios flows, una línea por cada flow. Estos son los datos de la tabla de flows en el dashboard. ```bash adapty flows metrics list --app --ids , ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--ids` | Sí | IDs de los flows (UUIDs). Sepáralos con comas o repite el flag | | `--csv` | No | Imprime CSV: una fila por cada flow, con el ID del flow y las 27 métricas. No se puede usar con `--json` | Cada línea muestra el ID del flow con sus visualizaciones, compras, pruebas y revenue. `--json` y `--csv` incluyen las 27 métricas de la página de [métricas de flow](flow-metrics#metrics-definitions), como `unique_profiles_views`, `net_revenue` y `conversion_rate_purchases`. Una métrica sin datos es `0`, nunca `null`. ## Valores de métricas de flows de Adapty \{#adapty-flows-metrics-values\} Lista las claves y valores de filtro de un flow: lo que acepta [`flows metrics get --filter`](#filter-the-metrics). ```bash adapty flows metrics values --app ``` | Argumento | Descripción | |---|---| | `flow-id` | ID del flow (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | Cada línea muestra una etiqueta y, entre paréntesis, el valor a pasar. Los valores están anidados tal como los filtros del dashboard los anidan: ```text Country (country) Americas (Americas) United States (US) Europe (Europe) Germany (DE) Store (store) App Store (app_store) Google Play (play_store) ``` El valor superior de cada grupo es la clave del filtro, y los valores más internos son los que acepta esa clave. En el ejemplo, `--filter country=US` mantiene Estados Unidos, y `--filter store=play_store` mantiene Google Play. ## adapty flows metrics get \{#adapty-flows-metrics-get\} Muestra las métricas de un flow para un período, como el árbol de la página de [métricas de flow](flow-metrics). ```bash adapty flows metrics get --app --date-from 2026-09-01 --date-to 2026-10-21 --date-type purchase_date --based-on placement ``` | Argumento | Descripción | |---|---| | `flow-id` | ID del flow (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--based-on` | Sí | Por qué se divide el primer nivel del árbol: `placement` o `audience`. Corresponden a las vistas basadas en placement y en audiencia de la página de [métricas de flow](flow-metrics#view-options-for-metrics-data) | | `--date-type` | Sí | A qué fecha se aplica el período: `purchase_date` o `profile_install_date` para [filtrar por fecha de instalación](flow-metrics#filter-metrics-by-install-date) | | `--date-from` | No | Primer día del período, inclusive (`YYYY-MM-DD`). Úsalo junto con `--date-to` | | `--date-to` | No | Último día del período, inclusive (`YYYY-MM-DD`). Úsalo junto con `--date-from` | | `--granularity` | No | Divide el período en puntos: `day`, `week`, `month`, `quarter` o `year` | | `--filter` | No | Muestra solo los datos con estos valores de una clave de filtro. Se puede repetir. Ver [Filtrar las métricas](#filter-the-metrics) | | `--timezone` | No | Zona horaria IANA de los puntos del período, por ejemplo `Europe/Berlin`. Sin este parámetro, Adapty usa la zona horaria de la app | | `--depth` | No | Cuántos niveles del árbol se muestran bajo `Total`. Por defecto: `1` | | `--csv` | No | Imprime CSV: una fila por cada nodo del árbol completo. No se puede usar con `--json` | El CLI termina con código 2 y no envía ninguna solicitud cuando falta `--based-on` o `--date-type`, o cuando solo se proporciona un extremo del período. Adapty valida los valores de `--based-on`, `--date-type` y `--granularity`: un valor incorrecto termina con código 4, y el error lista los valores aceptados. ### Lee el resultado \{#read-the-output\} El comando imprime una línea `Total` y, a continuación, el primer nivel del árbol. Cada línea muestra vistas, compras, trials y revenue. Con `--granularity`, los puntos del período aparecen justo debajo de su nodo: ```text Total views 19 purchases 7 trials 4 revenue 241.93 Onboarding views 10 purchases 7 trials 0 revenue 241.93 2026-09-01 views 0 purchases 0 trials 0 revenue 0.00 2026-10-01 views 10 purchases 0 trials 0 revenue 241.93 Settings views 0 purchases 0 trials 0 revenue 0.00 ``` Los niveles del árbol son los niveles del dashboard: placement, audiencia, prueba A/B, variante de flow, pantalla y producto. Con `--based-on audience`, el nivel de placement se omite. Para mostrar más niveles, usa `--depth`, por ejemplo `--depth 3`. Para el árbol completo con las 27 métricas, utiliza uno de estos flags: - `--json` imprime la respuesta del servidor sin modificaciones. - `--csv` imprime una fila por cada nodo, incluidos los puntos de período. Las columnas son `path` (los títulos desde `Total` hasta el nodo, separados por ` > `), `level`, `title` y luego las métricas. ### Filtrar las métricas \{#filter-the-metrics\} Escribe cada `--filter` como `key=value`, con las claves y valores que lista [`flows metrics values`](#adapty-flows-metrics-values). Para conservar varios valores a la vez, sepáralos con comas o repite el flag con la misma clave. Los filtros con claves distintas se combinan, por lo que los datos deben cumplir todos ellos: ```bash adapty flows metrics get --app --date-from 2026-09-01 --date-to 2026-10-21 \ --date-type purchase_date --based-on placement --filter country=US,DE --filter store=app_store ``` La CLI envía cada valor exactamente como está escrito. Para incluir una coma dentro de un valor, escribe `\,`. Un valor vacío también es un valor: `attribution_channel=` conserva los datos sin canal, que `flows metrics values` muestra como **No Data**. ## ¿Qué viene ahora? \{#whats-next\} - [Referencia de comandos](developer-cli-reference) — todos los temas de la CLI, los flags globales y los flags de paginación. --- # File: developer-cli-placements --- --- title: "Comandos de placements y segmentos" description: "Comandos de la CLI de Adapty que crean y actualizan placements y sus audiencias, leen las métricas de los placements de flow y crean los segmentos a los que se dirigen esas audiencias." --- Estos comandos de la CLI de Adapty gestionan los [placements](placements), las audiencias en ellos y los [segmentos](segments) a los que se dirigen esas audiencias. ## Placements \{#placements\} Los [placements](placements) son los puntos donde tus flows, paywalls y onboardings llegan a los usuarios. El tipo de contenido de un placement se fija en el momento de su creación, por lo que migrar una app de paywalls a flows implica crear nuevos placements en lugar de actualizar los existentes — el [skill migrate-placements](migrate-placements-skill) ejecuta estos comandos en bloque. ### Lista de placements de Adapty \{#adapty-placements-list\} Lista todos los [placements](placements) de una aplicación. ```bash adapty placements list --app ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--type` | No | Lista solo los placements de este tipo: `paywall`, `flow` u `onboarding` | Acepta [flags de paginación](developer-cli-reference#global-flags). Cada elemento contiene `id`, `developer_id`, `title`, `is_active` y `type`. `is_active` es `true` para un placement activo y `false` para uno inactivo. `type` es el tipo de contenido del placement: `paywall`, `flow` u `onboarding`. En `--json`, un placement sin tipo tiene `type: null`. El flag `--type` y el campo `type` requieren la versión 0.15.0 o posterior de la CLI. ### 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 | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | La respuesta contiene `id`, `developer_id`, `title`, `is_active`, `type` y un array `audiences`. `is_active` es `true` para un placement activo y `false` para uno inactivo. Cada entrada de audiencia incluye `content_type`, `segment_ids`, `priority` y el ID de su contenido: `paywall_id` para `paywall`, `flow_id` para `flow`, o `ab_test_id` y `ab_test_title` para una audiencia que ejecuta una [prueba A/B](ab-tests). La audiencia predeterminada tiene `segment_ids: []` y el valor de prioridad más alto (la última en evaluarse). Consulta [Formato de audiencias](#audiences-shape). ### adapty placements create Crea un nuevo [placement](placements). ```bash adapty placements create --app --title "Main" --developer-id "main" --audiences '[{"content_type":"paywall","segment_ids":[],"paywall_id":"","priority":0}]' ``` | Indicador | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | Sí | Título del placement | | `--developer-id` | Sí | Identificador de cadena usado en el código de la app para solicitar este [placement](placements) | | `--audiences` | Uno de los dos | Array JSON de entradas de audiencia, cada una con un `content_type` explícito. Ver [Formato de audiencias](#audiences-shape) | | `--paywall-id` | Uno de los dos | **Obsoleto.** ID del [paywall](paywalls) (UUID). Se envuelve en el lado del cliente en una única audiencia predeterminada | Pasa exactamente uno de `--audiences` o `--paywall-id`. Pasar ambos o ninguno devuelve un error. :::warning `--paywall-id` está en desuso y será eliminado. Al utilizarlo, la CLI muestra una advertencia en stderr y convierte el valor en una audiencia predeterminada. Usa `--audiences` para nueva automatización. ::: ### adapty placements update Reemplaza todos los campos de un [placement](placements) existente. ```bash adapty placements update --app --title "Main" --developer-id "main" --audiences '[{"content_type":"paywall","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 | Obligatorio | 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 de audiencia, cada una con un `content_type` explícito. Ver [Forma de audiencias](#audiences-shape) | | `--paywall-id` | Uno de los dos | **Obsoleto.** ID del [paywall](paywalls) (UUID). Reemplaza todas las audiencias con una única audiencia predeterminada | :::warning Pasar `--paywall-id` reescribe 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. ::: #### Estructura de audiences \{#audiences-shape\} El flag `--audiences` acepta un array JSON. Cada entrada tiene: | Campo | Tipo | Descripción | |---|---|---| | `content_type` | `string` | Obligatorio en cada entrada. `"paywall"`, `"flow"`, `"ab_test"` (prueba A/B de paywall), `"flow_ab_test"` (prueba A/B de flow), `"cross_ab_test"` o `"flow_cross_ab_test"` (prueba A/B de placement cruzado). Usa `paywall`, `ab_test` y `cross_ab_test` en un placement de paywall, y `flow`, `flow_ab_test` y `flow_cross_ab_test` en un placement de flow | | `segment_ids` | `string[]` | IDs de [segmento](segments) a los que se dirige esta audiencia. Longitud 0 o 1. Un array vacío indica la **audiencia predeterminada** — 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. Obligatorio cuando `content_type` es `"paywall"` | | `flow_id` | `string` | ID (UUID) del [flow](adapty-flow-builder) que se muestra a los usuarios de esta audiencia. Obligatorio cuando `content_type` es `"flow"` | | `ab_test_id` | `string` | ID de la [prueba A/B](ab-tests) que se ejecuta para los usuarios de esta audiencia. Obligatorio en los cuatro tipos de contenido de prueba A/B | | `ab_test_title` | `string` | Título de la prueba A/B. Solo lectura: la API lo ignora al escribir | | `priority` | `number` | Basado en 0, único dentro del placement. Las audiencias se evalúan de menor a mayor; la audiencia predeterminada debe tener el valor más alto | Un placement debe tener exactamente una audiencia predeterminada. :::warning `placements update` reemplaza la lista completa de audiencias, incluidas las que tienen una prueba A/B en curso. Para mantener una prueba activa, copia su entrada desde `placements get` a `--audiences` sin modificarla, con el mismo `ab_test_id` y los mismos `segment_ids`. Una escritura que elimine o reemplace esa entrada detiene la prueba sin ganador. Una escritura que mueva la entrada a otros segmentos reinicia la prueba, y un `ab_test_id` en otra audiencia la inicia allí. Una escritura que solo cambie las prioridades mantiene la prueba en curso. La CLI no avisa sobre ninguno de estos casos. ::: Las entradas de prueba A/B entre placements (`cross_ab_test`, `flow_cross_ab_test`) son de solo lectura. La API las ignora al escribir y conserva las almacenadas. El tipo de contenido de un placement se fija al crearlo, por lo que `placements update` no puede cambiar un placement de paywall a un flow ni viceversa. Sirve el flow desde un placement propio; consulta [Crear un nuevo placement para tu flow](migrate-to-flows#create-a-new-placement-for-your-flow). El CLI verifica cada entrada antes de enviar la solicitud. Una entrada con un `content_type` faltante o desconocido, o sin el ID que su `content_type` requiere, termina con código 2 y no se envía nada. Para un `content_type` que no reconoce, el CLI imprime `this CLI does not know this audience kind; update the CLI`. Si la entrada proviene de `placements get`, actualiza el CLI en lugar de eliminar la entrada: puede ser una prueba A/B en ejecución. Un flow solo puede vincularse después de haber sido publicado. El servidor rechaza un flow en estado `draft` o cuya primera publicación aún está en curso; el CLI reconoce ese rechazo y sale con el código 2, imprimiendo el comando `flows publish` para ese flow. Para publicar un flow, usa [`flows publish`](developer-cli-flows#adapty-flows-publish) o el [Flow & Paywall Builder](builder-save-publish) y espera a que su estado sea `published`. Ejemplo con una audiencia específica y una predeterminada: ```bash adapty placements update --app --title "Main" --developer-id "main" \ --audiences '[{"content_type":"paywall","segment_ids":[""],"paywall_id":"","priority":0},{"content_type":"paywall","segment_ids":[],"paywall_id":"","priority":1}]' ``` Ejemplo de un placement que sirve un flow publicado a todos los usuarios: ```bash adapty placements create --app --title "Onboarding" --developer-id "onboarding" \ --audiences '[{"content_type":"flow","segment_ids":[],"flow_id":"","priority":0}]' ``` Para intercambiar un paywall en varios placements sin perder el enrutamiento específico 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 `paywall_id` coincidentes en el cliente. Deja todas las demás entradas sin cambios, incluidas las entradas de pruebas A/B. 4. Escribe el payload modificado de nuevo: ```bash adapty placements update --app --title "" --developer-id "<developer-id>" --audiences '<modified-payload>' ``` ### Lista de métricas de placements de adapty Muestra las métricas de por vida de varios placements de flow, una línea por cada placement. Estos son los datos de la tabla de placements en el dashboard. ```bash adapty placements metrics list --app <app-id> --type flow --ids <placement-id>,<placement-id> ``` | Indicador | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--type` | Sí | Tipo de placement, tal como lo muestra [`placements list`](#adapty-placements-list): `flow` | | `--ids` | Sí | IDs de placements (UUIDs). Sepáralos con comas o repite el indicador | | `--csv` | No | Imprime CSV: una fila por placement, con el ID del placement y las 27 métricas. No se puede usar con `--json` | Los comandos `placements metrics` leen los números de la página de [métricas de placement](placement-metrics) para placements de flow, y requieren la versión 0.15.0 o posterior de la CLI. `--type` debe coincidir con el tipo de placement: Adapty rechaza un placement de otro tipo. Cada línea muestra el ID del placement con sus visualizaciones, compras, pruebas e ingresos. `--json` y `--csv` incluyen las 27 métricas. ### adapty placements metrics values Lista las claves y valores de filtro de un placement de tipo flow: lo que acepta `placements metrics get --filter`. ```bash adapty placements metrics values --app <app-id> <placement-id> --type flow ``` | Argumento | Descripción | |---|---| | `placement-id` | ID del placement (UUID) | | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--type` | Sí | Tipo de placement: `flow` | El resultado tiene la misma forma que [`flows metrics values`](developer-cli-flows#adapty-flows-metrics-values): el valor superior de cada grupo es la clave de filtro, y los valores más internos son lo que toma la clave. ### adapty placements metrics get Muestra las métricas de un placement de flow como el árbol de la página de [métricas de placement](placement-metrics), para toda su vida útil o para un período. ```bash adapty placements metrics get --app <app-id> <placement-id> --type flow --based-on flow ``` | Argumento | Descripción | |---|---| | `placement-id` | ID del placement (UUID) | | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--type` | Sí | Tipo de placement: `flow` | | `--based-on` | Sí | Por qué divide el primer nivel del árbol: `audience`, `flow` o `placement` | | `--group-by` | No | Divide cada audiencia por `country` o `store` | | `--date-from` | No | Primer día del período, inclusive (`YYYY-MM-DD`). Pásalo junto con `--date-to` y `--date-type` | | `--date-to` | No | Último día del período, inclusive (`YYYY-MM-DD`). Pásalo junto con `--date-from` y `--date-type` | | `--date-type` | No | A qué fecha se aplica el período: `purchase_date` o `profile_install_date`. Pásalo junto con `--date-from` y `--date-to` | | `--granularity` | No | Divide el período en puntos: `day`, `week`, `month`, `quarter` o `year` | | `--filter` | No | Muestra solo los datos con estos valores de una clave de filtro. Repetible. Ver [Filtrar las métricas](developer-cli-flows#filter-the-metrics) | | `--timezone` | No | Zona horaria IANA de los puntos del período, por ejemplo `Europe/Berlin`. Sin ella, Adapty usa la zona horaria de la app | | `--depth` | No | Cuántos niveles del árbol se muestran bajo `Total`. Por defecto: `1` | | `--csv` | No | Imprime CSV: una fila por cada nodo de todo el árbol. No se puede usar con `--json` | Sin `--date-from`, `--date-to` y `--date-type`, el comando devuelve las métricas de por vida. Para un período, pasa los tres: un período sin `--date-type` termina con el código 4. La salida, `--depth`, `--json`, `--csv` y `--filter` funcionan igual que en [`flows metrics get`](developer-cli-flows#read-the-output). Por ejemplo, para comparar las audiencias de un placement por store: ```bash adapty placements metrics get --app <app-id> <placement-id> --type flow --based-on audience --group-by store \ --date-from 2026-09-01 --date-to 2026-10-21 --date-type purchase_date --depth 2 ``` ## Segmentos \{#segments\} Los [segmentos](segments) agrupan usuarios según los atributos de su perfil. Usa estos comandos para crear segmentos y buscar IDs de segmento al componer [audiencias de placement](#audiences-shape). Para crear un segmento a partir de campañas, grupos de anuncios o palabras clave de Apple Ads, usa [`adapty asa segments create`](developer-cli-ads-manager-reference#segments) en su lugar: busca automáticamente los IDs de Apple y asigna al segmento el nombre que usa Ads Manager. ### Lista de segmentos de Adapty \{#adapty-segments-list\} Lista todos los [segmentos](segments) de una app. ```bash adapty segments list --app <app-id> ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | Acepta [flags de paginación](developer-cli-reference#global-flags). ### 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 | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | La respuesta contiene `id`, `title`, `description`, `filters`, `profile_count`, `refresh_in_progress`, `created_at` y `updated_at`. `list` devuelve únicamente `id`, `title` y `description`. ### adapty segments create Crea un [segmento](segments) a partir de uno o más filtros. ```bash adapty segments create --app <app-id> --title "US installs" --filter ip_country:IN:US --filter install_date:>=:2026-01-01 ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | Sí | Título del segmento | | `--filter` | Sí | Regla de filtro. Repetible. Ver [Sintaxis de filtros](#filter-syntax) | | `--description` | No | Descripción del segmento | Un usuario pertenece al segmento solo cuando cumple todos los filtros. La respuesta tiene los mismos campos que `segments get`. `profile_count` empieza en `0`, y Adapty lo recalcula después de que se crea el segmento. ### adapty segments update Reemplaza el título, la descripción y los filtros de un [segmento](segments) existente. ```bash adapty segments update --app <app-id> <segment-id> --title "US installs" --filter ip_country:IN:US,CA ``` | Argumento | Descripción | |---|---| | `segment-id` | ID del segmento (UUID) | | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | ID de la app (UUID) | | `--title` | Sí | Título del segmento | | `--filter` | Sí | Regla de filtro. Repetible. Ver [Sintaxis de filtros](#filter-syntax) | | `--description` | No | Descripción del segmento | :::warning `segments update` reemplaza el segmento completo. Pasa todos los filtros que quieras conservar, no solo el que modificas. Para leer los filtros actuales, ejecuta `adapty segments get --app <app-id> <segment-id> --json`. ::: #### Sintaxis de filtros \{#filter-syntax\} Escribe cada `--filter` como `field:OPERATOR:value`. Para coincidir con varios valores, sepáralos con comas: `ip_country:IN:US,CA`. | Operador | Valores | Ejemplo | |---|---|---| | `IN`, `NOT IN` | Uno o más | `ip_country:NOT IN:US,CA` | | `>`, `>=`, `<`, `<=` | Uno | `age:>:18` | | `BETWEEN` | Dos | `total_revenue_usd:BETWEEN:10,100` | | `IS NULL`, `IS NOT NULL` | Ninguno | `age:IS NULL` | Pasa los números como valores simples, por ejemplo `age:>:18`. Adapty los convierte para campos numéricos como `age`, `total_revenue_usd` y atributos personalizados de tipo float. Para un valor que esta forma abreviada no pueda expresar, pasa el filtro como un objeto JSON: ```bash adapty segments create --app <app-id> --title "Adults" --filter '{"field_name":"age","operator":">","value_list":[18]}' ``` Adapty valida los nombres de campo, los operadores y los tipos de valor, y reporta un error por cada filtro inválido. Para ver los nombres de campo y los formatos de valor de los filtros existentes, ejecuta `adapty segments get --app <app-id> <segment-id> --json` en un segmento creado en el dashboard. ## ¿Qué sigue? \{#whats-next\} - [Referencia de comandos](developer-cli-reference) — todos los temas de la CLI, los flags globales y los flags de paginación. --- # File: developer-cli-analytics --- --- title: "Consultar Analytics desde la CLI" description: "Lee Adapty Analytics desde la terminal: gráficos como ingresos y MRR, conversiones, el embudo, retención, LTV y cohortes de instalación, en formato tabla, JSON o CSV." --- El CLI de Adapty lee [Adapty Analytics](analytics) desde el terminal, bajo el tema `adapty analytics`. Devuelve los números que muestra el dashboard de Analytics para una app: [gráficos](charts) como ingresos y MRR, [conversiones](analytics-conversion), el [embudo](analytics-funnels), [retención](analytics-retention), [LTV](ltv) y [cohortes de instalación](analytics-cohorts). Úsalo para dar a un agente de IA acceso en tiempo real a los números de tu app, para extraer un gráfico a un script o una hoja de cálculo, o para responder una pregunta puntual sin abrir el dashboard. Cada comando `analytics` es de solo lectura y actúa sobre una sola app. ## Antes de empezar \{#before-you-start\} El tema `analytics` usa la misma instalación e inicio de sesión que el resto de la CLI. Si aún no lo has configurado, sigue los pasos 1 y 2 de la [guía de inicio rápido](developer-cli-quickstart). El tema requiere la versión 0.10.0 o posterior de la CLI. Para ver tu versión, ejecuta `adapty --version`. A diferencia de la [API de exportación de analytics](export-analytics-api), la CLI no necesita ninguna clave de API secreta. Con tu inicio de sesión en la CLI tienes acceso a todas las apps a las que tu usuario de Adapty puede acceder. Todos los comandos reciben el UUID de la app en `--app`. Para encontrarlo, ejecuta: ```bash adapty apps list ``` ## Crea una consulta \{#build-a-query\} Crea cada consulta en el mismo orden: busca los nombres en el catálogo, consulta los valores de los filtros y luego ejecuta un comando de datos. 1. **Muestra qué puede responder el análisis de la app**. El catálogo lista todos los gráficos con su unidad, cada conversión, las agrupaciones y filtros que acepta cada comando, y los valores aceptados y predeterminados de cada opción: ```bash adapty analytics catalog --app <app-id> ``` Los nombres de gráficos y conversiones, como `revenue`, `mrr` o `trial-paid`, provienen de esta lista. El CLI no mantiene su propia lista, por lo que un gráfico que Adapty añada más adelante funcionará sin necesidad de actualizar el CLI. 2. **Busca los valores de los filtros**. Omite este paso si no vas a filtrar. Por ejemplo, para listar los países por los que puedes filtrar: ```bash adapty analytics values --app <app-id> --dimension country ``` 3. **Ejecuta el comando de datos**: ```bash adapty analytics chart --app <app-id> --chart revenue \ --date-from 2026-07-01 --date-to 2026-09-30 --group-by country ``` La respuesta tiene una fila para el total y luego una fila por cada grupo. ### Ejemplos MRR de este trimestre, comparado con el trimestre anterior: ```bash adapty analytics chart --app <app-id> --chart mrr \ --date-from 2026-07-01 --date-to 2026-09-30 \ --compare-from 2026-04-01 --compare-to 2026-06-30 ``` Conversión mensual de prueba a pago en dos países: ```bash adapty analytics conversion --app <app-id> --conversion trial-paid \ --date-from 2026-04-01 --date-to 2026-09-30 --granularity month --filter country=US,GB ``` Retención por país, contada desde la prueba: ```bash adapty analytics retention --app <app-id> --date-from 2026-07-01 --date-to 2026-09-30 \ --group-by country --use-trial ``` LTV a los 7, 30 y 90 días, sobre ingresos después de comisiones e impuestos, guardado como archivo CSV: ```bash adapty analytics ltv --app <app-id> --date-from 2026-07-01 --date-to 2026-09-30 \ --revenue-basis net --period-type days --renewal-days 7,30,90 --csv > ltv.csv ``` Cohortes de instalación semanales sobre ingresos después de la comisión del store: ```bash adapty analytics cohort --app <app-id> --date-from 2026-07-01 --date-to 2026-09-30 \ --granularity week --revenue-basis proceeds ``` ## Elige el formato de salida \{#choose-the-output\} Cada comando de datos se imprime en uno de tres formatos: | Salida | Flag | Para qué usarla | |---|---|---| | Tabla | Ninguna | Lectura en la terminal. Cada tabla empieza con un encabezado que indica el gráfico, el período, la zona horaria y los filtros. Los importes tienen 2 decimales. Algunos campos se omiten, como los motivos de abandono del funnel y las predicciones de cohorte, y una línea debajo de la tabla lo indica. | | JSON | `--json` | Scripts y agentes de IA. La respuesta de la API sin modificar, con todos los campos. | | CSV | `--csv` | Hojas de cálculo. Una fila por grupo, con todos los campos de la respuesta. Un período comparado se incluye en las columnas `compare.*` de la misma fila. | No puedes usar `--csv` y `--json` a la vez. En JSON, el total es la primera entrada de `data.segments`, y `data.value` es el número para todo el período. `meta.query` muestra la consulta que se ejecutó, con todos los valores predeterminados completados y la zona horaria aplicada. ## Leer los resultados \{#read-the-results\} - **Fechas**: `--date-from` y `--date-to` son días inclusivos en la [zona horaria de informes](general) de la app. Para leer los días en otra zona horaria, pasa un nombre IANA en `--timezone`, como `Europe/Berlin`. `meta.query.timezone` muestra la zona horaria en uso. - **Ingresos**: `--revenue-basis` establece qué valores monetarios se usan: `gross` (el predeterminado), `proceeds` (tras la comisión del store) o `net` (tras la comisión e impuestos). Funciona con `ltv`, `cohort` y los gráficos que muestran dinero. El catálogo marca esos gráficos con `takes revenue_basis`. - **Valores vacíos**: Un valor vacío significa que Adapty no tiene ningún valor, nunca cero. Un período de LTV que una cohorte aún no ha alcanzado está vacío, al igual que una cohorte que no se puede predecir. En la salida `--json` estos valores son `null`. La tabla imprime `—` y el CSV deja la celda vacía. - **La fila de totales**: Encuentra el total mediante `is_total`, no por una `key` vacía. La `key` de un grupo también puede estar vacía, por ejemplo el grupo de usuarios sin país. - **Claves de grupo**: La `key` de un grupo no siempre es un valor que `--filter` acepta. Para filtrar, toma los valores de `analytics values`. - **Períodos comparados**: Solo `chart` y `conversion` aceptan `--compare-from` y `--compare-to`. El período comparado tiene su propio total y grupos, y la tabla lo imprime como una segunda tabla. - **Números de atribución**: No sumes estos números a los números de [Atribución](developer-cli-attribution). Adapty los calcula de forma diferente. El CLI devuelve los números del dashboard, con dos excepciones: - `funnel` aplica el filtro `cancellation_reason`, que el funnel del dashboard ignora. Con ese filtro, ambos números pueden diferir. - `retention` ofrece una lista de agrupaciones diferente a la del dashboard. Consulta la lista en el catálogo. ## Límites \{#limits\} Cada comando de análisis acepta como máximo 2 solicitudes por segundo por aplicación y 360 solicitudes por minuto en total. El CLI nunca reintenta una solicitud. Si envías solicitudes demasiado rápido, el comando termina con el código 4. Con `--json`, el error incluye `retry_after_seconds`: espera ese tiempo y vuelve a ejecutar el comando. ## Errores \{#errors\} Cada error sale con un código que indica qué debes corregir: | Código de salida | Causa | Qué hacer | |---|---|---| | 2 | La CLI rechazó la entrada antes de enviarla: un UUID de app no válido, un rango de fechas mal formado o invertido, un período de comparación con un extremo faltante, `--renewal-days` sin `--period-type days`, un `--filter` sin `=`, o `--csv` con `--json`. | Corrige el flag que indica el mensaje. | | 3 | No has iniciado sesión, o tu usuario de Adapty no tiene acceso a la app. | Inicia sesión con `adapty auth login`. Si ya has iniciado sesión, encuentra el ID de la app en `adapty apps list`, que solo muestra las apps a las que tienes acceso de lectura. | | 4 | Adapty rechazó la solicitud: un gráfico, conversión, agrupación, filtro o zona horaria desconocidos; `--revenue-basis` en un gráfico que no muestra dinero; o demasiadas solicitudes. | Corrige el nombre desde `adapty analytics catalog`. Con `--json`, el error incluye el código de error de la API. Para el caso de demasiadas solicitudes, consulta [Límites](#limits). | ## Referencia de comandos \{#command-reference\} Cada comando de datos acepta estos flags: | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | UUID de la app, obtenido con `adapty apps list`. | | `--date-from` | Sí | Primer día del período, inclusive, en formato `YYYY-MM-DD`. | | `--date-to` | Sí | Último día del período, inclusive. No puede ser anterior a `--date-from`. | | `--filter` | No | `dimension=value[,value]`. Filtra los datos donde la dimensión tenga uno de los valores indicados. Repite el flag para filtrar por varias dimensiones. Usa `\,` para incluir una coma dentro de un valor. Un valor vacío es un valor válido: `country=` mantiene los datos sin país. | | `--timezone` | No | Zona horaria IANA de los días, por ejemplo `Europe/Berlin`. Si se omite, se usa la zona horaria de la app. | | `--csv` | No | Muestra una tabla CSV en lugar de la tabla legible. No puede usarse con `--json`. | Cada comando añade sus propios flags a estos. ### `analytics catalog` Lista lo que los análisis de la aplicación pueden responder: cada comando con sus opciones, agrupaciones y filtros; cada gráfico con su unidad; cada conversión; y cada dimensión de filtro. | Flag | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | UUID de la aplicación, obtenido con `adapty apps list`. | ### `analytics values` Lista los valores que toma una dimensión de filtro para la app. Cada `value` es exactamente lo que acepta `--filter <dimension>=<value>`. | Flag | Required | Description | |---|---|---| | `--app` | Yes | App UUID, from `adapty apps list`. | | `--dimension` | Yes | A filter dimension from `adapty analytics catalog`. | ### `analytics chart` Lee un [gráfico](charts): el número del período completo, sus puntos y sus grupos. | Flag | Obligatorio | Descripción | |---|---|---| | `--chart` | Sí | Nombre de un gráfico de `adapty analytics catalog`, como `revenue` o `mrr`. | | `--group-by` | No | Una agrupación de este gráfico, del catálogo. | | `--granularity` | No | Duración de un punto: `day`, `week`, `month` (por defecto), `quarter` o `year`. | | `--revenue-basis` | No | `gross` (por defecto), `proceeds` o `net`. Solo para gráficos que muestran dinero. | | `--compare-from` | No | Primer día de un período con el que comparar. Requiere `--compare-to`. | | `--compare-to` | No | Último día de un período con el que comparar. Requiere `--compare-from`. | ### `analytics conversion` Lee una [conversión](analytics-conversion): la tasa en porcentaje, con los dos recuentos que la dividen, para cada punto y grupo. | Indicador | Requerido | Descripción | |---|---|---| | `--conversion` | Sí | Nombre de una conversión de `adapty analytics catalog`, como `trial-paid` o `install-paid`. | | `--group-by` | No | Una agrupación del catálogo. | | `--granularity` | No | Duración de un punto: `day` (por defecto), `week`, `month`, `quarter` o `year`. | | `--compare-from` | No | Primer día de un período con el que comparar. Requiere `--compare-to`. | | `--compare-to` | No | Último día de un período con el que comparar. Requiere `--compare-from`. | When the period has no conversions, `meta.recommended_period` in the `--json` output can name a period that has some. ### `analytics funnel` Lee el [embudo](analytics-funnels): el recuento en cada paso desde la instalación hasta las renovaciones. | Indicador | Obligatorio | Descripción | |---|---|---| | `--group-by` | No | Una agrupación del catálogo. | ### `analytics retention` Lee la [retención](analytics-retention): los suscriptores que permanecen en cada período. | Flag | Required | Description | |---|---|---| | `--group-by` | No | Una agrupación del catálogo. | | `--use-trial` | No | Contar desde la prueba, no desde el primer período de pago. | ### `analytics ltv` Lee el [LTV](ltv): los ingresos por usuario de pago después de cada renovación o día. | Bandera | Requerido | Descripción | |---|---|---| | `--group-by` | No | Una agrupación del catálogo. | | `--revenue-basis` | No | `gross` (por defecto), `proceeds` o `net`. | | `--period-type` | No | `renewals` (por defecto) cuenta por renovación. `days` cuenta por día desde la instalación. | | `--renewal-days` | No | Días a contar con `--period-type days`, separados por comas, en orden ascendente, como `7,30,90`. Solo con `--period-type days`. | ### `analytics cohort` Lee [cohortes de instalación](analytics-cohorts): los ingresos, suscriptores, promedios y predicciones de cada cohorte por renovación o día. Cada cohorte es una fila, y su fecha de inicio es su `key`. Las cohortes no se pueden agrupar. | Indicador | Requerido | Descripción | |---|---|---| | `--granularity` | No | Duración de una cohorte: `day`, `week`, `month` (por defecto), `quarter` o `year`. | | `--revenue-basis` | No | `gross` (por defecto), `proceeds` o `net`. | | `--period-type` | No | `renewals` (por defecto) cuenta por renovación. `days` cuenta por día desde la instalación. | | `--renewal-days` | No | Días a contar con `--period-type days`, separados por comas, en orden ascendente. Solo con `--period-type days`. | ## Qué sigue \{#whats-next\} - [Cómo funciona Adapty Analytics](how-adapty-analytics-works) — de dónde vienen los números. - [Controles, filtros y agrupación](controls-filters-grouping-compare-proceeds) — qué significan los filtros y las opciones de ingresos en el dashboard. - [Consultar atribución desde la CLI](developer-cli-attribution) — gasto, instalaciones y ROAS por canal y campaña. --- # File: developer-cli-ab-tests --- --- title: "Ejecutar pruebas A/B desde la CLI" description: "Crea, inicia, detiene, reemplaza y archiva pruebas A/B de paywalls y flows desde el terminal con la CLI de Adapty, y visualiza el efecto exacto antes de cada paso que no se puede deshacer." --- La CLI de Adapty ejecuta [pruebas A/B](ab-tests) de paywalls o flows directamente desde el terminal, bajo el tema `adapty ab-tests`. Puedes crear una prueba como borrador, iniciarla en un placement para una audiencia, detenerla con un ganador, reemplazar una prueba activa con una modificada y archivar las pruebas antiguas. Úsalo para que un agente de IA ejecute tus pruebas, para crear un script de prueba en varias aplicaciones, o para iniciar o detener una prueba sin abrir el dashboard. Una prueba regular divide los usuarios de una audiencia en un placement entre dos o más paywalls, o entre dos o más flows. Para ejecutar una prueba en varios placements, usa una [prueba crossplacement](ab-test-types) en el dashboard. Los comandos `ab-tests` no devuelven resultados de la prueba. Para ver los resultados, como la probabilidad de ser el mejor, usa el dashboard: [Resultados y métricas de pruebas A/B](results-and-metrics). Para leer el análisis de los usuarios de una prueba, filtra los [comandos de análisis](developer-cli-analytics) por la dimensión **A/B tests** de `adapty analytics catalog`. ## Antes de empezar \{#before-you-start\} El tema `ab-tests` utiliza la misma instalación e inicio de sesión que el resto de la CLI. Si aún no lo has configurado, sigue los pasos 1 y 2 de la [guía de inicio rápido](developer-cli-quickstart). El tema requiere la versión 0.14.0 o posterior de la CLI. Para ver tu versión, ejecuta `adapty --version`. Cada comando requiere el UUID de la app en `--app`. Para encontrarlo, ejecuta: ```bash adapty apps list ``` Una prueba usa contenido que ya existe: - **Flows**: Un flow se une a una prueba solo cuando está publicado. Un flow en borrador, o con cambios sin publicar, no es aceptado. Para publicar un flow, ejecuta `adapty flows publish`. - **Paywalls**: Para crear un paywall con productos que ya están en Adapty, ejecuta `adapty paywalls create`. Para darle un nuevo diseño a un paywall, usa el [Paywall Builder](adapty-paywall-builder). - **Placements**: Una prueba de paywall se ejecuta en un placement de paywall, y una prueba de flow en un placement de flow. Para crear un placement, ejecuta `adapty placements create`. Tu app no mostrará nada de un nuevo placement hasta que su código obtenga ese placement. - **Segments**: Para ejecutar una prueba solo para algunos usuarios, crea primero un [segmento](segments) con `adapty segments create`. For these commands, see the [command reference](developer-cli-reference). ## Listar y consultar pruebas \{#list-and-read-tests\} `list` muestra pruebas A/B de flows a menos que pases `--type paywall`. Para ver todas las pruebas de una app, ejecútalo una vez por cada tipo: ```bash adapty ab-tests list --app <app-id> --type flow --page-size 100 ``` ```bash adapty ab-tests list --app <app-id> --type paywall --page-size 100 ``` Una prueba puede estar en uno de estos estados: `draft`, `live`, `completed`, `inactive` o `archived`. Para consultar una prueba concreta con sus variantes, ejecuta: ```bash adapty ab-tests get --app <app-id> <test-id> ``` En `get --json`, `segment_ids` es la audiencia de la prueba: `[]` corresponde a Todos los usuarios y `null` significa que aún no se ha planificado ninguna audiencia. Cada variante tiene su `weight` y el ID del paywall o flow. Para identificar una variante, usa `paywall_title` o `flow_name`. El `title` de la variante está vacío a menos que lo hayas establecido. ## Crear una prueba \{#create-a-test\} Una prueba nueva es un borrador. Ningún usuario ve un borrador, así que puedes crear uno y modificarlo en cualquier momento. 1. **Encuentra los IDs** del contenido que quieres probar, con `adapty paywalls list` o `adapty flows list`. 2. **Crea el borrador** con dos o más variantes. Cada variante es un paywall o flow con un peso. El peso es el porcentaje de usuarios que ven esa variante, y los pesos deben sumar 100: ```bash adapty ab-tests create --app <app-id> --title "Annual vs monthly" \ --goal "More revenue per user" --placement <placement-id> \ --paywalls '[{"paywall_id":"<paywall-a>","weight":50},{"paywall_id":"<paywall-b>","weight":50}]' ``` `--placement` es obligatorio. Una prueba de paywall necesita un placement de paywall, y una prueba de flow necesita un placement de flow. Para una prueba de flow, usa `--flows` con `flow_id` en cada variante. Una variante también puede tener un `title`, como `"title":"A"`. 3. **Revisa el borrador** con `ab-tests get`. La ubicación de un borrador es solo provisional. `create` también puede aceptar una audiencia provisional, con `--segment` o `--all-users`. Estos valores provisionales no tienen ningún efecto sobre los usuarios. Cuando inicies la prueba, vuelve a pasar el placement y la audiencia a `start`. ## Iniciar una prueba \{#start-a-test\} :::warning No puedes deshacer un inicio. Desde el momento en que la prueba comienza, los usuarios de la audiencia que indiques dejan de ver su paywall o flow actual. ::: Para iniciar un borrador, indica el placement y una audiencia: `--all-users`, o `--segment` con un ID de segmento: ```bash adapty ab-tests start --app <app-id> <test-id> --placement <placement-id> --segment <segment-id> ``` El CLI muestra lo que hará el inicio y pregunta `Apply? [y/N]` antes de enviar nada. El lugar donde se ejecuta la prueba depende de las audiencias del placement: - **El placement tiene una audiencia para este segmento** (o para todos los usuarios): La prueba se ejecuta en esa audiencia. Una prueba que esté activa en ese momento se detiene y pasa al estado `completed`. - **El placement no tiene audiencia para este segmento**: `start` añade una nueva audiencia con la máxima prioridad en el placement. Los usuarios de este segmento que actualmente coincidan con otra audiencia pasan a la prueba. Para dar a la prueba una prioridad menor que otra audiencia, primero añade el segmento como audiencia con esa prioridad. Asígnale el paywall o flow que esos usuarios ven ahora y luego inicia la prueba en esa audiencia. Consulta [Cambiar la prioridad de la audiencia](change-audience-priority). Un cambio en una audiencia de segmento puede tardar hasta 20 minutos en llegar a todos los usuarios. Solo se inicia una prueba en borrador. Para ejecutar una prueba de nuevo, crea una nueva: lee la prueba antigua con `get` y luego crea una prueba con los mismos grupos usando `create`. ## Cambiar una prueba \{#change-a-test\} `update` envía la prueba completa de nuevo. Incluye todos los parámetros cada vez, incluyendo el obligatorio `--placement`. Si omites un parámetro, Adapty borra su valor: por ejemplo, el objetivo o la audiencia planificada. ```bash adapty ab-tests update --app <app-id> <test-id> --title "Annual vs monthly, Q4" \ --goal "More revenue per user" --placement <placement-id> \ --paywalls '[{"paywall_id":"<paywall-a>","weight":70},{"paywall_id":"<paywall-b>","weight":30}]' ``` En una prueba en vivo, `update` solo cambia el título y el objetivo. Pasa los brazos, el placement y la audiencia exactamente como los devuelve `get`; se rechazarán otros brazos u otro placement. La prueba mantiene su hora de inicio y los usuarios conservan sus brazos. No puedes modificar una prueba completada o archivada. Para cambiar los brazos o los pesos de una prueba en vivo, reemplázala. `replace` es la acción **Modify** del dashboard: ```bash adapty ab-tests replace --app <app-id> <test-id> --title "Annual vs monthly v2" \ --placement <placement-id> \ --paywalls '[{"paywall_id":"<paywall-a>","weight":80},{"paywall_id":"<paywall-b>","weight":20}]' ``` La prueba activa se detiene y pasa al estado `completed`, y sus resultados se conservan con su ID. Una nueva prueba con un nuevo ID comienza en la misma audiencia, y Adapty asigna de nuevo a cada usuario de la audiencia a un grupo. Si indicas un placement diferente en `--placement`, Adapty mueve la audiencia a ese placement. No puedes mover una prueba de todos los usuarios. ## Detener una prueba \{#stop-a-test\} `stop` finaliza la prueba. Tú decides qué ven los usuarios de su audiencia a partir de ese momento. Indica exactamente uno de estos flags: | Flag | Después de detener | |---|---| | `--winner <id>` | Toda la audiencia ve uno de los grupos de la prueba. Indica el ID del paywall o el ID del flow de ese grupo. | | `--other <id>` | Toda la audiencia ve un paywall o flow que no formaba parte de la prueba, del mismo tipo que la prueba. | | `--no-content` | La audiencia abandona el placement y sus usuarios pasan a la siguiente audiencia que les corresponda. No está disponible para pruebas con audiencia de todos los usuarios. | ```bash adapty ab-tests stop --app <app-id> <test-id> --winner <paywall-b> ``` La prueba pasa al estado `completed`. Para volver a probar los mismos grupos, crea una nueva prueba como se describe en [Iniciar una prueba](#start-a-test). Para elegir al ganador, consulta [Resultados y métricas de la prueba A/B](results-and-metrics). ## Archivar una prueba \{#archive-a-test\} Puedes archivar cualquier prueba que no esté activa. Para archivar una prueba activa, primero detenla. No puedes restaurar una prueba archivada. ```bash adapty ab-tests archive --app <app-id> <test-id> ``` ## Confirmación en scripts y agentes \{#confirmation-in-scripts-and-agents\} `start`, `stop`, `replace`, y `archive` no se pueden deshacer, por lo que cada uno muestra su efecto antes de enviar nada. La forma en que solicita confirmación depende de cómo se ejecute el comando: | Cómo se ejecuta el comando | Qué ocurre | |---|---| | En un terminal | La CLI imprime el efecto y pregunta `Apply? [y/N]`. Si respondes `y` o `yes`, la CLI envía la solicitud. Para cualquier otra respuesta, la CLI termina con código 1 y no envía nada. | | Con `--yes` | La CLI envía la solicitud sin preguntar. | | Con `--json`, en una tubería o sin terminal | La CLI no envía nada y termina con código 6. El mensaje de error describe el efecto. | De este modo, un script o un agente puede leer el efecto de un comando antes de ejecutarlo: lanzarlo sin `--yes`, leer el mensaje y volver a ejecutarlo con `--yes` solo después de que alguien haya aprobado dicho efecto. ## Errores \{#errors\} | Código de salida | Causa | Qué hacer | |---|---|---| | 1 | Respondiste algo distinto a `y` en el prompt. No se envió nada. | Vuelve a ejecutar el comando si era tu intención. | | 2 | La CLI rechazó la entrada antes de enviarla: un UUID inválido, arms que no son un array JSON, un arm sin ID o sin peso entero, `--paywalls` y `--flows` ambos o ninguno, o dos opciones de stop. | Corrige el flag que indica el mensaje. | | 3 | No has iniciado sesión, o tu usuario de Adapty no tiene acceso a la app. | Inicia sesión con `adapty auth login` y encuentra el ID de la app en `adapty apps list`. | | 4 | Adapty rechazó la solicitud. El mensaje indica el motivo. | Consulta [Mensajes de Adapty](#messages-from-adapty). | | 5 | La CLI no pudo conectar con Adapty. | Asegúrate de tener conexión y vuelve a ejecutar el comando. | | 6 | Un comando que no se puede deshacer se ejecutó sin `--yes` y sin nadie que respondiera. No se envió nada. | Lee el efecto en el mensaje y vuelve a ejecutar el comando con `--yes`. | | 130 | Pulsaste Ctrl+C en el prompt. No se envió nada. | — | ### Mensajes de Adapty | Mensaje | Qué hacer | |---|---| | `The sum of the weights must equal 100, but has N.` | Cambia los pesos para que sumen 100. | | `Flow(<id>) must be published before adding to AB test.` | Publica el flow y vuelve a ejecutar el comando. | | `Paywalls cannot be changed in non-draft ab tests.` o `Placement cannot be changed in non-draft ab tests.` | Pasa los arms y el placement tal como los devuelve `get`, o usa `replace`. | | `AB Test from default audience can not be stopped.` | Una prueba A/B para todos los usuarios necesita un paywall o flow tras detenerla: usa `--winner` o `--other`. | | `AB Test to stop must be live.` | Solo se puede detener o reemplazar una prueba en curso. | | `Live Ab Test can not be deleted.` | Detén la prueba antes de archivarla. | ## Referencia de comandos \{#command-reference\} Todos los comandos requieren `--app`, el UUID de la app obtenido con `adapty apps list`. Todos los comandos excepto `list` necesitan el ID de la prueba como argumento. ### `ab-tests list` | Flag | Required | Description | |---|---|---| | `--type` | No | `flow` (predeterminado) o `paywall`. | | `--state` | No | `live`, `draft`, `completed`, `inactive` o `archived`. | | `--placement` | No | Solo las pruebas de este placement (ID). Repite el flag para varios placements. | | `--segment` | No | Solo las pruebas que se ejecutan para exactamente este segmento (ID). Repite el flag para varios segmentos. | | `--search` | No | Solo las pruebas cuyo título contenga este texto. | | `--page` | No | Número de página. Predeterminado: 1. | | `--page-size` | No | Pruebas por página, hasta 100. Predeterminado: 20. | ### `ab-tests get` Devuelve el test con sus ramas. No acepta otros flags. ### `ab-tests create` | Indicador | Obligatorio | Descripción | |---|---|---| | `--title` | Sí | Título de la prueba. | | `--paywalls` | Uno de los dos | Variantes de una prueba A/B de paywall, en JSON: `[{"paywall_id":"<id>","weight":50,"title":"A"}, …]`. `title` es opcional. | | `--flows` | Uno de los dos | Variantes de una prueba A/B de flow, en JSON, con `flow_id` en lugar de `paywall_id`. | | `--goal` | No | Lo que la prueba pretende demostrar. | | `--placement` | Sí | Placement para el que se planifica la prueba (ID). Una prueba de paywall necesita un placement de paywall, y una prueba de flow necesita un placement de flow. | | `--segment` | No | Planifica la prueba para un segmento (ID). No se puede usar junto con `--all-users`. | | `--all-users` | No | Planifica la prueba para todos los usuarios. | ### `ab-tests update` Acepta los mismos flags que `create`, y `--placement` es obligatorio. Un flag que omitas se borrará. En una prueba en curso, solo se pueden cambiar `--title` y `--goal`. Pasa de nuevo el mismo placement y audiencia. ### `ab-tests start` | Flag | Obligatorio | Descripción | |---|---|---| | `--placement` | Sí | Placement donde ejecutar la prueba (ID). | | `--segment` | Una de las dos | Ejecutar la prueba para un segmento (ID). | | `--all-users` | Una de las dos | Ejecutar la prueba para todos los usuarios. | | `--yes`, `-y` | No | Iniciar sin confirmación. Obligatorio con `--json` o en un pipe. | ### `ab-tests stop` | Indicador | Requerido | Descripción | |---|---|---| | `--winner` | Uno de los tres | Muestra el paywall o flow de este arm (ID) a toda la audiencia. | | `--other` | Uno de los tres | Muestra un paywall o flow que no estaba en la prueba (ID). | | `--no-content` | Uno de los tres | Elimina la audiencia del placement. No está permitido para una prueba de todos los usuarios. | | `--yes`, `-y` | No | Detiene sin confirmar. Requerido con `--json` o en una tubería. | ### `ab-tests replace` | Flag | Requerido | Descripción | |---|---|---| | `--title` | Sí | Título de la nueva prueba. | | `--placement` | Sí | Placement de la nueva prueba (ID). Un placement diferente mueve la audiencia a ese placement. | | `--paywalls` o `--flows` | Sí | Variantes de la nueva prueba, como en `create`. | | `--goal` | No | Lo que la nueva prueba pretende demostrar. | | `--yes`, `-y` | No | Reemplazar sin confirmación. Obligatorio con `--json` o en un pipe. | La nueva prueba toma la audiencia de la prueba que reemplaza, por lo que `replace` no admite ningún flag de audiencia. ### `ab-tests archive` | Flag | Required | Description | |---|---|---| | `--yes`, `-y` | No | Archivar sin confirmación. Obligatorio con `--json` o en una tubería. | ## ¿Qué viene después? \{#whats-next\} - [Skill de agente para pruebas A/B](ab-tests-skill) — permite que tu agente planifique, inicie y detenga pruebas, con tu confirmación antes de cada paso que no se puede deshacer. - [Crear, ejecutar y detener una prueba A/B](run_stop_ab_tests) — los mismos pasos en el dashboard. - [Resultados y métricas de pruebas A/B](results-and-metrics) — cómo interpretar los resultados y elegir un ganador. --- # File: developer-cli-migrations --- --- title: "Comandos de migraciones" description: "Comandos de Adapty CLI para mover un proyecto de RevenueCat a Adapty: crear una migración, ejecutar sus pasos, comprobar su estado y cerrarla." --- Mueve un proyecto de RevenueCat a Adapty: su catálogo y, opcionalmente, sus eventos del store e historial de transacciones. Adapty ejecuta cada migración como una secuencia de pasos e indica qué acciones puedes ejecutar a continuación, así que consulta `status` antes de cada acción. Para ver el procedimiento completo, consulta [Migración desde RevenueCat](migration-from-revenuecat). Todos los comandos excepto `list`, `create`, `use`, `current` y `unuse` aceptan el flag de migración: | Flag | Requerido | Descripción | |---|---|---| | `-m`, `--migration` | No | ID de migración. Sin él, la CLI usa la variable de entorno `ADAPTY_MIGRATION`, y después la migración guardada por `create` o `use`. | ## adapty migrations list Lista tus migraciones y los flows de migración que puedes iniciar, agrupados por aplicación. ```bash adapty migrations list ``` ## adapty migrations create Inicia una migración y guárdala como la actual. ```bash adapty migrations create --name <app-name> adapty migrations create --flow <flow> --app <app-id> ``` | Indicador | Requerido | Descripción | |---|---|---| | `--name` | Uno de `--name` o `--flow` | Nombre de la nueva app de Adapty. Inicia la migración del catálogo. No se puede combinar con `--flow` ni con `--app`. | | `--flow` | Uno de `--name` o `--flow` | Flow a iniciar en una app existente, tal como lo muestra `adapty migrations list`. Requiere `--app`. | | `--app` | Con `--flow` | ID de la app (UUID) | | `--no-select` | No | Crea la migración sin guardarla como la actual | Los flows disponibles para una app existente después de su migración de catálogo: | Flow | Descripción | |---|---| | `transactions` | Importa el historial de transacciones desde una exportación Cloud Export diaria de RevenueCat | | `transactions_support` | Importa el historial de transacciones a través del soporte de RevenueCat y el soporte de Adapty, para planes de RevenueCat sin Cloud Export | | `apple_events` | Reenvía las notificaciones de App Store a Adapty mientras RevenueCat continúa recibiéndolas | | `google_events` | Suscribe a Adapty al tema Pub/Sub que RevenueCat utiliza para las notificaciones de Google Play | | `code` | Hace que tu agente de codificación reemplace el SDK de RevenueCat en una base de código de la app | | `integrations` | Hace que tu agente de codificación encuentre las herramientas de analítica, atribución y mensajería en el código de la app para configurarlas en Adapty | | `paywall_design` | Hace que tu agente de codificación reconstruya los diseños de paywall de RevenueCat en los flows creados por la migración del catálogo | ## adapty migrations use Guarda una migración como la actual, para que los demás comandos no necesiten `-m`. ```bash adapty migrations use <migration-id> ``` | Argumento | Descripción | |---|---| | `migration-id` | ID de migración obtenido de `adapty migrations list` | La CLI verifica que puedes acceder a la migración antes de guardar el ID. La migración guardada pertenece al token actual y se aplica en cualquier terminal. ## adapty migrations current Muestra la migración actual y su procedencia: la variable de entorno `ADAPTY_MIGRATION` o la selección guardada. ```bash adapty migrations current ``` ## adapty migrations unuse Borra la migración guardada. Un valor de `ADAPTY_MIGRATION` establecido en tu shell permanece activo hasta que lo elimines. ```bash adapty migrations unuse ``` ## adapty migrations status Muestra el estado de una migración, sus problemas y las acciones disponibles a continuación. ```bash adapty migrations status ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--wait` | No | Realiza polling hasta que la migración cambie y devuelve el estado más reciente | | `--timeout` | No | Tiempo de polling para `--wait`: de 1 a 600 segundos, 120 por defecto. Acepta `300`, `300s` o `5m`. Requiere `--wait`. | Con `--json`, `next_actions` y `available_actions` muestran el ID de cada acción, los recursos que lee, su `input_schema` y el texto de `confirm`. El código de salida `0` significa que la solicitud fue exitosa, no que la migración lo haya sido. Comprueba `migration.state` en la respuesta. ## Pasos de migraciones de Adapty \{#adapty-migrations-steps\} Muestra el checklist de migración y el estado de cada paso. ```bash adapty migrations steps ``` ## adapty migrations show Lista los recursos que puedes leer en este momento, o lee uno en concreto. ```bash adapty migrations show [resource] ``` | Argument | Description | |---|---| | `resource` | Nombre del recurso, por ejemplo `apps`, `mapping`, `report` o `export-config`. Omítelo para ver la lista de recursos disponibles en el paso actual. | ## adapty migrations run Ejecuta una acción de `adapty migrations status`. ```bash adapty migrations run <action-id> ``` | Argumento | Descripción | |---|---| | `action-id` | ID de acción de `next_actions` o `available_actions` en `adapty migrations status` | | Flag | Obligatorio | Descripción | |---|---|---| | `--input` | No | Entrada de la acción como objeto JSON que coincide con el `input_schema` de la acción. Sin entrada, la CLI envía `{}`. | | `--input-file` | No | Ruta a un archivo con la entrada de la acción, o `-` para leerla desde stdin. No se puede combinar con `--input`. | | `-y`, `--yes` | Para acciones con texto `confirm` | Confirma la acción tras leer su texto `confirm`. No hay ningún prompt interactivo. | | `--open` | No | Abre el enlace de la acción incluso con una tubería o `--json` | | `--no-browser` | No | Muestra el enlace de la acción sin abrir un navegador | Las acciones existen en tres tipos: - **Las acciones de entrada** envían tus datos a Adapty, por ejemplo las apps de RevenueCat a migrar o tus decisiones de mapeo. - **Las acciones externas** abren una página en el Adapty Dashboard, por ejemplo para pegar la clave API secreta de RevenueCat. Completa la página y luego comprueba `status`. - **Las acciones del agente**, por ejemplo `migrate_code`, son tareas para tu agente de codificación. Ejecútalas desde el repositorio git de la app: la CLI guarda los datos de la migración en `.git/adapty/<migration-id>/` e imprime las instrucciones para el agente. El agente reporta el resultado ejecutando la acción de nuevo con `--input`. ## adapty migrations close Finaliza o cancela una migración. No puedes reabrirla después. ```bash adapty migrations close --outcome finish --yes ``` | Flag | Required | Description | |---|---|---| | `--outcome` | Yes | `finish` marca la migración como completada; `cancel` la abandona | | `-y`, `--yes` | Yes | Confirma el cierre | ## Qué hacer a continuación \{#whats-next\} - [Referencia de comandos](developer-cli-reference) — todos los temas de la CLI, los flags globales y los flags de paginación. --- # File: developer-cli-ads-manager --- --- title: "Gestionar Ads Manager desde la CLI" description: "Ejecuta campañas de Apple Ads, palabras clave y reglas de automatización desde el terminal con la CLI para desarrolladores de Adapty." --- La CLI de Adapty puede gestionar tu cuenta de [Ads Manager](adapty-ads-manager) desde el terminal, bajo el tema `adapty asa`. Cubre campañas, grupos de anuncios, palabras clave, anuncios, páginas de producto, reglas de automatización, métricas e investigación de la competencia. Úsalo para las tareas que el navegador ralentiza: dar a un agente de IA acceso en vivo al rendimiento de tus anuncios, añadir varios cientos de palabras clave desde un archivo o aplicar la misma configuración en varias campañas. Para todo lo demás, el [dashboard](https://app.adapty.io) sigue siendo más rápido. :::warning La CLI no puede eliminar nada. Las campañas, anuncios y reglas de automatización se pueden crear, actualizar y pausar desde el terminal, pero eliminarlos solo es posible desde el dashboard. ::: ## Antes de comenzar \{#before-you-start\} Los comandos de Ads Manager usan la misma instalación e inicio de sesión que el resto de la CLI. Si aún no lo has configurado, sigue los pasos 1 y 2 de la [guía de inicio rápido](developer-cli-quickstart). ### Requisitos previos \{#prerequisites\} Dos condiciones adicionales se aplican a todos los comandos `adapty asa`: - **Una cuenta de Apple Ads conectada**: conéctala con `adapty asa connect`, o desde el dashboard como se describe en [Comenzar con Adapty Ads Manager](adapty-ads-manager-get-started). - **Una suscripción activa de Ads Manager**: sin ella, todos los comandos fallan con `402 ads_manager_subscription_required`. Un comando muestra ambas a la vez: ```bash adapty asa whoami ``` ### Diferencias con el resto de la CLI - **No existe el flag `--app`**: El alcance es la empresa a la que pertenece tu token. `--app` solo existe en algunos comandos `list` como filtro. Los comandos `asa segments` son la excepción: escriben en una app de Adapty, por lo que `--app` indica esa app. - **Las escrituras llegan directamente a Apple**: Cada comando que modifica tu cuenta imprime el cuerpo de la solicitud y pide confirmación antes de enviarla. No hay paso de staging. - **Las lecturas son baratas, las escrituras no**: Usa los comandos `list` y `--dry-run` con libertad. Trata todo lo demás como irreversible. Para omitir el mensaje de confirmación en un script, usa `--yes`. Con `--json` o en una tubería, un comando de escritura rechaza la operación en lugar de esperar una respuesta que nunca llegará, por lo que `--yes` es obligatorio en esos casos. ## Encuentra los IDs que necesitas \{#find-the-ids-you-need\} Cada comando requiere UUIDs, y cada UUID proviene de un comando `list`. Trabaja siguiendo la jerarquía: ```bash adapty asa orgs list adapty asa campaigns list --campaign-group <campaign-group-id> adapty asa ad-groups list --campaign <campaign-id> ``` Delimita cada lectura con un filtro. Los filtros acotan la consulta en lugar de la página impresa, así que una lectura delimitada es económica mientras que una sin delimitar pagina por toda la cuenta. `adapty asa keywords list` sin `--ad-group` es la lectura más amplia del tema. Estas listas devuelven solo metadatos. Los datos de rendimiento vienen de [`asa metrics`](#query-metrics-from-agents-and-scripts). ## Obtener recomendaciones de palabras clave \{#get-keyword-recommendations\} Para crear una lista de palabras clave sin investigación previa, obtén un conjunto predefinido para tu app. Pasa el `adam_id` de la app obtenido con `adapty asa apps list` y el tipo de conjunto — `brand`, `generic` o `competitor`: ```bash adapty asa keywords recommend --adam-id <adam-id> --type generic --country US ``` El pool no tiene pujas ni tipos de concordancia. Elige los tuyos y añade las palabras clave como se describe en [Añadir palabras clave en bloque](#add-keywords-in-bulk). Si el resultado muestra `status: building`, vuelve a intentarlo en aproximadamente un minuto. Consulta los [comandos de Ads Manager](developer-cli-ads-manager-reference#adapty-asa-keywords-recommend) para ver los tipos de pool y los límites. ## Añadir palabras clave en lote \{#add-keywords-in-bulk\} Añadir palabras clave una a una es el principal motivo para salir del dashboard. Escribe una palabra clave por línea en un archivo de texto: ```bash adapty asa keywords add --ad-group <ad-group-id> --from-file keywords.txt --bid 1.20 --match-type EXACT ``` Las palabras clave se aplican en lotes de 100 como máximo por llamada. Divide las listas más grandes en varias llamadas. Pueden producirse dos tipos de error y se comportan de manera distinta. Un ID no válido hace que todo el lote falle antes de llamar a Apple, por lo que no se aplica nada. Apple también puede rechazar palabras clave individuales: el resto se añaden igualmente, y cada rechazo se notifica con su motivo. Comprueba la línea de resumen en lugar de fijarte solo en el código de salida. Empieza con unas pocas palabras clave y revisa el resultado antes de enviar un archivo completo. ## Crear una campaña de máximas conversiones \{#max-conversions\} Una campaña que puja con `MAX_CONVERSIONS` solo se sirve cuando tiene un grupo de anuncios automatizado, así que crea ambos a la vez: ```bash adapty asa campaigns create --org <campaign-group-id> --name "Max Conv" --adam-id 123456 --country US --daily-budget 50 --bidding-strategy MAX_CONVERSIONS adapty asa ad-groups create --campaign <campaign-id> --name "Automated Max Conv" --automated ``` Hasta que ese grupo de anuncios exista, la campaña reporta `serving_status: NOT_RUNNING` con `AUTOMATED_KEYWORDS_REQUIRED_AD_GROUP_MISSING` entre sus `serving_state_reasons`, y `campaigns create` muestra tanto el motivo como el comando que lo resuelve. `--automated` es lo que satisface el requisito — un grupo de anuncios normal con `--automated-keywords` no lo hace. Apple programa y ejecuta el grupo de anuncios automatizado por sí mismo, por lo que no requiere `--start-time`, `--default-bid` es opcional y permanece habilitado: para detener el gasto, pausa la campaña. En la campaña, mantén `--target-cpa` por debajo de `--daily-budget`. ## Configurar opciones de facturación para una línea de crédito \{#invoicing-options\} Apple requiere opciones de facturación en cada campaña de una organización que factura por línea de crédito. `adapty asa orgs list` muestra el `payment_model` de cada organización — `LOC` significa que los cinco indicadores `--invoice-*` son aplicables: ```bash adapty asa campaigns create --org <campaign-group-id> --name "LOC push" --adam-id 123456 --country US --daily-budget 50 --invoice-advertiser "Acme Inc" --invoice-order-number PO-42 --invoice-contact-name "Jane Doe" --invoice-contact-email jane@acme.com --invoice-billing-email billing@acme.com ``` Pass all five in one call — a partial set is rejected before the request reaches Apple. Without them, the campaign is created but reports `serving_status: NOT_RUNNING` with `MISSING_BO_OR_INVOICING_FIELDS`. Los mismos cinco flags en `adapty asa campaigns update` configuran las Invoicing Options en una campaña que ya existe. Reemplazan el conjunto almacenado por completo, así que pasa los cinco incluso si solo quieres cambiar uno. ## Crear una estructura de campaña en una sola operación \{#create-a-campaign-structure-in-one-operation\} `campaigns bulk-create` reemplaza un script que itera sobre `campaigns create` y `ad-groups create`. Envía toda la estructura de una campaña — campañas con sus grupos de anuncios, palabras clave, palabras clave negativas y anuncios — como una sola operación: ```bash adapty asa campaigns bulk-create --file structure.json ``` La entrada es una descripción JSON de la estructura — consulta [el formato de la estructura](developer-cli-ads-manager-reference#adapty-asa-campaigns-bulk-create) para ver los campos. JSON es el camino natural para un agente de IA: genera la estructura y la pasa directamente: ```bash cat structure.json | adapty asa campaigns bulk-create --file - ``` Una [plantilla de Apple Ads en bloque](https://ads.apple.com/app-store/help/campaigns/0089-make-edits-in-bulk) también funciona como entrada — el servidor convierte `Campaign_And_Adgroup_Template.xlsx` o un [`.csv` de palabras clave](https://ads.apple.com/app-store/help/keywords/0014-add-and-manage-keywords) en una estructura. `--org-id` acepta el `org_id` numérico de `adapty asa orgs list`. Para revisar la conversión antes de que se cree nada, añade `--preview`: ```bash adapty asa campaigns bulk-create --from-file Campaign_And_Adgroup_Template.xlsx --org-id 1234567 --preview ``` Los problemas de conversión se notifican con su hoja, fila y columna. Cuando la estructura impresa tenga buen aspecto, elimina `--preview` para enviarla. La salida de `--preview` es también la forma más rápida de obtener un archivo de estructura inicial: guárdalo, edítalo y envíalo con `--file` — del mismo modo que [`automations get`](#run-automation-rules) proporciona una plantilla de regla. La estructura completa se valida antes de crear nada, y un rechazo lista todos los nodos inválidos. Una vez aceptada, los objetos se crean en el servidor mientras el comando informa el progreso. El informe final es `success`, `partial` o `failed` — un resultado `partial` lista cada objeto que no se creó, con el error de Apple. Para una estructura grande, usa `--no-wait` para obtener el ID de operación de inmediato y consultar el progreso más tarde: ```bash adapty asa campaigns bulk-status <operation-id> ``` ## Consulta métricas desde agentes y scripts \{#query-metrics-from-agents-and-scripts\} `asa metrics` genera informes sobre cualquier nivel de la cuenta para un rango de fechas. Añade `--json` para que un agente de IA o un script pueda consumir el resultado directamente: ```bash adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --metric spend --metric roas --json ``` `--metric` es obligatorio y repetible, y acepta los nombres que Ads Manager registra. Consulta [Métricas](adapty-ads-manager-metrics) para ver la lista completa. Cada métrica que indiques se calcula en todo el nivel de entidad antes de paginar, así que solicita solo las columnas que vayas a leer en lugar del catálogo completo. `--app`, `--campaign` y `--ad-group` acotan el informe, y acotarlo es la forma más económica de acelerar una llamada, porque el coste depende del número de entidades agregadas y no del tamaño de la página. Cuatro métricas cuentan perfiles únicos por entidad — `subscribers`, `paid_subscribers`, `arppu` y `arpas` — y estas requieren un filtro de campaña o grupo de anuncios en cualquier nivel de entidad: ```bash adapty asa metrics --entity keyword --date-from 2026-07-01 --date-to 2026-07-31 --metric arpas --campaign <campaign-id> --json ``` Las métricas de cohorte funcionan de manera diferente al resto. No existe la métrica `ltv`, porque el valor de vida se lee en una ventana de renovación en lugar de en una fecha. Solicita la ventana en su lugar: ```bash adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --metric roas --by-days 7 --by-days 90 --order-by-day 90 ``` Esto devuelve tus campañas clasificadas por ROAS al día 90. En una sola llamada caben hasta 16 ventanas. Cada fila es una entidad, ya agregada y ordenada en el servidor. Una pregunta del tipo "las cinco campañas con mayor gasto" es, por tanto, una sola llamada, no un recorrido por todas las páginas: ```bash adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --order-by spend --page-size 5 ``` Las métricas y la lista de términos de búsqueda comparten un único presupuesto de analytics por empresa: 5 llamadas por minuto y un máximo de 2 en cualquier intervalo de 10 segundos. Formula una pregunta concreta en lugar de hacer consultas repetidas. Dos límites se aplican a un informe. El período de generación de informes está limitado por el nivel de agrupación que elijas: 28 días con `--group-by day`, 90 días sin agrupación por período, 180 por semana, 365 por mes. Además, cada página está limitada a 5000 filas de desglose, contando una fila por entidad × país × período. Por eso, para ampliar un informe conviene usar una agrupación más amplia en `--group-by`, no dividirlo en más llamadas. ## Consulta palabras clave de la competencia \{#check-competitor-keywords\} Un solo comando devuelve las palabras clave por las que pujan las apps de la competencia, para hasta cinco apps de la App Store a la vez: ```bash adapty asa competitors summary --app-ids 1668337467,6503873027 --json ``` El período y los países están fijados en el servidor —el último mes completo, en todos los países— así que el comando no tiene más flags que los IDs de las apps. La primera llamada para un conjunto de apps puede tardar decenas de segundos. Utiliza esto para extraer datos de palabras clave de la competencia en un informe de forma programada. Para filtrar los resultados, comparar países en paralelo o añadir directamente las palabras clave que encuentres a una campaña, usa [Market Intelligence](ads-manager-market-intelligence) en el dashboard. ## Crear un segmento para cada campaña \{#create-a-segment-for-each-campaign\} Para mostrar a los usuarios de una campaña específica su propio paywall, crea un [segmento](segments) para esa campaña y úsalo como [audiencia](audience) en un placement. Primero, verifica que la campaña no tenga ya un segmento: ```bash adapty asa segments list --app <app-id> --campaign <campaign-id> ``` Si el resultado está vacío, crea el segmento: ```bash adapty asa segments create --app <app-id> --campaign <campaign-id> --yes ``` `--app` es el ID de la app de Adapty que se obtiene con `adapty apps list`. La CLI nombra el segmento `[ASA] Campaign: <name>`, igual que Ads Manager, para que Ads Manager lo reconozca como propio. Para crear un segmento a partir de grupos de anuncios o palabras clave, consulta [Segments](developer-cli-ads-manager-reference#segments) en la referencia de comandos. ## Encuentra quién modificó tus campañas \{#find-who-changed-your-campaigns\} Para saber quién pausó una campaña o cambió una puja, y cuándo, consulta el historial de cambios de la organización. Llega en tiempo real desde Apple, por lo que también incluye los cambios realizados directamente en Apple Ads o mediante otras herramientas: ```bash adapty asa change-history list --campaign-group <campaign-group-id> --entity-type Campaign --event-type UPDATE ``` Para ver el valor anterior y el nuevo de cada campo modificado, añade `--fields`. Para consultar un cambio en detalle, pasa su `detail_id` a `change-history get`: ```bash adapty asa change-history get <detail-id> --campaign-group <campaign-group-id> ``` Cada llamada lee una organización y hasta 14 días, dentro de los 6 meses que Apple conserva. Los filtros `--campaign` y `--ad-group` de este comando aceptan los IDs numéricos de Apple, no UUIDs. Consulta [Historial de cambios](developer-cli-ads-manager-reference#change-history) para ver todos los flags. Para ver los mismos cambios en el Adapty Dashboard, usa la [página de Historial de cambios](ads-manager-change-history). ## Ejecutar reglas de automatización \{#run-automation-rules\} La CLI almacena el JSON que le proporciones, por lo que la forma más rápida de obtener un archivo de [regla de automatización](ads-manager-automations) válido es crear una regla en el dashboard y luego leerla de vuelta: ```bash adapty asa automations get <automation-id> --json > rule.json ``` Edita ese archivo y úsalo como plantilla para nuevas reglas: ```bash adapty asa automations create --file rule.json ``` Un archivo de regla contiene exactamente una condición y exactamente una acción, que es lo que la API almacena por regla. Cuando pases un archivo a `automations update`, elimina primero el campo `internal_id` — la actualización se rechaza si está presente. ### Crea una acción de añadir como palabra clave a partir de flags \{#build-an-add-as-keyword-action-from-flags\} Una acción es la excepción al JSON escrito a mano. **Add as keyword**, que promueve un término de búsqueda o copia una palabra clave en otro grupo de anuncios, toma sus grupos de anuncios de destino, puja y tipo de concordancia de los flags: ```bash adapty asa automations create --file rule.json --target-ad-group <ad-group-id> --match-type EXACT --cpt-bid-type search_term_current_cpt --negate ad-group ``` :::warning Obtén los `params` de esta acción a partir de los flags, nunca de una regla que lleve una acción diferente. La API resuelve `params` por forma y no por nombre de variante, por lo que una clave perteneciente a otra acción hace que elija esa acción y descarte el resto, respondiendo con `200` y almacenando una regla que no añade palabras clave a ningún grupo de anuncios. ::: Los mismos flags en `automations update` reparan una regla ya almacenada con la forma incorrecta. El CLI lee la regla, reconstruye la acción desde cero y la vuelve a escribir, conservando solo los ajustes almacenados que encajan en una acción **Add as keyword**: ```bash adapty asa automations update <automation-id> --target-ad-group <ad-group-id> --match-type EXACT --cpt-bid-type search_term_current_cpt ``` Lo que faltaba en la regla incorrecta debe venir de un flag. Consulta [los comandos de Ads Manager](developer-cli-ads-manager-reference#add-keyword-flags) para ver el conjunto completo. Prueba una regla antes de dejarla modificar pujas: ```bash adapty asa automations run <automation-id> --dry-run ``` Una dry run evalúa las condiciones y registra lo que haría la regla sin tocar Apple Ads. Las ejecuciones se ponen en cola en lugar de realizarse de inmediato, por lo que el comando muestra un ID de ejecución y el resultado aparece en `adapty asa automations runs`. ## Volver a ejecutar scripts de forma segura \{#re-run-scripts-safely\} Cada escritura envía una clave de idempotencia. La CLI genera una por invocación y reintenta una vez tras un error de red, de modo que una solicitud que falló en tránsito nunca se aplica dos veces. En un script, fija la clave tú mismo para que toda la pipeline pueda volver a ejecutarse: ```bash adapty asa campaigns create --org <campaign-group-id> --name "Winter push" --adam-id 123456 --country US --daily-budget 50 --idempotency-key winter-push-2026 --yes ``` Re-ejecutar el mismo comando en menos de 24 horas devuelve el resultado almacenado e imprime `Already applied earlier` en lugar de crear una segunda campaña. La misma clave con un cuerpo distinto falla con `422`, lo que detecta un script editado que reutiliza una clave por error. ## Próximos pasos \{#whats-next\} - [Gestiona Apple Ads con una herramienta de codificación con IA](developer-cli-ads-manager-skill) — instala el plugin de Apple Ads para que Claude Code, Copilot CLI, Codex o Gemini CLI puedan ejecutar estos comandos por ti. - [Comandos de Ads Manager](developer-cli-ads-manager-reference) — todos los comandos con sus argumentos, flags y valores aceptados. - [Automatizaciones](ads-manager-automations) — qué hace cada tipo de regla y qué acciones puede ejecutar. - [Métricas](adapty-ads-manager-metrics) — los nombres de métrica aceptados por `--metric` y cómo se calcula cada una. --- # File: developer-cli-ads-manager-reference --- --- title: "Comandos de Ads Manager para el CLI de Adapty Developer" description: "Referencia de todos los comandos adapty asa: campañas, grupos de anuncios, palabras clave, anuncios, segmentos, automatizaciones, métricas e historial de cambios." --- Este artículo lista todos los comandos de [Ads Manager](adapty-ads-manager) en el CLI de Adapty, con sus argumentos, flags y valores aceptados. Los comandos de Ads Manager se encuentran bajo el topic `adapty asa`. :::link Para conocer los requisitos previos, las prácticas de escritura segura y ejemplos basados en tareas, consulta [Gestionar Ads Manager desde la CLI](developer-cli-ads-manager). ::: Estos comandos requieren una cuenta de Apple Ads conectada y una suscripción activa de Ads Manager. Ejecuta [`adapty asa whoami`](#adapty-asa-whoami) para verificar ambas. Para el resto de la CLI, consulta la [referencia de comandos](developer-cli-reference). ## Flags globales \{#global-flags\} Estos flags están disponibles en todos los comandos de Ads Manager. | Flag | Descripción | |---|---| | `--json` | Salida en formato JSON en lugar de texto formateado | | `--help` | Muestra 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` | `100` | Elementos por página (máx.: 1000) | Las páginas de Ads Manager son más grandes que en el resto de la CLI. Es preferible usar una página grande en lugar de un bucle de páginas pequeñas. Todos los comandos que modifican tu cuenta aceptan estos flags: | Indicador | Descripción | |---|---| | `--yes`, `-y` | Aplica sin pedir confirmación. Es obligatorio cuando la salida se redirige por pipe o se usa `--json` | | `--idempotency-key` | Clave fija para esta escritura. Una repetición con la misma clave y cuerpo en 24 horas devuelve el resultado almacenado en lugar de aplicar el cambio de nuevo | Los comandos de Ads Manager no aceptan el indicador `--app`. El ámbito es la empresa a la que pertenece tu token. `--app` existe en algunos comandos `list` solo como filtro. Los comandos de [segmentos](#segments) son la excepción: allí, `--app` es la app de Adapty a la que pertenece el segmento. ## Filtros de lista \{#list-filters\} Los filtros acotan la consulta en sí, no la página mostrada. Una `keywords list` sin filtros recorre todas las palabras clave de la cuenta, así que limita cada lectura al nivel que necesites. | Filtro | Aceptado por | |---|---| | `--campaign-group` | `campaigns`, `ad-groups`, `keywords`, `negative-keywords`, `search-terms`, `ads`, `creatives`, `product-pages` | | `--app` | `campaigns`, `ad-groups`, `keywords`, `negative-keywords`, `search-terms`, `creatives`, `product-pages` | | `--campaign` | `ad-groups`, `keywords`, `negative-keywords`, `search-terms`, `ads` | | `--ad-group` | `keywords`, `negative-keywords`, `search-terms`, `ads` | | `--status` | `campaigns`, `ad-groups`, `ads` (`ENABLED` o `PAUSED`), `keywords` (`ACTIVE` o `PAUSED`) | | `--search` | `campaigns`, `ad-groups`, `keywords`, `negative-keywords`, `search-terms`, `ads`. Coincidencia de subcadena sin distinción entre mayúsculas y minúsculas en el nombre | `adapty asa apps list`, `orgs list`, `automations list` y `automations runs` no admiten filtros. Solo aceptan flags de paginación. `--campaign-group`, `--app`, `--campaign` y `--ad-group` reciben los UUIDs que imprime el comando `list` correspondiente, y cada uno es repetible: ```bash adapty asa keywords list --ad-group <ad-group-id> --ad-group <other-ad-group-id> ``` Un ID que pertenece a otra empresa no devuelve ningún resultado, por lo que la página aparece vacía en lugar de mostrar un error. ## Cuenta \{#account\} ### adapty asa whoami Muestra la empresa, cómo se concedió el acceso a Ads Manager y si Apple Ads está conectado. ```bash adapty asa whoami ``` Ejecuta este comando primero. Informa de si se cumplen los dos requisitos previos para todos los demás comandos. ### adapty asa connect Vincula una cuenta de Apple Ads a Adapty. ```bash adapty asa connect ``` El comando imprime un enlace de autorización de Apple y espera hasta que Apple notifique que la cuenta está conectada. | Flag | Por defecto | Descripción | |---|---|---| | `--wait` / `--no-wait` | `--wait` | Espera hasta que Apple Ads notifique que está conectado. `--no-wait` devuelve el control inmediatamente | | `--timeout` | `300` | Segundos de espera para el paso del navegador | ### adapty asa orgs list Lista los grupos de campañas (organizaciones de Apple Ads) disponibles para tu empresa. ```bash adapty asa orgs list ``` Cada fila contiene dos identificadores que no son intercambiables: | Campo | Se usa como | |---|---| | `internal_id` | El UUID que `--org` acepta en [`campaigns create`](#adapty-asa-campaigns-create), y que `--campaign-group` acepta como [filtro de lista](#list-filters) | | `org_id` | El ID numérico de organización de Apple, que acepta `--org-id` en [`campaigns bulk-create`](#adapty-asa-campaigns-bulk-create). `--org` y `--campaign-group` lo rechazan | `--org` solo existe en `campaigns create`. Ningún comando de lista lo acepta. Cada fila también incluye un `payment_model`. Cuando es `LOC`, la organización factura por línea de crédito, y cada campaña en ella necesita los [indicadores `--invoice-*`](#adapty-asa-campaigns-create). Acepta [indicadores de paginación](#global-flags). ### adapty asa apps list Lista las apps promocionadas en Apple Ads. ```bash adapty asa apps list ``` Cada fila contiene dos identificadores que no son intercambiables: | Campo | Se usa como | |---|---| | `internal_id` | El UUID que `--app` acepta como [filtro de lista](#list-filters) | | `adam_id` | El ID numérico de App Store de Apple, que usa `--adam-id` en [`campaigns create`](#adapty-asa-campaigns-create) y [`product-pages sync`](#adapty-asa-product-pages-sync) | Acepta [flags de paginación](#global-flags). ## Campañas \{#campaigns\} ### adapty asa campaigns list Lista [campañas](ads-manager-create-campaign). Devuelve solo metadatos — consulta los datos de rendimiento con [`asa metrics`](#adapty-asa-metrics). ```bash adapty asa campaigns list --app <app-id> --status PAUSED ``` Acepta [indicadores de paginación](#global-flags) y los [filtros de lista](#list-filters) `--campaign-group`, `--app`, `--search` y `--status`. ### adapty asa campaigns get Obtén detalles de una campaña específica. ```bash adapty asa campaigns get <campaign-id> ``` | Argument | Description | |---|---| | `campaign-id` | Campaign ID (UUID) | ### adapty asa campaigns create Crea una campaña. ```bash adapty asa campaigns create --org <campaign-group-id> --name "Winter push" --adam-id 123456 --country US --daily-budget 50 ``` | Indicador | Obligatorio | Descripción | |---|---|---| | `--org` | Sí | ID del grupo de campañas (UUID). Ver [`orgs list`](#adapty-asa-orgs-list) | | `--name` | Sí | Nombre de la campaña | | `--adam-id` | Sí | ID de la app en el App Store (`adam_id`) | | `--country` | Sí | Código de país o región. Repite para varios: `--country US --country CA` | | `--daily-budget` | Sí | Presupuesto diario como cantidad simple, por ejemplo `50` o `12.50` | | `--budget` | No | Presupuesto total | | `--target-cpa` | No | Coste objetivo por adquisición. Debe ser inferior a `--daily-budget` | | `--currency` | No | Código de moneda para los importes de esta llamada. Valor predeterminado: `USD` | | `--bidding-strategy` | No | `MANUAL_CPT` o `MAX_CONVERSIONS`. Apple usa `MANUAL_CPT` por defecto | | `--ad-channel-type` | No | `SEARCH` o `DISPLAY`. Valor predeterminado: `SEARCH` | | `--billing-event` | No | `TAPS` o `IMPRESSIONS`. Valor predeterminado: `TAPS` | | `--supply-source` | No | Fuente de suministro, repetible. Valor predeterminado: `APPSTORE_SEARCH_RESULTS` | | `--status` | No | Estado inicial: `ENABLED` o `PAUSED` | | `--invoice-advertiser` | Línea de crédito | Opciones de facturación: nombre del anunciante | | `--invoice-order-number` | Línea de crédito | Opciones de facturación: número de pedido | | `--invoice-contact-name` | Línea de crédito | Opciones de facturación: nombre del contacto de compra | | `--invoice-contact-email` | Línea de crédito | Opciones de facturación: email del contacto de compra | | `--invoice-billing-email` | Línea de crédito | Opciones de facturación: email del contacto de facturación | Una organización cuyo `payment_model` es `LOC` factura mediante línea de crédito, y Apple requiere opciones de facturación en cada una de sus campañas. Pasa los cinco flags `--invoice-*` en una sola llamada — un conjunto parcial se rechaza antes de enviar la solicitud. Consulta [Configurar opciones de facturación para una línea de crédito](developer-cli-ads-manager#invoicing-options). La respuesta incluye `serving_status` y `serving_state_reasons`. Es posible crear una campaña y que aún así no sirva anuncios: una campaña de tipo `MAX_CONVERSIONS` espera a que se cree un [grupo de anuncios automatizado](#adapty-asa-ad-groups-create), y una campaña con línea de crédito espera a que se configuren sus Opciones de Facturación. En ambos casos, el comando muestra el motivo y el comando que lo resuelve. ### adapty asa campaigns update Actualiza una campaña existente. ```bash adapty asa campaigns update <campaign-id> --daily-budget 80 --status PAUSED ``` | Argumento | Descripción | |---|---| | `campaign-id` | ID de campaña (UUID) | | Flag | Descripción | |---|---| | `--name` | Nuevo nombre de campaña | | `--status` | `ENABLED` o `PAUSED` | | `--country` | Reemplaza la lista de países. Repite para varios | | `--daily-budget` | Nuevo presupuesto diario | | `--budget` | Nuevo presupuesto total | | `--target-cpa` | Nuevo coste objetivo por adquisición | | `--bidding-strategy` | `MANUAL_CPT` o `MAX_CONVERSIONS` | | `--currency` | Código de moneda para los importes de esta llamada. Por defecto: `USD` | | `--invoice-advertiser` | Opciones de facturación: nombre del anunciante | | `--invoice-order-number` | Opciones de facturación: número de pedido | | `--invoice-contact-name` | Opciones de facturación: nombre del contacto del comprador | | `--invoice-contact-email` | Opciones de facturación: email del contacto del comprador | | `--invoice-billing-email` | Opciones de facturación: email del contacto de facturación | Al menos un flag es obligatorio. Los cinco flags `--invoice-*` van juntos y reemplazan las Opciones de Facturación almacenadas como un conjunto, así que pasa los cinco aunque solo quieras cambiar uno. Un conjunto parcial se rechaza antes de enviar la solicitud. ### adapty asa campaigns bulk-create Crea toda una estructura de campaña — campañas con grupos de anuncios anidados, palabras clave, palabras clave negativas y anuncios — en una sola operación. ```bash adapty asa campaigns bulk-create --file structure.json ``` Convierte una plantilla bulk nativa de Apple Ads en lugar de escribir el JSON: ```bash adapty asa campaigns bulk-create --from-file Campaign_And_Adgroup_Template.xlsx --org-id 1234567 ``` | Flag | Predeterminado | Descripción | |---|---|---| | `--file` | — | Archivo JSON con la estructura de la campaña, o `-` para leer desde la entrada estándar | | `--from-file` | — | Plantilla en bloque de Apple Ads para convertir en el servidor: [`Campaign_And_Adgroup_Template.xlsx`](https://ads.apple.com/app-store/help/campaigns/0089-make-edits-in-bulk) o un [`.csv` de palabras clave](https://ads.apple.com/app-store/help/keywords/0014-add-and-manage-keywords) | | `--org-id` | — | ID numérico de organización de Apple (`org_id`). Ver [`orgs list`](#adapty-asa-orgs-list). Obligatorio con `--from-file` | | `--preview` | — | Con `--from-file`: muestra la estructura convertida y se detiene. No se crea nada | | `--wait` / `--no-wait` | `--wait` | Consulta el estado de la operación hasta que finaliza. `--no-wait` imprime el ID de la operación y termina | | `--poll-interval` | `5` | Segundos entre consultas de progreso | | `--timeout` | `900` | Segundos máximos para esperar a que finalice la operación | Exactamente uno de `--file` y `--from-file` es obligatorio. La estructura nombra el grupo de campaña en el nivel superior y anida todo lo demás: ```json title="structure.json" { "campaign_group_id": 555777, "campaigns": [ { "payload": { "name": "US Search", "adam_id": 123456, "countries_or_regions": ["US"], "daily_budget_amount": { "amount": "100", "currency": "USD" }, "status": "ENABLED", "ad_channel_type": "SEARCH", "billing_event": "TAPS", "supply_sources": ["APPSTORE_SEARCH_RESULTS"] }, "ad_groups": [ { "payload": { "name": "Brand", "start_time": "2026-08-01T00:00:00Z", "status": "ENABLED" }, "keywords": [{ "text": "meditation app", "match_type": "BROAD" }] } ] } ] } ``` `campaign_group_id` acepta el ID numérico de la organización de Apple; usa `campaign_group_internal_id` para pasar el UUID en su lugar. Un nodo con `payload` crea una entidad nueva. Un nodo que referencia una entidad existente por su `id` es un ancla — úsalo para añadir grupos de anuncios a una campaña existente, y añade `update_payload` para modificar la entidad en la misma operación. Cada campaña anida `ad_groups`, y cada grupo de anuncios anida `keywords`, `negative_keywords` y `ads`. Una campaña también acepta `negative_keywords` directamente para las palabras clave a nivel de campaña. Toda la estructura se valida antes de crear nada, y si hay un rechazo se listan todos los nodos inválidos. Con `--from-file`, los problemas de conversión se notifican con su hoja, fila y columna; los avisos de conversión no detienen el envío, pero los errores de conversión lo abortan. Antes de enviar, el comando informa de cuántas campañas, grupos de anuncios, palabras clave, palabras clave negativas y anuncios contiene la estructura, y pide confirmación. A continuación, el comando sondea la operación e imprime el progreso hasta que se alcanza uno de los tres estados finales: | Estado | Significado | |---|---| | `success` | Todos los objetos fueron creados | | `partial` | Algunos objetos no se crearon. Cada error se muestra con el error de Apple | | `failed` | La operación falló y el comando termina con código distinto de cero | Si la operación no ha finalizado tras `--timeout` segundos, el comando devuelve e imprime la llamada [`bulk-status`](#adapty-asa-campaigns-bulk-status) para ejecutar más tarde. ### adapty asa campaigns bulk-status Muestra el progreso de una operación masiva: el estado, el recuento de objetos y el registro por objeto. ```bash adapty asa campaigns bulk-status <operation-id> ``` | Argumento | Descripción | |---|---| | `operation-id` | ID de operación (UUID) que imprime [`campaigns bulk-create`](#adapty-asa-campaigns-bulk-create) | El campo `status` puede ser `pending`, `running`, `success`, `partial` o `failed`. El bloque `counts` indica cuántos objetos se han aplicado, fallado y están pendientes del total. Acepta [flags de paginación](#global-flags). ## Grupos de anuncios \{#ad-groups\} ### adapty asa ad-groups list Lista los [grupos de anuncios](ads-manager-create-ad-group). Devuelve solo metadatos — consulta el rendimiento con [`asa metrics`](#adapty-asa-metrics). ```bash adapty asa ad-groups list --campaign <campaign-id> ``` Acepta [flags de paginación](#global-flags) y los [filtros de lista](#list-filters) `--campaign-group`, `--app`, `--campaign`, `--search` y `--status`. ### adapty asa ad-groups get Obtén los detalles de un grupo de anuncios específico. ```bash adapty asa ad-groups get <ad-group-id> ``` | Argument | Description | |---|---| | `ad-group-id` | ID del grupo de anuncios (UUID) | ### adapty asa ad-groups create Crea un grupo de anuncios en una campaña. ```bash adapty asa ad-groups create --campaign <campaign-id> --name "Brand terms" --default-bid 1.20 ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--campaign` | Sí | ID de campaña (UUID) | | `--name` | Sí | Nombre del grupo de anuncios | | `--default-bid` | Salvo `--automated` | Puja predeterminada como importe simple, por ejemplo `1.20` | | `--cpa-goal` | No | Objetivo de coste por adquisición | | `--pricing-model` | No | `CPC` o `CPM`. Apple exige uno en cada grupo de anuncios. Valor predeterminado: `CPC` | | `--start-time` | No | Inicio de programación (`YYYY-MM-DD`). Por defecto, hoy | | `--end-time` | No | Fin de programación (`YYYY-MM-DD`) | | `--automated-keywords` / `--no-automated-keywords` | No | Permite que Apple añada palabras clave automáticamente | | `--automated` | No | Crea el grupo de anuncios automatizado que necesita una campaña `MAX_CONVERSIONS` | | `--currency` | No | Código de divisa para los importes de esta llamada. Valor predeterminado: `USD` | | `--status` | No | Estado inicial: `ENABLED` o `PAUSED` | `--automated` inscribe al grupo de anuncios en palabras clave automatizadas, por lo que no puede combinarse con `--automated-keywords`. Apple programa el grupo de anuncios automatizado por su cuenta y lo mantiene activo, por lo que `--start-time` y `--status PAUSED` no están permitidos — pausa la campaña en su lugar. Consulta [Crear una campaña de Conversiones Máximas](developer-cli-ads-manager#max-conversions). ### adapty asa ad-groups update Actualiza un grupo de anuncios existente. ```bash adapty asa ad-groups update <ad-group-id> --default-bid 1.50 --status PAUSED ``` | Argumento | Descripción | |---|---| | `ad-group-id` | ID del grupo de anuncios (UUID) | | Flag | Descripción | |---|---| | `--name` | Nuevo nombre del grupo de anuncios | | `--status` | `ENABLED` o `PAUSED` | | `--default-bid` | Nueva puja predeterminada | | `--cpa-goal` | Nuevo objetivo de coste por adquisición | | `--start-time` | Inicio de la programación (`YYYY-MM-DD`) | | `--end-time` | Fin de la programación (`YYYY-MM-DD`) | | `--automated-keywords` / `--no-automated-keywords` | Permitir que Apple añada palabras clave automáticamente | | `--currency` | Código de divisa para los importes de esta llamada. Por defecto: `USD` | Se requiere al menos un flag. La campaña padre se resuelve en el servidor y nunca se pasa directamente. ## Palabras clave \{#keywords\} Los comandos de palabras clave se aplican en lotes de un máximo de 100 elementos por llamada. Consulta [Gestionar palabras clave](ads-manager-manage-keywords) para conocer el equivalente en el dashboard. ### adapty asa keywords list Lista las palabras clave de targeting. Devuelve solo metadatos — consulta las métricas de rendimiento con [`asa metrics`](#adapty-asa-metrics). ```bash adapty asa keywords list --ad-group <ad-group-id> --status ACTIVE ``` Acepta [flags de paginación](#global-flags) y los [filtros de lista](#list-filters) `--campaign-group`, `--app`, `--campaign`, `--ad-group`, `--search` y `--status`. Filtra por `--ad-group` — sin filtro, esta es la lectura más amplia del tema. ### adapty asa keywords recommend Obtén un banco de palabras clave listo para usar para una de tus aplicaciones. Estos son los mismos bancos que utiliza Ads Manager cuando configuras una campaña en el dashboard. ```bash adapty asa keywords recommend --adam-id <adam-id> --type generic --country US ``` | Flag | Requerido | Descripción | |---|---|---| | `--adam-id` | Sí | ID de tu app en el App Store de Apple (`adam_id`), obtenido desde [`asa apps list`](#adapty-asa-apps-list) | | `--type` | Sí | `brand`, `generic` o `competitor` | | `--country` | No | Código de país ISO de dos letras. Repite para añadir varios. Por defecto: todos los países del banco | El flag `--type` selecciona el pool: | Tipo | Palabras clave | |---|---| | `brand` | Los términos de marca de tu app, sus variantes ortográficas y los términos orgánicos que contienen la marca. El resultado también incluye los propios términos de marca | | `generic` | Términos no relacionados con ninguna marca por los que tu app y sus principales competidores orgánicos aparecen en resultados, con todos los términos de marca conocidos eliminados. `relevance_tier: top_organic` marca los términos donde tu app ya aparece en el top 10 orgánico | | `competitor` | Los términos de marca de cada competidor seleccionado para la app en [AI Growth Advisor](autopilot-analysis#select-competitors), un grupo por competidor. Está vacío hasta que selecciones competidores | Cada keyword incluye su país, popularidad, puntuación y rango orgánico medio. Las keywords no tienen pujas ni tipos de concordancia. Elige los que consideres oportunos y luego añade las keywords con [`asa keywords add`](#adapty-asa-keywords-add). Comprueba el `status` del pool antes de usar sus keywords: | Estado | Significado | |---|---| | `ready` | El pool está listo | | `building` | El pool se está construyendo. Vuelve a intentarlo en un minuto | | `empty` | El pool se ha construido y no contiene keywords | | `failed` | La construcción falló. Inténtalo más tarde | La primera llamada de `brand` o `generic` para una app construye el pool durante la solicitud y puede tardar decenas de segundos. El comando tiene un límite de 10 llamadas por minuto por empresa, y solo se ejecuta una construcción de `brand` o `generic` a la vez por empresa. Una segunda construcción durante ese tiempo devuelve `429 cli_analytics_busy` con `Retry-After: 5`. ### adapty asa keywords add Añade palabras clave de segmentación a un grupo de anuncios. ```bash adapty asa keywords add --ad-group <ad-group-id> --text "running shoes" --text "trail shoes" --bid 1.20 ``` Lee las palabras clave desde un archivo, una por línea: ```bash adapty asa keywords add --ad-group <ad-group-id> --from-file keywords.txt ``` | Indicador | Obligatorio | Descripción | |---|---|---| | `--ad-group` | Sí | ID del grupo de anuncios (UUID). La campaña se resuelve a partir de él | | `--text` | Sí, a menos que se use `--from-file` | Texto de la palabra clave. Repite para varias | | `--from-file` | No | Archivo con una palabra clave por línea. Se combina con los valores `--text` indicados | | `--bid` | No | Puja por palabra clave como importe simple | | `--match-type` | No | `BROAD` o `EXACT`. Valor predeterminado: `BROAD` | | `--currency` | No | Código de moneda para los importes de esta llamada. Valor predeterminado: `USD` | | `--status` | No | `ACTIVE` o `PAUSED`. Valor predeterminado: `ACTIVE` | Un ID no válido hace fallar todo el lote antes de llamar a Apple. Apple puede rechazar palabras clave individuales, y cada rechazo se notifica con su motivo. ### adapty asa keywords update Cambia la puja, el estado, el texto o el tipo de coincidencia de una o varias palabras clave. ```bash adapty asa keywords update <keyword-id> <keyword-id> --bid 2.00 --status PAUSED ``` | Argumento | Descripción | |---|---| | `keyword-id` | ID de la palabra clave (UUID). Pasa varios como argumentos adicionales | | Flag | Descripción | |---|---| | `--bid` | Nueva puja | | `--status` | `ACTIVE` o `PAUSED` | | `--match-type` | `BROAD` o `EXACT` | | `--text` | Nuevo texto de la palabra clave. Solo tiene sentido para una única palabra clave | | `--currency` | Código de moneda para los importes de esta llamada. Por defecto: `USD` | Un único cambio se aplica a cada ID pasado. ## Palabras clave negativas \{#negative-keywords\} ### adapty asa negative-keywords list Lista las palabras clave negativas. Las filas con `ad_group_id` vacío corresponden al nivel de campaña. ```bash adapty asa negative-keywords list --campaign <campaign-id> ``` | Flag | Descripción | |---|---| | `--campaign-level-only` | Conserva solo las filas a nivel de campaña | Acepta [flags de paginación](#global-flags) y los [filtros de lista](#list-filters) `--campaign-group`, `--app`, `--campaign`, `--ad-group` y `--search`. ### adapty asa negative-keywords add Añade palabras clave negativas a un grupo de anuncios o a una campaña. ```bash adapty asa negative-keywords add --ad-group <ad-group-id> --text free ``` Aplícalas a todos los grupos de anuncios de una campaña en su lugar: ```bash adapty asa negative-keywords add --campaign <campaign-id> --all-ad-groups --text free ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--ad-group` | Uno de `--ad-group` o `--campaign` | ID del grupo de anuncios (UUID). La campaña se resuelve a partir de él | | `--campaign` | Uno de `--ad-group` o `--campaign` | ID de la campaña (UUID) | | `--text` | Sí | Texto de la palabra clave. Repite para varias | | `--all-ad-groups` | No | Aplica a todos los grupos de anuncios de la campaña en lugar de a la campaña en sí. Requiere `--campaign` | | `--match-type` | No | `BROAD` o `EXACT`. Por defecto: `EXACT` | | `--status` | No | `ACTIVE` o `PAUSED`. Por defecto: `ACTIVE` | `--ad-group` y `--campaign` son mutuamente excluyentes. Pasa exactamente uno de ellos. ## Términos de búsqueda \{#search-terms\} ### adapty asa search-terms list Lista los términos de búsqueda que activaron tus anuncios. Úsala para encontrar nuevas palabras clave y nuevas palabras clave negativas. ```bash adapty asa search-terms list --ad-group <ad-group-id> --date-from 2026-07-01 --date-to 2026-07-31 ``` | Flag | Por defecto | Descripción | |---|---|---| | `--date-from` | Hoy | Inicio del período de informe (`YYYY-MM-DD`) | | `--date-to` | Hoy | Fin del período de informe (`YYYY-MM-DD`) | Acepta [flags de paginación](#global-flags) y los [filtros de lista](#list-filters) `--campaign-group`, `--app`, `--campaign`, `--ad-group` y `--search`. Además de sus métricas de rendimiento, cada fila de término de búsqueda incluye los insights de Apple para ese término: | Campo | Valor | |---|---| | `impression_share_all`, `impression_share_first` | Cuota de impresiones en cualquier posición de anuncio, o en la primera posición | | `impression_midpoint_all`, `impression_midpoint_first` | Cuota de impresiones como un único porcentaje | | `rank_all`, `rank_first` | La posición de tu app frente a otras apps para el término, en cualquier posición de anuncio o en la primera | | `search_popularity` | Popularidad de búsqueda de 1 a 5 | Cada campo es `null` cuando Apple no tiene datos para el término. Consulta [Insights](adapty-ads-manager-metrics#insights) para ver cómo Apple define estos valores. Este comando comparte un pool de análisis con [`asa metrics`](#metrics). Consulta [Errors](#errors). ## Anuncios \{#ads\} ### Lista de anuncios de Adapty ASA \{#adapty-asa-ads-list\} Lista [anuncios](ads-manager-manage-ads). El campo `serving_state_reasons` explica por qué un anuncio no está en ejecución. ```bash adapty asa ads list --ad-group <ad-group-id> ``` Acepta [flags de paginación](#global-flags) y los [filtros de lista](#list-filters) `--campaign-group`, `--campaign`, `--ad-group`, `--search` y `--status`. Esta lista no tiene el filtro `--app`, ya que los anuncios pertenecen a grupos de anuncios. ### adapty asa ads get Obtén los detalles de un anuncio específico. ```bash adapty asa ads get <ad-id> ``` | Argument | Description | |---|---| | `ad-id` | Ad ID (UUID) | ### adapty asa ads create Crea un anuncio en un grupo de anuncios. ```bash adapty asa ads create --ad-group <ad-group-id> --creative-id 4321 --name "Summer ad" ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--ad-group` | Sí | ID del grupo de anuncios (UUID). La campaña se obtiene a partir de él | | `--creative-id` | Sí | ID de creatividad de Apple. Véase [`creatives list`](#adapty-asa-creatives-list) | | `--name` | Sí | Nombre del anuncio | | `--status` | No | Estado inicial: `ENABLED` o `PAUSED` | ### adapty asa ads update Actualiza un anuncio existente. ```bash adapty asa ads update <ad-id> --status PAUSED ``` | Argumento | Descripción | |---|---| | `ad-id` | ID del anuncio (UUID) | | Flag | Descripción | |---|---| | `--name` | Nuevo nombre del anuncio | | `--status` | `ENABLED` o `PAUSED` | Se requiere al menos un flag. El creativo y el grupo de anuncios padre son fijos desde la creación. ## Creatives \{#creatives\} ### adapty asa creatives list Lista los creativos disponibles para nuevos anuncios. ```bash adapty asa creatives list --app <app-id> ``` El `creative_id` devuelto aquí es el valor `--creative-id` para [`ads create`](#adapty-asa-ads-create). Acepta [indicadores de paginación](#global-flags) y los filtros de lista `--campaign-group` y `--app` [list filters](#list-filters). ## Páginas de producto \{#product-pages\} ### adapty asa product-pages list Lista las páginas de producto personalizadas disponibles para tus apps. ```bash adapty asa product-pages list --app <app-id> ``` Acepta [flags de paginación](#global-flags) y los filtros de lista `--campaign-group` y `--app` [list filters](#list-filters). ### adapty asa product-pages sync Actualiza las páginas de producto personalizadas desde App Store Connect. ```bash adapty asa product-pages sync --adam-id 123456 ``` | Flag | Description | |---|---| | `--adam-id` | Limita la actualización a una sola app. Omítelo para cubrir todas las apps | La actualización se pone en cola en lugar de ejecutarse de inmediato, y el comando confirma con `Sync queued.` Si la misma actualización ya está en curso, muestra `Already running; nothing new was queued.` en su lugar. ## Segmentos \{#segments\} Estos comandos crean [segmentos](segments) de Adapty a partir de campañas, grupos de anuncios o palabras clave de Apple Ads, de la misma forma que **Create segment from…** en [Ads Manager](ads-manager-create-segments). Usa los segmentos como audiencias en placements y pruebas A/B para mostrar a los usuarios de una campaña específica un paywall diferente. Cada segmento tiene un filtro de atribución sobre los Apple IDs de las entidades que elijas. El CLI busca esos IDs, por lo que debes pasar los UUIDs que muestran los comandos `list`. A diferencia de otros comandos `adapty asa`, estos aceptan `--app`: el ID de la aplicación de Adapty a la que pertenece el segmento, obtenido de [`adapty apps list`](developer-cli-apps#adapty-apps-list). No aceptan `--idempotency-key`. Para segmentos con otros filtros, como país o fecha de instalación, usa [`adapty segments create`](developer-cli-placements#adapty-segments-create). ### adapty asa segments create Crea un segmento a partir de campañas, grupos de anuncios o palabras clave. ```bash adapty asa segments create --app <app-id> --campaign <campaign-id> adapty asa segments create --app <app-id> --ad-group <ad-group-id> --keyword <keyword-id> --keyword <other-keyword-id> ``` | Flag | Required | Description | |---|---|---| | `--app` | Yes | ID de la app de Adapty (UUID) | | `--campaign` | Un tipo de entidad | ID de campaña (UUID). Repetible | | `--ad-group` | Un tipo de entidad | ID de grupo de anuncios (UUID). Repetible. Con `--keyword`, indica exactamente uno: el grupo de anuncios que contiene las palabras clave | | `--keyword` | Un tipo de entidad | ID de palabra clave (UUID). Repetible. Requiere un `--ad-group` | | `--title` | No | Título del segmento. Por defecto usa el formato de Ads Manager, por ejemplo `[ASA] Campaign: <name>` | | `--description` | No | Descripción del segmento. Por defecto usa el formato de Ads Manager | Construye cada segmento a partir de un solo tipo de entidad. Pasar `--campaign` y `--ad-group` a la vez, o `--campaign` con `--keyword`, falla con el código de salida `2` antes de enviar ninguna petición. Varias entidades del mismo tipo generan un único segmento que incluye a los usuarios de cualquiera de ellas. Conserva el título por defecto salvo que necesites otro. Ads Manager reconoce sus segmentos por el prefijo `[ASA]`, y [`asa segments list`](#adapty-asa-segments-list) solo muestra los segmentos con este prefijo. La respuesta contiene el segmento completo, incluyendo `id` y `filters`. `profile_count` comienza en `0`, y Adapty lo recalcula después de que se crea el segmento. ### adapty asa segments update Reemplaza las campañas, grupos de anuncios o palabras clave de un segmento existente. ```bash adapty asa segments update <segment-id> --app <app-id> --campaign <campaign-id> --campaign <other-campaign-id> ``` | Argumento | Descripción | |---|---| | `segment-id` | ID del segmento (UUID), obtenido desde `asa segments list` o `adapty segments list` | Acepta los mismos flags que [`asa segments create`](#adapty-asa-segments-create). El filtro del segmento se reemplaza con las entidades que indiques. El título y la descripción permanecen igual a menos que pases `--title` o `--description`. ### adapty asa segments list Lista los segmentos creados a partir de entidades de Apple Ads — aquellos cuyo título empieza por `[ASA]`. ```bash adapty asa segments list --app <app-id> --campaign <campaign-id> ``` | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | ID de la app de Adapty (UUID) | | `--campaign` | No | Muestra solo los segmentos creados a partir de esta campaña (UUID) | | `--ad-group` | No | Muestra solo los segmentos creados a partir de este grupo de anuncios (UUID) | Pasa `--campaign` o `--ad-group`, no ambos. Para evitar crear un segundo segmento para la misma campaña, ejecuta este comando antes de `asa segments create`: un resultado vacío significa que la campaña aún no tiene ningún segmento. El comando lee los primeros 100 segmentos de la app. ## Automatizaciones \{#automations\} La CLI almacena el JSON de reglas que le proporciones, con una excepción: los ajustes de una acción **Add as keyword** provienen de flags. Consulta [Automatizaciones](ads-manager-automations) para saber qué hace cada tipo de regla, y [Ejecutar reglas de automatización](developer-cli-ads-manager#run-automation-rules) para saber cómo generar un archivo de reglas. ### Marcadores de acción para añadir como keyword \{#add-keyword-flags\} `automations create` y `automations update` comparten un conjunto de marcadores que establecen `actions[0].params` para una acción `add-as-keyword-to` — **Add as keyword** en una [regla de términos de búsqueda](ads-manager-automations-search-terms), **Add as keyword to…** en una [regla de keyword](ads-manager-automations-keyword-rules). Al pasar cualquiera de ellos se reemplaza todo el objeto `params`, por lo que esta acción no necesita JSON escrito a mano. | Indicador | Campo JSON | Descripción | |---|---|---| | `--target-ad-group` | `targets.internal_ids` | Grupo de anuncios que recibe la palabra clave (UUID), repetible. Una regla sin grupo de anuncios de destino no hace nada | | `--match-type` | `match_type` | `BROAD` o `EXACT` | | `--cpt-bid-type` | `cpt_bid.type` | De dónde proviene la puja de la nueva palabra clave: `ad_group_default_bid`, `set_to`, `search_term_current_cpt` o `keyword_current_bid` | | `--cpt-bid` | `cpt_bid.value` | Con `set_to`, la puja en sí. Con `search_term_current_cpt` o `keyword_current_bid`, un incremento porcentual sobre la puja que se copia. No se acepta con `ad_group_default_bid` | | `--negate` | `negate` | También añade el término de búsqueda como palabra clave negativa, en su propio `ad-group` o en su propia `campaign`. `--no-negate` no deja ninguna. Solo para reglas de términos de búsqueda | | `--skip-enable-duplicates` | `skip_enable_duplicate_keywords` | Omite una palabra clave que ya existe en el grupo de anuncios de destino en lugar de habilitarla. Solo para reglas de términos de búsqueda | | `--pause-original` | `pause_in_original_ad_group` | Pausa la palabra clave de origen en su propio grupo de anuncios. Solo para reglas de palabras clave | `--cpt-bid-type` y `--match-type` no tienen valor por defecto, por lo que ambos son obligatorios cuando el archivo de reglas no contiene `params` propios. Cuatro de estos flags dependen del campo `operate_with` de la regla: `--negate`, `--no-negate` y `--skip-enable-duplicates` pertenecen a una regla `search-term`; `--pause-original`, a una regla `targeting-keyword`. Si se usa un flag del otro conjunto, el comando falla con el código de salida `2` y muestra la lista de flags aplicables. :::warning Toma los `params` de esta acción de los flags, nunca de una regla que tenga una acción diferente. La API resuelve los `params` por forma y no por un variante con nombre, por lo que una clave perteneciente a otra acción hace que la elija y descarte el resto, respondiendo con `200` y almacenando una regla que no añade palabras clave a ningún grupo de anuncios. ::: ### lista de automatizaciones de adapty asa \{#adapty-asa-automations-list\} Lista las [reglas de automatización](ads-manager-automations). El campo `status` vale `1` para activa y `0` para detenida. ```bash adapty asa automations list ``` Acepta [flags de paginación](#global-flags). ### adapty asa automations get Obtén una regla de automatización específica, incluyendo sus condiciones y acciones. ```bash adapty asa automations get <automation-id> ``` | Argument | Description | |---|---| | `automation-id` | Automation rule ID (UUID) | ### adapty asa automations create Crea una regla de automatización a partir de un archivo JSON. ```bash adapty asa automations create --file rule.json ``` | Flag | Descripción | |---|---| | `--file` | Archivo JSON con el cuerpo de la regla, o `-` para leer desde la entrada estándar | | `--run-now` | Pone en cola la primera ejecución justo después de guardar la regla | `--file` es obligatorio y el archivo debe contener exactamente una acción: la API mantiene una acción y una condición por regla. También acepta los [flags de acción add-as-keyword](#add-keyword-flags), que rellenan o sobreescriben `actions[0].params`: ```bash adapty asa automations create --file rule.json --target-ad-group <ad-group-id> --match-type EXACT --cpt-bid-type search_term_current_cpt --negate ad-group ``` ### adapty asa automations update Modifica una regla de automatización: detenla, renómbrala o reemplaza partes de la regla. ```bash adapty asa automations update <automation-id> --stop ``` | Argumento | Descripción | |---|---| | `automation-id` | ID de la regla de automatización (UUID) | | Flag | Descripción | |---|---| | `--start` | Activa la regla | | `--stop` | Detiene la regla y borra su próxima ejecución | | `--name` | Nuevo nombre de la regla | | `--file` | Archivo JSON con las partes a modificar, o `-` para leer desde la entrada estándar | `--start` y `--stop` son mutuamente excluyentes. Un archivo pasado aquí no debe contener `internal_id`. También acepta los [indicadores de acción add-as-keyword](#add-keyword-flags). Si se pasa alguno, la llamada se convierte en una operación de lectura-modificación-escritura: la CLI lee la regla, reconstruye `actions[0].params` y reenvía toda la lista `actions`, ya que la API reemplaza `actions` en lugar de fusionarse con ella. Cualquier edición realizada en el dashboard entre la lectura y la escritura se sobreescribirá. An update with an action flag also repairs a rule whose stored `params` carry the shape of another action. The params are rebuilt from scratch, so only the stored keys that fit `add-as-keyword-to` are kept and everything else has to come from a flag. ### adapty asa automations run Ejecuta una regla de automatización una vez, fuera de su programación. ```bash adapty asa automations run <automation-id> --dry-run ``` | Argumento | Descripción | |---|---| | `automation-id` | ID de la regla de automatización (UUID) | | Flag | Descripción | |---|---| | `--dry-run` | Evalúa la regla y registra el resultado sin realizar ningún cambio en Apple Ads | La ejecución se pone en cola y el comando muestra un ID de ejecución. Consulta el resultado con [`automations runs`](#adapty-asa-automations-runs). ### adapty asa automations runs Lista las ejecuciones pasadas de una regla de automatización, incluidas las ejecuciones de prueba. ```bash adapty asa automations runs <automation-id> ``` | Argumento | Descripción | |---|---| | `automation-id` | ID de la regla de automatización (UUID) | Acepta [indicadores de paginación](#global-flags). ## Métricas \{#metrics\} ### adapty asa metrics Consulta métricas para cualquier nivel de la cuenta en un rango de fechas. ```bash adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 ``` | Indicador | Requerido | Descripción | |---|---|---| | `--entity` | Sí | Sobre qué reportar: `campaign`, `ad-group`, `keyword` o `ad` | | `--date-from` | Sí | Inicio del período (`YYYY-MM-DD`) | | `--date-to` | Sí | Fin del período (`YYYY-MM-DD`) | | `--metric` | Sí | Nombre de la métrica, repetible | | `--app` | No | Reportar solo sobre estas apps. Repetible | | `--campaign` | No | Reportar solo sobre estas campañas. Repetible | | `--ad-group` | No | Reportar solo sobre estos grupos de anuncios. Repetible | | `--group-by` | No | Desglosar las filas por `country`, `day`, `week`, `month`, `quarter` o `year`. Repetible | | `--by-days` | No | Ventana de renovación en días para métricas de cohorte, repetible. Máximo 16 por llamada. Omitir para usar los valores predeterminados del dashboard | | `--order-by` | No | Métrica o campo por el que ordenar | | `--order-by-day` | No | Clasificar por una métrica de cohorte en esta ventana de renovación. Debe ser uno de los valores de `--by-days` | | `--order` | No | `asc` o `desc`. Valor predeterminado: `desc` | Acepta [opciones de paginación](#global-flags). De los [filtros de lista](#list-filters) solo admite `--app`, `--campaign` y `--ad-group`; no hay `--search` ni `--status`. Para acotar el informe por cualquier otro criterio, filtra las filas comparándolas con los IDs obtenidos de un comando `list` con el ámbito correspondiente. Cada fila es una entidad, agregada en el servidor y ordenada por `--order-by`. Una consulta de los N primeros resultados es, por tanto, una sola llamada: establece `--order-by` y `--page-size N` en lugar de paginar los resultados y sumarlos manualmente: ```bash adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --order-by spend --page-size 5 ``` `--metric` acepta los nombres de las métricas que Ads Manager registra, en la nomenclatura del dashboard — por ejemplo `spend`, `taps` o `gross_roas`. Consulta [Métricas](adapty-ads-manager-metrics) para ver la lista completa y cómo se calcula cada una. Si el nombre no existe, el comando falla y muestra los nombres válidos en el error. Con `--entity keyword`, `--metric` también acepta las métricas exclusivas de palabras clave. Apple reporta la cuota de impresiones y el ranking tanto para cualquier posición de anuncio como para la primera posición, por lo que cada una tiene dos versiones: `_all` y `_first`. Consulta [Insights](adapty-ads-manager-metrics#insights) para ver cómo las define Apple. | Métrica | Valor | |---|---| | `impression_midpoint_all`, `impression_midpoint_first` | Cuota de impresiones durante el período de reporte, en porcentaje | | `rank_all`, `rank_first` | Posición de tu app frente a otras apps para la palabra clave. El valor más reciente, independientemente de `--date-from` y `--date-to` | | `search_popularity` | Popularidad de búsqueda del 1 al 5 | Una palabra clave sin datos de reporte devuelve `0` para estas métricas. Desde CLI 0.9.2, estos nombres reemplazan a `rank` e `impression_midpoint`. Cada métrica que indiques se calcula sobre todo el nivel de entidad antes de paginar, así que solicita solo las columnas que vayas a leer. `subscribers` y `paid_subscribers` cuentan perfiles únicos por entidad, lo que cuesta alrededor de diecisiete veces más que una llamada simple de gasto e instalaciones; lo mismo ocurre con `arppu` y `arpas`, que se derivan de ellos. Para cualquier `--entity`, esos cuatro nombres necesitan `--campaign` o `--ad-group` para acotar la llamada: ```bash adapty asa metrics --entity keyword --date-from 2026-07-01 --date-to 2026-07-31 --metric arpas --campaign <campaign-id> ``` Sin uno de esos dos filtros, la llamada falla con `422 cli_metric_scope_too_wide`. Acotar el alcance es la forma más barata de acelerar cualquier llamada de métricas, porque el coste depende del número de entidades agregadas, no del tamaño de la página. La longitud del período de reporte está limitada por el valor más fino de `--group-by`. Para reportar un período más largo, agrupa de forma más gruesa en lugar de dividir la solicitud en varias llamadas: | Valor más fino de `--group-by` | Período máximo | |---|---| | `day` | 28 días | | Sin agrupación por período | 90 días | | `week` | 180 días | | `month` y más grueso | 365 días | El grano `day` cubre como máximo cuatro semanas. Para períodos más largos, usa el grano `week`, que permite abarcar medio año en una sola llamada. Cada página tiene un límite de 5000 filas de desglose, donde una fila equivale a una entidad × país × período. Si una página supera ese límite, la llamada falla con `422 cli_response_too_large`. Reduce la agrupación, acota el rango de fechas o baja el valor de `--page-size`; cuando la llamada agrupa por `day`, el error indica el tamaño de página que cabe, así que reintenta con ese número en lugar de calcularlo tú mismo. `metrics overview` no devuelve desglose por entidad, por lo que este límite nunca le aplica. No existe la métrica `ltv`. El valor del ciclo de vida es una métrica de cohorte que se lee en una ventana de renovación, por lo que `--by-days` es la forma de solicitar el valor del día 7 o del día 90: ```bash adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --metric roas --by-days 7 --by-days 90 ``` `--order-by-day` ordena las filas por una de esas ventanas, lo que devuelve las mejores campañas por ROAS del día 90 en una sola llamada. ### adapty asa metrics overview Consulta totales de un período, agrupados por una unidad de tiempo. ```bash adapty asa metrics overview --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --period-unit week ``` | Indicador | Requerido | Descripción | |---|---|---| | `--entity` | Sí | Sobre qué reportar: `campaign`, `ad-group`, `keyword` o `ad` | | `--date-from` | Sí | Inicio del período (`YYYY-MM-DD`) | | `--date-to` | Sí | Fin del período (`YYYY-MM-DD`) | | `--period-unit` | No | Tamaño del intervalo: `day`, `week`, `month`, `quarter` o `year`. Por defecto: `day` | | `--metric` | No | Nombre de la métrica, repetible. Omitir para todas las métricas | | `--by-days` | No | Ventana de renovación en días para métricas de cohorte, repetible. Máximo 16 por llamada | Este comando devuelve los totales del nivel de entidad completo junto con una serie por período, de modo que responde "cuánto gasté o gané en total" en una sola llamada. No tiene indicadores de ordenación ni paginación. En este comando, `--metric` acepta únicamente raíces de cohorte — `revenue`, `roas` y `arpu` — y no sus variantes `gross_`, `proceeds_` o `net_`. Tampoco acepta las [métricas exclusivas de palabras clave](#adapty-asa-metrics). La duración del período de reporte está limitada por `--period-unit`: | `--period-unit` | Período máximo | |---|---| | `day` | 90 días | | `week` | 180 días | | `month` y unidades mayores | 365 días | ## Competidores \{#competitors\} ### adapty asa competitors summary Resume las palabras clave de Apple Ads en las que pujan un conjunto de aplicaciones de App Store. Devuelve los mismos datos de competidores que [Market Intelligence](ads-manager-market-intelligence) en el dashboard. ```bash adapty asa competitors summary --app-ids 1668337467,6503873027 ``` | Flag | Obligatorio | Descripción | |---|---|---| | `--app-ids` | Sí | IDs de Apple App Store (`adam_id`), separados por comas. Entre 1 y 5 valores | El período de análisis y el conjunto de países están fijados en el servidor: el último mes completo, para todos los países. Este comando no tiene parámetros de período, país ni paginación. El comando imprime tres bloques: los totales del análisis, las apps con mejor rendimiento y los términos más disputados. Añade `--json` para obtener el resultado completo, que también desglosa los términos de cada app por país. La primera llamada para un conjunto de apps puede tardar decenas de segundos mientras se preparan los datos. Las llamadas posteriores para las mismas apps son más rápidas. ## Historial de cambios \{#change-history\} El historial de cambios se obtiene en tiempo real desde la API de Apple Ads. Adapty no lo almacena, y Apple lo conserva durante 6 meses. Incluye todos los cambios realizados en la organización, ya sean hechos directamente en Apple Ads, por el soporte de Apple o mediante otras herramientas de API. ### adapty asa change-history list Lista los cambios realizados en una organización de Apple Ads, del más reciente al más antiguo. ```bash adapty asa change-history list --campaign-group <campaign-group-id> --date-from 2026-10-01 --entity-type Campaign --event-type UPDATE ``` | Indicador | Obligatorio | Descripción | |---|---|---| | `--campaign-group` | Sí | Organización a leer (UUID de [`orgs list`](#adapty-asa-orgs-list)). Una organización por llamada. Para leer varias, ejecuta el comando por cada una | | `--date-from` | No | Inicio del período (`YYYY-MM-DD`). Predeterminado: hace 7 días | | `--date-to` | No | Fin del período (`YYYY-MM-DD`). Predeterminado: hoy. El período puede abarcar como máximo 14 días | | `--entity-type` | No | `Campaign`, `AdGroup`, `Keyword`, `NegativeKeyword`, `Ad`, `Creative`, `LocationGroup`, `Org` o `AdAccount`. Repetible. Predeterminado: todos los tipos excepto `Org` y `AdAccount`. `Org` y `AdAccount` no pueden combinarse con los demás tipos | | `--event-type` | No | `CREATE`, `UPDATE` o `DELETE`. Repetible | | `--user-type` | No | Quién realizó el cambio: `CUSTOMER` (en Apple Ads), `CUSTOMER_API` (a través de la API de Apple Ads) o `APPLE_SUPPORT`. Repetible | | `--campaign`, `--ad-group`, `--entity-id` | No | IDs numéricos de Apple de la campaña, el grupo de anuncios o la entidad modificada. Repetible | | `--user` | No | ID de usuario de Apple de la persona que realizó el cambio. Repetible | | `--txn` | No | ID de transacción de Apple. Repetible | | `--fields` | No | Expande cada cambio en una fila por campo modificado, con sus valores anterior y nuevo | :::important A diferencia de los [filtros de lista](#list-filters) de otros comandos, `--campaign`, `--ad-group` y `--entity-id` aceptan los ID numéricos de Apple tal como aparecen en las filas del historial de cambios, no UUIDs. Del mismo modo, `modified_by` en cada fila es un ID de usuario de Apple, no un correo electrónico. ::: Cada fila es una transacción: quién la realizó (`modified_by`, `user_type`), cuándo (`event_time`) y las entidades que modificó. Cada entidad incluye un `detail_id` para [`change-history get`](#adapty-asa-change-history-get). Con `--fields`, el tamaño de página predeterminado es 10 y se rechaza un `--page-size` mayor. Una página que requiera más de 20 búsquedas fallará: reduce los filtros o elimina `--fields` y lee cambios individuales con `change-history get`. Acepta [flags de paginación](#global-flags). `--page` × `--page-size` no puede superar 1000. ### adapty asa change-history get Muestra los cambios a nivel de campo de una entidad en una transacción. ```bash adapty asa change-history get Campaign.444555666.TXN_ID --campaign-group <campaign-group-id> ``` | Argumento | Descripción | |---|---| | `detail-id` | `detail_id` de [`change-history list`](#adapty-asa-change-history-list), en el formato `EntityType.entityId.txnId` | | Flag | Requerido | Descripción | |---|---|---| | `--campaign-group` | Sí | Organización a la que pertenece el cambio. Tómalo de `campaign_group_id` en la fila del listado | La respuesta lista cada campo modificado con sus `old_values` y `new_values`, más los metadatos de la entidad en `entity_metadata`. Un `detail_id` del que Apple no tiene registro devuelve `404`. ## Errores \{#errors\} | Estado | Código | Significado | |---|---|---| | `402` | `ads_manager_subscription_required` | La empresa no tiene una suscripción activa de Ads Manager | | `403` | `cli_ad_account_unreadable` | `change-history` no puede leer la organización: el token de Apple Ads está revocado, la organización ya no está vinculada, o Apple denegó el acceso | | `404` | — | La entidad no existe, o pertenece a otra empresa | | `409` | `cli_idempotency_in_progress` | Una escritura con la misma clave de idempotencia sigue en curso | | `422` | `cli_idempotency_key_reuse` | La misma clave de idempotencia se usó con un cuerpo de solicitud diferente | | `422` | `cli_metric_scope_too_wide` | Se solicitó una métrica de conteo de perfiles sin `--campaign` ni `--ad-group` | | `422` | `cli_response_too_large` | Una página de `metrics` superaría las 5000 filas de desglose | | `429` | `cli_analytics_busy` | El pool de análisis está ocupado, o sigue ejecutándose otra construcción de pool de palabras clave para la empresa. El tiempo de espera está en la cabecera `Retry-After` | | `429` | `cli_cooldown_active` | Demasiadas solicitudes rechazadas han puesto el token en período de enfriamiento | | `429` | `cli_upstream_rate_limited` | Apple limitó la tasa de una solicitud `change-history`. El tiempo de espera de Apple está en la cabecera `Retry-After` | | `504` | `cli_read_timeout` | Una solicitud `change-history` tardó más de 50 segundos. Reduce el período o los filtros | Métricas y la lista de términos de búsqueda comparten un presupuesto de análisis por empresa: 5 llamadas por minuto y como máximo 2 en cualquier período de 10 segundos. `change-history` tiene su propio presupuesto: 30 llamadas por minuto, como máximo 5 en cualquier período de 10 segundos y una solicitud a la vez por empresa. Una ráfaga de 20 solicitudes rechazadas en 5 minutos activa un período de enfriamiento escalonado de 5 minutos, luego 30 minutos y después 3 horas. Reintentar durante la pausa no la extiende, pero la solución es corregir la solicitud fallida en lugar de repetirla. La CLI gestiona las esperas cortas automáticamente. Ante un `429` que no sea de enfriamiento, si `Retry-After` es de 60 segundos o menos, el comando espera ese tiempo y reintenta una vez, informando de la espera en el error estándar. --- # File: developer-cli-ads-manager-skill --- --- title: "Gestionar Apple Ads con una herramienta de codificación con IA" description: "Instala el plugin de Apple Ads para que Claude Code, Copilot CLI, Codex o Gemini CLI puedan gestionar tus campañas de Apple Ads a través de la CLI de Adapty." --- El [plugin de Apple Ads](https://github.com/adaptyteam/apple-ads-cli) permite que una herramienta de programación con IA gestione tu cuenta de Apple Ads a través de la [CLI de Adapty](developer-cli-ads-manager). Hazle una pregunta en lenguaje natural, y determinará qué comandos necesita para responderla, leerá primero la cuenta y propondrá los cambios antes de que lleguen a Apple. Una skill (skill) es un conjunto de instrucciones que tu herramienta de IA carga bajo demanda. El plugin incluye tres de ellas: un operador que gestiona tu cuenta, un auditor de solo lectura y un planificador de estrategia, además de playbooks para tareas recurrentes como revisiones semanales, análisis de ROAS por cohorte y reasignación de presupuesto, guías verticales con taxonomías de palabras clave específicas por categoría y economías de partida, y [comandos de barra](#slash-commands) que convierten los trabajos más habituales en una sola línea. En conjunto, cubren las partes de la interfaz de Ads Manager donde es más fácil cometer errores: qué identificador acepta cada parámetro, qué filtro admite cada lista, cuántas palabras clave caben en un lote y qué llamadas tienen coste. ## Qué gestiona \{#what-it-handles\} - **Rendimiento de lectura**: Elige la llamada exacta que responde una pregunta en lugar de paginar entre resultados. Sabe que una pregunta de top-N es una llamada ordenada y que contar entidades no necesita ninguna llamada a métricas. - **Cambios estructurales**: Crea y actualiza campañas, grupos de anuncios, anuncios y palabras clave en el orden correcto, leyendo la entidad padre antes de escribir la entidad hijo. - **Trabajo con palabras clave**: Añade y reajusta pujas de palabras clave en lotes, y transforma términos de búsqueda en palabras clave y palabras clave negativas. - **Automatizaciones**: Configura automatizaciones basadas en reglas y las ejecuta en modo de prueba antes de que afecten a las pujas reales. - **Investigación de competencia**: Obtiene las palabras clave por las que pujan las aplicaciones competidoras. - **Auditorías de cuenta**: La skill `apple-ads-audit` comprueba el estado de la cuenta, el estado de publicación, la estructura y las palabras clave duplicadas sin modificar nada. - **Planificación desde cero**: La skill `apple-ads-strategy` diseña una estructura de cuenta, una taxonomía de palabras clave y presupuestos iniciales a partir de una descripción de tu app, sin necesidad de cuenta de Apple Ads ni de CLI. ## Lo que no hará \{#what-it-will-not-do\} El plugin trata cada escritura como irreversible, porque lo es: la CLI no tiene función de eliminación y los cambios llegan a Apple en cuestión de segundos. - Lee antes de escribir, en lugar de adivinar un ID o un presupuesto. - Pide confirmación antes de cualquier comando que implique un gasto, y mantiene el propio prompt de confirmación de la CLI cuando eres tú quien ejecuta el comando. - Fija una clave de idempotencia en cada escritura encadenada, para que una nueva ejecución tras un resultado incierto no cree una segunda campaña. - Prefiere lotes pequeños, de modo que un rechazo parcial de Apple sea fácil de interpretar. :::note Las skills recomiendan y preparan comandos. Leer métricas y hacer simulaciones no tiene ningún coste, pero eres responsable de aprobar cualquier cambio en una cuenta publicitaria activa. ::: ## Instala el plugin \{#install-the-plugin\} El [plugin de Apple Ads](https://github.com/adaptyteam/apple-ads-cli) enseña a tu herramienta de codificación con IA a operar Apple Ads mediante los comandos `adapty asa`. Incluye tres skills: - **`apple-ads`**: El operador. Lee el rendimiento, cambia pujas y presupuestos, añade palabras clave y negativas, recopila términos de búsqueda, y lanza y pausa campañas — con confirmación antes de gastar dinero. - **`apple-ads-audit`**: Un auditor de solo lectura. Comprueba el estado de la cuenta, el estado de publicación, la estructura, la propiedad del tráfico y las palabras clave de concordancia exacta duplicadas, y nunca escribe. - **`apple-ads-strategy`**: Un planificador que no necesita cuenta, CLI ni suscripción. Convierte una descripción de tu app en una estructura de cuenta, una taxonomía de palabras clave, presupuestos iniciales y una lista de palabras clave negativas. **Herramientas compatibles**: Claude Code, la app de Claude (antes Claude Cowork), GitHub Copilot CLI, OpenAI Codex, Gemini CLI, y cualquier otra herramienta que sea compatible con skills. El operador y el auditor se apoyan en la CLI de Adapty, así que primero instala y autentícate en la CLI — consulta la [guía de inicio rápido](developer-cli-quickstart). Los comandos `adapty asa` se incluyen en **Adapty CLI 0.4.0 y versiones posteriores**; en versiones anteriores no existen en absoluto. Compruébalo con `adapty asa whoami`, que también te indica si hay una cuenta de Apple Ads conectada y si la empresa tiene una suscripción activa a Ads Manager. El skill de estrategia funciona sin nada de esto. La app de Claude ejecuta la CLI en un sandbox que bloquea todos los dominios que no estén en su lista de permitidos. Autoriza los dominios de Adapty antes de instalar el plugin — consulta [Configurar la CLI de Adapty en la app de Claude](developer-cli-cowork). Cada conversación en la app de Claude también comienza en una máquina limpia, por lo que la CLI y su sesión no se conservan entre conversaciones — el skill `adapty-cli-setup` del plugin instala y autentica la CLI de nuevo al inicio de cada una. Para instalar, elige el formato correspondiente a tu herramienta. La lista completa está en el [README del plugin](https://github.com/adaptyteam/apple-ads-cli). **Claude Code** ```bash claude plugin marketplace add adaptyteam/apple-ads-cli claude plugin install apple-ads@adapty ``` **OpenAI Codex** ```bash codex plugin marketplace add adaptyteam/apple-ads-cli codex plugin add apple-ads@adapty ``` Ambas rutas de plugin incluyen los comandos `/asa-*` junto con los skills — prompts prediseñados para tareas habituales, como `/asa-review` para el seguimiento semanal y `/asa-harvest` para convertir términos de búsqueda en palabras clave y negativas. Claude Code los expone como comandos de barra; Codex los carga como skills que puedes solicitar por nombre. **Cualquier otra herramienta** — usa la [CLI de skills](https://skills.sh) (ten en cuenta que los skills instalados de esta forma no se actualizan automáticamente): ```bash npx skills add adaptyteam/apple-ads-cli --all ``` `--all` instala los tres skills en todos los agentes que detecta. Para instalar un solo skill, usa `--skill`, por ejemplo `--skill apple-ads-strategy`. Tras la instalación, ejecuta un skill en tu proyecto — ya sea directamente o mediante uno de los comandos de barra: ```bash /apple-ads ``` Leer métricas y hacer simulaciones no tiene ningún coste. Cualquier otro comando llega a Apple en cuestión de segundos, gasta dinero real y no se puede deshacer — los skills piden confirmación antes de cada uno, y tú también deberías hacerlo. ## Plugin de benchmarks \{#benchmarks-plugin\} El mismo marketplace incluye un complemento opcional, [`apple-ads-benchmarks`](https://github.com/adaptyteam/apple-ads-benchmarks) — las referencias de Apple Ads publicadas por Adapty para apps de suscripción, empaquetadas como datos locales que tu herramienta de IA puede consultar: tasa de clics, tasa de conversión, CPT y CPA en 90 países, 22 géneros de App Store y 59 nichos, además de métricas económicas de suscriptores como el LTV del primer año. Funciona completamente sin conexión, por lo que no necesita cuenta de Apple Ads, ni CLI ni suscripción a Adapty. Úsalo para evaluar si un número es un problema antes de profundizar en la cuenta: "¿Es alto un CPT de 4 $ para una app de fitness en Alemania?" es ante todo una pregunta sobre referencias del sector. **Claude Code** ```bash claude plugin marketplace add adaptyteam/apple-ads-cli claude plugin install apple-ads-benchmarks@adapty ``` **Cualquier otra herramienta** ```bash npx skills add adaptyteam/apple-ads-benchmarks --all ``` :::note El CPA de referencia es coste por descarga, no coste por instalación, y el CR es tap-to-download, no trial-to-paid. Compara cada uno con tu métrica usando la misma definición. ::: ## Comandos de barra \{#slash-commands\} En Claude Code, el plugin también añade nueve comandos `/asa-*`. Cada uno es un prompt predefinido que selecciona la skill y el manual adecuados para una tarea común, de modo que escribes el comando en lugar de redactar el prompt tú mismo: | Comando | Qué hace | |---|---| | `/asa-review` | Revisión semanal: dirección, comparativa de instalaciones, valores atípicos y una acción recomendada | | `/asa-audit` | Auditoría de salud de cuenta o de estructura activa en modo solo lectura | | `/asa-bids` | Revisar y ajustar pujas por ROAS de cohorte | | `/asa-budget` | Redistribuir presupuesto entre campañas | | `/asa-harvest` | Extraer términos de búsqueda como palabras clave exactas y palabras clave negativas | | `/asa-negatives` | Analizar términos de búsqueda para encontrar las palabras clave negativas más precisas y seguras | | `/asa-opportunities` | Encontrar oportunidades de palabras clave de competidores desde Market Intelligence | | `/asa-cpp` | Enrutar una intención de palabra clave a una Custom Product Page y anuncio existentes | | `/asa-launch` | Planificar y lanzar una nueva campaña de Apple Search Ads | Slash commands install with the Claude Code plugin. In other tools, ask for the same jobs in plain language — the skills carry the same playbooks. ## Ejemplos de prompts \{#example-prompts\} Las skills ya incluyen los comandos, el orden en que se ejecutan y cuáles gastan dinero, así que el prompt no tiene que especificar nada de eso. Escribe el prompt para lo que una skill no puede deducir por sí sola: la métrica y el período con los que evalúas las campañas, el punto de referencia con el que las comparas y el formato de respuesta que esperas. ### Auditar una cuenta que ya gestionas \{#audit-an-account-you-already-run\} Úsalo cuando las campañas estén activas y quieras saber adónde va el dinero. Todos los comandos que necesita son de lectura, así que nada en tu cuenta cambia. ``` Audit my Apple Search Ads account and tell me what needs my attention. This is read-only — don't change anything. Judge campaigns on cohort net ROAS over the last 30 days against a 3:1 benchmark, since Adapty attributes real subscription revenue back to the campaign that drove it. Cover campaigns, keywords, the search terms I'm matching on, and what my active automations do. Serving status and the reasons a campaign isn't serving come back on the metrics rows, not from campaigns list. Then report back in plain language: - what's blocked and how to unblock it - net ROAS per campaign against the benchmark - the top 3 places I'm wasting money - the top 3 growth opportunities - the changes you'd make, each as the exact command with its current and new value, for me to approve one at a time I'm not an Apple Ads expert. Assume I'm smart but busy. ``` ### Lanza tus primeras campañas \{#launch-your-first-campaigns\} Úsalo cuando la cuenta de Apple Ads esté vacía. La skill del operador crea campañas pausadas por defecto, y este prompt la mantiene así hasta que hayas revisado el plan. ``` I want to launch Apple Search Ads for my app from scratch. I'm not an Apple Ads expert — guide me and build it for me. Create everything paused. Interview me first, briefly: what my app does and who it's for, which countries to start in, my monthly test budget, and my target cost per subscriber. If I don't know that last one, work it back from my price and expected trial conversion. Then propose a starter structure as a table before you create anything: a brand campaign, a generic campaign, a competitor campaign if I name competitors, and a small discovery campaign to mine search terms. For each one give me a one-line reason it exists, its share of the budget, its ad groups, a starter keyword list with match types and bids, and the negative keywords to share across all of them. Build it once I approve, then show me what was created. Finish with a launch checklist: what I still have to fix in the Apple Search Ads console before anything can serve, such as a payment method or a pending tax verification; how to switch the campaigns on when I'm ready; and one automation rule worth adding after a week of data. Then tell me to come back in 7 days for a performance review. ``` ## Cómo se diferencia del AI Agent \{#how-it-differs-from-the-ai-agent\} Adapty Ads Manager también tiene un [AI Agent](ads-manager-ai-agent) integrado en el dashboard. Los dos responden a necesidades distintas: | | AI Agent | Plugin de Apple Ads | |---|---|---| | Dónde se ejecuta | El Adapty Dashboard | Tu terminal, en tu herramienta de IA para programar | | Qué hace | Analiza y recomienda | Analiza, recomienda y ejecuta los comandos | | Configuración | Ninguna | Adapty CLI más el plugin | Usa el AI Agent para preguntar qué está pasando en tu cuenta. Usa el plugin cuando también quieras que el cambio se lleve a cabo. ## ¿Y ahora qué? \{#whats-next\} - [Gestionar Ads Manager desde la CLI](developer-cli-ads-manager) — las mismas tareas a mano, sin una herramienta de IA. - [Comandos de Ads Manager](developer-cli-ads-manager-reference) — todos los comandos con sus argumentos, flags y valores aceptados. --- # File: developer-cli-cowork --- --- title: "Configurar la CLI de Adapty en la app de Claude" description: "Conecta la CLI para desarrolladores de Adapty a la app de Claude, anteriormente Claude Cowork: añade el plugin de Adapty, permite los dominios de Adapty y autoriza la CLI desde un chat." --- El [CLI para desarrolladores de Adapty](developer-cli) funciona en la app de Claude, por lo que puedes gestionar tu cuenta de Adapty desde un chat en lugar de una terminal. Claude ejecuta los comandos en un sandbox que solo accede a dominios incluidos en la lista de permitidos, así que el CLI necesita una configuración inicial en Claude antes de poder iniciar sesión en tu cuenta. :::note Claude Cowork y el chat ahora son [un único Claude](https://claude.com/resources/articles/cowork-is-now-claude): todo lo que hacías en Cowork, ahora puedes hacerlo en cualquier conversación. La fusión se está desplegando por plan. Si tu plan todavía tiene un Cowork separado, sigue los mismos pasos y empieza cada tarea en Cowork. ::: **Lo que necesitas:** - La app de Claude - Una cuenta de Adapty - Para espacios de trabajo corporativos de Claude, la ayuda de tu administrador de espacio de trabajo — consulta [Permitir los dominios de Adapty](#allow-the-adapty-domains) ## Añade el plugin de Adapty \{#add-the-adapty-plugin\} El plugin de Adapty incluye las instrucciones que Claude sigue para instalar, autenticar y manejar la CLI, incluida la [skill de Apple Ads](developer-cli-ads-manager-skill) para trabajar con Ads Manager. Para añadir el plugin: 1. En Claude, abre **Settings** → **Plugins**. 2. Haz clic en **Add** → **Add marketplace**. 3. Selecciona **Add from a repository**. 4. En el campo **URL**, pega `https://github.com/adaptyteam/apple-ads-cli`. 5. Haz clic en **Sync**. Los plugins de Adapty aparecen en **Personal** dentro del directorio de plugins. ## Permite los dominios de Adapty \{#allow-the-adapty-domains\} Hasta que permitas Adapty en la lista de dominios permitidos del sandbox, la CLI no puede iniciar sesión ni leer tu cuenta. Permite tanto `adapty.io` como `*.adapty.io`. Un comodín no cubre el dominio raíz en la mayoría de las implementaciones de listas de permitidos. La propia CLI accede a `api.adapty.io`, y un agente que consulta la documentación de Adapty mientras trabaja accede al dominio raíz. Claude aplica esta configuración al iniciar una conversación. Si cambias el modo de acceso o añades un dominio durante una conversación, esa conversación mantiene la configuración con la que comenzó — inicia una nueva conversación. ### Cuentas personales \{#personal-accounts\} Configuras la lista de permitidos tú mismo: 1. En Claude, abre **Settings** → **Capabilities**. 2. Activa **Code execution and file creation**. El bloque **Allow network egress** permanece oculto hasta que lo hagas. 3. Activa **Allow network egress**. 4. En **Domain allowlist**, selecciona **Package managers only**. 5. En **Additional allowed domains**, añade `adapty.io` y luego `*.adapty.io`. El otro modo de acceso, **All domains**, permite que el sandbox acceda a cualquier sitio. Con esa opción seleccionada, no necesitas añadir los dominios de Adapty en absoluto. ### Espacios de trabajo corporativos \{#corporate-workspaces\} Un administrador controla la lista de permitidos. Pídele que añada ambos dominios para el espacio de trabajo. ## Instalar y autorizar el CLI \{#install-and-authorize-the-cli\} Cada conversación empieza en una máquina limpia: sin CLI, sin token y sin configuración heredada de la conversación anterior, por lo que los pasos de instalación e inicio de sesión se repiten al comienzo de cada conversación. Para instalar y autorizar el CLI: 1. Inicia una nueva conversación en Claude. 2. Pide a Claude que instale el Adapty CLI — por ejemplo, "Install the Adapty CLI". Claude puede solicitar permiso para abrir enlaces de instalación — permítelo. 3. Una vez instalado, Claude ofrece iniciar sesión en Adapty — acepta, y publicará un enlace de autorización con un código de dispositivo. El código expira en unos 15 minutos. 4. Abre el enlace. La página de autorización de la CLI de Adapty se abrirá en tu navegador. 5. Verifica que el código de la página coincide con el código que te dio Claude y haz clic en **Authorize**. Si los códigos no coinciden, haz clic en **Deny** y pide a Claude un nuevo enlace. 6. De vuelta en el chat, dile a Claude que ya autorizaste — por ejemplo, "listo". Claude verificará el inicio de sesión e imprimirá los detalles de tu cuenta. El CLI está listo. Para configurar tu app, productos y paywalls desde el chat, sigue la [guía de inicio rápido](developer-cli-quickstart). ## Solución de problemas \{#troubleshooting\} | Problema | Cómo solucionarlo | |---------|---------------| | Los skills no aparecen después de hacer clic en **Sync** | Verifica que la URL del repositorio sea `https://github.com/adaptyteam/apple-ads-cli` y vuelve a hacer clic en **Sync**. | | Errores de red durante la instalación o la autorización | Añade `adapty.io` y `*.adapty.io` a la [lista de dominios permitidos](#allow-the-adapty-domains). La configuración se aplica al iniciar una conversación, así que abre una conversación nueva después de añadirlos. | | El enlace de autorización no se abre o es rechazado | El código caduca en unos 15 minutos. Pídele a Claude que genere un enlace nuevo. | | El código en el navegador no coincide con el código en el chat | No hagas clic en **Authorize**. Pídele a Claude un enlace nuevo y empieza de nuevo. | --- # File: developer-cli-attribution --- --- title: "Consultar atribución desde la CLI" description: "Lee informes de atribución de Adapty desde el terminal: gasto, instalaciones, ingresos, ROAS, cohortes y predicciones por canal, campaña, anuncio, país y fecha." --- La CLI de Adapty lee los datos de [Atribución de Adapty](adapty-user-acquisition) desde el terminal, bajo el tema `adapty attribution`. Devuelve los mismos números que el [dashboard de Atribución](ua-analytics): gasto, instalaciones, ingresos, ROAS, valores de cohorte y predicciones, agrupados por canal, campaña, grupo de anuncios, anuncio, palabra clave, país, store o fecha. Úsala para dar a un agente de IA acceso en tiempo real a tus datos de adquisición, extraer un informe en un script o responder una pregunta concreta sin necesidad de crear una vista en el dashboard. :::tip Para que una herramienta de codificación con IA responda preguntas como "¿Qué canal tuvo mejor retorno el mes pasado?", instala el [skill de Attribution](developer-cli-attribution-skill). Sabe qué comandos ejecutar y cómo interpretar sus resultados. ::: Cada comando `attribution` es de solo lectura. Nada de lo que ejecutes modifica una campaña, un enlace de seguimiento ni una integración. ## Antes de empezar \{#before-you-start\} El tema `attribution` usa la misma instalación e inicio de sesión que el resto de la CLI. Si todavía no lo has configurado, sigue los pasos 1 y 2 de la [guía de inicio rápido](developer-cli-quickstart). El tema requiere la versión 0.8.8 o posterior de la CLI. Para ver tu versión, ejecuta `adapty --version`. Se aplican dos condiciones más: - **Acceso a Attribution**: Sin él, `report` y `values` fallan con `402 attribution_access_required`. Volver a iniciar sesión no cambia nada. `metrics` y `dimensions` funcionan sin acceso, así que una llamada a `metrics` que funcione demuestra que tu sesión es válida, no que tengas acceso. - **Una app configurada en Attribution**: `report` y `values` reciben el UUID de la app a través de `adapty apps list`. Una app que no esté configurada en Attribution, o que tu usuario de Adapty no pueda leer, falla con `404 attribution_app_not_found`. ## Crear un informe \{#build-a-report\} Crea cada informe en el mismo orden: elige nombres de los catálogos, busca los valores de los filtros y ejecuta el informe. 1. **Lista las métricas**. Cada métrica incluye su unidad y, en el caso de los ratios, las métricas por las que se divide: ```bash adapty attribution metrics ``` Algunos nombres son plantillas, como `d{N}_roas`. El día debes completarlo tú mismo: `d7_roas`, `d30_roas`. Un cero a la izquierda, como en `d07_roas`, no se acepta. 2. **Lista las dimensiones** por las que puedes agrupar o filtrar: ```bash adapty attribution dimensions ``` `--group-by` acepta `date`, `campaign`, `adset`, `ad`, `keyword`, `channel`, `country` y `store`. Las campañas, los conjuntos de anuncios y los anuncios se filtran por ID, nunca por nombre. 3. **Busca los valores de los filtros**. Omite este paso si no vas a filtrar. Para campañas, devuelve el ID, el nombre y el canal de cada campaña durante el período: ```bash adapty attribution values --app <app-id> --date-from 2026-08-01 --date-to 2026-08-31 --dimension campaign ``` 4. **Ejecuta el informe**: ```bash adapty attribution report --app <app-id> --date-from 2026-08-01 --date-to 2026-08-31 \ --metrics spend,installs,cpi,roas,d7_roas --group-by campaign --sort roas:desc ``` Un informe devuelve una fila por cada combinación de dimensiones de `--group-by`, más `totals`. Lee los totales desde `totals`, no sumando filas: los ratios y los conteos únicos no se pueden sumar entre filas. ### Ejemplos Una tendencia semanal para dos países en una campaña: ```bash adapty attribution report --app <app-id> --date-from 2026-07-01 --date-to 2026-08-31 \ --metrics spend,installs,cost_per_trial,d7_roas --group-by date,channel --granularity week \ --filter campaign=<campaign-id> --filter country=US,GB ``` Calidad de pruebas por campaña, sobre ingresos netos de comisión del store: ```bash adapty attribution report --app <app-id> --date-from 2026-07-01 --date-to 2026-08-15 \ --metrics spend,count_trial_started,d14_count_trial_converted,cost_per_trial,d30_roas \ --group-by campaign --revenue-basis proceeds ``` Retorno de inversión previsto por día de instalaciones: ```bash adapty attribution report --app <app-id> --date-from 2026-09-01 --date-to 2026-09-29 \ --metrics spend,d7_revenue,d90_predict_roas,d365_predict_roas --group-by date,campaign --granularity day ``` Para obtener la respuesta en bruto para un script o un agente, añade `--json` a cualquier comando. ## Lee los resultados \{#read-the-results\} - **Moneda**: El dinero está en USD. Los ingresos siguen `--revenue-basis`: `gross` (el valor por defecto, y el del dashboard), `proceeds` (después de la comisión del store) o `net` (después de comisión e impuestos). El campo `meta.query` de la respuesta indica la base en uso. - **Porcentajes**: El ROAS y las tasas se expresan en una escala de 0–100. Un `roas` de 150 equivale al 150%, o 1,5x. - **Valores vacíos**: Un valor vacío significa que no se puede calcular, nunca que es cero. Una proporción queda vacía cuando no hay nada por lo que dividir, una predicción queda vacía cuando el modelo no tiene valor para ese día, y toda métrica basada en gasto queda vacía para los canales en los que la Atribución no tiene datos de gasto. En la salida `--json` estos valores son `null`; la vista de tabla muestra `—`. - **Métricas de cohorte**: Una métrica de cohorte solo cuenta lo que la cohorte ha hecho hasta el momento. `d30_roas` para instalaciones de la semana pasada cubre solo una semana de ingresos y seguirá aumentando. Compara campañas únicamente en un día que todas las cohortes del período hayan alcanzado. - **Fechas**: Las fechas son días en la zona horaria de informes de tu app. No hay ningún indicador de zona horaria. La zona horaria aparece como `meta.query.timezone` en la salida `--json` de `report` y `values`. ### Gasto en Apple Search Ads \{#apple-search-ads-spend\} La atribución recopila el gasto publicitario de Meta, TikTok y Google Ads. Las filas de Apple Search Ads incluyen instalaciones e ingresos, pero el gasto, el CPI, el ROAS y cualquier otra métrica basada en gasto aparecen vacíos. Conectar Apple Ads no añade ese gasto a la atribución: el gasto en Apple Search Ads y el ROAS están en [Ads Manager](developer-cli-ads-manager), bajo `adapty asa metrics`. No sumes los números de Ads Manager a las filas ni a los totales de atribución. Ads Manager informa en la divisa del grupo de campañas y atribuye las instalaciones de forma diferente. ## Límites \{#limits\} El agrupamiento de fechas más fino determina el período más amplio que puede cubrir un informe: | Agrupamiento | Período máximo | |---|---| | `--granularity day` | 31 días | | `--granularity week` | 180 días | | `--granularity month`, `quarter`, o `year` | 366 días | | Sin agrupamiento `date` | 92 días | Para un período más largo, usa un `--granularity` más grueso. Un año de datos equivale a un informe con `--granularity month`. Un informe también tiene estos límites: - Como máximo 10.000 filas. Para más, agrupa las fechas con menos granularidad, elimina el agrupamiento por `keyword` o `ad`, o filtra. - Como máximo 25 métricas y 100 valores en un solo filtro. - Las métricas de predicción (`d{N}_predict_…`) requieren `--group-by date --granularity day` y como máximo 2 agrupamientos adicionales, con hasta 4 horizontes distintos de 365 días como máximo. `adapty attribution metrics --json` devuelve estos límites en `data.limits`. Ejecuta los informes uno tras otro, no en paralelo: el servicio procesa solo unas pocas consultas por empresa a la vez. ## Errores \{#errors\} Las solicitudes que el servicio rechaza salen con el código 4. Las entradas que la CLI rechaza antes de enviar, como una fecha con formato incorrecto o `--granularity` sin `--group-by date`, salen con el código 2. Con `--json`, el error incluye el `error_code` del servicio. | Código | HTTP | Qué hacer | |---|---|---| | `auth_required` | 401 | Inicia sesión de nuevo con `adapty auth login`. | | `attribution_access_required` | 402 | Tu empresa no tiene acceso a Attribution. Volver a iniciar sesión no ayuda. | | `attribution_app_not_found` | 404 | Busca el ID de la app en `adapty apps list`, que solo muestra las apps que puedes leer. | | `attribution_unknown_metric` | 422 | Corrige los nombres de las métricas según `adapty attribution metrics`. El mensaje indica cuáles son inválidas y el resto del informe no se ejecuta. | | `attribution_validation_error` | 422 | Corrige la dimensión, el filtro, el campo de ordenación o las fechas indicadas en el mensaje. Incluye todos los valores de una misma dimensión en un único `--filter`. | | `attribution_query_too_large` | 422 | Reduce el informe como indica el mensaje. Consulta [Límites](#limits). | | `attribution_busy` | 429 | Tu empresa tiene demasiadas consultas en ejecución. Espera `retry_after_seconds` y vuelve a ejecutar el informe. | | `attribution_upstream_unavailable`, `attribution_query_unavailable` | 503 | Espera `retry_after_seconds` y vuelve a intentarlo. Si `attribution_query_unavailable` se repite, reduce el tamaño del informe. | El CLI nunca reintenta `report` ni `values` por ti, así que un informe fallido seguirá fallido hasta que lo ejecutes de nuevo. ## Referencia de comandos \{#command-reference\} ### `attribution metrics` \{#attribution-metrics\} Lista todas las métricas que acepta un informe, con su unidad, descripción y los divisores de cada ratio, además de los límites del informe. No acepta ningún flag salvo `--json`. ### `attribution dimensions` \{#attribution-dimensions\} Muestra qué acepta `--group-by` y `--filter`, si cada dimensión filtra por ID o por valor, y las granularidades de `date`. No acepta flags adicionales más allá de `--json`. ### `attribution values` Lista los valores que toma una dimensión para una app durante un período. Son exactamente los valores que acepta `report --filter`. | Flag | Requerido | Descripción | |---|---|---| | `--app` | Sí | UUID de la app, obtenido con `adapty apps list`. | | `--date-from` | Sí | Primer día del período, inclusive, en formato `YYYY-MM-DD` según la zona horaria de la app. | | `--date-to` | Sí | Último día del período, inclusive. No puede ser anterior a `--date-from`. | | `--dimension` | Sí | Una dimensión filtrable de `adapty attribution dimensions`. | | `--revenue-basis` | No | `gross`, `proceeds` o `net`. | Para `campaign`, `adset` y `ad`, cada valor incluye un ID, un nombre y un canal. Un valor con ID vacío corresponde a tráfico orgánico o de referencia de store, que ningún filtro puede seleccionar. ### `attribution report` Ejecuta un informe: métricas durante un período, agrupadas por dimensiones. | Indicador | Obligatorio | Descripción | |---|---|---| | `--app` | Sí | UUID de la app, obtenido con `adapty apps list`. | | `--date-from` | Sí | Primer día del período, inclusive, como `YYYY-MM-DD` en la zona horaria de la app. | | `--date-to` | Sí | Último día del período, inclusive. | | `--metrics` | Sí | Nombres de métricas de `adapty attribution metrics`, separados por comas o repetidos. Máximo 25. | | `--group-by` | Sí | `date`, `campaign`, `adset`, `ad`, `keyword`, `channel`, `country` o `store`, separados por comas o repetidos. | | `--granularity` | Con `--group-by date` | `day`, `week`, `month`, `quarter` o `year`. Obligatorio al agrupar por `date`, y rechazado en cualquier otro caso. | | `--filter` | No | `dimension=value[,value]`. Varios valores coinciden con cualquiera de ellos; los filtros en distintas dimensiones se aplican todos. Un `--filter` por dimensión. Escribe `\,` para incluir una coma dentro de un valor. | | `--revenue-basis` | No | `gross` (por defecto), `proceeds` o `net`. | | `--sort` | No | `field:asc` o `field:desc`, donde el campo es una métrica solicitada o una dimensión de `--group-by`. Orden ascendente si se omite la dirección. Los valores vacíos se ordenan al final. | Las filas agrupadas por `campaign`, `adset` o `ad` incluyen el ID, el nombre y el canal de esa entidad. El nombre de una campaña es el último nombre que tuvo dentro del período, así que para comparar campañas entre períodos usa el ID. El valor `date` es el primer día de su bucket. ## ¿Qué sigue? \{#whats-next\} - [Analiza la atribución con una herramienta de codificación IA](developer-cli-attribution-skill) — el skill que ejecuta estos comandos por ti. - [Métricas](ua-metrics) — qué mide cada métrica en el dashboard. - [Predicciones](ua-predicted-metrics) — cómo se modela el revenue previsto. --- # File: developer-cli-attribution-skill --- --- title: "Analiza la atribución con una herramienta de IA" description: "Instala el skill adapty-attribution para que Claude Code, Copilot CLI, Codex o Gemini CLI puedan responder preguntas sobre el rendimiento de campañas a partir de tus datos de atribución de Adapty." --- El skill adapty-attribution permite que una herramienta de IA responda preguntas sobre tus campañas publicitarias a partir de tus datos de [Atribución de Adapty](adapty-user-acquisition). Pregunta en lenguaje natural — "¿Qué canal tuvo mejor retorno el mes pasado?", "¿Deberíamos eliminar esta campaña?" — y ejecuta los [comandos `adapty attribution`](developer-cli-attribution), lee los resultados y responde con los números que respaldan la conclusión. Un skill es un conjunto de instrucciones que tu herramienta de IA carga bajo demanda. Este contiene las partes de los informes de atribución que son fáciles de malinterpretar: en qué base de ingresos se calcula el ROAS, qué valores aún no se pueden calcular y de dónde provienen los gastos de Apple Search Ads. :::note El skill solo lee. No modifica ninguna campaña, enlace de seguimiento ni integración, y los movimientos de presupuesto que sugiere los decides tú. ::: ## Qué responde \{#what-it-answers\} El skill funciona a partir de playbooks —un conjunto de comandos, una forma de leerlos y la estructura de la respuesta— para las preguntas que los equipos hacen con más frecuencia: - **Retorno y presupuesto**: Clasifica canales y campañas por ROAS e indica dónde destinar más presupuesto y en qué incrementos. - **Campañas nuevas**: Evalúa una campaña reciente según el día que han alcanzado sus cohortes, en lugar de un D30 que solo tiene una semana de ingresos. - **Calidad de los trials**: Clasifica campañas por coste por trial de pago en lugar de coste por instalación, para que salgan a la luz las campañas cuyos trials no convierten. - **Resumen semanal**: Compara la última semana con las anteriores y señala lo que ha cambiado. - **Países y stores**: Muestra dónde expandirse y dónde limitar el gasto. - **Ganadores dentro de una campaña**: Clasifica conjuntos de anuncios y anuncios dentro de una misma campaña. - **Tráfico de baja calidad**: Señala campañas con más reembolsos y contracargos que la media de la app. - **Retorno previsto**: Lee el ROAS predicho a horizontes más largos y lo convierte en un CPI de equilibrio. - **Cifras que no cuadran**: Explica por qué Meta, TikTok o Google reportan un ROAS distinto al de Adapty. - **Caída de instalaciones**: Analiza el patrón de una caída para distinguir si la causa es interna de la app o de una sola red. Setup questions — connecting Meta, TikTok, or Google Ads, tracking links, the web pixel, Stripe, or Paddle — go to the docs pages that own them. ## Apple Search Ads La atribución no recopila el gasto de Apple Search Ads, por lo que su ROAS aparece vacío. El skill obtiene el gasto y el ROAS de Apple Search Ads desde [Ads Manager](developer-cli-ads-manager) y los muestra como una línea propia, en la divisa del grupo de campañas, en lugar de fusionarlos con los datos de atribución. Si proporcionas un importe de gasto manualmente, el skill responde con un umbral —«Apple Search Ads supera a Google si su gasto fue inferior a $X»— en lugar de calcular un ROAS a partir de tu estimación. ## Instala el skill \{#install-the-skill\} El skill forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener nuevos skills a medida que se publican, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). El skill necesita el [Adapty CLI](developer-cli-quickstart), con sesión iniciada y acceso a Attribution para tu empresa. Luego haz tu pregunta en lenguaje natural — consulta los ejemplos de prompts a continuación. ## Ejemplos de prompts \{#example-prompts\} Indica el nombre de la app, el período y la decisión que debes tomar. El skill se encarga de los comandos. ``` Our app is app_finance. Which paid channels paid back best in August, and where should next month's extra $20k of budget go? Judge on revenue after the store's cut. ``` ``` Our TikTok "Fall Promo" campaign started on Sep 15. Leadership's rule is to kill anything under 80% D30 ROAS. How is it doing against "Spring Sleep", and should we kill it? ``` ``` Meta Ads Manager says our "Broad US" campaign had a 2.4 purchase ROAS in August. Adapty shows something much lower. Which number is right, and what do I tell my boss? ``` ## ¿Qué más puedes hacer? \{#whats-next\} - [Consultar atribución desde la CLI](developer-cli-attribution) — los mismos informes de forma manual, sin herramienta de IA. - [Análisis de atribución](ua-analytics) — la vista del dashboard con los mismos datos. --- # 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 del lado del servidor deben autenticarse con tu clave de API secreta como encabezado 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\} Los límites de frecuencia se aplican por clave de API, y el límite asignado a tu app está vinculado a tu clave — no existe una cifra única que sea válida para todas las apps. Si estás dimensionando una integración de alto volumen o empiezas a recibir respuestas `429`, consulta con soporte cuál es el límite que se aplica a tu app. Dos aspectos a tener en cuenta al diseñar: - **El límite es una tasa sostenida, no un presupuesto por minuto.** Las solicitudes se distribuyen a lo largo de la ventana y solo se absorbe un breve pico por encima de la tasa, por lo que una tasa de solicitudes constante rinde considerablemente más que enviar todo el tráfico de un minuto de golpe. - **Algunos endpoints tienen su propio límite, más bajo.** [Crear transacción de moneda virtual](api-adapty/operations/createVirtualCurrencyTransaction) permite 600 solicitudes por minuto por app de forma predeterminada, independientemente del límite global de tu app. Requests over a limit fail with `429 Too Many Requests`. Retry with backoff, and honor the `Retry-After` header when the response carries one. ## 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 analíticas 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 analíticas detalladas en formato CSV, lo que facilita el seguimiento, el intercambio y el refinamiento de tus datos según sea necesario. :::tip ¿Quieres que un agente de IA lea tus analíticas y sugiera qué probar? Consulta [Skill de agente para aumentar ingresos](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 aplicación. Todas las claves API de la misma aplicación comparten este límite, por lo que añadir una clave no incrementa la capacidad. Si se supera el límite, se devuelve un error `429 Too Many Requests`. Adapty también limita el tráfico total de la API entre todas las aplicaciones, por lo que una solicitud puede fallar ocasionalmente con `429` aunque tu aplicación esté por debajo de su propio límite. La respuesta no incluye el encabezado `Retry-After`. Para recuperarte, espera hasta un minuto y vuelve a intentarlo con retroceso exponencial. ## 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-10-30T13:00:39.485Z_ _Successfully processed: 31/32 files_ # CAPACITOR - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-10-30T13:00:39.487Z Total files: 48 --- # File: capacitor-sdk-overview --- --- title: "Descripción general del SDK de Capacitor" description: "Aprende sobre el SDK de Capacitor de Adapty 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 se te da: crear aplicaciones increíbles. Esto es lo que gestionamos por ti: - Gestiona compras, validación de recibos y administración de suscripciones sin configuración adicional - Crea y prueba paywalls sin 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 suscripción del usuario siempre actualizado entre sesiones y dispositivos - Integra tu app con servicios de atribución de marketing y análisis con una sola línea de código :::note Antes de entrar en el código, necesitas 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 listo primero. ::: ## Primeros pasos \{#get-started\} For a fully automated integration, use the [adapty-integration skill](https://github.com/adaptyteam/adapty-skills): 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. [Instala y configura 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. [Activa las compras con flows y paywalls](capacitor-quickstart-paywalls): Configura el flujo de compra para que los usuarios puedan adquirir productos. Si prefieres crear tu propia UI, consulta [Implementar paywalls personalizados](capacitor-quickstart-manual). 3. [Comprueba el estado de la suscripción](capacitor-check-subscription-status): Verifica automáticamente el estado de la suscripción del usuario y controla su acceso al contenido de pago. 4. [Identifica a los usuarios (opcional)](capacitor-quickstart-identify): Asocia los usuarios con sus perfiles de Adapty para 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. **Flows y paywalls** - Productos agrupados con configuración, vinculados a un placement. Dos formas de crearlos, comparadas en [Flows y paywalls](flows-and-paywalls): - **[Flow & Paywall Builder](adapty-flow-builder)** - Interfaz visual sin código. Adapty renderiza la UI y gestiona la compra por ti. - **[Paywalls personalizados](paywalls)** - Sin configuración visual; tú construyes la UI en tu propio código y llamas a `makePurchase` tú mismo. Consulta [Implementar paywalls personalizados](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 flow o paywall. Piensa en los placements como el "dónde" y "cuándo" de tu estrategia de monetización. Algunos placements habituales son: - `main` - Tu ubicación principal de paywall - `onboarding` - Se muestra durante el flow de onboarding del usuario - `settings` - Accesible desde la configuración de tu app Empieza con los básicos como `main` u `onboarding` para tu primera integración y luego piensa en qué otros puntos de tu app podrían estar los usuarios 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 principales para una integración sin problemas en tu app de Capacitor: - **Core Adapty**: Este módulo es necesario para que Adapty funcione correctamente en tu app. - **AdaptyUI**: Este módulo renderiza flows, además de los paywalls del builder antiguo. 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 | |---------------------------|----------------------|----------------| | 4.1+ | 8 | 15.0+ | | 3.16.0+ | 8 | 15.0+ | | 3.15 | 7 | 14.0+ | Las versiones 6 e inferiores de Capacitor no son compatibles. Compilar para iOS requiere **Xcode 26** o posterior — el SDK nativo de iOS está compilado con Swift tools 6.2. En Android, el SDK mínimo es 24. :::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. ::: ## Dependencias \{#dependencies\} El SDK de Adapty funciona con las siguientes versiones de Google Play Billing Library en Android: | Versión del SDK de Adapty | Versión de Billing Library | |---|---| | 3.17.0 y posteriores | v8 | | 3.15.0–3.16.3 | v7 por defecto, v8 si otra dependencia la eleva | :::note La compatibilidad con una versión de Billing Library no significa que Adapty admita todas las funciones que Google introdujo en ella. Antes de adoptar una nueva capacidad de facturación de Google Play, consulta [Producto en Play Store](android-products). ::: ## Instalar el SDK de Adapty \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Capacitor.svg?style=flat&logo=capacitor)](https://github.com/adaptyteam/AdaptySDK-Capacitor/releases) Siempre recomendamos instalar la versión más reciente disponible del SDK — incluye las últimas correcciones de estabilidad y mejoras. Instala el SDK de Adapty: ```sh npm install @adapty/capacitor npx cap sync ``` En iOS, el SDK de Adapty obtiene los SDKs nativos de Adapty únicamente a través de **Swift Package Manager** — el podspec de CocoaPods fue eliminado ([el repositorio de specs de CocoaPods pasará 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 aplicaciones nuevas, añade la plataforma iOS con el gestor de paquetes SPM: ```sh npx cap add ios --packagemanager SPM ``` - Para aplicaciones 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). ¿Actualizas desde v3? Consulta [Migrar el SDK de Adapty para Capacitor a v4.1](migration-to-capacitor-sdk-v4) para ver la lista completa de cambios en la API. ## 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. 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 el [Flow & Paywall Builder](adapty-flow-builder), sigue la [guía de inicio rápido](capacitor-quickstart-paywalls). - Si construyes tu propia UI 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 piensas usar el [Flow & Paywall Builder](adapty-flow-builder), necesitas el módulo AdaptyUI. Se activa automáticamente al activar el módulo principal; no tienes que 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, }, } }); ``` ### Habilitar la atribución de Adapty \{#enable-adapty-attribution\} :::info Este parámetro está disponible a partir de la versión 4.1 del SDK. ::: Si usas [Adapty Attribution](user-acquisition), establece `adaptyAttributionEnabled` en `true` al activar el SDK. El valor predeterminado es `false`: sin este parámetro, el SDK no registra instalaciones ni entrega los detalles de instalación a tu app. En versiones del SDK anteriores a la 4.1, la atribución de Adapty se habilita automáticamente. ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { adaptyAttributionEnabled: 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 } ``` :::note Con esta opción habilitada, el SDK omite la llamada de activación completa una vez que se ha activado, por lo que los cambios en los parámetros de activación no surten efecto al recargar en caliente. Para aplicar nuevos parámetros de activación, cierra la app completamente y vuelve a abrirla. ::: ## 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 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 con el 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 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 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 (que normalmente se encuentra 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 espacio de nombres `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 finales e indique al fusionador de manifiestos que reemplace los valores de las bibliotecas: ```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 usa diferentes 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 ejemplo. Reemplaza o añade reglas para cualquier otro SDK que estés usando 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 heredado 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 aplica a proyectos basados en CocoaPods con **SDK 3.x**. El SDK 4 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 de Capacitor para iOS, es posible que veas errores de compilación de Swift 6 en los targets del pod de Adapty. Los síntomas más comunes son 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 & Paywall Builder en el SDK de Capacitor" description: "Guía de inicio rápido para habilitar compras in-app con el Flow & Paywall Builder de Adapty." --- 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 y paywalls**](adapty-flow-builder) – pantallas que presentan productos a los usuarios, creadas con el Flow & Paywall Builder sin código. El SDK los obtiene mediante `getFlow`. Si prefieres construir la interfaz en tu propio código, consulta [Implementar paywalls personalizados](capacitor-quickstart-manual). - [**Placements**](placements) – dónde y cuándo muestras flows y paywalls en tu app (como `main`, `onboarding`, `settings`). Los vinculas 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 versiones distintas 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 & Paywall Builder | ✅ Fácil | [Creas un flow completo o un paywall listo para comprar en el builder sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona toda la lógica de compra, validación de recibos y gestión de suscripciones. | | Paywalls personalizados | 🟡 Media | Implementas la UI del 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 o paywall creado en el Adapty Flow & Paywall Builder.** Si prefieres crear la UI del paywall tú mismo, consulta [Implementar paywalls personalizados](capacitor-quickstart-manual). ::: Para mostrar un flow o paywall creado en el Adapty Flow & Paywall Builder, en el código de tu app solo necesitas: 1. **Obtén el flow o paywall**: Obtenlo desde Adapty. 2. **Muéstralo y Adapty gestionará las compras por ti**: Muestra la vista en tu app. 3. **Gestiona las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abre enlaces o cierra el paywall cuando el usuario pulse los 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 o paywall](paywall-builder-templates) y [añade productos](paywall-product-block). 4. [Crea un placement y añade tu flow o paywall](create-placement). 5. [Instala y activa el SDK de Adapty](sdk-installation-capacitor) en el código de tu app. Esta guía usa las APIs del SDK de Adapty para Capacitor v4. ## 1. Obtén el flow o el paywall \{#1-get-the-flow-or-paywall\} Tus flows y paywalls están asociados con placements configurados en el dashboard. Los placements te permiten mostrar distintos flows o paywalls a diferentes audiencias o ejecutar [pruebas A/B](ab-tests). Para obtener un flow o paywall creado en el Adapty Flow & Paywall Builder, obtén el objeto `flow` a partir del ID del [placement](placements) utilizando el método `getFlow`. El flow contiene los elementos de UI y el estilo 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. Mostrar el flow o el paywall \{#2-display-the-flow-or-paywall\} Ahora que tienes el flow, basta con añadir unas pocas líneas para mostrarlo. Crea una `view` con el método `createFlowView`, configura 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="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 o paywall, consulta nuestra [guía](capacitor-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#handle-button-actions\} Cuando los usuarios pulsan botones en el flow o en el paywall, el SDK de Capacitor gestiona automáticamente las compras, la restauración, el cierre de la pantalla y la apertura de URLs. Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren que gestiones sus acciones en tu código. O bien, puede que quieras sobrescribir su comportamiento predeterminado. Por ejemplo, aquí puedes ver 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. ::: ## 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 flow o paywall está listo para mostrarse en la aplicación. [Prueba tus compras](capacitor-test) para asegurarte de que puedes completar una compra de prueba desde el flow. Ahora necesitas [comprobar el nivel de acceso de los usuarios](capacitor-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 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 skill de integración del SDK" description: "Usa la skill adapty-integration para integrar el SDK de Adapty en tu app de Capacitor de principio a fin con tu herramienta de codificación con IA." --- :::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. ::: El skill `adapty-integration` automatiza la integración de Adapty de principio a fin: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación relevante de Adapty en cada etapa. Funciona en cualquier agente de programación que soporte skills: Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI y otros. El skill forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener los nuevos skills a medida que se publiquen, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Para iniciar la integración, pide a tu agente: ``` Integrate Adapty into my app ``` El skill hace algunas preguntas de configuración y luego te guía a través de 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-integration skill](https://github.com/adaptyteam/adapty-skills): 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á paso a paso, incluido cuándo abrir el Dashboard para conectar tus stores. El CLI de skills no actualiza el skill por sí solo. Para obtener la última versión, ejecuta `npx skills update`. ### 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 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()`. [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\} 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 requiere ningún cambio de código. [Pruebas A/B](ab-tests) - **Flows y placements adicionales**: Añade más llamadas `getFlow` 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) y [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 & Paywall Builder**](adapty-flow-builder): Crea flows en el editor sin código de Adapty y el SDK los renderiza automáticamente. - [**Paywalls personalizadas**](capacitor-making-purchases): Construyes tu propia UI 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? Consulta la [tabla comparativa en la guía de inicio rápido](capacitor-quickstart-paywalls). ### Instalar y configurar el SDK \{#install-and-configure-the-sdk\} Añade la dependencia del SDK de Adapty mediante npm y actívala 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-capacitor) 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 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="Flow & Paywall 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. Al pulsar un producto, se activa el diálogo de compra en sandbox. - **Problema habitual:** Flow vacío o error en `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="Custom paywalls"> **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](capacitor-quickstart-manual) - [Obtener paywalls y productos personalizados](fetch-paywalls-and-products-capacitor) - [Mostrar paywalls personalizados](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 los editores no-code de Adapty en tu app de Capacitor." --- ## Mostrar paywalls \{#display-paywalls\} ### Adapty Flow & 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 personalizados \{#implement-custom-paywalls\} <CustomDocCardList ids={['capacitor-quickstart-manual', 'fetch-paywalls-and-products-capacitor', 'present-remote-config-paywalls-capacitor', 'capacitor-making-purchases']} /> Para más guías sobre la implementación de paywalls personalizados y la gestión de compras, consulta [Implementar paywalls personalizados](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 app de Capacitor." --- <SDKv4> <MethodPromo method="getFlow" /> Después de [diseñar tu flow](adapty-flow-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 diseñados en los builders de Adapty. Si implementas paywalls personalizados, consulta el tema [Obtener paywalls y productos para paywalls personalizados 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. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Productos en el Adapty Dashboard**: [Crea tus productos](create-product). - **Un flow con productos**: Constrúyelo en el [Flow & Paywall Builder](adapty-flow-builder) y [asigna productos a él](paywall-product-block). - **Un placement**: [Crea un placement](create-placement) y asígnale tu flow. - **SDK de Adapty instalado**: Consulta la [guía de instalación del SDK para Capacitor](sdk-installation-capacitor). ## Obtener un flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall en 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 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. 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** | obligatorio | 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>`fetchPolicy` indica qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa petición falla. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados. Se pasa dentro del objeto opcional `params`.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen conexión inestable, considera `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y consulta el servidor solo cuando no hay nada almacenado. Es posible que los usuarios no reciban los datos más recientes, pero la carga será más rápida 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>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor de `maxAgeSeconds` que se pasa junto a ella, y consulta el servidor cuando sea más antigua.</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é 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 escasa.</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 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 `loadTimeoutMs`, ya que la operación puede consistir en varias peticiones internas.</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 variantes de paywall (`paywalls`), cualquier Remote Config (`remoteConfigs`) y un indicador `hasViewConfiguration`. | ## Obtén la configuración de la vista \{#fetch-the-view-configuration\} :::important Asegúrate de [publicar el flow](builder-save-publish). Un flow con ediciones sin publicar tiene el estado **Dirty**, y su placement sigue mostrando la última versión publicada. ::: Si el placement fue diseñado en el **Flow & Paywall Builder** o en el **old Paywall Builder**, Adapty renderiza la UI por ti. Crea la vista con `createFlowView` y luego [presenta el flow o paywall](capacitor-present-paywalls). Si el placement es un paywall personalizado sin UI del Builder, [renderízalo tú mismo](present-remote-config-paywalls-capacitor). El flag `hasViewConfiguration` del flow indica cuál es cuál antes de crear la vista: ```typescript showLineNumbers if (flow.hasViewConfiguration) { const view = await createFlowView(flow); await view.present(); } else { // Render your own screen from flow.remoteConfigs and flow.paywalls } ``` En el SDK de Capacitor, llama directamente a `createFlowView` — no necesitas obtener la configuración de vista primero. :::warning El resultado del método `createFlowView` solo se puede utilizar una vez. Si necesitas usarlo de nuevo, llama al método `createFlowView` otra vez. Llamarlo dos veces sin recrearlo puede provocar un error. ::: ```typescript showLineNumbers try { const view = await createFlowView(flow); } catch (error) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | :------------------- | :------- | :----------------------------------------------------------- | | **flow** | required | Un objeto `AdaptyFlow` para obtener un controlador del flow/paywall deseado. | | **locale** | optional | El identificador de la [localización del flow](add-paywall-locale-in-adapty-paywall-builder) con la que se renderizará la vista — por ejemplo, `en` o `pt-br`. Si se omite, la vista se renderiza en `en`, o en la localización predeterminada del flow cuando este no tiene `en`. Consulta [Localizaciones y códigos de idioma](capacitor-localizations-and-locale-codes). | | **customLayoutId** | <p>optional</p><p>default: `undefined`</p><p>SDK 4.1+</p> | El ID personalizado de un layout en la configuración de layouts del flow. Pásalo para renderizar ese layout específico en lugar del que el SDK selecciona automáticamente según el tipo de dispositivo y el tamaño de pantalla. Si ningún layout coincide con el ID, la llamada falla con un error de no-view-configuration. El Flow & Paywall Builder aún no asigna IDs de layout personalizados, así que deja esto sin definir. | | **customTags** | optional | Los valores de tu app para las [etiquetas personalizadas](flow-custom-tags) del flow, en un objeto con clave por el nombre completo de cada etiqueta. Para más detalles, consulta [Pasar datos a los flows](capacitor-flow-custom-tags). | | **prefetchProducts** | optional | 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: `true`. | | **android.enableSafeArea** | optional | Solo para Android (se ignora en iOS). Anidado bajo la clave `android`. Cuando es `true`, la vista del flow aplica los rellenos de área segura. Por defecto: `true`. 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í](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** | required | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | default: `'reload_revalidating_cache_data'` | <p>Se pasa dentro del objeto opcional `params`. `fetchPolicy` establece qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 inestables, considera `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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 peticiones de red.</p><p></p><p>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor `maxAgeSeconds` que le pases, y va al servidor cuando sea más antigua.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la aplicación y solo se borra cuando la aplicación se reinstala o mediante 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 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 la imagen principal remota. - Mostrar una imagen de vista previa antes de reproducir un vídeo. - Mostrar contenido multimedia incluido en la app, para que la primera pantalla se renderice sin necesidad de descarga. Consulta [Mostrar contenido multimedia de la primera pantalla desde el bundle de la app](capacitor-optimize-paywall-fetching#show-first-screen-media-from-the-app-bundle). A continuación, un ejemplo de cómo proporcionar assets 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 asset, el flow/paywall usará su apariencia predeterminada. ::: </SDKv4> <SDKv3> Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el antiguo 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. Ten en cuenta que este tema hace referencia a los paywalls personalizados con el Paywall Builder. Para obtener información sobre cómo obtener paywalls personalizados, consulta el tema [Obtener paywalls y productos para paywalls personalizados en tu aplicación móvil](fetch-paywalls-and-products-capacitor). <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-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. 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 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` con una lista de IDs de producto, el identificador del paywall, Remote Config y 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 un `ViewConfiguration`, lo que indica que fue creado con Paywall Builder. Esto te orientará sobre cómo mostrar el paywall. Si `ViewConfiguration` está presente, trátalo como un paywall del Paywall Builder; si no, [gestiónalo como un paywall personalizado](present-remote-config-paywalls-capacitor). En el SDK de Capacitor, llama directamente al método `createPaywallView` sin necesidad de obtener manualmente la configuración de la vista primero. :::warning El resultado del método `createPaywallView` solo puede usarse una vez. Si necesitas usarlo de nuevo, vuelve a llamar al método `createPaywallView`. ::: ```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 admites varios idiomas, añade una localización a tu paywall. Para ver los códigos que debes usar, consulta [Localizaciones y códigos de idioma](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 un paywall en el antiguo Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu aplicación móvil para mostrárselo al usuario. Ese flow contiene tanto lo que debe mostrarse dentro de él como la forma en que debe mostrarse. Antes de empezar, asegúrate de que: 1. Has creado un [flow](paywall-builder-templates) o un [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 solo para **flows y paywalls renderizados por Adapty**, que requieren SDK v4.0 o posterior. El proceso para presentar flows es diferente en el caso de los paywalls personalizados. - Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](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 usando el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Dicho paywall contiene tanto lo que se debe mostrar como la forma en que debe mostrarse. :::warning Esta guía es solo para paywalls creados con el antiguo Paywall Builder. Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](present-remote-config-paywalls-capacitor). ::: 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 las acciones de botones de flows y paywalls en Capacitor usando Adapty para una mejor monetización de la app." --- <SDKv4> Si estás creando flows en el [Flow & Paywall Builder](adapty-flow-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. En el antiguo Paywall Builder, añade un botón y asígnale una acción preexistente o 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. ## 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 en el [Flow & Paywall 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, selecciones de productos, etc.) y notificaciones sobre acciones relacionadas con compras realizadas en el flow. A continuación encontrarás cómo responder a estos eventos. Para controlar o monitorear 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(appearedView) { /***/ }, 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' // onAppeared appearedView.id; // '3f8a1c7e-9b24-4d51-8e30-6c5b2a9f1d47' appearedView.placementId; // 'onboarding_paywall' appearedView.variationId; // 'd21c4b6a-57e8-4f39-b0a2-8c7e13f5d94b' appearedView.locale; // 'es' // 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, p. ej., hace clic en un [botón personalizado](paywall-buttons). | | **onUrlPress** | Se invoca cuando el usuario hace clic en una URL 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., requieren aprobación parental) activan este evento, no `onPurchaseFailed`. | | **onPurchaseStarted** | Se invoca cuando el usuario pulsa el botón de acción "Purchase" 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, fallos de verificación de transacciones). No se invoca por 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 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; si te encuentras con uno, por favor, comunícanoslo. | | **onLoadingProductsFailed** | Se invoca cuando la carga de productos falla y proporciona un `AdaptyError`. Si no has establecido `prefetchProducts: true` en la creación de la vista, AdaptyUI recuperará los objetos necesarios del servidor por sí mismo. | | **onAppeared** | Se invoca cuando el flow se muestra al usuario y proporciona la vista que apareció; consulta [El argumento view](#the-view-argument). 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 flow y se abre un web paywall en un navegador in-app. | | **onDisappeared** | Se invoca cuando el usuario cierra el flow. En iOS, 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. | | **onWebPaymentNavigationFinished** | Se invoca tras intentar abrir un [web paywall](web-paywall) para realizar una compra, con independencia de si tuvo éxito o no. | | **onRequestAppReview** | Reservado para solicitudes de valoración de la app desde un flow. Los flows aún no generan solicitudes de valoración, por lo que no es necesario implementarlo. | | **onAnalytics** | Se invoca cuando un flow reporta un evento de analíticas, como una vista de pantalla. Consulta [Eventos de analíticas](#analytics-events) más abajo. | | **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 generan 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 restauración en un flow. Adapty no realiza la restauración; ejecútala tú mismo y luego notifica las transacciones restauradas. Consulta [Gestionar compras en modo observador](#handle-purchases-in-observer-mode) más abajo. | #### El argumento view \{#the-view-argument\} El argumento `view` requiere Capacitor SDK 4.1 o posterior. De todos los manejadores de eventos del flow, solo `onAppeared` recibe una descripción de la propia vista: un objeto `FlowEventView` con estos campos: | Campo | Descripción | |:-------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **id** | El identificador de esta instancia de vista. Es interno al SDK y no coincide con nada en el Adapty Dashboard. | | **placementId** | El [placement](placements) para el que se obtuvo el flow. | | **variationId** | La variante a la que se resolvió el flow, para atribuir tus propios análisis a una [prueba A/B](ab-tests). | | **locale** | La [localización del flow](add-paywall-locale-in-adapty-paywall-builder) con la que se construyó la vista. Difiere de la que solicitaste cuando el flow no tiene esa localización. La vista devuelta por `createFlowView` indica el mismo valor en su propiedad `locale`. Consulta [Usar localizaciones y códigos de idioma](capacitor-localizations-and-locale-codes). | ### Eventos de analytics \{#analytics-events\} ```typescript showLineNumbers title="Capacitor" view.setEventHandlers({ onAnalytics(name, params) { return false; // keep the flow open }, }); ``` Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. Consulta [Rastrear vistas de pantalla del flow](capacitor-flow-screen-views) para saber qué hacer con ellos. Un flow también reporta los valores que los usuarios escriben y seleccionan. Consulta [Procesar datos de flows](capacitor-flow-input) para leerlos. ### 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-flow-screen-views --- --- title: "Rastrear vistas de pantallas de flow en el SDK de Capacitor" description: "Recibe un evento cada vez que un usuario abre una pantalla de flow en tu app de Capacitor, y reenvíalo a tus analíticas de producto." --- Cada vez que un usuario abre una pantalla en un [flow](adapty-flow-builder), el SDK envía un evento `flow_screen_showed` a tu app. Reenvíalo a Amplitude, Mixpanel o la herramienta de analíticas de producto que uses, para analizar las pantallas del flow junto con el resto de los eventos de tu app. :::tip Para ver cuántos usuarios llegan a cada pantalla y dónde abandonan, no necesitas ningún código: el [embudo de flow](flow-funnel) ya lo muestra en el Adapty Dashboard. Usa esta guía para construir el mismo embudo en tu propia herramienta de análisis, o para combinar las pantallas del flow con eventos que Adapty no conoce. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty Capacitor SDK v4 o posterior**: El callback de analíticas de flow no existe en versiones anteriores. - **Una versión de flow publicada después de que se lanzara esta función**: Adapty genera los eventos de pantalla al publicar un flow, por lo que un flow publicado antes de eso no reportará nada. Si no llegan eventos a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Qué recibes \{#what-you-receive\} Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: Las vistas de pantalla llegan al mismo callback de análisis que todos los demás eventos del flow. Consulta [Gestionar eventos de flow y paywall](capacitor-handling-events) para saber dónde registrarlo. ## Reenvía cada vista de pantalla a tu sistema de analíticas \{#forward-every-screen-view-to-your-analytics\} Comprueba primero el nombre del evento antes de leer cualquier otro dato, ya que un flow notifica otros eventos a través del mismo callback. ```typescript showLineNumbers title="Capacitor" view.setEventHandlers({ onAnalytics(name, params) { if (name === 'flow_screen_showed') { const screenId = params.instanceId; const screenOrder = params.screen_order; // Send screenId and screenOrder to your analytics provider here. } return false; // keep the flow open }, }); ``` ## Ejecuta código cuando un usuario llega al final \{#run-code-when-a-user-reaches-the-end\} `is_last_screen` es `true` en una pantalla más allá de la cual el flow no puede avanzar. Úsalo para desbloquear contenido, marcar el onboarding como completado o enviar tu propio evento de finalización, sin necesidad de añadir una acción de botón en el builder. ```typescript showLineNumbers title="Capacitor" view.setEventHandlers({ onAnalytics(name, params) { if (name === 'flow_screen_showed' && params.is_last_screen) { // The user reached the end of the flow. } return false; // keep the flow open }, }); ``` Llegar a la última pantalla no es lo mismo que cerrar el flow. Un usuario puede ver la pantalla final y cerrar el flow sin realizar ninguna acción, y un flow con ramificaciones puede terminar en varias pantallas distintas. Para saber que el flow en sí ha desaparecido, usa `onDisappeared` en su lugar. --- # File: capacitor-flow-input --- --- title: "Procesar datos de flows en el SDK de Capacitor" description: "Guarda y utiliza los datos que tus usuarios introducen en los flows de tu app Capacitor con el SDK de Adapty." --- Cuando un usuario escribe en un campo de texto, responde un cuestionario o activa un interruptor en un [flow](adapty-flow-builder), el SDK pasa el valor a tu app a través de su callback de análisis. Las apps suelen usar esos datos para: - **Registrar usuarios en su propio backend**: Toma el correo electrónico y el nombre que un usuario introdujo en tu flow de onboarding, y crea su cuenta cuando el flow se cierre. - **Guardar respuestas y preferencias**: Registra lo que eligió el usuario para que tu app pueda usarlo más adelante — por ejemplo, [escríbelo en su perfil de Adapty](capacitor-setting-user-attributes) como atributos personalizados. - **Personalizar flows futuros**: Guarda las respuestas del cuestionario como atributos personalizados y, después, apunta a un placement posterior para que cada segmento vea un flow diferente, o un paywall distinto dentro de él. - **Mostrar valores calculados a partir de las respuestas**: Por ejemplo, calcula el IMC a partir del peso y la altura del usuario, y [pásalo a una etiqueta personalizada en el siguiente flow](capacitor-flow-custom-tags#example-show-a-bmi-from-onboarding-answers). - **Alimentar plataformas de analítica de terceros**: Envía las respuestas a Amplitude, Mixpanel o la herramienta de analítica de producto que utilices. Las entradas y los grupos seleccionables informan sus valores automáticamente. Para distinguir las entradas en tu código, asigna a cada entrada un **Element ID** significativo y a cada grupo seleccionable un **Group ID** en el builder. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: ## Antes de comenzar \{#before-you-start\} Necesitas: - **Adapty SDK v4 o posterior**: Los callbacks de flow no existen en versiones anteriores. - **Un flow creado en el [Flow & Paywall Builder](adapty-flow-builder)**: Solo los flows reportan valores de entrada a través de este callback. - **Una versión de flow publicada recientemente**: Un flow solo reporta valores de entrada si lo publicaste después de que esta función estuviera disponible. Si no llega nada a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Recibir valores de entrada \{#receive-input-values\} Los valores de entrada llegan al mismo handler que cualquier otro evento de analítica de un flow, con el nombre de evento `flow_user_input`. Registra `onAnalytics` junto con el resto de tus handlers de eventos del flow: ```typescript showLineNumbers title="Capacitor" view.setEventHandlers({ onAnalytics(name, params) { handleFlowInput(name, params); return false; // keep the flow open }, }); ``` El callback `onAnalytics` entrega todos los eventos de analítica de un flow, incluyendo [vistas de pantalla](capacitor-flow-screen-views). Para filtrar los eventos de entrada del usuario, compara `name` con `flow_user_input`. El parámetro que almacena el valor depende de `element_type`: | `element_type` | Parámetro que almacena el valor | Tipo TypeScript | |:---------------|:--------------------------------|:----------------| | `text_input`, `email_input`, `number_input`, `phone_input` | `value` | `string` | | `date_picker`, `time_picker`, `date_time_picker` | `value` | `number`, tiempo Unix en milisegundos. | | `single_choice`, `multi_choice` | `item_ids`, `item_titles` | `string[]` | | `toggle` | `value` | `boolean` | La sección [Qué recibes](#what-you-receive) enumera todos los parámetros y lo que reporta cada elemento del builder. Para confirmar que el callback se ejecuta, interactúa con el input en una versión de prueba de tu propia app. Si tu callback handler no recibe ningún evento, revisa los [prerequisitos](#before-you-start). Asegúrate de que el flow fue publicado después de que esta función estuviera disponible. ## Cuando tu app recibe el input \{#when-your-app-receives-the-input\} :::important El valor predeterminado de entrada o selección nunca llega a tu aplicación a través de este callback. Si un usuario acepta la opción marcada como **Set as default** y continúa, no se dispara ningún evento. No interpretes la ausencia del evento como "sin respuesta": el usuario simplemente dejó el valor predeterminado tal como estaba. ::: **Los siguientes elementos disparan este evento:** - Campos de texto, email, número y teléfono - Selectores de fecha, hora y fecha-hora - Grupos seleccionables de opción única y múltiple, y toggles **Los siguientes no lo hacen:** - Campos de contraseña, selecciones de productos y cambios de pestaña - Inputs dentro de un elemento Header, que se comparte entre pantallas - Grupos seleccionables con IDs de opción duplicados o faltantes, o con un ID de grupo reutilizado en otra pantalla. Ese tipo de grupo no envía nada en absoluto, en lugar de enviar parte de la respuesta. **El evento se dispara cuando:** - Un campo pierde el foco. Un campo vaciado reporta una cadena vacía; un campo que el usuario nunca editó no reporta nada. Un marcador de posición no es un valor. Si el usuario vuelve al campo, lo edita y lo abandona de nuevo, se genera un segundo evento. - El usuario cierra un selector tras elegir un nuevo valor. Cerrarlo sin cambios no reporta nada. - El usuario pulsa una opción o un toggle. Un evento de opción múltiple lista todas las opciones seleccionadas, por lo que deseleccionar la última envía dos arrays vacíos. **El evento no se dispara cuando:** - El usuario escribe. No hay flujo de pulsaciones de teclas, solo el valor que tiene el campo cuando pierde el foco. - El usuario envía o cierra el flow. Un valor que aún se está editando en ese momento puede perderse; la sección de [limitaciones de entrega](#delivery-and-limitations) explica cómo diseñar la última pantalla teniendo esto en cuenta. - Se establece un valor sin interacción del usuario. Una opción marcada como **Set as default** se preselecciona al abrirse la pantalla, y una acción **Set Variable** puede seleccionar una opción o rellenar un input a partir de otra interacción. Ninguna de las dos envía un evento; un input prerrellenado solo se reporta una vez que el usuario lo edita. ## Qué recibes \{#what-you-receive\} El callback entrega dos eventos. Filtra por `name` para mostrar únicamente los eventos `flow_user_input`. El payload JSON de respuesta tiene este aspecto: ```json { "name": "flow_user_input", "instanceId": "scr_registration", "isBackendEvent": false, "isCustomerEvent": true, "element_id": "email", "element_type": "email_input", "value": "jane@example.com" } ``` | Parámetro | Descripción | |:------------------|:------------| | `name` | `flow_user_input` para eventos de entrada, `flow_screen_showed` para vistas de pantalla. | | `instanceId` | El ID de la pantalla del input. Los IDs de elemento son únicos dentro de una pantalla, no en todo el flow. Si tu flow tiene inputs en más de una pantalla, combina `instanceId` con `element_id` al filtrar eventos. | | `element_id` | El **Element ID** del [input](builder-inputs-and-forms), o el **Group ID** del [grupo seleccionable](flow-selectable-elements). | | `element_type` | El tipo de elemento que envió el evento. Determina cuál de los parámetros siguientes contiene el valor del input. | | `value` | **Solo campos de texto, selectores y toggles.** El valor del input: una cadena para campos de texto, un entero para selectores, un booleano para toggles. | | `item_ids` | **Solo grupos seleccionables de elección única y múltiple.** Los **Element IDs** de las opciones seleccionadas, en el orden en que aparecen en el builder. Una entrada para un grupo de elección única; cualquier número para un grupo de elección múltiple. | | `item_titles` | **Solo grupos seleccionables de elección única y múltiple.** Los títulos de las opciones listadas en `item_ids`, en el mismo orden. Nunca está vacío: una opción sin título informa su ID en su lugar. | | `isCustomerEvent` | Indicador de utilidad, siempre `true` para este evento. Marca los eventos que el SDK entrega a tu callback. Resulta útil si un mismo handler reenvía todos los eventos del flow a tu sistema de análisis y filtras por este indicador en lugar de por `name`. | | `isBackendEvent` | Indicador de utilidad, siempre `false` para este evento. Marca los eventos que Adapty también registra para su propio análisis. `false` confirma que lo que los usuarios introducen llega a tu app y solo a ella — Adapty no lo recibe ni lo almacena. | Lo que reporta cada elemento: | En el builder | `element_type` | Parámetro que almacena el valor | Qué contiene | |:--------------|:---------------|:--------------------------------|:-------------| | Input de **Texto**, **Número**, **Número de teléfono** | `text_input`, `number_input`, `phone_input` | `value` | La cadena literal que escribió el usuario. Los números llegan como cadenas, no como tipos numéricos. | | Input de **E-mail** | `email_input` | `value` | La cadena literal que escribió el usuario, aunque no haya superado la validación de formato del builder. Valídala en tu lado antes de usarla. | | Input de **Contraseña** | ninguno | ninguno | No envía ningún evento. | | Input de **Fecha** | `date_picker` | `value` | Tiempo Unix en milisegundos, como entero, a medianoche local de la fecha seleccionada. | | Input de **Hora** | `time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Input de **Fecha y hora** | `date_picker` y `time_picker` | `value` | Dos elementos, un selector de fecha y un selector de hora. Cada uno envía su propio evento. | | Input cambiado a **Fecha y hora** en el desplegable **Type** | `date_time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Grupo de **elección única** | `single_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: un array con el Element ID de la opción seleccionada. `item_titles`: un array con el título de esa opción. | | Grupo de **elección múltiple** | `multi_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: los Element IDs de todas las opciones seleccionadas, en el orden en que aparecen en el builder. `item_titles`: sus títulos, en el mismo orden. Ambos arrays están vacíos cuando no hay nada seleccionado. | | Grupo de **Toggle** | `toggle` | `value` | Un booleano. | Para bifurcar según una respuesta, compara `item_ids`, no `item_titles`. El título es derivado: el **Element Title** de la opción si lo definiste, si no su texto en tu idioma predeterminado, y si no su Element ID. Un usuario que vio el flow en otro idioma vio un texto diferente. ## Ejemplos de eventos \{#event-examples\} Estos ejemplos muestran las propiedades disponibles en cada evento, con valores ilustrativos en los comentarios. <Details> <summary>Entrada de texto, email, número y teléfono (Haz clic para expandir)</summary> ```typescript onAnalytics(name, params) { name; // 'flow_user_input' params.name; // 'flow_user_input' params.instanceId; // 'scr_J260KU5q' params.isCustomerEvent; // true params.isBackendEvent; // false params.element_id; // 'email' params.element_type; // 'email_input' params.value; // 'jane@example.com' (string) } ``` </Details> <Details> <summary>Selectores de fecha, hora y fecha-hora (Click to expand)</summary> ```typescript onAnalytics(name, params) { params.element_id; // 'birthday' params.element_type; // 'date_picker' params.value; // 645408000000 (Unix milliseconds — 1990-06-15, local midnight) } ``` </Details> <Details> <summary>Single choice (Click to expand)</summary> ```typescript onAnalytics(name, params) { params.element_id; // 'experience' params.element_type; // 'single_choice' params.item_ids; // ['pro'] params.item_titles; // ['I train professionally'] } ``` </Details> <Details> <summary>Multi choice (Haz clic para expandir)</summary> ```typescript onAnalytics(name, params) { params.element_id; // 'interests' params.element_type; // 'multi_choice' params.item_ids; // ['sports', 'music'] params.item_titles; // ['Sports', 'Music'] } ``` </Details> <Details> <summary>Toggle (Haz clic para expandir)</summary> ```typescript onAnalytics(name, params) { params.element_id; // 'reminders' params.element_type; // 'toggle' params.value; // true (boolean) } ``` </Details> ## Entrega y limitaciones \{#delivery-and-limitations\} :::warning Los flows envían valores sin procesar: direcciones de correo electrónico, números de teléfono y cualquier otra cosa que el usuario escriba. Trata todo lo que entregue el callback de analíticas como datos personales y no los almacenes en ningún lugar donde no guardarías el correo electrónico de un usuario. ::: - **El último valor prevalece**: recibes un evento por campo con el valor con el que el usuario se quedó, no una secuencia tecla a tecla. Si edita un campo, solo llega la última versión. - **Sin garantía de envío**: los valores te llegan a medida que el usuario avanza por el flow, y puede salir en cualquier momento. Espera a que el flow se cierre antes de considerar un conjunto de respuestas como completo. - **Entrega de mejor esfuerzo**: si un usuario cierra el flow o minimiza la app mientras un campo tiene el foco o un selector está abierto, ese valor puede perderse. Para que el valor del último campo sea fiable, termina el flow con una pantalla que no tenga entradas ni selectores, y cierra el flow mediante una acción explícita del usuario en lugar de hacerlo automáticamente al aparecer esa pantalla. Pasar a la pantalla final quita el foco del campo anterior, que es lo que envía su valor. Guarda cada valor de entrada tal como lo recibe tu handler y envía el conjunto completo cuando el flow se cierre. Para detectar ese momento, registra un handler `onDisappeared` junto a `onAnalytics`. Se ejecuta cuando la vista del flow se cierra, ya sea porque el usuario completó el flow o lo cerró a medias. ## Casos de uso \{#use-cases\} ### Registrar usuarios en tu backend \{#register-users-on-your-backend\} Recoge los valores a medida que llegan y envíalos en cuanto el flow se cierre, para que una sola petición contenga el conjunto completo de respuestas. El flow desaparece tanto si el usuario lo completó como si lo abandonó a mitad. Comprueba que tienes los campos que necesitas antes de llamar a tu backend. El flow no puede mostrar errores procedentes de tu backend. El valor de retorno del handler solo cierra la vista del flow, y el SDK no dispone de ningún método para enviar datos a un flow en ejecución. Si el registro falla —por ejemplo, porque el email ya está en uso—, muestra el error en tu propia UI después de que el flow se cierre. ```typescript showLineNumbers title="Capacitor" const flowAnswers: Record<string, string> = {}; view.setEventHandlers({ onAnalytics(name, params) { if (name === 'flow_user_input' && typeof params.value === 'string') { flowAnswers[params.element_id as string] = params.value; } return false; }, onDisappeared() { if (flowAnswers.email) { // Send flowAnswers to your backend here to create the account. } return false; }, }); ``` ### Enriquece los perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Para vincular lo que un usuario introduce con su perfil y evitar pedirle los mismos datos dos veces, [actualiza el perfil de usuario](capacitor-setting-user-attributes) a medida que lleguen los valores. Por ejemplo, si tu flow tiene un campo de texto con el ID de elemento `name` y un campo de correo electrónico con el ID de elemento `email`: ```typescript showLineNumbers title="Capacitor" function handleFlowInput(name: string, params: Record<string, unknown>) { if (name !== 'flow_user_input') return; if (typeof params.value !== 'string') return; const profileParams: Partial<AdaptyProfileParameters> = {}; switch (params.element_id) { case 'name': profileParams.firstName = params.value; break; case 'email': profileParams.email = params.value; break; default: return; } adapty.updateProfile(profileParams).catch((error) => { // handle the error }); } ``` ### Personalizar flows mostrados más adelante \{#customize-flows-shown-later\} Las respuestas del quiz también pueden decidir qué ve un usuario en un [placement](placements) posterior: un flow diferente o un paywall distinto dentro de él. Por ejemplo, pregunta a los usuarios sobre su experiencia con el deporte en tu flow de onboarding y luego muestra a cada grupo su propio flow con productos y textos diferentes. 1. Añade un [quiz](onboarding-quizzes) a tu flow. Asigna al [grupo seleccionable](flow-selectable-elements) el Group ID `experience` y a cada opción un Element ID significativo. 2. Gestiona las respuestas y [establece atributos personalizados](capacitor-setting-user-attributes) para el usuario. ```typescript showLineNumbers title="Capacitor" function handleFlowInput(name: string, params: Record<string, unknown>) { if (name !== 'flow_user_input') return; if (params.element_id !== 'experience') return; const optionId = (params.item_ids as string[])[0]; adapty .updateProfile({ // Set the custom attribute 'experience' to the option the user selected // (beginner, amateur, or pro). codableCustomAttributes: { experience: optionId }, }) .catch((error) => { // handle the error }); } ``` 3. [Crea un segmento](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade una [audiencia](audience) para cada segmento. 5. [Muestra el flow](capacitor-present-paywalls) de ese placement en tu app. --- # File: capacitor-flow-custom-tags --- --- title: "Pasar datos a flows en el SDK de Capacitor" description: "Pasa un valor desde tu app de Capacitor a un flow y muéstralo en un elemento de texto." --- Un [custom tag](flow-custom-tags) es una variable personalizada con un valor proveniente de tu app. Después de seleccionar **Value comes from outside the app** en la configuración de una variable, tu app puede establecer su valor a través del SDK de Adapty. Por ejemplo, un elemento de texto contiene "Coins:" y un custom tag. Si tu app pasa "120" para el tag, los usuarios verán "Coins: 120". ## Antes de empezar \{#before-you-start\} Necesitas el SDK de Adapty v4 o posterior. Las versiones anteriores a v4 no muestran flows. ## Añadir etiquetas personalizadas al flow \{#add-custom-tags-to-the-flow\} Para mostrar los valores de tu app en un flow, configura etiquetas personalizadas en el builder: 1. [Crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) para cada valor que tu app envíe. <ZoomImage id="flow-custom-tag-create.webp" width="500px" alt="Formulario para crear una variable con nombre, descripción, tipo de valor String y valor inicial" /> 2. [Inserta cada etiqueta en un elemento de texto](flow-custom-tags#add-a-custom-tag-to-a-text-element). 3. Anota el nombre completo de cada etiqueta, incluido su grupo. El código de tu app pasa cada valor con ese nombre. Por ejemplo, si el grupo `user` contiene una etiqueta `streak`, el nombre completo de la etiqueta es `user.streak`. <ZoomImage id="flow-custom-tag-full-name.webp" width="500px" alt="Pestaña personalizada del desplegable Añadir variable con la etiqueta streak en el grupo user" /> ## Pasar valores de etiquetas personalizadas \{#pass-custom-tag-values\} Pasa los valores de tu app al parámetro `customTags` de `createFlowView`, como un objeto: - Cada clave es el nombre completo de una etiqueta personalizada, como `user.streak`. - Cada valor es el texto que los usuarios verán en lugar de la etiqueta. - Cada valor debe ser legible para las personas, como "1,250" y no "1250.0". Adapty no lo formatea ni traduce. El SDK lee los valores una sola vez, cuando crea la vista, por lo que no cambian mientras el flow está abierto. ```typescript showLineNumbers title="Capacitor" const customTags = { 'username': 'DragonSlayer42', 'coins': '1,250', }; try { const view = await createFlowView(flow, { customTags }); await view.present(); } catch (error) { // handle the error } ``` Para probar tus etiquetas personalizadas, abre el flow en tu app después de añadir el código que establece sus valores. El canvas del builder y la [vista previa en dispositivos](paywall-device-compatibility-preview#preview-on-devices) solo muestran los valores iniciales: la app móvil de Adapty no establece los valores de las etiquetas personalizadas. Si tu app ya define valores de etiquetas personalizadas para paywalls del antiguo Paywall Builder, revisa [cómo difieren las etiquetas personalizadas en los flows](migrate-to-flows#custom-tags-from-the-old-paywall-builder). ## Lo que los usuarios ven sin un valor \{#what-users-see-without-a-value\} Los usuarios ven el **Valor inicial** de una etiqueta personalizada como valor de respaldo en los siguientes casos: - Tu app no pasa ningún valor para la etiqueta. - El usuario no ha actualizado tu app a una versión que envíe el valor. - El nombre en el código de tu app no coincide con el nombre completo de la etiqueta, incluidas las mayúsculas y minúsculas. - Otra etiqueta personalizada en el mismo elemento de texto no tiene valor. En ese caso, todas las etiquetas personalizadas de ese elemento de texto muestran su valor inicial. Para no mostrar nada en lugar de una etiqueta, pasa una cadena vacía. Adapty trata una cadena vacía como un valor, por lo que el valor de respaldo no aparece. ## Ejemplo: mostrar el IMC a partir de las respuestas del onboarding \{#example-show-a-bmi-from-onboarding-answers\} Un flow puede recoger respuestas de los usuarios, y una etiqueta personalizada puede mostrar un valor de tu app. Combina ambas cosas para mostrar a los usuarios un resultado que tu app calcula a partir de sus respuestas, como una puntuación, un plan recomendado o una métrica de salud: 1. Un flow recoge las respuestas y las envía a tu app. 2. Tu app calcula el resultado. 3. Tu app pasa el resultado al siguiente flow como valor de etiqueta personalizada. En este ejemplo, un flow de onboarding recoge el peso y la altura del usuario. Cuando el usuario cierra el flow de onboarding tras su última pantalla, tu app calcula su índice de masa corporal (IMC). Luego abre un flow de paywall que muestra el IMC. ### Configura los flows \{#set-up-the-flows\} 1. [Crea dos flows](paywall-builder-templates): un flow de onboarding que recoja el peso y la altura del usuario, y un flow de paywall que muestre su IMC. 2. Añade dos campos de tipo [**Number**](builder-inputs-and-forms) al flow de onboarding: - Uno para el peso en kilogramos, con el **Element ID** `weight`. - Uno para la altura en centímetros, con el **Element ID** `height`. En ambos campos, establece el **Format** como **Integer**. Un campo envía su valor como texto, exactamente tal como lo escribe el usuario, y el [código de ejemplo](#pass-the-bmi-to-the-paywall) no interpreta "70.5" y "70,5" de la misma manera. Los números enteros no tienen separador decimal, por lo que el código los lee igual en cualquier idioma. 3. Para asegurarte de que tu app recibe ambos valores, termina el flow de onboarding con una pantalla que no tenga campos. En esa pantalla, añade un botón con la acción [**Close flow**](onboarding-actions#close-flow). Los campos envían sus valores cuando pierden el foco o cuando el usuario pasa a otra pantalla. La acción Close flow no los envía. El código de ejemplo solo abre el paywall si el usuario llegó a la última pantalla. 4. En el flow de paywall, [crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) llamada `bmi`. En el campo **Initial value**, introduce un guion (`—`). Los usuarios verán el guion si tu app no pasa ningún IMC. 5. Añade un elemento de texto con las palabras "Your BMI:" al flow de paywall. Después de esas palabras, [inserta la etiqueta personalizada `bmi`](flow-custom-tags#add-a-custom-tag-to-a-text-element). 6. Añade el flow de onboarding a un [placement](placements), por ejemplo `onboarding`. Añade el flow de paywall a un placement con el ID `bmi_paywall`. El código de [Pass the BMI to the paywall](#pass-the-bmi-to-the-paywall) obtiene el paywall usando este ID. ### Pasa el IMC al paywall Recopila el peso y la altura de los eventos `flow_user_input` del flow de onboarding, como se describe en [Procesar datos de flows](capacitor-flow-input). Para saber que el usuario llegó a la última pantalla, observa el evento `flow_screen_showed` con `is_last_screen` establecido en `true`, como se describe en [Rastrear vistas de pantalla del flow](capacitor-flow-screen-views). Cuando el usuario cierre el flow de onboarding después de esa pantalla, calcula el IMC. Luego crea la vista del paywall y pasa el IMC para la etiqueta personalizada `bmi`. Registra los siguientes handlers en `onboardingView`, la vista que creas para el flow de onboarding: ```typescript showLineNumbers title="Capacitor" const bodyAnswers: Record<string, string> = {}; let reachedLastScreen = false; await onboardingView.setEventHandlers({ onAnalytics(name, params) { if (name === 'flow_screen_showed' && params.is_last_screen) { reachedLastScreen = true; } if (name === 'flow_user_input' && typeof params.value === 'string') { bodyAnswers[params.element_id as string] = params.value; } return false; }, onDisappeared() { // Number inputs send their values as strings. const weight = Number(bodyAnswers.weight); const height = Number(bodyAnswers.height); const finished = reachedLastScreen; delete bodyAnswers.weight; delete bodyAnswers.height; reachedLastScreen = false; if (finished && weight > 0 && height > 0) { const heightInMeters = height / 100; const bmi = weight / (heightInMeters * heightInMeters); showBmiPaywall(bmi.toLocaleString(undefined, { maximumFractionDigits: 1 })); } return false; }, }); async function showBmiPaywall(bmi: string) { try { const flow = await adapty.getFlow({ placementId: 'bmi_paywall' }); const view = await createFlowView(flow, { customTags: { 'bmi': bmi } }); await view.present(); } catch (error) { // handle the error } } ``` `toLocaleString` usa el separador decimal de la configuración regional del usuario: "22.7" en inglés, "22,7" en alemán. ### Guardar el IMC en el perfil del usuario \{#save-the-bmi-to-the-users-profile\} Para mostrar flows diferentes según el IMC del usuario, guárdalo como un [atributo personalizado](capacitor-setting-user-attributes#custom-user-attributes) de su perfil. En el Adapty Dashboard, [crea un atributo personalizado de tipo **Number**](segments#custom-attributes) con la clave `bmi`. Luego muestra cada flow a una [audiencia](audience) creada a partir de ese atributo. Llama al método `updateProfile` en `onDisappeared`, justo después de la línea que calcula el `bmi`. Pasa ese número, no la cadena formateada, para que un segmento pueda comparar el BMI con un umbral. `onDisappeared` no puede ser `async`, así que gestiona el resultado con `.catch` en lugar de `await`: ```typescript showLineNumbers title="Capacitor" adapty .updateProfile({ codableCustomAttributes: { bmi } }) .catch((error) => { // handle the error }); ``` --- # File: capacitor-use-fallback-paywalls --- --- title: "Capacitor - 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" --- 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 elige la localización para un flow o un onboarding, 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 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\} En SDK v4, los flows y los onboardings coinciden con los códigos de idioma de forma diferente: los flows se localizan mediante el SDK en el dispositivo, y los onboardings mediante el servidor de Adapty. ### Flows y paywalls Un paywall que Adapty renderiza se entrega como un flow en el SDK v4, por lo que la regla a continuación aplica a ambos. La coincidencia es exacta. El SDK compara el código que pasas con los códigos de localización del flow carácter por carácter: no cambia las mayúsculas y minúsculas, no reemplaza los guiones bajos (`_`) por guiones (`-`) y no recurre al subtag de idioma. Para un flow con una localización `pt-br`, solo `pt-br` coincide: `pt-BR`, `pt_BR` y `pt-PT` no lo hacen. Cuando el código no coincide con ninguna localización, el flow se renderiza silenciosamente en su [idioma predeterminado](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) — el SDK no devuelve ningún error ni registra ninguna advertencia. Cuando el código coincide, Adapty combina la localización con la predeterminada: las cadenas de texto y los recursos que no están definidos en la localización coincidente se toman de la localización predeterminada. Omitir el código de idioma no es lo mismo que pedir la localización predeterminada del flow: el SDK sustituye un `en` fijo. Un flow cuyo idioma predeterminado es `de` seguirá renderizándose en `en` si tiene una localización `en`, y solo recurrirá a `de` cuando no la tenga. :::warning Pasa el código de idioma exactamente como está configurado en el dashboard — subetiquetas en minúsculas separadas por guiones. No pases un identificador de idioma del sistema tal cual: `navigator.language` devuelve `pt-BR`, y eso hace que recurra a la localización predeterminada. Convierte el valor en tu app antes de pasarlo. ::: ### Onboardings Los onboardings se localizan en el servidor, y las reglas del servidor toleran otros formatos. Cuando pasas un `locale` a [`getOnboarding`](capacitor-get-onboardings): 1. La cadena de configuración regional 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 configuración regional que coincida exactamente 3. Si no se encuentra ninguna coincidencia, Adapty toma la subcadena anterior al primer guión (`pt` para `pt-br`) y busca la localización que coincida 4. Si tampoco se encuentra ninguna coincidencia, Adapty devuelve el contenido en la configuración regional predeterminada del onboarding Esta forma, `pt_BR`, `pt-BR` y `pt-br` se resuelven todas en la misma localización de onboarding. ## Implementación de localizaciones \{#implementing-localizations\} En el SDK v4, no se pasa un código de idioma al obtener un flow — `getFlow` devuelve el flow con todas sus localizaciones, y Adapty aplica una cuando se construye la vista del flow. - **Flows creados en el builder**: el SDK no lee el idioma del dispositivo, así que resuélvelo en tu app y pásalo como opción `locale` en `createFlowView`. Es opcional — si lo omites, el flow se renderiza en `en`, o en su [idioma predeterminado](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) cuando el flow no tiene localización en `en`. ```typescript showLineNumbers import { createFlowView } from '@adapty/capacitor'; const view = await createFlowView(flow, { locale: 'es' }); ``` `view.locale` informa la localización con la que se construyó la vista. Tanto la opción `locale` como `view.locale` requieren el SDK de Capacitor 4.1 o posterior, y `view.locale` es `undefined` en versiones anteriores. El [manejador `onAppeared`](capacitor-handling-events#the-view-argument) reporta el mismo valor. - **Paywalls personalizados (Remote Config)**: `getFlow` devuelve cada localización configurada en `flow.remoteConfigs`. Cada entrada tiene un código `lang` y un objeto `data`. Selecciona la entrada que corresponda al usuario, con tu propio mecanismo de respaldo: ```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 ``` Adapty almacena esos códigos `lang` en el formato descrito en [Estándar de código de idioma en Adapty](#locale-code-standard-at-adapty). El SDK no compara los Remote Configs con un idioma concreto, por lo que es tu app quien decide qué entrada aplicar. </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 desde el 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. A continuación, 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 anterior al primer guión (`pt` en `pt-br`) y se busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, se devuelve el contenido del idioma predeterminado del paywall 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 Observer en el SDK de Capacitor" description: "Presenta flows en modo Observer en tu app de Capacitor mientras gestionas las compras con tu propio código." --- Si usas el [modo Observer](observer-vs-full-mode), eres responsable de tu propia lógica de compras. Pero puedes delegar la interfaz del paywall al [Flow & Paywall Builder](adapty-flow-builder). Diseña la pantalla en el builder y el SDK de Adapty la renderiza de forma nativa. Actualiza los textos, el diseño y los precios cuando quieras, sin publicar una nueva versión de la app. Esta guía muestra cómo presentar un flow y gestionar los callbacks de compra. Si Adapty gestiona las compras por ti, consulta [Mostrar flows y paywalls](capacitor-present-paywalls) en su lugar. ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty Capacitor SDK 4.1 o posterior**: Las versiones anteriores no pueden renderizar flows. Para migrar una app existente, consulta la [guía de migración](migration-to-capacitor-sdk-v4). - **`observerMode` establecido en `true`**: Consulta la [guía de instalación del SDK de Capacitor](sdk-installation-capacitor#activate-adapty-module-of-adapty-sdk). - **Integración inicial con el store**: Configura Adapty [con App Store](initial_ios) y [con Google Play](initial-android). - **Un flow publicado con productos**: Créalo en el [Flow & Paywall Builder](adapty-flow-builder) y asígnale [productos](create-product). - **Un placement para el flow**: [Crea un placement](create-placement) y asigna tu flow a él. - **El flow obtenido en tu app**: [Obtén el flow y su configuración](capacitor-get-pb-paywalls) antes de mostrarlo. ## Implementación \{#implementation\} 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 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 existe un objeto resolver separado — los manejadores forman parte de los [manejadores de eventos](capacitor-handling-events) habituales, así que configúralos 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() | 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. Presenta el 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 se activó 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 de origen de la compra. ::: --- # File: capacitor-implement-paywalls-manually --- --- title: "Implementar paywalls personalizados en el SDK de Capacitor" description: "Aprende cómo implementar paywalls personalizados 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 buscas la forma más sencilla de habilitar compras, usa el [Adapty Flow & Paywall Builder](capacitor-quickstart-paywalls). Con el Flow & 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) – cualquier cosa que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls personalizados**](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 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 distintos paywalls a distintos usuarios. Asegúrate de entender estos conceptos aunque trabajes con tu propio paywall. Básicamente, son tu forma de gestionar los productos que vendes en tu app. Para implementar tu propio paywall, necesitarás crear un **custom paywall** y añadirlo a un **placement**. Esta configuración te permite obtener tus productos. Para entender qué tienes que 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 personalizados y productos 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 paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls personalizados. Para obtener orientación sobre cómo obtener flows o paywalls personalizados en el **Flow & Paywall Builder** o el **old Paywall Builder**, consulta [Obtener flows y paywalls 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 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-capacitor) en tu app 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>`fetchPolicy` define qué capa lee el SDK primero, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud falla. 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 `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. Es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos sin importar 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>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre ambas: lee primero la caché mientras la copia almacenada sea más reciente que el valor de `maxAgeSeconds` que le pases junto a ella, y va al servidor cuando sea más antigua.</p><p></p><p>Ten en cuenta que la caché se mantiene 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 flows y paywalls en dos capas: la caché con 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 limitada.</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 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 ligeramente después de lo especificado en `loadTimeoutMs`, ya que la operación puede consistir en 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>predeterminado: `'reload_revalidating_cache_data'`</p> | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. Es posible que los usuarios no obtengan los datos más recientes, pero tendrá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>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor de `maxAgeSeconds` que le pases, y va al servidor una vez que sea más antigua.</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 paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls personalizados. Para obtener orientación sobre cómo obtener paywalls creados con Paywall Builder, consulta [Obtener 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 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-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>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](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>predeterminado: `'reload_revalidating_cache_data'`</p> | <p>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor `maxAgeSeconds` que le pases junto a ella, y va al servidor una vez que sea más antigua.</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> | | **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> | **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` 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` 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>`fetchPolicy` determina qué capa consulta primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. Recomendamos esta opción porque garantiza que los 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, prueba `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada almacenado. Es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos sin importar 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>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor de `maxAgeSeconds` que le pases, y va al servidor una vez que supera ese tiempo.</p><p></p><p>Ten en cuenta que la caché se conserva al reiniciar la app y solo se borra cuando se reinstala la aplicación o mediante una limpieza manual.</p> | </SDKv3> --- # File: present-remote-config-paywalls-capacitor --- --- title: "Presentar paywalls personalizados en el SDK de Capacitor" description: "Descubre cómo presentar paywalls personalizados en el SDK de Adapty para Capacitor y 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 solicitud: | Parámetro | Presencia | Descripción | | :---------- | :------- | :--------------------------------------------------------- | | **paywall** | obligatorio | Un objeto `AdaptyPaywall`. | </SDKv3> --- # File: capacitor-making-purchases --- --- title: "Realizar compras en la aplicación móvil con el SDK de Capacitor" 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, presentar un paywall solo gestiona las compras por sí solo cuando Adapty renderiza la pantalla, es decir, en un [flow](adapty-flow-builder) o en un paywall del [antiguo Paywall Builder](adapty-paywall-builder). Si renderizas la pantalla en tu propio código, 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. 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 **¿Adapty renderiza tu pantalla?** Para un [flow](adapty-flow-builder) o un paywall de [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 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>Acerca de los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios específicos. A diferencia de las ofertas regulares que se aplican automáticamente, los códigos de oferta se distribuyen fuera de la app — a través de campañas de correo electrónico, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, siguiendo 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 realiza al precio completo. - **Pay as you go** — el usuario paga un precio con descuento en cada ciclo de facturación durante un período determinado, luego la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único con descuento por toda la duración de la oferta, luego 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`. Una vez que el período de oferta termina y la suscripción se renueva al precio completo, la etiqueta ya no está presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Solución de problemas de discrepancias en 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 con descuento de la oferta, verifica lo siguiente en App Store Connect: - El código de oferta tiene el precio correcto configurado para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está establecido para el país o región específico 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 en su lugar. Puedes filtrar y verificar las transacciones con códigos 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 reemplazan con más funcionalidades: elegibilidad configurable, fechas de vencimiento y hasta 1 millón de códigos por trimestre. Si anteriormente usabas códigos promocionales para compras in-app, haz la transición a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app por versión) otorgaban 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ódigos promocionales — enviaba el precio completo del producto en el recibo. Como resultado, Adapty registraba estas transacciones al precio completo, lo que causaba discrepancias de ingresos 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. Dado que estos códigos están ahora obsoletos, haz la transición 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}` ::: ## Compras in-app promocionadas desde el App Store \{#in-app-purchases-from-the-app-store\} :::info Tu app puede gestionar las compras in-app promocionadas desde la versión 4.1.1 del SDK, en **iOS 16.4** o posterior. En versiones inferiores a iOS 16.4, el evento nunca se activa y las compras promocionadas se completan por sí solas. El evento tampoco se activa en Android. ::: Cuando un usuario inicia una compra desde la página de tu producto en App Store y la transacción se transfiere a tu app, el SDK la completa automáticamente: la pantalla del sistema de compras de Apple aparece de inmediato y Adapty procesa la transacción como cualquier otra compra. Esto no requiere ningún código de tu parte. Si el producto promocionado incluye una oferta de suscripción, el SDK la aplica automáticamente en el momento de la compra. La oferta se lee desde el intent de compra de App Store, que la expone en iOS 18.0 y versiones posteriores. En iOS 16.4–17.x, la compra se realiza al precio base. Para gestionar tú mismo el proceso de compra —por ejemplo, para mostrar primero tu propia pantalla— escucha el evento `'onPromotedPurchaseReceived'` y pasa el producto a `makePromotedPurchase`: ```typescript showLineNumbers const listener = await adapty.addListener('onPromotedPurchaseReceived', async ({ product }) => { const result = await adapty.makePromotedPurchase({ product }); // process the purchase result }); ``` :::warning Mientras tu listener esté registrado, el SDK deja de completar las compras promocionadas por ti. Si tu handler nunca llama a `makePromotedPurchase`, la compra nunca se realiza: el App Store entrega el producto a tu app y espera. ::: `makePromotedPurchase` no recibe parámetros de compra — un producto promocionado proviene del App Store en lugar de un paywall, por lo que no tiene contexto de paywall. Devuelve el mismo [`AdaptyPurchaseResult`](https://capacitor.adapty.io/types/adaptypurchaseresult) que `makePurchase`. Llamar a `adapty.removeAllListeners()` elimina el listener de compras promocionadas junto con el resto, y la finalización automática del SDK retoma su lugar. Vuelve a registrar el listener si tu aplicación sigue siendo responsable de la finalización. --- # File: capacitor-restore-purchase --- --- title: "Restaurar compras en una app móvil con el SDK de Capacitor" 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 función 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 función es especialmente útil para usuarios que hayan desinstalado y vuelto a instalar la app, o que hayan cambiado a un nuevo dispositivo y quieran acceder a su contenido previamente comprado sin pagar de nuevo. :::note Si creaste la pantalla con un builder, no necesitas restaurar las compras manualmente. Conecta el control de restauración — la acción **Restore purchases** en el Flow & Paywall Builder, o un enlace o botón de restauración en el antiguo — y el SDK completa la restauración. ::: Si creaste la pantalla tú mismo, 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 el SDK v4, también puedes presentar flows y paywalls renderizados por Adapty en modo Observer: establece los manejadores de eventos `onObserverPurchaseInitiated` y `onObserverRestoreInitiated` 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](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`. | ## Uso de flows y paywalls de Adapty en el Modo Observador \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los flows, paywalls y funciones de pruebas A/B de Adapty, puedes hacerlo, pero requiere algo de configuración adicional en el modo Observador. Esto es lo que necesitas hacer además de los pasos anteriores: 1. Muestra los [paywalls personalizados](present-remote-config-paywalls-capacitor) como de costumbre. Para los flows y paywalls creados en el Flow & Paywall Builder, consulta [Presentar flows en modo Observer](capacitor-present-flows-in-observer-mode). 2. [Asocia los flows y paywalls](report-transactions-observer-mode-capacitor) con las transacciones de compra. --- # File: report-transactions-observer-mode-capacitor --- --- title: "Informar transacciones en Modo Observador en el SDK de Capacitor" description: "Informa transacciones de compra en el Modo Observador de Adapty para obtener información sobre usuarios y seguimiento de ingresos en el SDK de Capacitor." --- En el modo Observer, el SDK de Adapty no puede registrar por sí solo las compras realizadas a través de tu sistema de compras existente. Debes informar las transacciones desde tu 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 utilizas flows y paywalls de Adapty, incluye el `variationId` al informar de una transacción. Esto vincula la compra con el flow o paywall que la desencadenó, garantizando una analítica precisa. ```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`. | --- # 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 correo electrónico, número de teléfono, etc., para el usuario de tu app. Luego puedes usar estos atributos para crear [segmentos](segments) de usuario o simplemente consultarlos 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: "Comprobar el estado de la suscripción en el SDK de Capacitor" 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 Capacitor." --- Con Adapty, hacer seguimiento del estado de la suscripción es muy sencillo. No tienes que insertar manualmente IDs de producto en tu código. En su lugar, puedes comprobar fácilmente el estado de la 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 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> ## 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 a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: <CustomDocCardList /> --- # File: capacitor-get-onboardings --- --- title: "Obtener onboardings en el SDK de Capacitor" description: "Aprende cómo recuperar onboardings en Adapty para Capacitor." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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: "Presentar onboardings en el SDK de Capacitor" description: "Descubre cómo presentar onboardings en Capacitor para aumentar las conversiones y los ingresos." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: Si has personalizado un onboarding con el builder, no necesitas preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Ese onboarding ya contiene tanto lo que debe mostrarse como la forma en que debe mostrarse. Antes de comenzar, 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: "Gestionar eventos de onboarding en Capacitor SDK" description: "Gestiona eventos relacionados con el onboarding en Capacitor usando Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 que: 1. Has [creado un onboarding](create-onboarding). 2. Has 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 el SDK de Capacitor" description: "Guarda y usa datos de onboardings en tu app de Capacitor con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: Cuando tus usuarios responden a una pregunta de un cuestionario o introducen datos en un campo de entrada, 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 las acciones [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 lanzar la app, primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `adapty.activate({ apiKey: '...', params: { customerUserId: '...' } })` | Al lanzar 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 lanzar la app, después del paso 1, si no tienes el ID de usuario del cliente (o no 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 se asignen al perfil correcto. | | 4 | `await adapty.identify({ customerUserId: 'YOUR_USER_ID' })` | 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` generan `#3006 profileWasChanged`. | | 5 | `getPaywall` (`getFlow` en SDK v4), `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution` (`updateExternalAttribution` en SDK 4.1), `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 flows y paywalls en el SDK de Capacitor" description: "Obtén flows y paywalls de Adapty de forma fiable: temporización, caché y patrones de respaldo para Capacitor." --- Una obtención fiable de flows o paywalls en Capacitor hace tres cosas: renderiza rápido, devuelve la variación según la audiencia y recurre al respaldo sin problemas cuando la red es lenta. Las siguientes reglas cubren la temporización, el 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). ::: ## 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 de forma simultánea al iniciar. | La precarga masiva bloquea el hilo principal y provoca una pantalla en negro durante el pico. | | Obtén `getFlow` después de que la atribución haya tenido oportunidad de resolverse — por ejemplo, 1–2 segundos después de `activate` o cuando se active el listener `onLatestProfileLoad`. | Llames a `getFlow` al inicio de la app en `App.tsx`. | La atribución aún no ha llegado. El flow se resuelve con la audiencia predeterminada y omite silenciosamente los segmentos y la personalización de ASA. | | Establece un `loadTimeoutMs` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | Esperes indefinidamente en `getFlow`. | Sin un tiempo de espera, los usuarios con mala conexión ven una pantalla en blanco hasta que la red responda — o cierran la app. | Consulta [Obtener paywalls y productos personalizados](fetch-paywalls-and-products-capacitor) para la referencia de los parámetros `fetchPolicy` y `loadTimeoutMs`, y [Placements](placements) para elegir el placement adecuado. ## Mostrar el contenido multimedia de la primera pantalla desde el bundle de la app \{#show-first-screen-media-from-the-app-bundle\} Un flow descarga sus imágenes y vídeos desde Adapty. Para mostrar al instante el contenido multimedia de la primera pantalla, sírvelo directamente desde el bundle de la app. Es una buena forma de reutilizar recursos que ya tienes incluidos, como los visuales de un onboarding nativo existente. 1. En el Flow & Paywall Builder, establece un [ID de multimedia personalizado](custom-media#custom-media-id) en la imagen o el vídeo. El archivo que subas allí se usará como respaldo. 2. Añade el archivo a los proyectos nativos: el bundle de la app iOS y la carpeta `assets` de Android. 3. Cuando crees la vista del flow, pasa el archivo empaquetado para ese ID en `customAssets`: ```typescript showLineNumbers // "welcome_video" is the custom media ID set in the Flow & Paywall Builder const bundledAssets: Record<string, AdaptyCustomAsset> = { 'welcome_video': { type: 'video', fileLocation: { ios: { fileName: 'welcome.mp4' }, android: { relativeAssetPath: 'videos/welcome.mp4' }, }, }, }; const view = await createFlowView(flow, { customAssets: bundledAssets }); ``` Los archivos incluidos en el bundle aumentan el tamaño de descarga de tu app, así que incluye solo los archivos multimedia que el usuario ve primero. Los archivos multimedia que no incluyas en el bundle aparecen igualmente de forma inmediata: el layout del flow incluye una copia en baja resolución de cada imagen, incluido el fotograma estático de un vídeo, y la muestra hasta que se carga el archivo completo. Para la referencia completa de `customAssets`, consulta [Personalizar assets](capacitor-get-pb-paywalls#customize-assets). ## Ajusta para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad habitualmente deficiente (zonas rurales, transporte, regiones con problemas de enrutamiento): - Establece `fetchPolicy: 'return_cache_data_else_load'` en cada petició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 respaldo cuando se agote el tiempo. - No condicionales la visualización del flow a `adapty.getProfile()`. Llama a `getFlow` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: capacitor-show-aa-targeted-paywall --- --- title: "Mostrar un paywall dirigido por Apple Ads en el primer lanzamiento con el SDK de Capacitor" description: "Muestra un paywall de inmediato y actualízalo para usuarios de Apple Ads una vez que se aplique la atribución en Capacitor, usando AdaptyProfile.appliedExternalAttributionProviders." --- Este artículo aplica a la compilación de iOS de tu app. La atribución de Apple Ads solo existe en iOS. 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 `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 obtienen la variante segmentada y todos los demás ven un paywall sin espera. `AdaptyProfile.appliedExternalAttributionProviders` te indica cuándo se ha aplicado la atribución de AA. :::important `appliedExternalAttributionProviders` solo reporta Apple Ads. La atribución de otros proveedores es visible en los perfiles de usuario y está disponible en los filtros de segmento, pero aún no aquí. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - Adapty Capacitor SDK **4.1** o posterior. En las versiones 3.17.1–4.0, la propiedad del perfil se llama `appliedAttributionSources`, y la 3.17.1 obtiene paywalls con `getPaywall` en lugar de `getFlow`. - 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 a tu listener `onLatestProfileLoad`, con `'apple_search_ads'` en su array `appliedExternalAttributionProviders`. Esto te permite cargar el paywall en dos pasos: 1. Llama a `getFlow` de inmediato. Sin atribución aplicada todavía, Adapty resuelve la solicitud contra la audiencia predeterminada, por lo que el usuario ve un paywall de inmediato. 2. Cuando aparezca `'apple_search_ads'`, llama a `getFlow` de nuevo. Adapty resuelve ahora la solicitud contra la audiencia de Apple Ads y devuelve el paywall personalizado, que reemplaza al primero. `appliedExternalAttributionProviders` puede estar vacío o ausente. Esto significa una de las siguientes cosas: - La atribución de Apple Ads aún no se ha procesado para este perfil. - No ha llegado ninguna atribución. - La atribución llegó de otro proveedor, que este array no reporta. En los tres casos, 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 cuando aparece `'apple_search_ads'`. :::important En cada inicio posterior, el perfil en caché ya incluye `'apple_search_ads'` en `appliedExternalAttributionProviders`, por lo que el primer `getFlow` devuelve directamente el paywall segmentado por Apple Ads — no hay una segunda solicitud ni ningún cambio visible. El flow de dos pasos solo importa en el primer inicio, mientras la atribución aún 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.appliedExternalAttributionProviders?.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_PROVIDER = 'apple_search_ads'; const placementId = 'YOUR_PLACEMENT_ID'; function hasAppleAdsAttribution(profile: AdaptyProfile): boolean { return profile.appliedExternalAttributionProviders?.includes(APPLE_ADS_PROVIDER) ?? 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" /> <!-- your existing manifest content --> </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 StoreKit del sistema \{#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 intentada. | | 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 el 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 el App Store. Asegúrate de que el producto esté disponible para el país utilizado. | | 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 del store. | | unauthorizedRequestData | 10 | Este código de error indica que la solicitud está construida de forma incorrecta. | | invalidOfferIdentifier | 11 | <p>El identificador de la oferta no es válido. Posibles razones:</p><p></p><p>- No has configurado una oferta con ese identificador en el App Store.</p><p>- Has revocado la oferta.</p><p>- Has escrito mal 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 completado 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 saber cómo configurarlas.</p> | | invalidOfferPrice | 14 | Este código de error indica que el precio que especificaste en el 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 `activate`. Aprende cómo hacerlo en [Install & configure Adapty SDK](sdk-installation-capacitor). | | productNotFound | 22 | Este error indica que el producto solicitado para la compra no está disponible en el store. | | currentSubscriptionToUpdateNotFoundInHistory | 24 | No se encontró la suscripción original que necesita renovarse. | | 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 con el servicio de Google Play Store a través del `BillingClient` se ha interrumpido. | | billingServiceUnavailable | 102 | Este error transitorio indica que el servicio de Google Play Billing no está disponible en este momento. En la mayoría de los casos, esto significa que hay un problema de conexión de red en algún punto entre el dispositivo cliente y los servicios de Google Play Billing. | | 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 se encuentra en un país no compatible.</p><p>3. El usuario es un empleado de empresa y su administrador ha deshabilitado las compras para los usuarios.</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 vencido.</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 con el propio 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ó. | | billingNetworkError | 112 | Este error indica que hubo un problema con la conexión de red entre el dispositivo y los sistemas de Play. | ## 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 el store.</p><p>Si encuentras este error, sigue los pasos a continuación para resolverlo:</p><p></p><p>1. Comprueba si 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 del Apple Connect.</p><p>3. Verifica que los identificadores de producto de los 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 el 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 sean válidos.</p><p>5. Comprueba si hay una cuenta bancaria asociada a la app para que sea elegible para la monetización.</p><p>6. Comprueba si 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 es posible obtener los productos disponibles en este momento. Posible razón:</p><p></p><p>- No se ha creado ninguna caché todavía 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ó la 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 envuelto (o activa los registros detallados para verlo en la consola) para conocer el motivo real. El error envuelto suele ser uno de los códigos de StoreKit 0–14 de la tabla anterior — normalmente `paymentCancelled`, `paymentInvalid`, `paymentNotAllowed` o `invalidOfferPrice`. Si no puedes identificar un motivo concreto, 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. Aplica únicamente 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 `activate`. Aprende cómo hacerlo en [Install & configure Adapty SDK](sdk-installation-capacitor). | | badRequest | 2003 | Solicitud incorrecta. | | serverError | 2004 | Error del servidor. | | networkFailed | 2005 | La solicitud de red falló. | | decodingFailed | 2006 | Este error indica que la decodificación de la respuesta falló. | | encodingFailed | 2009 | Este error indica que la codificación de la solicitud falló. | | analyticsDisabled | 3000 | No podemos gestionar los eventos de analíticas porque has optado por no participar. 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, o 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. | | unsupportedData | 3007 | Este error indica que el formato de datos no es compatible con el SDK. | | persistingDataError | 3100 | Ocurrió un error al guardar los datos. | | fetchTimeoutError | 3101 | Este error significa que no se pudo obtener el paywall dentro del límite establecido. Para evitar esta situación, [configura respaldos locales](fetch-paywalls-and-products). | | operationInterrupted | 9000 | El sistema interrumpió esta operación. | --- # 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 del SDK de Adapty para Capacitor. Elige la versión a la que quieres migrar para obtener instrucciones detalladas: - **[Migrar a v4.1.1](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 v4.1.1" description: "Migra al SDK de Adapty Capacitor v4.1.1: reemplaza las APIs de paywall por APIs de flow, adopta las APIs de atribución renombradas, habilita Adapty Attribution de forma explícita y vuelve a descargar tu archivo de respaldo." --- El SDK de Adapty Capacitor 4.1.1 es la versión estable actual de la línea 4.x — la 4.0 se publicó solo como beta, así que si estás en 3.x, migra directamente a la 4.1.1. Esta guía cubre toda la migración: los flows introducidos en la 4.0 y los cambios de la 4.1.1 sobre ellos. La línea 4.x introduce los flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan con flows y siguen siendo compatibles con los paywalls del antiguo builder — no se requieren cambios de configuración en el Adapty Dashboard. Además, la versión 4.1.1 hace que Adapty Attribution sea opt-in, renombra el método de atribución externa, cambia el formato del archivo de respaldo y añade las compras in-app promocionadas de App Store. :::note ¿Vienes de la beta 4.0? Sustituye la versión beta fijada por la [última versión](#installation) y solo aplican cuatro secciones: [la atribución de Adapty está desactivada por defecto](#adapty-attribution-is-disabled-by-default), [las APIs de atribución externa renombradas](#renamed-external-attribution-apis), [los archivos de respaldo](#fallback-files) y [las compras in-app promocionadas en App Store](#app-store-promoted-in-app-purchases). ::: ## Referencia rápida \{#quick-reference\} | v3 | v4.1.1 | |---|---| | Adapty Attribution habilitado automáticamente | deshabilitado por defecto — actívalo con `adaptyAttributionEnabled: true` | | `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` | | `adapty.updateAttribution({ attribution, source })` | `adapty.updateExternalAttribution({ attribution, provider })` | | `AdaptyProfile.appliedAttributionSources` | `AdaptyProfile.appliedExternalAttributionProviders` | | `AttributionSource` | `AdaptyExternalAttributionProvider` | | Archivo de respaldo descargado para 3.x | nuevo formato de archivo de respaldo — descarga el archivo de nuevo | | Las compras in-app promocionadas se completaban automáticamente, sin posibilidad de interceptarlas | el evento `'onPromotedPurchaseReceived'` y `adapty.makePromotedPurchase({ product })` delegan la finalización a tu app | `AdaptyPaywallProduct` conserva su nombre — los productos siguen perteneciendo a un flow, y `getPaywallProducts` también conserva su nombre, ahora tomando un `AdaptyFlow`. Los métodos `getFlow` y `getFlowForDefaultAudience` ya no aceptan el parámetro `locale` — pásalo a `createFlowView` en su lugar. Las APIs de compra y perfil (`makePurchase`, `restorePurchases`, `getProfile`, `identify`, `updateProfile`) y `setFallback` mantienen las mismas firmas, pero el archivo de respaldo debe descargarse de nuevo — consulta [Archivos de respaldo](#fallback-files). Los métodos de vista `present`, `dismiss`, `setEventHandlers`, `clearEventHandlers` 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. Los métodos de onboarding siguen funcionando pero están obsoletos — consulta [Obsolescencia 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 en tiempo 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 incluido en esta versión utiliza Swift tools 6.2. ## Instalación \{#installation\} ### Actualiza el paquete ```bash showLineNumbers npm install @adapty/capacitor@latest ``` 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. ## ⚠️ La atribución de Adapty está desactivada por defecto \{#adapty-attribution-is-disabled-by-default\} :::warning Si usas [Atribución de Adapty](user-acquisition) y actualizas al SDK 4.1.1 sin activarla, fallará silenciosamente: las instalaciones dejarán de registrarse y no recibirás ningún aviso. ::: En versiones anteriores, el SDK registraba las instalaciones para [Atribución de Adapty](user-acquisition) automáticamente. A partir de la versión 4.1.1 del SDK, esto está desactivado por defecto: el SDK no registra instalaciones, los eventos `'onInstallationDetailsSuccess'` y `'onInstallationDetailsFail'` nunca se disparan, y `getCurrentInstallationStatus` devuelve el estado `not_available`. Si utilizas Atribución de Adapty, actívalo al inicializar el SDK: ```diff showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { + adaptyAttributionEnabled: true, }, }); ``` Si no usas Adapty Attribution, no es necesario hacer ningún cambio. ## Obtener flows \{#fetching-flows\} ### getPaywall → getFlow El tipo devuelto cambia de `AdaptyPaywall` a `AdaptyFlow`, y la opción `locale` se traslada de la llamada de obtención a `createFlowView`; para paywalls personalizados, todos los locales 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' }); + const view = await createFlowView(flow, { locale: 'en' }); ``` `locale` es opcional en `createFlowView`: omítelo y la vista se renderizará en `en`, o en la localización predeterminada del flow cuando el flow no tenga `en`. Debido a ese fallback, la vista puede renderizarse en una localización distinta a la que solicitaste — la nueva propiedad `FlowViewController.locale` informa cuál se usó. Consulta [Localizaciones y códigos de idioma](capacitor-localizations-and-locale-codes). `getPaywallForDefaultAudience` se renombra de la misma manera: ```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 }); ``` ### Archivos de respaldo El formato del [archivo de respaldo](fallback-flows) cambió en la versión 4.0 y de nuevo en la 4.1.1. Descarga el archivo otra vez desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** y agrégalo a tu app, aunque ya hayas descargado uno para una beta de la versión 4.0. :::warning Este paso no genera ningún error de compilación. Si lo omites, `setFallback` rechazará el archivo desactualizado y todos los placements perderán su paywall de respaldo. ::: ## 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')`. | | `productIdentifiers` | `flow.paywalls[i].productIdentifiers` | Los identificadores de producto ahora están en cada variación del flow, no en el flow en sí. | | `products` (obsoleto en v3) | eliminado | Usa `flow.paywalls[i].productIdentifiers`, o llama a `getPaywallProducts(flow)` para obtener los productos completos. `ProductReference` se ha eliminado como tipo público. | | `webPurchaseUrl?` | `flow.paywalls[i].webPurchaseUrl` | Movido del flow a cada variación del paywall. | | `version?: number` | `flowVersionId?: string` | Renombrado, y el tipo cambió de `number` a `string`. | | `requestLocale` | eliminado | El idioma 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. | `requestLocale` permanece en `AdaptyOnboarding` — solo el modelo de flow lo elimina. Los identificadores de producto se han movido del flow a cada variación: ```diff showLineNumbers - const ids = paywall.productIdentifiers; + const ids = flow.paywalls[0].productIdentifiers; ``` Si tu código aún lee `paywall.products` — obsoleto en v3 y ahora eliminado — cambia a `productIdentifiers`, o llama a `getPaywallProducts(flow)` cuando necesites los productos completos en lugar de los identificadores. ## 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 sigue registrándose contra la misma variación, por lo que las métricas de embudo y pruebas A/B existentes seguirán funcionando sin cambios en el dashboard. ```diff showLineNumbers - await adapty.logShowPaywall({ paywall }); + await adapty.logShowFlow({ flow }); ``` Al igual que en la v3, no es necesario llamar a este método al mostrar flows o paywalls renderizados por Adapty — Adapty registra esas vistas automáticamente. ## Mostrar flows \{#displaying-flows\} ### createPaywallView → createFlowView Cambia el nombre de la función de fábrica y pasa el `AdaptyFlow`. El controlador retornado pasa de llamarse `PaywallViewController` a `FlowViewController`, pero sus métodos (`present`, `dismiss`, `setEventHandlers`, `clearEventHandlers` y `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(); ``` :::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. ::: ### Nuevos parámetros \{#new-parameters\} `CreateFlowViewParamsInput` conserva todos los parámetros de v3 (`prefetchProducts`, `loadTimeoutMs`, `customTags`, `customTimers`, `customAssets`, `productPurchaseParams`) y añade tres: :::note `customTimers` sigue existiendo, pero solo afecta a los paywalls creados con el Paywall Builder heredado. El temporizador de cuenta atrás de un [flow](flow-timer) se rige por la configuración establecida en el Flow & Paywall Builder, por lo que el flow ignorará cualquier valor que pases aquí. ::: | Parámetro | Descripción | |---|---| | `locale` | La localización con la que se renderiza el flow. Se trasladó aquí desde `getPaywall` — consulta [getPaywall → getFlow](#getpaywall--getflow). | | `customLayoutId` | El ID personalizado de un layout en la configuración de layouts del flow. Pásalo para renderizar ese layout concreto en lugar del que el SDK selecciona automáticamente según el tipo de dispositivo y el tamaño de pantalla. Si ningún layout coincide con el ID, la llamada falla con un error de no-view-configuration. El Flow & Paywall Builder todavía no asigna IDs de layout personalizados, así que deja este campo sin definir. | | `android.enableSafeArea` | Controla los márgenes de área segura de 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, { locale: 'en', customLayoutId: 'tablet_landscape', 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. Uno cambia su firma: `onAppeared` ahora es `(view)` en lugar de `()`, donde `view` es un `FlowEventView` que describe la vista que apareció, incluyendo la localización con la que fue construida. Los manejadores existentes seguirán funcionando, ya que ignoran el nuevo argumento. Consulta [Gestionar eventos de flow y paywall](capacitor-handling-events) para ver la lista completa. v4 también añade algunas funcionalidades que puedes activar de forma opcional: - `adapty.openWebUrl({ url, openIn })` y `adapty.requestAppReview()` — estos métodos respaldan los handlers predeterminados `onUrlPress` y `onRequestAppReview`, de modo que las URLs y las solicitudes de valoración de la app se gestionan de forma nativa sin configuración adicional. Llámalos directamente solo si reemplazas esos handlers. - Gestión de compras en Observer mode dentro de flows mediante los nuevos handlers `onObserverPurchaseInitiated` / `onObserverRestoreInitiated`. Consulta [Presentar flows en Observer mode](capacitor-present-flows-in-observer-mode). - `onAnalytics: (name, params)` — eventos de analíticas que emite un flow, comenzando con una vista de pantalla por cada pantalla que abre el usuario. Consulta [Rastrear vistas de pantalla de flows](capacitor-flow-screen-views). - `onRequestPermission: (permission, customArgs)` — reservado para solicitudes de permisos del sistema (como notificaciones push o acceso a la cámara) desde un flow. Los flows aún no disparan solicitudes de permisos, por lo que no es necesario implementarlo. Además, la versión 4.1.1 añade un evento a nivel de SDK en lugar de un manejador de flow: `'onPromotedPurchaseReceived'`, entregado a través de `adapty.addListener`. Si no hay ningún listener registrado, el SDK completa la compra promocionada por sí mismo; si se registra uno, la finalización pasa a tu aplicación. Consulta [Compras in-app promocionadas en App Store](#app-store-promoted-in-app-purchases). ## APIs de atribución externa renombradas \{#renamed-external-attribution-apis\} A partir de la versión 4.1.1 del SDK, las APIs para pasar datos de atribución desde un proveedor externo (Adjust, AppsFlyer, Branch, Tenjin o uno personalizado) han sido renombradas para coincidir con los SDKs nativos. No existen alias obsoletos, por lo que los sitios de llamada existentes dejarán de funcionar hasta que los renombres: | Antes de 4.1.1 | 4.1.1 | |---|---| | `adapty.updateAttribution({ attribution, source })` | `adapty.updateExternalAttribution({ attribution, provider })` | | `AttributionSource` | `AdaptyExternalAttributionProvider` | | `AdaptyProfile.appliedAttributionSources` | `AdaptyProfile.appliedExternalAttributionProviders` | ### updateAttribution → updateExternalAttribution El método ha sido renombrado y su opción `source` pasa a llamarse `provider`. Los datos de atribución siguen siendo un objeto plano: ```diff showLineNumbers - await adapty.updateAttribution({ attribution, source: 'adjust' }); + await adapty.updateExternalAttribution({ attribution, provider: 'adjust' }); ``` ### AttributionSource → AdaptyExternalAttributionProvider El tipo de proveedor cambia de nombre. Sigue siendo una unión abierta — los valores predefinidos son `'apple_search_ads'`, `'adjust'`, `'appsflyer'`, `'branch'` y `'tenjin'`, y cualquier otra cadena es válida, de modo que un proveedor que Adapty añada más adelante funciona sin actualizar el SDK: ```diff showLineNumbers - import type { AttributionSource } from '@adapty/capacitor'; + import type { AdaptyExternalAttributionProvider } from '@adapty/capacitor'; ``` ### AdaptyProfile.appliedAttributionSources → appliedExternalAttributionProviders La propiedad del perfil que lista los proveedores de atribución aplicados al perfil ha sido renombrada, y el tipo de sus elementos cambia en consecuencia: ```diff showLineNumbers - if (profile.appliedAttributionSources?.includes('apple_search_ads')) { + if (profile.appliedExternalAttributionProviders?.includes('apple_search_ads')) { // Apple Ads attribution has been applied } ``` El código que la lee necesita actualizarse — consulta [Mostrar un paywall segmentado con Apple Ads](capacitor-show-aa-targeted-paywall). ## Compras in-app promocionadas en la App Store \{#app-store-promoted-in-app-purchases\} Antes de la versión 4.1.1, una [compra in-app promocionada en la página de tu producto en la App Store](capacitor-making-purchases#in-app-purchases-from-the-app-store) se completaba por sí sola y Adapty registraba la transacción, pero tu app no tenía forma de interceptarla. La versión 4.1.1 añade ese gancho, por lo que se trata de una nueva funcionalidad y no de un paso de migración: si no añades código propio, el SDK sigue completando las compras promocionadas por ti. Escribe código solo para encargarte tú mismo de la finalización — por ejemplo, para mostrar primero una pantalla. Registra un listener para el nuevo evento `'onPromotedPurchaseReceived'` y completa la compra con `adapty.makePromotedPurchase`. Mientras ese listener esté registrado, el SDK deja de completar las compras promocionadas por ti. ## 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. ## Obsolescencia de la API de onboarding \{#onboarding-api-deprecation\} La API de onboarding heredada está obsoleta en v4 en favor del [Flow & Paywall 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 & Paywall Builder. Símbolos obsoletos: `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-10-30T13:00:39.524Z_ _Successfully processed: 48/48 files_ # FLUTTER - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-10-30T13:00:39.527Z Total files: 57 --- # File: flutter-sdk-overview --- --- title: "Resumen del SDK de Flutter" description: "Aprende sobre el SDK de Flutter de Adapty y sus características principales." --- [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Flutter.svg?style=flat&logo=flutter)](https://github.com/adaptyteam/AdaptySDK-Flutter/releases) ¡Bienvenido! Estamos aquí para hacer que las compras in-app sean pan comido 🚀 Hemos creado el SDK de Adapty para Flutter con el objetivo de quitarte el dolor de cabeza de las compras in-app para que puedas centrarte en lo que mejor se te da: crear aplicaciones 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 flows y 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 suscripción del usuario siempre actualizado en todas las sesiones y dispositivos - Integra tu app con servicios de atribución de marketing y analítica con una sola línea de código :::note Antes de entrar en el código, necesitas integrar Adapty con App Store Connect y Google Play Console, y después 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-integration skill](https://github.com/adaptyteam/adapty-skills): 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. [Instala y configura el SDK](sdk-installation-flutter): Añade el SDK como dependencia a tu proyecto y actívalo en el código. 2. [Activa las compras con flows y paywalls](flutter-quickstart-paywalls): Configura el flujo de compra para que los usuarios puedan adquirir productos. Si prefieres construir tu propia UI, consulta [Implementar paywalls personalizados](flutter-quickstart-manual). 3. [Comprueba el estado de la suscripción](flutter-check-subscription-status): Verifica automáticamente el estado de la suscripción del usuario y controla su acceso al contenido de pago. 4. [Identifica a los usuarios (opcional)](flutter-quickstart-identify): Asocia a 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? Te lo ponemos fácil: - **App de ejemplo**: Consulta nuestro [ejemplo completo](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example) que muestra la configuración completa ## Conceptos principales \{#main-concepts\} Antes de entrar en el código, familiarízate con los conceptos clave que hacen funcionar Adapty. Lo más valioso del enfoque de Adapty es que solo los placements están hardcodeados en tu app. Todo lo demás —productos, diseños de paywall, precios y ofertas— se puede gestionar de forma flexible desde el Adapty Dashboard sin necesidad de actualizar la app: 1. [**Producto**](product) - Cualquier elemento disponible para comprar en tu app: una suscripción, un producto consumible o acceso de por vida. 2. **Flows y paywalls** - Productos agrupados con configuración, vinculados a un placement. Dos formas de crearlos, comparadas en [Flows y paywalls](flows-and-paywalls): - **[Flow & Paywall Builder](adapty-flow-builder)** - Interfaz visual sin código. Adapty renderiza la UI y gestiona la compra por ti. - **[Paywalls personalizados](paywalls)** - Sin configuración visual; tú construyes la UI en tu propio código y llamas a `makePurchase` tú mismo. Consulta [Implementar paywalls personalizados](flutter-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 un paywall. Los placements son el "dónde" y el "cuándo" de tu estrategia de monetización. Los placements más habituales son: - `main` - Tu ubicación 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` o `onboarding` para 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, su perfil recibe un **nivel de acceso** que usas para definir el acceso a las funciones de pago. --- # 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 apps basadas en suscripciones." --- El SDK de Adapty incluye dos módulos clave para una integración fluida en tu app de Flutter: - **Core Adapty**: Este SDK esencial es necesario para que Adapty funcione correctamente en tu app. - **AdaptyUI**: Este módulo renderiza flows, además de los paywalls del builder antiguo. :::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 funcionalidades básicas. ::: ## Requisitos \{#requirements\} El SDK de Adapty para Flutter requiere **iOS 15.0+**, **Xcode 26+** y **Flutter 3.32.0+** (Dart 3.8.0+). Consulta [Swift Package Manager (iOS)](#swift-package-manager-ios) más abajo para los detalles de instalación. :::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. ::: ## Dependencias \{#dependencies\} El SDK de Adapty funciona con las siguientes versiones de la Google Play Billing Library en Android: | Versión del SDK de Adapty | Versión de la Billing Library | |---|---| | 4.0.4 y posteriores | v8 | | 4.0.0–4.0.3 | v7 por defecto, v8 si otra dependencia la eleva | :::note La compatibilidad con una versión de la Billing Library no significa que Adapty sea compatible con todas las funciones que Google introdujo en ella. Antes de adoptar una nueva capacidad de facturación de Google Play, consulta [Producto en Play Store](android-products). ::: ## 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) Siempre recomendamos instalar la versión más reciente disponible del SDK — incluye las últimas correcciones de estabilidad y mejoras. :::important Estos pasos requieren **Flutter 3.32.0+** (Dart 3.8.0+). El plugin obtiene el SDK nativo de iOS a través de Swift Package Manager — consulta [Swift Package Manager (iOS)](#swift-package-manager-ios) para la configuración inicial. ::: 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'; ``` ### Swift Package Manager (iOS) \{#swift-package-manager-ios\} El plugin obtiene el SDK nativo de iOS 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 showLineNumbers title="Terminal" flutter config --enable-swift-package-manager ``` Flutter 3.44 y versiones posteriores activan Swift Package Manager por defecto, por lo que no es necesario hacer nada. Para ver los cambios de API en la 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 [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 [Flow & Paywall Builder](adapty-flow-builder), primero [activa el módulo AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) más abajo y luego sigue la [guía de inicio rápido](flutter-quickstart-paywalls). - Si construyes tu propia UI 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 tienes previsto usar el [Flow & Paywall Builder](adapty-flow-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 de Adapty. Si Adapty no renderiza tus pantallas 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 ); ``` ### Habilitar la atribución de Adapty \{#enable-adapty-attribution\} :::info Este parámetro está disponible a partir de la versión 4.1 del SDK. ::: Si usas [Adapty Attribution](user-acquisition), llama a `withAdaptyAttributionEnabled(true)` al activar el SDK. El valor predeterminado es `false`: sin este parámetro, el SDK no registra instalaciones ni envía los detalles de instalación a tu app. En versiones del SDK anteriores a la 4.1, la atribución de Adapty se habilita automáticamente. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAdaptyAttributionEnabled(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 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 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 (que normalmente se encuentra 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 espacio de nombres `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 finales e indique al fusionador de manifiestos que reemplace los valores de las bibliotecas: ```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 usa diferentes 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 ejemplo. Reemplaza o añade reglas para cualquier otro SDK que estés usando 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 heredado 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 esté configurada como `standard` o `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### Errores de compilación en Swift 6 causados por la sobreescritura de SWIFT_VERSION en el Podfile :::note Esto solo aplica si los SDK de Adapty para iOS se instalan como CocoaPods en tu proyecto. Cuando se instalan como paquetes de Swift, una sobreescritura de `SWIFT_VERSION` en `post_install` no les afecta. ::: Al compilar tu app Flutter para iOS, es posible que veas errores de compilación de Swift 6 en los targets del pod de Adapty. Los síntomas más habituales son: 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 compilar. Tu propio código de la app puede seguir en Swift 5 — solo los targets del pod 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 cada target de 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 Adapty de la sobreescritura: ```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: "Activar compras con Flow y Paywall Builder en el SDK de Flutter" description: "Guía de inicio rápido para activar compras in-app con Adapty Flow y Paywall 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 & paywalls**](adapty-flow-builder) – pantallas que presentan productos a los usuarios, creadas en el Flow & Paywall Builder sin código. El SDK los recupera mediante `getFlow`. Si prefieres construir la interfaz en tu propio código, consulta [Implementar paywalls personalizados](flutter-quickstart-manual). - [**Placements**](placements) – dónde y cuándo mostrar flows y paywalls en tu app (como `main`, `onboarding`, `settings`). Los vinculas a placements desde el dashboard y los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar diferentes opciones a distintos 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 usarla | |------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Adapty Flow & Paywall Builder | ✅ Fácil | [Creas un flow o paywall completo y listo para compras en el editor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona toda la lógica de compra, validación de recibos y gestión de suscripciones en segundo plano. | | Paywalls personalizados | 🟡 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](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 o paywall creado en el Adapty Flow & Paywall Builder.** Si prefieres construir la UI del paywall tú mismo, consulta [Implementar paywalls personalizados](flutter-quickstart-manual). ::: Para mostrar un flow o paywall creado en el Adapty Flow & Paywall Builder, en el código de tu app solo necesitas: 1. **Obtén el flow o paywall**: Obtenlo desde Adapty. 2. **Muéstralo y Adapty gestionará las compras por ti**: Muestra la vista en tu app. 3. **Gestiona las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abre enlaces o cierra el paywall cuando el usuario pulse los 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 o paywall](paywall-builder-templates) y [añade productos](paywall-product-block). 4. [Crea un placement y añade tu flow o paywall](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 para 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. Obtén el flow o el paywall \{#1-get-the-flow-or-paywall\} Tus flows y paywalls están asociados a placements configurados en el dashboard. Los placements te permiten mostrar distintos flows o paywalls a diferentes audiencias o ejecutar [pruebas A/B](ab-tests). Para obtener un flow o paywall creado en el Adapty Flow & Paywall Builder, debes: 1. Obtén el objeto `flow` mediante el ID del [placement](placements) usando el método `getFlow` y comprueba si fue creado en el builder usando la propiedad `hasViewConfiguration`. 2. Crea la vista del flow con el método `createFlowView`. La vista contiene los elementos de UI y el estilo necesarios para mostrarla. :::important Para obtener la configuración de la vista, [publica el flow o el paywall](builder-save-publish). Un flow o paywall con cambios sin publicar tiene el estado **Dirty**, y su placement sigue sirviendo la última versión publicada. ::: ```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 o el paywall \{#display-the-flow-or-paywall\} Ahora que tienes la vista del flow, basta con añadir unas pocas líneas para mostrarla. Para mostrar el flow, usa el método `view.present()` sobre el `view` creado por el método `createFlowView`. Cada `view` solo puede presentarse una vez: cuando lo cierras, la vista se libera de la memoria. Si necesitas mostrar el flow 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 Para más detalles sobre cómo mostrar un flow o paywall, consulta nuestra [guía](flutter-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#3-handle-button-actions\} Cuando los usuarios hacen clic en los botones del flow o del paywall, 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 que 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 observador 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 iniciar la app, antes de mostrar cualquier flow o paywall: ```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 flow o 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 flow. Ahora necesitas [comprobar el nivel de acceso de los usuarios](flutter-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 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 de Flutter con la skill de integración del SDK" description: "Usa la skill adapty-integration para integrar el SDK de Adapty en tu app de Flutter de principio a fin con tu herramienta de codificación con IA." --- :::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. ::: El skill `adapty-integration` automatiza la integración de Adapty de principio a fin: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación relevante de Adapty en cada etapa. Funciona en cualquier agente de programación que soporte skills: Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI y otros. El skill forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener los nuevos skills a medida que se publiquen, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Para iniciar la integración, pide a tu agente: ``` Integrate Adapty into my app ``` El skill hace algunas preguntas de configuración y luego te guía a través de 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-integration skill](https://github.com/adaptyteam/adapty-skills): 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á en cada paso, incluso cuando tengas que abrir el Dashboard para conectar tus stores. El CLI de skills no actualiza el skill por sí solo. Para obtener la última versión, ejecuta `npx skills update`. ### 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 flow o paywall y un placement**: En el Adapty Dashboard, crea un flow (o un paywall si vas a construir la interfaz tú mismo) 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().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 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\} Estos elementos no son necesarios para empezar a programar, pero los querrás a medida que tu integración madure: - **Pruebas A/B**: Configúralas en la página **Placements**. No se necesitan cambios en el código. [Pruebas A/B](ab-tests) - **Flows y placements adicionales**: Añade más llamadas a `getFlow` 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) y [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 Flow & Paywall Builder**](adapty-flow-builder): Creas flows en el constructor sin código de Adapty, y el SDK los renderiza automáticamente. - [**Paywalls personalizadas**](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 solo para análisis e integraciones. ¿No sabes cuál elegir? Lee 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="Flow & Paywall Builder" default> **Guías:** - [Habilitar compras con Flow y Paywall Builder (inicio rápido)](flutter-quickstart-paywalls) - [Obtener flows y su configuración](flutter-get-pb-paywalls) - [Mostrar flows](flutter-present-paywalls) - [Gestionar eventos de flow](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 sandbox. - **Problema común:** Flow vacío o error `getFlow` → verifica que el ID del placement coincida exactamente con el dashboard y que el placement tenga una audiencia asignada. ::: </TabItem> <TabItem value="manual" label="Custom paywalls"> **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](flutter-quickstart-manual) - [Obtener paywalls y productos personalizados](fetch-paywalls-and-products-flutter) - [Presentar paywalls personalizados](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-paywalls --- --- title: "Flows y paywalls - Flutter" description: "Muestra y gestiona flows y paywalls creados con los editores sin código de Adapty en tu app Flutter." --- ## Mostrar paywalls \{#display-paywalls\} ### Adapty Flow y Paywall Builder \{#adapty-flow--paywall-builder\} <CustomDocCardList ids={['flutter-get-pb-paywalls', 'flutter-present-paywalls', 'flutter-handling-events', 'flutter-handle-paywall-actions']} /> :::tip Para empezar rápidamente con los flows y paywalls de Adapty, consulta nuestra [guía de inicio rápido](flutter-quickstart-paywalls). ::: ### Implementar paywalls personalizados <CustomDocCardList ids={['flutter-quickstart-manual', 'fetch-paywalls-and-products-flutter', 'present-remote-config-paywalls-flutter', 'flutter-making-purchases']} /> Para más guías sobre cómo implementar paywalls personalizados y gestionar compras, consulta [Implementar paywalls personalizados](flutter-implement-paywalls-manually). ## Funciones útiles \{#useful-features\} <CustomDocCardList ids={['flutter-use-fallback-paywalls', 'flutter-web-paywall']} /> --- # File: flutter-get-pb-paywalls --- --- title: "Obtener flows y paywalls - Flutter" description: "Obtén flows y paywalls desde Adapty en tu app de Flutter." --- <SDKv4> <MethodPromo method="getFlow" /> Después de [diseñar tu flow](adapty-flow-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 diseñados en los builders de Adapty. Si implementas paywalls personalizados, consulta el tema [Obtener paywalls y productos para paywalls personalizados 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. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Productos en el Adapty Dashboard**: [Crea tus productos](create-product). - **Un flow con productos**: Constrúyelo en el [Flow & Paywall Builder](adapty-flow-builder) y [asígnales productos](paywall-product-block). - **Un placement**: [Crea un placement](create-placement) y asígnale tu flow. - **SDK de Adapty instalado**: Consulta la [guía de instalación del SDK de Flutter](sdk-installation-flutter). ## Obtener un flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall en el builder, no tienes que preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Un flow o paywall de este tipo 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, después, presentarlo en tu app móvil. 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** | predeterminado: `AdaptyFlowFetchPolicy.reloadRevalidatingCacheData` | <p>`fetchPolicy` establece qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyFlowFetchPolicy.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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>Una tercera política, `AdaptyFlowFetchPolicy.returnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que la `Duration` que indiques, y va al servidor cuando sea más antigua.</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é que se actualiza periódicamente (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 asegurarse de que siempre obtengas la versión más reciente de tus paywalls, garantizando la fiabilidad incluso cuando la conexión a internet es escasa.</p> | | **loadTimeout** | predeterminado: 5 seg | <p>Una `Duration` que 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 `loadTimeout`, ya que la operación puede estar formada por distintas solicitudes 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`). | ## Obtener la configuración de la vista \{#fetch-the-view-configuration\} :::important Asegúrate de [publicar el flow](builder-save-publish). Un flow con ediciones sin publicar tiene el estado **Dirty**, y su placement sigue sirviendo la última versión publicada. ::: Si el placement fue diseñado en el **Flow & Paywall Builder** o el **old 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 del Builder (`hasViewConfiguration` es `false`), [renderízalo tú mismo](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. | | **locale** | opcional | El identificador de la [localización del flow](add-paywall-locale-in-adapty-paywall-builder) con la que se renderiza la vista; por ejemplo, `en` o `pt-br`. Si se omite, la vista se renderiza en `en`, o en la localización por defecto del flow cuando este no tiene `en`. Consulta [Localizaciones y códigos de idioma](flutter-localizations-and-locale-codes). | | **customLayoutId** | <p>opcional</p><p>por defecto: `null`</p><p>SDK 4.1+</p> | El ID personalizado de un layout en la configuración de layouts del flow. Pásalo para renderizar ese layout específico en lugar del que el SDK selecciona automáticamente según el tipo de dispositivo y el tamaño de pantalla. Si ningún layout coincide con el ID, la llamada falla con un `AdaptyError`. El Flow & Paywall Builder no asigna IDs de layout personalizados todavía, así que deja este campo sin definir. | | **customTags** | opcional | Los valores de tu app para las [etiquetas personalizadas](flow-custom-tags) del flow, en un mapa con el nombre completo de cada etiqueta como clave. Para más detalles, consulta [Pasar datos a flows](flutter-flow-custom-tags). | | **preloadProducts** | opcional | Actívalo para optimizar el momento 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 respaldo 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: `AdaptyFlowFetchPolicy.reloadRevalidatingCacheData` | <p>`fetchPolicy` establece qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyFlowFetchPolicy.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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 solicitudes de red.</p><p></p><p>Una tercera política, `AdaptyFlowFetchPolicy.returnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que la `Duration` que indiques, y va al servidor una vez que es más antigua.</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 imágenes y vídeos en tu flow/paywall, implementa los recursos personalizados. Las imágenes hero y los vídeos hero tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizados, seleccionas estos elementos por su ID 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. - Muestra contenido multimedia incluido en la app, de modo que la primera pantalla se renderice sin necesidad de descarga. Consulta [Mostrar contenido multimedia de la primera pantalla desde el bundle de la app](flutter-optimize-paywall-fetching#show-first-screen-media-from-the-app-bundle). A continuación se muestra 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().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 antiguo Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu aplicación móvil. El primer paso en este proceso es obtener el paywall asociado con el placement y su configuración de vista, tal como se describe a continuación. :::warning Los paywalls creados con el Paywall Builder de SDK 3.x requieren la versión 3.3.0 o superior del SDK de Flutter. ::: Por favor, ten en cuenta que este tema hace referencia a paywalls personalizadas con Paywall Builder. Si implementas paywalls personalizadas, consulta el tema [Obtener paywalls y productos para paywalls personalizadas en tu app 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 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-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** | 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. 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** | por defecto: `AdaptyPaywallFetchPolicy.reloadRevalidatingCacheData` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada almacenado. 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>Una tercera política, `AdaptyPaywallFetchPolicy.returnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el `Duration` que especifiques, y va al servidor cuando es más antigua.</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 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 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 de este método. Si se alcanza el timeout, 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 timeout especificado en `loadTimeout`, ya que la operación puede consistir en distintas solicitudes internamente.</p><p>Para Android: puedes crear `TimeInterval` con funciones de extensión (como `5.seconds`, donde `.seconds` viene 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/3.17.2/adapty_flutter/AdaptyPaywall-class.html) con una lista de IDs de productos, el identificador del paywall, Remote Config y otras propiedades. | ## Obtén la configuración de vista del 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 su recuperación. ::: Después de 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; si no, [trátalo como un paywall personalizado](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 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.reloadRevalidatingCacheData` | <p>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada almacenado. 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>Una tercera política, `AdaptyPaywallFetchPolicy.returnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor `Duration` que indiques, y va al servidor una vez que sea más antigua.</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> | ## 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 de Flutter usando las funciones de monetización de Adapty." --- <SDKv4> Si has creado un flow o un paywall en el antiguo Paywall 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 se debe mostrar como la forma en que debe mostrarse. :::warning Esta guía es para flows y paywalls renderizados por Adapty. Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](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 se puede presentar una vez: cuando lo cierras, el view se libera de la memoria. Si necesitas mostrar el flow o paywall de nuevo, vuelve a llamar a `createFlowView` para crear una nueva instancia de `view`, o [ciérralo sin liberarlo](#dismiss-the-flow-or-paywall) en el SDK 4.1 o posterior. ```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 paywall \{#dismiss-the-flow-or-paywall\} Cuando necesites cerrar el flow o paywall de forma programática, 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 Cerrar una vista la elimina de la memoria: una vista cerrada ya no puede volver a mostrarse. Crea una nueva con `createFlowView` en su lugar. ::: A partir del SDK 4.1, pasa `destroy: false` para mantener la vista en memoria: ```dart showLineNumbers title="Flutter" await view.dismiss(destroy: false); ``` Muestra esa vista de nuevo y el usuario vuelve a la pantalla donde estaba, con el estado que el flow había acumulado. Una vista conservada de esta forma se mantiene hasta que la descartes con `destroy: true`. ### 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 locale: 'en', // The localization to render the flow with 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 cubre solo los paywalls creados en el antiguo Paywall Builder para **Adapty SDK 3.x**. Requieren SDK v3.2.0 o posterior. Presentar un paywall creado para SDK 2.x o anterior, o un paywall personalizado, funciona de forma diferente. - Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](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 Adapty Paywall Builder, es fundamental configurar los botones correctamente: 1. En el Paywall Builder anterior, añade un botón y asígnale una acción preexistente o un ID de acción personalizada. 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. El cierre de la vista y la apertura de enlaces los gestiona 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 gestionar acciones de botones personalizadas. ::: 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\} ```dart showLineNumbers title="Flutter" void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { } ``` Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. Consulta [Rastrear vistas de pantallas del flow](flutter-flow-screen-views) para saber qué hacer con ellos. Un flow también informa los valores que los usuarios escriben y seleccionan. Consulta [Procesar datos de flows](flutter-flow-input) para leerlos. ### 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, 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 cubre únicamente **paywalls creados en el builder antiguo**, que requieren el SDK de Adapty 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-flow-screen-views --- --- title: "Rastrear vistas de pantallas del flow en Flutter SDK" description: "Recibe un evento cada vez que un usuario abre una pantalla de un flow en tu app de Flutter y reenvíalo a tus analíticas de producto." --- Cada vez que un usuario abre una pantalla de un [flow](adapty-flow-builder), el SDK reporta un evento `flow_screen_showed` a tu app. Reenvíalo a Amplitude, Mixpanel o a cualquier herramienta de analíticas de producto que uses, para poder analizar las pantallas del flow junto con el resto de los eventos de tu app. :::tip Para ver cuántos usuarios llegan a cada pantalla y dónde abandonan, no necesitas ningún código: el [embudo de flow](flow-funnel) ya lo muestra en el Adapty Dashboard. Usa esta guía para construir el mismo embudo en tu propia herramienta de análisis, o para combinar las pantallas del flow con eventos que Adapty no conoce. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty Flutter SDK v4 o posterior**: El callback de análisis de flow no existe en versiones anteriores. - **Una versión del flow publicada después de que se lanzara esta función**: Adapty genera los eventos de pantalla al publicar un flow, por lo que un flow publicado antes de entonces no reportará nada. Si no llegan eventos a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Qué recibes \{#what-you-receive\} Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: Las vistas de pantalla llegan al mismo callback de analítica que el resto de eventos del flow. Consulta [Gestionar eventos de flow y paywall](flutter-handling-events) para saber dónde registrarlo. ## Reenvía cada vista de pantalla a tu sistema de analíticas \{#forward-every-screen-view-to-your-analytics\} Comprueba primero el nombre del evento antes de leer cualquier otro dato, ya que un flow reporta otros eventos a través del mismo callback. ```dart showLineNumbers title="Flutter" void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { if (name != 'flow_screen_showed') return; final screenId = params['instanceId']; final screenOrder = params['screen_order']; // Send screenId and screenOrder to your analytics provider here. } ``` ## Ejecutar código cuando el usuario llega al final \{#run-code-when-a-user-reaches-the-end\} `is_last_screen` es `true` en una pantalla a partir de la cual el flow no puede avanzar. Úsalo para desbloquear contenido, marcar el onboarding como completado o enviar un evento de finalización propio, sin necesidad de añadir una acción de botón en el builder. ```dart showLineNumbers title="Flutter" void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { if (name != 'flow_screen_showed') return; if (params['is_last_screen'] != true) return; // The user reached the end of the flow. } ``` Llegar a la última pantalla no es lo mismo que cerrar el flow. Un usuario puede ver la pantalla final y cerrar el flow sin interactuar con él, y un flow con ramificaciones puede terminar en varias pantallas distintas. Para saber cuándo el flow en sí desaparece, usa `flowViewDidDisappear` en su lugar. --- # File: flutter-flow-input --- --- title: "Procesar datos de flows en Flutter SDK" description: "Guarda y utiliza los datos que tus usuarios introducen en los flows de tu app Flutter con el SDK de Adapty." --- Cuando un usuario escribe en un campo de texto, responde un cuestionario o activa un interruptor en un [flow](adapty-flow-builder), el SDK pasa el valor a tu app a través de su callback de analíticas. Las apps suelen usar esos datos para: - **Registrar usuarios en su propio backend**: Toma el correo electrónico y el nombre que el usuario introdujo en tu onboarding flow, y crea su cuenta cuando el flow se cierre. - **Guardar respuestas y preferencias**: Registra lo que el usuario eligió para que tu app pueda usarlo más adelante — por ejemplo, [escríbelo en su perfil de Adapty](flutter-setting-user-attributes) como atributos personalizados. - **Personalizar flows futuros**: Guarda las respuestas del cuestionario como atributos personalizados y luego apunta a un placement posterior para que cada segmento vea un flow diferente, o un paywall distinto dentro de él. - **Mostrar valores calculados a partir de las respuestas**: Por ejemplo, calcula el IMC a partir del peso y la altura del usuario, y [pásalo a una etiqueta personalizada en el siguiente flow](flutter-flow-custom-tags#example-show-a-bmi-from-onboarding-answers). - **Alimentar plataformas de analítica de terceros**: Reenvía las respuestas a Amplitude, Mixpanel o la plataforma de analítica de producto que utilices. Las entradas y los grupos seleccionables informan sus valores automáticamente. Para distinguirlos en tu código, asigna a cada entrada un **Element ID** significativo y a cada grupo seleccionable un **Group ID** en el builder. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: ## Antes de empezar Necesitas: - **Adapty SDK v4 o posterior**: Los callbacks de flow no existen en versiones anteriores. - **Un flow creado en el [Flow & Paywall Builder](adapty-flow-builder)**: Solo los flows reportan valores de entrada a través de este callback. - **Una versión de flow publicada recientemente**: Un flow solo reporta valores de entrada si lo publicaste después de que esta función estuviera disponible. Si no llega nada a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Recibir valores de entrada \{#receive-input-values\} Los valores de entrada llegan al mismo callback que todos los demás eventos analíticos de un flow, bajo el nombre de evento `flow_user_input`. Implementa `flowViewDidReceiveAnalyticEvent` en el observer que registras con `AdaptyUI().setFlowsEventsObserver`: ```dart showLineNumbers title="Flutter" void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { handleFlowInput(name, params); } ``` El callback `flowViewDidReceiveAnalyticEvent` entrega todos los eventos de análisis de un flow, incluyendo [vistas de pantalla](flutter-flow-screen-views). Para filtrar los eventos de entrada del usuario, compara `name` con `flow_user_input`. El parámetro que almacena el valor depende del `element_type`: | `element_type` | Parámetro que almacena el valor | Tipo Dart | |:---------------|:--------------------------------|:--------| | `text_input`, `email_input`, `number_input`, `phone_input` | `value` | `String` | | `date_picker`, `time_picker`, `date_time_picker` | `value` | `num`, tiempo Unix en milisegundos: `int` en iOS, `double` en Android. | | `single_choice`, `multi_choice` | `item_ids`, `item_titles` | `List` | | `toggle` | `value` | `bool` | La sección [Qué recibes](#what-you-receive) lista todos los parámetros y lo que reporta cada elemento del builder. Para confirmar que el callback se ejecuta, interactúa con el input en una versión de prueba de tu propia app. Si tu callback handler no recibe ningún evento, revisa los [prerequisitos](#before-you-start). Asegúrate de que el flow fue publicado después de que esta función estuviera disponible. ## Cuando tu aplicación recibe la entrada \{#when-your-app-receives-the-input\} :::important El valor predeterminado de entrada o selección nunca llega a tu aplicación a través de este callback. Si un usuario acepta la opción marcada como **Set as default** y continúa, no se dispara ningún evento. No interpretes la ausencia del evento como "sin respuesta": el usuario simplemente dejó el valor predeterminado tal como estaba. ::: **Los siguientes elementos disparan este evento:** - Campos de texto, email, número y teléfono - Selectores de fecha, hora y fecha-hora - Grupos seleccionables de opción única y múltiple, y toggles **Los siguientes no lo hacen:** - Campos de contraseña, selecciones de productos y cambios de pestaña - Inputs dentro de un elemento Header, que se comparte entre pantallas - Grupos seleccionables con IDs de opción duplicados o faltantes, o con un ID de grupo reutilizado en otra pantalla. Ese tipo de grupo no envía nada en absoluto, en lugar de enviar parte de la respuesta. **El evento se dispara cuando:** - Un campo pierde el foco. Un campo vaciado reporta una cadena vacía; un campo que el usuario nunca editó no reporta nada. Un marcador de posición no es un valor. Si el usuario vuelve al campo, lo edita y lo abandona de nuevo, se genera un segundo evento. - El usuario cierra un selector tras elegir un nuevo valor. Cerrarlo sin cambios no reporta nada. - El usuario pulsa una opción o un toggle. Un evento de opción múltiple lista todas las opciones seleccionadas, por lo que deseleccionar la última envía dos arrays vacíos. **El evento no se dispara cuando:** - El usuario escribe. No hay flujo de pulsaciones de teclas, solo el valor que tiene el campo cuando pierde el foco. - El usuario envía o cierra el flow. Un valor que aún se está editando en ese momento puede perderse; la sección de [limitaciones de entrega](#delivery-and-limitations) explica cómo diseñar la última pantalla teniendo esto en cuenta. - Se establece un valor sin interacción del usuario. Una opción marcada como **Set as default** se preselecciona al abrirse la pantalla, y una acción **Set Variable** puede seleccionar una opción o rellenar un input a partir de otra interacción. Ninguna de las dos envía un evento; un input prerrellenado solo se reporta una vez que el usuario lo edita. ## Qué recibes \{#what-you-receive\} El callback entrega dos eventos. Filtra por `name` para mostrar únicamente los eventos `flow_user_input`. El payload JSON de respuesta tiene este aspecto: ```json { "name": "flow_user_input", "instanceId": "scr_registration", "isBackendEvent": false, "isCustomerEvent": true, "element_id": "email", "element_type": "email_input", "value": "jane@example.com" } ``` | Parámetro | Descripción | |:------------------|:------------| | `name` | `flow_user_input` para eventos de entrada, `flow_screen_showed` para vistas de pantalla. | | `instanceId` | El ID de la pantalla del input. Los IDs de elemento son únicos dentro de una pantalla, no en todo el flow. Si tu flow tiene inputs en más de una pantalla, combina `instanceId` con `element_id` al filtrar eventos. | | `element_id` | El **Element ID** del [input](builder-inputs-and-forms), o el **Group ID** del [grupo seleccionable](flow-selectable-elements). | | `element_type` | El tipo de elemento que envió el evento. Determina cuál de los parámetros siguientes contiene el valor del input. | | `value` | **Solo campos de texto, selectores y toggles.** El valor del input: una cadena para campos de texto, un entero para selectores, un booleano para toggles. | | `item_ids` | **Solo grupos seleccionables de elección única y múltiple.** Los **Element IDs** de las opciones seleccionadas, en el orden en que aparecen en el builder. Una entrada para un grupo de elección única; cualquier número para un grupo de elección múltiple. | | `item_titles` | **Solo grupos seleccionables de elección única y múltiple.** Los títulos de las opciones listadas en `item_ids`, en el mismo orden. Nunca está vacío: una opción sin título informa su ID en su lugar. | | `isCustomerEvent` | Indicador de utilidad, siempre `true` para este evento. Marca los eventos que el SDK entrega a tu callback. Resulta útil si un mismo handler reenvía todos los eventos del flow a tu sistema de análisis y filtras por este indicador en lugar de por `name`. | | `isBackendEvent` | Indicador de utilidad, siempre `false` para este evento. Marca los eventos que Adapty también registra para su propio análisis. `false` confirma que lo que los usuarios introducen llega a tu app y solo a ella — Adapty no lo recibe ni lo almacena. | Lo que reporta cada elemento: | En el builder | `element_type` | Parámetro que almacena el valor | Qué contiene | |:--------------|:---------------|:--------------------------------|:-------------| | Input de **Texto**, **Número**, **Número de teléfono** | `text_input`, `number_input`, `phone_input` | `value` | La cadena literal que escribió el usuario. Los números llegan como cadenas, no como tipos numéricos. | | Input de **E-mail** | `email_input` | `value` | La cadena literal que escribió el usuario, aunque no haya superado la validación de formato del builder. Valídala en tu lado antes de usarla. | | Input de **Contraseña** | ninguno | ninguno | No envía ningún evento. | | Input de **Fecha** | `date_picker` | `value` | Tiempo Unix en milisegundos, como entero, a medianoche local de la fecha seleccionada. | | Input de **Hora** | `time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Input de **Fecha y hora** | `date_picker` y `time_picker` | `value` | Dos elementos, un selector de fecha y un selector de hora. Cada uno envía su propio evento. | | Input cambiado a **Fecha y hora** en el desplegable **Type** | `date_time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Grupo de **elección única** | `single_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: un array con el Element ID de la opción seleccionada. `item_titles`: un array con el título de esa opción. | | Grupo de **elección múltiple** | `multi_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: los Element IDs de todas las opciones seleccionadas, en el orden en que aparecen en el builder. `item_titles`: sus títulos, en el mismo orden. Ambos arrays están vacíos cuando no hay nada seleccionado. | | Grupo de **Toggle** | `toggle` | `value` | Un booleano. | Para bifurcar según una respuesta, compara `item_ids`, no `item_titles`. El título es derivado: el **Element Title** de la opción si lo definiste, si no su texto en tu idioma predeterminado, y si no su Element ID. Un usuario que vio el flow en otro idioma vio un texto diferente. ## Ejemplos de eventos \{#event-examples\} Estos ejemplos muestran las propiedades disponibles en cada evento, con valores ilustrativos en los comentarios. <Details> <summary>Entrada de texto, email, número y teléfono (haz clic para expandir)</summary> ```dart void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { name; // 'flow_user_input' params['name']; // 'flow_user_input' params['instanceId']; // 'scr_J260KU5q' params['isCustomerEvent']; // true params['isBackendEvent']; // false params['element_id']; // 'email' params['element_type']; // 'email_input' params['value']; // 'jane@example.com' (String) } ``` </Details> <Details> <summary>Selectores de fecha, hora y fecha-hora (Click to expand)</summary> ```dart void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { params['element_id']; // 'birthday' params['element_type']; // 'date_picker' params['value']; // 645408000000 (Unix milliseconds — 1990-06-15, local midnight; int on iOS, double on Android) } ``` </Details> <Details> <summary>Selección única (Click to expand)</summary> ```dart void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { params['element_id']; // 'experience' params['element_type']; // 'single_choice' params['item_ids']; // ['pro'] (List) params['item_titles']; // ['I train professionally'] } ``` </Details> <Details> <summary>Multi choice (Click to expand)</summary> ```dart void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { params['element_id']; // 'interests' params['element_type']; // 'multi_choice' params['item_ids']; // ['sports', 'music'] (List) params['item_titles']; // ['Sports', 'Music'] } ``` </Details> <Details> <summary>Toggle (Haz clic para expandir)</summary> ```dart void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { params['element_id']; // 'reminders' params['element_type']; // 'toggle' params['value']; // true (bool) } ``` </Details> ## Entrega y limitaciones \{#delivery-and-limitations\} :::warning Los flows envían valores sin procesar: direcciones de correo electrónico, números de teléfono y cualquier otra cosa que el usuario escriba. Trata todo lo que entregue el callback de analíticas como datos personales y no los almacenes en ningún lugar donde no guardarías el correo electrónico de un usuario. ::: - **El último valor prevalece**: recibes un evento por campo con el valor con el que el usuario se quedó, no una secuencia tecla a tecla. Si edita un campo, solo llega la última versión. - **Sin garantía de envío**: los valores te llegan a medida que el usuario avanza por el flow, y puede salir en cualquier momento. Espera a que el flow se cierre antes de considerar un conjunto de respuestas como completo. - **Entrega de mejor esfuerzo**: si un usuario cierra el flow o minimiza la app mientras un campo tiene el foco o un selector está abierto, ese valor puede perderse. Para que el valor del último campo sea fiable, termina el flow con una pantalla que no tenga entradas ni selectores, y cierra el flow mediante una acción explícita del usuario en lugar de hacerlo automáticamente al aparecer esa pantalla. Pasar a la pantalla final quita el foco del campo anterior, que es lo que envía su valor. Almacena cada valor de entrada a medida que lo recibe tu observer, y envía el conjunto completo cuando el flow se cierre. Para detectar ese momento, implementa `flowViewDidDisappear` en el mismo observer. Se ejecuta cuando la vista del flow se descarta, tanto si el usuario completó el flow como si lo cerró a mitad. ## Casos de uso \{#use-cases\} ### Registra usuarios en tu backend Recoge los valores a medida que llegan y envíalos una vez que el flow se cierre, de modo que una sola solicitud lleve un conjunto completo de respuestas. La vista desaparece tanto si el usuario completó el flow como si lo abandonó a medias. Comprueba que tienes los campos que necesitas antes de llamar a tu backend. El flow no puede mostrar errores de tu backend. El callback no tiene valor de retorno y el SDK no dispone de ningún método para enviar datos a un flow en ejecución. Si el registro falla, por ejemplo porque el correo electrónico ya está en uso, muestra el error en tu propia interfaz una vez que el flow se cierre. ```dart showLineNumbers title="Flutter" final Map<String, String> flowAnswers = {}; void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { if (name != 'flow_user_input') return; final elementId = params['element_id']; final value = params['value']; if (elementId is! String || value is! String) return; flowAnswers[elementId] = value; } void flowViewDidDisappear(AdaptyUIFlowView view) { if (!flowAnswers.containsKey('email')) return; // Send flowAnswers to your backend here to create the account. flowAnswers.clear(); } ``` ### Enriquece los perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Para vincular lo que un usuario introduce a su perfil y evitar pedirle los mismos datos dos veces, [actualiza el perfil de usuario](flutter-setting-user-attributes) a medida que lleguen los valores. Por ejemplo, si tu flow tiene un campo de texto con el ID de elemento `name` y un campo de email con el ID de elemento `email`: ```dart showLineNumbers title="Flutter" void handleFlowInput(String name, Map<String, dynamic> params) async { if (name != 'flow_user_input') return; final value = params['value']; if (value is! String) return; final builder = AdaptyProfileParametersBuilder(); switch (params['element_id'] as String) { case 'name': builder.setFirstName(value); break; case 'email': builder.setEmail(value); break; default: return; } try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } } ``` ### Personalizar flows que aparecen más adelante \{#customize-flows-shown-later\} Las respuestas del quiz también pueden decidir qué ve el usuario en un [placement](placements) posterior: un flow diferente, o un paywall distinto dentro de él. Por ejemplo, pregunta a los usuarios sobre su experiencia con el deporte en tu flow de onboarding y, después, muestra a cada grupo su propio flow con diferentes productos y textos. 1. Añade un [quiz](onboarding-quizzes) a tu flow. Asigna al [grupo seleccionable](flow-selectable-elements) el Group ID `experience`, y a cada opción un Element ID descriptivo. 2. Gestiona las respuestas y [establece atributos personalizados](flutter-setting-user-attributes) para el usuario. ```dart showLineNumbers title="Flutter" void handleFlowInput(String name, Map<String, dynamic> params) async { if (name != 'flow_user_input') return; if (params['element_id'] != 'experience') return; final optionId = (params['item_ids'] as List).cast<String>().first; final builder = AdaptyProfileParametersBuilder(); // Set the custom attribute 'experience' to the option the user selected // (beginner, amateur, or pro). builder.setCustomStringAttribute(optionId, 'experience'); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } } ``` 3. [Crea un segmento](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade una [audiencia](audience) para cada segmento. 5. [Muestra el flow](flutter-present-paywalls) de ese placement en tu app. --- # File: flutter-flow-custom-tags --- --- title: "Pasar datos a flows en Flutter SDK" description: "Pasa un valor desde tu app Flutter a un flow y muéstralo en un elemento de texto." --- Una [etiqueta personalizada](flow-custom-tags) es una variable personalizada con un valor de tu app. Después de seleccionar **Value comes from outside the app** en la configuración de una variable, tu app puede establecer su valor a través del SDK de Adapty. Por ejemplo, un elemento de texto contiene "Coins:" y una etiqueta personalizada. Si tu app pasa "120" para la etiqueta, los usuarios ven "Coins: 120". ## Antes de empezar \{#before-you-start\} Necesitas el SDK de Adapty v4 o posterior. Las versiones anteriores a v4 no muestran flows. ## Añadir etiquetas personalizadas al flow \{#add-custom-tags-to-the-flow\} Para mostrar los valores de tu app en un flow, configura etiquetas personalizadas en el builder: 1. [Crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) para cada valor que tu app envíe. <ZoomImage id="flow-custom-tag-create.webp" width="500px" alt="Formulario para crear una variable con nombre, descripción, tipo de valor String y valor inicial" /> 2. [Inserta cada etiqueta en un elemento de texto](flow-custom-tags#add-a-custom-tag-to-a-text-element). 3. Anota el nombre completo de cada etiqueta, incluido su grupo. El código de tu app pasa cada valor con ese nombre. Por ejemplo, si el grupo `user` contiene una etiqueta `streak`, el nombre completo de la etiqueta es `user.streak`. <ZoomImage id="flow-custom-tag-full-name.webp" width="500px" alt="Pestaña personalizada del desplegable Añadir variable con la etiqueta streak en el grupo user" /> ## Pasar valores de etiquetas personalizadas \{#pass-custom-tag-values\} Pasa los valores de tu app al parámetro `customTags` de `AdaptyUI().createFlowView` o `AdaptyUIFlowPlatformView`, como un `Map<String, String>`: - Cada clave es el nombre completo de una etiqueta personalizada, como `user.streak`. - Cada valor es la cadena que los usuarios verán en lugar de la etiqueta. - Cada valor debe ser legible por humanos, por ejemplo "1,250" en lugar de "1250.0". Adapty no lo formatea ni traduce. El SDK lee los valores una vez, cuando crea la vista, por lo que no cambian mientras el flow está abierto. <Tabs> <TabItem value="standalone" label="Pantalla independiente" default> ```dart showLineNumbers title="Flutter" final customTags = { 'username': 'DragonSlayer42', 'coins': '1,250', }; try { final view = await AdaptyUI().createFlowView( flow: flow, customTags: customTags, ); await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="embedded" label="Incrustado en la jerarquía de widgets"> ```dart showLineNumbers title="Flutter" AdaptyUIFlowPlatformView( flow: flow, customTags: const { 'username': 'DragonSlayer42', 'coins': '1,250', }, ) ``` Un `AdaptyUIFlowPlatformView` montado ignora los cambios posteriores en `customTags`. </TabItem> </Tabs> Para probar tus etiquetas personalizadas, abre el flow en tu app después de añadir el código que establece sus valores. El canvas del builder y la [vista previa en dispositivos](paywall-device-compatibility-preview#preview-on-devices) solo muestran los valores iniciales: la app móvil de Adapty no establece los valores de las etiquetas personalizadas. Si tu app ya establece valores de etiquetas personalizadas para paywalls del antiguo Paywall Builder, consulta [cómo difieren las etiquetas personalizadas en los flows](migrate-to-flows#custom-tags-from-the-old-paywall-builder). ## Lo que ven los usuarios sin un valor \{#what-users-see-without-a-value\} Los usuarios ven el **Valor inicial** de una etiqueta personalizada como valor de respaldo en los siguientes casos: - Tu app no pasa ningún valor para la etiqueta. - El usuario no ha actualizado tu app a una versión que envíe el valor. - El nombre en el código de tu app no coincide con el nombre completo de la etiqueta, incluidas las mayúsculas y minúsculas. - Otra etiqueta personalizada en el mismo elemento de texto no tiene valor. En ese caso, todas las etiquetas personalizadas de ese elemento de texto muestran su valor inicial. Para no mostrar nada en lugar de una etiqueta, pasa una cadena vacía. Adapty trata una cadena vacía como un valor, por lo que el valor de respaldo no aparece. ## Ejemplo: mostrar un IMC a partir de las respuestas del onboarding \{#example-show-a-bmi-from-onboarding-answers\} Un flow puede recoger respuestas de los usuarios, y una etiqueta personalizada puede mostrar un valor de tu app. Combina ambas cosas para mostrar a los usuarios un resultado que tu app calcula a partir de sus respuestas, como una puntuación, un plan recomendado o una métrica de salud: 1. Un flow recoge las respuestas y las envía a tu app. 2. Tu app calcula el resultado. 3. Tu app pasa el resultado al siguiente flow como valor de etiqueta personalizada. En este ejemplo, un flow de onboarding recoge el peso y la altura del usuario. Cuando el usuario cierra el flow de onboarding tras su última pantalla, tu app calcula su índice de masa corporal (IMC). Luego abre un flow de paywall que muestra el IMC. ### Configura los flows \{#set-up-the-flows\} 1. [Crea dos flows](paywall-builder-templates): un flow de onboarding que recoja el peso y la altura del usuario, y un flow de paywall que muestre su IMC. 2. Añade dos campos de tipo [**Number**](builder-inputs-and-forms) al flow de onboarding: - Uno para el peso en kilogramos, con el **Element ID** `weight`. - Uno para la altura en centímetros, con el **Element ID** `height`. En ambos campos, establece el **Format** como **Integer**. Un campo envía su valor como texto, exactamente tal como lo escribe el usuario, y el [código de ejemplo](#pass-the-bmi-to-the-paywall) no interpreta "70.5" y "70,5" de la misma manera. Los números enteros no tienen separador decimal, por lo que el código los lee igual en cualquier idioma. 3. Para asegurarte de que tu app recibe ambos valores, termina el flow de onboarding con una pantalla que no tenga campos. En esa pantalla, añade un botón con la acción [**Close flow**](onboarding-actions#close-flow). Los campos envían sus valores cuando pierden el foco o cuando el usuario pasa a otra pantalla. La acción Close flow no los envía. El código de ejemplo solo abre el paywall si el usuario llegó a la última pantalla. 4. En el flow de paywall, [crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) llamada `bmi`. En el campo **Initial value**, introduce un guion (`—`). Los usuarios verán el guion si tu app no pasa ningún IMC. 5. Añade un elemento de texto con las palabras "Your BMI:" al flow de paywall. Después de esas palabras, [inserta la etiqueta personalizada `bmi`](flow-custom-tags#add-a-custom-tag-to-a-text-element). 6. Añade el flow de onboarding a un [placement](placements), por ejemplo `onboarding`. Añade el flow de paywall a un placement con el ID `bmi_paywall`. El código de [Pass the BMI to the paywall](#pass-the-bmi-to-the-paywall) obtiene el paywall usando este ID. ### Pasar el IMC al paywall \{#pass-the-bmi-to-the-paywall\} Recoge el peso y la altura de los eventos `flow_user_input` del flow de onboarding, como se explica en [Procesar datos de flows](flutter-flow-input). Para saber que el usuario llegó a la última pantalla, observa el evento `flow_screen_showed` con `is_last_screen` establecido en `true`, como se describe en [Rastrear vistas de pantallas del flow](flutter-flow-screen-views). Cuando el usuario cierre el flow de onboarding tras esa pantalla, calcula el IMC. Luego crea la vista del paywall y pasa el IMC para la etiqueta personalizada `bmi`. Añade los siguientes métodos al observer que registras con `AdaptyUI().setFlowsEventsObserver`. El observer también debe implementar `flowViewDidFinishRestore` y `flowViewDidReceiveError`, que este ejemplo omite: ```dart showLineNumbers title="Flutter" final Map<String, String> bodyAnswers = {}; bool reachedLastScreen = false; void flowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, String name, Map<String, dynamic> params, ) { if (name == 'flow_screen_showed' && params['is_last_screen'] == true) { reachedLastScreen = true; return; } if (name != 'flow_user_input') return; final elementId = params['element_id']; final value = params['value']; if (elementId is! String || value is! String) return; bodyAnswers[elementId] = value; } void flowViewDidDisappear(AdaptyUIFlowView view) { // The observer receives events from every flow. Reset `bodyAnswers` and // `reachedLastScreen` whenever a flow disappears, so that the paywall // doesn't open again when the user closes it. final answers = Map<String, String>.of(bodyAnswers); final finished = reachedLastScreen; bodyAnswers.clear(); reachedLastScreen = false; // Number inputs send their values as strings. final weight = double.tryParse(answers['weight'] ?? ''); final height = double.tryParse(answers['height'] ?? ''); if (!finished || weight == null || height == null || height <= 0) return; final heightInMeters = height / 100; final bmi = weight / (heightInMeters * heightInMeters); showBmiPaywall(bmi.toStringAsFixed(1)); } Future<void> showBmiPaywall(String bmi) async { try { final flow = await Adapty().getFlow(placementId: 'bmi_paywall'); final view = await AdaptyUI().createFlowView( flow: flow, customTags: {'bmi': bmi}, ); await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } } ``` `toStringAsFixed` siempre utiliza un punto como separador decimal. Para usar el separador de la configuración regional del usuario, formatea el IMC con `NumberFormat` del paquete `intl`. ### Guardar el IMC en el perfil del usuario \{#save-the-bmi-to-the-users-profile\} Para mostrar a los usuarios distintos flows según su IMC, guárdalo como un [atributo personalizado](flutter-setting-user-attributes#custom-user-attributes) de su perfil. En el Adapty Dashboard, [crea un atributo personalizado de tipo **Number**](segments#custom-attributes) con la clave `bmi`. Luego muestra cada flow a una [audiencia](audience) definida a partir de ese atributo. Llama al método `updateProfile` en `flowViewDidDisappear`, justo después de la línea que calcula `bmi`. Pasa el `double`, no el string formateado, para que un segmento pueda comparar el IMC con un umbral: ```dart showLineNumbers title="Flutter" try { final builder = AdaptyProfileParametersBuilder() ..setCustomDoubleAttribute(bmi, 'bmi'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` --- # 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 app en la raíz del proyecto. 2. Llama al método `.setFallback` **antes** de obtener el flow, paywall u onboarding de destino. ```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 elige la localización para un flow o un onboarding, 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 en todas las plataformas que admite. Entender ese estándar te ayuda a predecir qué localización recibe 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 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\} En SDK v4, los flows y los onboardings hacen coincidir los códigos de idioma de forma diferente: los flows se localizan mediante el SDK en el dispositivo, y los onboardings, mediante el servidor de Adapty. ### Flows y paywalls Un paywall que Adapty renderiza se entrega como un flow en el SDK v4, por lo que la regla a continuación aplica a ambos. La coincidencia es exacta. El SDK compara el código que pasas con los códigos de localización del flow carácter por carácter: no cambia mayúsculas ni minúsculas, no reemplaza guiones bajos (`_`) por guiones (`-`), y no recurre a la subtag de idioma. Para un flow con una localización `pt-br`, solo coincide `pt-br`: `pt-BR`, `pt_BR` y `pt-PT` no coinciden. Cuando el código no coincide con ninguna localización, el flow se renderiza silenciosamente en su [idioma predeterminado](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) — el SDK no devuelve ningún error ni registra ninguna advertencia. Cuando el código coincide, Adapty combina la localización con la predeterminada: las cadenas de texto y los recursos que la localización encontrada no define se toman de la localización predeterminada. Omitir el código de idioma no equivale a solicitar la localización predeterminada del flow: el SDK sustituye un `en` fijo. Un flow cuyo idioma predeterminado es `de` seguirá renderizándose en `en` si dispone de una localización en `en`, y solo recurrirá a `de` cuando no la tenga. :::warning Pasa el código de idioma exactamente como está configurado en el dashboard: subtags en minúsculas separados por guiones. No pases directamente un identificador de idioma del sistema: `Platform.localeName` devuelve `pt_BR` y `PlatformDispatcher.instance.locale.toLanguageTag()` devuelve `pt-BR`, y ambos recurren a la localización predeterminada. Convierte el valor en tu app antes de pasarlo. ::: ### Onboardings Los onboardings se localizan en el servidor, y las reglas del servidor admiten otros formatos. Cuando pasas un `locale` a [`getOnboarding`](flutter-get-onboardings): 1. La cadena de locale 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 locale que coincida exactamente 3. Si no se encuentra ninguna coincidencia, Adapty toma la subcadena anterior al primer guion (`pt` para `pt-br`) y busca la localización que coincida 4. Si tampoco se encuentra ninguna coincidencia, Adapty devuelve el contenido para el locale predeterminado del onboarding Esta forma, `pt_BR`, `pt-BR` y `pt-br` resuelven a la misma localización de onboarding. ## Implementación de localizaciones \{#implementing-localizations\} En el SDK v4, no es necesario pasar un código de idioma al obtener un flow: `getFlow` devuelve el flow con todas sus localizaciones, y Adapty aplica una cuando se construye la vista del flow. El argumento `locale` de `getFlow` y `getFlowForDefaultAudience` no tiene efecto en los flows; está obsoleto y registra una advertencia. - **Flows creados en el builder**: el SDK no lee el idioma del dispositivo, así que resuélvelo en tu app y pásalo como argumento `locale` de `createFlowView` o `AdaptyUIFlowPlatformView`. El argumento es opcional — si lo omites, el flow se renderiza en `en`, o en su [idioma predeterminado](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) cuando el flow no tiene localización `en`. `AdaptyUIFlowView.locale` informa la localización con la que se construyó la vista. Requiere Flutter SDK 4.0.3 con las versiones nativas iOS 4.0.2 y Android 4.0.1, y es `null` con SDKs nativos más antiguos. - **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` parseado). Selecciona la entrada que corresponda al usuario, con tu propio fallback: ```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 ``` Adapty almacena esos códigos `locale` en el formato descrito en [Estándar de código de locale en Adapty](#locale-code-standard-at-adapty). El SDK no compara los Remote Configs con un locale, por lo que tu app es quien decide qué entrada aplicar. </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 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, se busca la localización cuyo código de idioma coincida exactamente 3. Si no se encuentra ninguna coincidencia, se toma la subcadena anterior al primer guión (`pt` en `pt-br`) y se busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, se devuelve el contenido del idioma predeterminado del paywall 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: "Solución de problemas del antiguo Paywall Builder en el SDK de Flutter" description: "Soluciona errores al mostrar paywalls creados con el antiguo Paywall Builder en el SDK de Flutter de Adapty." --- Esta guía te ayuda a resolver problemas comunes al usar paywalls diseñados en el Adapty Paywall Builder en el SDK de Flutter. Para problemas con flows, consulta [Problemas comunes](flow-common-issues). ## 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 contador de visualizaciones del paywall muestra el doble de lo 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 de un paywall creado en el Paywall Builder antiguo. Adapty registra esas visualizaciones automáticamente, por lo que no necesitas este método. **Solución**: Asegúrate de no estar llamando a `logShowFlow` (Flutter SDK v4+) / `logShowPaywall` en tu código para un paywall creado en el Paywall Builder antiguo. ## 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 Flutter SDK" description: "Presenta flows en modo Observer en tu app de Flutter mientras gestionas las compras con tu propio código." --- Si usas el [modo Observer](observer-vs-full-mode), eres responsable de tu propia lógica de compras. Pero puedes delegar la UI del paywall al [Flow & Paywall Builder](adapty-flow-builder). Diseña la pantalla en el builder y el SDK de Adapty la renderiza de forma nativa. Actualiza los textos, el diseño y los precios cuando quieras, sin publicar una nueva versión de la app. Esta guía explica cómo presentar un flow y gestionar los callbacks de compra. Si Adapty gestiona las compras por ti, consulta [Mostrar flows y paywalls](flutter-present-paywalls). ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty Flutter SDK 4.0 o posterior**: Las versiones anteriores no pueden renderizar flows. Para migrar una app existente, consulta la [guía de migración](migration-to-flutter-sdk-v4). - **`observerMode` establecido en `true`**: Consulta la [guía de instalación del SDK de Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk). - **Integración inicial con el store**: Configura Adapty [con App Store](initial_ios) y [con Google Play](initial-android). - **Un flow publicado con productos**: Créalo en el [Flow & Paywall Builder](adapty-flow-builder) y asigna [productos](create-product) a él. - **Un placement del flow**: [Crea un placement](create-placement) y asigna tu flow a él. - **El flow obtenido en tu app**: [Obtén el flow y su configuración](flutter-get-pb-paywalls) antes de mostrarlo. ## Implementación \{#implementation\} 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 renderizado por Adapty, el SDK llama a tu `AdaptyUIObserverModeResolver` en su lugar — realiza la compra o restauración con tu propio código allí. 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 personalizado de compra o restauración en respuesta. Recuerda también 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 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. | 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 renderizado por Adapty enruta 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-implement-paywalls-manually --- --- title: "Implementar paywalls personalizados en el SDK de Flutter" description: "Aprende cómo implementar paywalls personalizados en tu app Flutter 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 compras a Adapty mediante el método `makePurchase`. De esta forma, nosotros nos encargamos de todos los escenarios del usuario y tú solo necesitas 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 obtenerlos en el dashboard siguiendo la [guía de inicio rápido](quickstart). ::: <CustomDocCardList ids={['flutter-quickstart-manual', 'fetch-paywalls-and-products-flutter', 'present-remote-config-paywalls-flutter', 'flutter-making-purchases', 'flutter-restore-purchase', 'flutter-troubleshoot-purchases']} /> ## Modo observador \{#observer-mode\} Si quieres implementar tu propia lógica de gestión de compras desde cero, pero aun así quieres aprovechar las analíticas avanzadas de Adapty, puedes usar el modo observador. :::important Consulta las limitaciones del modo observador [aquí](observer-vs-full-mode). ::: <CustomDocCardList ids={['implement-observer-mode-flutter', 'report-transactions-observer-mode-flutter', 'flutter-troubleshoot-purchases']} /> --- # 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 Flow & Paywall Builder](flutter-quickstart-paywalls). Con el Flow & 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 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 personalizados**](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 gestiona un objeto **flow** — obtienes un flow y consultas sus productos. - [**Placements**](placements) – dónde y cuándo muestras 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 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, tendrás que crear un **paywall personalizado** y añadirlo a un **placement**. Esta configuración te permite recuperar tus productos. Para entender qué tienes que 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 personalizados y productos en el SDK de Flutter" description: "Obtén paywalls y productos en el SDK de Flutter de Adapty para mejorar la monetización de los usuarios." --- <SDKv4> Antes de mostrar paywalls personalizados, necesitas obtener información sobre ellos. Ten en cuenta que este tema hace referencia a 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 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-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: `AdaptyFlowFetchPolicy.reloadRevalidatingCacheData` | <p>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyFlowFetchPolicy.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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>Una tercera política, `AdaptyFlowFetchPolicy.returnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché sea más reciente que el `Duration` que le pases, y va al servidor cuando sea más antigua.</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é 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 sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls 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></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 estar compuesta por 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** | requerido | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | predeterminado: `AdaptyFlowFetchPolicy.reloadRevalidatingCacheData` | <p>`fetchPolicy` establece qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyFlowFetchPolicy.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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>Una tercera política, `AdaptyFlowFetchPolicy.returnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché sea más reciente que la `Duration` que indiques, y va al servidor una vez que sea más antigua.</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> | </SDKv4> <SDKv3> Antes de mostrar paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls personalizados. Para obtener orientación sobre cómo recuperar paywalls creados con el Paywall Builder, consulta [Obtener paywalls del 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** | 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` 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 más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.reloadRevalidatingCacheData` | <p>`fetchPolicy` define qué capa lee el SDK primero, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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>Una tercera política, `AdaptyPaywallFetchPolicy.returnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché sea más reciente que la `Duration` que indiques, y va al servidor cuando sea más antigua.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la app y solo se elimina 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](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 la 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 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 de espera especificado en `loadTimeout`, ya que la operación puede consistir en diferentes solicitudes 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 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/3.17.2/adapty_flutter/AdaptyPaywall-class.html) con: una lista de IDs de producto, 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** | requerido | El identificador del [Placement](placements). Este 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](flutter-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | predeterminado: `AdaptyPaywallFetchPolicy.reloadRevalidatingCacheData` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. Es posible que los usuarios no obtengan los datos más recientes, pero experimentarán tiempos de carga más rápidos sin importar qué tan inestable 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>Una tercera política, `AdaptyPaywallFetchPolicy.returnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché sea más reciente que la `Duration` que indiques, y va al servidor cuando es más antigua.</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> | </SDKv3> --- # File: present-remote-config-paywalls-flutter --- --- title: "Presentar paywalls personalizados en Flutter SDK" description: "Descubre cómo presentar paywalls personalizados en el SDK de Flutter de Adapty 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/3.17.2/adapty_flutter/AdaptyPaywall-class.html). | </SDKv3> --- # File: flutter-making-purchases --- --- title: "Realizar compras in-app en Flutter 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, mostrar un paywall solo admite compras de forma autónoma cuando Adapty renderiza la pantalla, es decir, un [flow](adapty-flow-builder) o un paywall del [antiguo Paywall Builder](adapty-paywall-builder). Si renderizas la pantalla con tu propio código, debes usar un método específico 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 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 Adapty renderiza la pantalla. 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 **¿Adapty renderiza tu pantalla?** En un [flow](adapty-flow-builder) o un paywall de [Paywall Builder](adapty-paywall-builder), las compras se procesan automáticamente; puedes omitir este paso. **¿Buscas una guía paso a paso?** Consulta la [guía de inicio rápido](flutter-implement-paywalls-manually) para instrucciones de implementación completas 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>Acerca de los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios específicos. A diferencia de las ofertas regulares que se aplican automáticamente, los códigos de oferta se distribuyen fuera de la app — a través de campañas de correo electrónico, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, siguiendo 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 realiza al precio completo. - **Pay as you go** — el usuario paga un precio con descuento en cada ciclo de facturación durante un período determinado, luego la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único con descuento por toda la duración de la oferta, luego 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`. Una vez que el período de oferta termina y la suscripción se renueva al precio completo, la etiqueta ya no está presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Solución de problemas de discrepancias en 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 con descuento de la oferta, verifica lo siguiente en App Store Connect: - El código de oferta tiene el precio correcto configurado para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está establecido para el país o región específico 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 en su lugar. Puedes filtrar y verificar las transacciones con códigos 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 reemplazan con más funcionalidades: elegibilidad configurable, fechas de vencimiento y hasta 1 millón de códigos por trimestre. Si anteriormente usabas códigos promocionales para compras in-app, haz la transición a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app por versión) otorgaban 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ódigos promocionales — enviaba el precio completo del producto en el recibo. Como resultado, Adapty registraba estas transacciones al precio completo, lo que causaba discrepancias de ingresos 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. Dado que estos códigos están ahora obsoletos, haz la transición 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}` ::: ## Compras in-app promocionadas desde la App Store \{#promoted-in-app-purchases-from-the-app-store\} :::info Las compras in-app promocionadas son compatibles a partir de la versión 4.1 del SDK, en **iOS 16.4** o posterior. Por debajo de iOS 16.4, en Android y en modo observador, `didReceivePromotedPurchaseStream` nunca emite. ::: Cuando un usuario inicia una compra desde la página de tu producto en la App Store y la transacción llega a tu app, el SDK la completa automáticamente: la pantalla del sistema de compras de Apple aparece de inmediato y Adapty procesa la transacción como cualquier otra compra. No necesitas escribir ningún código para esto. Si el producto promocionado lleva una oferta de suscripción, el SDK la aplica automáticamente en el momento de la compra. La oferta se obtiene del intent de compra de App Store, que la expone en iOS 18.0 y versiones posteriores. En iOS 16.4–17.x, la compra se realiza al precio base. Para gestionar tú mismo las compras promocionadas —por ejemplo, para mostrar primero tu propia pantalla— suscríbete a `didReceivePromotedPurchaseStream` y pasa el producto a `makePromotedPurchase`: ```dart showLineNumbers Adapty().didReceivePromotedPurchaseStream.listen((product) async { try { final result = await Adapty().makePromotedPurchase(product: product); // process the purchase result } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } }); ``` :::warning Mientras haya algo suscrito al stream, el SDK deja de completar las compras promocionadas por ti. Si tu handler nunca llama a `makePromotedPurchase`, la compra nunca se realiza: el App Store entrega el producto a tu app y espera. El SDK tampoco interviene si tu handler lanza un error. ::: `makePromotedPurchase` no recibe parámetros de compra, porque un producto promocionado proviene del App Store en lugar de un paywall y no lleva contexto de paywall. Devuelve el mismo `AdaptyPurchaseResult` que `makePurchase`. El responsable de completar la compra se decide para cada evento, por lo que un stream al que escuchas solo mientras una pantalla está montada devuelve las compras promocionadas al SDK una vez que esa pantalla desaparece. Puedes suscribirte antes de `activate`, que es la forma de capturar una compra que la store entrega al arrancar. ## Gestiona planes prepago (Android) \{#manage-prepaid-plans-android\} 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. ```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 la app móvil con Flutter SDK" description: "Aprende a restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- Restaurar compras en iOS y Android es una función 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 función es especialmente útil para quienes hayan desinstalado y reinstalado la app o hayan cambiado a un dispositivo nuevo y quieran acceder a su contenido ya adquirido sin pagar de nuevo. :::note Si construiste la pantalla con un builder, no necesitas restaurar las compras manualmente. Conecta su control de restauración — la acción **Restore purchases** en el Flow & Paywall Builder, o un enlace o botón de restauración en el antiguo — y el SDK completa la restauración. ::: Si construiste la pantalla tú mismo, 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> | :::info En Adapty Flutter SDK 4.0.4 y versiones posteriores, `restorePurchases()` se completa correctamente aunque no haya nada que restaurar: devuelve el perfil actual, así que comprueba el estado del nivel de acceso en él. En versiones anteriores del SDK, el método falla con el error `noPurchasesToRestore` en Android en este caso. ::: :::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. :::tip En el SDK v4, también puedes presentar flows y paywalls renderizados por Adapty en el modo Observer: registra un `AdaptyUIObserverModeResolver` con `setObserverModeResolver` 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). ::: ## 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 flows y paywalls de Adapty en el modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los flows, paywalls y funciones de prueba A/B de Adapty, puedes hacerlo, pero requiere configuración adicional en el modo Observer. Esto es lo que necesitarás hacer además de los pasos anteriores: 1. Muestra los [paywalls personalizados](present-remote-config-paywalls-flutter) como de costumbre. Para flows y paywalls creados con Flow & Paywall Builder, consulta [Presentar flows en modo Observer](flutter-present-flows-in-observer-mode). 2. [Asocia flows y paywalls](report-transactions-observer-mode-flutter) con transacciones de compra. --- # 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 flows y paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el flow o paywall que la originó, garantizando análisis precisos. ```dart showLineNumbers try { // every time when calling transaction.finish() await Adapty().reportTransaction( transactionId: "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 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://pub.dev/documentation/adapty_flutter/3.17.2/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 flows y paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el flow o paywall que la generó, lo que garantiza un análisis preciso. ```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( transactionId: "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, 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/3.17.2/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 informar una transacción a Adapty en Observer Mode, 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> **Asociar flows y 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 tienes previsto usar flows, paywalls y/o pruebas A/B en el modo Observer, necesitas asociar la transacción proveniente de tu store con el flow o paywall correspondiente en el código de tu app. Es importante hacerlo correctamente antes de publicar tu app; de lo contrario, se producirán 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-user --- --- title: "Usuarios y acceso en el SDK de Flutter" description: "Aprende a trabajar con usuarios y niveles de acceso en tu app de Flutter con el SDK de Adapty." --- <CustomDocCardList /> --- # File: flutter-identifying-users --- --- title: "Identificar usuarios en Flutter 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, 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 el SDK de Flutter" description: "Aprende cómo establecer atributos de usuario en Adapty para mejorar la segmentación de audiencias." --- Puedes establecer atributos opcionales como correo electrónico, número de teléfono, etc., para los usuarios de tu app. Luego puedes usar estos 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: "Comprobar el estado de la suscripción en Flutter SDK" description: "Haz un seguimiento y gestiona el estado de la suscripción de los usuarios en Adapty para mejorar la retención de clientes en tu aplicación Flutter." --- Con Adapty, hacer un 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 fácilmente el estado de la suscripción de un usuario comprobando si tiene un [nivel de acceso](access-level) activo. <details> <summary>Antes de empezar a verificar 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-onboardings --- --- title: "Onboardings en Flutter SDK" description: "Aprende a trabajar con onboardings en tu app de Flutter con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: <CustomDocCardList /> --- # 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 a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 Flutter SDK" description: "Aprende a presentar onboardings de forma efectiva para conseguir más conversiones." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 eventos relacionados con onboarding en Flutter usando Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 en pantalla completa**: Requiere configurar un observador de eventos global que gestione los eventos de todas las vistas de onboarding - **Widget embebido**: Gestiona los eventos mediante parámetros de callback directamente en el widget Antes de comenzar, asegúrate de que: 1. Tienes 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 usa datos de onboardings en tu app Flutter con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: Cuando tus usuarios responden a una pregunta de un quiz o introducen sus 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: ```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 la 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 params (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-best-practices --- --- title: "Buenas prácticas en el SDK de Flutter" description: "Patrones de referencia para integrar el SDK de Adapty en Flutter: orden de llamadas, manejo de errores y otras reglas para entornos de producción." --- <CustomDocCardList /> --- # 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 iniciar la app, lo primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `Adapty().activate(configuration: ...)` con `withCustomerUserId` establecido en la configuración | Al iniciar 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 iniciar 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 lleguen al perfil correcto. | | 4 | `await Adapty().identify(customerUserId)` | 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` (`getFlow` en SDK v4), `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution` (`updateExternalAttribution` en SDK 4.1), `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 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 flows y paywalls en el SDK de Flutter" description: "Obtén flows y paywalls de Adapty de forma fiable: temporización, caché y patrones de respaldo para Flutter." --- Una obtención fiable de un flow o paywall en Flutter hace tres cosas: se renderiza rápido, devuelve la variación dirigida a la audiencia correspondiente y tiene un respaldo elegante cuando la red es lenta. Las reglas que se describen a continuación cubren los patrones de temporización, caché y respaldo necesarios para lograrlo. :::tip Las reglas asumen que `Adapty().activate()` y `Adapty().identify()` ya se han resuelto. Consulta [Orden de llamadas en el SDK de Flutter](flutter-sdk-call-order). ::: ## 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 de forma concurrente al iniciar. | La precarga masiva bloquea el hilo principal y produce una pantalla en negro durante el pico de carga. | | Llama a `getFlow` después de que la atribución haya tenido tiempo de resolverse — por ejemplo, 1–2 segundos después de `activate` o tras que `didUpdateProfileStream` se dispare. | Llama a `getFlow` en `main()` antes de `runApp`. | La atribución aún no ha llegado. El flow 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. | Esperar `getFlow` indefinidamente. | Sin un tiempo de espera, 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 personalizados](fetch-paywalls-and-products-flutter) para la referencia de los parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado. ## Mostrar el contenido multimedia de la primera pantalla desde el bundle de la app \{#show-first-screen-media-from-the-app-bundle\} Un flow descarga sus imágenes y vídeos desde Adapty. Para mostrar el contenido multimedia de la primera pantalla al instante, sírvelo desde el bundle de la app. Es una buena forma de reutilizar recursos que ya incluyes en la app, como los visuales de un onboarding nativo existente. 1. En el Flow & Paywall Builder, asigna un [ID de medio personalizado](custom-media#custom-media-id) a la imagen o el vídeo. El archivo que subas ahí se usará como respaldo. 2. Añade el archivo a los assets de Flutter y regístralo en `assets` dentro de `pubspec.yaml`. 3. Al crear la vista del flow, pasa el archivo incluido para ese ID en `customAssets`: ```dart showLineNumbers // "welcome_video" is the custom media ID set in the Flow & Paywall Builder final bundledAssets = { 'welcome_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/welcome.mp4', ), }; try { final view = await AdaptyUI().createFlowView( flow: flow, customAssets: bundledAssets, ); } on AdaptyError catch (e) { // handle the error } ``` Los archivos incluidos aumentan el tamaño de descarga de tu app, así que incluye solo los recursos que el usuario ve primero. Los archivos multimedia que no incluyas en el bundle aparecerán igualmente de inmediato: el layout del flow incluye una copia en baja resolución de cada imagen, incluyendo el fotograma estático de los vídeos, y la muestra hasta que se cargue el archivo completo. Para la referencia completa de `customAssets`, consulta [Personalizar assets](flutter-get-pb-paywalls#customize-assets). ## Ajusta para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte, regiones afectadas por enrutamiento): - Establece `fetchPolicy: AdaptyFlowFetchPolicy.returnCacheDataElseLoad` en cada llamada 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 bloquees la visualización del flow esperando `getProfile()`. Llama a `getFlow` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: flutter-show-aa-targeted-paywall --- --- title: "Mostrar un paywall dirigido por AA en el primer lanzamiento con el SDK de Flutter" description: "Espera brevemente a la atribución de Apple Ads antes de mostrar el paywall en el primer lanzamiento en Flutter, volviendo a la audiencia predeterminada si hay timeout. Usa AdaptyProfile.appliedExternalAttributionProviders." --- Este artículo aplica a la compilación iOS de tu app. La atribución de Apple Ads solo existe en iOS. Apple Ads (AA) attribution llega de forma asíncrona después de `Adapty().activate()`. En el primer arranque, normalmente aún no ha llegado, así que si llamas a `getFlow` de inmediato, Adapty resuelve la solicitud con la audiencia predeterminada y los usuarios de Apple Ads no ven el 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.appliedExternalAttributionProviders` indica cuándo se ha aplicado la atribución de AA. :::important Esta propiedad solo reporta Apple Ads. La atribución de otros proveedores es visible en los perfiles de usuario y está disponible en los filtros de segmento, pero aún no aquí. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - Adapty Flutter SDK **4.1** o posterior. En la versión 4.0.x, la propiedad del perfil se llama `appliedAttributionSources` y sus valores son `AdaptyAttributionSource` — consulta [Migrar a v4.1](migration-to-flutter-sdk-41). En las versiones 3.17.0–3.x, los flows también se obtienen con `getPaywall`/`getPaywallForDefaultAudience` y el tipo devuelto es `AdaptyPaywall` — consulta [Migrar a v4.0](migration-to-flutter-sdk-v4). - Apple Ads configurado para la aplicación 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 para el perfil, el SDK entrega un `AdaptyProfile` actualizado a tu listener `didUpdateProfileStream`, con `AdaptyExternalAttributionProvider.appleAds` en su lista `appliedExternalAttributionProviders`. En el primer lanzamiento, esto genera dos resultados que debes gestionar: 1. **La atribución llega antes de que expire el tiempo de espera.** Llama a `getFlow` — Adapty resuelve la solicitud con la audiencia de Apple Ads y devuelve el paywall objetivo. 2. **El tiempo de espera expira primero.** Muestra el paywall de la audiencia predeterminada en su lugar, para que los usuarios sin atribución de Apple Ads no tengan que esperar. `getFlowForDefaultAudience` lo devuelve sin esperar a la segmentación. `appliedExternalAttributionProviders` puede estar vacío. Eso significa una de las siguientes situaciones: - La atribución de Apple Ads aún no se ha procesado para este perfil. - No ha llegado ninguna atribución. - La atribución llegó desde otro proveedor, que este array no informa. En los tres casos, `getFlowForDefaultAudience` es seguro de llamar: devuelve el paywall de 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 incluye `AdaptyExternalAttributionProvider.appleAds` en `appliedExternalAttributionProviders`, por lo que la ruta de atribución se resuelve de inmediato y `getFlow` devuelve el paywall segmentado por Apple Ads sin ningún retraso. ::: ## Implementación \{#implementation\} En el primer lanzamiento, espera a `AdaptyExternalAttributionProvider.appleAds` y aplica un tiempo límite 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 Flutter](sdk-installation-flutter). 2. **Suscríbete a las actualizaciones de 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 `AdaptyExternalAttributionProvider.appleAds` en `appliedExternalAttributionProviders`.** Cuando aparezca, carga el paywall con `getFlow` — Adapty devuelve la variante segmentada por AA: ```dart final subscription = Adapty().didUpdateProfileStream.listen((profile) async { if (!profile.appliedExternalAttributionProviders.contains(AdaptyExternalAttributionProvider.appleAds)) return; final paywall = await Adapty().getFlow(placementId: placementId); // present the segmented paywall, then cancel the subscription and the timer }); ``` `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 `AdaptyExternalAttributionProvider.appleAds`, carga el paywall de la audiencia predeterminada con `getFlowForDefaultAudience`. Muestra el primer paywall que se resuelva y cancela el otro proceso, 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 que se muestra a continuación compite en velocidad entre la atribución y un timeout, precarga el paywall de audiencia predeterminada en paralelo y devuelve el paywall que corresponda. El código que llama a esta función solo espera el resultado de una sola función — sin listeners ni indicadores de estado que gestionar: - Si la atribución llega antes del `timeout`, devuelve el paywall segmentado mediante `getFlow`. - Si el `timeout` vence primero, devuelve el paywall de audiencia predeterminada precargado mediante `getFlowForDefaultAudience`. ```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<AdaptyFlow> getFlowOrDefault({ required String placementId, required Duration timeout, }) { // Prefetch the default-audience paywall right away so the timeout path resolves // without an extra network round-trip. `getFlowForDefaultAudience` 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().getFlowForDefaultAudience(placementId: placementId)..ignore(); final completer = Completer<AdaptyFlow>(); late final StreamSubscription<AdaptyProfile> subscription; late final Timer timer; void resolve(Future<AdaptyFlow> paywall) { if (completer.isCompleted) return; timer.cancel(); subscription.cancel(); completer.complete(paywall); } void onProfile(AdaptyProfile profile) { if (profile.appliedExternalAttributionProviders.contains(AdaptyExternalAttributionProvider.appleAds)) { resolve(Adapty().getFlow(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 getFlowOrDefault( 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: flutter-reference --- --- title: "Referencia del SDK de Flutter" description: "Documentación de referencia del SDK de Flutter de Adapty." --- Esta página contiene la documentación de referencia del SDK de Flutter de Adapty. Elige el tema que necesitas: - **[Modelos del SDK](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/#classes)** - Modelos de datos y estructuras utilizados por el SDK - **[Gestión de errores](error-handling-on-flutter-react-native-unity)** - Gestión de errores y resolución de problemas --- # File: error-handling-on-flutter-react-native-unity --- --- title: "Gestionar errores en el SDK de Flutter" description: "Gestiona errores en el SDK de Flutter." --- Todos los errores que devuelve el SDK son de tipo `AdaptyErrorCode`. 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 (`await Adapty().setLogLevel(AdaptyLogLevel.verbose)` — consulta [Logging](sdk-installation-flutter#logging)), ese error 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 conocer los pasos a seguir antes de contactar con soporte, y así poder ayudarte de forma más eficiente. ::: ```dart showLineNumbers try { final result = await adapty.makePurchase(product: product); } on AdaptyError catch (adaptyError) { if (adaptyError.code == AdaptyErrorCode.paymentCancelled) { // Cancelled } } catch (e) { } ``` ## Códigos del sistema StoreKit \{#system-storekit-codes\} | Error | Código | Solución | |-----|----|-----------| | [unknown](https://developer.apple.com/documentation/storekit/skerror/code/unknown) | 0 | Código de error que indica que ocurrió un error desconocido o inesperado. <br/> Vuelve a intentarlo o consulta la sección [Otros problemas](#other-issues). | | [clientInvalid](https://developer.apple.com/documentation/storekit/skerror/code/clientinvalid) | 1 | Este código de error indica que el cliente no tiene permiso para realizar la acción intentada. | | [paymentCancelled](https://developer.apple.com/documentation/storekit/skerror/code/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, aunque a nivel de lógica de negocio puedes ofrecer un descuento al usuario o recordárselo más adelante.</p> | | [paymentInvalid](https://developer.apple.com/documentation/storekit/skerror/code/paymentinvalid) | 3 | Este error indica que uno de los parámetros de pago no fue reconocido por el App Store. | | [paymentNotAllowed](https://developer.apple.com/documentation/storekit/skerror/code/paymentnotallowed) | 4 | Este código de error indica que el usuario no tiene permiso para autorizar pagos. | | [storeProductNotAvailable](https://developer.apple.com/documentation/storekit/skerror/code/storeproductnotavailable) | 5 | Este código de error indica que el producto solicitado no está disponible en el store. <br/> Prueba a reinstalar la app. | | [cloudServicePermissionDenied](https://developer.apple.com/documentation/storekit/skerror/code/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](https://developer.apple.com/documentation/storekit/skerror/code/cloudservicenetworkconnectionfailed) | 7 | Este código de error indica que el dispositivo no pudo conectarse a la red. | | [cloudServiceRevoked](https://developer.apple.com/documentation/storekit/skerror/code/cloudservicerevoked/) | 8 | Este código de error indica que el usuario ha revocado el permiso para usar este servicio en la nube. | | [privacyAcknowledgementRequired](https://developer.apple.com/documentation/storekit/skerror/code/privacyacknowledgementrequired) | 9 | Este código de error indica que el usuario aún no ha aceptado la política de privacidad de Apple. | | [unauthorizedRequestData](https://developer.apple.com/documentation/storekit/skerror/code/unauthorizedrequestdata) | 10 | Este código de error indica que la app intenta usar una propiedad para la que no tiene el entitlement necesario. | | [invalidOfferIdentifier](https://developer.apple.com/documentation/storekit/skerror/code/invalidofferidentifier) | 11 | <p>El [`identifier`](https://developer.apple.com/documentation/storekit/skpaymentdiscount/identifier) de la oferta no es válido. Por ejemplo, no has configurado ninguna oferta con ese identificador en el App Store, o has revocado la oferta.</p><p>Asegúrate de configurar las ofertas deseadas en AppStore Connect y de pasar un identificador de oferta válido.</p> | | [invalidSignature](https://developer.apple.com/documentation/storekit/skerror/code/invalidsignature) | 12 | Este código de error indica que la firma en un descuento de pago no es válida. | | [missingOfferParams](https://developer.apple.com/documentation/storekit/skerror/code/missingofferparams) | 13 | Este código de error indica que faltan parámetros en un descuento de pago. | | [invalidOfferPrice](https://developer.apple.com/documentation/storekit/skerror/code/invalidofferprice/) | 14 | Este código de error indica que el precio que especificaste en App Store Connect ya no es válido. Las ofertas deben representar siempre un precio con descuento. | ## Códigos personalizados de Android \{#custom-android-codes\} | Error | Code | Solution | |-----|----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | adaptyNotInitialized | 20 | Debes configurar correctamente el SDK de Adapty mediante el método `Adapty.activate`. Aprende cómo hacerlo [para Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk). | | productNotFound | 22 | Este error indica que el producto solicitado para la compra no está disponible en el store. | | invalidJson | 23 | El JSON del paywall no es válido. Corrígelo en el Adapty Dashboard. Consulta el tema [Personalizar el paywall con Remote Config](customize-paywall-with-remote-config) para saber cómo solucionarlo. | | currentSubscriptionToUpdateNotFoundInHistory | 24 | No se encuentra la suscripción original que debe renovarse. | | pendingPurchase | 25 | Este error indica que el estado de la compra es 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 ha alcanzado el tiempo de espera máximo antes de que Google Play pueda 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 el 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 de `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 se produjo un error de facturación del usuario durante el proceso de compra. Algunos casos en los que puede ocurrir:</p><p></p><p>1\. La app del Play Store en el dispositivo del usuario está desactualizada.</p><p>2. El usuario se encuentra en un país no compatible.</p><p>3. El usuario es un empleado de una empresa y su administrador ha desactivado las compras para los usuarios.</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 expirado.</p><p>5. El usuario no ha iniciado sesión en la app del 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 el propio 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ó sin | ## Códigos StoreKit personalizados \{#custom-storekit-codes\} | Error | Código | Solución | |-----------------------------------------------------------------------------------------------------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | noProductIDsFound | 1000 | <p>Este error indica que ninguno de los productos que solicitaste en el paywall está disponible para su compra en el App Store, aunque estén listados allí. Este error puede ir acompañado a veces de una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin error, ignórala.</p><p>Si encuentras este error, sigue los pasos en la sección [Solución para el error `noProductIDsFound` con código 1000](InvalidProductIdentifiers-flutter).</p> | | productRequestFailed | 1002 | No se pueden obtener los productos disponibles en este momento. | | cantMakePayments | 1003 | Las compras in-app no están permitidas en este dispositivo. Consulta la [guía](cantMakePayments-flutter) de solución de problemas. | | noPurchasesToRestore | 1004 | Obsoleto a partir de Adapty Flutter SDK 4.0.4: `restorePurchases()` ya no devuelve este error. Cuando no hay nada que restaurar, el método se completa correctamente con el perfil actual, así que comprueba el estado del nivel de acceso en él. En versiones anteriores del SDK, este error indica que Google Play no encontró la compra a restaurar. | | [cantReadReceipt](https://developer.apple.com/documentation/storekit/skerror/code/paymentcancelled) | 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>En el sandbox, no tendrás un archivo de recibo válido hasta que hagas una compra, así que asegúrate de realizar una antes de acceder a él. Durante las pruebas en sandbox, asegúrate también de haber iniciado sesión en el dispositivo con una cuenta de sandbox de Apple válida.</p> | | productPurchaseFailed | 1006 | La compra del producto falló. Esto envuelve un error subyacente de StoreKit — lee el error interno (o activa los logs detallados para verlo en la consola) para conocer el motivo real. El error interno suele ser uno de los códigos de StoreKit 0–14 de la tabla anterior — habitualmente `paymentCancelled`, `paymentInvalid`, `paymentNotAllowed` o `invalidOfferPrice`. Si no puedes identificar un motivo concreto, 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. Aplicable solo a StoreKit 1. | | receiveRestoredTransactionsFailed | 1011 | La restauración de compras falló. | ## Códigos de red personalizados \{#custom-network-codes\} | Error | Código | Solución | | :------------------- | :--- |:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | notActivated | 2002 | El SDK de Adapty no está activado. <br/> Ocurre con frecuencia cuando una pantalla de presentación o un hook temprano de UI llama a métodos de Adapty antes de que `Adapty().activate` devuelva el resultado. El síntoma es intermitente y puede no reproducirse en el emulador porque el timing en dispositivos reales es diferente. Usa `await` con el Future de `activate` antes de programar cualquier otra llamada al SDK. Consulta [Orden de llamadas en el SDK de Flutter](flutter-sdk-call-order) para ver la secuencia completa. | | badRequest | 2003 | Solicitud incorrecta. <br/> Asegúrate de haber completado todos los pasos necesarios para [integrarte con el App Store](app-store-connection-configuration). | | serverError | 2004 | Error del servidor. <br/> Vuelve a intentarlo después de un tiempo. Si el problema persiste, contacta con el equipo de soporte de Adapty. | | networkFailed | 2005 | Este error indica problemas con la conexión de red en el dispositivo del usuario. <br/> Prueba a desactivar la VPN o a cambiar de red móvil a WiFi, o viceversa. | | decodingFailed | 2006 | Este error indica que falló la decodificación de la respuesta. <br/> 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 de API no válida. | | encodingFailed | 2009 | Este error indica que falló la codificación de la solicitud. | | analyticsDisabled | 3000 | No podemos gestionar los eventos de analíticas porque has [desactivado esta opción](analytics-integration#disabling-external-analytics-for-a-specific-customer). | | wrongParam | 3001 | Este error indica que alguno de tus parámetros no es correcto. <br/> Si usas el Paywall Builder de Adapty y no puedes mostrar un paywall debido a este error, activa **Show on device** en el Paywall Builder.<br/> Otra posible causa es que la versión del archivo [fallback](fallback-paywalls) local no coincida con la versión del SDK. Descarga un archivo nuevo desde el dashboard. | | 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. <br/> Esto ocurre cuando se llama a un método mientras `Adapty().identify` aún está en curso: la llamada en vuelo llega a un perfil que está a punto de reemplazarse y el SDK la rechaza. Usa siempre `await` con `identify` antes de cualquier llamada relacionada con acciones del usuario. Consulta [Orden de llamadas en el SDK de Flutter](flutter-sdk-call-order). | | unsupportedData | 3007 | Este error indica que el formato de datos no es compatible con el SDK. | | fetchTimeoutError | 3101 | Este error indica que la operación de obtención de datos superó el tiempo de espera. | ## Código de interfaz personalizado y códigos de plugins \{#custom-ui-and-plugin-codes\} | Error | Código | Solución | |:---------------------|:--------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | jsException | 4105 | Se lanzó una excepción desde JavaScript mientras se ejecutaba un paywall u onboarding creado en el builder. [Activa el registro detallado](sdk-installation-flutter#logging) para ver el error de JavaScript subyacente. | | operationInterrupted | 9000 | Esta operación fue interrumpida por el sistema. <br/> Vuelve a intentar la llamada. | | emptyResult | 10001 | El plugin de Flutter recibió un resultado vacío del SDK nativo. <br/> Vuelve a intentar la llamada y consulta la sección [Otros problemas](#other-issues). | | internalPluginError | 10002 | Se produjo un error interno en el plugin de Flutter. <br/> Consulta la sección [Otros problemas](#other-issues). | ## Otros problemas \{#other-issues\} Si aún 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 para problemas conocidos. - **Contacta con el equipo de soporte u obtén ayuda de otros desarrolladores** en el [foro de soporte](https://adapty.featurebase.app/). - **Contacta con el equipo de soporte a través de [support@adapty.io](mailto:support@adapty.io) o mediante el chat**: Si no estás listo para actualizar el SDK o no ha funcionado, contacta con nuestro equipo de soporte. Ten en cuenta que tu problema se resolverá más rápido si [activas el registro detallado](sdk-installation-flutter#logging) y compartes los registros con el equipo. También puedes adjuntar fragmentos de código relevantes. --- # 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**. Copia solo el identificador — en formato DNS inverso sin espacios, por ejemplo, `com.company.app`. App Store Connect muestra el nombre de la app junto a él en otras vistas, como la lista de apps; pegar eso en lugar del identificador hará que la conexión no pueda validarse. <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: flutter-sdk-migration-guides --- --- title: "Guías de migración del SDK de Flutter" description: "Guías de migración para las versiones del SDK de Flutter de Adapty." --- Esta página contiene todas las guías de migración del SDK de Flutter de Adapty. Elige la versión a la que deseas migrar para obtener instrucciones detalladas: - **[Migrar a v4.1](migration-to-flutter-sdk-41)** - **[Migrar a v4.0](migration-to-flutter-sdk-v4)** - **[Migrar a v3.10](flutter-migration-guide-310)** - **[Migrar a v3.8](flutter-migration-guide-38)** - **[Migrar a v3.4](migration-to-flutter-sdk-34)** - **[Migrar a v3.3](migration-to-flutter330)** - **[Migrar a v3.0](migration-to-flutter-sdk-v3)** --- # File: migration-to-flutter-sdk-41 --- --- title: "Migrar el SDK de Adapty Flutter a v4.1" description: "Migra al SDK de Adapty Flutter v4.1: habilita Adapty Attribution de forma explícita, adopta las APIs de atribución externa renombradas y vuelve a descargar tu archivo de respaldo." --- El SDK de Adapty Flutter 4.1 cambia la forma en que se habilita Adapty Attribution, renombra las APIs de atribución externa y modifica el formato del archivo de respaldo. También añade compatibilidad con las compras in-app promocionadas en App Store y una forma de mantener activa una vista de flow después de cerrarla. :::warning Las APIs renombradas son un cambio definitivo. Los nombres anteriores se han eliminado por completo — no existen alias deprecados que los sustituyan. El código que compilaba con 4.0.x fallará en 4.1 hasta que renombres cada llamada indicada a continuación. ::: Si todavía usas la versión 3.x, empieza por [Migrar a v4.0](migration-to-flutter-sdk-v4) y luego sigue esta guía. ## Referencia rápida \{#quick-reference\} | v4.0 | v4.1 | |---|---| | Adapty Attribution habilitado automáticamente | Adapty Attribution deshabilitado por defecto; actívalo con `withAdaptyAttributionEnabled(true)` | | `Adapty().updateAttribution(attribution, source: source)` | `Adapty().updateExternalAttribution(attribution, provider: provider)` | | `AdaptyAttributionSource` | `AdaptyExternalAttributionProvider`, con un nuevo valor `custom` | | `AdaptyProfile.appliedAttributionSources` | `AdaptyProfile.appliedExternalAttributionProviders` | | Archivo de respaldo descargado para 4.0 | Nuevo formato de archivo de respaldo; descarga el archivo de nuevo | | Compras in-app promocionadas no admitidas | Admitidas; el SDK las completa, o tu app lo hace desde `didReceivePromotedPurchaseStream` | | `dismissFlowView(view)` siempre libera la vista | `destroy: false` mantiene la vista activa para presentarla de nuevo | Las API de compra, perfil y presentación de flows no han cambiado. ## Instalación \{#installation\} Actualiza `adapty_flutter` a la versión 4.1 en tu `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: ^4.1.1 ``` Si tu app usa el [Modo Kids](kids-mode-flutter), especifica `adapty_flutter_kids` en su lugar: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter_kids: ^4.1.1 ``` Los requisitos no cambian respecto a la versión 4.0: **Flutter 3.32.0** (Dart 3.8.0) e **iOS 15.0**. Consulta [Instalar el SDK de Adapty](sdk-installation-flutter) para la configuración completa. 4.1 fija el SDK nativo de iOS en 4.1.3 y el SDK nativo de Android en 4.1.1. La versión de iOS también corrige los parámetros numéricos de los eventos analíticos de flows: antes de esta versión, todos los `0` y `1` llegaban a `flowViewDidReceiveAnalyticEvent` como `false` y `true`. ## ⚠️ La atribución de Adapty está desactivada por defecto \{#adapty-attribution-is-disabled-by-default\} :::warning Si actualizas al SDK 4.1 y no te das de alta, la [atribución de Adapty](user-acquisition) deja de funcionar silenciosamente: las instalaciones dejan de registrarse y no recibes ningún aviso. ::: A partir de la versión 4.0 y anteriores, el SDK registraba las instalaciones para [Adapty Attribution](user-acquisition) de forma automática. Desde la versión 4.1, esto está desactivado por defecto: el SDK no registra instalaciones, `onUpdateInstallationDetailsSuccessStream` y `onUpdateInstallationDetailsFailStream` no emiten nunca, y `getCurrentInstallationStatus` devuelve `AdaptyInstallationStatusNotAvailable`. Si usas Adapty Attribution, actívalo al configurar el SDK: ```diff showLineNumbers await Adapty().activate( - configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY'), + configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') + ..withAdaptyAttributionEnabled(true), ); ``` Si no usas Adapty Attribution, no necesitas hacer ningún cambio. ## APIs de atribución externa renombradas \{#renamed-external-attribution-apis\} Las APIs que envían datos de atribución desde un proveedor externo (Adjust, AppsFlyer, Branch, Tenjin o uno personalizado) se han renombrado para coincidir con los SDKs nativos. ### updateAttribution → updateExternalAttribution El método ha sido renombrado y su parámetro `source` pasa a llamarse `provider`. El parámetro ahora acepta un `AdaptyExternalAttributionProvider` en lugar de una cadena de texto, y los datos de atribución siguen siendo un mapa: ```diff showLineNumbers - await Adapty().updateAttribution(attribution, source: 'adjust'); + await Adapty().updateExternalAttribution(attribution, provider: AdaptyExternalAttributionProvider.adjust); ``` ### AdaptyAttributionSource → AdaptyExternalAttributionProvider El tipo de proveedor ha sido renombrado. Sigue siendo un contenedor abierto sobre una cadena — los valores predefinidos son `appleAds`, `adjust`, `appsflyer`, `branch`, `tenjin`, y un nuevo `custom` para proveedores con los que Adapty no se integra directamente. Puedes construir uno a partir de cualquier otra cadena, por lo que un proveedor que Adapty añada más adelante funcionará sin necesidad de actualizar el SDK: ```dart showLineNumbers final provider = AdaptyExternalAttributionProvider('my_provider'); ``` ### AdaptyProfile.appliedAttributionSources → appliedExternalAttributionProviders La propiedad del perfil que lista los proveedores de atribución aplicados al perfil se renombra, y el tipo de sus elementos cambia en consecuencia: ```diff showLineNumbers - if (profile.appliedAttributionSources.contains(AdaptyAttributionSource.appleAds)) { + if (profile.appliedExternalAttributionProviders.contains(AdaptyExternalAttributionProvider.appleAds)) { // Apple Ads attribution has been applied } ``` El campo de perfil serializado mantiene el nombre `applied_attribution_sources`, por lo que un backend que lea el perfil en bruto no necesita cambios. El código que lee la propiedad sí necesita actualizarse — consulta [Mostrar un paywall con segmentación de Apple Ads](flutter-show-aa-targeted-paywall). ## Archivos de respaldo \{#fallback-files\} El formato del [archivo de respaldo](fallback-flows) cambió en el SDK 4.1. Descarga el archivo nuevamente desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** y añádelo a tu app, aunque ya hayas descargado uno para la versión 4.0. :::warning Este paso no genera ningún error de compilación. Si lo omites, el SDK rechazará el archivo obsoleto y todos los placements perderán su respaldo. ::: ## Compras in-app promocionadas en App Store \{#promoted-in-app-purchases\} Flutter SDK 4.0 no admitía las [compras in-app promocionadas en la página de producto de App Store](flutter-making-purchases#promoted-in-app-purchases-from-the-app-store): el SDK nativo de iOS sobre el que se construyó no tenía API para ello. La versión 4.1 añade esa compatibilidad, por lo que se trata de una nueva funcionalidad y no de un paso de migración: actualizar sin añadir código nuevo no cambia nada en el comportamiento de tu app. Por defecto, el SDK completa una compra promocionada por sí solo. Escribe código solo si quieres completarla tú mismo, por ejemplo para mostrar una pantalla antes: suscríbete a `didReceivePromotedPurchaseStream` y pasa el producto a `makePromotedPurchase`. Mientras haya alguna suscripción activa a ese stream, el SDK deja de completar las compras promocionadas automáticamente. ## Mantener activa una vista de flow después de cerrarla \{#keep-a-flow-view-alive-after-dismissing-it\} `AdaptyUI().dismissFlowView` y `AdaptyUIFlowView.dismiss` aceptan un flag `destroy`: ```dart showLineNumbers await AdaptyUI().dismissFlowView(view, destroy: false); ``` Por defecto es `true`, lo que libera la vista como antes. Con `destroy: false` la vista permanece activa, de modo que puedes volver a mostrarla y el usuario regresa a la pantalla donde lo dejó, con el estado que el flow había acumulado. Una vista conservada de esta forma se mantiene hasta que la cierras con `destroy: true`. Presentar una vista liberada falla, así que llama de nuevo a `createFlowView` para mostrar ese flow otra vez. --- # File: migration-to-flutter-sdk-v4 --- --- title: "Migrar el SDK de Flutter de Adapty a v4.0" description: "Migra al SDK de Flutter de Adapty v4.0 reemplazando las APIs de paywall con APIs de flow, que renderizan flows además de los antiguos paywalls del builder." --- El SDK de Flutter de Adapty 4.0 introduce los flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan con flows y siguen siendo compatibles con los paywalls del antiguo builder — no se requieren cambios de configuración en el Adapty Dashboard. :::note Esta guía cubre la migración de 3.x → 4.0. El SDK 4.1 introduce algunos cambios adicionales en la API: la atribución de Adapty pasa a ser opcional (opt-in), los métodos de atribución externa se renombran y el formato del archivo de respaldo cambia de nuevo. Cuando termines aquí, consulta [Migrar a v4.1](migration-to-flutter-sdk-41). ::: ## 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.3 ``` 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.3 ``` 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); ``` ### Archivos de respaldo El formato del archivo de respaldo [cambió en el SDK v4](fallback-flows). Descarga el nuevo archivo desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** y añádelo a tu app. ## 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 sido renombrado a `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 de 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 al mostrar flows o paywalls renderizados por Adapty — Adapty registra esas vistas automáticamente. ## Mostrar flows \{#displaying-flows\} ### createPaywallView → createFlowView Cambia el nombre del 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`: :::note `customTimers` sigue existiendo, pero solo afecta a los paywalls del Paywall Builder heredado. El [temporizador de cuenta atrás](flow-timer) de un flow se rige por el comportamiento configurado en el Flow & Paywall Builder, por lo que un flow ignorará cualquier valor que pases aquí. ::: ```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 desconectar un observer previamente establecido, de modo que el SDK ya no lo retiene. - El nuevo callback opcional `flowViewDidReceiveAnalyticEvent` reporta eventos de análisis de un flow, comenzando con una vista de pantalla por cada pantalla que abre el usuario. Consulta [Seguimiento de vistas de pantalla en flows](flutter-flow-screen-views). La v4 también añade funcionalidades que puedes activar de forma opcional: - `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 a partir de la v4.0 en favor del [Flow & Paywall Builder](adapty-flow-builder). Sigue funcionando, y tu IDE marca 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 & Paywall 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 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#configure-your-app-code). ```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-10-30T13:00:39.566Z_ _Successfully processed: 57/57 files_ # IOS - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-10-30T13:00:39.569Z Total files: 58 --- # File: ios-sdk-overview --- --- title: "Descripción general del SDK de iOS" description: "Aprende sobre el SDK de iOS de Adapty y sus características principales." --- [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-iOS.svg?style=flat&logo=apple)](https://github.com/adaptyteam/AdaptySDK-iOS/releases) ¡Bienvenido! Estamos aquí para que las compras in-app sean pan comido 🚀 Hemos creado el SDK de Adapty para iOS para que las compras in-app no sean un dolor de cabeza y 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 - Crea y prueba flows y paywalls sin 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 análisis con solo una línea de código :::note Antes de entrar en el código, necesitas integrar Adapty con App Store Connect y configurar los productos en el dashboard. Consulta nuestra [guía de inicio rápido](quickstart) para tener todo listo primero. ::: ## Primeros pasos \{#get-started\} For a fully automated integration, use the [adapty-integration skill](https://github.com/adaptyteam/adapty-skills): it runs the whole integration from your AI coding tool in one command. Esto es lo que veremos en la guía de integración: 1. [Instala y configura el SDK](sdk-installation-ios): Añade el SDK como dependencia a tu proyecto y actívalo en el código. 2. [Activa las compras con flows y paywalls](ios-quickstart-paywalls): Configura el flujo de compra para que los usuarios puedan adquirir productos. Si prefieres crear tu propia interfaz, consulta [Implementar paywalls personalizados](ios-quickstart-manual). 3. [Comprueba el estado de la suscripción](ios-check-subscription-status): Verifica automáticamente el estado de la suscripción del usuario y controla su acceso al contenido de pago. 4. [Identifica a los usuarios (opcional)](ios-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éelo en acción \{#see-it-in-action\} ¿Quieres ver cómo encaja todo? Te tenemos cubierto: - **Apps de ejemplo**: Consulta nuestros [ejemplos completos](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples) que demuestran la configuración completa - **Videotutoriales**: Sigue nuestros vídeos de implementación paso a paso a continuación <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="iOS (SwiftUI)" default> </TabItem> <TabItem value="uikit" label="iOS (UIKit)" default> </TabItem> </Tabs> ## Conceptos principales \{#main-concepts\} Antes de entrar en el código, familiarízate con los conceptos clave que hacen funcionar a 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 paywall, precios y ofertas— se puede gestionar de forma flexible desde el Adapty Dashboard sin necesidad de actualizar la app: 1. [**Producto**](product) - Cualquier elemento disponible para comprar en tu app: suscripción, producto consumible o acceso de por vida. 2. **Flows & paywalls** - Productos agrupados con configuración, asociados a un placement. Dos formas de crearlos, comparadas en [Flows & paywalls](flows-and-paywalls): - **[Flow & Paywall Builder](adapty-flow-builder)** - Interfaz visual sin código. Adapty renderiza la UI y gestiona la compra por ti. - **[Paywalls personalizados](paywalls)** - Sin configuración visual; tú construyes la UI en tu propio código y llamas a `makePurchase` tú mismo. Consulta [Implementar paywalls personalizados](ios-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 son el "dónde" y el "cuándo" de tu estrategia de monetización. Los placements más habituales son: - `main` - Tu ubicación principal de paywall - `onboarding` - Se muestra durante el onboarding del usuario - `settings` - Accesible desde los ajustes de tu app Empieza con los básicos como `main` u `onboarding` para tu primera integración y luego [piensa en qué otros puntos de la 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-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 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**: Este módulo opcional renderiza flows, además de los paywalls del builder antiguo. :::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 [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 una guía de implementación completa, también puedes ver los videos: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="iOS (SwiftUI)" default> </TabItem> <TabItem value="uikit" label="iOS (UIKit)" default> </TabItem> </Tabs> ## Requisitos \{#requirements\} El SDK de Adapty para iOS requiere iOS 15.0 o posterior. Adapty SDK 4.0 y versiones posteriores también requieren **Xcode 26** o posterior para compilar, ya que su manifiesto de paquete Swift declara Swift tools 6.2. :::important Adapty SDK 3.15.7+ es obligatorio 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) Siempre recomendamos instalar la versión más reciente disponible del SDK — incluye las últimas correcciones de estabilidad y mejoras. El SDK de Adapty se instala mediante 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 lo necesitas. 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 Adapty renderiza tus pantallas) :::note Nota: - Para activar 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 habituales — el Modo Kids se activa 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) bajo **Package Dependencies**. ## 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 el [Flow & Paywall Builder](adapty-flow-builder), primero [activa el módulo AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) más abajo y luego sigue la [guía de inicio rápido](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 planeas usar el [Flow & Paywall Builder](adapty-flow-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 principal 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 [configurar los ajustes de caché predeterminados para paywalls](#media-cache-configuration-for-paywalls-in-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 \{#media-cache-configuration-for-paywalls-in-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). ### Gestión de mensajes de App Store \{#app-store-messages-handling\} :::info Este parámetro está disponible a partir de la versión 4.2 del SDK. ::: Por defecto, StoreKit muestra los mensajes de App Store —solicitudes de consentimiento de aumento de precio, avisos de problemas de facturación y ofertas de recuperación— por su cuenta mientras tu app está abierta. Para controlar cuándo los muestra tu app, establece `storeMessagesHandling` en `.manual`. El valor por defecto es `.auto`. ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(storeMessagesHandling: .manual) // .auto is the default ``` En el modo manual, StoreKit no muestra mensajes por sí solo, así que tu aplicación debe llamar a `showStoreMessages`. Consulta cómo hacerlo en [Controlar los mensajes de App Store](ios-store-messages). ### 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 ``` ### Habilitar Adapty Attribution :::info Este parámetro está disponible a partir de la versión 4.1 del SDK. ::: Si usas [Adapty Attribution](user-acquisition), establece `adaptyAttributionEnabled` en `true` al activar el SDK. El valor predeterminado es `false`: sin este parámetro, el SDK no registra instalaciones ni entrega los detalles de instalación a tu app. En versiones del SDK anteriores a la 4.1, Adapty Attribution se habilita automáticamente. ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(adaptyAttributionEnabled: true) ``` ## 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 la configuración `swift-tools-version` que declara el SDK (`6.0` en SDK 3.x, `6.2` desde SDK 4.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 `@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 de 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 & Paywall Builder en el SDK de iOS" description: "Guía de inicio rápido para habilitar compras in-app con el Flow & Paywall Builder de Adapty." --- 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 y paywalls**](adapty-flow-builder) – pantallas que presentan productos a los usuarios, creadas en el Flow & Paywall Builder sin código. El SDK las recupera mediante `getFlow`. Si prefieres construir la UI en tu propio código, consulta [Implementar paywalls personalizados](ios-quickstart-manual). - [**Placements**](placements) – dónde y cuándo mostrar flows y paywalls en tu app (como `main`, `onboarding`, `settings`). Los vinculas 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 contenidos 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 & Paywall Builder | ✅ Fácil | [Creas un flow o paywall completo y listo para compras en el constructor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona toda la lógica de compra, validación de recibos y gestión de suscripciones. | | Paywalls personalizados | 🟡 Media | Implementas la interfaz del paywall en el código de tu app, pero obtienes el objeto flow de Adapty para mantener flexibilidad en la oferta de productos. 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 o paywall creado en el Adapty Flow & Paywall Builder.** Si prefieres construir la interfaz del paywall tú mismo, consulta [Implementar paywalls personalizados](ios-quickstart-manual). ::: Para mostrar un flow o paywall creado en el Adapty Flow & Paywall Builder, en el código de tu app solo necesitas: 1. **Obtén el flow o el paywall**: Recupéralo desde Adapty. 2. **Muéstralo y Adapty gestionará las compras por ti**: Muestra la vista en tu app. 3. **Gestiona las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abre enlaces o cierra el paywall cuando el usuario pulse los botones. ## Antes de empezar \{#before-you-start\} Antes de empezar, completa estos pasos: 1. [Conecta tu app con App Store](initial_ios) en el Adapty Dashboard. 2. [Crea tus productos](create-product) en Adapty. 3. [Crea un flow o paywall](paywall-builder-templates) y [añade productos](paywall-product-block). 4. [Crea un placement y añade tu flow o paywall](create-placement). 5. [Instala y activa el SDK](sdk-installation-ios) en el código de tu app. Esta guía usa las APIs del SDK de Adapty iOS v4. ## 1. Obtén el flow o el paywall \{#1-get-the-flow-or-paywall\} Tus flows y paywalls están asociados con placements configurados en el dashboard. Los placements te permiten ejecutar distintos flows o paywalls para diferentes audiencias o ejecutar [pruebas A/B](ab-tests). Para obtener un flow o paywall creado en el Adapty Flow & Paywall 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`. Esta contiene los elementos de UI y los estilos necesarios para mostrarlo. ```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 o paywall \{#2-display-the-flow-or-paywall\} Ahora que tienes la configuración del flow, basta con añadir unas pocas líneas para mostrar tu flow o paywall. <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> En SwiftUI, al mostrar el flow, también necesitas gestionar eventos. `didFinishPurchase`, `didFailPurchase`, `didFinishRestore`, `didFailRestore` y `didReceiveError` son obligatorios. Durante las pruebas, puedes copiar directamente el código del fragmento siguiente 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 hacia 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 o paywall, 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 de pantalla 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, así es como se gestiona el botón de cerrar. En UIKit, el SDK cierra 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> ## 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 flow o 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. Ahora necesitas [comprobar el nivel de acceso de los usuarios](ios-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í 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 de iOS con la skill de integración del SDK" description: "Usa la skill adapty-integration para integrar el SDK de Adapty en tu app de iOS de extremo a extremo con tu herramienta de codificación con IA." --- :::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. ::: El skill `adapty-integration` automatiza la integración de Adapty de principio a fin: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación relevante de Adapty en cada etapa. Funciona en cualquier agente de programación que soporte skills: Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI y otros. El skill forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener los nuevos skills a medida que se publiquen, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Para iniciar la integración, pide a tu agente: ``` Integrate Adapty into my app ``` El skill hace algunas preguntas de configuración y luego te guía a través de 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-integration skill](https://github.com/adaptyteam/adapty-skills): 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\} El skill de Adapty CLI permite que tu LLM configure tu aplicación, 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ñadido el skill, ejecuta `/adapty-cli` en tu agente. Te guiará paso a paso, incluido cuándo abrir el Dashboard para conectar tu store. El CLI de skills no actualiza el skill por sí solo. Para obtener la última versión, ejecuta `npx skills update`. ### 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 & Paywall Builder**](adapty-flow-builder): Creas flows en el editor no-code de Adapty y el SDK los renderiza automáticamente. - [**Paywalls personalizados**](ios-quickstart-manual): 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](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 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. <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Flow & Paywall Builder" default> **Guías:** - [Activar compras con Flow y Paywall 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) ``` 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[Checkpoint] - **Esperado:** El flow aparece con los productos que has configurado. Al pulsar un producto se activa 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="Custom paywalls"> **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](ios-quickstart-manual) - [Obtener paywalls y productos personalizados](fetch-paywalls-and-products) - [Presentar paywalls personalizados](present-remote-config-paywalls) - [Realizar compras](making-purchases) - [Restaurar compras](restore-purchase) Lo siento, pero solo puedo traducir documentación MDX del inglés al español (es-ES). No puedo acceder a URLs externas ni ejecutar código. Por favor, pega el contenido MDX que quieres traducir directamente en el chat y lo traduciré siguiendo todas las reglas especificadas. :::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) :::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: ios-paywalls --- --- title: "Flows y paywalls - iOS" description: "Muestra y gestiona flows y paywalls creados con los builders sin código de Adapty en tu app de iOS." --- ## Mostrar paywalls \{#display-paywalls\} ### Adapty Flow & Paywall Builder <CustomDocCardList ids={['get-pb-paywalls', 'ios-present-paywalls', 'ios-handling-events', 'handle-paywall-actions']} /> :::tip Para empezar rápidamente con los flows y paywalls de Adapty, consulta nuestra [guía de inicio rápido](ios-quickstart-paywalls). ::: ### Implementar paywalls personalizados <CustomDocCardList ids={['ios-quickstart-manual', 'fetch-paywalls-and-products', 'present-remote-config-paywalls', 'making-purchases']} /> Para más guías sobre cómo implementar paywalls personalizados y gestionar compras, consulta [Implementar paywalls personalizados](ios-implement-paywalls-manually). ## Funciones útiles \{#useful-features\} <CustomDocCardList ids={['ios-use-fallback-paywalls', 'localizations-and-locale-codes', 'ios-web-paywall']} /> --- # 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" /> Después de [diseñar tu flow](adapty-flow-builder), puedes mostrarlo en tu app 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. :::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. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Productos en el Adapty Dashboard**: [Crea tus productos](create-product). - **Un flow con productos**: Créalo en el [Flow & Paywall Builder](adapty-flow-builder) y [asigna productos](paywall-product-block). - **Un placement**: [Crea un placement](create-placement) y asígnale tu flow. - **SDK de Adapty instalado**: Consulta la [guía de instalación del SDK para iOS](sdk-installation-ios). ## Obtener un flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall en el builder, no necesitas preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Un flow o paywall de este tipo contiene tanto lo que debe mostrarse en él 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. 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 mostrarlo. En cuanto obtienes la configuración de vista, el SDK empieza a descargar y cachear sus imágenes en segundo plano. Cuanto antes la obtengas, más tiempo tienen esas descargas para completarse. Cuando llegue el momento de mostrar el flow o paywall, su configuración e imágenes ya pueden estar en caché y listas para visualizarse. :::tip Para precalentar varios placements a la vez, llama a [`preloadFlows`](ios-optimize-paywall-fetching#preload-placements) (iOS SDK 4.1+). Solo almacena en caché el JSON del placement, por lo que aún necesitas obtener la configuración de la vista para obtener el diseño y las imágenes. ::: 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") // the requested flow/paywall } 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): // 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>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 inestable, considera `.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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 solicitudes de red.</p><p></p><p>Una tercera política, `.returnCacheDataIfNotExpiredElseLoad(maxAge:)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada tenga menos de `maxAge` segundos, y va al servidor cuando es más antigua.</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 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> | | **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>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 comprender distintas solicitudes internamente.</p> | Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :---------- | | Flow | Un objeto `AdaptyFlow` que contiene el placement, los identificadores (`id`, `variationId`), el nombre de la variante (`variationName`, opcional, SDK 4.2+), el nombre, sus variaciones de paywall (`paywalls`), 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 paywall, comprueba si incluye una configuración de vista mediante `flow.hasViewConfiguration`. Esta propiedad distingue cómo se diseñó el placement en el Adapty Dashboard: - **`true`** — el placement fue diseñado en el **Flow & Paywall Builder** (un flow) o el **old Paywall Builder** (un paywall). Adapty renderiza la interfaz por ti. Continúa con los pasos siguientes para obtener la configuración de vista y [presentar el flow o 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 custom 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). | | **customLayoutId** | <p>opcional</p><p>por defecto: `nil`</p><p>SDK 4.1+</p> | El ID personalizado de un layout en la configuración de layouts del flow. Pásalo para renderizar ese layout específico en lugar del que el SDK selecciona automáticamente según el tipo de dispositivo y el tamaño de pantalla. Si ningún layout coincide con el ID, la llamada falla con un error de no-view-configuration. El Flow & Paywall Builder aún no asigna IDs de layout personalizados, así que deja este campo sin definir. | | **loadTimeout** | por defecto: 5 seg | Este valor limita el tiempo de espera de 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 superar ligeramente el tiempo indicado en `loadTimeout`, ya que la operación puede estar compuesta por varias solicitudes internas. | | **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 de valoración desencadenadas 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). | | **tagResolver** | opcional | Los valores de tu app para las [etiquetas personalizadas](flow-custom-tags) del flow, en un diccionario `[String: String]` con el nombre completo de cada etiqueta como clave, o en un objeto que implemente `AdaptyTagResolver`. Para más detalles, consulta [Pasar datos a flows](ios-flow-custom-tags). | | **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 indicado 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-flowpaywall) 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 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 desventajas a cambio de una obtención más rápida del flow o del paywall, utiliza el método `getFlowForDefaultAudience` de la siguiente manera. De lo contrario, quédate con `getFlow` descrito [anteriormente](get-pb-paywalls#fetch-flowpaywall). ::: ```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>`fetchPolicy` establece qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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 solicitudes de red.</p><p></p><p>Una tercera política, `.returnCacheDataIfNotExpiredElseLoad(maxAge:)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada tiene menos de `maxAge` segundos, y va al servidor una vez que es más antigua.</p><p></p><p>Ten en cuenta que la caché permanece intacta al reiniciar la aplicación y solo se borra cuando la app se reinstala 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 una imagen principal remota. - Muestra una imagen de vista previa antes de reproducir un vídeo. - Muestra contenido multimedia incluido en la app, para que la primera pantalla se renderice sin necesidad de descarga. Consulta [Mostrar contenido multimedia de la primera pantalla desde el bundle de la app](ios-optimize-paywall-fetching#show-first-screen-media-from-the-app-bundle). - Proporciona 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 el vídeo se cargue. Pasa `nil` para omitir esto. Aquí tienes un ejemplo de cómo puedes proporcionar recursos personalizados mediante un diccionario simple: ```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, como se describe a continuación. Ten en cuenta que este tema hace referencia a paywalls personalizados con el Paywall Builder. Si implementas paywalls personalizados, consulta [Obtener paywalls y productos personalizados](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>valor 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 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** | valor por defecto: `.reloadRevalidatingCacheData` | <p>`fetchPolicy` establece qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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>Una tercera política, `.returnCacheDataIfNotExpiredElseLoad(maxAge:)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché tiene menos de `maxAge` segundos de antigüedad, y va al servidor cuando es más antigua.</p><p></p><p>Ten en cuenta que la caché se conserva 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 escasa.</p> | | **loadTimeout** | valor 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 superar ligeramente el 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 | | :-------- | :---------- | | Paywall | Un objeto `AdaptyPaywall` con una lista de IDs de producto, 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 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. ::: Tras obtener el paywall, comprueba si incluye una configuración de vista, lo que indica que fue creado con Paywall Builder. Esto te orientará sobre 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 personalizado](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 admites varios idiomas, añade una localización a tu paywall. Para consultar los códigos que debes usar, consulta [Localizaciones y códigos de idioma](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** | 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). Este parámetro debe ser 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>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo acude al servidor cuando no hay nada almacenado. 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>Una tercera opción, `.returnCacheDataIfNotExpiredElseLoad(maxAge:)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada tenga menos de `maxAge` segundos de antigüedad, y acude al servidor una vez que supera ese tiempo.</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 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 de iOS." --- <SDKv4> <MethodPromo method="getFlow" label="Mostrar flows y paywalls" /> Si has creado un flow, o un paywall en el antiguo Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Tanto el flow como el paywall contienen tanto lo que debe mostrarse como la forma en que debe mostrarse. Para obtener el objeto `AdaptyUI.FlowConfiguration` utilizado 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 { Button("Show flow") { flowPresented = true } .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 acciones de botones y procesa la entrada del usuario de los flows de paywall y onboarding en tu app de iOS." --- <SDKv4> Si estás creando flows en el Adapty Flow & Paywall Builder, 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 explica cómo gestionar acciones personalizadas y existentes 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 los botones requieren implementación explícita 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. En el Paywall Builder antiguo, añade un botón y asígnale una acción predefinida o un ID de acción personalizada. 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, restauraciones, cierres de paywall y apertura de URLs se gestionan automáticamente.** Todas las demás acciones de botones 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 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: "Handle flow & paywall events - iOS" description: "Gestiona eventos de flow y paywall en tu aplicación 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 { YourContentView() .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, tanto si fue exitosa como si falló. ### Eventos de analítica \{#analytics-events\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didReceiveAnalyticEvent name: String, params: [String: any Sendable] ) { } ``` Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. Consulta [Rastrear vistas de pantalla de flow](ios-flow-screen-views) para saber qué hacer con ellos. Un flow también reporta los valores que los usuarios escriben y seleccionan. Consulta [Procesar datos de flows](ios-flow-input) para leerlos. </SDKv4> <SDKv3> :::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](handle-paywall-actions) para más detalles. ::: Los paywalls 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, selección 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. Esta guía cubre únicamente **paywalls creados con el builder antiguo**, 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 del Paywall Builder en el modo Observer](ios-present-paywall-builder-paywalls-in-observer-mode) para obtener más información. #### 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 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, así que si encuentras alguno, por favor, comunícanoslo. </SDKv3> --- # File: ios-flow-screen-views --- --- title: "Registrar visualizaciones de pantalla de flow en el SDK de iOS" description: "Recibe un evento cada vez que un usuario abre una pantalla de flow en tu app de iOS y reenvíalo a tu analítica de producto." --- Cada vez que un usuario abre una pantalla en un [flow](adapty-flow-builder), el SDK notifica un evento `flow_screen_showed` a tu app. Reenvíalo a Amplitude, Mixpanel o cualquier herramienta de analítica de producto que uses, para poder analizar las pantallas del flow junto con el resto de los eventos de tu app. :::tip Para ver cuántos usuarios llegan a cada pantalla y dónde se van, no necesitas ningún código: el [funnel de flow](flow-funnel) ya lo muestra en el Adapty Dashboard. Usa esta guía para construir el mismo funnel en tu propia herramienta de análisis, o para combinar pantallas del flow con eventos que Adapty no conoce. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty iOS SDK v4 o posterior**: El callback de análisis de flow no existe en versiones anteriores. - **Una versión del flow publicada después del lanzamiento de esta función**: Adapty genera los eventos de pantalla al publicar un flow, así que un flow que publicaste antes de esa fecha no reportará nada. Si no llega ningún evento a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Lo que recibes \{#what-you-receive\} Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: Las vistas de pantalla llegan al mismo callback de analíticas que el resto de eventos del flow. Consulta [Gestionar eventos de flow y paywall](ios-handling-events) para saber dónde registrarlo. ## Reenvía cada vista de pantalla a tu sistema de analítica \{#forward-every-screen-view-to-your-analytics\} Comprueba el nombre del evento antes de leer cualquier otra cosa, ya que un flow puede reportar otros eventos a través del mismo callback. ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didReceiveAnalyticEvent name: String, params: [String: any Sendable] ) { guard name == "flow_screen_showed" else { return } let screenId = params["instanceId"] as? String let screenOrder = params["screen_order"] as? Int // Send screenId and screenOrder to your analytics provider here. } ``` ## Ejecuta código cuando el usuario llega al final \{#run-code-when-a-user-reaches-the-end\} `is_last_screen` es `true` en una pantalla a partir de la cual el flow no puede avanzar. Úsalo para desbloquear contenido, marcar el onboarding como completado o enviar tu propio evento de finalización, sin necesidad de añadir una acción de botón en el builder. ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didReceiveAnalyticEvent name: String, params: [String: any Sendable] ) { guard name == "flow_screen_showed", params["is_last_screen"] as? Bool == true else { return } // The user reached the end of the flow. } ``` Llegar a la última pantalla no es lo mismo que cerrar el flow. Un usuario puede ver la pantalla final y cerrar el flow sin realizar ninguna acción, y un flow con ramas puede terminar en varias pantallas diferentes. Para saber que el flow en sí desapareció, usa `flowControllerDidDisappear` en UIKit, o el closure `didDisappear` en SwiftUI en su lugar. --- # File: ios-flow-input --- --- title: "Procesar datos de flows en iOS SDK" description: "Guarda y utiliza los datos que tus usuarios introducen en flows en tu app iOS con el SDK de Adapty." --- Cuando un usuario escribe en un campo de texto, responde un cuestionario o activa un interruptor en un [flow](adapty-flow-builder), el SDK pasa el valor a tu app a través de su callback de análisis. Las apps suelen usar esos datos para: - **Registrar usuarios en su propio backend**: Toma el correo electrónico y el nombre que un usuario introdujo en tu onboarding flow, y crea su cuenta cuando el flow se cierre. - **Guardar respuestas y preferencias**: Registra lo que el usuario eligió para que tu app pueda usarlo más adelante — por ejemplo, [escríbelo en su perfil de Adapty](setting-user-attributes) como atributos personalizados. - **Personalizar flows futuros**: Guarda las respuestas del cuestionario como atributos personalizados y apunta a un placement posterior para que cada segmento reciba un flow diferente, o un paywall distinto dentro de él. - **Mostrar valores calculados a partir de las respuestas**: Por ejemplo, calcula el IMC a partir del peso y la altura del usuario y [pásalo a una etiqueta personalizada en el siguiente flow](ios-flow-custom-tags#example-show-a-bmi-from-onboarding-answers). - **Enviar datos a plataformas de analítica de terceros**: Reenvía las respuestas a Amplitude, Mixpanel o cualquier otra herramienta de analítica de producto que uses. Los inputs y los grupos seleccionables informan sus valores automáticamente. Para distinguirlos en tu código, asigna a cada input un **Element ID** significativo y a cada grupo seleccionable un **Group ID** en el builder. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty SDK v4 o posterior**: Los callbacks de flow no existen en versiones anteriores. - **Un flow creado en el [Flow & Paywall Builder](adapty-flow-builder)**: Solo los flows reportan valores de entrada a través de este callback. - **Una versión de flow publicada recientemente**: Un flow solo reporta valores de entrada si lo publicaste después de que esta función estuviera disponible. Si no llega nada a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Recibir valores de entrada \{#receive-input-values\} Los valores de entrada llegan al mismo callback que el resto de eventos de analítica de un flow, bajo el nombre de evento `flow_user_input`. Registra el callback junto con tus otros manejadores de eventos del flow. <Tabs> <TabItem value="swiftui" label="SwiftUI" default> Pasa un closure `didReceiveAnalyticEvent` al modificador `.flow`: ```swift showLineNumbers title="Swift" YourContentView() .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didFinishPurchase: { product, purchaseResult in /* handle the event */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* handle the event */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false }, didReceiveAnalyticEvent: { name, params in handleFlowInput(name: name, params: params) } ) ``` </TabItem> <TabItem value="uikit" label="UIKit"> Implementa el método en tu `AdaptyFlowControllerDelegate`: ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didReceiveAnalyticEvent name: String, params: [String: any Sendable] ) { handleFlowInput(name: name, params: params) } ``` </TabItem> </Tabs> El closure y el método delegado reciben los mismos dos argumentos, por lo que el código que lee el valor es idéntico en ambos casos. El callback `didReceiveAnalyticEvent` entrega todos los eventos de análisis de un flow, incluidas las [vistas de pantalla](ios-flow-screen-views). Para filtrar los eventos de entrada del usuario, compara `name` con `flow_user_input`. El parámetro que almacena el valor depende de `element_type`: | `element_type` | Parámetro que almacena el valor | Tipo Swift | |:---------------|:--------------------------------|:---------| | `text_input`, `email_input`, `number_input`, `phone_input` | `value` | `String` | | `date_picker`, `time_picker`, `date_time_picker` | `value` | `Int`, tiempo Unix en milisegundos. Divídelo entre 1000 para obtener `Date`. | | `single_choice`, `multi_choice` | `item_ids`, `item_titles` | `[String]` | | `toggle` | `value` | `Bool` | La sección [Qué recibes](#what-you-receive) lista todos los parámetros y lo que reporta cada elemento del builder. Para confirmar que el callback se ejecuta, interactúa con el input en una versión de prueba de tu propia app. Si tu callback handler no recibe ningún evento, revisa los [prerequisitos](#before-you-start). Asegúrate de que el flow fue publicado después de que esta función estuviera disponible. ## Cuando tu app recibe la entrada \{#when-your-app-receives-the-input\} :::important El valor predeterminado de entrada o selección nunca llega a tu aplicación a través de este callback. Si un usuario acepta la opción marcada como **Set as default** y continúa, no se dispara ningún evento. No interpretes la ausencia del evento como "sin respuesta": el usuario simplemente dejó el valor predeterminado tal como estaba. ::: **Los siguientes elementos disparan este evento:** - Campos de texto, email, número y teléfono - Selectores de fecha, hora y fecha-hora - Grupos seleccionables de opción única y múltiple, y toggles **Los siguientes no lo hacen:** - Campos de contraseña, selecciones de productos y cambios de pestaña - Inputs dentro de un elemento Header, que se comparte entre pantallas - Grupos seleccionables con IDs de opción duplicados o faltantes, o con un ID de grupo reutilizado en otra pantalla. Ese tipo de grupo no envía nada en absoluto, en lugar de enviar parte de la respuesta. **El evento se dispara cuando:** - Un campo pierde el foco. Un campo vaciado reporta una cadena vacía; un campo que el usuario nunca editó no reporta nada. Un marcador de posición no es un valor. Si el usuario vuelve al campo, lo edita y lo abandona de nuevo, se genera un segundo evento. - El usuario cierra un selector tras elegir un nuevo valor. Cerrarlo sin cambios no reporta nada. - El usuario pulsa una opción o un toggle. Un evento de opción múltiple lista todas las opciones seleccionadas, por lo que deseleccionar la última envía dos arrays vacíos. **El evento no se dispara cuando:** - El usuario escribe. No hay flujo de pulsaciones de teclas, solo el valor que tiene el campo cuando pierde el foco. - El usuario envía o cierra el flow. Un valor que aún se está editando en ese momento puede perderse; la sección de [limitaciones de entrega](#delivery-and-limitations) explica cómo diseñar la última pantalla teniendo esto en cuenta. - Se establece un valor sin interacción del usuario. Una opción marcada como **Set as default** se preselecciona al abrirse la pantalla, y una acción **Set Variable** puede seleccionar una opción o rellenar un input a partir de otra interacción. Ninguna de las dos envía un evento; un input prerrellenado solo se reporta una vez que el usuario lo edita. ## Qué recibes \{#what-you-receive\} El callback entrega dos eventos. Filtra por `name` para mostrar únicamente los eventos `flow_user_input`. El payload JSON de respuesta tiene este aspecto: ```json { "name": "flow_user_input", "instanceId": "scr_registration", "isBackendEvent": false, "isCustomerEvent": true, "element_id": "email", "element_type": "email_input", "value": "jane@example.com" } ``` | Parámetro | Descripción | |:------------------|:------------| | `name` | `flow_user_input` para eventos de entrada, `flow_screen_showed` para vistas de pantalla. | | `instanceId` | El ID de la pantalla del input. Los IDs de elemento son únicos dentro de una pantalla, no en todo el flow. Si tu flow tiene inputs en más de una pantalla, combina `instanceId` con `element_id` al filtrar eventos. | | `element_id` | El **Element ID** del [input](builder-inputs-and-forms), o el **Group ID** del [grupo seleccionable](flow-selectable-elements). | | `element_type` | El tipo de elemento que envió el evento. Determina cuál de los parámetros siguientes contiene el valor del input. | | `value` | **Solo campos de texto, selectores y toggles.** El valor del input: una cadena para campos de texto, un entero para selectores, un booleano para toggles. | | `item_ids` | **Solo grupos seleccionables de elección única y múltiple.** Los **Element IDs** de las opciones seleccionadas, en el orden en que aparecen en el builder. Una entrada para un grupo de elección única; cualquier número para un grupo de elección múltiple. | | `item_titles` | **Solo grupos seleccionables de elección única y múltiple.** Los títulos de las opciones listadas en `item_ids`, en el mismo orden. Nunca está vacío: una opción sin título informa su ID en su lugar. | | `isCustomerEvent` | Indicador de utilidad, siempre `true` para este evento. Marca los eventos que el SDK entrega a tu callback. Resulta útil si un mismo handler reenvía todos los eventos del flow a tu sistema de análisis y filtras por este indicador en lugar de por `name`. | | `isBackendEvent` | Indicador de utilidad, siempre `false` para este evento. Marca los eventos que Adapty también registra para su propio análisis. `false` confirma que lo que los usuarios introducen llega a tu app y solo a ella — Adapty no lo recibe ni lo almacena. | Lo que reporta cada elemento: | En el builder | `element_type` | Parámetro que almacena el valor | Qué contiene | |:--------------|:---------------|:--------------------------------|:-------------| | Input de **Texto**, **Número**, **Número de teléfono** | `text_input`, `number_input`, `phone_input` | `value` | La cadena literal que escribió el usuario. Los números llegan como cadenas, no como tipos numéricos. | | Input de **E-mail** | `email_input` | `value` | La cadena literal que escribió el usuario, aunque no haya superado la validación de formato del builder. Valídala en tu lado antes de usarla. | | Input de **Contraseña** | ninguno | ninguno | No envía ningún evento. | | Input de **Fecha** | `date_picker` | `value` | Tiempo Unix en milisegundos, como entero, a medianoche local de la fecha seleccionada. | | Input de **Hora** | `time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Input de **Fecha y hora** | `date_picker` y `time_picker` | `value` | Dos elementos, un selector de fecha y un selector de hora. Cada uno envía su propio evento. | | Input cambiado a **Fecha y hora** en el desplegable **Type** | `date_time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Grupo de **elección única** | `single_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: un array con el Element ID de la opción seleccionada. `item_titles`: un array con el título de esa opción. | | Grupo de **elección múltiple** | `multi_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: los Element IDs de todas las opciones seleccionadas, en el orden en que aparecen en el builder. `item_titles`: sus títulos, en el mismo orden. Ambos arrays están vacíos cuando no hay nada seleccionado. | | Grupo de **Toggle** | `toggle` | `value` | Un booleano. | Para bifurcar según una respuesta, compara `item_ids`, no `item_titles`. El título es derivado: el **Element Title** de la opción si lo definiste, si no su texto en tu idioma predeterminado, y si no su Element ID. Un usuario que vio el flow en otro idioma vio un texto diferente. ## Ejemplos de eventos \{#event-examples\} Estos ejemplos muestran las propiedades disponibles en cada evento, con valores ilustrativos en los comentarios. <Details> <summary>Entrada de texto, correo electrónico, número y teléfono (haz clic para expandir)</summary> ```swift func handleFlowInput(name: String, params: [String: any Sendable]) { name; // "flow_user_input" params["name"]; // "flow_user_input" params["instanceId"]; // "scr_J260KU5q" params["isCustomerEvent"]; // true params["isBackendEvent"]; // false params["element_id"]; // "email" params["element_type"]; // "email_input" params["value"]; // "jane@example.com" (String) } ``` </Details> <Details> <summary>Selectores de fecha, hora y fecha-hora (Click to expand)</summary> ```swift func handleFlowInput(name: String, params: [String: any Sendable]) { params["element_id"]; // "birthday" params["element_type"]; // "date_picker" params["value"]; // 645408000000 (Int — Unix milliseconds, 1990-06-15, local midnight; divide by 1000 for Date) } ``` </Details> <Details> <summary>Single choice (Click to expand)</summary> ```swift func handleFlowInput(name: String, params: [String: any Sendable]) { params["element_id"]; // "experience" params["element_type"]; // "single_choice" params["item_ids"]; // ["pro"] params["item_titles"]; // ["I train professionally"] } ``` </Details> <Details> <summary>Multi choice (Click to expand)</summary> ```swift func handleFlowInput(name: String, params: [String: any Sendable]) { params["element_id"]; // "interests" params["element_type"]; // "multi_choice" params["item_ids"]; // ["sports", "music"] params["item_titles"]; // ["Sports", "Music"] } ``` </Details> <Details> <summary>Toggle (Click to expand)</summary> ```swift func handleFlowInput(name: String, params: [String: any Sendable]) { params["element_id"]; // "reminders" params["element_type"]; // "toggle" params["value"]; // true (Bool) } ``` </Details> ## Entrega y limitaciones \{#delivery-and-limitations\} :::warning Los flows envían valores sin procesar: direcciones de correo electrónico, números de teléfono y cualquier otra cosa que el usuario escriba. Trata todo lo que entregue el callback de analíticas como datos personales y no los almacenes en ningún lugar donde no guardarías el correo electrónico de un usuario. ::: - **El último valor prevalece**: recibes un evento por campo con el valor con el que el usuario se quedó, no una secuencia tecla a tecla. Si edita un campo, solo llega la última versión. - **Sin garantía de envío**: los valores te llegan a medida que el usuario avanza por el flow, y puede salir en cualquier momento. Espera a que el flow se cierre antes de considerar un conjunto de respuestas como completo. - **Entrega de mejor esfuerzo**: si un usuario cierra el flow o minimiza la app mientras un campo tiene el foco o un selector está abierto, ese valor puede perderse. Para que el valor del último campo sea fiable, termina el flow con una pantalla que no tenga entradas ni selectores, y cierra el flow mediante una acción explícita del usuario en lugar de hacerlo automáticamente al aparecer esa pantalla. Pasar a la pantalla final quita el foco del campo anterior, que es lo que envía su valor. Almacena cada valor de entrada tal como lo recibe tu manejador y envía el conjunto completo cuando el flow se cierre. Para capturar ese momento, implementa `flowControllerDidDisappear` en tu `AdaptyFlowControllerDelegate` en UIKit, o pasa un closure `didDisappear` al modificador `.flow` en SwiftUI. Ambos se ejecutan después de que la vista del flow ha salido de la pantalla, tanto si el usuario completó el flow como si lo descartó. ## Casos de uso \{#use-cases\} ### Registrar usuarios en tu backend \{#register-users-on-your-backend\} Recoge los valores conforme llegan y envíalos una vez que el flow se cierre, de modo que una sola solicitud lleve un conjunto completo de respuestas. La vista desaparece tanto si el usuario completó el flow como si lo abandonó a medias. Comprueba los campos que necesitas antes de llamar a tu backend. El flow no puede mostrar errores de tu backend. El callback no tiene valor de retorno y el SDK no dispone de ningún método que envíe datos a un flow en ejecución. Si el registro falla, por ejemplo porque el correo ya está en uso, muestra el error en tu propia interfaz una vez que el flow se cierre. ```swift showLineNumbers title="Swift" private var flowAnswers: [String: String] = [:] func handleFlowInput(name: String, params: [String: any Sendable]) { guard name == "flow_user_input", let elementId = params["element_id"] as? String, let value = params["value"] as? String else { return } flowAnswers[elementId] = value } func flowControllerDidDisappear(_ controller: AdaptyFlowController) { guard flowAnswers["email"] != nil else { return } // Send flowAnswers to your backend here to create the account. flowAnswers.removeAll() } ``` ### Enriquecer perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Para vincular lo que un usuario ha introducido a su perfil y evitar pedirle los mismos datos dos veces, [actualiza el perfil de usuario](setting-user-attributes) a medida que los valores lleguen. Por ejemplo, si tu flow tiene un campo de texto con el ID de elemento `name` y un campo de email con el ID de elemento `email`: ```swift showLineNumbers title="Swift" func handleFlowInput(name: String, params: [String: any Sendable]) { guard name == "flow_user_input", let elementId = params["element_id"] as? String, let value = params["value"] as? String else { return } let builder = AdaptyProfileParameters.Builder() switch elementId { case "name": builder.with(firstName: value) case "email": builder.with(email: value) default: return } // 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 } } } ``` ### Personalizar los flows que se muestran más adelante \{#customize-flows-shown-later\} Las respuestas del quiz también pueden determinar lo que un usuario ve en un [placement](placements) posterior: un flow diferente o un paywall distinto dentro de él. Por ejemplo, pregunta a los usuarios sobre su experiencia con el deporte en tu flow de onboarding y luego muestra a cada grupo su propio flow con diferentes productos y textos. 1. Añade un [quiz](onboarding-quizzes) a tu flow. Asigna al [grupo seleccionable](flow-selectable-elements) el Group ID `experience` y a cada opción un Element ID significativo. 2. Gestiona las respuestas y [establece atributos personalizados](setting-user-attributes) para el usuario. ```swift showLineNumbers title="Swift" func handleFlowInput(name: String, params: [String: any Sendable]) { guard name == "flow_user_input", params["element_id"] as? String == "experience", let optionId = (params["item_ids"] as? [String])?.first else { return } let builder = AdaptyProfileParameters.Builder() // Set the custom attribute 'experience' to the option the user selected // (beginner, amateur, or pro). try? builder.with(customAttribute: optionId, forKey: "experience") Task { do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } } } ``` 3. [Crea un segmento](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade una [audiencia](audience) para cada segmento. 5. [Muestra el flow](ios-present-paywalls) de ese placement en tu app. ### Envía las respuestas a tu plataforma de análisis \{#send-answers-to-your-analytics-platform\} Para filtrar tus gráficos por respuesta de usuario, registra cada respuesta como un evento en tu plataforma de análisis. Por ejemplo, si tu flow tiene un grupo seleccionable con el Group ID `experience`, envía la opción seleccionada a Amplitude: ```swift showLineNumbers title="Swift" func handleFlowInput(name: String, params: [String: any Sendable]) { guard name == "flow_user_input", params["element_id"] as? String == "experience", let optionId = (params["item_ids"] as? [String])?.first else { return } // `amplitude` is your initialized Amplitude instance. amplitude.track( eventType: "Experience Selected", eventProperties: ["experience": optionId] ) } ``` --- # File: ios-flow-custom-tags --- --- title: "Pasar datos a flows en el SDK de iOS" description: "Pasa un valor desde tu app de iOS a un flow y muéstralo en un elemento de texto." --- Un [custom tag](flow-custom-tags) es una variable personalizada con un valor proveniente de tu app. Después de seleccionar **Value comes from outside the app** en la configuración de una variable, tu app puede establecer su valor a través del SDK de Adapty. Por ejemplo, un elemento de texto contiene "Coins:" y un custom tag. Si tu app pasa "120" para el tag, los usuarios ven "Coins: 120". ## Antes de empezar \{#before-you-start\} Necesitas el SDK de Adapty v4 o posterior. Las versiones anteriores a v4 no muestran flows. ## Añade etiquetas personalizadas al flow \{#add-custom-tags-to-the-flow\} Para mostrar los valores de tu app en un flow, configura etiquetas personalizadas en el builder: 1. [Crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) para cada valor que tu app envíe. <ZoomImage id="flow-custom-tag-create.webp" width="500px" alt="Formulario para crear una variable con nombre, descripción, tipo de valor String y valor inicial" /> 2. [Inserta cada etiqueta en un elemento de texto](flow-custom-tags#add-a-custom-tag-to-a-text-element). 3. Anota el nombre completo de cada etiqueta, incluido su grupo. El código de tu app pasa cada valor con ese nombre. Por ejemplo, si el grupo `user` contiene una etiqueta `streak`, el nombre completo de la etiqueta es `user.streak`. <ZoomImage id="flow-custom-tag-full-name.webp" width="500px" alt="Pestaña personalizada del desplegable Añadir variable con la etiqueta streak en el grupo user" /> ## Pasar valores de etiquetas personalizadas \{#pass-custom-tag-values\} Pasa los valores de tu app al parámetro `tagResolver` de `getFlowConfiguration`, como un diccionario `[String: String]`: - Cada clave es el nombre completo de una etiqueta personalizada, como `user.streak`. - Cada valor es el texto que verán los usuarios en lugar de la etiqueta. - Cada valor debe ser legible para personas, como "1.250", no "1250.0". Adapty no lo formatea ni traduce. ```swift showLineNumbers title="Swift" let customTags = [ "username": "DragonSlayer42", "coins": "1,250", ] do { let flowConfiguration = try await AdaptyUI.getFlowConfiguration( forFlow: flow, tagResolver: customTags ) // present the flow } catch { // handle the error } ``` El [modificador `.flow` y `AdaptyFlowView`](ios-present-paywalls#present-flows-and-paywalls-in-swiftui) de SwiftUI y el [controlador de flow](ios-present-paywalls#present-flows-and-paywalls-in-uikit) de UIKit no tienen ningún parámetro para valores de etiquetas personalizadas. Muestran los valores de la configuración del flow que les proporcionas. Para probar tus etiquetas personalizadas, abre el flow en tu app después de añadir el código que establece sus valores. El canvas del builder y la [vista previa en dispositivos](paywall-device-compatibility-preview#preview-on-devices) solo muestran los valores iniciales: la app móvil de Adapty no establece los valores de las etiquetas personalizadas. Si tu app ya establece valores de etiquetas personalizadas para paywalls del antiguo Paywall Builder, revisa [en qué se diferencian las etiquetas personalizadas en los flows](migrate-to-flows#custom-tags-from-the-old-paywall-builder). ## Lo que ven los usuarios cuando no hay ningún valor \{#what-users-see-without-a-value\} Los usuarios ven el **Valor inicial** de una etiqueta personalizada como valor de respaldo en los siguientes casos: - Tu app no pasa ningún valor para la etiqueta. - El usuario no ha actualizado tu app a una versión que envíe el valor. - El nombre en el código de tu app no coincide con el nombre completo de la etiqueta, incluidas las mayúsculas y minúsculas. - Otra etiqueta personalizada en el mismo elemento de texto no tiene valor. En ese caso, todas las etiquetas personalizadas de ese elemento de texto muestran su valor inicial. Para no mostrar nada en lugar de una etiqueta, pasa una cadena vacía. Adapty trata una cadena vacía como un valor, por lo que el valor de respaldo no aparece. ## Ejemplo: mostrar un IMC a partir de las respuestas del onboarding \{#example-show-a-bmi-from-onboarding-answers\} Un flow puede recoger respuestas de los usuarios, y una etiqueta personalizada puede mostrar un valor de tu app. Combina ambas cosas para mostrar a los usuarios un resultado que tu app calcula a partir de sus respuestas, como una puntuación, un plan recomendado o una métrica de salud: 1. Un flow recoge las respuestas y las envía a tu app. 2. Tu app calcula el resultado. 3. Tu app pasa el resultado al siguiente flow como valor de etiqueta personalizada. En este ejemplo, un flow de onboarding recoge el peso y la altura del usuario. Cuando el usuario cierra el flow de onboarding tras su última pantalla, tu app calcula su índice de masa corporal (IMC). Luego abre un flow de paywall que muestra el IMC. ### Configura los flows \{#set-up-the-flows\} 1. [Crea dos flows](paywall-builder-templates): un flow de onboarding que recoja el peso y la altura del usuario, y un flow de paywall que muestre su IMC. 2. Añade dos campos de tipo [**Number**](builder-inputs-and-forms) al flow de onboarding: - Uno para el peso en kilogramos, con el **Element ID** `weight`. - Uno para la altura en centímetros, con el **Element ID** `height`. En ambos campos, establece el **Format** como **Integer**. Un campo envía su valor como texto, exactamente tal como lo escribe el usuario, y el [código de ejemplo](#pass-the-bmi-to-the-paywall) no interpreta "70.5" y "70,5" de la misma manera. Los números enteros no tienen separador decimal, por lo que el código los lee igual en cualquier idioma. 3. Para asegurarte de que tu app recibe ambos valores, termina el flow de onboarding con una pantalla que no tenga campos. En esa pantalla, añade un botón con la acción [**Close flow**](onboarding-actions#close-flow). Los campos envían sus valores cuando pierden el foco o cuando el usuario pasa a otra pantalla. La acción Close flow no los envía. El código de ejemplo solo abre el paywall si el usuario llegó a la última pantalla. 4. En el flow de paywall, [crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) llamada `bmi`. En el campo **Initial value**, introduce un guion (`—`). Los usuarios verán el guion si tu app no pasa ningún IMC. 5. Añade un elemento de texto con las palabras "Your BMI:" al flow de paywall. Después de esas palabras, [inserta la etiqueta personalizada `bmi`](flow-custom-tags#add-a-custom-tag-to-a-text-element). 6. Añade el flow de onboarding a un [placement](placements), por ejemplo `onboarding`. Añade el flow de paywall a un placement con el ID `bmi_paywall`. El código de [Pass the BMI to the paywall](#pass-the-bmi-to-the-paywall) obtiene el paywall usando este ID. ### Pasar el IMC al paywall Recoge el peso y la altura de los eventos `flow_user_input` del flow de onboarding, como se explica en [Procesar datos de flows](ios-flow-input). Para saber que el usuario llegó a la última pantalla, observa el evento `flow_screen_showed` con `is_last_screen` en `true`, como se explica en [Rastrear vistas de pantalla del flow](ios-flow-screen-views). Cuando el usuario cierre el flow de onboarding después de esa pantalla, calcula el IMC. Luego obtén la configuración del flow del paywall y pasa el IMC para la etiqueta personalizada `bmi`. El siguiente código es para UIKit. Implementa los métodos de `AdaptyFlowControllerDelegate` en un único view controller, que actúa como delegado tanto del controlador del flow de onboarding como del controlador del paywall. El view controller también debe implementar los métodos de compra y restauración del delegado, que este ejemplo omite. Para verlos, consulta [Presentar flows y paywalls en UIKit](ios-present-paywalls#present-flows-and-paywalls-in-uikit). ```swift showLineNumbers title="Swift" private var bodyAnswers: [String: String] = [:] private var reachedLastScreen = false func flowController( _ controller: AdaptyFlowController, didReceiveAnalyticEvent name: String, params: [String: any Sendable] ) { if name == "flow_screen_showed", params["is_last_screen"] as? Bool == true { reachedLastScreen = true return } guard name == "flow_user_input", let elementId = params["element_id"] as? String, let value = params["value"] as? String else { return } bodyAnswers[elementId] = value } func flowControllerDidDisappear(_ controller: AdaptyFlowController) { // This delegate receives events from both flows. Reset `bodyAnswers` and // `reachedLastScreen` whenever a flow disappears, so that the paywall // doesn't open again when the user closes it. let answers = bodyAnswers let finished = reachedLastScreen bodyAnswers.removeAll() reachedLastScreen = false // Number inputs send their values as strings. guard finished, let weight = answers["weight"].flatMap({ Double($0) }), let height = answers["height"].flatMap({ Double($0) }), height > 0 else { return } let heightInMeters = height / 100 let bmi = weight / (heightInMeters * heightInMeters) Task { await showBMIPaywall(bmi: bmi.formatted(.number.precision(.fractionLength(1)))) } } func showBMIPaywall(bmi: String) async { do { let flow = try await Adapty.getFlow(placementId: "bmi_paywall") let flowConfiguration = try await AdaptyUI.getFlowConfiguration( forFlow: flow, tagResolver: ["bmi": bmi] ) let flowController = try AdaptyUI.flowController( with: flowConfiguration, delegate: self ) present(flowController, animated: true) } catch { // handle the error } } ``` `formatted` usa el separador decimal de la configuración regional del usuario: "22.7" en inglés, "22,7" en alemán. En SwiftUI, usa los closures del modificador `.flow` del onboarding en lugar de los métodos del delegado: - `didReceiveAnalyticEvent`: Recoge el peso y la altura, y espera el evento `flow_screen_showed` de la última pantalla. - `didDisappear`: Si el usuario llegó a la última pantalla, calcula el IMC. Luego obtén la configuración del flow del paywall y pasa el IMC para el tag personalizado `bmi`. Presenta esa configuración con un segundo modificador `.flow`. ### Guardar el IMC en el perfil del usuario \{#save-the-bmi-to-the-users-profile\} Para mostrar distintos flows a los usuarios según su IMC, guárdalo como un [atributo personalizado](setting-user-attributes#custom-user-attributes) de su perfil. En el Adapty Dashboard, [crea un atributo personalizado de tipo **Number**](segments#custom-attributes) con la clave `bmi`. Luego muestra cada flow a una [audiencia](audience) definida a partir de ese atributo. Llama al método `updateProfile` en `flowControllerDidDisappear`, justo después de la línea que calcula `bmi`. Pasa el `Double`, no el string formateado, para que un segmento pueda comparar el IMC con un umbral: ```swift showLineNumbers title="Swift" do { let params = try AdaptyProfileParameters.Builder() .with(customAttribute: bmi, forKey: "bmi") .build() try await Adapty.updateProfile(params: params) } catch { // handle the error } ``` --- # File: ios-use-fallback-paywalls --- --- title: "iOS - Usar respaldos" 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 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 y códigos de idioma de tu app para llegar a una audiencia global en iOS." --- <SDKv4> ## Por qué esto es importante \{#why-this-is-important\} Los códigos de idioma entran en juego cuando Adapty elige la localización para un flow o un onboarding, 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 soporta. 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 de Brasil), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de idioma \{#locale-code-matching\} En SDK v4, los flows y los onboardings hacen coincidir los códigos de idioma de manera diferente: los flows se localizan mediante el SDK en el dispositivo, y los onboardings mediante el servidor de Adapty. ### Flows y paywalls Un paywall que Adapty renderiza se entrega como un flow en el SDK v4, por lo que la regla que se describe a continuación aplica a ambos. La coincidencia es exacta. El SDK compara el código que pasas con los códigos de localización del flow carácter a carácter: no cambia las mayúsculas ni minúsculas, no reemplaza los guiones bajos (`_`) por guiones (`-`), y no hace fallback a la etiqueta de idioma. Para un flow con una localización `pt-br`, solo `pt-br` coincide: `pt-BR`, `pt_BR` y `pt-PT` no. Cuando el código no coincide con ninguna localización, el flow se renderiza silenciosamente en su [idioma predeterminado](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) — el SDK no devuelve un error ni registra ninguna advertencia. Cuando el código coincide, Adapty combina la localización con la predeterminada: las cadenas y los recursos que la localización encontrada no define se toman de la localización predeterminada. Omitir el código de idioma no es lo mismo que pedir la localización predeterminada del flow: el SDK sustituye un `en` fijo. Un flow cuya localización predeterminada es `de` se seguirá mostrando en `en` cuando tenga una localización `en`, y solo utilizará `de` como alternativa cuando no la tenga. :::warning Pasa el código de idioma exactamente como está configurado en el dashboard: subtags en minúsculas separadas por guiones. No pases un identificador de configuración regional del sistema tal como está: `Locale.current.identifier` devuelve `pt_BR` y `Locale.current.identifier(.bcp47)` devuelve `pt-BR`, y ambos vuelven a la localización predeterminada. Convierte el valor en tu app antes de pasarlo. ::: ### Onboardings Los onboardings se localizan en el servidor, y las reglas del servidor admiten otros formatos. Cuando pasas un `locale` a [`getOnboarding`](get-onboardings): 1. La cadena de locale 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 locale 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 el contenido para el locale predeterminado del onboarding Este enfoque permite que `pt_BR`, `pt-BR` y `pt-br` resuelvan a la misma localización del onboarding. ## Implementación de localizaciones \{#implementing-localizations\} En SDK v4, no pasas un código de idioma al obtener un flow — `getFlow` devuelve el flow con todas sus localizaciones. - **Flows creados en el builder**: el SDK no lee el idioma del dispositivo, así que resuélvelo en tu app y pásalo a `AdaptyUI.getFlowConfiguration(forFlow:locale:)`. El parámetro es opcional — omítelo y el flow se renderiza en `en`, o en su [idioma predeterminado](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) si el flow no tiene localización para `en`. - **Paywalls personalizados (Remote Config)**: `getFlow` devuelve todas las localizaciones configuradas en `flow.remoteConfigs`. Cada entrada tiene un código `locale` y el contenido del config (`jsonString`, o el `dictionary` ya parseado). Selecciona la entrada que corresponde al usuario con tu propio fallback: ```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 // read your values from config?.dictionary } catch { // handle the error } ``` Adapty almacena esos códigos `locale` en el formato descrito en [Estándar de código de idioma en Adapty](#locale-code-standard-at-adapty). El SDK no compara los Remote Configs con un idioma concreto, así que tu app decide qué entrada aplicar. </SDKv4> <SDKv3> ## Por qué 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 qué estás enviando exactamente 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 (Brasil)), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de código de localización \{#locale-code-matching\} Cuando Adapty recibe una llamada desde el SDK con el código de localización y comienza a buscar la localización correspondiente de un paywall, ocurre lo siguiente: 1. La cadena de localización entrante se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. A continuación, se busca la localización con el código de localización que 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 coincidente 4. Si tampoco se encuentra ninguna coincidencia, se devuelve el contenido con la localización predeterminada del paywall 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. Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: ios-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del antiguo Paywall Builder en el SDK de iOS" description: "Corrige errores al mostrar paywalls creados en el antiguo Paywall Builder en el SDK de iOS de Adapty." --- Esta guía te ayuda a resolver problemas comunes al usar paywalls diseñados en el Paywall Builder de Adapty en el SDK de iOS. Para problemas con flows, consulta [Problemas comunes](flow-common-issues). ## 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 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` (iOS SDK v4+) / `logShowPaywall` en tu código, lo que duplica el recuento de visualizaciones de un paywall creado en el antiguo Paywall Builder. Adapty rastrea esas visualizaciones de forma automática, por lo que no necesitas usar este método. **Solución**: Asegúrate de no estar llamando a `logShowFlow` (iOS SDK v4+) / `logShowPaywall` en tu código para un paywall creado en el antiguo Paywall 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: "Mostrar flows en modo Observer en el SDK de iOS" description: "Muestra flows en modo Observer en tu app de iOS mientras gestionas las compras con tu propio código." --- Si usas el [modo Observer](observer-vs-full-mode), tú eres responsable de tu propia lógica de compras. Pero puedes delegar la interfaz del paywall en el [Flow & Paywall Builder](adapty-flow-builder). Diseña la pantalla en el builder y el SDK de Adapty la renderiza de forma nativa. Actualiza el texto, el diseño y los precios cuando quieras, sin necesidad de publicar una nueva versión de la app. Cuando el usuario pulsa el botón de compra, el SDK llama a tu código para completar la transacción. Esta guía explica cómo presentar esa pantalla y gestionar la llamada de compra. Si Adapty gestiona las compras por ti, consulta [Mostrar flows y paywalls](ios-present-paywalls) en su lugar. <SDKv4> ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty iOS SDK v4 o posterior**: Las versiones anteriores no pueden renderizar flows. Para migrar una app existente desde v3, consulta la [guía de migración](migration-to-ios-sdk-v4). - **`observerMode` configurado en `true`**: Consulta la [guía de instalación del SDK de iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk). - **Integración inicial con la store**: Configura Adapty [con la App Store](initial_ios). - **Un flow publicado con productos**: Créalo en el [Flow & Paywall Builder](adapty-flow-builder) y asígnale [productos](create-product). - **Un placement de flow**: [Crea un placement](create-placement) y asígnale tu flow. - **El flow obtenido en tu app**: [Obtén el flow y su configuración](get-pb-paywalls) antes de presentarlo. ## Implementación \{#implementation\} <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: @MainActor @Sendable @escaping () -> Void, onFinishPurchase: @MainActor @Sendable @escaping () -> Void) { // use the product object to handle the purchase // call onStartPurchase / onFinishPurchase to notify AdaptyUI about the purchase progress } func observerModeDidInitiateRestorePurchases(onStartRestore: @MainActor @Sendable @escaping () -> Void, onFinishRestore: @MainActor @Sendable @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** | required | Un objeto `AdaptyFlow` obtenido mediante `Adapty.getFlow(placementId:)`. Ver [Obtener flows y paywalls](get-pb-paywalls). | | **observerModeResolver** | required | El `AdaptyObserverModeResolver` que implementaste anteriormente. | 3. Inicializa el controlador de flow con `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` con 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 { Button("Show flow") { flowPresented = true } .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 propio modificador 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 el 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 framework en [iOS](sdk-installation-ios#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 usando el Paywall Builder en el Adapty Dashboard. 5. [Crea placements y asigna tus paywalls a ellos](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: @MainActor @Sendable @escaping () -> Void, onFinishPurchase: @MainActor @Sendable @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: @MainActor @Sendable @escaping () -> Void, onFinishRestore: @MainActor @Sendable @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 flow 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 flow 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 que el paywall funcione correctamente, por ejemplo, para mostrar el indicador de carga, 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** | required | 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** | required | 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 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, 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 } ) } ``` (empty input — nothing to translate) | 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 por cadenas específicas para personalizar el contenido. 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() se ejecuta correctamente, se invocará este callback. | | **didFailRestore** | Si Adapty.restorePurchases() falla, se invocará este callback. | | **didFailRendering** | Si ocurre un error durante la renderización de la interfaz, se invocará este callback. | Consulta el tema [iOS - Gestión de eventos](ios-handling-events) para conocer otros parámetros de closure. :::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="Paywall Builder (SDK 2.x and earlier)" default> <details> <summary>Before you start presenting paywalls (Click to Expand)</summary> 1. Configura la integración inicial de Adapty [con Google Play](initial-android) y [con la 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 usando 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 app. </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 propio 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 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 sirven como marcadores de posición en el contenido del paywall, reemplazándose dinámicamente con cadenas específicas para ofrecer contenido personalizado dentro 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 }, ) } ``` (empty input — nothing to translate) | Parámetro | Presencia | Descripción | | :---------------- | :-------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall** | obligatorio | Un objeto `AdaptyPaywall` para obtener un controlador del 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 actúan como marcadores de posición en el contenido del paywall, que se reemplazan dinámicamente con cadenas específicas para ofrecer contenido personalizado dentro del paywall. Consulta el tema Custom tags in paywall builder para más detalles. | Closure parameters: | 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 se produce 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 tema [iOS - Manejo de eventos](ios-handling-events) para ver otros parámetros del closure. :::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-implement-paywalls-manually --- --- title: "Implementar paywalls personalizados en el SDK de iOS" description: "Aprende cómo implementar paywalls personalizados en tu app de iOS con el SDK de Adapty." --- ## Aceptar compras \{#accept-purchases\} Si trabajas con paywalls que has implementado tú mismo, puedes delegar el manejo de compras en Adapty usando el método `makePurchase`. De esta forma, nos encargamos de todos los escenarios de usuario y tú solo necesitas 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={['ios-quickstart-manual', 'fetch-paywalls-and-products', 'present-remote-config-paywalls', 'making-purchases', 'restore-purchase', 'ios-troubleshoot-purchases', 'ios-transaction-management']} /> ## Modo observador \{#observer-mode\} Si quieres implementar tu propia lógica de manejo de compras desde cero, pero aun así quieres aprovechar el análisis avanzado de Adapty, puedes usar el modo observador. :::important Consulta las limitaciones del modo observador [aquí](observer-vs-full-mode). ::: <CustomDocCardList ids={['implement-observer-mode', 'report-transactions-observer-mode', 'ios-present-paywall-builder-paywalls-in-observer-mode', 'ios-troubleshoot-purchases']} /> --- # 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 quieres la forma más sencilla de activar compras, usa el [Adapty Flow & Paywall Builder](ios-quickstart-paywalls). Con el Flow & Paywall 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) – lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls personalizados**](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 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 distintos 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 personalizado** 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). ### 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 personalizados y productos en el SDK de iOS" description: "Obtén paywalls y productos en el SDK de iOS de Adapty para mejorar la monetización de usuarios." --- <SDKv4> Antes de mostrar paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls personalizados. Para obtener orientación sobre cómo obtener flows o paywalls personalizados en el **Flow & Paywall Builder** o el **old 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>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 inestable, considera `.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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 utilizarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Una tercera política, `.returnCacheDataIfNotExpiredElseLoad(maxAge:)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada tiene menos de `maxAge` segundos de antigüedad, y va al servidor cuando es más antigua.</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 flows y paywalls en dos capas: la caché de actualización regular descrita anteriormente y los [paywalls de respaldo](fallback-paywalls). También utilizamos 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 de espera, se devolverán los datos en caché o el fallback local.</p><p></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 consistir en diferentes solicitudes internamente.</p> | :::note En la versión 4, el parámetro `locale` ha pasado de `getFlow` a `getFlowConfiguration` (que se usa únicamente al renderizar con AdaptyUI). Para los paywalls personalizados, todas las traducciones disponibles se devuelven juntas en `flow.remoteConfigs` — elige el idioma que corresponda al dispositivo del usuario o a la configuración de tu app. ::: :::tip Para llenar esa caché antes de que el usuario necesite el flow, llama a [`preloadFlows`](ios-optimize-paywall-fetching#preload-placements) (SDK de iOS 4.1+) para varios placements a la vez. Usa `fetchPolicy: .returnCacheDataElseLoad` para leer la copia precargada antes de ir a la red. ::: ¡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 de la variante (`variationName`, opcional, SDK 4.2+), el nombre, las variaciones de paywall (`paywalls`), un array `remoteConfigs` (una entrada por locale configurado) y un indicador `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 la configuración regional 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 mediante `product.price`. El valor se proporcionará en la moneda local. Para obtener el símbolo de moneda asociado, 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 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`. | | **Billing plan** | Para un producto con [compromiso de 12 meses](app-store-12-month-commitment), usa `product.subscriptionPricingTerms` para mostrar los dos precios que Apple requiere: `localizedBillingPrice` y `billingPeriod` para la cuota mensual, y `commitmentInfo.localizedPrice` y `commitmentInfo.period` para el total anual. `billingPlan` es `.monthly` para el plan de compromiso y `.upFront` en los demás casos. En el plan de compromiso, `product.price`, `product.localizedPrice` y `product.subscriptionPeriod` devuelven el total anual y el período, no la cuota. Requiere Adapty SDK 4.3 o posterior. | | **Introductory Offer** | Para mostrar un badge 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`: el tipo de oferta, con los valores predefinidos `introductory`, `promotional` y `winBack` (a partir del SDK 4.1 es una struct abierta en lugar de un enum, por lo que un `switch` sobre ella necesita una rama `default`). 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, el valor aquí 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ía `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>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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 solicitudes de red.</p><p></p><p>Una tercera política, `.returnCacheDataIfNotExpiredElseLoad(maxAge:)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada tenga menos de `maxAge` segundos, y va al servidor una vez que es más antigua.</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 paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema se refiere a paywalls personalizados. Para obtener orientación sobre cómo obtener paywalls personalizados 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** | requerido | 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](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>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. Es posible que los usuarios no reciban 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 solicitudes de red.</p><p></p><p>Una tercera política, `.returnCacheDataIfNotExpiredElseLoad(maxAge:)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada tiene menos de `maxAge` segundos, y va al servidor cuando es más antigua.</p><p></p><p>Ten en cuenta que la caché se conserva 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 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** | 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 superar ligeramente el tiempo de espera especificado en `loadTimeout`, ya que la operación puede estar compuesta por distintas 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` 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: <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`. 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 en formato localizado, usa `product.localizedPrice`. Esta localización se basa en la configuración regional del dispositivo. También puedes obtener el precio como número con `product.price`. El valor se proporcionará 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 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 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á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.subscriptionOffer`. Dentro de este objeto encontrarás las siguientes propiedades útiles:<br/>• `offerType`: el tipo de oferta, con los valores predefinidos `introductory`, `promotional` y `winBack` (en el SDK 4.1 y versiones posteriores es una struct abierta en lugar de un enum, por lo que un `switch` sobre ella necesita una rama `default`). 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 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. | ## 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** | 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` 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>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `.returnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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>Una tercera política, `.returnCacheDataIfNotExpiredElseLoad(maxAge:)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché tenga menos de `maxAge` segundos de antigüedad, y va al servidor cuando es más antigua.</p><p></p><p>Ten en cuenta que la caché se conserva 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 --- --- title: "Mostrar paywalls personalizados en el SDK de iOS" description: "Descubre cómo mostrar paywalls personalizados 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`. | </SDKv3> --- # File: making-purchases --- --- title: "Realizar compras in-app en iOS 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, presentar un paywall por sí solo solo admite compras cuando Adapty renderiza la pantalla, es decir, un [flow](adapty-flow-builder) o un paywall del [antiguo Paywall Builder](adapty-paywall-builder). Si renderizas la pantalla en tu propio código, 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 realicen las transacciones que deseen. 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 Adapty renderiza la pantalla. 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 una compra \{#make-purchase\} :::note **¿Adapty renderiza tu pantalla?** En un [flow](adapty-flow-builder) o un paywall creado con 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 ver una implementación completa 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 promocionadas desde la App Store \{#in-app-purchases-from-the-app-store\} Cuando un usuario inicia una compra en la App Store y la transacción llega a tu app, el SDK llama al método delegado `didReceivePromotedPurchase` con un objeto `AdaptyPromotedProduct`. Tienes dos opciones: - **Procesar la transacción de inmediato:** No implementes `didReceivePromotedPurchase`. La implementación predeterminada inicia la compra de inmediato y la pantalla del sistema de compras de Apple se muestra automáticamente. - **Guardar el objeto del producto para procesarlo más tarde:** Implementa `didReceivePromotedPurchase`, guarda el producto y luego llama a `makePurchase` con el producto guardado cuando lo necesites. Esto puede ser útil si necesitas mostrar algo personalizado al usuario antes de activar una compra. Si el producto promocionado incluye una oferta de suscripción, el SDK la aplica automáticamente en la compra. La oferta se lee del intent de compra de App Store, que la expone en iOS 18.0 y versiones posteriores. En iOS 16.4–17.x, `subscriptionOffer` siempre es `nil` y la compra se realiza al precio base. Este es el fragmento completo: ```swift showLineNumbers title="Swift" final class YourAdaptyDelegateImplementation: AdaptyDelegate { // 1. Store the product object to defer the transaction. // To process the transaction immediately instead, don't implement this method — // the default implementation starts the purchase right away. nonisolated func didReceivePromotedPurchase(_ product: AdaptyPromotedProduct) { // store the product object } // 2. Continue the deferred purchase later on by passing the product to `makePurchase` when the timing is appropriate func continueDeferredPurchase() async { let storedProduct: AdaptyPromotedProduct = // get the product object from 1. do { let purchaseResult = try await Adapty.makePurchase(product: storedProduct) // process the purchase result } catch { // handle the error } } } ``` :::warning La API `didReceivePromotedPurchase` está disponible a partir de la versión 4.1 del SDK y requiere **iOS 16.4** o posterior. La versión 4.0 del SDK no admite compras in-app promocionadas en absoluto, y en dispositivos con iOS inferior a 16.4 el método nunca se activa. Si necesitas compras promocionadas en versiones anteriores de iOS, permanece en el SDK 3.x y utiliza `shouldAddStorePayment` tal como se muestra a continuación. ::: <Details summary="¿Usas Adapty SDK 3.x?"> En el SDK 3.x, el SDK llama al método delegado `shouldAddStorePayment` en su lugar: - **Procesar la transacción de inmediato:** Devuelve `true` en `shouldAddStorePayment`. Esto activará la pantalla de compra de Apple al instante. - **Guardar el objeto del producto para procesarlo más tarde:** Devuelve `false` en `shouldAddStorePayment` y luego llama a `makePurchase` con el producto guardado cuando sea necesario. ```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 } } } ``` </Details> ## Canjear códigos de oferta en iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Acerca de los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios específicos. A diferencia de las ofertas regulares que se aplican automáticamente, los códigos de oferta se distribuyen fuera de la app — a través de campañas de correo electrónico, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, siguiendo 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 realiza al precio completo. - **Pay as you go** — el usuario paga un precio con descuento en cada ciclo de facturación durante un período determinado, luego la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único con descuento por toda la duración de la oferta, luego 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`. Una vez que el período de oferta termina y la suscripción se renueva al precio completo, la etiqueta ya no está presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Solución de problemas de discrepancias en 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 con descuento de la oferta, verifica lo siguiente en App Store Connect: - El código de oferta tiene el precio correcto configurado para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está establecido para el país o región específico 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 en su lugar. Puedes filtrar y verificar las transacciones con códigos 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 reemplazan con más funcionalidades: elegibilidad configurable, fechas de vencimiento y hasta 1 millón de códigos por trimestre. Si anteriormente usabas códigos promocionales para compras in-app, haz la transición a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app por versión) otorgaban 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ódigos promocionales — enviaba el precio completo del producto en el recibo. Como resultado, Adapty registraba estas transacciones al precio completo, lo que causaba discrepancias de ingresos 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. Dado que estos códigos están ahora obsoletos, haz la transición 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 la app móvil en iOS SDK" description: "Aprende cómo restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- Restoring Purchases (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 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 anteriormente sin pagar de nuevo. :::note Si construiste la pantalla en un builder, no necesitas restaurar las compras manualmente. Conecta su control de restauración — la acción **Restore purchases** en el Flow & Paywall Builder, o un enlace o botón de restauración en el antiguo — y el SDK completa la restauración. ::: Si construiste la pantalla tú mismo, 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) de tu infraestructura de compras existente a Adapty. Si también necesitas flows, paywalls y pruebas A/B, se requiere 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`. | ## Uso de flows y paywalls de Adapty en Observer Mode \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los flows, paywalls y funciones de pruebas A/B de Adapty, puedes hacerlo, pero requiere algo de configuración adicional en Observer Mode. Esto es lo que necesitarás hacer además de los pasos anteriores: 1. Muestra los [paywalls personalizados](present-remote-config-paywalls) como de costumbre. Para flows y paywalls creados en el Flow & Paywall Builder, consulta [Presenta flows en modo Observer](ios-present-paywall-builder-paywalls-in-observer-mode). 2. [Asocia flows y paywalls](report-transactions-observer-mode) con las transacciones de compra. --- # File: report-transactions-observer-mode --- --- title: "Reportar transacciones en Observer Mode en el SDK de iOS" description: "Reporta transacciones de compra en el Observer Mode de Adapty para obtener información sobre los usuarios y el seguimiento de ingresos en el SDK de iOS." --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+ (actual)" 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 notificar las transacciones desde tu store. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` para notificar explícitamente cada transacción y que Adapty la reconozca. :::warning **¡No omitas el registro 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 flows y paywalls de Adapty, incluye el `variationId` al registrar una transacción. Esto vincula la compra al flow o paywall que la activó, garantizando un análisis preciso. ```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. Recupéralo desde la propiedad `variationId` del objeto `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 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` 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 utilizas flows y paywalls de Adapty, incluye `withVariationId` al reportar una transacción. Esto vincula la compra al flow o paywall que la originó y garantiza unas analíticas precisas. ```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** | obligatorio | <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 de la propiedad `variationId` del objeto `AdaptyPaywall`. | </TabItem> <TabItem value="old2" label="Adapty SDK up to 3.2.x (legacy)" default> **Registro de transacciones** - Las versiones hasta la 3.1.x escuchan automáticamente las transacciones en el App Store, por lo que no es necesario reportarlas manualmente. - La versión 3.2 no admite el modo Observer. **Asociar flows y 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 flows, paywalls y/o pruebas A/B en el modo Observer, necesitas asociar la transacción proveniente de tu store con el flow o 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 solicitud: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | variationId | requerido | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto `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 Hay dos versiones del método `openWebPaywall`: 1. `openWebPaywall(product)` que genera URLs por paywall y 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. En SDK v4 este método recibe un `AdaptyFlowPaywall`, una variación de paywall del flow obtenido. Comprueba que `flow.paywalls` no esté vacío antes de acceder a sus elementos 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 | ## 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: ios-user --- --- title: "Usuarios y acceso en el SDK de iOS" description: "Aprende a trabajar con usuarios y niveles de acceso en tu app de iOS con el SDK de Adapty." --- <CustomDocCardList /> --- # File: identifying-users --- --- title: "Identificar usuarios en el SDK de iOS" 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: "Establecer atributos de usuario en el SDK de iOS" description: "Aprende a establecer atributos de usuario en Adapty para mejorar la segmentación de audiencias." --- Puedes establecer atributos opcionales como email, número de teléfono, etc., para los usuarios de tu app. Luego puedes usar estos atributos para crear [segmentos](segments) de usuarios o simplemente visualizarlos 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 el SDK de iOS" description: "Rastrea y gestiona el estado de suscripción de los usuarios en Adapty para mejorar la retención de clientes." --- Con Adapty, hacer un 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 la 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: ios-store-messages --- --- title: "Controla los mensajes de App Store en el SDK de iOS" description: "Elige cuándo tu app de iOS muestra mensajes de App Store sobre aumentos de precio, problemas de facturación y ofertas de recuperación, para que nunca interrumpan un flow ni un onboarding." --- A veces App Store necesita mostrar a tus usuarios un mensaje sobre su suscripción: pedirles que acepten un aumento de precio, informarles de un problema de facturación o presentarles una oferta de recuperación. Por defecto, StoreKit muestra estos mensajes por su cuenta mientras la app está abierta, incluso encima de un paywall, un onboarding o una pantalla de pago. Para elegir tú el momento, cambia el SDK a gestión manual de mensajes. El SDK añade entonces cada mensaje a una lista pendiente, y tu app muestra esa lista cuando llama a `showStoreMessages`. :::warning En el modo manual, StoreKit no muestra mensajes por sí solo. Si tu app nunca llama a `showStoreMessages`, los usuarios nunca verán una solicitud de consentimiento de aumento de precio ni un aviso de problema de facturación. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty iOS SDK 4.2 o posterior**: Las versiones anteriores no tienen la API de mensajes de la store. - **iOS 16 o Mac Catalyst 16 o posterior**: StoreKit no entrega mensajes a las apps en versiones anteriores, por lo que la API de mensajes de la store está marcada como disponible a partir de iOS 16. La API no está disponible en macOS. El SDK de Adapty es compatible con iOS 15. Si tu app también es compatible con iOS 15, envuelve cada llamada a store messages en `if #available(iOS 16.0, *)`, o marca el tipo que realiza la llamada con `@available(iOS 16.0, *)`. Sin una de estas comprobaciones, la app no compilará. Los ejemplos de este artículo incluyen las comprobaciones. Si tu deployment target es iOS 16 o posterior, puedes omitirlas. ## Habilitar la gestión manual \{#enable-manual-handling\} Para capturar los mensajes de App Store, establece `storeMessagesHandling` en `.manual` cuando [actives el SDK](sdk-installation-ios#activate-adapty-module-of-adapty-sdk): ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(storeMessagesHandling: .manual) // .auto is the default ``` El SDK comienza a capturar mensajes en cuanto se activa, y los mantiene en memoria durante la sesión actual de la app. Con el valor predeterminado `.auto`, el SDK no captura nada, y `getPendingStoreMessageTypes` y `showStoreMessages` no hacen nada. ## Obtener tipos de mensajes pendientes \{#get-pending-message-types\} Para saber si hay mensajes en espera, llama a `getPendingStoreMessageTypes`. Devuelve los tipos de mensajes pendientes sin mostrarlos ni eliminarlos: ```swift showLineNumbers if #available(iOS 16.0, *) { let pendingTypes = await Adapty.getPendingStoreMessageTypes() if pendingTypes.contains(.billingIssue) { // For example, skip the upsell and show the billing message first } } ``` Cada mensaje tiene uno de estos tipos: | Tipo | Mensaje | |:-----|:--------| | `.generic` | Un mensaje general de App Store. | | `.priceIncreaseConsent` | Una solicitud de consentimiento para un aumento de precio de suscripción. | | `.billingIssue` | Un aviso sobre un problema de facturación de suscripción. Disponible en iOS 16.4 y versiones posteriores. | | `.winBackOffer` | Una [oferta de recuperación](app-store-offers#win-back-offers) elegible. Disponible en iOS 18 y versiones posteriores. | Un tipo de mensaje que el SDK no reconoce tiene el valor sin procesar `storekit_<number>`. ## Mostrar mensajes pendientes \{#show-pending-messages\} Cuando tu app llega a un punto donde una pantalla del sistema no interrumpirá al usuario — por ejemplo, después de que el usuario cierra un flow o termina un onboarding — llama a `showStoreMessages`: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> En SwiftUI, pasa la acción `displayStoreKitMessage` desde el entorno de la vista actual: ```swift showLineNumbers @available(iOS 16.0, *) struct HomeView: View { @Environment(\.displayStoreKitMessage) private var displayStoreKitMessage var body: some View { Text("Home") .task { do { try await Adapty.showStoreMessages(using: displayStoreKitMessage) } catch { // handle the error } } } } ``` </TabItem> <TabItem value="uikit" label="UIKit"> En UIKit, pasa el window scene para mostrar los mensajes en: ```swift showLineNumbers if #available(iOS 16.0, *) { do { try await Adapty.showStoreMessages(in: view.window?.windowScene) } catch { // handle the error } } ``` Si pasas `nil` u omites `in`, el SDK utiliza la escena activa en primer plano con la ventana clave. </TabItem> </Tabs> El SDK muestra los mensajes uno tras otro. Cuando StoreKit muestra un mensaje, el SDK lo elimina de la lista de pendientes. Si StoreKit no puede mostrar un mensaje, el SDK escribe el error en el log de Adapty, conserva el mensaje para la próxima llamada y continúa con los demás mensajes. ### Mostrar solo algunos tipos de mensajes \{#show-only-some-message-types\} Para mostrar solo algunos de los mensajes pendientes, indica los tipos en `for`. Los demás mensajes permanecen pendientes: ```swift showLineNumbers try await Adapty.showStoreMessages( for: [.billingIssue, .priceIncreaseConsent], using: displayStoreKitMessage ) ``` Sin `for`, el SDK muestra todos los mensajes pendientes, incluidos los tipos que no reconoce. ### Errores \{#errors\} `showStoreMessages` lanza un [`AdaptyError`](ios-sdk-error-handling) con uno de estos códigos: | Código | Nombre | Causa | |:-----|:-----|:------| | 3201 | `operationInProgress` | Otra llamada a `showStoreMessages` sigue mostrando mensajes. Espera a que termine e inténtalo de nuevo. | | 3202 | `resolverFailure` | Solo UIKit: no pasaste una escena y el SDK no encontró ninguna escena activa en primer plano. Llama al método de nuevo cuando tu app esté en primer plano, o pasa la escena de forma explícita. | | 9000 | `operationInterrupted` | La tarea que llamó al método fue cancelada. | --- # 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 Adapty renderiza tus pantallas) 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: ios-onboardings --- --- title: "Onboardings en el SDK de iOS" description: "Aprende a trabajar con onboardings en tu app de iOS con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: <CustomDocCardList /> --- # File: get-onboardings --- --- title: "Obtener onboardings y su configuración" description: "Aprende a recuperar onboardings en Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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: "Presentar onboardings en iOS SDK" description: "Descubre cómo presentar onboardings en iOS para aumentar las conversiones y los ingresos." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 el onboarding en iOS usando Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: Antes de empezar, 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 iOS SDK" description: "Guarda y utiliza datos de onboardings en tu app iOS con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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-best-practices --- --- title: "Mejores prácticas en el SDK de iOS" description: "Patrones de referencia para integrar el SDK de Adapty en iOS: orden de llamadas, gestión de errores y otras reglas para entornos de producción." --- <CustomDocCardList /> --- # 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 iniciar la app, primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `Adapty.activate(with: config)` con `customerUserId` definido en la config | Al iniciar la app, tras el paso 1, si tienes el ID de usuario | Recomendado. No se crea ningún perfil anónimo. | | 2b | `Adapty.activate(with: config)` sin `customerUserId` | Al iniciar 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 de usuario | Necesario para que los IDs del MMP queden asociados al 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 el flujo 2b con autenticación | Siempre usa `await`. Las llamadas concurrentes durante `identify` producen `#3006 profileWasChanged`. | | 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateExternalAttribution`, `updateProfile` | Tras el paso 4 si llamas a `identify`; de lo contrario, 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. ::: ## Las actualizaciones repetidas del perfil están limitadas \{#repeated-profile-updates-are-throttled\} Envía los identificadores de integración y los atributos del perfil una sola vez, tras completar la activación, y solo cuando cambie algún valor. Si tu app actualiza el perfil con demasiada frecuencia, el backend de Adapty limita el endpoint: la solicitud falla con [`#2004 serverError`](ios-sdk-error-handling#network-errors) y el SDK bloquea las llamadas posteriores a ese endpoint hasta que finalice el intervalo de reintento especificado por el servidor. La limitación es por endpoint, por lo que el resto de llamadas del SDK siguen funcionando. ## Obtener un placement justo después de `updateProfile` \{#fetching-a-placement-right-after-updateprofile\} Si llamas a `updateProfile` para establecer atributos personalizados y luego obtienes un placement (`getPaywall`) en la misma sesión, espera a que `updateProfile` termine antes de hacer la solicitud. Ejecutarlos al mismo tiempo puede hacer que el SDK calcule el hash del segmento del placement a partir de atributos que aún no han terminado de propagarse, lo que añade un reintento antes de que la solicitud se resuelva. Consulta [Gestionar errores en el SDK de iOS](ios-sdk-error-handling#general-errors) si el propio reintento falla. ## 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 obtención de flows y paywalls en el SDK de iOS" description: "Obtén flows y paywalls de Adapty de forma fiable: temporización, caché y patrones de respaldo para iOS." --- Una obtención fiable de un flow o paywall en iOS hace tres cosas: renderiza rápido, devuelve la variación con la audiencia configurada y recurre al respaldo sin problemas cuando la red es lenta. Las reglas que se explican a continuación cubren los patrones de temporizació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 iOS](ios-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, o precarga la caché con [`preloadFlows`](#preload-placements) (SDK 4.1+). | Lances tus propias llamadas concurrentes `getFlow` al arrancar. | Una ráfaga de prefetch manual bloquea el hilo principal y produce una pantalla en negro. `preloadFlows` está diseñado para esto y comparte un único presupuesto de tiempo de espera para todo el lote. | | Llama a `getFlow` después de que la atribución haya tenido tiempo de resolverse — por ejemplo, 1–2 segundos después de `activate` o tras que se dispare `didLoadLatestProfile`. | Llamar a `getFlow` en `App.init()`. | La atribución aún no ha llegado. El flow se resuelve contra la audiencia por defecto y silenciosamente omite los segmentos y la personalización de ASA. | | Establece un `loadTimeout` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | Esperar a `getFlow` indefinidamente. | Sin un tiempo de espera, los usuarios con mala conectividad ven una pantalla en blanco hasta que la red se resuelva — o cierran la app. | Cuando `loadTimeout` se activa en cualquier solicitud de fetch —incluyendo un `getFlow` simple—, el SDK devuelve la variante en caché si existe; de lo contrario, obtiene la variante de la audiencia predeterminada (**All Users**) dentro del tiempo restante. La segmentación se pierde para esa solicitud, no se pospone: los segmentos y las audiencias basadas en atribución no se aplican al resultado. Consulta [Obtener paywalls y productos personalizados](fetch-paywalls-and-products) para la referencia de los parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado. ## Precargar placements \{#preload-placements\} :::info `preloadFlows` y `preloadFlowsForDefaultAudience` están disponibles a partir de la versión 4.1 del SDK. ::: `preloadFlows` almacena en caché el JSON del flow con antelación — una solicitud por placement. Luego lo usas de la forma habitual: `getFlow` para el flow y `getFlowConfiguration` para su configuración de vista. `fetchPolicy` determina qué capa lee primero un `getFlow` posterior, no si puede acceder a la caché en absoluto: - `.returnCacheDataElseLoad` lee primero la copia precargada y solo va a la red si no hay nada en caché. `.returnCacheDataIfNotExpiredElseLoad(maxAge:)` hace lo mismo mientras la copia sea más reciente que `maxAge`. - El valor predeterminado, `.reloadRevalidatingCacheData`, va primero a la red y cae de vuelta a la copia precargada cuando la solicitud falla o expira. Una precarga resulta útil en ambos casos, pero de forma distinta: una política de caché primero elimina la solicitud, mientras que la predeterminada la mantiene y obtiene una copia en caliente como respaldo. Úsalo cuando sepas qué placements necesitará la sesión pero no quieras mostrarlos todavía — por ejemplo, justo después de que resuelvan `activate` e `identify`, para el flow detrás de un botón que el usuario aún no ha pulsado. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers try await Adapty.preloadFlows( placementIds: ["onboarding", "main_paywall"], loadTimeout: 5 ) // Later, when the user reaches the paywall: let flow = try await Adapty.getFlow( placementId: "main_paywall", fetchPolicy: .returnCacheDataElseLoad ) ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.preloadFlows(placementIds: ["onboarding", "main_paywall"], loadTimeout: 5) { error in if let error { // at least one placement failed to preload } } // Later, when the user reaches the paywall: Adapty.getFlow(placementId: "main_paywall", fetchPolicy: .returnCacheDataElseLoad) { result in // ... } ``` </TabItem> </Tabs> **Parámetros:** - `placementIds` (obligatorio): los placements a precargar. Los IDs en blanco y duplicados se ignoran. - `loadTimeout` (opcional): tiempo de espera en segundos para todo el lote, no por placement. El valor predeterminado es 5 segundos, y los valores por debajo de 1 segundo se elevan a 1 segundo. **Comportamiento que debes conocer:** - El método solo lanza un error tras intentarlo con todos los placements, y el error agrupa los fallos individuales de cada placement. Un fallo en un placement no detiene a los demás. - Si un placement supera el tiempo límite o falla por un error de red, el SDK recurre a la variación de audiencia predeterminada para ese placement. El resto de fallos se notifican tal cual. - Si se agota el tiempo límite antes de que complete la obtención de la audiencia segmentada, el SDK aún intenta la variación de audiencia predeterminada dentro del tiempo restante. - La precarga solo calienta la caché. No devuelve contenido: para mostrarlo, sigue siendo necesario llamar a `getFlow`. ### Lo que cubre una precarga \{#what-a-preload-covers\} Un flow llega a la pantalla en capas. Una precarga cubre la primera, exactamente igual que `getFlow`: | Capa | Obtenida por | Calentada por una precarga | |---|---|---| | Flow JSON — la variante elegida, sus IDs de producto y el Remote Config | `getFlow` | Sí | | Diseño de UI — la estructura, el estilo y el texto de la pantalla | `getFlowConfiguration` | No | | Imágenes, incluido el fotograma estático que sustituye a un elemento de vídeo | `getFlowConfiguration`, en segundo plano | No | | Archivos de vídeo | El reproductor del sistema, mientras se renderiza la pantalla | El SDK no los almacena en caché | `getFlowConfiguration` espera el layout, por lo que la primera solicitud para un layout determinado tiene el coste de un viaje de ida y vuelta incluso después de una precarga. El SDK guarda ese layout en su propia caché de disco, que sobrevive a los reinicios de la app y se lee antes de cualquier llamada de red, de modo que el coste recae en la primera solicitud y no en todas. Una vez que el SDK tiene el layout, comienza a cachear las imágenes de forma independiente a la llamada: no bloquea la pantalla, y no hay ningún callback, método delegado ni error que informe de cuándo termina. ### Descubre qué placement falló \{#find-out-which-placement-failed\} El error lanzado es un único `AdaptyError` que cubre todo el lote, con el código `networkFailed` (2005). Para ver los fallos individuales, lee su propiedad `preloadErrors` — un diccionario cuya clave es el ID del placement: ```swift showLineNumbers do { try await Adapty.preloadFlows(placementIds: ["onboarding", "main_paywall"]) } catch { for (placementId, placementError) in error.preloadErrors ?? [:] { // log or retry the individual placement } } ``` `preloadErrors` es `nil` para cualquier error que no provenga de una llamada de precarga, así que trata un valor `nil` como "no es un fallo de precarga" en lugar de "sin fallos". ### Omitir la segmentación de audiencias \{#skip-audience-segmentation\} Para calentar la caché sin esperar a la segmentación de audiencias en absoluto, usa la variante de audiencia predeterminada: ```swift showLineNumbers try await Adapty.preloadFlowsForDefaultAudience(placementIds: ["main_paywall"]) ``` ## Mostrar medios de la primera pantalla desde el bundle de la app \{#show-first-screen-media-from-the-app-bundle\} Un flow descarga sus imágenes y vídeos desde Adapty. Para mostrar los medios de la primera pantalla de forma instantánea, sírvelos desde el bundle de la app. Es una buena forma de reutilizar medios que ya incluyes en la app, como los visuales de un onboarding nativo existente. 1. En el Flow & Paywall Builder, asigna un [ID de medio personalizado](custom-media#custom-media-id) a la imagen o vídeo. El archivo que subas allí se mantendrá como respaldo. 2. Añade el archivo al bundle de tu app. 3. Cuando llames a `getFlowConfiguration`, pasa el archivo del bundle para ese ID a través de `assetsResolver`: ```swift showLineNumbers // "welcome_video" is the custom media ID set in the Flow & Paywall Builder let bundledAssets: [String: AdaptyCustomAsset] = [ "welcome_video": .video( .file( url: Bundle.main.url(forResource: "welcome", withExtension: "mp4")!, preview: .uiImage(value: UIImage(named: "welcome_poster")!), resolution: CGSize(width: 1080, height: 1920) ) ), ] let flowConfig = try await AdaptyUI.getFlowConfiguration( forFlow: flow, assetsResolver: bundledAssets ) ``` Los archivos incluidos en el bundle aumentan el tamaño de descarga de tu app, así que incluye solo los archivos multimedia que el usuario ve al principio. Los archivos multimedia que no incluyas en el bundle aparecen igualmente de inmediato: la configuración de la vista lleva una copia de baja resolución de cada imagen, incluido el fotograma estático de cada vídeo, y la muestra hasta que carga el archivo completo. Para la referencia completa de `assetsResolver`, consulta [Personalizar assets](get-pb-paywalls#customize-assets). ## Ajustes para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad habitualmente deficiente (zonas rurales, transporte, regiones con problemas de enrutamiento): - Establece `fetchPolicy: .returnCacheDataElseLoad` en cada fetch excepto el primero. - 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 bloquees la visualización del flow esperando a `getProfile()`. Llama a `getFlow` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: ios-show-aa-targeted-paywall --- --- title: "Mostrar un paywall orientado a AA en el primer lanzamiento con el SDK de iOS" description: "Espera la atribución de Apple Ads antes de solicitar el paywall en iOS usando AdaptyProfile.appliedExternalAttributionProviders." --- Apple Ads (AA) attribution llega de forma asíncrona después de `Adapty.activate()`. Si llamas a `getFlow` pronto, la atribución puede que aún no haya llegado y Adapty resuelve el placement contra la audiencia predeterminada, omitiendo tus paywalls segmentados por AA. `AdaptyProfile.appliedExternalAttributionProviders` permite que la app detecte cuándo se ha aplicado la atribución de AA al perfil, para que la solicitud del paywall pueda esperar hasta que la segmentación por AA se resuelva correctamente. :::important `appliedExternalAttributionProviders` solo reporta Apple Ads. La atribución de otros proveedores es visible en los perfiles de usuario y está disponible en los filtros de segmento, pero aún no aquí. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - Adapty iOS SDK **4.1** o posterior. - Apple Ads configurado para la app en Adapty. Consulta [Apple Ads](apple-search-ads). :::note Los ejemplos usan los nombres de la API de SDK 4.1. En SDK 4.0, la propiedad del perfil se llama `appliedAttributionSources`. En SDK 3.x, esa propiedad tiene el mismo nombre anterior, y los paywalls se obtienen con `getPaywall`/`getPaywallForDefaultAudience`, que devuelven un `AdaptyPaywall`. Consulta [Migrar Adapty iOS SDK a v4.1](migration-to-ios-sdk-41). ::: ## 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 `appliedExternalAttributionProviders` contiene `.appleAds`. Un array vacío puede significar cualquiera de lo siguiente: - La atribución de Apple Ads aún no se ha procesado para este perfil. - No ha llegado ninguna atribución. - La atribución llegó de otro proveedor, que este array no reporta. Incluso con un array vacío, `getFlow` es seguro de llamar — Adapty resuelve la solicitud según la audiencia que coincida con el estado actual del perfil, normalmente la audiencia predeterminada. :::important La espera solo se aplica al **primer lanzamiento**. Una vez que se ha registrado la atribución de Apple Ads, se almacena permanentemente en el perfil. En cada lanzamiento posterior, el perfil en caché ya incluye `.appleAds` en `appliedExternalAttributionProviders`, `didLoadLatestProfile` se activa con ese valor de forma inmediata y `getFlow` 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 aún no has configurado el delegado, consulta [Escuchar actualizaciones de suscripción](ios-check-subscription-status#listen-to-subscription-updates). 3. **Observa `.appleAds` en `appliedExternalAttributionProviders`.** Cuando aparezca, solicita el paywall — Adapty devolverá la variante segmentada por AA: ```swift extension <YourAdaptyDelegateImpl>: AdaptyDelegate { nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { if profile.appliedExternalAttributionProviders.contains(where: { $0 == .appleAds }) { // load the paywall via Adapty.getFlow(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 se muestra a continuación compite con la atribución contra un tiempo de espera, precarga el paywall de la audiencia predeterminada en paralelo y devuelve el paywall que corresponda. El código que llama a esta función espera una única función `async` — sin delegados ni indicadores de estado que gestionar en el punto de llamada. `ProfileObserver` es un singleton reutilizable que publica actualizaciones de perfil desde `AdaptyDelegate`. `FlowLoader.getFlowOrDefault` ejecuta la carrera usando un `TaskGroup` de concurrencia estructurada: - Si la atribución llega dentro del `timeout`, devuelve el paywall segmentado mediante `getFlow(placementId:)`. - Si el `timeout` expira primero, devuelve el paywall de audiencia por defecto precargado mediante `getFlowForDefaultAudience(placementId:)`. ```swift title="FlowLoader.swift" /// Demonstrates how to fetch a paywall that depends on attribution being applied, /// falling back to the default-audience paywall if attribution doesn't arrive in time. /// /// Stateless and self-contained: every call kicks off its own default-audience /// prefetch and races it against attribution + segmented fetch. enum FlowLoader { static func getFlowOrDefault( placementId: String, timeout: TimeInterval ) async throws -> AdaptyFlow { struct TimedOut: Error {} // Kick off the default-audience request immediately so it has the full // `timeout` window to load. We'll either cancel it on success or await // its result on timeout — never a duplicate network call. let defaultFlowTask = Task { try await Adapty.getFlowForDefaultAudience(placementId: placementId) } do { // Race two child tasks: whichever finishes first wins. let result = try await withThrowingTaskGroup(of: AdaptyFlow.self) { group in // 1. Wait for attribution, then ask Adapty for the segmented paywall. group.addTask { await waitForAttribution() return try await Adapty.getFlow(placementId: placementId) } // 2. Time-bomb: throws `TimedOut` after `timeout` seconds. 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() // stop the loser (sleeper or the attribution wait). return value } // Segmented paywall won — we no longer need the default-audience prefetch. defaultFlowTask.cancel() return result } catch is TimedOut { // Attribution didn't apply in time — return the prefetched default // (instant if already done, otherwise we await the in-flight request). return try await defaultFlowTask.value } } /// Suspends until a profile with the desired attribution source is observed. /// `@Published.values` emits the current profile immediately on subscription, /// so this returns on the first iteration if attribution is already applied. @MainActor private static func waitForAttribution() async { for await profile in ProfileObserver.shared.$profile.values { if profile?.appliedExternalAttributionProviders.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 flow = try await FlowLoader.getFlowOrDefault( 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: ios-reference --- --- title: "Referencia del SDK para iOS" description: "Documentación de referencia del SDK de Adapty para iOS." --- Esta página contiene la documentación de referencia del SDK de Adapty para iOS. Elige el tema que necesitas: - **[Modelos del SDK](https://swift.adapty.io/)** - Modelos de datos y estructuras utilizados por el SDK - **[Gestionar errores](ios-sdk-error-handling)** - Gestión de errores y resolución de problemas --- # File: ios-sdk-error-handling --- --- title: "Gestión de errores en el SDK de iOS" description: "Gestiona los errores del SDK de iOS de forma eficiente con la guía de solución de problemas de Adapty." --- El SDK de Adapty tiene su propio contenedor para todo tipo de errores, llamado `AdaptyError`. Básicamente, todo error que devuelve el SDK es un `AdaptyError`. Tiene dos propiedades útiles: `originalError` y `adaptyErrorCode`, que se describen a continuación. **originalError** contiene el error original en caso de que necesites trabajar directamente con él. Puede ser [SKError](https://developer.apple.com/documentation/storekit/skerror), [NSError](https://developer.apple.com/documentation/foundation/nserror) o simplemente un [Error](https://developer.apple.com/documentation/swift/error) genérico de Swift. Esta propiedad es opcional, ya que algunos errores pueden ser generados directamente por el SDK —como datos inconsistentes o faltantes— y no tendrán un error original sobre el que se construyó el wrapper. **adaptyErrorCode** se puede usar para gestionar problemas comunes, como: - credenciales no válidas - errores de red - pagos cancelados - problemas de facturación - recibo no válido - y mucho más Es bastante sencillo comprobar el error buscando códigos específicos y reaccionar en consecuencia. ```swift showLineNumbers title="Swift" do { let info = try await Adapty.makePurchase(product: product) } catch { if error.adaptyErrorCode == .paymentCancelled { // purchase was cancelled // you can offer discount to your user or remind them later } } ``` :::tip **Activa los logs detallados antes de depurar.** La mayoría de los `AdaptyError` envuelven un error subyacente de StoreKit, de red o del backend. Con los logs detallados activados (`Adapty.logLevel = .verbose` — consulta [Logging](sdk-installation-ios#logging)), ese error envuelto se imprime en la consola, lo que normalmente indica la causa real. La propiedad `originalError` se rellena independientemente del nivel de log — los logs detallados simplemente lo muestran en la consola. ::: :::important Si estas soluciones no resuelven tu problema, consulta [Otros problemas](#other-issues) para conocer los pasos a seguir antes de contactar con el soporte y así ayudarnos a asistirte de forma más eficiente. ::: ## Errores de StoreKit \{#storekit-errors\} | Error | Código | Solución | |--------------------------------------------------------------------------------------------------------------------------------------------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [unknown](https://developer.apple.com/documentation/storekit/skerror/code/unknown) | 0 | Código de error que indica que ocurrió un error desconocido o inesperado. <br/> Reintenta o consulta la sección [Otros problemas](#other-issues). | | [clientInvalid](https://developer.apple.com/documentation/storekit/skerror/code/clientinvalid) | 1 | Este código de error indica que el cliente no tiene permiso para realizar la acción que intentó. | | [paymentCancelled](https://developer.apple.com/documentation/storekit/skerror/code/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](https://developer.apple.com/documentation/storekit/skerror/code/paymentinvalid) | 3 | Este error indica que uno de los parámetros de pago no fue reconocido por el App Store. | | [paymentNotAllowed](https://developer.apple.com/documentation/storekit/skerror/code/paymentnotallowed) | 4 | Este código de error indica que el usuario no tiene permiso para autorizar pagos. | | [storeProductNotAvailable](https://developer.apple.com/documentation/storekit/skerror/code/storeproductnotavailable) | 5 | Este código de error indica que el producto solicitado no está disponible en el store. <br/> Intenta reinstalar la app. | | [cloudServicePermissionDenied](https://developer.apple.com/documentation/storekit/skerror/code/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](https://developer.apple.com/documentation/storekit/skerror/code/cloudservicenetworkconnectionfailed) | 7 | Este código de error indica que el dispositivo no pudo conectarse a la red. | | [cloudServiceRevoked](https://developer.apple.com/documentation/storekit/skerror/code/cloudservicerevoked/) | 8 | Este código de error indica que el usuario ha revocado el permiso para usar este servicio en la nube. | | [privacyAcknowledgementRequired](https://developer.apple.com/documentation/storekit/skerror/code/privacyacknowledgementrequired) | 9 | Este código de error indica que el usuario aún no ha aceptado la política de privacidad de Apple. | | [unauthorizedRequestData](https://developer.apple.com/documentation/storekit/skerror/code/unauthorizedrequestdata) | 10 | Este código de error indica que la app intenta usar una propiedad para la que no tiene el entitlement requerido. | | [invalidOfferIdentifier](https://developer.apple.com/documentation/storekit/skerror/code/invalidofferidentifier) | 11 | <p>El [`identifier`](https://developer.apple.com/documentation/storekit/skpaymentdiscount/identifier) de la oferta no es válido. Por ejemplo, no has configurado una oferta con ese identificador en el App Store, o la has revocado.</p><p>Asegúrate de configurar las ofertas deseadas en AppStore Connect y de pasar un identificador de oferta válido.</p> | | [invalidSignature](https://developer.apple.com/documentation/storekit/skerror/code/invalidsignature) | 12 | Este código de error indica que la firma en un descuento de pago no es válida. | | [missingOfferParams](https://developer.apple.com/documentation/storekit/skerror/code/missingofferparams) | 13 | Este código de error indica que faltan parámetros en un descuento de pago. | | [invalidOfferPrice](https://developer.apple.com/documentation/storekit/skerror/code/invalidofferprice/) | 14 | Este código de error indica que el precio que especificaste en App Store Connect ya no es válido. Las ofertas siempre deben representar un precio con descuento. | | noProductIDsFound | 1000 | <p>Este error 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 puede ir acompañado de una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin error, ignórala.</p><p>Si te encuentras con este error, sigue los pasos de la sección [Solución para el error Code-1000 `noProductIDsFound`](InvalidProductIdentifiers).</p> | | productRequestFailed | 1002 | No se pueden obtener los productos disponibles en este momento. | | cantMakePayments | 1003 | Las compras in-app no están permitidas en este dispositivo. Consulta la [guía](cantMakePayments) de solución de problemas. | | [cantReadReceipt](https://developer.apple.com/documentation/storekit/skerror/code/paymentcancelled) | 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>En el sandbox no tendrás un archivo de recibo válido hasta que realices una compra, así que asegúrate de hacer una antes de acceder a él. Durante las pruebas en sandbox, verifica también que hayas iniciado sesión en el dispositivo con una cuenta sandbox de Apple válida.</p> | | productPurchaseFailed | 1006 | La compra del producto falló. Esto envuelve un error subyacente de StoreKit — lee `originalError` (o activa los registros detallados para verlo en la consola) para conocer el motivo real. El error envuelto suele ser uno de los códigos StoreKit 0–14 de la tabla anterior — los más comunes son `paymentCancelled`, `paymentInvalid`, `paymentNotAllowed` o `invalidOfferPrice`. Si no puedes identificar un motivo concreto, prueba con un nuevo [perfil sandbox](test-purchases-in-sandbox); si sigue fallando, contacta con el soporte de Apple. | | refreshReceiptFailed | 1010 | La operación de actualización del recibo falló. | | fetchSubscriptionStatusFailed | 1020 | No se pudo obtener el estado de la suscripción desde el App Store. | | unknownTransactionId | 1030 | El identificador de transacción es desconocido. | | paymentPendingError | 1050 | El pago está pendiente actualmente. | | billingPlanUnavailable | 1060 | El producto no ofrece el plan de facturación solicitado en este dispositivo. Un plan de [compromiso de 12 meses](app-store-12-month-commitment) requiere iOS 26.4 o posterior y una app compilada con Xcode 26.5 o posterior. | ## Errores de red \{#network-errors\} | Error | Code | Solution | | :------------- | :--- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | notActivated | 2002 | El SDK de Adapty no está activado. <br/> Se produce con mayor frecuencia cuando una splash screen o un hook de UI temprano llama a métodos de Adapty antes de que `Adapty.activate` retorne. El síntoma es intermitente y puede no reproducirse en el simulador porque el timing en dispositivo real es diferente. Espera al completion handler o al resultado asíncrono de `activate` antes de programar cualquier otra llamada al SDK. Consulta [Orden de llamadas en el SDK de iOS](ios-sdk-call-order) para ver la secuencia completa. | | badRequest | 2003 | Solicitud incorrecta. <br/> En `getPaywall`, esto generalmente significa que el placement solicitado no existe en la app a la que pertenece tu API key. Verifica que el ID del placement esté copiado exactamente y que la API key y el placement provengan de la misma app en el Adapty Dashboard. | | serverError | 2004 | Error del servidor. <br/> Vuelve a intentarlo después de un tiempo. Si el problema persiste, contacta al equipo de soporte de Adapty. <br/> Este código también cubre el throttling: si llamas al mismo endpoint con demasiada frecuencia (por ejemplo, actualizaciones frecuentes de perfil), el servidor responde con 429 y el SDK bloquea ese endpoint hasta que transcurra el intervalo de reintento. | | networkFailed | 2005 | El error indica problemas con la conexión de red en el dispositivo del usuario. <br/> Prueba desactivando la VPN o cambiando a WiFi desde una red móvil, o viceversa. | | decodingFailed | 2006 | Este error indica que falló la decodificación de la respuesta: el SDK recibió datos que no puede parsear. <br/> Si ocurre mientras el SDK carga un archivo de [respaldo](fallback-paywalls) local, el archivo es más antiguo de lo que espera el SDK: descarga un archivo actualizado desde la página **Placements**. Una discrepancia de versión del paywall de respaldo también puede manifestarse como el error 3001. | | encodingFailed | 2009 | Este error indica que falló la codificación de la solicitud. | ## Errores generales \{#general-errors\} | Error | Code | Solution | | :------------------- | :--- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | analyticsDisabled | 3000 | No podemos gestionar eventos de analítica porque has [desactivado esta opción](analytics-integration#disabling-external-analytics-for-a-specific-customer). | | wrongParam | 3001 | Este error indica que algunos de tus parámetros no son correctos. <br/> Si usas el antiguo Paywall Builder y no puedes mostrar un paywall debido a este error, activa **Show on device** en ese builder.<br/> Otra posible causa es que la versión del archivo de [respaldo](fallback-paywalls) local no coincida con la versión del SDK. Descarga un nuevo archivo desde el dashboard. | | 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. <br/> Esto ocurre cuando se llama a un método mientras `Adapty.identify` todavía está en curso — la llamada en vuelo llega a un perfil que está a punto de ser reemplazado y el SDK la rechaza. Siempre usa `await` con `identify` (o su completion handler) antes de cualquier llamada de acción del usuario. Consulta [Orden de llamadas en el SDK de iOS](ios-sdk-call-order). | | unsupportedData | 3007 | Este error indica que el formato de datos no es compatible con el SDK. | | unidentifiedUserLogout | 3020 | No es posible llamar al método `logout` para un usuario no identificado. | | fetchTimeoutError | 3101 | Este error indica que la operación de obtención de datos superó el tiempo de espera. | | operationInProgress | 3201 | Otra operación del mismo tipo sigue en ejecución. <br/> Lo devuelve `showStoreMessages` cuando una llamada anterior todavía muestra [mensajes del App Store](ios-store-messages). Espera a que finalice y vuelve a intentarlo. | | resolverFailure | 3202 | El SDK no pudo encontrar el recurso que necesita la operación. <br/> Lo devuelve el método `showStoreMessages` de UIKit cuando no se pasa una window scene y no existe ninguna escena activa en primer plano. Pasa la escena explícitamente o llama al método de nuevo cuando la app esté en primer plano. | | operationInterrupted | 9000 | El sistema interrumpió esta operación. | ## Fetches lentas justo después de `updateProfile` \{#slow-fetches-right-after-updateprofile\} Si estableces atributos personalizados con `updateProfile` y obtienes un placement en la misma sesión, el SDK puede calcular el hash de segmento del placement a partir de atributos que aún no han terminado de propagarse. Reintenta una vez que el hash de segmento se actualiza, por lo que el síntoma es una obtención más lenta en lugar de un error. La obtención falla solo cuando el reintento sigue sin coincidir, lo que significa que la escritura del atributo no se ha completado. Para evitar el reintento, usa `await` con `updateProfile` antes de obtener el placement. Consulta [Orden de llamadas en el SDK de iOS](ios-sdk-call-order). ## Otros problemas \{#other-issues\} Si aún 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 para problemas conocidos. - **Contacta con el equipo de soporte u obtén ayuda de otros desarrolladores** en el [foro de soporte](https://adapty.featurebase.app/). - **Contacta con el equipo de soporte en [support@adapty.io](mailto:support@adapty.io) o a través del chat**: Si no estás listo para actualizar el SDK o no te ha ayudado, contacta con nuestro equipo de soporte. Ten en cuenta que tu problema se resolverá más rápido si [activas el registro detallado](sdk-installation-ios#logging) y compartes los logs con el equipo. También puedes adjuntar fragmentos de código relevantes. --- # 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: ## Antes de empezar: verifica que el paywall tenga productos \{#before-you-start-verify-the-paywall-has-products\} El mismo error ocurre cuando el paywall al que resuelve tu placement no tiene productos asociados: el SDK solicita entonces una lista de productos vacía, y ninguna comprobación en el App Store puede corregirlo. En el Adapty Dashboard, abre el paywall que aparece en este placement y confirma que su sección **Products** lista los productos que esperas. Si es así, continúa con el Paso 1. ## 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**. Copia solo el identificador — en formato DNS inverso sin espacios, por ejemplo, `com.company.app`. App Store Connect muestra el nombre de la app junto a él en otras vistas, como la lista de apps; pegar eso en lugar del identificador hará que la conexión no pueda validarse. <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: ios-sdk-migration-guides --- --- title: "Guías de migración del SDK de iOS" description: "Guías de migración para las versiones del SDK de iOS de Adapty." --- Esta página contiene todas las guías de migración para el SDK de iOS de Adapty. Elige la versión a la que quieres migrar para obtener instrucciones detalladas: - [**Migrar a v4.1**](migration-to-ios-sdk-41) - [**Migrar a v4.0**](migration-to-ios-sdk-v4) - [**Migrar a v3.15**](migration-to-ios-315) - **[Migrar a v3.4](migration-to-ios-sdk-34)** - **[Migrar a v3.3](migration-to-ios330)** - **[Migrar a v3.0](migration-to-ios-sdk-v3)** --- # File: migration-to-ios-sdk-41 --- --- title: "Migrar el SDK de iOS de Adapty a v4.1" description: "Migra al SDK de iOS de Adapty v4.1: habilita Adapty Attribution explícitamente, adopta las APIs de atribución externa renombradas y vuelve a descargar tu archivo de respaldo." --- El SDK de iOS de Adapty 4.1 cambia la forma en que se habilita Adapty Attribution, renombra las APIs de atribución externa y cambia el formato del archivo de respaldo. También recupera el soporte para las compras in-app promocionadas en el App Store, que se eliminaron en la versión 4.0. :::warning Las APIs renombradas son un cambio radical. Los nombres anteriores se han eliminado por completo — no están obsoletos, y no hay typealiases ni anotaciones `@available(renamed:)` que sirvan de puente. El código que compila con 4.0.x no compilará con 4.1 hasta que renombres cada punto de llamada indicado a continuación. ::: ## Referencia rápida \{#quick-reference\} | v4.0 | v4.1 | |---|---| | Atribución de Adapty habilitada automáticamente | Atribución de Adapty deshabilitada por defecto; actívala con `.with(adaptyAttributionEnabled: true)` | | `Adapty.updateAttribution(_:source:)` | `Adapty.updateExternalAttribution(_:provider:)` | | `Adapty.updateAttribution(_ attributionJson: String, source:)` | Eliminado de la API pública; pasa un diccionario en su lugar | | `AdaptyAttributionSource` | `AdaptyExternalAttributionProvider`, con un nuevo valor `.custom` | | `AdaptyProfile.appliedAttributionSources` | `AdaptyProfile.appliedExternalAttributionProviders` | | Enum `AdaptySubscriptionOfferType` | Struct `AdaptySubscriptionOfferType`; `.code` eliminado | | Archivo de respaldo descargado para 4.0 | Nuevo formato de archivo de respaldo; descarga el archivo de nuevo | | Las compras in-app promocionadas se completaban automáticamente, sin posibilidad de interceptarlas | El método delegado `didReceivePromotedPurchase(_:)` y `AdaptyPromotedProduct` delegan la finalización a tu app | ## ⚠️ La atribución de Adapty está desactivada por defecto \{#adapty-attribution-is-disabled-by-default\} :::warning Si actualizas al SDK 4.1 y no lo activas explícitamente, la [atribución de Adapty](user-acquisition) deja de funcionar sin avisar: las instalaciones dejan de registrarse y no recibirás ninguna advertencia. ::: En la versión 4.0 y anteriores, el SDK registraba instalaciones para [Adapty Attribution](user-acquisition) automáticamente. A partir de la versión 4.1, esta función está desactivada por defecto: el SDK no registra instalaciones, y los callbacks delegados `onInstallationDetailsSuccess` y `onInstallationDetailsFail` nunca se ejecutan. `getCurrentInstallationStatus()` devuelve `.notAvailable`. Si usas Adapty Attribution, actívalo al inicializar el SDK: ```diff showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") + .with(adaptyAttributionEnabled: true) ``` Si no usas Adapty Attribution, no es necesario hacer ningún cambio. ## APIs de atribución externa renombradas \{#renamed-external-attribution-apis\} ### updateAttribution(_:source:) → updateExternalAttribution(_:provider:) El método que pasa datos de atribución desde un proveedor externo (Adjust, AppsFlyer, Branch, Tenjin o uno personalizado) ha sido renombrado, y su parámetro `source` pasa a llamarse `provider`: ```diff showLineNumbers - try await Adapty.updateAttribution(attribution, source: .adjust) + try await Adapty.updateExternalAttribution(attribution, provider: .adjust) ``` ### La sobrecarga de cadena JSON ha sido eliminada \{#the-json-string-overload-is-removed\} En la versión 4.0 se aceptaban datos de atribución como un diccionario `[AnyHashable: Any]` o como una `String` JSON. En la versión 4.1, solo la sobrecarga de diccionario es pública: la sobrecarga de cadena JSON está reservada para los SDKs multiplataforma de Adapty. Deserializa el JSON antes de pasarlo: ```diff showLineNumbers - try await Adapty.updateAttribution(attributionJson, source: .adjust) + guard let attribution = try JSONSerialization.jsonObject( + with: Data(attributionJson.utf8) + ) as? [AnyHashable: Any] else { return } + try await Adapty.updateExternalAttribution(attribution, provider: .adjust) ``` ### AdaptyAttributionSource → AdaptyExternalAttributionProvider El tipo ha sido renombrado. Los proveedores predefinidos mantienen los mismos nombres: `.appleAds`, `.adjust`, `.appsflyer`, `.branch`, `.tenjin`. Sigue siendo compatible con `ExpressibleByStringLiteral`, por lo que los argumentos como literales de cadena siguen compilando. Si guardas el proveedor en una variable de tipo `String`, envuélvelo así: `AdaptyExternalAttributionProvider(rawValue: yourProvider)`. La versión 4.1 también añade el valor predefinido `.custom` para proveedores con los que Adapty no se integra directamente. En la 4.0, ese mismo valor solo estaba disponible como literal de cadena `"custom"`. ### AdaptyProfile.appliedAttributionSources → appliedExternalAttributionProviders La propiedad del perfil que lista los proveedores de atribución aplicados al perfil ha sido renombrada. Su tipo de elemento cambia en consecuencia: ```diff showLineNumbers - if profile.appliedAttributionSources.contains(.appleAds) { + if profile.appliedExternalAttributionProviders.contains(.appleAds) { // Apple Ads attribution has been applied } ``` ## AdaptySubscriptionOfferType ahora es una struct \{#adaptysubscriptionoffertype-is-now-a-struct\} `AdaptySubscriptionOfferType` — el tipo de `AdaptySubscriptionOffer.offerType` — cambia de enum a una struct `RawRepresentable`, de modo que el backend puede introducir nuevos tipos de oferta sin actualizar el SDK: ```diff showLineNumbers - public enum AdaptySubscriptionOfferType: String, Sendable { ... } + public struct AdaptySubscriptionOfferType: Sendable, RawRepresentable, Equatable, Hashable { ... } ``` Esto afecta a tu código de tres maneras: - **Las sentencias `switch` exhaustivas ya no compilan.** Un struct no tiene un conjunto fijo de casos, por lo que el compilador no puede garantizar que un switch sea exhaustivo. Añade una rama `default`: ```diff showLineNumbers switch offer.offerType { case .introductory: // ... case .promotional: // ... case .winBack: // ... + default: // handle offer types added later } ``` - **`.code` se ha eliminado.** Los códigos de oferta ya no se informan a través de `AdaptySubscriptionOffer.offerType`. Elimina cualquier rama `case .code`. Consulta [Canjear códigos de oferta en iOS](making-purchases#redeem-offer-codes-in-ios) para gestionar los códigos de oferta. - **La conformidad con `Codable` se ha eliminado.** Si codificabas o decodificabas `AdaptySubscriptionOfferType` directamente, persiste `offerType.rawValue` (un `String`) en su lugar y reconstruye el valor con `AdaptySubscriptionOfferType(rawValue:)`. Las comparaciones con los valores predefinidos no cambian: `.introductory`, `.promotional` y `.winBack` siguen funcionando en comprobaciones `==` y como patrones `case`. ## Archivos de respaldo \{#fallback-files\} El formato del [archivo de respaldo](fallback-flows) cambió en el SDK 4.1. Descarga el archivo de nuevo desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** e inclúyelo en tu app, aunque ya hubieras descargado uno para la versión 4.0. :::warning Este paso no genera ningún error de compilación. Si lo omites, el SDK rechazará el archivo desactualizado y todos los placements perderán su paywall de respaldo. ::: ## Retorno de las compras in-app promocionadas en el App Store \{#app-store-promoted-in-app-purchases-return\} SDK 4.1 recupera el hook para las compras in-app promocionadas en el App Store, que [4.0 eliminó](migration-to-ios-sdk-v4#app-store-promoted-in-app-purchases-temporarily-removed). Con 4.0, la compra se completaba igualmente y Adapty registraba la transacción; simplemente, tu app no podía interceptarla. 4.1 mantiene ese comportamiento por defecto, así que esto es una nueva funcionalidad y no un paso de migración. Si migras desde la versión 3.x y usabas `shouldAddStorePayment(for:)` con `AdaptyDeferredProduct`, adopta el nuevo método de delegado `didReceivePromotedPurchase(_:)` con `AdaptyPromotedProduct`. A diferencia de `shouldAddStorePayment`, el nuevo método no devuelve un valor: si no lo implementas, el SDK inicia la compra de inmediato; para diferirla, impleméntalo, guarda el producto y pásalo a `makePurchase` más adelante. Consulta [Compras in-app desde el App Store](making-purchases#in-app-purchases-from-the-app-store). :::warning El nuevo mecanismo está basado en StoreKit 2 y requiere **iOS 16.4** o posterior. El método `shouldAddStorePayment` de la versión 3.x funcionaba en versiones anteriores de iOS. Por debajo de iOS 16.4, `didReceivePromotedPurchase` no se ejecuta nunca y las compras promocionadas se completan sin pasar por tu app. ::: --- # File: migration-to-ios-sdk-v4 --- --- title: "Migrar el SDK de Adapty iOS a v4.0" description: "Migra al SDK de Adapty iOS v4.0 reemplazando las APIs de paywall por APIs de flow, que renderizan flows además de los paywalls del builder anterior." --- El SDK de Adapty iOS 4.0 introduce los flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan con flows y siguen siendo compatibles con los paywalls del builder anterior — 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:)` | | `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\} Adapty iOS SDK 4.0 elimina el soporte para CocoaPods. Instala el SDK con [Swift Package Manager](sdk-installation-ios#install-adapty-sdk). La v4 también requiere **Xcode 26** o posterior, ya que su manifiesto de paquete declara Swift tools 6.2. 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 a través de **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.2", 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`. ## Gestión de compras promocionadas en App Store eliminada temporalmente \{#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 la API para gestionar 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. Estas compras siguen completándose y Adapty registra la transacción, pero la versión 4.0 no ofrece ninguna forma de interceptarlas ni diferirlas. :::warning Esta eliminación solo aplica a la versión 4.0 — el manejo regresa en el SDK 4.1 con una nueva API. Si tu app intercepta compras in-app promocionadas, migra de 3.x directamente a 4.1. Consulta [Compras in-app desde el App Store](making-purchases#in-app-purchases-from-the-app-store). ::: ## 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`: `AdaptyFlow` también expone `paywalls` — las variaciones de paywall del flow, como valores `AdaptyFlowPaywall`. Los métodos del paywall web reciben uno de estos valores. ```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) ``` ### Archivos de respaldo \{#fallback-files\} El formato del archivo de respaldo [cambió en SDK v4](fallback-flows). Descarga el nuevo archivo desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** y agrégalo a tu app. ## 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 embudo y pruebas A/B existentes siguen 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 cuando se muestran flows o paywalls renderizados por Adapty — 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 { YourContentView() - .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)`. :::note SDK 4.1 cambia el nombre de este método a `updateExternalAttribution(_:provider:)` y el tipo a `AdaptyExternalAttributionProvider`. Los nombres antiguos se eliminan en la versión 4.1 sin deprecación previa. Si migras directamente a la versión 4.1, consulta [Migrar a v4.1](migration-to-ios-sdk-41). ::: ### 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. ## 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 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#configure-your-app-code). ```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-10-30T13:00:39.615Z_ _Successfully processed: 58/58 files_ # KMP - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-10-30T13:00:39.617Z Total files: 52 --- # File: kmp-sdk-overview --- --- title: "Descripción general del SDK de Kotlin Multiplatform" description: "Aprende sobre el SDK de Kotlin Multiplatform de Adapty y sus características principales." --- [![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 hacer que las compras in-app sean pan comido 🚀 Hemos creado el SDK de Adapty para Kotlin Multiplatform 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 sin configuración adicional - Crea y prueba paywalls sin publicar actualizaciones de 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 suscripción del usuario siempre actualizado en todas las 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 entrar en el código, necesitarás integrar Adapty con Google Play Console y configurar los productos en el dashboard. Consulta nuestra [guía de inicio rápido](quickstart) para configurarlo todo primero. ::: ## Primeros pasos \{#get-started\} For a fully automated integration, use the [adapty-integration skill](https://github.com/adaptyteam/adapty-skills): 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. [Instala y configura el SDK](sdk-installation-kotlin-multiplatform): Añade el SDK como dependencia a tu proyecto y actívalo en el código. 2. [Activa las compras con flows y paywalls](kmp-quickstart-paywalls): Configura el flujo de compra para que los usuarios puedan adquirir productos. Si prefieres construir tu propia UI, consulta [Implementar paywalls personalizados](kmp-quickstart-manual). 3. [Comprueba 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. [Identifica a los usuarios (opcional)](kmp-quickstart-identify): Asocia a los usuarios con sus perfiles de Adapty para que sus datos se almacenen de forma coherente en todos los dispositivos. ### Véalo en acción \{#see-it-in-action\} ¿Quieres ver cómo encaja todo? Te lo ponemos fácil: - **App de ejemplo**: Consulta nuestro [ejemplo completo](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example) con la configuración al completo - **Tutorial en vídeo**: Sigue nuestra implementación paso a paso en el vídeo de abajo ## 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. **Flows y paywalls** - Productos agrupados con configuración, vinculados a un placement. Dos formas de crearlos, comparadas en [Flows y paywalls](flows-and-paywalls): - **[Flow & Paywall Builder](adapty-flow-builder)** - Interfaz visual sin código. Adapty renderiza la UI y gestiona la compra por ti. - **[Paywalls personalizados](paywalls)** - Sin configuración visual; tú construyes la UI en tu propio código y llamas a `makePurchase` manualmente. Consulta [Implementar paywalls personalizados](kmp-quickstart-manual). En el código del SDK, ambos se recuperan 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 apps de 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 renderiza flows y paywalls del builder antiguo a través de la capa de renderizado de Compose Multiplatform (`view.present()`). Si tu proyecto no usa Compose Multiplatform, puedes usar [`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 app móvil? Echa un vistazo a nuestra [app 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 completa de implementación, también puedes ver el vídeo: ## Requisitos \{#requirements\} El SDK de Adapty para Kotlin Multiplatform es compatible con Xcode 16.2 y versiones posteriores. :::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. ::: ## Dependencias \{#dependencies\} El SDK de Adapty funciona con las siguientes versiones de la Google Play Billing Library en Android: | Versión del SDK de Adapty | Versión de la Billing Library | |---|---| | 3.17.0 y posteriores | v8 | | 3.15.0–3.16.2 | v7 por defecto, v8 si otra dependencia la eleva | :::note La compatibilidad con una versión de la Billing Library no significa que Adapty sea compatible con todas las funciones que Google introdujo en ella. Antes de adoptar una nueva función de facturación de Google Play, consulta [Producto en Play Store](android-products). ::: ## Instala el SDK de Adapty con Gradle \{#install-adapty-sdk-via-gradle\} La instalación del SDK de Adapty con Gradle es necesaria tanto para apps Android como 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 referéncialas en `build.gradle.kts` [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-KMP.svg?style=flat&logo=kotlin)](https://github.com/adaptyteam/AdaptySDK-KMP/releases) Siempre recomendamos instalar la versión más reciente disponible del SDK — incluye las últimas correcciones de estabilidad y mejoras. <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="Versions library" 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 el [Flow & Paywall Builder](adapty-flow-builder), primero [activa el módulo AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) más abajo y luego sigue la [guía de inicio rápido](kmp-quickstart-paywalls). - Si construyes tu propia interfaz de paywall, consulta la [guía de inicio rápido para paywalls personalizados](kmp-quickstart-manual). ## Activa el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyu-i-module-of-adapty-sdk\} Si planeas activar el módulo **AdaptyUI** para usar el [Flow & Paywall Builder](adapty-flow-builder), 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() ``` ### Habilitar la atribución de Adapty \{#enable-adapty-attribution\} :::info Este parámetro está disponible a partir de la versión 4.1 del SDK. ::: Si usas [Atribución de Adapty](user-acquisition), establece `withAdaptyAttributionEnabled` en `true` al activar el SDK. El valor predeterminado es `false`: sin este parámetro, el SDK no registra instalaciones ni entrega los detalles de instalación a tu app. En versiones del SDK anteriores a la 4.1, la atribución de Adapty se habilita automáticamente. ```kotlin showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withAdaptyAttributionEnabled(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 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 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 (que normalmente se encuentra 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 espacio de nombres `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 finales e indique al fusionador de manifiestos que reemplace los valores de las bibliotecas: ```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 usa diferentes 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 ejemplo. Reemplaza o añade reglas para cualquier otro SDK que estés usando 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 heredado 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 & Paywall Builder en Kotlin Multiplatform SDK" description: "Guía rápida para habilitar compras in-app con Adapty Flow & Paywall 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: - [**Productos**](product) – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Flows y paywalls**](adapty-flow-builder) – pantallas que presentan productos a los usuarios, creadas en el Flow & Paywall Builder sin código. El SDK los recupera mediante `getFlow`. Si prefieres construir la interfaz en tu propio código, consulta [Implementar paywalls personalizados](kmp-quickstart-manual). - [**Placements**](placements) – dónde y cuándo mostrar flows y paywalls en tu app (como `main`, `onboarding`, `settings`). Los vinculas 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 opciones 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 usarla | |---|---|---| | Adapty Flow & Paywall Builder | ✅ Fácil | [Creas un flow o paywall completo y listo para compras en el editor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona toda la lógica de compra, validación de recibos y gestión de suscripciones. | | Paywalls personalizados | 🟡 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 o paywall creado en el Adapty Flow & Paywall Builder.** Si prefieres crear la UI del paywall tú mismo, consulta [Implementar paywalls personalizados](kmp-quickstart-manual). ::: Para mostrar un flow o paywall creado en el Adapty Flow & Paywall Builder, en el código de tu app solo necesitas: 1. **Obtén el flow o paywall**: Obténlo desde Adapty. 2. **Muéstralo y deja que Adapty gestione las compras**: Muestra la vista en tu app. 3. **Gestiona las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abre enlaces o cierra el paywall cuando los usuarios pulsen botones. ## Antes de empezar 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 o paywall](paywall-builder-templates) y [añade productos](paywall-product-block). 4. [Crea un placement y añade tu flow o paywall](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. Obtén el flow o el paywall \{#1-get-the-flow-or-paywall\} Tus flows y paywalls están asociados a placements configurados en el dashboard. Los placements te permiten mostrar diferentes flows o paywalls para distintas audiencias o ejecutar [pruebas A/B](ab-tests). Para obtener un flow o paywall creado en el Adapty Flow & Paywall Builder, debes: 1. Obtener el objeto `flow` mediante el ID del [placement](placements) usando el método `getFlow`. 2. Crea la vista del flow con el método `createFlowView`. La vista contiene los elementos de UI y el estilo necesarios para mostrarla. Si el flow no tiene ninguna vista configurada, `createFlowView` devuelve un error; trátalo en `onError`. :::important Para obtener la vista, [publica el flow o el paywall](builder-save-publish). Un flow o paywall con ediciones sin publicar tiene el estado **Dirty**, y su placement sigue sirviendo la última versión publicada. ::: ```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 o el paywall \{#2-display-the-flow-or-paywall\} 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 presentarla en la pantalla del dispositivo. Cada vista solo se puede usar una vez: después de llamar a `dismiss()`, llama a `createFlowView` de nuevo para mostrar el flow otra vez. :::tip Para más detalles sobre cómo mostrar un flow o paywall, consulta nuestra [guía](kmp-present-paywalls). ::: ## 3. Gestionar acciones de botones \{#3-handle-button-actions\} Cuando los usuarios pulsan botones en el flow o paywall, el SDK de Kotlin Multiplatform gestiona automáticamente las compras, la restauración, el cierre de pantalla 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 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 fuera 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 flow o 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 flow. 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 the flow using the KMP SDK Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> if (flow.hasViewConfiguration) { AdaptyUI.createFlowView(flow = flow) .onSuccess { view -> view.present() } .onError { error -> showError("Unable to display the paywall") } } else { // Handle custom paywall or show custom UI handleRemoteConfigPaywall(flow) } } .onError { error -> // Handle flow 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 la skill de integración del SDK" description: "Usa la skill adapty-integration para integrar el SDK de Adapty en tu app Kotlin Multiplatform de principio a fin con tu herramienta de codificación con IA." --- :::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. ::: El skill `adapty-integration` automatiza la integración de Adapty de principio a fin: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación relevante de Adapty en cada etapa. Funciona en cualquier agente de programación que soporte skills: Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI y otros. El skill forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener los nuevos skills a medida que se publiquen, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Para iniciar la integración, pide a tu agente: ``` Integrate Adapty into my app ``` El skill hace algunas preguntas de configuración y luego te guía a través de 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-integration skill](https://github.com/adaptyteam/adapty-skills): 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. El CLI de skills no actualiza el skill por sí solo. Para obtener la última versión, ejecuta `npx skills update`. ### 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 flow o un 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 la cadena que pasas a `Adapty.getFlow("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 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\} Estos elementos 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) - **Flows y placements adicionales**: Añade más llamadas `getFlow` 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 Flow & Paywall Builder**](adapty-flow-builder): Creas flows en el editor sin código de Adapty y el SDK los renderiza automáticamente. - [**Paywalls personalizadas**](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 análisis 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 gestionas las compras. Prueba cada compra en el sandbox a medida que avanzas, 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 & Paywall Builder" default> **Guías:** - [Activar compras con Flow & Paywall Builder (inicio rápido)](kmp-quickstart-paywalls) - [Obtener flows y su configuración](kmp-get-pb-paywalls) - [Mostrar flows](kmp-present-paywalls) - [Gestionar eventos de flow](kmp-handling-events) - [Responder a acciones de botones](kmp-handle-paywall-actions) 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 tocar un producto se activa el diálogo de compra en sandbox. - **Problema común:** Flow vacío o error en `getFlow` → verifica que el ID del placement coincida exactamente con el dashboard y que el placement tenga una audiencia asignada. ::: </TabItem> <TabItem value="manual" label="Custom paywalls"> **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](kmp-quickstart-manual) - [Obtener paywalls y productos personalizados](fetch-paywalls-and-products-kmp) - [Presentar paywalls personalizados](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) ``` 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[Checkpoint] - **Esperado:** Después de una compra en sandbox usando tu flow de compra existente, la transacción aparece en el **Event Feed** del Adapty Dashboard. - **Posible problema:** 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, 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 los editores no-code de Adapty en tu aplicación Kotlin Multiplatform." --- ## Mostrar paywalls \{#display-paywalls\} ### Adapty Flow & Paywall Builder <CustomDocCardList ids={['kmp-get-pb-paywalls', 'kmp-present-paywalls', 'kmp-handling-events', 'kmp-handle-paywall-actions']} /> :::tip Para comenzar rápidamente con los flows y paywalls de Adapty, consulta nuestra [guía de inicio rápido](kmp-quickstart-paywalls). ::: ### Implementa paywalls personalizados \{#implement-custom-paywalls\} <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 personalizados y gestionar compras, consulta [Implementa paywalls personalizados](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 app de Kotlin Multiplatform." --- <SDKv4> <MethodPromo method="getFlow" /> Después de [diseñar tu flow](adapty-flow-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. :::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 \{#before-you-start\} Necesitas: - **Productos en el Adapty Dashboard**: [Crea tus productos](create-product). - **Un flow con productos**: Créalo en el [Flow & Paywall Builder](adapty-flow-builder) y [asigna productos a él](paywall-product-block). - **Un placement**: [Crea un placement](create-placement) y asigna tu flow a él. - **SDK instalado**: Consulta la [guía de instalación del SDK de Kotlin Multiplatform](sdk-installation-kotlin-multiplatform). ## Obtener un flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall en el builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Un flow o paywall así ya incluye tanto lo que debe mostrarse como la forma en que debe hacerlo. 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 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>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud falla. 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 inestable, considera `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee la caché primero y solo consulta el servidor cuando no hay nada en caché. Es posible que los usuarios no obtengan los datos absolutamente 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>Una tercera política, `AdaptyPaywallFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee la caché primero mientras la copia almacenada sea más reciente que `maxAgeMillis`, y consulta el servidor cuando sea más antigua.</p><p></p><p>Ten en cuenta que la caché se mantiene al reiniciar la app y solo se borra al reinstalarla o mediante una limpieza manual.</p><p></p><p>El SDK de Adapty almacena los flows y 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 obtenerlos 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 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 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 superar ligeramente el tiempo de espera especificado en `loadTimeout`, ya que la operación puede incluir distintas 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\} Tras obtener el flow o 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 verificar: si el placement fue diseñado en el **Flow & Paywall Builder** (un flow) o en el **old Paywall Builder** (un paywall), `createFlowView` devuelve la vista lista para presentar. Si el placement es un paywall personalizado sin interfaz del Builder, `createFlowView` devuelve un `AdaptyResult.Error` — [renderízalo tú mismo](present-remote-config-paywalls-kmp). :::important Asegúrate de [publicar el flow](builder-save-publish). Un flow con ediciones sin publicar tiene el estado **Dirty**, y su placement sigue mostrando la última versión publicada. ::: ```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`. | | **locale** | opcional | El identificador de la [localización del flow](add-paywall-locale-in-adapty-paywall-builder) con la que se renderizará la vista — por ejemplo, `en` o `pt-br`. Si se omite, la vista se renderiza en `en`, o en la localización predeterminada del flow cuando este no tiene `en`. Consulta [Localizaciones y códigos de idioma](kmp-localizations-and-locale-codes). | | **customLayoutId** | <p>opcional</p><p>por defecto: `null`</p><p>SDK 4.1+</p> | El ID personalizado de un layout definido en el Flow Builder. Pásalo para renderizar ese layout específico en lugar del que el SDK selecciona automáticamente según el tipo de dispositivo y el tamaño de pantalla. Si ningún layout coincide con el ID, el flow se carga sin configuración de vista. | | **loadTimeout** | opcional | Este valor limita el tiempo de espera de este método. Si se alcanza el timeout, se devolverán datos en caché o el fallback local. Ten en cuenta que en casos excepcionales este método puede agotar el tiempo ligeramente más tarde de lo indicado en `loadTimeout`, ya que la operación puede estar compuesta por distintas solicitudes internamente. Puedes usar funciones de extensión como `5.seconds` de `kotlin.time.Duration.Companion`. | | **preloadProducts** | opcional | Ponlo en `true` para precargar los 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. | | **customTags** | opcional | Los valores de tu app para las [etiquetas personalizadas](flow-custom-tags) del flow, en un mapa indexado por el nombre completo de cada etiqueta. Para más detalles, consulta [Pasar datos a flows](kmp-flow-custom-tags). | | **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 específicos de compra, como ofertas personalizadas o parámetros de actualización de suscripción para productos individuales en el flow o paywall. | :::note Si usas varios idiomas, aprende cómo añadir una [localización de flow](add-paywall-locale-in-adapty-paywall-builder). ::: Una vez cargado, [presenta el flow o 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** | por defecto: `AdaptyPaywallFetchPolicy.Default` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. Es posible 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 regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Una tercera política, `AdaptyPaywallFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché tenga menos de `maxAgeMillis`, y va al servidor cuando es más antigua.</p><p></p><p>Ten en cuenta que la caché se mantiene al reiniciar la aplicación 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 o 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, 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. - Mostrar contenido multimedia incluido en la app, para que la primera pantalla se muestre sin necesidad de descarga. Consulta [Mostrar contenido multimedia de la primera pantalla desde el bundle de la app](kmp-optimize-paywall-fetching#show-first-screen-media-from-the-app-bundle). Aquí tienes un ejemplo de cómo proporcionar assets personalizados mediante un mapa: :::info El SDK de Kotlin Multiplatform solo admite activos locales. Para contenido remoto, debes descargar y almacenar en caché los activos localmente antes de usarlos en activos 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" ) ) // 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 falla al 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 antiguo Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu aplicación 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. Por favor, ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Si implementas paywalls personalizados, consulta el tema [Obtener paywalls y productos para paywalls personalizados 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 app 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** | 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>predeterminado: `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 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** | predeterminado: `AdaptyPaywallFetchPolicy.Default` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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 solicitudes de red.</p><p></p><p>Una tercera política, `AdaptyPaywallFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché sea más reciente que `maxAgeMillis`, y va al servidor cuando es más antigua.</p><p></p><p>Ten en cuenta que la caché se mantiene 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é 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 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** | 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>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><p>Para Kotlin Multiplatform: 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://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 del 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 orientará 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 personalizado](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 admites varios idiomas, añade una localización a tu paywall. Para conocer los códigos que debes usar, consulta [Localizaciones y códigos de idioma](kmp-localizations-and-locale-codes). ::: 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** | requerido | 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 subtags separadas por el carácter menos (**-**). La primera subtag 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** | predeterminado: `AdaptyPaywallFetchPolicy.Default` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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>Una tercera política, `AdaptyPaywallFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché sea más reciente que `maxAgeMillis`, y va al servidor una vez que sea más antigua.</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 con el antiguo Paywall 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 ya incluye tanto lo que debe mostrarse como la forma en que debe mostrarse. :::warning Esta guía cubre los flows y paywalls renderizados por Adapty. El proceso es diferente para los paywalls personalizados y el [modo Observer](observer-vs-full-mode). - Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](present-remote-config-paywalls-kmp). - Para presentar flows en el **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 para 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\} :::link Artículo principal: [Pasar datos a flows](kmp-flow-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!" Aquí tienes algunas formas de usar las etiquetas personalizadas: - Mostrar el nombre o el correo del usuario en el flow o el paywall. - Mostrar el día de la semana actual para impulsar 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 permiten crear un flow flexible que se adapta a distintas situaciones, haciendo que la interfaz de tu app sea más personalizada y atractiva. Pasa los valores de tu app al parámetro `customTags` de `AdaptyUI.createFlowView` o `AdaptyUI.createNativeFlowView`, como un `Map<String, String>`: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTags = mapOf( "username" to "DragonSlayer42", "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 "DragonSlayer42", "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 "DragonSlayer42", "day_of_week" to "Thursday" ) val nativeView = AdaptyUI.createNativeFlowView( flow = flow, observer = myFlowObserver, customTags = customTags, ) ``` </TabItem> </Tabs> Si tu app no pasa ningún valor para una etiqueta personalizada, los usuarios verán [el valor inicial de la etiqueta](kmp-flow-custom-tags#what-users-see-without-a-value) en su lugar. ## 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 contiene tanto lo que se debe mostrar como la forma en que debe mostrarse. :::warning Esta guía es solo para **paywalls creados en el builder antiguo**. El proceso para presentar paywalls es diferente para los paywalls personalizados y el [modo Observer](observer-vs-full-mode). Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](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 creando flows en el Adapty Flow & Paywall Builder, 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.** El resto de las acciones de los botones, como cerrar paywalls o abrir enlaces, requieren implementar las respuestas correspondientes en el código de la aplicación. ::: Si estás creando paywalls con el Paywall Builder de Adapty, es fundamental configurar los botones correctamente: 1. En el antiguo Paywall Builder, añade un botón y asígnale una acción predefinida o un ID de acción personalizada. 2. Escribe código en tu aplicación para gestionar cada acción que hayas asignado. Esta guía muestra cómo gestionar acciones personalizadas y predefinidas 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 que renderiza Adapty 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. A continuación aprenderás cómo responder a estos eventos. 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 una situación normal, estos errores no deberían ocurrir, así que si te encuentras con uno, por favor, háznoslo saber. #### Eventos de análisis \{#analytics-events\} ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String ) { } ``` Kotlin Multiplatform te entrega los parámetros del evento como una cadena JSON sin procesar, así que decodifica `paramsJsonString` antes de leer cualquiera de ellos. Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. Consulta [Rastrear vistas de pantallas del flow](kmp-flow-screen-views) para saber qué hacer con ellos. Un flow también registra los valores que los usuarios escriben y seleccionan. Consulta [Procesar datos de flows](kmp-flow-input) para leerlos. ### 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 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 solo para **paywalls creados en el builder antiguo**. ::: 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-flow-screen-views --- --- title: "Rastrear vistas de pantalla de flow en el SDK de Kotlin Multiplatform" description: "Recibe un evento cada vez que un usuario abre una pantalla de flow en tu app de Kotlin Multiplatform, y reenvíalo a tu analítica de producto." --- Cada vez que un usuario abre una pantalla en un [flow](adapty-flow-builder), el SDK reporta un evento `flow_screen_showed` a tu app. Reenvíalo a Amplitude, Mixpanel o cualquier analítica de producto que uses, para analizar las pantallas del flow junto con el resto de los eventos de tu app. :::tip Para ver cuántos usuarios llegan a cada pantalla y en cuál abandonan, no necesitas escribir ni una línea de código: el [embudo del flow](flow-funnel) ya lo muestra en el Adapty Dashboard. Usa esta guía para construir el mismo embudo en tu propia herramienta de analítica, o para combinar las pantallas del flow con eventos que Adapty no conoce. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty Kotlin Multiplatform SDK v4 o posterior**: El callback de análisis de flows no existe en versiones anteriores. - **Una versión del flow publicada después de que esta función esté disponible**: Adapty genera los eventos de pantalla al publicar un flow, por lo que un flow publicado antes de este momento no reportará nada. Si no llegan eventos a tu aplicación, publica una nueva versión del flow e inténtalo de nuevo. ## Qué recibes \{#what-you-receive\} Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: Kotlin Multiplatform entrega estos parámetros como una cadena JSON sin procesar, así que decodifica `paramsJsonString` antes de leer cualquiera de ellos. Las visualizaciones de pantalla llegan al mismo callback de análisis que todos los demás eventos de flow. Consulta [Gestionar eventos de flow y paywall](kmp-handling-events) para ver dónde registrarlo. ## Reenvía cada vista de pantalla a tu análisis \{#forward-every-screen-view-to-your-analytics\} Comprueba el nombre del evento antes de leer cualquier otra cosa, porque un flow reporta otros eventos a través del mismo callback. ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String, ) { if (name != "flow_screen_showed") return val params = Json.parseToJsonElement(paramsJsonString).jsonObject val screenId = params["instanceId"]?.jsonPrimitive?.contentOrNull val screenOrder = params["screen_order"]?.jsonPrimitive?.intOrNull // Send screenId and screenOrder to your analytics provider here. } ``` ## Ejecuta código cuando el usuario llega al final \{#run-code-when-a-user-reaches-the-end\} `is_last_screen` es `true` en una pantalla desde la que el flow no puede avanzar. Úsalo para desbloquear contenido, marcar el onboarding como completado o enviar un evento de finalización propio, sin necesidad de añadir una acción de botón en el builder. ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String, ) { if (name != "flow_screen_showed") return val params = Json.parseToJsonElement(paramsJsonString).jsonObject if (params["is_last_screen"]?.jsonPrimitive?.booleanOrNull != true) return // The user reached the end of the flow. } ``` Llegar a la última pantalla no es lo mismo que cerrar el flow. Un usuario puede ver la pantalla final y descartar el flow sin hacer nada, y un flow con ramificaciones puede terminar en varias pantallas diferentes. Para saber que el flow en sí ha desaparecido, usa `flowViewDidDisappear` en su lugar. --- # File: kmp-flow-input --- --- title: "Procesar datos de flows en el SDK de Kotlin Multiplatform" description: "Guarda y utiliza los datos que introducen tus usuarios en los flows de tu app de Kotlin Multiplatform con el SDK de Adapty." --- Cuando un usuario escribe en un campo de texto, responde un cuestionario o activa un interruptor en un [flow](adapty-flow-builder), el SDK pasa el valor a tu app a través de su callback de analíticas. Las apps suelen utilizar esos datos para: - **Registrar usuarios en su propio backend**: Toma el correo electrónico y el nombre que el usuario introdujo en tu flow de onboarding y crea su cuenta cuando el flow se cierre. - **Guardar respuestas y preferencias**: Registra lo que el usuario eligió para que tu app pueda usarlo más adelante; por ejemplo, [guárdalo en su perfil de Adapty](kmp-setting-user-attributes) como atributos personalizados. - **Personalizar flows futuros**: Guarda las respuestas del cuestionario como atributos personalizados y luego apunta a un placement posterior para que cada segmento vea un flow distinto o un paywall diferente dentro de él. - **Mostrar valores calculados a partir de las respuestas**: Por ejemplo, calcula el IMC a partir del peso y la altura del usuario y [pásalo a una etiqueta personalizada en el siguiente flow](kmp-flow-custom-tags#example-show-a-bmi-from-onboarding-answers). - **Alimentar plataformas de analítica de terceros**: Reenvía las respuestas a Amplitude, Mixpanel o cualquier otra herramienta de analítica de producto que utilices. Los inputs y los grupos seleccionables informan sus valores automáticamente. Para distinguirlos en tu código, asigna a cada input un **Element ID** y a cada grupo seleccionable un **Group ID** en el builder. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: ## Antes de comenzar \{#before-you-start\} Necesitas: - **Adapty SDK v4 o posterior**: Los callbacks de flow no existen en versiones anteriores. - **Un flow creado en el [Flow & Paywall Builder](adapty-flow-builder)**: Solo los flows reportan valores de entrada a través de este callback. - **Una versión de flow publicada recientemente**: Un flow solo reporta valores de entrada si lo publicaste después de que esta función estuviera disponible. Si no llega nada a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Recibir valores de entrada \{#receive-input-values\} Los valores de entrada llegan al mismo callback que el resto de eventos analíticos de un flow, bajo el nombre de evento `flow_user_input`. Sobreescribe `flowViewDidReceiveAnalyticEvent` en el observador que registras con `AdaptyUI.setFlowsEventsObserver`: ```kotlin showLineNumbers title="Kotlin" AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String, ) { handleFlowInput(name, paramsJsonString) } }) ``` El callback `flowViewDidReceiveAnalyticEvent` entrega todos los eventos de análisis de un flow, incluidas las [vistas de pantalla](kmp-flow-screen-views). Los parámetros del evento llegan como una cadena JSON en `paramsJsonString`; decodifícala una vez y lee los campos. Para filtrar los eventos de entrada del usuario, compara `name` con `flow_user_input`. El parámetro que almacena el valor depende de `element_type`: | `element_type` | Parámetro que almacena el valor | Tipo una vez decodificado | |:---------------|:--------------------------------|:--------------------------| | `text_input`, `email_input`, `number_input`, `phone_input` | `value` | String | | `date_picker`, `time_picker`, `date_time_picker` | `value` | Number, tiempo Unix en milisegundos. Léelo como double, ya que Android serializa los números de esa manera. | | `single_choice`, `multi_choice` | `item_ids`, `item_titles` | Array de strings | | `toggle` | `value` | Boolean | La sección [Qué recibes](#what-you-receive) enumera todos los parámetros y lo que reporta cada elemento del builder. Para confirmar que el callback se ejecuta, interactúa con el input en una versión de prueba de tu propia app. Si tu callback handler no recibe ningún evento, revisa los [prerequisitos](#before-you-start). Asegúrate de que el flow fue publicado después de que esta función estuviera disponible. ## Cuando tu app recibe el input \{#when-your-app-receives-the-input\} :::important El valor predeterminado de entrada o selección nunca llega a tu aplicación a través de este callback. Si un usuario acepta la opción marcada como **Set as default** y continúa, no se dispara ningún evento. No interpretes la ausencia del evento como "sin respuesta": el usuario simplemente dejó el valor predeterminado tal como estaba. ::: **Los siguientes elementos disparan este evento:** - Campos de texto, email, número y teléfono - Selectores de fecha, hora y fecha-hora - Grupos seleccionables de opción única y múltiple, y toggles **Los siguientes no lo hacen:** - Campos de contraseña, selecciones de productos y cambios de pestaña - Inputs dentro de un elemento Header, que se comparte entre pantallas - Grupos seleccionables con IDs de opción duplicados o faltantes, o con un ID de grupo reutilizado en otra pantalla. Ese tipo de grupo no envía nada en absoluto, en lugar de enviar parte de la respuesta. **El evento se dispara cuando:** - Un campo pierde el foco. Un campo vaciado reporta una cadena vacía; un campo que el usuario nunca editó no reporta nada. Un marcador de posición no es un valor. Si el usuario vuelve al campo, lo edita y lo abandona de nuevo, se genera un segundo evento. - El usuario cierra un selector tras elegir un nuevo valor. Cerrarlo sin cambios no reporta nada. - El usuario pulsa una opción o un toggle. Un evento de opción múltiple lista todas las opciones seleccionadas, por lo que deseleccionar la última envía dos arrays vacíos. **El evento no se dispara cuando:** - El usuario escribe. No hay flujo de pulsaciones de teclas, solo el valor que tiene el campo cuando pierde el foco. - El usuario envía o cierra el flow. Un valor que aún se está editando en ese momento puede perderse; la sección de [limitaciones de entrega](#delivery-and-limitations) explica cómo diseñar la última pantalla teniendo esto en cuenta. - Se establece un valor sin interacción del usuario. Una opción marcada como **Set as default** se preselecciona al abrirse la pantalla, y una acción **Set Variable** puede seleccionar una opción o rellenar un input a partir de otra interacción. Ninguna de las dos envía un evento; un input prerrellenado solo se reporta una vez que el usuario lo edita. ## Qué recibes \{#what-you-receive\} El callback entrega dos eventos. Filtra por `name` para mostrar únicamente los eventos `flow_user_input`. El payload JSON de respuesta tiene este aspecto: ```json { "name": "flow_user_input", "instanceId": "scr_registration", "isBackendEvent": false, "isCustomerEvent": true, "element_id": "email", "element_type": "email_input", "value": "jane@example.com" } ``` | Parámetro | Descripción | |:------------------|:------------| | `name` | `flow_user_input` para eventos de entrada, `flow_screen_showed` para vistas de pantalla. | | `instanceId` | El ID de la pantalla del input. Los IDs de elemento son únicos dentro de una pantalla, no en todo el flow. Si tu flow tiene inputs en más de una pantalla, combina `instanceId` con `element_id` al filtrar eventos. | | `element_id` | El **Element ID** del [input](builder-inputs-and-forms), o el **Group ID** del [grupo seleccionable](flow-selectable-elements). | | `element_type` | El tipo de elemento que envió el evento. Determina cuál de los parámetros siguientes contiene el valor del input. | | `value` | **Solo campos de texto, selectores y toggles.** El valor del input: una cadena para campos de texto, un entero para selectores, un booleano para toggles. | | `item_ids` | **Solo grupos seleccionables de elección única y múltiple.** Los **Element IDs** de las opciones seleccionadas, en el orden en que aparecen en el builder. Una entrada para un grupo de elección única; cualquier número para un grupo de elección múltiple. | | `item_titles` | **Solo grupos seleccionables de elección única y múltiple.** Los títulos de las opciones listadas en `item_ids`, en el mismo orden. Nunca está vacío: una opción sin título informa su ID en su lugar. | | `isCustomerEvent` | Indicador de utilidad, siempre `true` para este evento. Marca los eventos que el SDK entrega a tu callback. Resulta útil si un mismo handler reenvía todos los eventos del flow a tu sistema de análisis y filtras por este indicador en lugar de por `name`. | | `isBackendEvent` | Indicador de utilidad, siempre `false` para este evento. Marca los eventos que Adapty también registra para su propio análisis. `false` confirma que lo que los usuarios introducen llega a tu app y solo a ella — Adapty no lo recibe ni lo almacena. | Lo que reporta cada elemento: | En el builder | `element_type` | Parámetro que almacena el valor | Qué contiene | |:--------------|:---------------|:--------------------------------|:-------------| | Input de **Texto**, **Número**, **Número de teléfono** | `text_input`, `number_input`, `phone_input` | `value` | La cadena literal que escribió el usuario. Los números llegan como cadenas, no como tipos numéricos. | | Input de **E-mail** | `email_input` | `value` | La cadena literal que escribió el usuario, aunque no haya superado la validación de formato del builder. Valídala en tu lado antes de usarla. | | Input de **Contraseña** | ninguno | ninguno | No envía ningún evento. | | Input de **Fecha** | `date_picker` | `value` | Tiempo Unix en milisegundos, como entero, a medianoche local de la fecha seleccionada. | | Input de **Hora** | `time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Input de **Fecha y hora** | `date_picker` y `time_picker` | `value` | Dos elementos, un selector de fecha y un selector de hora. Cada uno envía su propio evento. | | Input cambiado a **Fecha y hora** en el desplegable **Type** | `date_time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Grupo de **elección única** | `single_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: un array con el Element ID de la opción seleccionada. `item_titles`: un array con el título de esa opción. | | Grupo de **elección múltiple** | `multi_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: los Element IDs de todas las opciones seleccionadas, en el orden en que aparecen en el builder. `item_titles`: sus títulos, en el mismo orden. Ambos arrays están vacíos cuando no hay nada seleccionado. | | Grupo de **Toggle** | `toggle` | `value` | Un booleano. | Para bifurcar según una respuesta, compara `item_ids`, no `item_titles`. El título es derivado: el **Element Title** de la opción si lo definiste, si no su texto en tu idioma predeterminado, y si no su Element ID. Un usuario que vio el flow en otro idioma vio un texto diferente. ## Ejemplos de eventos \{#event-examples\} Estos ejemplos muestran las propiedades disponibles en cada evento, con valores ilustrativos en los comentarios. <Details> <summary>Entrada de texto, email, número y teléfono (Haz clic para expandir)</summary> ```kotlin override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String, ) { name // "flow_user_input" paramsJsonString // "{\"name\":\"flow_user_input\",\"instanceId\":\"scr_J260KU5q\",\"isBackendEvent\":false,\"isCustomerEvent\":true,\"element_id\":\"email\",\"element_type\":\"email_input\",\"value\":\"jane@example.com\"}" // paramsJsonString, una vez decodificado: params["name"] // "flow_user_input" params["instanceId"] // "scr_J260KU5q" params["isCustomerEvent"] // true params["isBackendEvent"] // false params["element_id"] // "email" params["element_type"] // "email_input" params["value"] // "jane@example.com" } ``` </Details> <Details> <summary>Selectores de fecha, hora y fecha-hora (haz clic para expandir)</summary> ```kotlin override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String, ) { // paramsJsonString, once decoded: params["element_id"] // "birthday" params["element_type"] // "date_picker" params["value"] // 645408000000 (Unix milliseconds — 1990-06-15, local midnight; read it as a double, since Android serializes numbers that way) } ``` </Details> <Details> <summary>Opción única (haz clic para expandir)</summary> ```kotlin override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String, ) { // paramsJsonString, once decoded: params["element_id"] // "experience" params["element_type"] // "single_choice" params["item_ids"] // ["pro"] params["item_titles"] // ["I train professionally"] } ``` </Details> <Details> <summary>Multi choice (Click to expand)</summary> ```kotlin override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String, ) { // paramsJsonString, once decoded: params["element_id"] // "interests" params["element_type"] // "multi_choice" params["item_ids"] // ["sports", "music"] params["item_titles"] // ["Sports", "Music"] } ``` </Details> <Details> <summary>Toggle (Click to expand)</summary> ```kotlin override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String, ) { // paramsJsonString, once decoded: params["element_id"] // "reminders" params["element_type"] // "toggle" params["value"] // true } ``` </Details> ## Entrega y limitaciones \{#delivery-and-limitations\} :::warning Los flows envían valores sin procesar: direcciones de correo electrónico, números de teléfono y cualquier otra cosa que el usuario escriba. Trata todo lo que entregue el callback de analíticas como datos personales y no los almacenes en ningún lugar donde no guardarías el correo electrónico de un usuario. ::: - **El último valor prevalece**: recibes un evento por campo con el valor con el que el usuario se quedó, no una secuencia tecla a tecla. Si edita un campo, solo llega la última versión. - **Sin garantía de envío**: los valores te llegan a medida que el usuario avanza por el flow, y puede salir en cualquier momento. Espera a que el flow se cierre antes de considerar un conjunto de respuestas como completo. - **Entrega de mejor esfuerzo**: si un usuario cierra el flow o minimiza la app mientras un campo tiene el foco o un selector está abierto, ese valor puede perderse. Para que el valor del último campo sea fiable, termina el flow con una pantalla que no tenga entradas ni selectores, y cierra el flow mediante una acción explícita del usuario en lugar de hacerlo automáticamente al aparecer esa pantalla. Pasar a la pantalla final quita el foco del campo anterior, que es lo que envía su valor. Almacena cada valor de entrada a medida que lo recibe tu observer y envía el conjunto completo cuando el flow se cierre. Para capturar ese momento, sobreescribe `flowViewDidDisappear` en el mismo observer. Se ejecuta cuando la vista del flow se cierra, tanto si el usuario completó el flow como si lo cerró a mitad. ## Casos de uso \{#use-cases\} ### Registrar usuarios en tu backend \{#register-users-on-your-backend\} Recopila los valores según lleguen y envíalos una vez que el flow se cierre, de modo que una sola solicitud incluya el conjunto completo de respuestas. La vista desaparece tanto si el usuario completó el flow como si lo abandonó a mitad. Verifica los campos que necesitas antes de llamar a tu backend. El flow no puede mostrar errores de tu backend. El callback no tiene valor de retorno y el SDK no dispone de ningún método para enviar datos a un flow en ejecución. Si el registro falla, por ejemplo porque el correo electrónico ya está en uso, muestra el error en tu propia UI una vez que el flow se haya cerrado. ```kotlin showLineNumbers title="Kotlin" class MyFlowsEventsObserver : AdaptyUIFlowsEventsObserver { private val flowAnswers = mutableMapOf<String, String>() override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String, ) { if (name != "flow_user_input") return val params = Json.parseToJsonElement(paramsJsonString).jsonObject val elementId = params["element_id"]?.jsonPrimitive?.content ?: return val value = params["value"]?.jsonPrimitive?.contentOrNull ?: return flowAnswers[elementId] = value } override fun flowViewDidDisappear(view: AdaptyUIFlowView) { if (!flowAnswers.containsKey("email")) return // Send flowAnswers to your backend here to create the account. flowAnswers.clear() } } ``` ### Enriquecer perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Para vincular lo que un usuario ha introducido a su perfil y evitar pedirle los mismos datos dos veces, [actualiza el perfil de usuario](kmp-setting-user-attributes) a medida que lleguen los valores. Por ejemplo, si tu flow tiene un campo de texto con el ID de elemento `name` y un campo de email con el ID de elemento `email`: ```kotlin showLineNumbers title="Kotlin" private fun handleFlowInput(name: String, paramsJsonString: String) { if (name != "flow_user_input") return val params = Json.parseToJsonElement(paramsJsonString).jsonObject val value = params["value"]?.jsonPrimitive?.contentOrNull ?: return val builder = AdaptyProfileParameters.Builder() when (params["element_id"]?.jsonPrimitive?.content) { "name" -> builder.withFirstName(value) "email" -> builder.withEmail(value) else -> return } mainUiScope.launch { Adapty.updateProfile(builder.build()) .onError { error -> // handle the error } } } ``` ### Personalizar flows que se muestran más adelante \{#customize-flows-shown-later\} Las respuestas del quiz también pueden decidir qué ve un usuario en un [placement](placements) posterior: un flow diferente o un paywall distinto dentro de él. Por ejemplo, pregunta a los usuarios sobre su experiencia con el deporte en tu flow de onboarding y, a continuación, muestra a cada grupo su propio flow con distintos productos y textos. 1. Añade un [quiz](onboarding-quizzes) a tu flow. Asigna al [grupo seleccionable](flow-selectable-elements) el Group ID `experience` y a cada opción un Element ID descriptivo. 2. Gestiona las respuestas y [establece atributos personalizados](kmp-setting-user-attributes) para el usuario. ```kotlin showLineNumbers title="Kotlin" private fun handleFlowInput(name: String, paramsJsonString: String) { if (name != "flow_user_input") return val params = Json.parseToJsonElement(paramsJsonString).jsonObject if (params["element_id"]?.jsonPrimitive?.content != "experience") return val optionId = params["item_ids"]?.jsonArray?.firstOrNull() ?.jsonPrimitive?.contentOrNull ?: return val builder = AdaptyProfileParameters.Builder() // Set the custom attribute 'experience' to the option the user selected // (beginner, amateur, or pro). builder.withCustomAttribute("experience", optionId) mainUiScope.launch { Adapty.updateProfile(builder.build()) .onError { error -> // handle the error } } } ``` 3. [Crea un segmento](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade una [audiencia](audience) para cada segmento. 5. [Muestra el flow](kmp-present-paywalls) de ese placement en tu app. --- # File: kmp-flow-custom-tags --- --- title: "Pasar datos a flows en Kotlin Multiplatform SDK" description: "Pasa un valor desde tu app de Kotlin Multiplatform a un flow y muéstralo en un elemento de texto." --- Un [custom tag](flow-custom-tags) es una variable personalizada con un valor procedente de tu app. Después de seleccionar **Value comes from outside the app** en la configuración de una variable, tu app puede establecer su valor a través del SDK de Adapty. Por ejemplo, un elemento de texto contiene "Coins:" y un custom tag. Si tu app pasa "120" para el tag, los usuarios ven "Coins: 120". ## Antes de empezar \{#before-you-start\} Necesitas el SDK de Adapty v4 o posterior. Las versiones anteriores a v4 no muestran flows. ## Añadir etiquetas personalizadas al flow \{#add-custom-tags-to-the-flow\} Para mostrar los valores de tu app en un flow, configura etiquetas personalizadas en el builder: 1. [Crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) para cada valor que tu app envíe. <ZoomImage id="flow-custom-tag-create.webp" width="500px" alt="Formulario para crear una variable con nombre, descripción, tipo de valor String y valor inicial" /> 2. [Inserta cada etiqueta en un elemento de texto](flow-custom-tags#add-a-custom-tag-to-a-text-element). 3. Anota el nombre completo de cada etiqueta, incluido su grupo. El código de tu app pasa cada valor con ese nombre. Por ejemplo, si el grupo `user` contiene una etiqueta `streak`, el nombre completo de la etiqueta es `user.streak`. <ZoomImage id="flow-custom-tag-full-name.webp" width="500px" alt="Pestaña personalizada del desplegable Añadir variable con la etiqueta streak en el grupo user" /> ## Pasar valores de etiquetas personalizadas \{#pass-custom-tag-values\} Pasa los valores de tu app al parámetro `customTags` de `AdaptyUI.createFlowView` o `createNativeFlowView`, como un `Map<String, String>`: - Cada clave es el nombre completo de una etiqueta personalizada, como `user.streak`. - Cada valor es la cadena que verán los usuarios en lugar de la etiqueta. - Cada valor debe ser legible por humanos, como "1,250", no "1250.0". Adapty no lo formatea ni traduce. El SDK lee los valores una sola vez, cuando crea la vista, por lo que no cambian mientras el flow está abierto. <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTags = mapOf( "username" to "DragonSlayer42", "coins" to "1,250", ) AdaptyUI.createFlowView( flow = flow, customTags = customTags, ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` Para incrustar el flow en un layout de Compose, pasa el mismo parámetro `customTags` al composable `AdaptyUIFlowPlatformView`. Si pasas un mapa `customTags` diferente, el composable crea una nueva vista y los usuarios vuelven a la primera pantalla del flow. </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTags = mapOf( "username" to "DragonSlayer42", "coins" to "1,250", ) 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 "DragonSlayer42", "coins" to "1,250", ) val nativeView = AdaptyUI.createNativeFlowView( flow = flow, observer = myFlowObserver, customTags = customTags, ) ``` </TabItem> </Tabs> Para probar tus etiquetas personalizadas, abre el flow en tu app después de añadir el código que establece sus valores. El canvas del builder y la [vista previa en dispositivos](paywall-device-compatibility-preview#preview-on-devices) solo muestran los valores iniciales: la app móvil de Adapty no establece los valores de las etiquetas personalizadas. Si tu app ya establece valores de etiquetas personalizadas para paywalls del antiguo Paywall Builder, revisa [en qué se diferencian las etiquetas personalizadas en los flows](migrate-to-flows#custom-tags-from-the-old-paywall-builder). ## Lo que los usuarios ven sin un valor \{#what-users-see-without-a-value\} Los usuarios ven el **Valor inicial** de una etiqueta personalizada como valor de respaldo en los siguientes casos: - Tu app no pasa ningún valor para la etiqueta. - El usuario no ha actualizado tu app a una versión que envíe el valor. - El nombre en el código de tu app no coincide con el nombre completo de la etiqueta, incluidas las mayúsculas y minúsculas. - Otra etiqueta personalizada en el mismo elemento de texto no tiene valor. En ese caso, todas las etiquetas personalizadas de ese elemento de texto muestran su valor inicial. Para no mostrar nada en lugar de una etiqueta, pasa una cadena vacía. Adapty trata una cadena vacía como un valor, por lo que el valor de respaldo no aparece. ## Ejemplo: mostrar un IMC a partir de las respuestas del onboarding \{#example-show-a-bmi-from-onboarding-answers\} Un flow puede recoger respuestas de los usuarios, y una etiqueta personalizada puede mostrar un valor de tu app. Combina ambas cosas para mostrar a los usuarios un resultado que tu app calcula a partir de sus respuestas, como una puntuación, un plan recomendado o una métrica de salud: 1. Un flow recoge las respuestas y las envía a tu app. 2. Tu app calcula el resultado. 3. Tu app pasa el resultado al siguiente flow como valor de etiqueta personalizada. En este ejemplo, un flow de onboarding recoge el peso y la altura del usuario. Cuando el usuario cierra el flow de onboarding tras su última pantalla, tu app calcula su índice de masa corporal (IMC). Luego abre un flow de paywall que muestra el IMC. ### Configurar los flows \{#set-up-the-flows\} 1. [Crea dos flows](paywall-builder-templates): un flow de onboarding que recoja el peso y la altura del usuario, y un flow de paywall que muestre su IMC. 2. Añade dos campos de tipo [**Number**](builder-inputs-and-forms) al flow de onboarding: - Uno para el peso en kilogramos, con el **Element ID** `weight`. - Uno para la altura en centímetros, con el **Element ID** `height`. En ambos campos, establece el **Format** como **Integer**. Un campo envía su valor como texto, exactamente tal como lo escribe el usuario, y el [código de ejemplo](#pass-the-bmi-to-the-paywall) no interpreta "70.5" y "70,5" de la misma manera. Los números enteros no tienen separador decimal, por lo que el código los lee igual en cualquier idioma. 3. Para asegurarte de que tu app recibe ambos valores, termina el flow de onboarding con una pantalla que no tenga campos. En esa pantalla, añade un botón con la acción [**Close flow**](onboarding-actions#close-flow). Los campos envían sus valores cuando pierden el foco o cuando el usuario pasa a otra pantalla. La acción Close flow no los envía. El código de ejemplo solo abre el paywall si el usuario llegó a la última pantalla. 4. En el flow de paywall, [crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) llamada `bmi`. En el campo **Initial value**, introduce un guion (`—`). Los usuarios verán el guion si tu app no pasa ningún IMC. 5. Añade un elemento de texto con las palabras "Your BMI:" al flow de paywall. Después de esas palabras, [inserta la etiqueta personalizada `bmi`](flow-custom-tags#add-a-custom-tag-to-a-text-element). 6. Añade el flow de onboarding a un [placement](placements), por ejemplo `onboarding`. Añade el flow de paywall a un placement con el ID `bmi_paywall`. El código de [Pass the BMI to the paywall](#pass-the-bmi-to-the-paywall) obtiene el paywall usando este ID. ### Pasar el IMC al paywall Recoge el peso y la altura de los eventos `flow_user_input` del flow de onboarding, como se explica en [Procesar datos de flows](kmp-flow-input). Para saber que el usuario llegó a la última pantalla, observa un evento `flow_screen_showed` con `is_last_screen` en `true`, como se explica en [Rastrear vistas de pantalla del flow](kmp-flow-screen-views). Cuando el usuario cierre el flow de onboarding tras esa pantalla, calcula el IMC. Luego crea la vista del paywall y pasa el IMC para el tag personalizado `bmi`. El método `flowViewDidReceiveAnalyticEvent` analiza los parámetros del evento con la biblioteca `kotlinx.serialization.json`. Si tu proyecto aún no tiene esta biblioteca, añade `org.jetbrains.kotlinx:kotlinx-serialization-json` a las dependencias de tu app. A continuación, implementa los siguientes métodos en el observer que registres con `AdaptyUI.setFlowsEventsObserver`: ```kotlin showLineNumbers title="Kotlin Multiplatform" class MyFlowsEventsObserver( private val scope: CoroutineScope, ) : AdaptyUIFlowsEventsObserver { private val bodyAnswers = mutableMapOf<String, String>() private var reachedLastScreen = false override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String, ) { if (name != "flow_user_input" && name != "flow_screen_showed") return val params = Json.parseToJsonElement(paramsJsonString).jsonObject if (name == "flow_screen_showed") { if (params["is_last_screen"]?.jsonPrimitive?.booleanOrNull == true) { reachedLastScreen = true } return } val elementId = params["element_id"]?.jsonPrimitive?.content ?: return val value = params["value"]?.jsonPrimitive?.contentOrNull ?: return bodyAnswers[elementId] = value } override fun flowViewDidDisappear(view: AdaptyUIFlowView) { // The observer receives events from every flow. Reset `bodyAnswers` and // `reachedLastScreen` whenever a flow disappears, so that the paywall // doesn't open again when the user closes it. val answers = bodyAnswers.toMap() val finished = reachedLastScreen bodyAnswers.clear() reachedLastScreen = false // Number inputs send their values as strings. if (!finished) return val weight = answers["weight"]?.toDoubleOrNull() ?: return val height = answers["height"]?.toDoubleOrNull()?.takeIf { it > 0 } ?: return val heightInMeters = height / 100 val bmi = weight / (heightInMeters * heightInMeters) val bmiText = ((bmi * 10).roundToInt() / 10.0).toString() scope.launch { showBmiPaywall(bmiText) } } } suspend fun showBmiPaywall(bmi: String) { Adapty.getFlow(placementId = "bmi_paywall").onSuccess { flow -> AdaptyUI.createFlowView( flow = flow, customTags = mapOf("bmi" to bmi), ).onSuccess { view -> view.present() }.onError { error -> // handle the error } }.onError { error -> // handle the error } } ``` `toString` siempre usa un punto como separador decimal: "22.7". Para usar el separador de la configuración regional del usuario, formatea el IMC con un formateador adaptado a la configuración regional en cada plataforma, como `java.text.NumberFormat` en Android y `NSNumberFormatter` en iOS. ### Guardar el IMC en el perfil del usuario \{#save-the-bmi-to-the-users-profile\} Para mostrar distintos flows a los usuarios según su IMC, guárdalo como un [atributo personalizado](kmp-setting-user-attributes#custom-user-attributes) de su perfil. En el Adapty Dashboard, [crea un atributo personalizado de tipo **Number**](segments#custom-attributes) con la clave `bmi`. Después, muestra cada flow a una [audiencia](audience) definida a partir de ese atributo. Llama al método `updateProfile` dentro de `flowViewDidDisappear`, justo después de la línea que calcula el `bmi`. Pasa ese `Double`, no el string formateado, para que un segmento pueda comparar el IMC con un umbral: ```kotlin showLineNumbers title="Kotlin Multiplatform" val params = AdaptyProfileParameters.Builder() .withCustomAttribute("bmi", bmi) .build() scope.launch { Adapty.updateProfile(params).onError { error -> // handle the error } } ``` --- # 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 configuración regional entran en juego cuando Adapty selecciona la localización para un flow o un onboarding, y cuando lees un Remote Config para un paywall personalizado. Los códigos de configuración regional son complejos y pueden variar de una plataforma a otra, por lo que Adapty se basa en un estándar interno común 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 de Brasil), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de configuración regional \{#locale-code-matching\} En SDK v4, los flows y los onboardings hacen coincidir los códigos de configuración regional de forma diferente: los flows se localizan mediante el SDK en el dispositivo, mientras que los onboardings los localiza el servidor de Adapty. ### Flows y paywalls Un paywall que Adapty renderiza se entrega como un flow en el SDK v4, por lo que la regla siguiente aplica a ambos. La coincidencia es exacta. El SDK compara el código que pasas con los códigos de localización del flow carácter a carácter: no cambia las mayúsculas/minúsculas, no reemplaza guiones bajos (`_`) por guiones (`-`) y no recurre a la subetiqueta de idioma. Para un flow con una localización `pt-br`, solo `pt-br` coincide: `pt-BR`, `pt_BR` y `pt-PT` no funcionan. Cuando el código no coincide con ninguna localización, el flow se renderiza en silencio en su [configuración regional predeterminada](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) — el SDK no devuelve un error ni registra ninguna advertencia. Cuando el código coincide, Adapty fusiona la localización con la predeterminada: las cadenas y recursos que no estén definidos en la localización encontrada se toman de la localización predeterminada. Omitir el código de idioma no equivale a solicitar la localización predeterminada del flow: el SDK sustituye un `en` fijo. Un flow cuyo idioma predeterminado es `de` se renderiza en `en` si tiene una localización `en`, y solo recurre a `de` cuando no la tiene. :::warning Pasa el código de idioma exactamente como está configurado en el dashboard: subtags en minúsculas separados por guiones. No pases un identificador de configuración regional de plataforma tal cual: en Android, `Locale.getDefault().toLanguageTag()` devuelve `pt-BR`; en iOS, `NSLocale.currentLocale.localeIdentifier` devuelve `pt_BR`. Ambos recurren a la localización predeterminada. Convierte el valor en tu app antes de pasarlo. ::: ### Onboardings Los onboardings se localizan en el servidor, y las reglas del servidor admiten otros formatos. Cuando pasas un `locale` a [`getOnboarding`](kmp-get-onboardings): 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 el contenido para la configuración regional predeterminada del onboarding Esta forma, `pt_BR`, `pt-BR`, y `pt-br` se resuelven todas a la misma localización de onboarding. ## Implementación de localizaciones \{#implementing-localizations\} En SDK v4, no se pasa un código de idioma al obtener un flow — `getFlow` devuelve el flow con todas sus localizaciones, y Adapty aplica una cuando se construye la vista del flow. - **Flows y paywalls renderizados por Adapty**: el SDK no lee el idioma del dispositivo, así que resuélvelo en tu app y pásalo como parámetro `locale` de `createFlowView`. Es opcional — si lo omites, el flow se renderiza en `en`, o en su [idioma predeterminado](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) cuando el flow no tiene localización en `en`. ```kotlin showLineNumbers import com.adapty.kmp.AdaptyUI AdaptyUI.createFlowView(flow = flow, locale = "es") .onSuccess { view -> view.present() } .onError { error -> // handle the error } ``` `createNativeFlowView` y el composable `AdaptyUIFlowPlatformView` aceptan el mismo parámetro opcional `locale`. `view.locale` indica la localización con la que se construyó la vista. Tanto el parámetro `locale` como `view.locale` requieren Kotlin Multiplatform SDK 4.0.1-beta.1 o posterior. - **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 propia lógica de respaldo: ```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 } ``` Adapty almacena esos códigos `locale` en el formato descrito en [Estándar de código de idioma en Adapty](#locale-code-standard-at-adapty). El SDK no coteja los Remote Configs con un idioma, por lo que corresponde a tu app decidir qué entrada aplicar. </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). ## Correspondencia 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 entrante se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. A continuación, 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 guión (`pt` para `pt-br`) y buscamos la localización que coincida 4. Si tampoco se encuentra ninguna coincidencia, devolvemos el contenido para el idioma predeterminado del paywall 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 en modo Observer en tu app de Kotlin Multiplatform mientras gestionas las compras con tu propio código." --- Si usas el [modo Observer](observer-vs-full-mode), tú eres responsable de tu propia lógica de compras. Pero puedes delegar la UI del paywall al [Flow & Paywall Builder](adapty-flow-builder). Diseña la pantalla en el builder, y el SDK de Adapty la renderiza de forma nativa. Actualiza el texto, el diseño y los precios cuando quieras, sin necesidad de publicar una nueva versión de la app. Esta guía muestra cómo presentar un flow y gestionar los callbacks de compra. Si Adapty gestiona las compras por ti, consulta [Mostrar flows y paywalls](kmp-present-paywalls) en su lugar. ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty Kotlin Multiplatform SDK 4.0 (beta) o posterior**: Las versiones anteriores no pueden renderizar flows. Para migrar una aplicación existente, consulta la [guía de migración](migration-to-kmp-sdk-v4). - **`withObserverMode(true)` llamado en el configuration builder**: Consulta la [guía de instalación del SDK para Kotlin Multiplatform](sdk-installation-kotlin-multiplatform#activate-adapty-sdk). - **Integración inicial con el store**: Configura Adapty [con App Store](initial_ios) y [con Google Play](initial-android). - **Un flow publicado con productos**: Créalo en el [Flow & Paywall Builder](adapty-flow-builder) y asígnale [productos](create-product). - **Un placement para el flow**: [Crea un placement](create-placement) y asígnale tu flow. - **El flow obtenido en tu app**: [Obtén el flow y su configuración](kmp-get-pb-paywalls) antes de mostrarlo. ## Implementación \{#implementation\} 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 renderizado por Adapty, el SDK llama a tu `AdaptyUIObserverModeResolver` en su lugar — realiza la compra o restauración con tu propio código allí. 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 el comportamiento correcto del flow, como mostrar el indicador de carga, entre otras cosas: | Callback | Description | | :----------------- | :------------------------------------------------------------------------------- | | onStartPurchase() | Este callback debe invocarse para notificar a AdaptyUI que la compra ha comenzado. | | onFinishPurchase() | Este callback debe invocarse para notificar a AdaptyUI que la compra ha finalizado. | | onStartRestore() | Este callback debe invocarse para notificar a AdaptyUI que la restauración ha comenzado. | | onFinishRestore() | Este 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 presentar cualquier pantalla: ```kotlin showLineNumbers import com.adapty.kmp.AdaptyUI AdaptyUI.setObserverModeResolver(MyObserverModeResolver()) ``` Sin un resolver registrado, la vista del flow no tiene manera 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), luego [preséntala](kmp-present-paywalls). No se necesitan parámetros adicionales — una vez que el resolver está registrado, todo flow 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 ni determinará el paywall de origen de la compra. ::: --- # File: kmp-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del antiguo Paywall Builder en el SDK de Kotlin Multiplatform" description: "Corrige errores al mostrar paywalls creados con el antiguo Paywall Builder en el SDK de Adapty para Kotlin Multiplatform." --- Esta guía te ayuda a resolver problemas comunes al usar paywalls diseñados en el Adapty Paywall Builder con el SDK de Kotlin Multiplatform. Para problemas con flows, consulta [Problemas comunes](flow-common-issues). ## La obtención de la configuración del paywall falla \{#getting-a-paywall-configuration-fails\} **Problema**: El método `createFlowView` no consigue crear una vista, o el flow no tiene una configuración de vista. **Motivo**: 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. También puedes leer la propiedad `hasViewConfiguration` del objeto `AdaptyFlow` para comprobar si hay una configuración de vista disponible. <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` (SDK v4+) / `logShowPaywall` en tu código, lo que duplica el recuento de visualizaciones de un paywall creado en el antiguo Paywall Builder. Adapty realiza ese seguimiento automáticamente, por lo que no necesitas este método. **Solución**: Asegúrate de no estar llamando a `logShowFlow` (SDK v4+) / `logShowPaywall` en tu código para un paywall creado en el antiguo Paywall Builder. --- # File: kmp-implement-paywalls-manually --- --- title: "Implementar paywalls personalizados en el SDK de Kotlin Multiplatform" description: "Aprende a implementar paywalls personalizados 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 & Paywall Builder](kmp-quickstart-paywalls). Con el Flow & Paywall 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) – cualquier cosa que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida) - [**Paywalls personalizados**](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 gestiona un objeto **flow** — obtienes un flow y consultas sus productos. - [**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 distintos 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, tendrás que crear un **paywall personalizado** 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 personalizados en el SDK de Kotlin Multiplatform" description: "Obtén paywalls y productos en el SDK de Adapty para Kotlin Multiplatform y mejora la monetización de tus usuarios." --- <SDKv4> Antes de mostrar paywalls personalizados, necesitas obtener información sobre ellos. Ten en cuenta que este tema se refiere a paywalls personalizados. Para obtener orientación sobre cómo obtener flows o paywalls personalizados en el **Flow & Paywall Builder** o el **old 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 comenzar 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 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-kotlin-multiplatform) en tu aplicación 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>`fetchPolicy` establece qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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>Una tercera política, `AdaptyPaywallFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que `maxAgeMillis`, y va al servidor una vez que sea más antigua.</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 flows y paywalls en dos capas: la caché con actualización regular 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 no esté disponible. 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 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 agotar el tiempo de espera un poco más tarde de lo especificado en `loadTimeout`, ya que la operación puede estar compuesta por 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>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y acude al servidor solo cuando no hay nada en caché. Es posible que los usuarios no obtengan los datos más recientes, pero la carga será más rápida 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>Una tercera política, `AdaptyPaywallFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada tiene menos de `maxAgeMillis` de antigüedad, y acude al servidor cuando es más antigua.</p><p></p><p>Ten en cuenta que la caché se conserva al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.</p> | </SDKv4> <SDKv3> Antes de mostrar paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls personalizados. Para obtener orientación sobre cómo obtener paywalls con Paywall Builder, consulta [Obtener paywalls del 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 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-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 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> | | **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.Default` | <p>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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 peticiones de red.</p><p></p><p>Una tercera política, `AdaptyPaywallFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia en caché sea más reciente que `maxAgeMillis`, y va al servidor cuando es más antigua.</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é actualizada regularmente que se describe arriba 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 última versión 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 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 un poco más tarde de lo especificado en `loadTimeout`, ya que la operación puede estar compuesta por diferentes solicitudes 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** | 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` significa inglés, `pt-br` representa el portugués de Brasil.</p><p></p> | | **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.Default` | <p>`fetchPolicy` determina qué capa consulta primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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>Una tercera política, `AdaptyPaywallFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que `maxAgeMillis`, y va al servidor una vez que sea más antigua.</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> | </SDKv3> --- # File: present-remote-config-paywalls-kmp --- --- title: "Presentar paywalls personalizados en Kotlin Multiplatform SDK" description: "Descubre cómo presentar paywalls personalizados en Adapty Kotlin Multiplatform SDK 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 in-app en una aplicación móvil con el SDK de Kotlin Multiplatform" 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, presentar un paywall solo gestiona las compras de forma autónoma cuando Adapty renderiza la pantalla — es decir, un [flow](adapty-flow-builder), o un paywall del [antiguo Paywall Builder](adapty-paywall-builder). Si renderizas la pantalla con tu propio código, 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 lleven a cabo sus transacciones. 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 se aplicará automáticamente solo si Adapty renderiza la pantalla. 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 **¿Adapty renderiza tu pantalla?** Para un [flow](adapty-flow-builder) o un paywall de [Paywall Builder](adapty-paywall-builder), las compras se procesan automáticamente; puedes saltarte este paso. **¿Buscas orientación 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>Acerca de los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios específicos. A diferencia de las ofertas regulares que se aplican automáticamente, los códigos de oferta se distribuyen fuera de la app — a través de campañas de correo electrónico, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, siguiendo 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 realiza al precio completo. - **Pay as you go** — el usuario paga un precio con descuento en cada ciclo de facturación durante un período determinado, luego la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único con descuento por toda la duración de la oferta, luego 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`. Una vez que el período de oferta termina y la suscripción se renueva al precio completo, la etiqueta ya no está presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Solución de problemas de discrepancias en 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 con descuento de la oferta, verifica lo siguiente en App Store Connect: - El código de oferta tiene el precio correcto configurado para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está establecido para el país o región específico 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 en su lugar. Puedes filtrar y verificar las transacciones con códigos 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 reemplazan con más funcionalidades: elegibilidad configurable, fechas de vencimiento y hasta 1 millón de códigos por trimestre. Si anteriormente usabas códigos promocionales para compras in-app, haz la transición a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app por versión) otorgaban 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ódigos promocionales — enviaba el precio completo del producto en el recibo. Como resultado, Adapty registraba estas transacciones al precio completo, lo que causaba discrepancias de ingresos 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. Dado que estos códigos están ahora obsoletos, haz la transición 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 } ``` ## Compras in-app promocionadas desde la App Store \{#in-app-purchases-from-the-app-store\} :::info Tu app recibe compras in-app promocionadas a partir de la versión 4.1 del SDK, en **iOS 16.4** o posterior. Por debajo de iOS 16.4, el listener no se activa y las compras promocionadas se completan por sí solas. Esta es una función exclusiva de iOS: en Android el listener tampoco se activa, y `makePromotedPurchase` devuelve un `AdaptyErrorCode.DEVELOPER_ERROR`. ::: Cuando un usuario inicia una compra desde la página de tu producto en la App Store y la transacción llega a tu app, el SDK te entrega el producto a través de un `OnPromotedPurchaseListener`. Completar la compra depende de tu app: pasa el producto a `makePromotedPurchase`. Como tú controlas cuándo ocurre esto, puedes mostrar tu propia pantalla antes de que comience la compra. Para admitir compras promocionadas, registra el listener y completa la compra desde él: ```kotlin showLineNumbers Adapty.setOnPromotedPurchaseListener(OnPromotedPurchaseListener { product -> scope.launch { Adapty.makePromotedPurchase(product) .onSuccess { result -> /* process the purchase result */ } .onError { error -> /* handle the error */ } } }) ``` :::warning Sin un listener registrado, una compra promocionada nunca se completa: el App Store entrega el producto a tu app y espera. Pasar `null` a `setOnPromotedPurchaseListener` también impide que las compras promocionadas funcionen. ::: Registra el listener al iniciar la app, justo después de `Adapty.activate`. Una compra promocionada normalmente lanza la app en frío, por lo que suele llegar al SDK antes de que tu registro se ejecute. El SDK retiene una de estas compras y la entrega en cuanto te registras, conservando solo la más reciente, y escribe una advertencia en la consola cada vez que retiene una compra. Si el producto promocionado incluye una oferta de suscripción, el SDK la aplica automáticamente en el momento de la compra. La oferta se lee desde el intent de compra del App Store, que la expone en iOS 18.0 y versiones posteriores. En iOS 16.4–17.x, la compra se realiza al precio base. `makePromotedPurchase` no acepta parámetros de compra — un producto promocionado proviene del App Store y no de un paywall, por lo que no lleva contexto de paywall. Devuelve el mismo `AdaptyPurchaseResult` que `makePurchase`. `AdaptyPromotedProduct` incluye `vendorProductId`, `localizedTitle`, `localizedDescription`, `price`, `regionCode`, `isFamilyShareable` y una `subscription` de tipo `AdaptyProductSubscription`. --- # File: kmp-restore-purchase --- --- title: "Restaurar compras en la app móvil con el SDK de Kotlin Multiplatform" description: "Aprende a 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 quienes han desinstalado y vuelto a instalar la app, o han cambiado de dispositivo, y quieren acceder a su contenido sin pagar de nuevo. :::note Si creaste la pantalla con un builder, no necesitas restaurar las compras manualmente. Conecta su control de restauración — la acción **Restore purchases** en el Flow & Paywall Builder, o un enlace o botón de restauración en el antiguo — y el SDK completa la restauración. ::: Si creaste la pantalla tú mismo, 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 presentar flows y paywalls renderizados por Adapty en el modo Observer: registra un `AdaptyUIObserverModeResolver` con `setObserverModeResolver` 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](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`. | ## Uso de flows y paywalls de Adapty en Observer Mode \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los flows, paywalls y funciones de pruebas A/B de Adapty, puedes hacerlo, aunque en Observer Mode requiere una configuración adicional. Esto es lo que necesitas hacer además de los pasos anteriores: 1. Muestra los [paywalls personalizados](present-remote-config-paywalls-kmp) como de costumbre. Para los flows y paywalls creados con el Flow & Paywall Builder, consulta [Presentar flows en modo Observer](kmp-present-flows-in-observer-mode). 2. [Asocia los flows y paywalls](report-transactions-observer-mode-kmp) con las transacciones de compra. --- # File: report-transactions-observer-mode-kmp --- --- title: "Reportar transacciones en Observer Mode en Kotlin Multiplatform SDK" description: "Reporta transacciones de compra en Adapty Observer Mode para obtener información sobre usuarios y seguimiento de ingresos en Kotlin Multiplatform SDK." --- 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 reportar las transacciones desde tu 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 utilizas flows y paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el flow o paywall que la originó, garantizando una analítica precisa. ```kotlin showLineNumbers Adapty.reportTransaction( transactionId = "your_transaction_id", variationId = paywall.variationId ).onSuccess { // Transaction reported successfully }.onError { error -> // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | --------------- | --------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | requerido | El ID de transacción de tu compra en el store. Normalmente es el token de compra o el identificador de transacción que devuelve 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 el SDK de Kotlin Multiplatform" 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 los 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 a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: <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 a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 { onboarding -> // the requested onboarding }.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 -> // 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>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 eficaz para impulsar más conversiones." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: Antes de empezar, 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 app 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 the flow by placement ID // Adapty.getFlow("your_placement_id").onSuccess { flow -> // AdaptyUI.createFlowView(flow).onSuccess { view -> // view.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 flow using the placement ID from the action mainUiScope.launch { Adapty.getFlow(placementId = actionId) .onSuccess { flow -> AdaptyUI.createFlowView(flow) .onSuccess { view -> view.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 usa datos de onboardings en tu app de Kotlin Multiplatform con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: Cuando tus usuarios respondan a una pregunta de un cuestionario o introduzcan sus datos en un campo de texto, 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 SDK de MMP o analíticas (AppsFlyer, Adjust, PostHog, Branch) | Al iniciar la app, lo primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `Adapty.activate(configuration = AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | 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(configuration = AdaptyConfig.Builder("KEY").build())` sin `withCustomerUserId` | 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("appsflyer_id", uid)` para cada MMP | Después del paso 2, antes de cualquier llamada de acción de usuario | Necesario para que los IDs del MMP lleguen 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 el `onSuccess` antes de cualquier llamada de acción de usuario. Las llamadas concurrentes durante `identify` pueden llegar al perfil anónimo. | | 5 | `getPaywall` (`getFlow` en SDK v4), `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution` (`updateExternalAttribution` en SDK 4.1), `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 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 flows y paywalls en el SDK de Kotlin Multiplatform" description: "Obtén flows y paywalls de Adapty de forma fiable: tiempo, caché y patrones de respaldo para Kotlin Multiplatform." --- Una obtención fiable de un flow o paywall en Kotlin Multiplatform hace tres cosas: renderiza rápido, devuelve la variante dirigida a la audiencia y tiene un respaldo elegante cuando la red es lenta. Las reglas que se explican a continuación cubren los patrones de tiempo, caché y respaldo para lograrlo. :::tip Se asume que `Adapty.activate()` y `Adapty.identify()` ya se han completado. Consulta [Orden de llamadas en el SDK de Kotlin Multiplatform](kmp-sdk-call-order). ::: ## Reglas y advertencias \{#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 de forma concurrente al iniciar la app. | La precarga masiva bloquea el hilo principal y provoca una pantalla en negro durante el pico. | | Llama a `getFlow` después de que la atribución haya tenido tiempo de resolverse — por ejemplo, 1–2 segundos después de `activate` o tras que se dispare `setOnProfileUpdatedListener`. | Llama a `getFlow` al iniciar la app. | La atribución aún no ha llegado. El flow 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 `getFlow` responda. | Sin un timeout, los usuarios con mala conectividad ven una pantalla en blanco hasta que la red responde — o cierran la app. | Consulta [Obtener paywalls personalizados 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. ## Mostrar el contenido multimedia de la primera pantalla desde el bundle de la app \{#show-first-screen-media-from-the-app-bundle\} Un flow descarga sus imágenes y vídeos desde Adapty. Para mostrar el contenido multimedia de la primera pantalla de forma inmediata, sírvelo desde el bundle de la app. Es una buena forma de reutilizar recursos que ya incluyes en la app, como los visuales de un onboarding nativo existente. 1. En el Flow & Paywall Builder, establece un [ID de medio personalizado](custom-media#custom-media-id) en la imagen o el vídeo. El archivo que subas ahí será el de respaldo. 2. Añade el archivo a los recursos de tu app, en `commonMain/composeResources/files/`. 3. Al crear la vista del flow, pasa el archivo incluido en el bundle para ese ID en `customAssets`: ```kotlin showLineNumbers // "welcome_video" is the custom media ID set in the Flow & Paywall Builder val bundledAssets: Map<String, AdaptyCustomAsset> = mapOf( "welcome_video" to AdaptyCustomAsset.localVideoResource( path = Res.getUri("files/videos/welcome.mp4") ), ) AdaptyUI.createFlowView( flow = flow, customAssets = bundledAssets, ).onSuccess { view -> view.present() }.onError { error -> // handle the error } ``` Los archivos incluidos aumentan el tamaño de descarga de tu app, así que incluye solo los recursos multimedia que el usuario ve primero. Los archivos multimedia que no incluyas en el bundle seguirán apareciendo de inmediato: el layout del flow incluye una copia en baja resolución de cada imagen, incluido el fotograma estático de los vídeos, y la muestra hasta que se cargue el archivo completo. Para la referencia completa de `customAssets`, consulta [Personalizar assets](kmp-get-pb-paywalls#customize-assets). ## Ajuste para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte, regiones afectadas por enrutamiento): - Establece `fetchPolicy = AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` en cada fetch excepto el primero. - 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 bloquees la visualización del flow esperando `Adapty.getProfile()`. Llama a `getFlow` de forma independiente para que un perfil lento no bloquee la UI. --- # File: kmp-show-aa-targeted-paywall --- --- title: "Mostrar un paywall orientado a AA en el primer lanzamiento en el SDK de Kotlin Multiplatform" description: "Espera la atribución de Apple Ads antes de solicitar el paywall en Kotlin Multiplatform usando AdaptyProfile.appliedAttributionSources." --- Este artículo se aplica a la compilación iOS de tu app. La atribución de Apple Ads solo existe en iOS. Apple Ads (AA) attribution llega de forma asíncrona después de `Adapty.activate()`. Si llamas a `getFlow` demasiado pronto, la atribución aún no habrá llegado y Adapty resolverá el placement contra la audiencia predeterminada, sin tener en cuenta tus paywalls segmentados por AA. `AdaptyProfile.appliedAttributionSources` permite que la app detecte 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 por AA se resuelva correctamente. :::important Esta propiedad solo reporta Apple Ads. La atribución de otros proveedores es visible en los perfiles de usuario y está disponible en los filtros de segmentos, pero aún no aquí. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - Adapty Kotlin Multiplatform SDK **4.0** o posterior. - Apple Ads configurado para la app en Adapty. Consulta [Apple Ads](apple-search-ads). :::note Los ejemplos usan los nombres de API del SDK 4.0. En el SDK 3.x, los paywalls se obtienen con `getPaywall`/`getPaywallForDefaultAudience`, que devuelven un `AdaptyPaywall`. Consulta [Migrar el SDK de Adapty Kotlin Multiplatform a v4](migration-to-kmp-sdk-v4). ::: ## 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 a tu `OnProfileUpdatedListener`, con `"apple_search_ads"` en su lista `appliedAttributionSources`. Una lista vacía puede significar cualquiera de estos casos: - La atribución de Apple Ads aún no se ha procesado para este perfil. - No ha llegado ninguna atribución. - La atribución llegó desde otro proveedor, que esta lista no registra. Incluso con una lista vacía, `getFlow` sigue siendo seguro de llamar — Adapty resuelve la solicitud según la audiencia que coincida con el estado actual del perfil, normalmente la audiencia predeterminada. :::important La espera solo se aplica al **primer arranque**. Una vez que se ha registrado la atribución de Apple Ads, queda almacenada en el perfil de forma permanente. En cada arranque posterior, el perfil en caché ya lleva `"apple_search_ads"` en `appliedAttributionSources`, el listener se activa con ese valor de inmediato y `getFlow` devuelve el paywall segmentado por Apple Ads sin ningún retraso. ::: ## Implementación \{#implementation\} En el primer arranque, controla si aparece `"apple_search_ads"` en el perfil y aplica un timeout estricto: si la atribución de Apple Ads nunca llega, esos usuarios igualmente tienen que ver un paywall. 1. **Activa el SDK.** Consulta [Instalar y configurar el SDK de Kotlin Multiplatform](sdk-installation-kotlin-multiplatform). 2. **Suscríbete a las actualizaciones del perfil** con `Adapty.setOnProfileUpdatedListener`. Si todavía no has configurado el listener, consulta [Escuchar actualizaciones de suscripción](kmp-check-subscription-status#listen-to-subscription-updates). 3. **Observa si aparece `"apple_search_ads"` en `appliedAttributionSources`.** Cuando aparezca, solicita el paywall — Adapty devolverá la variante segmentada por AA: ```kotlin Adapty.setOnProfileUpdatedListener { profile -> if ("apple_search_ads" in profile.appliedAttributionSources) { // load the paywall via Adapty.getFlow(placementId) } } ``` 4. **Inicia un temporizador de 3–5 segundos en paralelo con la suscripción.** Si el temporizador se dispara antes de que aparezca `"apple_search_ads"`, solicita el paywall de la audiencia predeterminada con `getFlowForDefaultAudience`. Este método devuelve el paywall sin esperar a la segmentación. Cualquiera de los dos caminos que se active primero debe cargar el paywall; el otro camino debe ignorarse. Usa una única bandera de estado (por ejemplo, `hasLoadedPaywall`) para evitar duplicados y que el paywall no se obtenga dos veces. Configura un [paywall de respaldo](kmp-use-fallback-paywalls) para el placement para que el usuario nunca se quede bloqueado si la solicitud de red falla. `setOnProfileUpdatedListener` acepta un solo listener. Si tu aplicación ya lo utiliza para otros propósitos (por ejemplo, [escuchar actualizaciones de suscripción](kmp-check-subscription-status#listen-to-subscription-updates)), añade la comprobación al listener existente en lugar de registrar uno nuevo. --- # 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 del manejo de errores \{#error-handling-basics\} Los métodos del SDK de Adapty son funciones suspend que devuelven un `AdaptyResult`, que puede ser un éxito o un error. Gestiona ambos casos con `onSuccess` y `onError`: ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // Handle success } .onError { error -> // Handle error println("Adapty error: ${error.message}") } ``` `AdaptyResult` es una clase sellada, por lo que también puedes ramificar directamente sobre ella cuando necesites el resultado como valor: ```kotlin showLineNumbers when (val result = Adapty.getProfile()) { is AdaptyResult.Success -> { val profile = result.value // Handle success } is AdaptyResult.Error -> { val error = result.error // Handle error } } ``` ## Códigos de error comunes \{#common-error-codes\} `AdaptyError.code` es un valor enum de tipo `AdaptyErrorCode`, no un número; compáralo contra la constante del enum, no contra el código numérico. Los números que aparecen a continuación se muestran únicamente porque son los que suele mostrar una línea de log o un ticket de soporte. | Código de error | Número | Descripción | Solución | |---|---|---|---| | `AdaptyErrorCode.NO_PRODUCT_IDS_FOUND` | 1000 | Ninguno de los productos del paywall está disponible en el store. | Consulta [Solución para el error Code-1000 noProductIDsFound](InvalidProductIdentifiers-kmp). | | `AdaptyErrorCode.CANT_MAKE_PAYMENTS` | 1003 | Las compras in-app no están permitidas en este dispositivo. | Consulta [Solución para el error Code-1003 cantMakePayments](cantMakePayments-kmp). | | `AdaptyErrorCode.PRODUCT_NOT_FOUND` | 22 | El producto solicitado para la compra no está disponible en el store. | Comprueba que el producto esté configurado tanto en el store como en el Adapty Dashboard. | | `AdaptyErrorCode.NETWORK_FAILED` | 2005 | La solicitud de red ha fallado. | Pide al usuario que compruebe su conexión o vuelve a intentar la llamada. | | `AdaptyErrorCode.ADAPTY_NOT_INITIALIZED` | 20 | El SDK no se activó antes de realizar la llamada. | Espera a que `Adapty.activate` se complete antes de realizar cualquier otra llamada al SDK. | Para ver la lista completa de códigos, consulta `AdaptyErrorCode` en la [referencia de modelos del SDK](https://kmp.adapty.io/adapty/). ## Gestión de errores específicos \{#handle-specific-errors\} ### Errores de red \{#network-errors\} ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> // Use the flow } .onError { error -> when (error.code) { AdaptyErrorCode.NETWORK_FAILED -> { // Network error - show offline message showOfflineMessage() } else -> { showErrorMessage(error.message) } } } ``` ### Errores de compra \{#purchase-errors\} `makePurchase` reporta fallos y resultados por separado. Un `AdaptyResult.Error` significa que la llamada falló; un resultado exitoso devuelve un `AdaptyPurchaseResult` que indica si el usuario completó la compra, la canceló o la dejó pendiente: ```kotlin showLineNumbers Adapty.makePurchase(product) .onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> showSuccessMessage() AdaptyPurchaseResult.UserCanceled -> { // The user dismissed the store sheet — not an error } AdaptyPurchaseResult.Pending -> { // Awaiting an out-of-band payment, such as a prepaid plan showPendingMessage() } } } .onError { error -> when (error.code) { AdaptyErrorCode.CANT_MAKE_PAYMENTS -> showPaymentNotAvailableMessage() AdaptyErrorCode.PRODUCT_NOT_FOUND -> showProductNotAvailableMessage() else -> showPurchaseErrorMessage(error.message) } } ``` ## Estrategias de recuperación ante errores \{#error-recovery-strategies\} ### Reintentos ante errores de red \{#retry-on-network-errors\} Dado que los métodos del SDK son funciones suspend, un bucle de reintentos es simplemente un bucle con `delay` entre intentos: ```kotlin showLineNumbers suspend fun getFlowWithRetry(placementId: String, maxRetries: Int = 3): AdaptyFlow? { repeat(maxRetries) { attempt -> when (val result = Adapty.getFlow(placementId)) { is AdaptyResult.Success -> return result.value is AdaptyResult.Error -> { if (result.error.code != AdaptyErrorCode.NETWORK_FAILED) { showErrorMessage(result.error.message) return null } delay(1000L * (attempt + 1)) } } } return null } ``` ### Usa datos en caché como respaldo \{#fall-back-to-cached-data\} No necesitas gestionar la caché de los flows tú mismo. El SDK de Adapty los guarda en caché en el dispositivo y devuelve la copia en caché cuando falla la red, así que pasa una fetch policy en lugar de implementar tu propia caché: ```kotlin showLineNumbers Adapty.getFlow( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad ) .onSuccess { flow -> // Use the flow } .onError { error -> showErrorMessage(error.message) } ``` Con `ReturnCacheDataElseLoad`, los usuarios con una conexión inestable obtienen un flow más rápido, aunque es posible que no vean la versión más reciente. Si ni la red ni la caché producen un flow, el SDK recurre al [archivo de respaldo](kmp-use-fallback-paywalls) que incluyes con la app. ## 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://kmp.adapty.io/adapty/) - 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**. Copia solo el identificador — en formato DNS inverso sin espacios, por ejemplo, `com.company.app`. App Store Connect muestra el nombre de la app junto a él en otras vistas, como la lista de apps; pegar eso en lugar del identificador hará que la conexión no pueda validarse. <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 deseas migrar para obtener instrucciones detalladas: - **[Migrar a v4.1](migration-to-kmp-sdk-v4)** - **[Migrar a v3.15](migration-to-kmp-315)** --- # File: migration-to-kmp-sdk-v4 --- --- title: "Migrar el SDK de Kotlin Multiplatform de Adapty a v4.1" description: "Migra al SDK de Kotlin Multiplatform de Adapty v4.1: sustituye las APIs de paywall por APIs de flow, adopta el método de atribución renombrado y habilita explícitamente Adapty Attribution." --- El SDK de Kotlin Multiplatform de Adapty 4.1 es la primera versión estable de la línea 4.x — la 4.0 se publicó solo como beta, así que si estás en la 3.x, migra directamente a la 4.1. Esta guía cubre la migración completa: los flows introducidos en la 4.0 y los cambios de la 4.1 sobre ellos. La línea 4.x introduce flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan tanto con el Flow & Paywall Builder como con el antiguo Paywall Builder — no se requieren cambios de configuración en el Adapty Dashboard. Además, la versión 4.1 hace que Adapty Attribution sea opcional (opt-in), renombra las APIs de atribución externa y el tipo de suscripción del producto, y añade las compras in-app promocionadas de App Store. :::note ¿Vienes de la beta 4.0? Actualiza la versión y solo aplican cinco secciones: [La atribución de Adapty está desactivada por defecto](#adapty-attribution-is-disabled-by-default), [las APIs de atribución externa renombradas](#renamed-external-attribution-apis), [AdaptyPaywallProductSubscription → AdaptyProductSubscription](#adaptypaywallproductsubscription--adaptyproductsubscription), [compras in-app promocionadas en App Store](#app-store-promoted-in-app-purchases) y [elegir un layout específico](#choosing-a-specific-layout). `hasViewConfiguration` también [vuelve al modelo de flow](#data-model). ::: ## Referencia rápida \{#quick-reference\} | v3 | v4.1 | |---|---| | Adapty Attribution habilitado automáticamente | deshabilitado por defecto — actívalo con `.withAdaptyAttributionEnabled(true)` | | `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` | | `Adapty.updateAttribution(attribution, source)` con una fuente de tipo `String` | `Adapty.updateExternalAttribution(attribution, provider)` con un `AdaptyExternalAttributionProvider` | | proveedor pasado como string, por ejemplo `"adjust"` | `AdaptyExternalAttributionProvider`, por ejemplo `AdaptyExternalAttributionProvider.ADJUST` | | `AdaptyProfile.appliedAttributionSources: List<String>` | `AdaptyProfile.appliedExternalAttributionProviders: List<AdaptyExternalAttributionProvider>` | | `AdaptyPaywallProductSubscription` | `AdaptyProductSubscription` | | Las compras in-app promocionadas se completaban automáticamente, sin posibilidad de interceptarlas | `OnPromotedPurchaseListener` y `Adapty.makePromotedPurchase(product)` delegan la finalización a tu app | `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 aceptan un parámetro `locale` — pásalo a `createFlowView` en su lugar. Las APIs de compra y perfil (`makePurchase`, `restorePurchases`, `getProfile`, `identify`, `updateProfile`) y `setFallback` mantienen las mismas firmas, pero el propio archivo de respaldo debe volver a descargarse — consulta [Archivos de respaldo](#fallback-files). Los métodos de onboarding siguen funcionando pero están obsoletos — consulta [Obsolescencia 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\} Actualiza la versión y sincroniza el proyecto: ```toml showLineNumbers title="libs.versions.toml" [versions] adapty-kmp = "<the latest SDK version>" [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 de Compose Multiplatform (`view.present()`). Consulta [Instalar el SDK de Adapty](sdk-installation-kotlin-multiplatform) para la configuración completa. Los SDKs nativos de Adapty subyacentes se han actualizado a sus versiones 4.x en ambas plataformas y se resuelven automáticamente — no se necesitan cambios en la compilación. El target de despliegue de iOS sigue siendo **15.0**, sin cambios en esta versión. ## ⚠️ La atribución de Adapty está desactivada por defecto \{#adapty-attribution-is-disabled-by-default\} :::warning Si usas [Atribución de Adapty](user-acquisition) y actualizas al SDK 4.1 sin activarla explícitamente, fallará en silencio: las instalaciones dejarán de registrarse y no recibirás ningún aviso. ::: En versiones anteriores, el SDK registraba instalaciones para [Atribución de Adapty](user-acquisition) automáticamente. A partir de la versión 4.1 del SDK, esto está desactivado por defecto: el SDK no registra instalaciones, el listener configurado con `setOnInstallationDetailsListener` nunca se activa, y `getCurrentInstallationStatus` devuelve `AdaptyInstallationStatus.Determined.NotAvailable`. Si usas la Atribución de Adapty, actívala al inicializar el SDK: ```diff showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") + .withAdaptyAttributionEnabled(true) .build() Adapty.activate(configuration = config) ``` Si no usas Adapty Attribution, no es necesario hacer ningún cambio. ## Obtener flows \{#fetching-flows\} ### getPaywall → getFlow El tipo devuelto cambia de `AdaptyPaywall` a `AdaptyFlow`, y el parámetro `locale` se mueve de la llamada de obtención a `createFlowView`; para 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 -> + AdaptyUI.createFlowView(flow = flow, locale = "en") } .onError { error -> // handle the error } ``` `locale` sigue siendo opcional en `createFlowView`: omítelo y la vista se renderizará en `en`, o en la localización predeterminada del flow cuando este no tenga `en`. Consulta [Localizaciones y códigos de idioma](kmp-localizations-and-locale-codes). `getPaywallForDefaultAudience` se renombra de la misma forma: ```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 } ``` ### Archivos de respaldo \{#fallback-files\} El formato del archivo de respaldo [cambió en la versión 4 del SDK](fallback-flows). Descarga el nuevo archivo desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** y agrégalo a tu app. ## 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" }`. | | _(nuevo)_ | `paywalls: List<AdaptyFlowPaywall>` | Cada entrada es una variación de paywall en el flow, con su propio `name`, `variationId` e `productIdentifiers`. Los métodos de paywall web reciben un `AdaptyFlowPaywall` — consulta [Métodos de paywall web](#web-paywall-methods). | | `productIdentifiers` | movido | Los identificadores de producto ahora están en cada variación: `flow.paywalls[i].productIdentifiers`. Para obtener productos, sigue llamando a `getPaywallProducts(flow)`. | | `hasViewConfiguration` | mantenido | Indica si el flow incluye un layout que AdaptyUI puede renderizar. Estaba ausente en la beta 4.0 y ha vuelto en 4.1: si eliminaste tus comprobaciones para la beta, puedes volver a usarlo. `false` significa que el flow no lleva layout, así que trátalo como solo Remote Config. También puedes llamar a `createFlowView` y gestionar el error (consulta [Mostrar flows](#displaying-flows)). | `hasViewConfiguration` también está disponible en `AdaptyOnboarding`, sin cambios. ## 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 sigue registrándose 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 - 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 Adapty — Adapty registra esas vistas automáticamente. ## Mostrando flows \{#displaying-flows\} ### createPaywallView → createFlowView Renombra el método de fábrica y pasa el `AdaptyFlow`. El tipo de vista devuelto cambia de `AdaptyUIPaywallView` a `AdaptyUIFlowView`, pero sus métodos (`present`, `dismiss`) y los parámetros opcionales (`loadTimeout`, `preloadProducts`, `customTags`, `customTimers`, `customAssets`, `productPurchaseParams`) no cambian. Hay un nuevo parámetro opcional: `locale`, que reemplaza el `locale` que antes pasabas a `getPaywall` — consulta [Obtención de flows](#fetching-flows). :::note `customTimers` sigue existiendo, pero solo afecta a los paywalls del Paywall Builder heredado. El [temporizador de cuenta atrás](flow-timer) de un flow se ejecuta según el comportamiento configurado en el Flow & Paywall Builder, por lo que un flow ignora lo que pases aquí. ::: ```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 manera: ```diff showLineNumbers - AdaptyUI.createNativePaywallView(paywall) + AdaptyUI.createNativeFlowView(flow) ``` `createFlowView` devuelve un `AdaptyResult.Error` si el flow no tiene ninguna vista configurada, así que puedes eliminar la comprobación `hasViewConfiguration` de v3 y gestionar el error en su lugar: ```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, por lo que debes llamar 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 compras y restauraciones iniciadas desde flows mientras el SDK funciona en [modo Observer](implement-observer-mode-kmp). Antes 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 (solicitudes de permisos del SO y peticiones de valoración de la app). Los flows aún no activan estas solicitudes, por lo que no es necesario registrar un handler. - El nuevo callback opcional `flowViewDidReceiveAnalyticEvent` reporta eventos de analítica desde un flow, comenzando con una vista de pantalla por cada pantalla que abre el usuario. Consulta [Rastrear vistas de pantalla de flows](kmp-flow-screen-views). - `AdaptyUI.openWebUrl(url, openIn)` y `AdaptyUI.requestAppReview()` — son la base del manejo predeterminado de `OpenUrlAction` y del `handleAppReviewRequest` predeterminado, por lo que las URLs y las solicitudes de valoración de la app se gestionan de forma nativa sin configuración adicional. Llámalos directamente solo si sobreescribes esos comportamientos predeterminados. - `AdaptyUIFlowView.locale` — informa la localización con la que se construyó la vista, para que puedas saber cuál ve realmente el usuario. - `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). ## API de atribución externa renombradas \{#renamed-external-attribution-apis\} A partir de la versión 4.1 del SDK, las API que envían datos de atribución desde un proveedor externo (Adjust, AppsFlyer, Branch, Tenjin, Apple Ads o uno personalizado) se han renombrado para coincidir con los SDK nativos, y el proveedor pasa de ser una cadena de texto a un tipo. No existen alias deprecados, por lo que los sitios de llamada existentes dejarán de compilar hasta que los actualices. ### updateAttribution → updateExternalAttribution El método ha sido renombrado, su parámetro `source` pasa a llamarse `provider`, y ahora acepta un `AdaptyExternalAttributionProvider` en lugar de un `String`: ```diff showLineNumbers - Adapty.updateAttribution(attribution, "adjust") + Adapty.updateExternalAttribution(attribution, AdaptyExternalAttributionProvider.ADJUST) ``` Los datos de atribución siguen siendo un `Map<String, Any>`. La llamada retorna en cuanto el backend acepta los datos para procesarlos de forma asíncrona. Un resultado exitoso no significa que los datos ya se hayan aplicado al perfil. ### AdaptyExternalAttributionProvider El proveedor es ahora un tipo con valores predefinidos: `APPLE_ADS`, `ADJUST`, `APPSFLYER`, `BRANCH`, `TENJIN` y `CUSTOM`. Para cualquier otro proveedor, constrúyelo a partir de su identificador: ```kotlin showLineNumbers AdaptyExternalAttributionProvider("your_provider") ``` Construirlo directamente también cubre los proveedores que Adapty añada después de esta versión del SDK — el identificador llega al backend sin modificaciones en lugar de colapsar en un valor desconocido. Los espacios en blanco circundantes se eliminan automáticamente. Cada valor predefinido encapsula el mismo identificador que pasaste a `updateAttribution` anteriormente: `AdaptyExternalAttributionProvider.APPLE_ADS.value` es `apple_search_ads`, y el resto son sus propios nombres en minúsculas. ### AdaptyProfile.appliedAttributionSources → appliedExternalAttributionProviders La propiedad del perfil que lista los proveedores de atribución aplicados al perfil se renombra, y el tipo de sus elementos cambia en consecuencia: ```diff showLineNumbers - if (profile.appliedAttributionSources.contains("apple_search_ads")) { + if (profile.appliedExternalAttributionProviders.contains(AdaptyExternalAttributionProvider.APPLE_ADS)) { // Apple Ads attribution has been applied } ``` ## AdaptyPaywallProductSubscription → AdaptyProductSubscription \{#adaptypaywall-product-subscription--adaptyproductsubscription\} El tipo de detalles de suscripción se renombra porque los productos promocionados ahora comparten el mismo tipo. Solo cambia el nombre — cada propiedad mantiene su nombre y tipo: ```diff showLineNumbers - val subscription: AdaptyPaywallProductSubscription? = product.subscription + val subscription: AdaptyProductSubscription? = product.subscription ``` ## Compras in-app promocionadas en el App Store \{#app-store-promoted-in-app-purchases\} SDK 4.1 entrega [compras in-app promocionadas en la página de tu producto en el App Store](kmp-making-purchases#in-app-purchases-from-the-app-store) a tu app en iOS. Las versiones anteriores completaban este tipo de compra automáticamente, sin que tu app pudiera interceptarla. A partir de la 4.1, la compra espera a que tu código la gestione, por lo que es necesario actuar aunque nunca hayas trabajado con compras promocionadas. Para admitir compras promocionadas, registra un `OnPromotedPurchaseListener` y completa la compra pasando el producto a `Adapty.makePromotedPurchase`. Sin un listener registrado, la compra queda pendiente en lugar de completarse: el usuario pulsa **Buy** en tu página de App Store y no ocurre nada en tu app. Registra el listener lo antes posible — consulta [Compras in-app desde el App Store](kmp-making-purchases#in-app-purchases-from-the-app-store) para ver el momento adecuado y el ejemplo completo. ## Elegir un layout específico \{#choosing-a-specific-layout\} `createFlowView`, `createNativeFlowView` y `AdaptyUIFlowPlatformView` aceptan un nuevo parámetro opcional `customLayoutId`. Úsalo para renderizar un layout concreto de la configuración de layouts del flow en lugar del que el SDK selecciona automáticamente según el tipo de dispositivo y el tamaño de pantalla. El Flow & Paywall Builder aún no asigna IDs de layout personalizados, así que deja este parámetro sin definir: ```kotlin showLineNumbers AdaptyUI.createFlowView(flow, customLayoutId = "tablet_landscape") ``` Si ningún layout coincide con el ID, el flow se carga sin una configuración de vista. El parámetro es opcional y su valor predeterminado es `null`, por lo que las llamadas existentes no se ven afectadas. ## 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. ## Desaprobación de la API de onboarding \{#onboarding-api-deprecation\} La API de onboarding heredada está desaprobada en v4 en favor del [Flow & Paywall 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 & Paywall Builder. Símbolos desaprobados: `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: AdaptyUIView, productId: String) { + override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, 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-10-30T13:00:39.656Z_ _Successfully processed: 52/52 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-10-30T13:00:39.659Z Total files: 61 --- # File: react-native-sdk-overview --- --- title: "Descripción general del SDK de React Native" description: "Aprende sobre el SDK de React Native de Adapty y sus principales características." --- [![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) ¡Bienvenido! Estamos aquí para que las compras in-app sean pan comido 🚀 Hemos creado el SDK de Adapty para React Native para que las compras in-app no sean un dolor de cabeza y puedas centrarte en lo que mejor sabes hacer: crear aplicaciones increíbles. Esto es lo que gestionamos por ti: - Gestiona compras, validación de recibos y administración de suscripciones sin configuración adicional - Crea y prueba flows y paywalls sin actualizar la app - Obtén analíticas de compras detalladas sin ninguna configuración: cohortes, LTV, churn y análisis de embudos incluidos - Mantén el estado de 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 Tanto si tu app está construida con **Expo** como con **React Native puro**, el SDK de Adapty es completamente compatible con ambos entornos. :::note Antes de entrar 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 configurarlo todo primero. ::: ## Empezar \{#get-started\} For a fully automated integration, use the [adapty-integration skill](https://github.com/adaptyteam/adapty-skills): 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-reactnative): Añade el SDK como dependencia a tu proyecto y actívalo en el código. Adapty funciona tanto en proyectos **Expo** como en proyectos de **React Native puro**. 2. [Activar compras con flows y paywalls](react-native-quickstart-paywalls): Configura el flujo de compra para que los usuarios puedan adquirir productos. Si prefieres crear tu propia UI, consulta [Implementar paywalls personalizados](react-native-quickstart-manual). 3. [Comprobar el estado de la suscripción](react-native-check-subscription-status): Verifica automáticamente el estado de suscripción del usuario y controla su acceso al contenido de pago. 4. [Identificar usuarios (opcional)](react-native-quickstart-identify): Asocia a los usuarios con sus perfiles de Adapty para garantizar que sus datos se almacenen de forma coherente en todos los dispositivos. ### Véalo en acción \{#see-it-in-action\} ¿Quiere ver cómo encaja todo? Aquí tiene: - **Apps de ejemplo**: Consulte nuestros [ejemplos completos](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples) con la configuración completa - **Tutorial en vídeo**: Siga la implementación paso a paso en el vídeo de abajo ## Conceptos principales \{#main-concepts\} Antes de entrar en el código, familiarízate con los conceptos clave que hacen funcionar a Adapty. Lo interesante del enfoque de Adapty es que solo los placements están hardcodeados en tu app. Todo lo demás —productos, diseños de paywall, precios y ofertas— se puede gestionar de forma flexible desde el Adapty Dashboard sin necesidad de actualizar la app: 1. [**Producto**](product) - Cualquier cosa disponible para comprar en tu app: suscripción, producto consumible o acceso de por vida. 2. **Flows y paywalls** - Productos agrupados con configuración, vinculados a un placement. Dos formas de crearlos, comparadas en [Flows y paywalls](flows-and-paywalls): - **[Flow & Paywall Builder](adapty-flow-builder)** - Interfaz visual sin código. Adapty renderiza la UI y gestiona la compra por ti. - **[Paywalls personalizados](paywalls)** - Sin configuración visual; construyes la UI en tu propio código y llamas a `makePurchase` tú mismo. Consulta [Implementar paywalls personalizados](react-native-quickstart-manual). En el código del SDK, ambos se recuperan 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. 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` - Tu ubicación principal de 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` para 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 utilizas para definir el acceso a las funciones de pago. --- # File: sdk-installation-reactnative --- --- title: "Instalar y configurar el SDK de React Native" description: "Guía paso a paso para instalar el SDK de Adapty en React Native para apps con suscripciones." --- Adapty ofrece un SDK de React Native completamente nativo que funciona tanto en proyectos **Expo** como en proyectos de **React Native puro**. Como estos entornos utilizan sistemas de compilación distintos, los pasos de instalación también son diferentes. Elige la guía de configuración que corresponda a tu proyecto: <CustomDocCardList /> --- # File: sdk-installation-react-native-expo --- --- title: "Instalar y configurar el SDK de Adapty para React Native en un proyecto Expo" description: "Guía paso a paso para instalar el SDK de Adapty para React Native en un proyecto Expo para apps basadas en suscripciones." --- :::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 para 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 renderiza flows, además de los paywalls del builder antiguo. 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? Consulta nuestras apps de ejemplo: - [Ejemplo de Expo dev build](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) con funcionalidad completa, incluyendo compras reales y Paywall Builder - [Ejemplo de Expo Go & Web](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) para pruebas con modo mock ::: Para ver una guía completa de implementación, también puedes ver el vídeo: ## Requisitos \{#requirements\} | Requisito | Versión | |---|---| | React Native | 0.75 o posterior | | iOS | 15.0 o posterior | | Swift | 6.2 o posterior, incluido con Xcode 26 | :::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. ::: ## Dependencias \{#dependencies\} El SDK de Adapty funciona con estas versiones de Google Play Billing Library en Android: | Versión del SDK de Adapty | Versión de Billing Library | |---|---| | 3.17.0 y posteriores | v8 | | 3.15.0–3.15.6 | v7 por defecto, v8 si otra dependencia la eleva | :::note La compatibilidad con una versión de Billing Library no implica que Adapty sea compatible con todas las funciones que Google introdujo en ella. Antes de adoptar una nueva capacidad de facturación de Google Play, consulta [Producto en Play Store](android-products). ::: ## Instalar el SDK de Adapty \{#install-adapty-sdk\} :::important El SDK de Adapty para React Native v4 y versiones posteriores distribuyen sus SDKs nativos para iOS (`Adapty`, `AdaptyUI`, `AdaptyPlugin`) como Swift packages. CocoaPods los instala igualmente en un proyecto Expo, pero los Swift packages enlazan de forma dinámica, por lo que tu build necesita frameworks dinámicos. A partir de la versión 4.2.3 del SDK, los proyectos que no puedan cambiar a frameworks dinámicos pueden instalar los SDKs nativos como pods. Consulta [Configura tu proyecto iOS](#enable-dynamic-frameworks). ::: [![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) Siempre recomendamos instalar la versión más reciente disponible del SDK — incluye las últimas correcciones de estabilidad y mejoras. :::important Se necesita [Expo Dev Client](https://docs.expo.dev/versions/latest/sdk/dev-client/) (una compilación de desarrollo personalizada) 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: ```sh npx expo install react-native-adapty npx expo prebuild ``` 2. Compila tu aplicación 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 ``` ### Configura tu proyecto iOS \{#enable-dynamic-frameworks\} React Native SDK 4.0 — que añade compatibilidad con [flows](adapty-flow-builder) — requiere **React Native 0.75 o posterior**. Instala el SDK: ```sh npx expo install react-native-adapty@^4.0.0 ``` El SDK de React Native instala sus SDK nativos de iOS (`Adapty`, `AdaptyUI`, `AdaptyPlugin`) de una de estas dos formas: - **CocoaPods + SPM (por defecto, SDK 4.0 y posteriores)**: CocoaPods añade los paquetes Swift de Adapty a tu proyecto. Los paquetes Swift se enlazan de forma dinámica, por lo que tu compilación debe cambiar a frameworks dinámicos. Esta es la opción recomendada. - **CocoaPods sin SPM (SDK 4.2.3 y posteriores)**: CocoaPods instala los SDKs nativos como pods desde el [repositorio de specs de Adapty](https://github.com/adaptyteam/AdaptySDK-CocoaPods-Specs), no desde el repositorio público de specs de CocoaPods, que [pasará a ser de solo lectura en diciembre de 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/). Los pods no requieren frameworks dinámicos. <Tabs groupId="ios-package-manager" queryString> <TabItem value="cocoapods" label="CocoaPods + SPM (recommended)" default> Para habilitar los frameworks dinámicos en Expo, usa 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 ``` Si la compilación de iOS falla con `'React/RCTBridge.h' file not found`, consulta [la entrada de solución de problemas](#react-header-not-found). </TabItem> <TabItem value="cocoapods-pods" label="CocoaPods without SPM"> :::warning Usa esta opción solo si tu proyecto no puede cambiar a frameworks dinámicos — por ejemplo, porque depende de enlazado estático o de Flipper. Para todos los demás proyectos, instala los SDKs nativos como paquetes Swift. ::: Esta opción requiere SDK 4.2.3 o posterior. Para instalar los pods, habilita la opción `iosDisableSPM` del plugin de configuración de Adapty en `app.json` (o `app.config.js`): ```json showLineNumbers title="app.json" { "expo": { "plugins": [ ["react-native-adapty", { "iosDisableSPM": true }] ] } } ``` Luego regenera el proyecto nativo: ```sh npx expo prebuild ``` Con `iosDisableSPM` habilitado, no necesitas el plugin `expo-build-properties` para Adapty. </TabItem> </Tabs> 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 el [Flow & Paywall Builder](adapty-flow-builder), sigue el [inicio rápido](react-native-quickstart-paywalls). - Si creas tu propia interfaz 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, usa los [consejos](#development-environment-tips). ::: ## Activa el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Si vas a usar el [Flow & Paywall Builder](adapty-flow-builder), necesitas el módulo AdaptyUI. Se activa automáticamente al activar el módulo principal; no tienes que 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 }, }); ``` ### Habilitar la atribución de Adapty \{#enable-adapty-attribution\} :::info Este parámetro está disponible a partir de la versión 4.1 del SDK. ::: Si usas [Adapty Attribution](user-acquisition), establece `adaptyAttributionEnabled` en `true` al activar el SDK. El valor predeterminado es `false`: sin este parámetro, el SDK no registra instalaciones ni entrega los detalles de instalación a tu app. En versiones del SDK anteriores a la 4.1, Adapty Attribution se habilita automáticamente. ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { adaptyAttributionEnabled: 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** flows ni paywalls del builder antiguo (AdaptyUI). - Los módulos nativos de Adapty quedan **completamente ignorados**: 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. Para probar compras reales y paywalls del Paywall Builder, utiliza un Expo Dev Client o una compilación 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 } ``` :::note Con esta opción activada, el SDK omite toda la llamada de activación una vez que ha sido activada, por lo que los cambios en los parámetros de activación no surten efecto en un fast refresh. Para aplicar nuevos parámetros de activación, cierra la app completamente y vuelve a abrirla. ::: ## 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 librerías de tu proyecto definen reglas de copia de seguridad personalizadas, tendrás que configurarlas manualmente. ::: #### La compilación de iOS falla con `'React/RCTBridge.h' file not found` \{#react-header-not-found\} Con los [frameworks dinámicos habilitados para el SDK de Adapty v4](#enable-dynamic-frameworks), la compilación de iOS puede fallar en `expo-updates`, `@expo/ui` u otro paquete con fuentes Objective-C: ``` error: 'React/RCTBridge.h' file not found ``` El error está causado por un bug en el paquete `expo-modules-autolinking` (una dependencia de `expo`). Las versiones afectadas son 57.0.5–57.0.9 (Expo SDK 57) y 56.0.19–56.0.21 (Expo SDK 56); el bug está corregido en 57.0.10 y 56.0.22. Para solucionar el error de compilación, actualiza `expo-modules-autolinking` a la versión corregida y regenera el proyecto nativo: ```sh npm update expo-modules-autolinking npx expo prebuild ``` Si la versión corregida aún no está disponible para tu versión de Expo SDK, compila React Native desde el código fuente. Esto evita el bug a costa de tiempos de compilación más largos en iOS: ```json title="app.json" { "expo": { "plugins": [ [ "expo-build-properties", { "ios": { "useFrameworks": "dynamic", "buildReactNativeFromSource": true } } ] ] } } ``` --- # File: sdk-installation-react-native-pure --- --- title: "Instalar y configurar el SDK de Adapty en un proyecto React Native puro" description: "Guía paso a paso para instalar el SDK de Adapty en React Native para aplicaciones basadas en suscripciones." --- :::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 renderiza flows, además de los paywalls del builder antiguo. 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\} | Requisito | Versión | |---|---| | React Native | 0.75 o posterior. La [integración SPM de React Native](https://reactnative.dev/blog/2026/08/11/react-native-0.87#experimental-swift-package-manager-support-for-ios) requiere 0.87 o posterior. | | iOS | 15.0 o posterior | | Swift | 6.2 o posterior, incluido con Xcode 26 | :::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. ::: ## Dependencias \{#dependencies\} El SDK de Adapty funciona con estas versiones de la Google Play Billing Library en Android: | Versión del SDK de Adapty | Versión de la Billing Library | |---|---| | 3.17.0 y posteriores | v8 | | 3.15.0–3.15.6 | v7 por defecto, v8 si otra dependencia la eleva | :::note La compatibilidad con una versión de la Billing Library no significa que Adapty sea compatible con todas las funciones que Google introdujo en ella. Antes de adoptar una nueva capacidad de facturación de Google Play, consulta [Producto en Play Store](android-products). ::: ## Instalar el SDK de Adapty \{#install-adapty-sdk\} [![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) Siempre recomendamos instalar la versión más reciente disponible del SDK — incluye las últimas correcciones de estabilidad y mejoras. Añade el paquete a tu proyecto: ```sh showLineNumbers title="Shell" # using npm npm install react-native-adapty # or using yarn yarn add react-native-adapty ``` ### Configura tu proyecto iOS \{#set-up-your-ios-project\} El SDK de React Native instala sus SDKs nativos de iOS (`Adapty`, `AdaptyUI`, `AdaptyPlugin`) de una de tres maneras. Las dos primeras los instalan como paquetes Swift, que es la opción recomendada; la diferencia está en qué gestiona la instalación. - **CocoaPods + SPM (predeterminado, SDK 4.0 y posteriores)**: Tu proyecto iOS mantiene su `Podfile`, y el helper `spm_dependency` de React Native añade los paquetes Swift de Adapty al proyecto Pods. Los paquetes Swift enlazan de forma dinámica, por lo que el Podfile debe cambiar a frameworks dinámicos. - **Integración SPM de React Native (SDK 4.1 y posteriores)**: Tu proyecto iOS elimina CocoaPods, y React Native resuelve los paquetes Swift directamente desde el manifiesto `Package.swift` que incluye Adapty. [React Native añadió esta integración en la versión 0.87](https://reactnative.dev/blog/2026/08/11/react-native-0.87#experimental-swift-package-manager-support-for-ios) como vista previa y aún no la recomienda para producción. - **CocoaPods sin SPM (SDK 4.2.3 y posteriores)**: Tu `Podfile` instala los SDK nativos como pods desde el [repositorio de specs de Adapty](https://github.com/adaptyteam/AdaptySDK-CocoaPods-Specs), no desde el repositorio público de specs de CocoaPods, que [pasará a ser de solo lectura en diciembre de 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/). Los pods no requieren frameworks dinámicos. Usa esta opción solo si tu proyecto no puede cambiar a frameworks dinámicos — por ejemplo, porque depende de enlazado estático o de Flipper. <Tabs groupId="ios-package-manager" queryString> <TabItem value="cocoapods" label="CocoaPods + SPM (recommended)" default> Declara los frameworks dinámicos en el target de tu `ios/Podfile`: ```ruby showLineNumbers title="ios/Podfile" use_frameworks! :linkage => :dynamic ``` Si anteriormente añadiste `Adapty`, `AdaptyUI` o `AdaptyPlugin` como sub-dependencias de CocoaPods, elimina primero cualquier línea `pod 'Adapty'`, `pod 'AdaptyUI'` o `pod 'AdaptyPlugin'` de tu `Podfile`. Luego instala los pods: ```sh showLineNumbers title="Shell" cd ios && pod install --repo-update ``` :::warning Cambiar del enlace estático predeterminado a frameworks dinámicos puede entrar en conflicto con bibliotecas que aún no son compatibles con encabezados 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. ::: </TabItem> <TabItem value="spm" label="React Native's SPM integration"> En la raíz de tu proyecto, ejecuta: ```sh showLineNumbers title="Shell" cd ios && npx react-native spm ``` React Native lee el manifiesto `Package.swift` de Adapty y resuelve los SDK nativos de iOS por su cuenta. No es necesario añadir nada a tu proyecto de Xcode. Mantén `ios/Podfile` en su lugar, aunque no instale ningún pod. El CLI de React Native localiza tu proyecto iOS buscando el Podfile, por lo que sin él el autolinking omite iOS por completo. Si tu app usa pods además de React Native, mantenlos en el Podfile, ejecuta `pod install` y abre el `.xcworkspace` en lugar del `.xcodeproj`. El workspace abarca ambos gestores de paquetes. :::note En un clone nuevo o un checkout de CI, ejecuta `npx react-native spm` una vez antes de compilar. El proyecto de Xcode resincroniza las dependencias durante una compilación, después de que Xcode ya ha resuelto el grafo de paquetes, por lo que una compilación iniciada justo tras una instalación puede tener éxito sin Adapty. ::: Para volver a CocoaPods, ejecuta `npx react-native spm deinit` y luego `pod install`. </TabItem> <TabItem value="cocoapods-pods" label="CocoaPods without SPM"> :::warning Usa esta opción solo si tu proyecto no puede cambiar a frameworks dinámicos — por ejemplo, porque depende de enlazado estático o de Flipper. Para todos los demás proyectos, instala los SDKs nativos como paquetes Swift. ::: Esta opción requiere SDK 4.2.3 o posterior. En `ios/Podfile`, añade los helpers de Podfile de Adapty. Luego, en el target de tu app, llama a `adapty_disable_spm!` **antes** de `use_native_modules!`: ```ruby showLineNumbers title="ios/Podfile" require Pod::Executable.execute_command('node', ['-p', 'require.resolve( "react-native-adapty/ios/adapty_podfile.rb", {paths: [process.argv[1]]}, )', __dir__]).strip target 'MyApp' do adapty_disable_spm! config = use_native_modules! # ... end ``` `adapty_disable_spm!` declara los pods `Adapty`, `AdaptyUI` y `AdaptyPlugin` y los ancla al repositorio de specs de Adapty, por lo que no necesitas añadir una línea `source` para ese repositorio. Elimina cualquier línea explícita `pod 'Adapty'`, `pod 'AdaptyUI'` o `pod 'AdaptyPlugin'` de tu `Podfile`. Si `adapty_disable_spm!` aparece después de `use_native_modules!`, `pod install` falla y te pide que muevas la llamada. Luego instala los pods: ```sh showLineNumbers title="Shell" cd ios && pod install --repo-update ``` Cuando actualices `react-native-adapty` más adelante, `pod install` no actualiza el repositorio spec de Adapty. Para actualizar los SDKs nativos, ejecuta: ```sh showLineNumbers title="Shell" cd ios pod repo update pod update Adapty AdaptyUI AdaptyPlugin ``` </TabItem> </Tabs> ## 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 el [Flow & Paywall Builder](adapty-flow-builder), sigue la [guía de inicio rápido](react-native-quickstart-paywalls). - Si creas 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, usa los [consejos](#development-environment-tips). ::: ## Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Si planeas usar el [Flow & Paywall Builder](adapty-flow-builder), necesitas el módulo AdaptyUI. Se activa automáticamente al activar el módulo principal; no tienes que 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 }, }); ``` ### Habilitar la atribución de Adapty \{#enable-adapty-attribution\} :::info Este parámetro está disponible a partir de la versión 4.1 del SDK. ::: Si usas [Adapty Attribution](user-acquisition), establece `adaptyAttributionEnabled` en `true` al activar el SDK. El valor predeterminado es `false`: sin este parámetro, el SDK no registra instalaciones ni entrega los detalles de instalación a tu app. En versiones del SDK anteriores a la 4.1, la atribución de Adapty se habilita automáticamente. ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { adaptyAttributionEnabled: 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 flag 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 } ``` :::note Con esta opción activada, el SDK omite toda la llamada de activación una vez que ha sido activado, por lo que los cambios en los parámetros de activación no surten efecto con un refresco rápido. Para aplicar nuevos parámetros de activación, cierra la app completamente y vuelve a abrirla. ::: #### Configurar el modo mock para pruebas locales \{#set-up-mock-mode-for-local-testing\} Durante el desarrollo local y las pruebas, puedes activar el modo mock para evitar necesitar cuentas sandbox de App Store/Google Play y acelerar la iteración. El modo mock omite por completo 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 flows de compra de App Store / Google Play y **no crea** transacciones reales. - **No renderiza** flows ni paywalls del constructor antiguo (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 API no es válida, no se producirá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 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 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 (que normalmente se encuentra 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 espacio de nombres `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 finales e indique al fusionador de manifiestos que reemplace los valores de las bibliotecas: ```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 usa diferentes 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 ejemplo. Reemplaza o añade reglas para cualquier otro SDK que estés usando 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 heredado 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 asegurarte de que las compras funcionen correctamente, utiliza solo 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" /> ``` #### Error de versión del plugin Kotlin Gradle en React Native por debajo de 0.73 \{#kotlin-gradle-plugin-version-error-on-react-native-below-073\} En React Native anteriores a 0.73.0, el build de Android falla por la versión del plugin Kotlin Gradle. Actualiza el archivo `/android/build.gradle` y asegúrate de que incluya 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" } } ... ``` #### 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\} :::note Esto aplica cuando los SDK nativos de iOS se instalan como pods de CocoaPods: en **SDK 3.x**, y en SDK 4.2.3 y versiones posteriores con la opción [CocoaPods without SPM](#set-up-your-ios-project). Cuando se instalan como paquetes de Swift, una sobreescritura de `SWIFT_VERSION` en `post_install` no llega a ellos. ::: Al compilar tu app de React Native para iOS, es posible que veas errores de compilación de Swift 6 en los targets de pods de Adapty. Los síntomas típicos incluyen discrepancias 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 puede quedarse en Swift 5 — solo los targets de los pods de Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`, `AdaptyLogger`, `AdaptyPlugin`, y a partir del SDK 4.2.3 también `AdaptyCodable`) necesitan compilarse con Swift 6. La causa más habitual 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 ``` **Fix**: Excluye los targets del pod de Adapty de la sobreescritura: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin AdaptyCodable].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 y Paywall Builder en el SDK de React Native" description: "Guía de inicio rápido para habilitar compras in-app con el Flow y Paywall Builder de Adapty." --- 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 & paywalls**](adapty-flow-builder) – pantallas que presentan productos a los usuarios, creadas en el Flow & Paywall Builder sin código. El SDK los recupera mediante `getFlow`. Si prefieres construir la interfaz con tu propio código, consulta [Implementar paywalls personalizados](react-native-quickstart-manual). - [**Placements**](placements) – dónde y cuándo mostrar flows & paywalls en tu app (como `main`, `onboarding`, `settings`). Los vinculas a placements en el dashboard y los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar opciones distintas 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 usarla | |---|---|---| | Adapty Flow & Paywall Builder | ✅ Fácil | [Creas un flow o paywall completo y listo para compras en el editor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona toda la lógica de compra, validación de recibos y administración de suscripciones. | | Paywalls personalizados | 🟡 Media | Implementas la UI 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](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 o paywall creado en el Adapty Flow & Paywall Builder.** Si prefieres construir la UI del paywall tú mismo, consulta [Implementar paywalls personalizados](react-native-quickstart-manual). ::: Para mostrar un flow o paywall creado en el Adapty Flow & Paywall Builder, en el código de tu app solo necesitas: 1. **Obtén el flow o el paywall**: Obtenlo desde Adapty. 2. **Muéstralo y Adapty gestionará las compras**: Muestra la vista en tu app. 3. **Gestiona las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abre enlaces o cierra el paywall cuando el usuario pulse los 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 o un paywall](paywall-builder-templates) y [añade productos a él](paywall-product-block). 4. [Crea un placement y añade tu flow o paywall a él](create-placement). 5. [Instala y activa el SDK de Adapty](sdk-installation-reactnative) en el código de tu app. Esta guía usa las APIs del SDK de Adapty para React Native v4. ## 1. Obtener el flow o el paywall \{#1-get-the-flow-or-paywall\} Tus flows y paywalls están asociados a placements configurados en el dashboard. Los placements te permiten mostrar distintos flows o paywalls a diferentes audiencias o ejecutar [pruebas A/B](ab-tests). Para obtener un flow o paywall creado en Adapty Flow & Paywall 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 los estilos 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 o el paywall \{#2-display-the-flow-or-paywall\} 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 integrar 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`, 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, vuelve a llamar a `createFlowView` 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 o un paywall, consulta nuestra [guía](react-native-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#3-handle-button-actions\} Cuando los usuarios hacen clic en botones del flow o el paywall, el SDK de React Native gestiona automáticamente las compras, la restauración, el cierre de pantalla y la apertura de URLs. Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren gestionar las acciones en tu código. También puedes querer anular su comportamiento predeterminado. Por ejemplo, aquí está el comportamiento predeterminado del botón de cierre. No es necesario añadirlo en el código, pero aquí puedes ver cómo se haría 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 flow o paywall está listo para mostrarse en la aplicación. [Prueba tus compras](react-native-test) para asegurarte de que puedes completar una compra de prueba desde el flow. Ahora necesitas [verificar 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-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." --- :::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. ::: El skill `adapty-integration` automatiza la integración de Adapty de principio a fin: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación relevante de Adapty en cada etapa. Funciona en cualquier agente de programación que soporte skills: Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI y otros. El skill forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener los nuevos skills a medida que se publiquen, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Para iniciar la integración, pide a tu agente: ``` Integrate Adapty into my app ``` El skill hace algunas preguntas de configuración y luego te guía a través de 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-integration skill](https://github.com/adaptyteam/adapty-skills): 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 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, incluido cuándo abrir el Dashboard para conectar tus stores. El CLI de skills no actualiza el skill por sí solo. Para obtener la última versión, ejecuta `npx skills update`. ### 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 flow o paywall y un placement**: En el Adapty Dashboard, crea un flow (o un paywall si vas a construir la interfaz tú mismo), 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.getFlow("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\} No son necesarios para empezar a escribir código, 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) - **Flows y placements adicionales**: Añade más llamadas `getFlow` 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) y [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 Flow & Paywall Builder**](adapty-flow-builder): Creas flows en el editor no-code de Adapty y el SDK los renderiza automáticamente. - [**Paywalls personalizados**](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): Conservas tu infraestructura de compras existente y usas Adapty solo para análisis e integraciones. ¿No sabes cuál elegir? Consulta la [tabla comparativa en el 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 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="Flow & Paywall Builder" default> **Guías:** - [Habilitar compras con Flow y Paywall Builder (inicio rápido)](react-native-quickstart-paywalls) - [Obtener flows y su configuración](react-native-get-pb-paywalls) - [Mostrar flows](react-native-present-paywalls) - [Gestionar eventos de flow](react-native-handling-events-1) - [Responder a acciones de 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] - **Expected:** El paywall aparece con los productos configurados. Al pulsar un producto se activa el diálogo de compra en sandbox. - **Gotcha:** 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 personalizados"> **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](react-native-quickstart-manual) - [Obtener paywalls y productos personalizados](fetch-paywalls-and-products-react-native) - [Presentar paywalls personalizados](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-paywalls --- --- title: "Flows y paywalls - React Native" description: "Muestra y gestiona flows y paywalls creados con los editores no-code de Adapty en tu app de React Native." --- ## Mostrar paywalls \{#display-paywalls\} ### Adapty Flow & Paywall Builder <CustomDocCardList ids={['react-native-get-pb-paywalls', 'react-native-present-paywalls', 'react-native-handling-events-1', 'react-native-handle-paywall-actions']} /> :::tip Para empezar rápidamente con los flows y paywalls de Adapty, consulta nuestra [guía de inicio rápido](react-native-quickstart-paywalls). ::: ### Implementar paywalls personalizados \{#implement-custom-paywalls\} <CustomDocCardList ids={['react-native-quickstart-manual', 'fetch-paywalls-and-products-react-native', 'present-remote-config-paywalls-react-native', 'react-native-making-purchases']} /> Para más guías sobre cómo implementar paywalls personalizados y gestionar compras, consulta [Implementar paywalls personalizados](react-native-implement-paywalls-manually). ## Características útiles \{#useful-features\} <CustomDocCardList ids={['react-native-use-fallback-paywalls', 'react-native-web-paywall']} /> --- # 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](adapty-flow-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 diseñados con los builders de Adapty. Si implementas paywalls personalizados, consulta el tema [Obtener paywalls y productos para paywalls personalizados 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. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Productos en el Adapty Dashboard**: [Crea tus productos](create-product). - **Un flow con productos**: Créalo en el [Flow & Paywall Builder](adapty-flow-builder) y [asigna productos](paywall-product-block). - **Un placement**: [Crea un placement](create-placement) y asígnale tu flow. - **SDK de Adapty instalado**: Consulta la [guía de instalación del SDK para React Native](sdk-installation-reactnative). ## Obtener un flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall en el builder, no tienes que preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Tanto el flow como el paywall ya contienen qué debe mostrarse y cómo 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. 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** | 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>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y acude al servidor solo cuando no hay nada en caché. 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>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor `maxAgeSeconds` que indiques, y acude al servidor cuando sea más antigua.</p><p></p><p>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.</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, 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 agotar el tiempo de espera ligeramente después de lo especificado en `loadTimeout`, ya que la operación puede constar de diferentes 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 bien `TimeInterval.seconds(5)`. Para no establecer ningún límite, usa `TimeInterval.INFINITE`.</p> | Parámetros de respuesta: | Parameter | Description | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | Un objeto `AdaptyFlow` con los identificadores del flow (`id`, `variationId`), el nombre de la variación (`variationName`, opcional, SDK 4.2+), el nombre, el placement, sus variaciones de paywall (`paywalls`), los Remote Configs (`remoteConfigs`) y, a partir del SDK 4.1, un indicador `hasViewConfiguration`. | ## Obtener la configuración de la vista \{#fetch-the-view-configuration\} :::important Asegúrate de [publicar el flow](builder-save-publish). Un flow con cambios sin publicar tiene el estado **Dirty**, y su placement continúa mostrando la última versión publicada. ::: Si el placement fue diseñado en el **Flow & Paywall Builder** o en el **old Paywall Builder**, Adapty renderiza la interfaz por ti. Crea la vista con `createFlowView` y luego [presenta el flow o paywall](react-native-present-paywalls). Si el placement es un paywall personalizado sin interfaz en el Builder, [renderízalo tú mismo](present-remote-config-paywalls-react-native). En el SDK 4.1 y versiones posteriores, el flag `hasViewConfiguration` del flow permite distinguirlos antes de crear la vista: ```typescript showLineNumbers if (flow.hasViewConfiguration) { const view = await createFlowView(flow); await view.present(); } else { // Render your own screen from flow.remoteConfigs and flow.paywalls } ``` En la versión 4.0, el flag no existe y `createFlowView` lanza un `AdaptyError` si el flow no tiene configuración de vista. En el SDK de React Native, llama directamente a `createFlowView` — no necesitas obtener primero la configuración de la vista. :::warning El resultado del método `createFlowView` solo puede usarse una vez. Si necesitas volver a usarlo, 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. | | **locale** | opcional | El identificador de la [localización del flow](add-paywall-locale-in-adapty-paywall-builder) con la que se renderizará la vista — por ejemplo, `en` o `pt-br`. Si se omite, la vista se renderiza en `en`, o en la localización predeterminada del flow cuando este no tiene `en`. Requiere SDK 4.0.2 o posterior. Consulta [Localizaciones y códigos de idioma](react-native-localizations-and-locale-codes). | | **customLayoutId** | <p>opcional</p><p>predeterminado: `undefined`</p><p>SDK 4.1+</p> | Reservado para una funcionalidad de Flow & Paywall Builder que aún no se ha publicado. Hasta que el builder pueda definir IDs de layout, cualquier valor que pases no coincidirá con ningún layout y `createFlowView` lanzará un `AdaptyError`. Déjalo sin definir. | | **customTags** | opcional | Los valores de tu app para las [etiquetas personalizadas](flow-custom-tags) del flow, en un objeto con clave por el nombre completo de cada etiqueta. Para más detalles, consulta [Pasar datos a flows](react-native-flow-custom-tags). | | **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. Predeterminado: `false`. | | **android.enableSafeArea** | opcional | Solo Android (se ignora en iOS). Pásalo como objeto anidado: `android: { enableSafeArea: true }`. Cuando es `true`, la vista del flow aplica paddings de área segura. El valor predeterminado es `true` para la presentación modal (`createFlowView` + `present()`) y `false` para el componente embebido `AdaptyFlowView`. 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: `'reload_revalidating_cache_data'` | <p>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud falla. 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 inestable, considera `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor de `maxAgeSeconds` que indiques junto a ella, y va al servidor cuando sea más antigua.</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 imágenes y vídeos en tu flow/paywall, implementa recursos personalizados. Las imágenes y vídeos hero tienen IDs predefinidos: `hero_image` y `hero_video`. En un paquete de recursos personalizado, seleccionas estos elementos por sus IDs y personalizas su comportamiento. Para otras imágenes y vídeos, debes [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. - Mostrar contenido multimedia incluido en la app para que la primera pantalla se muestre sin necesidad de descarga. Consulta [Mostrar contenido multimedia de la primera pantalla desde el bundle de la app](react-native-optimize-paywall-fetching#show-first-screen-media-from-the-app-bundle). :::important Para usar esta función, actualiza el SDK de Adapty para React Native 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 no se encuentra un recurso, el flow/paywall usará su apariencia predeterminada como respaldo. ::: </SDKv4> <SDKv3> Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el antiguo 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 Los paywalls creados con el Paywall Builder del SDK 3.x requieren la versión 3.0 o superior del SDK de React Native. ::: Por favor, ten en cuenta que este tema hace referencia a paywalls personalizadas con Paywall Builder. Si implementas paywalls personalizadas, consulta el tema [Obtener paywalls y productos para paywalls personalizadas 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>predeterminado: `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 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 obtener más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | predeterminado: `'reload_revalidating_cache_data'` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. De forma predeterminada, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor `maxAgeSeconds` que le pases junto a ella, y va al servidor cuando sea más antigua.</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é que se actualiza regularmente (descrita anteriormente) y los [paywalls de respaldo](fallback-paywalls). También utilizamos 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 asegurar la fiabilidad incluso cuando la conexión a internet es limitada.</p> | | **loadTimeoutMs** | 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 un poco más tarde de lo especificado en `loadTimeout`, ya que la operación puede consistir en 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` con una lista de IDs de producto, el identificador del paywall, 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 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; de lo contrario, [gestíonalo como un paywall personalizado](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 la vista manualmente. :::warning El resultado del método `createPaywallView` solo puede usarse una vez. Si necesitas volver a usarlo, vuelve a llamar al método `createPaywallView`. Llamarlo dos veces sin recrearlo puede producir 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 admites varios idiomas, añade una localización a tu paywall. Para los códigos a utilizar, consulta [Localizaciones y códigos de idioma](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>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](react-native-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: `'reload_revalidating_cache_data'` | <p>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. Los usuarios puede que no obtengan los datos más recientes, pero tendrá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 utilizarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor `maxAgeSeconds` que se le pasa, y va al servidor una vez que sea más antigua.</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 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. ::: 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 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 React Native con Adapty." --- <SDKv4> <MethodPromo method="getFlow" label="Display flows and paywalls" /> Si has creado un flow o un paywall en el antiguo Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu aplicación móvil para mostrárselo al usuario. Un flow de este tipo contiene tanto lo que debe mostrarse dentro de él como la forma en que debe mostrarse. Antes de comenzar, asegúrate de que: 1. Has creado un [flow](paywall-builder-templates) o un [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 renderizados por Adapty**, que requieren SDK v4.0 o posterior. El proceso para presentar flows es diferente para los paywalls personalizados. - Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](present-remote-config-paywalls-react-native). ::: El SDK de Adapty para React Native ofrece dos formas de presentar flows y paywalls: - **Componente React**: Un componente integrado que puedes incorporar a la arquitectura y al 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, locale: 'en', // The localization to render the flow with }), []); 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']>((view) => {}, []); 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 cubre únicamente los paywalls creados con el antiguo Paywall Builder para **Adapty SDK 3.x**. Requieren SDK v3.0 o posterior. La presentación de un paywall creado para SDK 2.x o anterior, o de un paywall personalizado, funciona de forma diferente. - Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](present-remote-config-paywalls-react-native). ::: El SDK de Adapty para React Native ofrece dos formas de presentar paywalls: - **Componente React**: Un componente integrado que puedes incorporar a 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 en el Adapty Flow & Paywall Builder, 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 **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 personalizados: ```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 los botones correctamente: 1. En el antiguo Paywall Builder, añade un botón y asígnale una acción predefinida o un ID de acción personalizada. 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, 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 en el Flow & 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 flow. A continuación aprende cómo responder a estos eventos. Para controlar o monitorear los procesos que ocurren en la pantalla del flow dentro de tu app, 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']>((view) => {}, []); 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(view) { /***/ }, 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 { "view": { "id": "3f8a1c7e-9b24-4d51-8e30-6c5b2a9f1d47", "placementId": "onboarding_paywall", "variationId": "d21c4b6a-57e8-4f39-b0a2-8c7e13f5d94b", "locale": "es" } } // 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, p. ej., 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** | Solo en presentación modal: se invoca cuando el usuario toca el botón **Back** del sistema Android. | | **onCloseButtonPress** | Se invoca cuando el botón de cierre está visible y el usuario lo toca. Se recomienda cerrar la pantalla del flow en este manejador. | | **onPurchaseCompleted** | Se invoca cuando la compra finaliza, ya sea exitosa, 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., aprobación parental requerida) desencadenan este evento, no `onPurchaseFailed`. | | **onPurchaseStarted** | Se invoca cuando el usuario toca 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 ante cancelaciones del usuario ni pagos pendientes, que desencadenan `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 realiza correctamente 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 flow, lo que permite monitorizar qué elige el usuario antes de la compra. | | **onError** | 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, comunícanoslo. | | **onLoadingProductsFailed** | Se invoca cuando la carga de productos falla y proporciona `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 y proporciona el `view` que apareció — consulta [El argumento view](#the-view-argument). En iOS, también se invoca cuando el usuario toca el [botón de paywall web](web-paywall#step-2a-add-a-web-purchase-button) dentro de un flow y se abre un paywall web en un navegador in-app. | | **onDisappeared** | Solo en 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 realizar una compra, independientemente de si se completó con éxito o no. | | **onAnalytics** | Se invoca cuando un flow reporta un evento de analítica, como una vista de pantalla. Consulta [Eventos de analítica](#analytics-events) más abajo. | | **onRequestAppReview** | Reservado para solicitudes de valoración de la app desde un flow. Los flows aún no desencadenan 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 desencadenan solicitudes de permisos, por lo que no es necesario implementarlo. | | **onObserverPurchaseInitiated** | Solo en modo observador: se invoca cuando el usuario toca el botón de compra en un flow. Adapty no realiza la compra — efectúala con tu propio código de compra y luego reporta 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 toca el botón de restauración en un flow. Adapty no realiza la restauración — hazlo tú mismo y luego reporta las transacciones restauradas. Consulta [Gestionar compras en modo observador](#handle-purchases-in-observer-mode) más abajo. | #### El argumento view \{#the-view-argument\} El argumento `view` requiere React Native SDK 4.0.3 o posterior. De todos los manejadores de flow, solo `onAppeared` recibe una descripción de la propia vista: un objeto `FlowEventView` con estos campos: | Campo | Descripción | |:-------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **id** | El identificador de esta instancia de vista. Es interno al SDK y no corresponde a ningún elemento del Adapty Dashboard. | | **placementId** | El [placement](placements) para el que se obtuvo el flow. | | **variationId** | La variante a la que se resolvió el flow, para atribuir tus propios análisis a una [prueba A/B](ab-tests). | | **locale** | La [localización del flow](add-paywall-locale-in-adapty-paywall-builder) con la que se construyó la vista. Puede diferir de la que solicitaste si el flow no tiene esa localización. Consúltala para alinear el resto de tu pantalla con el idioma en que se renderizó el flow. Consulta [Usar localizaciones y códigos de idioma](react-native-localizations-and-locale-codes). | ### Eventos de analítica \{#analytics-events\} ```javascript showLineNumbers title="React Native" const unsubscribe = view.setEventHandlers({ onAnalytics(name, params) { return false; // keep the flow open }, }); ``` Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. Consulta [Registrar vistas de pantallas del flow](react-native-flow-screen-views) para saber qué hacer con ellos. Un flow también informa de los valores que los usuarios escriben y seleccionan. Consulta [Procesar datos de flows](react-native-flow-input) para leerlos. ### 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. ::: Os 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. Esos 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. Aprende a responder a estos eventos a continuación. :::warning Esta guía cubre únicamente **paywalls creados con el builder antiguo**, que requieren el SDK de Adapty 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-flow-screen-views --- --- title: "Rastrear vistas de pantallas de flow en React Native SDK" description: "Recibe un evento cada vez que un usuario abre una pantalla de flow en tu app de React Native y reenvíalo a tu análisis de producto." --- Cada vez que un usuario abre una pantalla en un [flow](adapty-flow-builder), el SDK envía un evento `flow_screen_showed` a tu app. Reenvíalo a Amplitude, Mixpanel o el análisis de producto que uses, para que puedas analizar las pantallas del flow junto con el resto de los eventos de tu app. :::tip Para ver cuántos usuarios llegan a cada pantalla y dónde abandonan, no necesitas ningún código: el [embudo de flow](flow-funnel) ya lo muestra en el Adapty Dashboard. Usa esta guía para construir el mismo embudo en tu propia herramienta de analítica, o para combinar pantallas de flow con eventos que Adapty no conoce. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty React Native SDK v4 o posterior**: El callback de análisis de flow no existe en versiones anteriores. - **Una versión del flow publicada después de que se lanzara esta funcionalidad**: Adapty genera los eventos de pantalla al publicar un flow, por lo que un flow publicado antes de entonces no reportará nada. Si no llegan eventos a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Qué recibes \{#what-you-receive\} Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: Las vistas de pantalla llegan al mismo callback de análisis que cualquier otro evento de flow. Consulta [Gestionar eventos de flow y paywall](react-native-handling-events-1) para saber dónde registrarlo. ## Reenvía cada vista de pantalla a tu analítica \{#forward-every-screen-view-to-your-analytics\} Comprueba el nombre del evento antes de leer cualquier otra cosa, porque un flow reporta otros eventos a través del mismo callback. ```javascript showLineNumbers title="React Native" const unsubscribe = view.setEventHandlers({ onAnalytics(name, params) { if (name === 'flow_screen_showed') { const screenId = params.instanceId; const screenOrder = params.screen_order; // Send screenId and screenOrder to your analytics provider here. } return false; // keep the flow open }, }); ``` ## Ejecutar código cuando el usuario llega al final \{#run-code-when-a-user-reaches-the-end\} `is_last_screen` es `true` en una pantalla a partir de la cual el flow no puede avanzar. Úsalo para desbloquear contenido, marcar el onboarding como completado o enviar tu propio evento de finalización, sin necesidad de añadir una acción de botón en el builder. ```javascript showLineNumbers title="React Native" const unsubscribe = view.setEventHandlers({ onAnalytics(name, params) { if (name === 'flow_screen_showed' && params.is_last_screen) { // The user reached the end of the flow. } return false; // keep the flow open }, }); ``` Llegar a la última pantalla no es lo mismo que cerrar el flow. Un usuario puede ver la pantalla final y cerrar el flow sin realizar ninguna acción, y un flow con ramificaciones puede terminar en varias pantallas diferentes. Para saber que el flow en sí desapareció, usa `onDisappeared` en su lugar. --- # File: react-native-flow-input --- --- title: "Procesar datos de flows en React Native SDK" description: "Guarda y usa los datos que tus usuarios introducen en flows en tu app de React Native con el SDK de Adapty." --- Cuando un usuario escribe en un campo de texto, responde un cuestionario o activa un interruptor en un [flow](adapty-flow-builder), el SDK pasa el valor a tu app a través de su callback de análisis. Las apps suelen usar esos datos para: - **Registrar usuarios en tu propio backend**: Toma el correo electrónico y el nombre que el usuario introdujo en tu flow de onboarding y crea su cuenta cuando el flow se cierre. - **Guardar respuestas y preferencias**: Registra lo que eligió el usuario para que tu app pueda usarlo más adelante — por ejemplo, [escríbelo en su perfil de Adapty](react-native-setting-user-attributes) como atributos personalizados. - **Personalizar flows futuros**: Guarda las respuestas del cuestionario como atributos personalizados y luego apunta a un placement posterior para que cada segmento vea un flow diferente, o un paywall distinto dentro de él. - **Mostrar valores calculados a partir de las respuestas**: Por ejemplo, calcula el IMC a partir del peso y la altura del usuario y [pásalo a una etiqueta personalizada en el siguiente flow](react-native-flow-custom-tags#example-show-a-bmi-from-onboarding-answers). - **Alimentar plataformas de analítica de terceros**: Reenvía las respuestas a Amplitude, Mixpanel o la plataforma de analítica de producto que uses. Los inputs y los grupos de selección reportan sus valores automáticamente. Para distinguirlos en tu código, asigna a cada input un **Element ID** significativo y a cada grupo de selección un **Group ID** en el builder. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: ## Antes de empezar Necesitas: - **Adapty SDK v4 o posterior**: Los callbacks de flow no existen en versiones anteriores. - **Un flow creado en el [Flow & Paywall Builder](adapty-flow-builder)**: Solo los flows reportan valores de entrada a través de este callback. - **Una versión de flow publicada recientemente**: Un flow solo reporta valores de entrada si lo publicaste después de que esta función estuviera disponible. Si no llega nada a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Recibir valores de entrada \{#receive-input-values\} Los valores de entrada llegan al mismo handler que cualquier otro evento de analítica de un flow, bajo el nombre de evento `flow_user_input`. Registra `onAnalytics` junto con el resto de handlers de eventos del flow: ```javascript showLineNumbers title="React Native" const unsubscribe = view.setEventHandlers({ onAnalytics(name, params) { handleFlowInput(name, params); return false; // keep the flow open }, }); ``` El callback `onAnalytics` entrega todos los eventos de analíticas de un flow, incluidas las [vistas de pantalla](react-native-flow-screen-views). Para filtrar los eventos de entrada del usuario, compara `name` con `flow_user_input`. El parámetro que almacena el valor depende de `element_type`: | `element_type` | Parámetro que almacena el valor | Tipo JavaScript | |:---------------|:--------------------------------|:----------------| | `text_input`, `email_input`, `number_input`, `phone_input` | `value` | `string` | | `date_picker`, `time_picker`, `date_time_picker` | `value` | `number`, tiempo Unix en milisegundos. | | `single_choice`, `multi_choice` | `item_ids`, `item_titles` | Array de strings | | `toggle` | `value` | `boolean` | La sección [Qué recibes](#what-you-receive) lista todos los parámetros y lo que reporta cada elemento del builder. Para confirmar que el callback se ejecuta, interactúa con el input en una versión de prueba de tu propia app. Si tu callback handler no recibe ningún evento, revisa los [prerequisitos](#before-you-start). Asegúrate de que el flow fue publicado después de que esta función estuviera disponible. ## Cuando tu app recibe el input \{#when-your-app-receives-the-input\} :::important El valor predeterminado de entrada o selección nunca llega a tu aplicación a través de este callback. Si un usuario acepta la opción marcada como **Set as default** y continúa, no se dispara ningún evento. No interpretes la ausencia del evento como "sin respuesta": el usuario simplemente dejó el valor predeterminado tal como estaba. ::: **Los siguientes elementos disparan este evento:** - Campos de texto, email, número y teléfono - Selectores de fecha, hora y fecha-hora - Grupos seleccionables de opción única y múltiple, y toggles **Los siguientes no lo hacen:** - Campos de contraseña, selecciones de productos y cambios de pestaña - Inputs dentro de un elemento Header, que se comparte entre pantallas - Grupos seleccionables con IDs de opción duplicados o faltantes, o con un ID de grupo reutilizado en otra pantalla. Ese tipo de grupo no envía nada en absoluto, en lugar de enviar parte de la respuesta. **El evento se dispara cuando:** - Un campo pierde el foco. Un campo vaciado reporta una cadena vacía; un campo que el usuario nunca editó no reporta nada. Un marcador de posición no es un valor. Si el usuario vuelve al campo, lo edita y lo abandona de nuevo, se genera un segundo evento. - El usuario cierra un selector tras elegir un nuevo valor. Cerrarlo sin cambios no reporta nada. - El usuario pulsa una opción o un toggle. Un evento de opción múltiple lista todas las opciones seleccionadas, por lo que deseleccionar la última envía dos arrays vacíos. **El evento no se dispara cuando:** - El usuario escribe. No hay flujo de pulsaciones de teclas, solo el valor que tiene el campo cuando pierde el foco. - El usuario envía o cierra el flow. Un valor que aún se está editando en ese momento puede perderse; la sección de [limitaciones de entrega](#delivery-and-limitations) explica cómo diseñar la última pantalla teniendo esto en cuenta. - Se establece un valor sin interacción del usuario. Una opción marcada como **Set as default** se preselecciona al abrirse la pantalla, y una acción **Set Variable** puede seleccionar una opción o rellenar un input a partir de otra interacción. Ninguna de las dos envía un evento; un input prerrellenado solo se reporta una vez que el usuario lo edita. ## Qué recibes \{#what-you-receive\} El callback entrega dos eventos. Filtra por `name` para mostrar únicamente los eventos `flow_user_input`. El payload JSON de respuesta tiene este aspecto: ```json { "name": "flow_user_input", "instanceId": "scr_registration", "isBackendEvent": false, "isCustomerEvent": true, "element_id": "email", "element_type": "email_input", "value": "jane@example.com" } ``` | Parámetro | Descripción | |:------------------|:------------| | `name` | `flow_user_input` para eventos de entrada, `flow_screen_showed` para vistas de pantalla. | | `instanceId` | El ID de la pantalla del input. Los IDs de elemento son únicos dentro de una pantalla, no en todo el flow. Si tu flow tiene inputs en más de una pantalla, combina `instanceId` con `element_id` al filtrar eventos. | | `element_id` | El **Element ID** del [input](builder-inputs-and-forms), o el **Group ID** del [grupo seleccionable](flow-selectable-elements). | | `element_type` | El tipo de elemento que envió el evento. Determina cuál de los parámetros siguientes contiene el valor del input. | | `value` | **Solo campos de texto, selectores y toggles.** El valor del input: una cadena para campos de texto, un entero para selectores, un booleano para toggles. | | `item_ids` | **Solo grupos seleccionables de elección única y múltiple.** Los **Element IDs** de las opciones seleccionadas, en el orden en que aparecen en el builder. Una entrada para un grupo de elección única; cualquier número para un grupo de elección múltiple. | | `item_titles` | **Solo grupos seleccionables de elección única y múltiple.** Los títulos de las opciones listadas en `item_ids`, en el mismo orden. Nunca está vacío: una opción sin título informa su ID en su lugar. | | `isCustomerEvent` | Indicador de utilidad, siempre `true` para este evento. Marca los eventos que el SDK entrega a tu callback. Resulta útil si un mismo handler reenvía todos los eventos del flow a tu sistema de análisis y filtras por este indicador en lugar de por `name`. | | `isBackendEvent` | Indicador de utilidad, siempre `false` para este evento. Marca los eventos que Adapty también registra para su propio análisis. `false` confirma que lo que los usuarios introducen llega a tu app y solo a ella — Adapty no lo recibe ni lo almacena. | Lo que reporta cada elemento: | En el builder | `element_type` | Parámetro que almacena el valor | Qué contiene | |:--------------|:---------------|:--------------------------------|:-------------| | Input de **Texto**, **Número**, **Número de teléfono** | `text_input`, `number_input`, `phone_input` | `value` | La cadena literal que escribió el usuario. Los números llegan como cadenas, no como tipos numéricos. | | Input de **E-mail** | `email_input` | `value` | La cadena literal que escribió el usuario, aunque no haya superado la validación de formato del builder. Valídala en tu lado antes de usarla. | | Input de **Contraseña** | ninguno | ninguno | No envía ningún evento. | | Input de **Fecha** | `date_picker` | `value` | Tiempo Unix en milisegundos, como entero, a medianoche local de la fecha seleccionada. | | Input de **Hora** | `time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Input de **Fecha y hora** | `date_picker` y `time_picker` | `value` | Dos elementos, un selector de fecha y un selector de hora. Cada uno envía su propio evento. | | Input cambiado a **Fecha y hora** en el desplegable **Type** | `date_time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Grupo de **elección única** | `single_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: un array con el Element ID de la opción seleccionada. `item_titles`: un array con el título de esa opción. | | Grupo de **elección múltiple** | `multi_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: los Element IDs de todas las opciones seleccionadas, en el orden en que aparecen en el builder. `item_titles`: sus títulos, en el mismo orden. Ambos arrays están vacíos cuando no hay nada seleccionado. | | Grupo de **Toggle** | `toggle` | `value` | Un booleano. | Para bifurcar según una respuesta, compara `item_ids`, no `item_titles`. El título es derivado: el **Element Title** de la opción si lo definiste, si no su texto en tu idioma predeterminado, y si no su Element ID. Un usuario que vio el flow en otro idioma vio un texto diferente. ## Ejemplos de eventos \{#event-examples\} Estos ejemplos muestran las propiedades disponibles en cada evento, con valores ilustrativos en los comentarios. <Details> <summary>Entrada de texto, correo electrónico, número y teléfono (Haz clic para expandir)</summary> ```javascript onAnalytics(name, params) { name; // 'flow_user_input' params.name; // 'flow_user_input' params.instanceId; // 'scr_J260KU5q' params.isCustomerEvent; // true params.isBackendEvent; // false params.element_id; // 'email' params.element_type; // 'email_input' params.value; // 'jane@example.com' (string) } ``` </Details> <Details> <summary>Selectores de fecha, hora y fecha-hora (Click to expand)</summary> ```javascript onAnalytics(name, params) { params.element_id; // 'birthday' params.element_type; // 'date_picker' params.value; // 645408000000 (Unix milliseconds — 1990-06-15, local midnight) } ``` </Details> <Details> <summary>Single choice (Click to expand)</summary> ```javascript onAnalytics(name, params) { params.element_id; // 'experience' params.element_type; // 'single_choice' params.item_ids; // ['pro'] params.item_titles; // ['I train professionally'] } ``` </Details> <Details> <summary>Multi choice (Haz clic para expandir)</summary> ```javascript onAnalytics(name, params) { params.element_id; // 'interests' params.element_type; // 'multi_choice' params.item_ids; // ['sports', 'music'] params.item_titles; // ['Sports', 'Music'] } ``` </Details> <Details> <summary>Toggle (Haz clic para expandir)</summary> ```javascript onAnalytics(name, params) { params.element_id; // 'reminders' params.element_type; // 'toggle' params.value; // true (boolean) } ``` </Details> ## Entrega y limitaciones \{#delivery-and-limitations\} :::warning Los flows envían valores sin procesar: direcciones de correo electrónico, números de teléfono y cualquier otra cosa que el usuario escriba. Trata todo lo que entregue el callback de analíticas como datos personales y no los almacenes en ningún lugar donde no guardarías el correo electrónico de un usuario. ::: - **El último valor prevalece**: recibes un evento por campo con el valor con el que el usuario se quedó, no una secuencia tecla a tecla. Si edita un campo, solo llega la última versión. - **Sin garantía de envío**: los valores te llegan a medida que el usuario avanza por el flow, y puede salir en cualquier momento. Espera a que el flow se cierre antes de considerar un conjunto de respuestas como completo. - **Entrega de mejor esfuerzo**: si un usuario cierra el flow o minimiza la app mientras un campo tiene el foco o un selector está abierto, ese valor puede perderse. Para que el valor del último campo sea fiable, termina el flow con una pantalla que no tenga entradas ni selectores, y cierra el flow mediante una acción explícita del usuario en lugar de hacerlo automáticamente al aparecer esa pantalla. Pasar a la pantalla final quita el foco del campo anterior, que es lo que envía su valor. Guarda cada valor de entrada tal como lo recibe tu handler y envía el conjunto completo cuando el flow se cierre. Para detectar ese momento, registra un handler `onDisappeared` junto a `onAnalytics`. Se ejecuta cuando la vista del flow se descarta, tanto si el usuario completó el flow como si lo cerró a mitad. ## Casos de uso \{#use-cases\} ### Registrar usuarios en tu backend \{#register-users-on-your-backend\} Recopila los valores según van llegando y envíalos cuando el flow se cierre, para que una sola solicitud contenga el conjunto completo de respuestas. El flow desaparece tanto si el usuario lo completó como si lo abandonó a mitad. Comprueba que tienes los campos necesarios antes de llamar a tu backend. El flow no puede mostrar errores de tu backend. El valor de retorno del handler solo cierra la vista del flow, y el SDK no tiene ningún método que envíe datos a un flow en ejecución. Si el registro falla, por ejemplo porque el email ya está en uso, muestra el error en tu propia UI después de que el flow se cierre. ```javascript showLineNumbers title="React Native" const flowAnswers = {}; const unsubscribe = view.setEventHandlers({ onAnalytics(name, params) { if (name === 'flow_user_input' && typeof params.value === 'string') { flowAnswers[params.element_id] = params.value; } return false; }, onDisappeared() { if (flowAnswers.email) { // Send flowAnswers to your backend here to create the account. } return false; }, }); ``` ### Enriquece los perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Para vincular lo que un usuario ha introducido a su perfil y evitar pedirle los mismos datos dos veces, [actualiza el perfil de usuario](react-native-setting-user-attributes) a medida que lleguen los valores. Por ejemplo, si tu flow tiene un campo de texto con el ID de elemento `name` y un campo de email con el ID de elemento `email`: ```javascript showLineNumbers title="React Native" function handleFlowInput(name, params) { if (name !== 'flow_user_input') return; if (typeof params.value !== 'string') return; const profileParams = {}; switch (params.element_id) { case 'name': profileParams.firstName = params.value; break; case 'email': profileParams.email = params.value; break; default: return; } adapty.updateProfile(profileParams).catch(error => { // handle the error }); } ``` ### Personalizar flows que aparecen más adelante \{#customize-flows-shown-later\} Las respuestas del cuestionario también pueden decidir qué ve un usuario en un [placement](placements) posterior — un flow diferente, o un paywall distinto dentro de él. Por ejemplo, pregunta a los usuarios sobre su experiencia con el deporte en tu flow de onboarding y luego muestra a cada grupo su propio flow con diferentes productos y textos. 1. Añade un [cuestionario](onboarding-quizzes) a tu flow. Dale al [grupo seleccionable](flow-selectable-elements) el ID de grupo `experience`, y a cada opción un ID de elemento significativo. 2. Gestiona las respuestas y [establece atributos personalizados](react-native-setting-user-attributes) para el usuario. ```javascript showLineNumbers title="React Native" function handleFlowInput(name, params) { if (name !== 'flow_user_input') return; if (params.element_id !== 'experience') return; adapty .updateProfile({ // Set the custom attribute 'experience' to the option the user selected // (beginner, amateur, or pro). codableCustomAttributes: { experience: params.item_ids[0] }, }) .catch(error => { // handle the error }); } ``` 3. [Crea un segmento](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade una [audiencia](audience) para cada segmento. 5. [Muestra el flow](react-native-present-paywalls) de ese placement en tu app. --- # File: react-native-flow-custom-tags --- --- title: "Pasar datos a flows en React Native SDK" description: "Pasa un valor desde tu app de React Native a un flow y muéstralo en un elemento de texto." --- Un [custom tag](flow-custom-tags) es una variable personalizada con un valor proveniente de tu app. Cuando seleccionas **Value comes from outside the app** en la configuración de una variable, tu app puede establecer su valor a través del SDK de Adapty. Por ejemplo, un elemento de texto contiene "Coins:" y un custom tag. Si tu app pasa "120" para el tag, los usuarios ven "Coins: 120". ## Antes de comenzar \{#before-you-start\} Necesitas el SDK de Adapty v4 o posterior. Las versiones anteriores a v4 no muestran flows. ## Añade etiquetas personalizadas al flow \{#add-custom-tags-to-the-flow\} Para mostrar los valores de tu app en un flow, configura etiquetas personalizadas en el builder: 1. [Crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) para cada valor que tu app envíe. <ZoomImage id="flow-custom-tag-create.webp" width="500px" alt="Formulario para crear una variable con nombre, descripción, tipo de valor String y valor inicial" /> 2. [Inserta cada etiqueta en un elemento de texto](flow-custom-tags#add-a-custom-tag-to-a-text-element). 3. Anota el nombre completo de cada etiqueta, incluido su grupo. El código de tu app pasa cada valor con ese nombre. Por ejemplo, si el grupo `user` contiene una etiqueta `streak`, el nombre completo de la etiqueta es `user.streak`. <ZoomImage id="flow-custom-tag-full-name.webp" width="500px" alt="Pestaña personalizada del desplegable Añadir variable con la etiqueta streak en el grupo user" /> ## Pasar valores de etiquetas personalizadas \{#pass-custom-tag-values\} Pasa los valores de tu app en el campo `customTags` de los parámetros de la vista, como un objeto. Con el componente `AdaptyFlowView`, los parámetros son su prop `params`. Con `createFlowView`, son su segundo argumento. - Cada clave es el nombre completo de una etiqueta personalizada, como `user.streak`. - Cada valor es la cadena que los usuarios ven en lugar de la etiqueta. - Cada valor debe ser legible por humanos, como "1,250", no "1250.0". Adapty no lo formatea ni traduce. `createFlowView` lee los valores una sola vez, al crear la vista, por lo que no cambian mientras el flow está abierto. Si los `params` de un `AdaptyFlowView` montado cambian, el componente vuelve a crear la vista del flow y el flow comienza de nuevo desde su primera pantalla. Mantén los `params` iguales mientras el flow esté abierto. <Tabs> <TabItem value="component" label="React component" default> ```typescript showLineNumbers title="React Native (TSX)" const flowParams = { customTags: { 'username': 'DragonSlayer42', 'coins': '1,250', }, }; function MyFlow({ flow }: { flow: AdaptyFlow }) { return <AdaptyFlowView flow={flow} params={flowParams} style={{ flex: 1 }} />; } ``` </TabItem> <TabItem value="modal" label="Presentación modal"> ```typescript showLineNumbers title="React Native" const customTags = { 'username': 'DragonSlayer42', 'coins': '1,250', }; try { const view = await createFlowView(flow, { customTags }); await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> Para probar tus etiquetas personalizadas, abre el flow en tu app después de añadir el código que establece sus valores. El canvas del builder y la [vista previa en dispositivos](paywall-device-compatibility-preview#preview-on-devices) solo muestran los valores iniciales: la app móvil de Adapty no establece los valores de las etiquetas personalizadas. Si tu app ya establece valores de etiquetas personalizadas para paywalls del antiguo Paywall Builder, revisa [en qué se diferencian las etiquetas personalizadas en los flows](migrate-to-flows#custom-tags-from-the-old-paywall-builder). ## Lo que ven los usuarios cuando no hay valor \{#what-users-see-without-a-value\} Los usuarios ven el **Valor inicial** de una etiqueta personalizada como valor de respaldo en los siguientes casos: - Tu app no pasa ningún valor para la etiqueta. - El usuario no ha actualizado tu app a una versión que envíe el valor. - El nombre en el código de tu app no coincide con el nombre completo de la etiqueta, incluidas las mayúsculas y minúsculas. - Otra etiqueta personalizada en el mismo elemento de texto no tiene valor. En ese caso, todas las etiquetas personalizadas de ese elemento de texto muestran su valor inicial. Para no mostrar nada en lugar de una etiqueta, pasa una cadena vacía. Adapty trata una cadena vacía como un valor, por lo que el valor de respaldo no aparece. ## Ejemplo: mostrar un IMC a partir de las respuestas del onboarding \{#example-show-a-bmi-from-onboarding-answers\} Un flow puede recoger respuestas de los usuarios, y una etiqueta personalizada puede mostrar un valor de tu app. Combina ambas cosas para mostrar a los usuarios un resultado que tu app calcula a partir de sus respuestas, como una puntuación, un plan recomendado o una métrica de salud: 1. Un flow recoge las respuestas y las envía a tu app. 2. Tu app calcula el resultado. 3. Tu app pasa el resultado al siguiente flow como valor de etiqueta personalizada. En este ejemplo, un flow de onboarding recoge el peso y la altura del usuario. Cuando el usuario cierra el flow de onboarding tras su última pantalla, tu app calcula su índice de masa corporal (IMC). Luego abre un flow de paywall que muestra el IMC. ### Configurar los flows \{#set-up-the-flows\} 1. [Crea dos flows](paywall-builder-templates): un flow de onboarding que recoja el peso y la altura del usuario, y un flow de paywall que muestre su IMC. 2. Añade dos campos de tipo [**Number**](builder-inputs-and-forms) al flow de onboarding: - Uno para el peso en kilogramos, con el **Element ID** `weight`. - Uno para la altura en centímetros, con el **Element ID** `height`. En ambos campos, establece el **Format** como **Integer**. Un campo envía su valor como texto, exactamente tal como lo escribe el usuario, y el [código de ejemplo](#pass-the-bmi-to-the-paywall) no interpreta "70.5" y "70,5" de la misma manera. Los números enteros no tienen separador decimal, por lo que el código los lee igual en cualquier idioma. 3. Para asegurarte de que tu app recibe ambos valores, termina el flow de onboarding con una pantalla que no tenga campos. En esa pantalla, añade un botón con la acción [**Close flow**](onboarding-actions#close-flow). Los campos envían sus valores cuando pierden el foco o cuando el usuario pasa a otra pantalla. La acción Close flow no los envía. El código de ejemplo solo abre el paywall si el usuario llegó a la última pantalla. 4. En el flow de paywall, [crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) llamada `bmi`. En el campo **Initial value**, introduce un guion (`—`). Los usuarios verán el guion si tu app no pasa ningún IMC. 5. Añade un elemento de texto con las palabras "Your BMI:" al flow de paywall. Después de esas palabras, [inserta la etiqueta personalizada `bmi`](flow-custom-tags#add-a-custom-tag-to-a-text-element). 6. Añade el flow de onboarding a un [placement](placements), por ejemplo `onboarding`. Añade el flow de paywall a un placement con el ID `bmi_paywall`. El código de [Pass the BMI to the paywall](#pass-the-bmi-to-the-paywall) obtiene el paywall usando este ID. ### Pasar el IMC al paywall Recopila el peso y la altura de los eventos `flow_user_input` del onboarding, tal como se describe en [Procesar datos de flows](react-native-flow-input). Para saber que el usuario llegó a la última pantalla, observa el evento `flow_screen_showed` con `is_last_screen` configurado como `true`, tal como se describe en [Rastrear vistas de pantallas de flows](react-native-flow-screen-views). Cuando el usuario cierre el onboarding después de esa pantalla, calcula el IMC. Luego crea la vista del paywall y pasa el IMC para el custom tag `bmi`. Registra los siguientes handlers en `onboardingView`, la vista que devuelve `createFlowView` para el flow de onboarding. El componente `AdaptyFlowView` no tiene un handler `onDisappeared`, por lo que este ejemplo no funciona con él. ```javascript showLineNumbers title="React Native" const bodyAnswers = {}; let reachedLastScreen = false; const unsubscribe = onboardingView.setEventHandlers({ onAnalytics(name, params) { if (name === 'flow_screen_showed' && params.is_last_screen) { reachedLastScreen = true; } if (name === 'flow_user_input' && typeof params.value === 'string') { bodyAnswers[params.element_id] = params.value; } return false; // keep the flow open }, onDisappeared() { // Number inputs send their values as strings. const weight = Number(bodyAnswers.weight); const height = Number(bodyAnswers.height); const finished = reachedLastScreen; delete bodyAnswers.weight; delete bodyAnswers.height; reachedLastScreen = false; if (finished && weight > 0 && height > 0) { const heightInMeters = height / 100; const bmi = weight / (heightInMeters * heightInMeters); showBmiPaywall(bmi.toLocaleString(undefined, { maximumFractionDigits: 1 })); } return false; }, }); async function showBmiPaywall(bmi) { try { const flow = await adapty.getFlow('bmi_paywall'); const view = await createFlowView(flow, { customTags: { 'bmi': bmi } }); await view.present(); } catch (error) { // handle the error } } ``` `toLocaleString` usa el separador decimal de la configuración regional del usuario: "22.7" en inglés, "22,7" en alemán. ### Guardar el IMC en el perfil del usuario \{#save-the-bmi-to-the-users-profile\} Para mostrar distintos flows a los usuarios según su IMC, guárdalo como un [atributo personalizado](react-native-setting-user-attributes#custom-user-attributes) de su perfil. En el Adapty Dashboard, [crea un atributo personalizado de tipo **Number**](segments#custom-attributes) con la clave `bmi`. Luego muestra cada flow a una [audiencia](audience) creada a partir de ese atributo. Llama al método `updateProfile` en `onDisappeared`, justo después de la línea que calcula `bmi`. Pasa ese número, no la cadena formateada, para que un segmento pueda comparar el IMC con un umbral. `onDisappeared` no puede ser `async`, así que gestiona el resultado con `.catch` en lugar de `await`: ```javascript showLineNumbers title="React Native" adapty .updateProfile({ codableCustomAttributes: { bmi } }) .catch((error) => { // handle the error }); ``` --- # File: react-native-use-fallback-paywalls --- --- title: "Usar paywalls de respaldo en React Native" description: "Configura paywalls de respaldo en tu proyecto de React Native para gestionar escenarios sin conexión." --- Los paywalls de respaldo de Adapty funcionan tanto en proyectos **Expo** como en proyectos de **React Native puro**. Como estos entornos usan sistemas de compilación distintos, los pasos de configuración también difieren. Elige la guía de configuración que corresponda a tu proyecto: <CustomDocCardList /> --- # 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 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 React Native puros (sin Expo)**. Si usas **Expo**, sigue la [guía 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 elige la localización para un flow o un onboarding, 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 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 localización \{#locale-code-matching\} En SDK v4, los flows y los onboardings hacen coincidir los códigos de localización de forma diferente: los flows se localizan mediante el SDK en el dispositivo, y los onboardings mediante el servidor de Adapty. ### Flows y paywalls Un paywall que Adapty renderiza se entrega como un flow en el SDK v4, por lo que la regla siguiente aplica a ambos. La coincidencia es exacta. El SDK compara el código que pasas con los códigos de localización del flow carácter a carácter: no cambia las mayúsculas/minúsculas, no reemplaza guiones bajos (`_`) por guiones (`-`) y no recurre a la subetiqueta de idioma. Para un flow con una localización `pt-br`, solo `pt-br` coincide: `pt-BR`, `pt_BR` y `pt-PT` no coinciden. Cuando el código no coincide con ninguna localización, el flow se renderiza silenciosamente en su [localización predeterminada](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale): el SDK no devuelve un error ni registra ninguna advertencia. Cuando el código coincide, Adapty fusiona la localización con la predeterminada: las cadenas y recursos que no estén definidos en la localización encontrada se toman de la localización predeterminada. Omitir el código de idioma no equivale a solicitar la localización predeterminada del flow: el SDK sustituye un `en` fijo. Un flow cuya localización predeterminada es `de` se seguirá renderizando en `en` si tiene una localización `en`, y solo recurrirá a `de` cuando no la tenga. :::warning Pasa el código de idioma exactamente como está configurado en el dashboard — subtags en minúsculas separados por guiones. No pases un identificador de idioma del dispositivo tal cual: `getLocales()[0].languageTag` de [`react-native-localize`](https://github.com/zoontek/react-native-localize) devuelve `pt-BR`, y recurre a la localización predeterminada. Convierte el valor en tu app antes de pasarlo. ::: ### Onboardings Los onboardings se localizan en el servidor, y las reglas del servidor toleran otros formatos. Cuando pasas un `locale` a [`getOnboarding`](react-native-get-onboardings): 1. La cadena de locale 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 locale que coincida exactamente 3. Si no se encuentra ninguna coincidencia, Adapty toma la subcadena anterior al primer guión (`pt` para `pt-br`) y busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, Adapty devuelve el contenido para el locale predeterminado del onboarding Esta forma, `pt_BR`, `pt-BR` y `pt-br` se resuelven todas en la misma localización del onboarding. ## Implementación de localizaciones \{#implementing-localizations\} En el SDK v4, no se pasa un código de idioma al obtener un flow — `getFlow` devuelve el flow con todas sus localizaciones, y Adapty aplica una cuando se construye la vista del flow. - **Flows creados en el builder**: el SDK no lee el idioma del dispositivo, así que resuélvelo en tu app y pásalo como parámetro `locale` de `createFlowView`, o en la prop `params` del componente `AdaptyFlowView` embebido. Es opcional — si lo omites, el flow se renderiza en `en`, o en su [idioma predeterminado](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) cuando el flow no tiene localización en `en`. ```typescript showLineNumbers import { createFlowView } from 'react-native-adapty'; const view = await createFlowView(flow, { locale: 'es' }); ``` `view.locale` indica la localización con la que se construyó realmente la vista: la que pediste si esa localización existe, o la predeterminada del flow en caso contrario. Tanto el parámetro `locale` como `view.locale` requieren React Native SDK 4.0.2 o posterior, y `view.locale` es `undefined` en versiones anteriores. El componente `AdaptyFlowView` integrado crea su propia vista, por lo que tu código no tiene ningún objeto del que leer `locale`. En su lugar, obtén la localización del objeto que recibe su [manejador `onAppeared`](react-native-handling-events-1#the-view-argument): ```typescript showLineNumbers <AdaptyFlowView flow={flow} params={{ locale: 'es' }} onAppeared={(view) => setScreenLocale(view.locale)} /> ``` El argumento `onAppeared` requiere React Native SDK 4.0.3 o posterior. - **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('placement_id'); const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0]; // read your values from config?.data ``` Adapty almacena esos códigos `lang` en el formato descrito en [Estándar de código de idioma en Adapty](#locale-code-standard-at-adapty). El SDK no compara los Remote Configs con un idioma, así que tu app es quien decide qué entrada aplicar. </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ódigos de idioma \{#locale-code-matching\} Cuando Adapty recibe una llamada del SDK del cliente 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 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 coincidente 4. Si tampoco se encuentra ninguna coincidencia, se devuelve el contenido con el idioma predeterminado del paywall 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. En SDK v4, esto recibe un `AdaptyFlowPaywall` — una variación de paywall del flow obtenido. Comprueba que `flow.paywalls` no esté vacío antes de indexarlo, por ejemplo `flow.paywalls[0]`. ::: #### 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-present-flows-in-observer-mode --- --- title: "Mostrar flows en modo Observer - React Native" description: "Muestra flows en modo Observer en tu app de React Native mientras gestionas las compras con tu propio código." --- Si usas el [modo Observer](observer-vs-full-mode), eres responsable de tu propia lógica de compras. Pero puedes delegar la UI del paywall al [Flow & Paywall Builder](adapty-flow-builder). Diseña la pantalla en el builder, y el SDK de Adapty la renderiza de forma nativa. Actualiza los textos, el diseño y los precios cuando quieras, sin necesidad de publicar una nueva versión de la app. Esta guía muestra cómo presentar un flow y gestionar los callbacks de compra. Si Adapty gestiona las compras por ti, consulta [Mostrar flows y paywalls](react-native-present-paywalls) en su lugar. ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty React Native SDK 4.0 o posterior**: Las versiones anteriores no pueden renderizar flows. Para migrar una app existente, consulta la [guía de migración](migration-to-react-native-sdk-v4). - **`observerMode` configurado como `true`**: Consulta la [guía de instalación del SDK de React Native](sdk-installation-reactnative). - **Integración inicial con la store**: Configura Adapty [con App Store](initial_ios) y [con Google Play](initial-android). - **Un flow publicado con productos**: Créalo en el [Flow & Paywall Builder](adapty-flow-builder) y asígnale [productos](create-product). - **Un placement para el flow**: [Crea un placement](create-placement) y asigna tu flow a él. - **El flow obtenido en tu app**: [Obtén el flow y su configuración](react-native-get-pb-paywalls) antes de mostrarlo. 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 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í. ## Configura los controladores de eventos en modo observador \{#set-the-observer-mode-event-handlers\} A diferencia de otras plataformas, no existe un objeto resolver independiente — los controladores son parte de los [controladores de eventos](react-native-handling-events-1) habituales, así que configúralos en cada flow que presentes. <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Para el componente React, pasa los controladores como props a `AdaptyFlowView`: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const onObserverPurchaseInitiated = useCallback< FlowEventHandlers['onObserverPurchaseInitiated'] >((product, onStartPurchase, onFinishPurchase) => { onStartPurchase(); // the flow shows its loading indicator myPurchaseApi(product.vendorProductId) .then((transactionId) => adapty.reportTransaction(transactionId, flow.variationId)) .finally(() => onFinishPurchase()); // the flow hides the loading indicator }, [flow]); const onObserverRestoreInitiated = useCallback< FlowEventHandlers['onObserverRestoreInitiated'] >((onStartRestore, onFinishRestore) => { onStartRestore(); myRestoreApi().finally(() => onFinishRestore()); }, []); return ( <AdaptyFlowView flow={flow} onObserverPurchaseInitiated={onObserverPurchaseInitiated} onObserverRestoreInitiated={onObserverRestoreInitiated} // ... your other event handlers /> ); } ``` :::note En el componente embebido, el valor que devuelve un handler no cierra el flow — desmonta el componente tú mismo tras una compra o restauración exitosa. ::: </TabItem> <TabItem value="standalone" label="Modal presentation"> Para la presentación modal, establece los handlers en la vista que creaste: ```typescript showLineNumbers title="React Native (TSX)" const view = await createFlowView(flow); const unsubscribe = view.setEventHandlers({ onObserverPurchaseInitiated(product, onStartPurchase, onFinishPurchase) { onStartPurchase(); // the flow shows its loading indicator myPurchaseApi(product.vendorProductId) .then((transactionId) => adapty.reportTransaction(transactionId, flow.variationId)) .finally(() => onFinishPurchase()); // the flow hides the loading indicator return false; // keep the flow open; dismiss it yourself after success }, onObserverRestoreInitiated(onStartRestore, onFinishRestore) { onStartRestore(); myRestoreApi().finally(() => onFinishRestore()); return false; }, }); ``` </TabItem> </Tabs> 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. Recuerda también 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() | 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. | ## Presenta el flow \{#present-the-flow\} Presenta el flow como de costumbre: [obtén el flow y crea su vista](react-native-get-pb-paywalls), luego [preséntalo](react-native-present-paywalls). No se necesitan parámetros adicionales — los handlers se activan solo cuando el SDK fue inicializado con `observerMode: true`. :::warning No olvides [reportar la transacción y asociarla con el paywall](report-transactions-observer-mode-react-native). De lo contrario, Adapty no reconocerá la transacción y no podrá determinar el paywall de origen de la compra. ::: --- # File: react-native-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del antiguo Paywall Builder en el SDK de React Native" description: "Corrige errores al mostrar paywalls creados en el antiguo Paywall Builder en el SDK de React Native de Adapty." --- Esta guía te ayuda a resolver problemas frecuentes al usar paywalls diseñados en el Adapty Paywall Builder con el SDK de React Native. Para problemas con flows, consulta [Problemas comunes](flow-common-issues). ## 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 recuento 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 recuento de visualizaciones de un paywall creado en el antiguo Paywall Builder. Adapty registra esas visualizaciones automáticamente, por lo que no necesitas este método. **Solución**: Asegúrate de no estar llamando a `logShowFlow` (React Native SDK v4+) / `logShowPaywall` en tu código para un paywall creado en el antiguo Paywall 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-implement-paywalls-manually --- --- title: "Implementar paywalls personalizados en React Native SDK" description: "Aprende cómo implementar paywalls personalizados en tu app de React Native 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 en Adapty usando el método `makePurchase`. De esta forma, nos encargamos de todos los escenarios del usuario y tú solo necesitas 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={['react-native-quickstart-manual', 'fetch-paywalls-and-products-react-native', 'present-remote-config-paywalls-react-native', 'react-native-making-purchases', 'react-native-restore-purchase', 'react-native-troubleshoot-purchases']} /> ## Modo observador \{#observer-mode\} Si quieres implementar tu propia lógica de manejo de compras desde cero, pero aun así quieres 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-react-native', 'report-transactions-observer-mode-react-native', 'react-native-troubleshoot-purchases']} /> --- # 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 & Paywall Builder](react-native-quickstart-paywalls). Con el Flow & Paywall 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) - [**Custom 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 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 la forma de gestionar los productos que vendes en tu app. Para implementar tu paywall personalizado, tendrás que crear un **paywall personalizado** 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 personalizados y productos 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 usuarios." --- <SDKv4> Antes de mostrar paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls personalizados. Para obtener orientación sobre cómo obtener flows o paywalls personalizados en el **Flow & Paywall Builder** o el **old Paywall Builder**, consulta [Obtener flows y paywalls 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 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-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: `'reload_revalidating_cache_data'` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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 regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada es más reciente que los `maxAgeSeconds` que le indicas, y va al servidor cuando es más antigua.</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 flows y 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 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 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 de espera especificado en `loadTimeout`, ya que la operación puede estar compuesta por diferentes solicitudes 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 de la variación (`variationName`, opcional, SDK 4.2+), 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** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. | | **fetchPolicy** | por defecto: `'reload_revalidating_cache_data'` | <p>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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 solicitudes de red.</p><p></p><p>Una tercera opción, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el valor `maxAgeSeconds` que indiques junto a ella, y va al servidor cuando es más antigua.</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 paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls personalizados. Para obtener orientación sobre cómo recuperar paywalls para 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 utilizarlos.</p> | | **fetchPolicy** | por defecto: `'reload_revalidating_cache_data'` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y va al servidor solo cuando no hay nada en caché. 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 mala 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>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que los `maxAgeSeconds` que indiques junto a ella, y va al servidor cuando sea más antigua.</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 en dos capas: la caché de actualización periódica 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 por si 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 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 constar de diferentes solicitudes 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` con: una lista de IDs de productos, 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 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>por defecto: `en`</p> | <p>El identificador de la [localización del paywall](add-remote-config-locale). Este parámetro debe ser 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: `'reload_revalidating_cache_data'` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa petición falla. 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 inestables, considera `'return_cache_data_else_load'`, que invierte ese orden: lee primero la caché y solo consulta el servidor cuando no hay nada almacenado. 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>Una tercera política, `'return_cache_data_if_not_expired_else_load'`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que los `maxAgeSeconds` que indiques, y consulta el servidor cuando supere ese tiempo.</p><p></p><p>Ten en cuenta que la caché se mantiene al reiniciar la app y solo se borra cuando se reinstala la aplicación o mediante una limpieza manual.</p> | </SDKv3> --- # File: present-remote-config-paywalls-react-native --- --- title: "Presenta paywalls personalizados en React Native SDK" description: "Descubre cómo presentar paywalls personalizados 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`. | </SDKv3> --- # File: react-native-making-purchases --- --- title: "Realizar compras in-app en React Native SDK" description: "Guía sobre cómo gestionar compras in-app y suscripciones usando 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, presentar un paywall solo gestiona las compras por sí solo cuando Adapty renderiza la pantalla — es decir, un [flow](adapty-flow-builder), o un paywall del [antiguo Paywall Builder](adapty-paywall-builder). Si renderizas la pantalla en tu propio código, 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 un 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 Adapty renderiza la pantalla. 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 **¿Adapty renderiza tu pantalla?** En un [flow](adapty-flow-builder) o un paywall creado con [Paywall Builder](adapty-paywall-builder), las compras se procesan automáticamente; puedes omitir este paso. **¿Buscas una guía paso a paso?** Consulta la [guía de inicio rápido](react-native-implement-paywalls-manually) para 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>Acerca de los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios específicos. A diferencia de las ofertas regulares que se aplican automáticamente, los códigos de oferta se distribuyen fuera de la app — a través de campañas de correo electrónico, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, siguiendo 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 realiza al precio completo. - **Pay as you go** — el usuario paga un precio con descuento en cada ciclo de facturación durante un período determinado, luego la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único con descuento por toda la duración de la oferta, luego 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`. Una vez que el período de oferta termina y la suscripción se renueva al precio completo, la etiqueta ya no está presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Solución de problemas de discrepancias en 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 con descuento de la oferta, verifica lo siguiente en App Store Connect: - El código de oferta tiene el precio correcto configurado para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está establecido para el país o región específico 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 en su lugar. Puedes filtrar y verificar las transacciones con códigos 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 reemplazan con más funcionalidades: elegibilidad configurable, fechas de vencimiento y hasta 1 millón de códigos por trimestre. Si anteriormente usabas códigos promocionales para compras in-app, haz la transición a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app por versión) otorgaban 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ódigos promocionales — enviaba el precio completo del producto en el recibo. Como resultado, Adapty registraba estas transacciones al precio completo, lo que causaba discrepancias de ingresos 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. Dado que estos códigos están ahora obsoletos, haz la transición 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}` ::: ## Compras in-app promocionadas desde la App Store \{#in-app-purchases-from-the-app-store\} :::info Tu app puede gestionar compras in-app promocionadas desde la versión 4.1 del SDK, en **iOS 16.4** o posterior. En versiones anteriores a iOS 16.4 y en Android, el evento `'onPromotedPurchaseReceived'` nunca se dispara. ::: Cuando un usuario inicia una compra desde la página de tu producto en la App Store y la transacción se transfiere a tu app, el SDK la completa automáticamente: la pantalla de compra de Apple aparece de inmediato y Adapty procesa la transacción como cualquier otra compra. Esto no requiere ningún código de tu parte. Si el producto promocionado incluye una oferta de suscripción, el SDK la aplica automáticamente en la compra. La oferta se lee del intent de compra del App Store, que la expone en iOS 18.0 y versiones posteriores. En iOS 16.4–17.x, la compra se procesa al precio base. Para gestionar la finalización tú mismo — por ejemplo, para mostrar primero tu propia pantalla — escucha el evento `'onPromotedPurchaseReceived'` y pasa el producto a `makePromotedPurchase`: ```typescript showLineNumbers adapty.addEventListener('onPromotedPurchaseReceived', async product => { const result = await adapty.makePromotedPurchase(product); // process the purchase result }); ``` :::warning Mientras tu listener esté registrado, el SDK deja de completar las compras promocionadas por ti. Si tu manejador nunca llama a `makePromotedPurchase`, la compra nunca ocurre: el App Store entrega el producto a tu app y espera. ::: `makePromotedPurchase` no recibe parámetros de compra — un producto promocionado proviene del App Store y no de un paywall, por lo que no lleva contexto de paywall. Devuelve el mismo `AdaptyPurchaseResult` que `makePurchase`. Los listeners son aditivos: si registras varios, cada uno recibe el producto, y ninguno completa la compra por los demás. ## 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 aplicaciones móviles con React Native SDK" description: "Aprende cómo restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- Restaurar compras en iOS y Android es una funcionalidad 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 funcionalidad resulta especialmente útil para quienes han desinstalado y vuelto a instalar la app, o han cambiado de dispositivo, y quieren acceder a su contenido anterior sin pagar de nuevo. :::note Si construiste la pantalla con un builder, no necesitas restaurar las compras manualmente. Conecta su control de restauración — la acción **Restore purchases** en el Flow & Paywall Builder, o un enlace o botón de restauración en el antiguo — y el SDK completa la restauración. ::: Si construiste la pantalla tú mismo, 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 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 aplicación.</p> | :::info En Adapty React Native SDK 4.0.3 y versiones posteriores, `restorePurchases()` se completa correctamente incluso cuando no hay nada que restaurar: devuelve el perfil actual, así que comprueba en él el estado del nivel de acceso. En versiones anteriores del SDK, el método devuelve el error `noPurchasesToRestore` en Android en este caso. ::: :::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 análisis avanzados e integración fluida con sistemas de atribución y analíticas. Si esto se adapta 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 [React Native](sdk-installation-reactnative). 2. [Informar las transacciones](report-transactions-observer-mode-react-native) desde tu infraestructura de compras existente a Adapty. :::tip En el SDK v4, también puedes mostrar flows y paywalls renderizados por Adapty en el modo Observer: configura los manejadores de eventos `onObserverPurchaseInitiated` y `onObserverRestoreInitiated` 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](react-native-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 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`. | ## Uso de flows y paywalls de Adapty en el modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los flows, paywalls y 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 personalizados](present-remote-config-paywalls-react-native) como de costumbre. Para flows y paywalls creados en el Flow & Paywall Builder, consulta [Presentar flows en modo Observer](react-native-present-flows-in-observer-mode). 2. [Asocia flows y paywalls](report-transactions-observer-mode-react-native) con las transacciones de compra. --- # File: report-transactions-observer-mode-react-native --- --- title: "Informar transacciones en Observer Mode en React Native SDK" description: "Informa transacciones de compra en el Observer Mode de Adapty para obtener información sobre usuarios y seguimiento de ingresos en React Native SDK." --- <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. Debes informar las transacciones desde tu 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 registro 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 flows y paywalls de Adapty, incluye el `variationId` al registrar una transacción. Esto vincula la compra al flow o paywall que la desencadenó, garantizando análisis precisos. ```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 | required | <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 | optional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto `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 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 registro 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 flows y paywalls de Adapty, incluye el `variationId` al registrar una transacción. Esto vincula la compra con el flow o paywall que la originó, lo que garantiza unas métricas precisas. ```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 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 mediante la propiedad `variationId` del objeto `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> **Informar transacciones** - Las versiones hasta la 3.1.x escuchan automáticamente las transacciones en el App Store, por lo que no es necesario informarlas manualmente. - La versión 3.2 no es compatible con el Modo Observador. </TabItem> <TabItem value="kotlin" label="Android and Android-based cross-platforms" default> **Informar transacciones** Usa `restorePurchases` para informar una transacción a Adapty en el modo Observer, 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 flows y 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 tienes intención de usar flows, paywalls o pruebas A/B en modo Observer, necesitas asociar la transacción proveniente de tu store con el flow o paywall correspondiente en el código de tu app. Es importante hacerlo correctamente antes de publicar tu app, ya que 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` } ``` (empty input — nothing to translate) | 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 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.</p> | | variationId | requerido | El identificador de tipo String de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto `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-user --- --- title: "Usuarios y acceso en React Native SDK" description: "Aprende a trabajar con usuarios y niveles de acceso en tu app de React Native con Adapty SDK." --- <CustomDocCardList /> --- # File: react-native-identifying-users --- --- title: "Identificar usuarios en React Native SDK" 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 a actualizar los atributos de usuario y los 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 el usuario de tu app. Luego puedes usar estos atributos 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()`: ```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: "Verificar el estado de suscripción en el SDK de React Native" description: "Rastrea y gestiona el estado de suscripción de los usuarios en Adapty para mejorar la retención de clientes en tu app de React Native." --- Con Adapty, hacer un seguimiento del estado de suscripción es muy sencillo. No necesitas insertar manualmente 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 verificar 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://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 infantil se activa mediante el trait `KidsMode` del paquete Swift, que elimina en compilación todo el código de IDFA, AdSupport y AppTrackingTransparency. Requiere el SDK v4 y **Xcode 26** o posterior. La forma de aplicar el trait depende del gestor de paquetes que uses en tu proyecto iOS — consulta [Actualizaciones en tu proyecto iOS](#updates-in-your-ios-project). ::: ## ¿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 proyecto iOS Para la categoría Kids de la App Store (o cumplimiento de COPPA), el SDK nativo de iOS debe compilarse con el trait de paquete Swift `KidsMode`, que excluye todo el código de IDFA, AdSupport y AppTrackingTransparency. Este paso requiere **Xcode 26** o posterior. Swift Package Manager no puede transferir traits de paquetes a través de ninguna de las dos rutas de integración, por lo que el SDK de Adapty React Native incluye una herramienta que aplica el trait por ti. Elige la que corresponda a cómo está configurado tu proyecto iOS, tal como se describe en [Instalar el SDK de Adapty](sdk-installation-react-native-pure#install-adapty-sdk). <Tabs groupId="ios-package-manager" queryString> <TabItem value="cocoapods" label="CocoaPods" default> El SDK incluye un helper de Podfile. En `ios/Podfile`, require 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_podfile.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 ``` Mantén la llamada al helper en `post_install` de forma permanente — React Native recrea las referencias del Swift package en cada `pod install`, y el helper vuelve a aplicar el trait cada vez. El mismo helper cubre la opción [CocoaPods sin SPM](sdk-installation-react-native-pure#set-up-your-ios-project) del SDK 4.2.3 y versiones posteriores. Con los SDKs nativos instalados como pods, no hay ningún Swift package en el que establecer el trait, por lo que el helper añade la condición de compilación `KidsMode` al pod `Adapty` y desvincula el framework AdSupport. :::note Antes del SDK 4.2.3, el archivo auxiliar es `adapty_kids_mode.rb`. En el SDK 4.2.3 y versiones posteriores, ambos nombres de archivo funcionan. ::: </TabItem> <TabItem value="spm" label="React Native's SPM integration"> El SDK incluye el comando `adapty-spm-kids-mode`. Ejecútalo como un hook postinstall en el `package.json` de tu app: ```json showLineNumbers title="package.json" { "scripts": { "postinstall": "adapty-spm-kids-mode enable" } } ``` Luego reinstala las dependencias para ejecutar el hook por primera vez: ```sh showLineNumbers title="Shell" npm install # or using yarn yarn install ``` El hook debe estar en `package.json` de forma permanente. El autolinking de Swift Package Manager en React Native no tiene noción de traits de paquete y regenera el paquete agregador en cada sincronización, por lo que el comando habilita el trait en el manifiesto propio del SDK dentro de `node_modules`, que cada instalación reemplaza. Después de habilitar o deshabilitar Kids Mode, limpia la carpeta de compilación antes del siguiente build de iOS (**Product → Clean Build Folder** en Xcode). :::note `adapty-spm-kids-mode enable` muestra una advertencia, sin interrumpir la instalación, cuando no puede confirmar una integración con Swift Package Manager o detecta una aplicación con CocoaPods — donde React Native no lee ningún `Package.swift`, por lo que el parámetro no tiene efecto. Si tu proyecto iOS está fuera del directorio desde el que instalas, usa `--app-root=ruta/a/la/app`. ::: Para desactivar el modo Kids Mode, cambia el hook a `adapty-spm-kids-mode disable`. </TabItem> </Tabs> Para confirmar que el modo Kids Mode está activo, verifica que la línea de log `adapty.activate(...)` muestre `kids_mode_enabled: true`. ### 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-onboardings --- --- title: "Onboardings en React Native SDK" description: "Aprende a trabajar con onboardings en tu aplicación React Native con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: <CustomDocCardList /> --- # File: react-native-get-onboardings --- --- title: "Obtener onboardings en React Native SDK" description: "Aprende cómo recuperar onboardings en Adapty para React Native." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 las conversiones y los ingresos." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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: "Manejar eventos de onboarding en React Native SDK" description: "Maneja eventos relacionados con el onboarding en React Native usando Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 React Native SDK" description: "Guarda y usa datos de onboardings en tu app de React Native con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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: ```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-best-practices --- --- title: "Mejores prácticas en el SDK de React Native" description: "Patrones de referencia para integrar el SDK de Adapty en React Native: orden de llamadas, manejo de errores y otras reglas para entornos de producción." --- <CustomDocCardList /> --- # 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 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('YOUR_PUBLIC_SDK_KEY', { customerUserId: 'YOUR_USER_ID' })` | 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('YOUR_PUBLIC_SDK_KEY')` 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 se vinculen 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` generan `#3006 profileWasChanged`. | | 5 | `getPaywall` (`getFlow` en SDK v4), `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution` (`updateExternalAttribution` en SDK 4.1), `updateProfile` | Después del paso 4 si llamas a `identify`; si no, 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 obtención de flows y paywalls en el SDK de React Native" description: "Obtén flows y paywalls de Adapty de forma fiable: temporización, caché y patrones de respaldo para React Native." --- Una obtención fiable de un flow o paywall en React Native hace tres cosas: renderiza rápido, devuelve la variación dirigida a la audiencia y recurre al respaldo de forma elegante cuando la red es lenta. Las reglas que se describen a continuación cubren los patrones de temporizació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 React Native SDK](react-native-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. | Precarga todos los placements de forma concurrente al arrancar. | La precarga masiva bloquea el hilo JS y produce una pantalla en negro durante el pico. | | Llama a `getFlow` 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 `onLatestProfileLoad`. | Llama a `getFlow` en el montaje del componente raíz. | La atribución aún no ha llegado. El flow se resuelve contra la audiencia predeterminada y omite silenciosamente los segmentos y la personalización de ASA. | | Establece un `loadTimeoutMs` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | Espera indefinidamente a `getFlow`. | Sin un timeout, los usuarios con mala conectividad ven una pantalla en blanco hasta que la red responde — o cierran la app. | Consulta [Obtener paywalls y productos personalizados](fetch-paywalls-and-products-react-native) para la referencia de los parámetros `fetchPolicy` y `loadTimeoutMs`, y [Placements](placements) para elegir el placement adecuado. ## Mostrar el contenido multimedia de la primera pantalla desde el bundle de la app \{#show-first-screen-media-from-the-app-bundle\} Un flow descarga sus imágenes y vídeos desde Adapty. Para mostrar el contenido multimedia de la primera pantalla de forma instantánea, sírvelo desde el bundle de la app. Es una buena manera de reutilizar los recursos que ya tienes, como los elementos visuales de un onboarding nativo existente. 1. En el Flow & Paywall Builder, establece un [ID de medio personalizado](custom-media#custom-media-id) en la imagen o el vídeo. El archivo que subas allí se usará como respaldo. 2. Añade el archivo a los proyectos nativos: el bundle de la app iOS y la carpeta `assets` de Android. 3. Al crear la vista del flow, pasa el archivo incluido en el bundle para ese ID en `customAssets`: ```typescript showLineNumbers // "welcome_video" is the custom media ID set in the Flow & Paywall Builder const bundledAssets: Record<string, AdaptyCustomAsset> = { 'welcome_video': { type: 'video', fileLocation: { ios: { fileName: 'welcome.mp4' }, android: { relativeAssetPath: 'videos/welcome.mp4' }, }, }, }; const view = await createFlowView(flow, { customAssets: bundledAssets }); ``` Los archivos incluidos aumentan el tamaño de descarga de tu app, así que incluye solo los recursos multimedia que los usuarios ven primero. Los archivos multimedia que no incluyas en el bundle se muestran de inmediato de todas formas: el layout del flow incluye una copia pequeña de baja resolución de cada imagen, incluido el fotograma estático de un vídeo, y la muestra hasta que se cargue el archivo completo. Para la referencia completa de `customAssets`, consulta [Personalizar assets](react-native-get-pb-paywalls#customize-assets). ## Ajustes para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad habitualmente deficiente (zonas rurales, transporte, regiones con problemas de enrutamiento): - Establece `fetchPolicy: 'return_cache_data_else_load'` en todas las llamadas 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 paywall de respaldo cuando se agote el tiempo. - No condicionales la visualización del flow a `getProfile()`. Llama a `getFlow` de forma independiente para que un perfil lento no bloquee la UI. --- # File: react-native-show-aa-targeted-paywall --- --- title: "Mostrar un paywall orientado a Apple Ads 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 aplique la atribución en React Native, usando AdaptyProfile.appliedExternalAttributionProviders." --- Este artículo se aplica a la compilación iOS de tu app. La atribución de Apple Ads solo existe en iOS. 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 `getFlow` 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 de inmediato y actualízalo una vez que se aplique la atribución de AA, para que los usuarios de Apple Ads obtengan la variante segmentada y el resto no tenga que esperar. `AdaptyProfile.appliedExternalAttributionProviders` te indica cuándo se ha aplicado la atribución de AA. :::important Esta propiedad solo muestra datos de Apple Ads. La atribución de otros proveedores es visible en los perfiles de usuario y está disponible en los filtros de segmento, pero aún no aquí. ::: ## Antes de comenzar \{#before-you-start\} Necesitas: - Adapty React Native SDK **4.1** o posterior. En las versiones 3.17.1–4.0.x, la propiedad del perfil se llama `appliedAttributionSources` y los flows se obtienen con `getPaywall`. - 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 para el perfil, el SDK entrega un `AdaptyProfile` actualizado a tu listener `onLatestProfileLoad`, con `'apple_search_ads'` en su array `appliedExternalAttributionProviders`. Esto te permite cargar el paywall en dos pasos: 1. Llama a `getFlow` de inmediato. Sin atribución aplicada todavía, Adapty resuelve la solicitud frente a la audiencia predeterminada, por lo que el usuario ve un paywall de inmediato. 2. Cuando aparece `'apple_search_ads'`, llama a `getFlow` de nuevo. Adapty ahora resuelve la solicitud frente a la audiencia de Apple Ads y devuelve el paywall segmentado, que reemplaza al primero. `appliedExternalAttributionProviders` puede estar vacío o ausente. Eso significa una de las siguientes cosas: - La atribución de Apple Ads aún no se ha procesado para este perfil. - No ha llegado ninguna atribución. - La atribución llegó de otro proveedor, que este array no registra. En los tres casos, 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 cuando aparece `'apple_search_ads'`. :::important En cada inicio posterior, el perfil en caché ya incluye `'apple_search_ads'` en `appliedExternalAttributionProviders`, por lo que el primer `getFlow` ya devuelve el paywall segmentado por Apple Ads — no hay una segunda solicitud ni ningún cambio visible. El flujo de dos pasos solo importa en el primer inicio, 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 [Instalar y configurar el SDK de React Native](sdk-installation-reactnative). 2. **Carga y muestra un paywall** con `getFlow` como de costumbre — no bloquees esperando la atribución. 3. **Suscríbete a actualizaciones del perfil** con `adapty.addEventListener('onLatestProfileLoad', …)` y espera `'apple_search_ads'`. Cuando aparezca, vuelve a obtener el paywall y muestra el actualizado. Si aún no has configurado el listener, consulta [Escuchar actualizaciones de suscripción](react-native-check-subscription-status#listen-to-subscription-updates): ```typescript const subscription = adapty.addEventListener('onLatestProfileLoad', async profile => { if (!profile.appliedExternalAttributionProviders?.includes('apple_search_ads')) return; const targeted = await adapty.getFlow(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_PROVIDER = 'apple_search_ads'; const placementId = 'YOUR_PLACEMENT_ID'; function hasAppleAdsAttribution(profile: AdaptyProfile): boolean { return profile.appliedExternalAttributionProviders?.includes(APPLE_ADS_PROVIDER) ?? 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 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 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: react-native-reference --- --- title: "Referencia del SDK de React Native" description: "Documentación de referencia para el SDK de React Native de Adapty." --- Esta página contiene la documentación de referencia del SDK de React Native de Adapty. Elige el tema que necesites: - **[Modelos del SDK](https://react-native.adapty.io/modules)** - Modelos de datos y estructuras utilizados por el SDK - **[Gestionar errores](react-native-handle-errors)** - Gestión de errores y solución de problemas --- # File: react-native-handle-errors --- --- title: "Gestionar errores en el SDK de React Native" description: "Gestiona errores en el SDK de React Native." --- Todos los errores que devuelve el SDK son de tipo `AdaptyErrorCode`. A continuación 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('verbose')` — consulta Logging para [RN puro](sdk-installation-react-native-pure#logging) o [Expo](sdk-installation-react-native-expo#logging)), ese error interno se imprime en la consola, lo que normalmente indica la causa real. ::: :::important Si estas soluciones no resuelven tu problema, consulta [Otros problemas](#other-issues) para conocer los pasos a seguir antes de contactar con soporte y ayudarnos a asistirte de forma más eficiente. ::: ```typescript showLineNumbers try { const params: MakePurchaseParamsInput = {}; await adapty.makePurchase(product, params); } catch (error) { if ( error instanceof AdaptyError && error.adaptyCode === getErrorCode('cantMakePayments') ) { // Las compras in-app no están permitidas en este dispositivo } } ``` `error.adaptyCode` es un número, así que compáralo con un número: ya sea un literal (`1003`) o el resultado de `getErrorCode('<name>')`. `ErrorCode` funciona en sentido contrario — de un número a su nombre — así que úsalo para registrar logs, no para comparar. :::note Una compra cancelada por el usuario **no** es un error. `makePurchase` resuelve con un [`AdaptyPurchaseResult`](react-native-sdk-models) cuyo `type` es `'user_cancelled'` — consulta [Realizar compras](react-native-making-purchases). ::: ## Códigos del sistema StoreKit \{#system-storekit-codes\} | Error | Código | Solución | |-----|----|-----------| | [unknown](https://developer.apple.com/documentation/storekit/skerror/code/unknown) | 0 | Código de error que indica que ocurrió un error desconocido o inesperado. <br/> Reintenta o consulta la sección [Otros problemas](#other-issues). | | [clientInvalid](https://developer.apple.com/documentation/storekit/skerror/code/clientinvalid) | 1 | Este código de error indica que el cliente no tiene permiso para realizar la acción solicitada. | | [paymentCancelled](https://developer.apple.com/documentation/storekit/skerror/code/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, aunque desde el punto de vista del negocio puedes ofrecer un descuento al usuario o recordárselo más adelante.</p> | | [paymentInvalid](https://developer.apple.com/documentation/storekit/skerror/code/paymentinvalid) | 3 | Este error indica que uno de los parámetros de pago no fue reconocido por el App Store. | | [paymentNotAllowed](https://developer.apple.com/documentation/storekit/skerror/code/paymentnotallowed) | 4 | Este código de error indica que el usuario no tiene permiso para autorizar pagos. | | [storeProductNotAvailable](https://developer.apple.com/documentation/storekit/skerror/code/storeproductnotavailable) | 5 | Este código de error indica que el producto solicitado no está disponible en el store. <br/> Intenta reinstalar la app. | | [cloudServicePermissionDenied](https://developer.apple.com/documentation/storekit/skerror/code/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](https://developer.apple.com/documentation/storekit/skerror/code/cloudservicenetworkconnectionfailed) | 7 | Este código de error indica que el dispositivo no pudo conectarse a la red. | | [cloudServiceRevoked](https://developer.apple.com/documentation/storekit/skerror/code/cloudservicerevoked/) | 8 | Este código de error indica que el usuario ha revocado el permiso para usar este servicio en la nube. | | [privacyAcknowledgementRequired](https://developer.apple.com/documentation/storekit/skerror/code/privacyacknowledgementrequired) | 9 | Este código de error indica que el usuario aún no ha aceptado la política de privacidad de Apple. | | [unauthorizedRequestData](https://developer.apple.com/documentation/storekit/skerror/code/unauthorizedrequestdata) | 10 | Este código de error indica que la app está intentando usar una propiedad para la que no tiene el entitlement necesario. | | [invalidOfferIdentifier](https://developer.apple.com/documentation/storekit/skerror/code/invalidofferidentifier) | 11 | <p>El [`identifier`](https://developer.apple.com/documentation/storekit/skpaymentdiscount/identifier) de la oferta no es válido. Por ejemplo, no has configurado una oferta con ese identificador en el App Store, o la oferta ha sido revocada.</p><p>Asegúrate de configurar las ofertas deseadas en App Store Connect y de pasar un identificador de oferta válido.</p> | | [invalidSignature](https://developer.apple.com/documentation/storekit/skerror/code/invalidsignature) | 12 | Este código de error indica que la firma en un descuento de pago no es válida. | | [missingOfferParams](https://developer.apple.com/documentation/storekit/skerror/code/missingofferparams) | 13 | Este código de error indica que faltan parámetros en un descuento de pago. | | [invalidOfferPrice](https://developer.apple.com/documentation/storekit/skerror/code/invalidofferprice/) | 14 | Este código de error indica que el precio especificado en App Store Connect ya no es válido. Las ofertas siempre deben representar un precio con descuento. | ## Códigos de Android personalizados \{#custom-android-codes\} | Error | Código | Solució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 el store. | | currentSubscriptionToUpdateNotFoundInHistory | 24 | No se encuentra la suscripción original que debe renovarse. | | 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 Play Store en el dispositivo actual. | | billingServiceDisconnected | 99 | Este error fatal indica que la conexión de la app cliente con el servicio de Google Play Store a través de `BillingClient` se ha interrumpido. | | billingServiceUnavailable | 102 | Este error transitorio indica que el servicio Google Play Billing no está disponible en este momento. En la mayoría de los casos, significa que hay un problema de conexión de red en algún punto entre el dispositivo cliente y los servicios de Google Play Billing. | | billingUnavailable | 103 | <p>Este error indica que se produjo 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 se encuentra en un país no compatible.</p><p>3. El usuario es un usuario empresarial y su administrador ha deshabilitado las compras.</p><p>4. Google Play no puede cobrar el método de pago del usuario. Por ejemplo, la tarjeta de crédito del usuario puede haber expirado.</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ó sin | | [billingNetworkError](https://developer.android.com/google/play/billing/errors#network_error_error_code_12) | 112 | Este error indica que hubo un problema con la conexión de red entre el dispositivo y los sistemas de Play. | ## Códigos personalizados de StoreKit \{#custom-storekit-codes\} | Error | Código | Solución | |-----|----|-----------| | noProductIDsFound | 1000 | <p>Este error 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 puede aparecer junto con una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin un error, puedes ignorarla sin problema.</p><p>Si encuentras este error, sigue los pasos de la sección [Fix for Code-1000 `noProductIDsFound` error](InvalidProductIdentifiers-react-native).</p> | | productRequestFailed | 1002 | <p>No se pueden obtener los productos disponibles en este momento. Posible causa:</p><p></p><p>- No se ha creado ninguna caché todavía y tampoco hay conexión a internet.</p> | | cantMakePayments | 1003 | Las compras in-app no están permitidas en este dispositivo. Consulta la [guía](cantMakePayments-react-native) de resolución de problemas. | | noPurchasesToRestore | 1004 | Obsoleto en Adapty React Native SDK 4.0.3: `restorePurchases()` ya no devuelve este error. Cuando no hay nada que restaurar, el método resuelve con el perfil actual, así que comprueba el estado del nivel de acceso en él. En versiones anteriores del SDK, este error indica que Google Play no encontró la compra a 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 a tu 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 logs detallados para verlo en la consola) para conocer el motivo real. El error interno suele ser uno de los códigos de StoreKit del 0 al 14 de la tabla anterior — los más habituales son `paymentCancelled`, `paymentInvalid`, `paymentNotAllowed` o `invalidOfferPrice`. Si no puedes identificar una causa 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 | Solución | | :------------------- | :--- |:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | notActivated | 2002 | El SDK de Adapty no está activado. <br/> Suele ocurrir cuando una pantalla de bienvenida o un hook de UI temprano llama a métodos de Adapty antes de que `adapty.activate` retorne. El síntoma es intermitente y puede no reproducirse en el simulador porque el timing en dispositivos reales es diferente. Usa `await` con `activate` antes de programar cualquier otra llamada al SDK. Consulta [Orden de llamadas en el SDK de React Native](react-native-sdk-call-order) para ver la secuencia completa. | | badRequest | 2003 | Solicitud incorrecta. <br/> Asegúrate de haber completado todos los pasos necesarios para [integrarte con el App Store](app-store-connection-configuration). | | serverError | 2004 | Error del servidor. <br/> Inténtalo de nuevo después de un tiempo. Si el problema persiste, contacta con el equipo de soporte de Adapty. | | networkFailed | 2005 | Este error indica problemas con la conexión de red del dispositivo del usuario. <br/> Prueba a desactivar la VPN o a cambiar de red móvil a WiFi, o viceversa. | | decodingFailed | 2006 | Este error indica que falló la decodificación de la respuesta. <br/> 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 inválida. | | encodingFailed | 2009 | Este error indica que falló la codificación de la solicitud. | | analyticsDisabled | 3000 | No podemos gestionar eventos de analíticas porque has [desactivado esa opción](analytics-integration#disabling-external-analytics-for-a-specific-customer). | | wrongParam | 3001 | Este error indica que alguno de tus parámetros no es correcto. <br/> Si usas el Paywall Builder antiguo y no puedes mostrar un paywall por este error, activa **Show on device** en ese builder.<br/> Otra posible causa es que la versión del archivo de [respaldo](fallback-paywalls) local no coincide con la versión del SDK. Descarga un nuevo archivo desde el dashboard. | | 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. <br/> Esto ocurre cuando se llama a un método mientras `adapty.identify` aún está en curso: la llamada en vuelo aterriza en un perfil que está a punto de ser reemplazado y el SDK la rechaza. Usa siempre `await` con `identify` antes de cualquier llamada de acción del usuario. Consulta [Orden de llamadas en el SDK de React Native](react-native-sdk-call-order). | | unsupportedData | 3007 | Este error indica que el formato de datos no es compatible con el SDK. | | persistingDataError | 3100 | Se produjo un error al guardar los datos. | | fetchTimeoutError | 3101 | Este error indica que la operación de obtención de datos superó el tiempo de espera. | | operationInterrupted | 9000 | El sistema interrumpió esta operación. <br/> Vuelve a intentar la llamada. | ## Otros problemas \{#other-issues\} Si todavía no has encontrado una solución, los siguientes pasos pueden ser: - **Actualiza 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 errores conocidos. - **Contacta con el equipo de soporte u obtén ayuda de otros desarrolladores** en el [foro de soporte](https://adapty.featurebase.app/). - **Contacta con el equipo de soporte a través de [support@adapty.io](mailto:support@adapty.io) o por el chat**: Si no estás listo para actualizar el SDK o no ha resuelto el problema, contacta con nuestro equipo de soporte. Ten en cuenta que tu problema se resolverá más rápido si [activas el registro detallado](sdk-installation-reactnative) y compartes los logs con el equipo. También puedes adjuntar fragmentos de código relevantes. --- # 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**. Copia solo el identificador — en formato DNS inverso sin espacios, por ejemplo, `com.company.app`. App Store Connect muestra el nombre de la app junto a él en otras vistas, como la lista de apps; pegar eso en lugar del identificador hará que la conexión no pueda validarse. <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: react-native-sdk-migration-guides --- --- title: "Guías de migración del SDK para React Native" description: "Guías de migración para las versiones del SDK de Adapty para React Native." --- Esta página contiene todas las guías de migración del SDK de Adapty para React Native. Elige la versión a la que quieres migrar para ver las instrucciones detalladas: - **[Migrar a v4.1](migration-to-react-native-sdk-41)** - **[Migrar a v4.0](migration-to-react-native-sdk-v4)** - **[Migrar a v3.14](migration-react-native-314)** - **[Migrar a v3.8](react-native-migration-guide-380)** - **[Migrar a v3.4](migration-to-react-native-sdk-34)** - **[Migrar a v3.3](migration-to-react-native330)** - **[Migrar a v3.0](migration-to-react-native-sdk-v3)** --- # File: migration-to-react-native-sdk-41 --- --- title: "Migrar el SDK de React Native de Adapty a v4.1" description: "Migra al SDK de React Native de Adapty v4.1: activa Adapty Attribution explícitamente, adopta las APIs de atribución externa renombradas y vuelve a descargar tu archivo de respaldo." --- El SDK de React Native de Adapty 4.1 cambia la forma en que se activa Adapty Attribution, renombra las APIs de atribución externa y cambia el formato del archivo de respaldo. También permite que tu app tome el control de las compras in-app promocionadas en el App Store y recupera el indicador `hasViewConfiguration` en `AdaptyFlow`. :::warning Las APIs renombradas suponen un cambio radical. Los nombres anteriores han sido eliminados por completo — no existen alias obsoletos que sirvan de puente. El código que verifica tipos contra la versión 4.0.x fallará en la 4.1 hasta que renombres todos los puntos de llamada indicados a continuación. ::: Si todavía estás en la versión 3.x, empieza por [Migrar a v4.0](migration-to-react-native-sdk-v4) y luego sigue esta guía. ## Referencia rápida \{#quick-reference\} | v4.0 | v4.1 | |---|---| | Atribución de Adapty habilitada automáticamente | Atribución de Adapty deshabilitada por defecto; actívala con `adaptyAttributionEnabled: true` | | `adapty.updateAttribution(attribution, source)` | `adapty.updateExternalAttribution(attribution, provider)` | | `AttributionSource` | `AdaptyExternalAttributionProvider`, con un nuevo valor `'custom'` | | `AdaptyProfile.appliedAttributionSources` | `AdaptyProfile.appliedExternalAttributionProviders` | | Archivo de respaldo descargado para 4.0 | Nuevo formato de archivo de respaldo; descarga el archivo de nuevo | | Las compras in-app promocionadas se completaban automáticamente, sin posibilidad de interceptarlas | El evento `'onPromotedPurchaseReceived'` y `adapty.makePromotedPurchase(product)` delegan la finalización a tu app | | `hasViewConfiguration` ausente en `AdaptyFlow` | `hasViewConfiguration` de vuelta en `AdaptyFlow` | | Proyecto iOS integrado con CocoaPods | CocoaPods, o la integración SPM de React Native en un proyecto React Native puro | ## Instalación \{#installation\} Actualiza el paquete `react-native-adapty`: ```bash showLineNumbers npm install react-native-adapty@latest # or yarn add react-native-adapty@latest ``` Los requisitos de tiempo de ejecución no han cambiado respecto a la versión 4.0: **iOS 15.0** y **React Native 0.75**. Consulta [Instalar el SDK de Adapty](sdk-installation-reactnative) para ver la configuración completa. 4.1 también admite la integración SPM de React Native, que reemplaza CocoaPods en tu proyecto iOS y requiere **React Native 0.87** o superior. Aplica únicamente a proyectos React Native puros, CocoaPods sigue siendo la opción predeterminada y la migración es opcional — consulta [Configura tu proyecto iOS](sdk-installation-react-native-pure#set-up-your-ios-project). Si tu app usa el [Modo Niños](kids-mode-react-native), cada gestor de paquetes lo aplica de forma diferente. ## ⚠️ La atribución de Adapty está desactivada por defecto \{#adapty-attribution-is-disabled-by-default\} :::warning Si actualizas al SDK 4.1 y no te registras, la [atribución de Adapty](user-acquisition) deja de funcionar sin avisar — las instalaciones dejan de registrarse y no recibes ninguna advertencia. ::: En la versión 4.0 y anteriores, el SDK registraba las instalaciones para [Adapty Attribution](user-acquisition) automáticamente. A partir de la versión 4.1, esto está desactivado por defecto: el SDK no registra instalaciones, los eventos `'onInstallationDetailsSuccess'` y `'onInstallationDetailsFail'` nunca se disparan, y `getCurrentInstallationStatus` devuelve el estado `not_available`. Si usas Adapty Attribution, actívalo al inicializar el SDK: ```diff showLineNumbers adapty.activate('YOUR_PUBLIC_SDK_KEY', { + adaptyAttributionEnabled: true, }); ``` Si no usas Adapty Attribution, no es necesario realizar ningún cambio. ## APIs de atribución externa renombradas \{#renamed-external-attribution-apis\} Las APIs que pasan datos de atribución desde un proveedor externo (Adjust, AppsFlyer, Branch, Tenjin o uno personalizado) se han renombrado para coincidir con los SDKs nativos. ### updateAttribution → updateExternalAttribution El método ha sido renombrado y su segundo parámetro ha pasado de llamarse `source` a `provider`. Los datos de atribución siguen siendo un objeto plano: ```diff showLineNumbers - await adapty.updateAttribution(attribution, 'adjust'); + await adapty.updateExternalAttribution(attribution, 'adjust'); ``` ### AttributionSource → AdaptyExternalAttributionProvider El tipo de proveedor cambia de nombre. Sigue siendo una unión abierta — los valores predefinidos son `'apple_search_ads'`, `'adjust'`, `'appsflyer'`, `'branch'`, `'tenjin'`, y un nuevo `'custom'` para proveedores con los que Adapty no se integra directamente. También se acepta cualquier otra cadena, de modo que si Adapty añade un proveedor más adelante, funciona sin actualizar el SDK: ```diff showLineNumbers - import type { AttributionSource } from 'react-native-adapty'; + import type { AdaptyExternalAttributionProvider } from 'react-native-adapty'; ``` ### AdaptyProfile.appliedAttributionSources → appliedExternalAttributionProviders La propiedad del perfil que lista los proveedores de atribución aplicados al perfil se renombra, y el tipo de sus elementos cambia en consecuencia: ```diff showLineNumbers - if (profile.appliedAttributionSources?.includes('apple_search_ads')) { + if (profile.appliedExternalAttributionProviders?.includes('apple_search_ads')) { // Apple Ads attribution has been applied } ``` El código que la lee necesita actualizarse — consulta [Mostrar un paywall dirigido de Apple Ads](react-native-show-aa-targeted-paywall). ## Archivos de respaldo \{#fallback-files\} El formato del [archivo de respaldo](fallback-flows) cambió en el SDK 4.1. Descarga el archivo de nuevo desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** y añádelo a tu app, aunque ya lo hayas descargado para la versión 4.0. :::warning Este paso no genera ningún error de compilación. Si lo omites, `setFallback` rechazará el archivo obsoleto y todos los placements perderán su paywall de respaldo. ::: ## hasViewConfiguration regresa a AdaptyFlow \{#hasviewconfiguration-returns-to-adaptyflow\} La versión 4.0 eliminó `hasViewConfiguration` cuando `AdaptyPaywall` pasó a llamarse `AdaptyFlow`. La 4.1 la restaura en `AdaptyFlow`, de modo que una rama `hasViewConfiguration` de la época de v3 vuelve a funcionar en cuanto renombras la función y el objeto que lee: ```diff showLineNumbers - if (paywall.hasViewConfiguration) { - const view = await createPaywallView(paywall); + if (flow.hasViewConfiguration) { + const view = await createFlowView(flow); await view.present(); } ``` En 4.0, cuando el flag no está presente, `createFlowView` lanza un `AdaptyError` para un flow sin configuración de vista. Esto sigue siendo válido en 4.1 — el flag es una alternativa a capturar el error, no un reemplazo. Consulta [Obtener la configuración de vista](react-native-get-pb-paywalls#fetch-the-view-configuration). ## Compras in-app promocionadas en la App Store \{#app-store-promoted-in-app-purchases\} En la versión 4.0, una [compra in-app promocionada en tu página de producto de la App Store](react-native-making-purchases#in-app-purchases-from-the-app-store) se completaba por sí sola y Adapty registraba la transacción como cualquier otra, pero tu app no tenía forma de interceptarla. La versión 4.1 añade ese hook, por lo que se trata de una nueva funcionalidad y no de un paso de migración: sin código propio, el SDK sigue completando las compras promocionadas por ti. Escribe código solo para hacerte cargo de la finalización tú mismo, por ejemplo, para mostrar primero una pantalla. Registra un listener para el nuevo evento `'onPromotedPurchaseReceived'` y completa la compra con `adapty.makePromotedPurchase`. Mientras ese listener esté registrado, el SDK deja de completar las compras promocionadas por ti. :::warning El hook está construido sobre StoreKit 2 y requiere **iOS 16.4** o posterior. Por debajo de iOS 16.4, el evento nunca se dispara y las compras promocionadas se completan como lo hacían en la versión 4.0. El evento tampoco se dispara en Android. ::: --- # File: migration-to-react-native-sdk-v4 --- --- title: "Migrar el SDK de React Native de Adapty a v4.0" description: "Migra al SDK de React Native de Adapty v4.0 reemplazando las APIs de paywall por APIs de flow, que renderizan flows además de los paywalls del builder anterior." --- El SDK de React Native de Adapty 4.0 introduce los flows y cambia el nombre de las APIs de paywall en consecuencia. Las nuevas APIs funcionan con flows y siguen siendo compatibles con los paywalls del builder anterior; no se requieren cambios de configuración en el Adapty Dashboard. :::note Esta guía cubre la migración de 3.x → 4.0. El SDK 4.1 introduce algunos cambios adicionales en la API: la atribución de Adapty pasa a ser opt-in, los métodos de atribución externa se renombran y el formato del archivo de paywall de respaldo vuelve a cambiar. Cuando termines aquí, consulta [Migrar a v4.1](migration-to-react-native-sdk-41). ::: ## 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` conserva su nombre — los productos siguen perteneciendo a un flow, y `getPaywallProducts` ahora toma un `AdaptyFlow`. Los métodos `getFlow` y `getFlowForDefaultAudience` ya no aceptan el parámetro `locale` — pásalo a `createFlowView` en su lugar. Los métodos de la 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. Algunos comportamientos por defecto han cambiado — consulta [Cambios en el comportamiento por defecto](#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.2 # or yarn add react-native-adapty@4.0.2 ``` ### iOS: los SDK nativos ahora se obtienen 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/), así que a partir de la v4 los SDK nativos `Adapty`, `AdaptyUI` y `AdaptyPlugin` **ya no se incluyen como sub-dependencias de CocoaPods** — el podspec los obtiene a través de **Swift Package Manager** (mediante el helper `spm_dependency`). Esto requiere tres cosas: - **Xcode 26 o posterior** — el manifiesto del paquete del SDK nativo de iOS declara Swift tools 6.2, que las versiones anteriores de Xcode no pueden leer. - **React Native 0.75 o posterior** — necesario para el helper de podspec `spm_dependency`. En versiones anteriores, `pod install` falla con un error explícito; actualiza React Native primero, o quédate en `react-native-adapty` 3.x. - **Frameworks dinámicos** — las dependencias de SPM requieren enlazado dinámico. La forma de habilitarlo difiere entre Expo y React Native sin configuración adicional. :::tip A partir del SDK 4.2.3, si tu proyecto no puede cambiar a frameworks dinámicos —por ejemplo, porque depende de enlazado estático o de Flipper— puedes instalar los SDK nativos de iOS como pods de CocoaPods. Consulta la opción **CocoaPods without SPM** para [React Native puro](sdk-installation-react-native-pure#set-up-your-ios-project) o [Expo](sdk-installation-react-native-expo#enable-dynamic-frameworks). ::: #### 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 ``` Si el build de iOS falla con `'React/RCTBridge.h' file not found`, actualiza `expo-modules-autolinking` a la versión 57.0.10 o posterior (Expo SDK 57) o 56.0.22 o posterior (Expo SDK 56) — consulta [la entrada de solución de problemas](sdk-installation-react-native-expo#react-header-not-found) para más detalles y una solución alternativa. #### Bare React Native \{#bare-react-native\} Añade 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 has incluido `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 de la vinculación estática predeterminada 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 la integración de 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 la configuración completa. ## Obtención de flows \{#fetching-flows\} ### getPaywall → getFlow El tipo devuelto cambia de `AdaptyPaywall` a `AdaptyFlow`, y el parámetro `locale` se traslada de la llamada de obtención a `createFlowView`; para paywalls personalizados, todas las configuraciones regionales se devuelven en `flow.remoteConfigs`: ```diff showLineNumbers - const paywall = await adapty.getPaywall('YOUR_PLACEMENT_ID', 'en'); + const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); + const view = await createFlowView(flow, { locale: 'en' }); ``` `locale` es opcional en `createFlowView`: si se omite, la vista se renderiza en `en`, o en la localización predeterminada del flow si no tiene `en`. Requiere el SDK 4.0.2 o posterior; consulta [Localizaciones y códigos de idioma](react-native-localizations-and-locale-codes). `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); ``` ### Archivos de respaldo \{#fallback-files\} El formato del archivo de respaldo [cambió en el SDK v4](fallback-flows). Descarga el nuevo archivo desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** y agrégalo a tu app. ## 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 se encuentran en cada variación del flow, no en el flow en sí. | | `webPurchaseUrl?` | `flow.paywalls[i].webPurchaseUrl` | Movido del flow a cada variación del paywall. | | `version?: number` | `flowVersionId?: string` | Renombrado, y el tipo cambió de `number` a `string`. | | `hasViewConfiguration` | eliminado en 4.0, de vuelta en 4.1 | Elimina cualquier comprobación de `hasViewConfiguration` de tu código mientras uses la 4.0. El flag vuelve en `AdaptyFlow` en la 4.1 — consulta [Migrar a v4.1](migration-to-react-native-sdk-41#hasviewconfiguration-returns-to-adaptyflow). | | `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. | 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 sido renombrado a `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 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 al mostrar flows o paywalls renderizados por Adapty — 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. ## Obsolescencia de la API de onboarding \{#onboarding-api-deprecation\} La API de onboarding heredada está obsoleta en v4.0 en favor del [Flow & Paywall 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 & Paywall 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#configure-your-app-code). ```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-10-30T13:00:39.706Z_ _Successfully processed: 61/61 files_ # TUTORIAL - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-10-30T13:00:39.709Z Total files: 363 --- # File: what-is-adapty --- --- title: "Bienvenido a Adapty" description: "Descubre qué es Adapty y cómo te ayuda a gestionar suscripciones." --- <Homepage /> #### Adapty funciona para desarrolladores, marketers y directivos Los marketers pueden interactuar directamente con los usuarios mediante ofertas promocionales para recuperarlos o hacerles upsell de nuevos productos. Con Adapty, los programadores y analistas no necesitan extraer segmentos manualmente. Para product managers y ejecutivos, Adapty cuenta con un dashboard con métricas de suscripción relevantes e informes diarios, semanales y mensuales enviados a Slack y por email. --- # 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: quickstart --- --- title: "Guía de inicio rápido" description: "Integra Adapty con App Store, Google Play, stores personalizadas, Stripe y Paddle." --- ¡Bienvenido a Adapty! Estás a punto de dar el primer paso para hacer crecer tus compras in-app con la mejor solución para aumentar los ingresos de tu app. La implementación es sencilla, y esta guía de inicio rápido te guiará por todo el proceso. Una vez que completes esta guía: - Adapty gestionará las compras in-app con toda la lógica asociada. - Tendrás la flexibilidad de mostrar el paywall adecuado en el momento oportuno a usuarios específicos. - Accederás a analíticas detalladas de las compras in-app. - Podrás empezar pruebas A/B y enviar eventos de suscripción a analíticas de terceros. Puedes empezar con Adapty en solo cinco pasos sencillos: 1. [**Integra con stores o plataformas de pago**](integrate-payments): Conecta Adapty con App Store, Google Play, Stripe, Paddle u otras stores donde vendes productos. 2. [**Añade productos**](quickstart-products): Añade tus productos in-app o suscripciones a Adapty y vincúlalos con stores o plataformas de pago. 3. [**Añade un paywall para habilitar compras**](quickstart-paywalls): Añade un paywall para habilitar las compras in-app con Adapty. Te mostraremos la forma más sencilla de hacerlo. 4. [**Integra el SDK de Adapty en el código de tu app**](quickstart-sdk): El SDK de Adapty gestiona las compras, la gestión de suscripciones y la identificación de usuarios. 5. [**Prueba tu integración con Adapty**](quickstart-test): Asegúrate de que tu integración funciona como se espera y de que puedes ver tus compras en el Adapty Dashboard. --- # 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 fichas de App Store, Google Play, Stripe, Paddle o tiendas personalizadas." --- :::tip ¿Configurando Adapty de forma programática? Puedes completar este paso usando la [CLI para desarrolladores](developer-cli-quickstart). ::: Antes de poder utilizar las funciones principales de Adapty, debes añadir cada producto que vendes y vincularlo a cada store o plataforma de pago que soportas. Esta configuración te permite entregar productos a los dispositivos de los usuarios y hacer seguimiento de ellos en analytics más adelante. En Adapty, todo lo que tu app vende 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 gestiónalo en todas las plataformas 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> </TabItem> <TabItem value="products-in-stores" label="Products in stores already"> </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 vincularlo a un placement." --- :::info Para continuar con esta guía, asegúrate de haber completado la [integración con el store](integrate-payments) y de haber creado al menos un producto como se describe en la guía anterior sobre [cómo añadir productos](quickstart-products). ::: Ahora que ya tienes productos, necesitas una forma de mostrárselos a tus usuarios. Adapty te ofrece dos opciones: - **Flow & Paywall Builder (recomendado)**: Editor visual sin código para todo el flujo de compra. El SDK de Adapty renderiza el resultado de forma nativa, por lo que no hay que escribir código de UI. - **Paywall personalizado**: Tú creas un paywall personalizado, le asocias productos y renderizas la UI tú mismo en el código de la app. Ambas opciones terminan igual: lo que hayas creado lo vinculas 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 & Paywall Builder" default> :::important Los flows requieren Adapty SDK v4.0 o posterior. ::: Un flow es una o más pantallas con productos integrados directamente. Lo diseñas en el [Flow & Paywall 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 muestra las pantallas, gestiona las compras y registra los eventos. Sin código de UI adicional, sin un paywall que mantener por separado. ## 1. Crea el flow \{#1-build-the-flow\} 1. Ve a [**Flows & Paywalls**](https://app.adapty.io/flows) en el menú principal de Adapty y abre la pestaña **Flow & Paywall builder**. 2. Haz clic en **Create flow** y diseña tu flow. Aprende más sobre el [Adapty Flow & Paywall Builder](adapty-flow-builder). Las guías de plantillas a continuación explican paso a paso los patrones más habituales: <CustomDocCardList ids={['basic-paywall-screen', 'paywall-with-tabs', 'paywall-features-per-product', 'onboarding-flow-tutorial']} /> :::tip Para crear el flow desde tu herramienta de codificación con IA, instala el [plugin `adapty-skills`](flow-generator-skill#install-the-skill). Su skill flow-generator crea un flow a partir de una descripción de texto o una captura de pantalla y lo guarda como borrador. Antes de publicarlo, el [skill flow-audit](flow-audit-skill) verifica que el flow esté listo para producción. ::: Una vez que tu flow esté guardado y publicado, continúa conectándolo 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 escribes directamente los IDs de los placements. Todo lo demás —qué flow se ejecuta, qué productos vende, cómo se ve— se configura en el Adapty Dashboard y se puede cambiar en cualquier momento sin actualizar la app. :::tip Adapty te permite mostrar distintos flows a diferentes grupos de usuarios y analizar su rendimiento. Aprende más sobre [audiencias](audience) y [pruebas A/B](ab-tests). ::: </TabItem> <TabItem value="manual-paywall" label="Usar un paywall personalizado"> Un paywall personalizado es un contenedor configurado de forma remota para uno o más productos. Adapty envía la lista de productos y, opcionalmente, un payload JSON de [Remote Config](customize-paywall-with-remote-config) — tu código de aplicación los lee y construye la UI. :::tip ¿Configurando Adapty de forma programática? Puedes completar este paso usando el [Developer CLI](developer-cli-quickstart). ::: ### 1. Crear un paywall personalizado \{#1-create-a-custom-paywall\} 1. Ve a [**Flows & Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty y abre la pestaña **Custom Paywalls**. 2. Haz clic en **Create custom paywall**. 3. Introduce un **Paywall name**. Es un identificador interno en el Adapty Dashboard. 4. Haz clic en **Add product** y selecciona 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 personalizado 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 personalizado en el código de tu app. <InlineTooltip tooltip="implementación de paywalls personalizados">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), [Kotlin Multiplatform](kmp-implement-paywalls-manually), [Capacitor](capacitor-implement-paywalls-manually).</InlineTooltip> ### 2. Añade el paywall personalizado a un placement Crea un <InlineTooltip tooltip="placement">Un placement es un punto concreto de tu app donde muestras un flow, un paywall, un onboarding o una prueba A/B. Los placements te permiten dirigir contenido a [audiencias](audience) específicas. Más información sobre los [placements](placements).</InlineTooltip> para que tu app pueda solicitar el paywall en tiempo de ejecución. Empecemos por el más esencial: el placement de onboarding. Más adelante podrás añadir más [placements significativos](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 **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 codificas los IDs de placement. 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> </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." --- Repasa esta lista de verificación antes de publicar para confirmar que el flujo de monetización funciona de extremo a extremo en una build real. ## 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: migrate-to-adapty-from-another-solutions --- --- title: "Migrar a Adapty" description: "Migra a Adapty desde otras soluciones de gestión de suscripciones de forma sencilla." --- La migración tiene tres pasos: 1. Cambiar al SDK de Adapty. 2. Cambiar el webhook de notificaciones server2server de [Apple](enable-app-store-server-notifications) / [Google](enable-real-time-developer-notifications-rtdn). 3. (Opcional) [Importar datos históricos a Adapty](importing-historical-data-to-adapty) para obtener estadísticas de inmediato. Veamos cada parte rápidamente. :::info Tus suscriptores migrarán automáticamente Todos los usuarios que alguna vez hayan activado una suscripción se migrarán en cuanto abran la nueva versión 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. ::: ### Instalar el SDK de Adapty \{#installing-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)) en tu app y reemplaza la lógica heredada con los métodos correspondientes del SDK de Adapty. Las cosas principales que debes reemplazar: - Comprobar un [nivel de acceso](access-level) para abrir contenido restringido; - Realizar una compra; - Restaurar una compra; - Obtener/establecer información sobre tu usuario. :::tip ¿Cambias desde otro proveedor de suscripciones? Sigue nuestra guía para una explicación detallada: - [Migración desde RevenueCat](migration-from-revenuecat) (20 minutos) ::: ### Cambiar las notificaciones del servidor de Apple \{#changing-apple-server-notifications\} Apple y Google nos envían los eventos que ocurren con las suscripciones de los usuarios fuera de la aplicación (renovación, cancelación, pausa, reembolso, etc.) a través de las [notificaciones del servidor de App Store](enable-app-store-server-notifications). Adapty puede funcionar sin esta URL, pero tendrás un conjunto de funciones limitado. Por ejemplo, las [integraciones](events) con servicios de terceros se retrasarán, los análisis de suscripciones no serán en tiempo real y las métricas de las pruebas A/B de paywalls no serán precisas. Al cambiar desde un sistema heredado, a veces quieres que ambos sistemas funcionen simultáneamente durante un tiempo. En ese caso, puedes usar el [reenvío de eventos sin procesar](enable-app-store-server-notifications#raw-events-forwarding), donde Adapty actúa como servidor proxy para tu sistema heredado. <img src="/assets/shared/img/c7d4fd0-Seamless_migrat_a.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Mover datos históricos a Adapty \{#move-historical-data-to-adapty\} Mover los datos históricos es opcional y no afectará al estado de tus suscriptores. Sin embargo, hay varias razones por las que conviene hacerlo: 1. **Los análisis funcionarán correctamente de inmediato**. Adapty identifica a los suscriptores por el ID de transacción original, y no contamos los eventos del webhook de Apple sin haberlos expuesto al SDK de Adapty (técnicamente no podemos hacerlo). 2. **Los datos utilizados estarán disponibles**. Tendrás todos los perfiles de Adapty con las propiedades del usuario y podrás usarlos en [segmentos](segments) y en [Perfiles/CRM](profiles-crm). Sigue nuestro [tutorial](importing-historical-data-to-adapty) para enviarnos los datos históricos. --- # 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 para 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 cambiar al sistema de Adapty, puedes explorar el modo Observer de Adapty. Este modo limitado prescinde del uso de los paywalls de Adapty, la segmentación de audiencias, la gestión de suscripciones (incluida la gestión de renovaciones y reintentos de cobro), y se centra exclusivamente en el análisis. A pesar de sus limitaciones, el modo Observer sigue ofreciendo sólidas capacidades analíticas, incluyendo integración con sistemas de atribución, análisis avanzado, mensajería y perfiles CRM. Ambos modos se ofrecen al mismo precio y requieren actualizar tu aplicación móvil, por lo que la elección se reduce esencialmente a migrar a la infraestructura de Adapty para obtener la funcionalidad completa o conservar tu infraestructura actual ganando únicamente integraciones con terceros y capacidades analíticas. | Funcionalidad | Modo Observer | Modo completo | |-------------|-------------|---------| | **Analíticas completas** | ✅ | ✅ | | **Integraciones con terceros** | ✅ | ✅ | | **Respuesta a eventos de compra para otorgar/restringir el acceso de pago a tus usuarios** | ❌ | ✅ | | **Responsable de la infraestructura de compras** | Tú | Adapty | | **Pruebas A/B** | <p>Es posible, 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íticas 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 estableciendo el parámetro `observerMode` en `true`: [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) 3. Reporta las transacciones de tu infraestructura de compras existente a Adapty: [iOS](report-transactions-observer-mode) | [Android](report-transactions-observer-mode-android) | [React Native](report-transactions-observer-mode-react-native) | [Flutter](report-transactions-observer-mode-flutter) | [Unity](report-transactions-observer-mode-unity) | [Kotlin Multiplatform](report-transactions-observer-mode-kmp) | [Capacitor](report-transactions-observer-mode-capacitor) 4. (opcional) Si deseas usar integraciones de terceros, configúralas como se describe en el tema [Configurar integraciones de 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 flows, paywalls y pruebas A/B en modo Observer \{#how-to-use-paywalls-and-a-b-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 eso, si quieres usar flows, paywalls y/o pruebas A/B en modo Observer, debes asociar la transacción proveniente de tu store con el flow o paywall correspondiente en el código de tu app móvil al reportar una transacción. Además, los flows y paywalls construidos en el Flow & Paywall Builder deben mostrarse de una forma especial cuando se usa el modo Observer: - Presentar flows y paywalls en modo Observer: [iOS](ios-present-paywall-builder-paywalls-in-observer-mode) | [Android](android-present-paywall-builder-paywalls-in-observer-mode) | [React Native](react-native-present-flows-in-observer-mode) | [Flutter](flutter-present-flows-in-observer-mode) | [Unity](unity-present-flows-in-observer-mode) | [Kotlin Multiplatform](kmp-present-flows-in-observer-mode) | [Capacitor](capacitor-present-flows-in-observer-mode) - Asociar flows y paywalls con transacciones de compra al informar transacciones en modo Observer: [iOS](report-transactions-observer-mode) | [Android](report-transactions-observer-mode-android) | [React Native](report-transactions-observer-mode-react-native) | [Flutter](report-transactions-observer-mode-flutter) | [Unity](report-transactions-observer-mode-unity) | [Kotlin Multiplatform](report-transactions-observer-mode-kmp) | [Capacitor](report-transactions-observer-mode-capacitor) --- # File: migration-from-revenuecat --- --- title: "Migración desde RevenueCat" description: "Migra de RevenueCat a Adapty: traslada tu catálogo y el código de la app con la CLI de Adapty, cambia las notificaciones de la store e importa tu historial de transacciones." --- Una migración desde RevenueCat tiene ocho pasos. Un agente de codificación hace la mayor parte del trabajo: ejecuta las migraciones con el [Adapty CLI](developer-cli) y reemplaza el SDK de RevenueCat en tu app. Tú te encargas de lo que el agente no puede alcanzar: los secretos y la configuración en RevenueCat, App Store Connect y Google Cloud. 1. [Aprende las diferencias principales](#learn-the-core-differences). 2. [Migra tu catálogo a Adapty](#move-your-catalog-to-adapty). 3. [Migra los diseños de tus paywalls](#move-your-paywall-designs), si tus ofertas se convierten en flows. 4. [Reemplaza el SDK de RevenueCat](#replace-the-revenuecat-sdk) con tu agente de codificación. 5. [Migra tus integraciones](#move-your-integrations). 6. [Reenvía los eventos del store a Adapty](#forward-store-events-to-adapty). 7. [Prueba y publica una nueva versión de tu app](#test-and-release-a-new-version-of-your-app). 8. (Opcional) [Importa el historial de transacciones](#optional-import-your-transaction-history). :::info Tus suscriptores migrarán automáticamente Todos los usuarios que alguna vez hayan activado una suscripción pasarán automáticamente a Adapty 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 la 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 principales \{#learn-the-core-differences\} Los SDK 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 bajo demanda de la forma más rápida posible cuando la solicitas. Por ejemplo, al pedir un paywall, primero obtienes el [Remote Config](customize-paywall-with-remote-config) para construir previamente tu onboarding o paywall y luego solicitas los productos en una petición independiente. Los nombres difieren ligeramente: | RevenueCat | Adapty | | :---------- | :-------------- | | Package | Producto | | Offering | Paywall | | Paywall | Flow | | Entitlement | Nivel de acceso | 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, tienes uno o dos placements: - Onboarding (porque el 80% de todas las compras se realizan ahí); - General (lo muestras 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 */ }} /> ## Migra tu catálogo a Adapty \{#move-your-catalog-to-adapty\} La migración del catálogo crea una nueva app de Adapty a partir de tu proyecto de RevenueCat: niveles de acceso a partir de entitlements, productos a partir de paquetes y un placement con un paywall o un flow para cada offering. ### Antes de empezar - Utiliza un agente de codificación que ejecute comandos de terminal, por ejemplo Claude Code, Codex o Cursor. - En la terminal que usa tu agente, [instala la CLI de Adapty](developer-cli-quickstart) y [accede con tu cuenta](developer-cli-authentication). - En RevenueCat, [crea una clave de API secreta (v2)](#create-a-revenuecat-secret-api-key) para el proyecto que vas a migrar. - Comprueba las stores. La migración solo cubre apps de App Store y Google Play. ### Crea una clave de API secreta de RevenueCat \{#create-a-revenuecat-secret-api-key\} Adapty lee tu catálogo de RevenueCat con una clave de API secreta del proyecto que migras. Las claves secretas en RevenueCat pertenecen a un único proyecto, así que créala en ese proyecto. 1. Para abrir la lista de claves, en el dashboard de RevenueCat abre el proyecto y ve a **API keys**. 2. Para crear la clave, haz clic en **New secret API key**. 3. Para configurar la clave, introduce un nombre, por ejemplo `Adapty migration`, y elige **V2** en **API version**. 4. Para que Adapty pueda leer el catálogo, establece **Project configuration permissions** en **Read only**. Adapty no necesita ningún otro permiso. 5. Para crear la clave, haz clic en **Generate** y cópiala. :::warning Usa una clave secreta, no una pública. Una clave pública específica de la app no puede leer la configuración del proyecto. ::: Cuando la migración solicite la clave, pégala en la página del Adapty Dashboard que se abre. ### Ejecutar la migración del catálogo \{#run-the-catalog-migration\} 1. Para iniciar la migración, pregúntale a tu agente: ```text Migrate my RevenueCat project to a new Adapty app called "Acme Fitness" with the Adapty CLI. ``` 2. Para conectar RevenueCat, pega la clave API secreta en la página del Adapty Dashboard que abre el agente. La clave va directamente a Adapty y nunca pasa por el agente ni por la CLI. 3. Para elegir qué migrar, indícale al agente qué aplicaciones de RevenueCat pertenecen juntas, normalmente una por plataforma del mismo producto. Juntas formarán una única app de Adapty. 4. Para cerrar el mapeo, responde las preguntas del agente sobre las entidades que Adapty no pudo mapear por sí solo. Para cada offering, elige qué contiene su placement: un paywall que tu app renderiza por su cuenta, o un flow del [Flow & Paywall Builder](adapty-paywall-builder). Adapty sugiere un flow para los offerings que tienen un paywall de RevenueCat. :::warning Las decisiones son definitivas. Cuando todas las entidades tienen una decisión, Adapty crea el catálogo y no podrás cambiar el mapeo después. ::: Adapty crea niveles de acceso, productos, paywalls y flows, y placements, en ese orden. Si alguno falla, el agente te indica el motivo. Cuando todas las entidades están en Adapty, la migración se completa. 5. Para que la nueva app pueda validar las compras, conéctala a los stores en el Adapty Dashboard: [App Store](initial_ios) y [Google Play](initial-android). :::note Para ejecutar la migración sin un agente, usa los [comandos de migración](developer-cli-migrations). `adapty migrations status` indica la próxima acción y los datos que necesita. ::: ### Cómo se relacionan las entidades de RevenueCat con Adapty - Un identificador de entitlement se convierte en el ID del nivel de acceso, por lo que `customerInfo.entitlements["premium"]` se convierte en `profile.accessLevels["premium"]`. - Un producto que no concede ningún entitlement concede el nivel de acceso `premium`. - Un identificador de offering se convierte en el ID del placement. - Los metadatos del offering se convierten en el [Remote Config](customize-paywall-with-remote-config) del paywall o del flow, hasta 256 KB. ### Lo que la migración del catálogo no incluye \{#what-the-catalog-migration-leaves-out\} - Offerings sin productos. - Productos cuyo ID de producto de App Store Adapty rechaza, por ejemplo un ID con dos puntos o un espacio. El agente te indica cuáles son, y no requieren ninguna decisión. - Apps en stores distintos de App Store y Google Play. - **Integraciones**: [Muévelas por separado](#move-your-integrations). ## Mueve los diseños de tus paywalls Cada flow que crea la migración del catálogo se publica con una pantalla vacía sobre los productos de la oferta. Para reconstruir los diseños de tus paywalls de RevenueCat en estos flows, pídele a tu agente: ```text Migrate my RevenueCat paywall designs to my Adapty app "Acme Fitness" with the Adapty CLI. ``` El agente reconstruye los flows uno a uno y publica cada uno después de que lo apruebes. Indica qué elementos debes añadir tú mismo, como vídeos y fuentes personalizadas. Lee los diseños con tu clave secreta de API de RevenueCat, así que consérvala hasta que todos los diseños estén migrados. :::warning Hasta que se le asigne un diseño, un flow muestra una pantalla en blanco. No publiques una versión de la app que muestre ese flow. ::: ## Reemplazar el SDK de RevenueCat \{#replace-the-revenuecat-sdk\} Un agente de codificación reemplaza el SDK de RevenueCat con el SDK de Adapty en el código de tu app, usando los IDs del catálogo que creó la migración. Cada ejecución migra una base de código: si tu app tiene proyectos separados para iOS y Android, ejecútalo en cada uno. Antes de empezar, haz commit o stash de tus cambios en el repositorio de la app, para que la migración quede como un único diff revisable. 1. Para iniciar la migración de código, abre tu agente en la raíz del repositorio de la app y pregunta: ```text Migrate the app code in this repository from RevenueCat to Adapty with the Adapty CLI, for my Adapty app "Acme Fitness". ``` Si el agente te pide que lo reinicies para cargar los skills de Adapty, reinícialo y repite la solicitud. 2. Si el diseño de un flow todavía no se ha migrado, dile al agente si la app muestra el flow o si mantiene su propia pantalla de paywall hasta entonces. 3. Para terminar la migración del código, revisa los cambios en la rama `adapty-migrate` y luego sigue las instrucciones de `ADAPTY_SETUP.md` en la raíz de tu proyecto. Ahí encontrarás lo que el agente dejó pendiente para ti. ## Migra tus integraciones \{#move-your-integrations\} Tu agente encuentra las herramientas de analítica, atribución y mensajería en el código de tu app e indica cómo configurar cada una en Adapty. No modifica ningún archivo. Pregunta: ```text Find the integrations in my app code to set up in my Adapty app "Acme Fitness", with the Adapty CLI. ``` Si el repositorio solo contiene el código de una plataforma, indica al agente dónde está la otra. A continuación, configura las integraciones tal como se describe en [Migrar integraciones a Adapty](migrate-integrations-to-adapty). Los webhooks, Slack y las exportaciones a almacenamiento en la nube no están en el código de la app: encuéntralos en la página **Integrations** de RevenueCat. ## Reenvía los eventos de la store a Adapty \{#forward-store-events-to-adapty\} El App Store y Google Play notifican a tu servidor sobre renovaciones, cancelaciones y reembolsos a través de notificaciones, incluidas las que ocurren fuera de tu app. Dos migraciones conectan Adapty a estas notificaciones mientras RevenueCat sigue recibiendo todos los eventos, de modo que ambos permanecen actualizados mientras tus usuarios se pasan a la nueva versión de la app. Inícielas tras completar la [migración del catálogo](#move-your-catalog-to-adapty). Adapty obtiene el bundle ID y el package name de las apps de RevenueCat que seleccionaste allí. <span id="switch-app-store-server-side-notifications-to-adapty"></span> ### Reenviar eventos de App Store \{#forward-app-store-events\} Adapty recibe las notificaciones de App Store y las reenvía a RevenueCat. Antes de empezar, crea una [clave de compras in-app](in-app-purchase-api-storekit-2) en App Store Connect y anota su **Issuer ID** y **Key ID**. 1. Para iniciar el reenvío, pídele a tu agente: ```text Forward App Store events for my Adapty app "Acme Fitness" to Adapty, and keep RevenueCat receiving them. ``` 2. Para que Adapty acceda a los eventos de App Store, en la página del Adapty Dashboard que abre el agente, sube el archivo `.p8` e introduce el **Issuer ID** y el **Key ID**. La clave va directamente a Adapty y nunca pasa por el agente ni por la CLI. 3. Para que RevenueCat siga recibiendo eventos de App Store, facilítale al agente la URL de notificaciones de RevenueCat. En RevenueCat, abre tu app de iOS y copia la **Apple Server Notification URL** desde **Apple Server to Server notification settings**. Empieza por `https://api.revenuecat.com/v1/incoming-webhooks/`. 4. Para que App Store apunte a Adapty, en App Store Connect abre tu app y ve a **App Information** > **App Store Server Notifications**. Establece tanto la **Production Server URL** como la **Sandbox Server URL** con la URL de Adapty que te proporciona el agente, con notificaciones **Version 2**. Para más detalles, consulta [Activar notificaciones del servidor de App Store](enable-app-store-server-notifications). El reenvío a RevenueCat ya está activo en este punto, por lo que no se pierde ningún evento. 5. Para verificar la conexión, indícale al agente que las URLs están configuradas. Adapty solicita a App Store una notificación de prueba y espera a recibirla. Si no llega, el agente te explica por qué. ### Reenviar eventos de Google Play \{#forward-google-play-events\} Google Play envía notificaciones a un topic de Pub/Sub. Adapty se suscribe al topic que ya usa RevenueCat como segundo suscriptor, para que RevenueCat siga recibiendo los eventos. :::warning No cambies el topic en Google Play Console y no sigas los pasos de [Habilitar RTDN](enable-real-time-developer-notifications-rtdn) para esta app. Un nuevo topic interrumpe las notificaciones a RevenueCat. ::: 1. Para comenzar el reenvío, pídele a tu agente: ```text Forward Google Play events for my Adapty app "Acme Fitness" to Adapty, and keep RevenueCat receiving them. ``` 2. Para conceder acceso a Adapty al topic, crea una clave JSON para la cuenta de servicio que usa RevenueCat. En la consola de Google Cloud, ve a **IAM & Admin** > **Service Accounts**, abre la cuenta, normalmente `revenuecat-service-account`, luego ve a **Keys** > **Add key** y elige **JSON**. La nueva clave no afecta a RevenueCat. Para más detalles, consulta la guía de Google sobre cómo [crear una clave de cuenta de servicio](https://cloud.google.com/iam/docs/keys-create-delete#creating). 3. Para subir la clave, en la página del Adapty Dashboard que abre el agente, carga el archivo en **Google Play service account key (JSON)**. La clave va directamente a Adapty y nunca pasa por el agente ni por la CLI. 4. Para suscribir a Adapty al topic, proporciona al agente su ID. En RevenueCat, abre la configuración de tu app de Google Play y copia el **Topic ID** de **Google developer notifications**. Google Play Console muestra el mismo valor en **Monetize** > **Monetization setup** > **Topic name**. Adapty se suscribe al tema, luego publica una notificación de prueba y espera recibirla. Si no llega, el agente te indica el motivo. 5. Si la prueba falla por un problema de permisos, otorga al service account el rol **Pub/Sub Editor** en el proyecto del tema, o el rol **Pub/Sub Subscriber** en el tema y **Pub/Sub Editor** en el proyecto de la clave. Luego pide al agente que vuelva a comprobarlo. ## Prueba y lanza una nueva versión de tu app \{#test-and-release-a-new-version-of-your-app\} Si estás leyendo esto, ya has: - [x] Migrado tu catálogo a Adapty - [x] Migrado los diseños de tus paywalls, si usas flows - [x] Reemplazado el SDK de RevenueCat por Adapty - [x] Trabajado con el archivo `ADAPTY_SETUP.md` de tu agente de código - [x] Configurado tus integraciones en Adapty - [x] Redirigido los eventos de App Store y Google Play a Adapty - [ ] Realizado una compra en sandbox - [ ] Publicado una nueva versión de la app Si has marcado los puntos anteriores, solo tienes que hacer una compra de prueba en el Sandbox y luego lanzar la app. :::info Consulta la [lista de verificación para el lanzamiento](release-checklist). Realiza la comprobación final con nuestra lista para validar la integración existente o añadir funciones adicionales como integraciones de [atribución](attribution-integration) o [analíticas](analytics-integration). ::: ## (Opcional) Importa tu historial de transacciones \{#optional-import-your-transaction-history\} La importación rellena los [perfiles](profiles-crm) y los [gráficos](charts) con el historial de pagos de tus usuarios. No afecta al acceso de nadie. :::warning No te precipites con la importación de datos históricos Debes esperar al menos una semana tras el lanzamiento con el SDK antes de hacer la importación de datos históricos. Durante ese tiempo, obtendremos toda la información sobre los precios de las compras desde el SDK, por lo que los datos que importes serán más relevantes. ::: Dos migraciones importan el historial en la app que creó tu migración de catálogo. Usa una exportación en la nube si tu plan de RevenueCat lo incluye. ### Con una exportación Cloud de RevenueCat \{#with-a-revenuecat-cloud-export\} RevenueCat realiza una exportación diaria en un bucket que Adapty prepara para tu app, y Adapty importa todos los archivos que recibe. 1. Para iniciar la importación, pregúntale a tu agente: ```text Import the RevenueCat transaction history into my Adapty app "Acme Fitness" with a Cloud Export. ``` 2. Para conectar la exportación, proporciona al agente el External ID de RevenueCat. En RevenueCat, abre los ajustes de **Scheduled Data Exports**, elige **IAM Role (ARN)** como tipo de credencial y copia el **External ID** que muestra RevenueCat. El External ID no es un dato secreto. 3. Para finalizar la configuración de la exportación, en RevenueCat, introduce el bucket, la región y el ARN del rol que te indique el agente. Activa **Receive new and updated transactions only**. De lo contrario, RevenueCat exporta todo el historial cada día y Adapty importa todos los archivos. RevenueCat puede tardar hasta 24 horas en entregar el primer archivo, y Adapty busca nuevos archivos en el bucket cada hora. La migración finaliza automáticamente una semana después de que Adapty importe el primer archivo. El Customer User ID de cada perfil importado es `rc_last_seen_app_user_id_alias`. ### A través de soporte Usa este método si tu plan de RevenueCat no incluye Cloud Export: el soporte de RevenueCat te envía el export y el soporte de Adapty lo importa. 1. Para iniciar la importación, dile a tu agente: ```text Import the RevenueCat transaction history into my Adapty app "Acme Fitness" through support. ``` 2. Para solicitar la exportación, envía al soporte de RevenueCat el primero de los dos mensajes que te proporciona el agente. 3. Para importar la exportación, reenvía los archivos que RevenueCat te envíe al soporte de Adapty con el segundo mensaje. Adjunta los archivos exactamente como te los envió RevenueCat: abrirlos, volver a guardarlos o fusionarlos cambia el formato de columnas que espera la importación. ### Qué incluye la importación \{#what-the-import-brings-over\} Los siguientes datos se importarán a Adapty por 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 suscripción, 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 | Identificador único que proporciona 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 ellas en un dispositivo. | | advertising_id | 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. | En además, se importarán los identificadores de integración para las siguientes integraciones: Amplitude, Mixpanel, AppsFlyer, Adjust y FacebookAds. ### Lo que no se transmite \{#what-doesnt-come-across\} - Cada transacción se revalida con el store durante la importación, por lo que las filas que el store ya no reconoce se descartan. - Las filas sin un ID de transacción real del store — por ejemplo, las entitlements promocionales o concedidas manualmente de RevenueCat — se importan como perfiles sin transacciones. Para restaurar el acceso de estos usuarios, concédeles un nivel de acceso con la [API del servidor](api-adapty/operations/grantAccessLevel). - El historial de reembolsos y problemas de facturación no se transfiere tal cual: un reembolso conserva únicamente su fecha de cancelación, y el estado de problema de facturación de una suscripción se registra en el momento de la importación, no con las fechas históricas. ## 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 tienes que hacerlo con prisa; primero lanza la app con el SDK de Adapty y luego importa tu historial. Restauraremos el historial de pagos de tus usuarios y rellenaremos los [perfiles](profiles-crm) y los [gráficos](charts). ### Uso MMP (AppsFlyer, Adjust, etc.) y análisis (Mixpanel, Amplitude, etc.). ¿Cómo me aseguro de que todo funcione? \{#i-use-mmp-appsflyer-adjust-etc-and-analytics-mixpanel-amplitude-etc-how-do-i-make-sure-that-everything-will-work\} Primero debes 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 la de [integración de análisis](analytics-integration). Para datos históricos y usuarios legacy, **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 a Adapty" description: "Importa datos históricos a Adapty para obtener análisis detallados." --- Tras 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é ocurre si tienes una infraestructura heredada 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, no es necesario importar datos históricos. 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. Una vez que el soporte importa el `apple_original_transaction_id` o el `google_purchase_token` de una transacción, Adapty asocia automáticamente las futuras notificaciones del servidor de App Store y Play para esa transacción al mismo perfil — el usuario no necesita abrir la app primero. ::: :::warning Si un webhook u otra integración otorga recompensas (por ejemplo, moneda virtual) en un evento de suscripción, activa **Exclude Historical Events** para esa integración en los [ajustes de integración](configuration) antes de iniciar la importación. Sin esto, las compras históricas importadas vuelven a disparar eventos de integración, y un webhook que otorga recompensas concede la recompensa por segunda vez para una compra que el usuario ya tiene. Consulta [Migrar integraciones a Adapty](migrate-integrations-to-adapty#exclude-historical-events). ::: Para importar datos a Adapty: 1. Exporta tus transacciones a un archivo CSV (deben proporcionarse archivos separados para iOS, Android y Stripe). Consulta la [sección de formato del archivo de importación](importing-historical-data-to-adapty#import-file-format) a continuación 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**, el **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 por [correo electrónico](mailto:support@adapty.io) o a través del chat en línea en el Adapty Dashboard. No te preocupes, importar datos históricos no creará duplicados, aunque esos datos se superpongan 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 las 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 las columnas son exactamente los que aparecen en la columna **Column name** de la tabla siguiente. 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 los 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 a Adapty \{#import-data-to-adapty\} Por favor, contáctanos y comparte tus archivos de importación a través de [support@adapty.io](mailto:support@adapty.io) o mediante el 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). ::: ## Octubre de 2026 \{#october-2026\} - **Columnas de flow y prueba A/B en exportaciones a S3 y Google Cloud Storage**: Las exportaciones de eventos a Amazon S3 y Google Cloud Storage ahora muestran el flow, la prueba A/B y la variante de los que procede cada transacción. [Amazon S3](s3-exports#table-structure) | [Google Cloud Storage](google-cloud-storage#events) **Advertencia**: Las exportaciones tienen 8 nuevas columnas al final de cada archivo CSV: `variation_id`, `flow_id`, `flow_name`, `flow_variation_id`, `ab_test_id`, `ab_test_variant_id`, `ab_test_variant_name` y `ab_test_parent_name`. Para seguir cargando los archivos en una tabla con un esquema fijo, como una tabla de BigQuery, añade estas columnas al final del esquema de la tabla. - **Flows & Paywalls en el Adapty Dashboard**: El elemento de menú **Paywalls** ahora forma parte de **Flows & Paywalls**. Crea flows en una pestaña y gestiona los paywalls personalizados —los paywalls que renderizas en el código de tu propia app— en la otra. [Más información](flows-and-paywalls) - **Plan de compromiso de 12 meses**: Ofrece la suscripción anual de Apple que los suscriptores pagan en cuotas mensuales. Añade el plan a un flow o a un paywall personalizado, y muestra el precio mensual con las variables de precio y período de facturación. Requiere el SDK de Adapty 4.3 o posterior, y 4.3.1 o posterior para flows en iOS. [Más información](set-up-12-month-commitment) - **Etiquetas personalizadas en flows**: Pasa un valor desde tu app a un flow y muéstralo en el texto del flow — un nombre de usuario en un saludo, los días de una racha o el saldo de monedas. [Flow & Paywall Builder](flow-custom-tags) | [iOS](ios-flow-custom-tags) | [Android](android-flow-custom-tags) | [React Native](react-native-flow-custom-tags) | [Flutter](flutter-flow-custom-tags) | [Unity](unity-flow-custom-tags) | [Kotlin Multiplatform](kmp-flow-custom-tags) | [Capacitor](capacitor-flow-custom-tags) - **Embudo de flow**: La pestaña **Funnel** de un flow muestra cuántos usuarios llegan a cada pantalla, dónde abandonan y en qué pantalla del paywall compran. Úsala para identificar la pantalla donde se pierden usuarios y corregirla en lugar de cambiar el flow completo. No se necesita código. [Más información](flow-funnel) - **Imágenes, vídeos y degradados en modo oscuro**: Asigna a cada estilo de color un relleno independiente para el modo claro y el oscuro: un color sólido, un degradado, una imagen o un vídeo. Los elementos de imagen y vídeo también pueden tener un archivo específico para el modo oscuro. El flow muestra la versión que coincide con el tema del dispositivo del usuario. [Más información](paywall-dark-mode) - **Tabla de localizaciones en el Flow & Paywall Builder**: El panel **Localizations** lista todas las cadenas de texto y URL de acción de un flow en una sola tabla, con una columna por cada idioma. Traduce las cadenas manualmente o con IA, o expórtalas para un traductor. [Más información](add-paywall-locale-in-adapty-paywall-builder) - **Mapa de pantallas del flow**: Pide al skill flow-generator que dibuje un flow como un mapa de cada pantalla y cada camino entre ellas, incluyendo ramas de preguntas y callejones sin salida. [Más información](flow-screen-map) - **Skill Growth-advisor**: Pregunta a tu herramienta de IA de código qué probar a continuación. El skill growth-advisor lee tus analíticas, placements, paywalls y productos a través de la CLI de Adapty, y devuelve hipótesis de pruebas A/B clasificadas según las compras en juego. [Más información](export-analytics-with-ai) - **Pruebas A/B, análisis y ofertas en el Developer CLI**: Crea, inicia, detén y reemplaza pruebas A/B desde el terminal, o instala el skill ab-tests y deja que tu herramienta de IA las gestione. Los nuevos comandos también leen Adapty Analytics y vinculan ofertas de la store a productos. [Comandos de pruebas A/B](developer-cli-ab-tests) | [Skill de pruebas A/B](ab-tests-skill) | [Comandos de análisis](developer-cli-analytics) | [Comandos de productos y ofertas](developer-cli-products) - **Localización, estado del dominio y asistentes de IA en Adapty Mail**: Traduce una campaña con IA y cada destinatario recibirá los correos en el idioma de su dispositivo. Monitorea rebotes y la entrega en bandeja de entrada de tu dominio de envío, y lanza campañas desde Claude, ChatGPT o tu herramienta de desarrollo con IA a través del servidor MCP de Adapty Mail y el skill del agente. [Localización](mail-localization) | [Estado del dominio](mail-domain-health) | [Servidor MCP](mail-mcp) | [Skill del agente](mail-skill) - **Historial de cambios en Adapty Ads Manager**: Consulta todos los cambios realizados en tus campañas, grupos de anuncios, palabras clave y anuncios de Apple Ads durante los últimos 6 meses, ya sea desde Adapty o desde fuera. [Más información](ads-manager-change-history) - **Elige los ingresos que se envían a Meta, TikTok y Google Ads**: Envía los ingresos brutos, los ingresos netos tras la comisión del store, o los ingresos netos tras la comisión del store e impuestos como valor de conversión. Para comparar el ROAS en la red publicitaria con el ROAS en Adapty, selecciona el tipo de ingresos que muestras en los análisis de Adapty. [Meta](ua-facebook#revenue-sent-to-meta) | [TikTok](ua-tiktok#revenue-sent-to-tiktok) | [Google Ads](ua-google-ads#revenue-sent-to-google-ads) - **Singular Device ID**: Pasa el Singular Device ID (SDID) a Adapty, y Adapty enviará tus eventos a la API de eventos v2 de Singular. Las cuentas de Singular creadas a partir del 15 de julio de 2026 solo aceptan eventos a través de esta API. [Más información](singular#connect-your-app-to-singular) ## Septiembre 2026 \{#september-2026\} - **Importar flows desde Figma**: Selecciona frames en tu archivo de Figma y el plugin de Adapty para Figma los convierte en un flow en borrador. El diseño, el texto y las imágenes se importan tal cual, así que empiezas desde tu propio diseño en lugar de una pantalla en blanco. [Más información](import-from-figma) - **Imágenes en el editor de IA**: Adjunta hasta 5 imágenes a un mensaje del editor de IA. La IA las usa como referencia visual o las coloca en la pantalla, por ejemplo como imagen principal o como fondo. [Más información](flow-ai-editor#work-with-images) - **Nuevos elementos en el Flow & Paywall Builder**: Una hoja deslizante superpone tu contenido principal sobre una imagen hero y sube sobre ella a medida que el usuario hace scroll. Los nuevos elementos Toggle y Checkbox ofrecen al usuario un interruptor o una casilla de consentimiento, y el Review Carousel muestra varias reseñas en fila que el usuario puede deslizar. [Sliding sheet](builder-containers#sliding-sheet) | [Toggles y checkboxes](builder-toggles) | [Review Carousel](builder-reviews-and-testimonials#review-carousel) - **Procesar la entrada del usuario desde flows**: Lee lo que los usuarios escriben en campos de entrada, responden en cuestionarios o seleccionan con interruptores en un flow. Usa los valores para registrar usuarios en tu backend, guardarlos como atributos personalizados o segmentar flows posteriores. [iOS](ios-flow-input) | [Android](android-flow-input) | [React Native](react-native-flow-input) | [Flutter](flutter-flow-input) | [Unity](unity-flow-input) | [Kotlin Multiplatform](kmp-flow-input) | [Capacitor](capacitor-flow-input) - **Pruebas A/B multiplacement para flows**: Prueba un conjunto de flows en varios placements a la vez. Cada usuario recibe una variante y la ve en todos los placements de la prueba. [Más información](ab-test-types) - **Nombres de variantes de prueba A/B en el SDK**: Ponle nombre a tus variantes de prueba A/B en el Adapty Dashboard y léelo en tu app mediante la propiedad `variationName` del flow obtenido. Disponible en el SDK de iOS, Android y React Native 4.2 y versiones posteriores. [Más información](run_stop_ab_tests) - **Controla los mensajes del App Store en iOS**: Elige cuándo tu app muestra los mensajes del App Store sobre aumentos de precio, problemas de facturación y ofertas de recuperación, para que no interrumpan un flow ni un proceso de pago. Disponible en el SDK de iOS 4.2 y versiones posteriores. [Más información](ios-store-messages) - **Google Ads en la atribución de Adapty**: Conecta tus cuentas de Google Ads para importar gasto, impresiones y clics, y envía instalaciones, trials y compras a Google para que optimice tus campañas en base a ingresos reales. Adapty admite campañas de App en Android, y otras campañas en iOS, Android y embudos web mediante una plantilla de seguimiento. [Más información](ua-google-ads) - **LTV predicho para Meta**: Envía a Meta un valor de vida útil predicho con cada conversión de prueba o suscripción. Meta puja entonces por lo que un usuario probablemente valdrá en el horizonte que elijas, no solo por el primer pago. [Más información](ua-facebook#predicted-ltv) - **Eventos sintéticos**: Los eventos **Trial still active** y **Subscription still active** se activan cuando una prueba o suscripción sigue activa tras un retraso que tú configuras, de 1 hora a 90 días. Envíalos a tu MMP o plataforma de analítica como señal de usuarios que permanecen. [Más información](events#synthetic-events) - **Atribución de Adapty en el CLI para desarrolladores**: Consulta gasto, instalaciones, ingresos, ROAS, cohortes y predicciones desde el terminal con el tópico `adapty attribution`. Instala el skill adapty-attribution y tu herramienta de código con IA responderá preguntas como "¿Qué canal tuvo mejor retorno el mes pasado?" con los datos detrás de la respuesta. [Comandos CLI](developer-cli-attribution) | [Skill de atribución](developer-cli-attribution-skill) - **Flows y placements en Developer CLI**: Crea, edita, valida y publica flows desde el terminal, y adjúntalos a placements. El nuevo skill migrate-placements usa estos comandos para crear un flow placement por cada paywall placement que migres a flows. [Comandos CLI](developer-cli-flows) | [Skill migrate-placements](migrate-placements-skill) - **Doble opt-in y FunnelFox en Adapty Mail**: Activa el doble opt-in para que los nuevos destinatarios confirmen su dirección de correo electrónico antes de recibir emails de marketing. Conecta FunnelFox para enviar emails, compras y eventos de suscripción desde tus funnels web a Adapty Mail sin necesidad de trabajo con el SDK. [Doble opt-in](mail-double-opt-in) | [FunnelFox](mail-funnelfox) ## Agosto de 2026 \{#august-2026\} - **Retention Messaging**: Muestra a tus suscriptores un mensaje personalizado en la pantalla **Cancel Subscription** de iOS, justo antes de que confirmen la cancelación. Escribe, localiza y prueba mensajes en el Adapty Dashboard, y Adapty responde por ti a la API de Retention Messaging de Apple — sin backend, sin publicar una nueva versión de la app. [Más información](retention-messaging) - **Editor de IA en el Flow Builder**: Describe un cambio en lenguaje natural y Adapty reescribe, restylea o reorganiza la pantalla del flow que estás editando. Adapty te indica qué cambió y **Undo** revierte todos los cambios de un prompt juntos. [Más información](flow-ai-editor) - **Skill de agente para trabajar con flows**: Instala la skill experimental de generación de flows y tu herramienta de codificación con IA crea y edita flows a través del CLI de Adapty. Crea un flow a partir de una descripción o una captura de pantalla, o adapta un flow existente a un nuevo idioma. [Más información](flow-generator-skill) - **iOS, Android y Unity SDK 4.1**: La atribución de Adapty ahora es opcional — actívala al inicializar el SDK o las instalaciones dejarán de registrarse. Esta versión también renombra las APIs de atribución externa, y iOS recupera el soporte para compras promocionadas en el App Store. Unity 4.1 es la primera versión estable 4.x, por lo que los proyectos 3.x migran directamente a ella. [iOS](migration-to-ios-sdk-41) | [Android](migration-to-android-sdk-41) | [Unity](migration-to-unity-sdk-v4) - **Convertir un paywall heredado en un flow**: La acción **Move to new builder** recrea un paywall de Paywall Builder como un flow en borrador, conservando el diseño, los textos en cada idioma y los productos. Tu paywall original sigue activo mientras editas y pruebas el flow. El Paywall Builder y el Onboarding Builder heredados quedan obsoletos el 1 de octubre de 2026. Los paywalls y onboardings publicados siguen funcionando y pueden editarse, pero no se pueden crear nuevos. [Convertir un paywall](convert-paywall-to-flow) | [Detalles sobre la obsolescencia](paywall-onboarding-builder-deprecation) - **Eventos de visualización de pantallas de flow**: El SDK ahora reporta un evento `flow_screen_showed` cada vez que un usuario abre una pantalla de flow. Reenvíalo a Amplitude, Mixpanel o cualquier otra herramienta de analítica de producto que uses, y analiza las pantallas de flow junto con el resto de los eventos de tu app. [iOS](ios-flow-screen-views) | [Android](android-flow-screen-views) | [React Native](react-native-flow-screen-views) | [Flutter](flutter-flow-screen-views) | [Unity](unity-flow-screen-views) | [Kotlin Multiplatform](kmp-flow-screen-views) | [Capacitor](capacitor-flow-screen-views) - **Keyword Suggester y Search Popularity en Adapty Ads Manager**: Dos herramientas de investigación de palabras clave basadas en datos propios de Apple. Keyword Suggester expande una frase semilla en términos de búsqueda relacionados de la App Store y contexto de categoría. Search Popularity clasifica los términos de búsqueda más populares para un mercado, categoría y semana o mes completados. [Keyword Suggester](ads-manager-keyword-suggester) | [Search Popularity](ads-manager-search-popularity) - **Ads Manager en el CLI de desarrolladores**: Gestiona tu cuenta de Apple Ads desde la terminal bajo el tema `adapty asa` — campañas, grupos de anuncios, palabras clave, anuncios, páginas de producto, reglas de automatización, métricas e investigación de la competencia. Úsalo para dar acceso en tiempo real al rendimiento de tus anuncios a un agente de IA, o añade cientos de palabras clave desde un archivo. [Más información](developer-cli-ads-manager) - **Stripe y Paddle en Adapty Attribution**: Conecta tu cuenta de Stripe o Paddle para atribuir las compras del embudo web a los anuncios que las generaron. Las compras, renovaciones, reembolsos y contracargos aparecen en los análisis de Adapty Attribution, y los eventos de conversión se envían de vuelta a tus redes publicitarias. [Stripe](ua-stripe) | [Paddle](ua-paddle) - **Ensayos cualificados para Meta y TikTok**: Retén un evento de prueba entre 1 y 24 horas para que tu píxel reciba solo las pruebas que los usuarios conservaron. Adapty envía el evento cuando termina la retención, y solo si la renovación automática sigue activa. Adapty utiliza la hora de inicio original del periodo de prueba, por lo que la red registra la prueba el día en que comenzó. [Meta](ua-facebook#qualified-trial) | [TikTok](ua-tiktok#qualified-trial) - **Stripe Payment Links**: Vende a través de un Payment Link sin backend pasando tu ID de usuario en el parámetro de consulta `client_reference_id` del enlace. Establece **Profile creation behavior** en **Use client_reference_id** en **App Settings → Stripe**, y Adapty asociará la compra al perfil correcto. [Más información](stripe#payment-links-no-code) ## 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 segmentado con 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 alcanzará a tu audiencia de Apple Ads. Usa la lista de proveedores de atribución aplicados en `AdaptyProfile` (`appliedExternalAttributionProviders` en SDK 4.1 y versiones posteriores, `appliedAttributionSources` en versiones anteriores) para mostrar el paywall segmentado con 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**: Crea secuencias de pantallas completas en un editor visual sin código — paywalls de una sola pantalla, onboardings de varios pasos y cualquier cosa intermedia, todo en un mismo 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 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 admiten el flag `--audiences` — un array JSON de entradas `{segment_ids, paywall_id, priority}` — para que puedas dirigir diferentes paywalls a distintos segmentos desde la terminal. El nuevo comando `adapty paywalls placements` lista todos los placements que usan un paywall determinado, para que puedas previsualizar el impacto antes de cambiarlo. [Más información](developer-cli-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**: Proporciona a Meta y TikTok una imagen más completa de las conversiones para un modelado de audiencia más preciso. Adapty ahora permite reenviar instalaciones y transacciones de usuarios orgánicos y no atribuidos a tu píxel, no solo de los usuarios vinculados a una campaña. [Meta](ua-facebook#send-events-from-all-users) | [TikTok](ua-tiktok#send-events-from-all-users) - **Documentación en ruso y turco**: La documentación de Adapty ya está disponible en ruso (Русский) y turco (Türkçe). Cambia de idioma con 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 el patrón y las métricas clave que trabaja. [Más información](autopilot-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. Un solo registro te da acceso a todo el ecosistema de Adapty desde el primer día. Haz clic en el logo de Adapty para cambiar entre los productos: - **Core**: Procesa compras sin tocar StoreKit ni Google Play Billing, diseña paywalls sin código y rastrea ingresos en tiempo real. Los demás productos se construyen sobre esta base. - **Adapty Ads Manager**: Lanza y optimiza campañas en Apple Ads, medidas con ingresos reales de suscripciones. - **Adapty Attribution**: Descubre qué canales publicitarios realmente generan ingresos, sin necesidad de un MMP. - **Adapty Mail**: Convierte trials y recupera usuarios perdidos 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 impulsan la conversión: descuentos de 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 luego segmenta 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 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 siguiente prueba A/B. Compara tu paywall con más de 20.000 apps de suscripción y ordena los experimentos por el incremento de ingresos esperado. Consulta [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 en lenguaje natural y recomendaciones. - **[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)**: Páginas de producto personalizadas comparadas de forma directa. - **[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 configurados. ## 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 de publicidad](ua-integrations)**: Meta Ads, TikTok for Business, FunnelFox y pipes de S3/GCS. - **[Enlaces de seguimiento](ua-tracking-links)**: Se generan en Adapty, se añaden a tus campañas y se asocian a las instalaciones en el primer lanzamiento. - **[Deeplinks diferidos](ua-deferred-data)**: Dirige a los nuevos usuarios al contenido correcto dentro de la app en el primer lanzamiento, incluso si hicieron 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. Comienza a enviarse en cuanto la adjuntas a un flow. - **[Flows](mail-flows)**: Vincula una campaña a un segmento y a un disparador de 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 correctamente al correo. - **[Segmentos](mail-segments)** y **[perfiles](mail-profiles)**: Llega a los usuarios con mayor probabilidad de conversión. Crea un segmento a partir del estado de compra, el país o los ingresos, y lanza una campaña o un flow desde él. Los datos provienen de Adapty Core y se limitan a perfiles identificados con 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) es un constructor de funnels web-to-app. Crea páginas de aterrizaje y cuestionarios que llevan a los usuarios a tu app. Su motor de [facturación](https://funnelfox.com/docs/billing/integration-billing-funnelfox) cobra los pagos en la web. Los equipos lo usan para vender una suscripción antes de la instalación, recuperar el gasto en anuncios en el mismo clic que lo generó, y probar precios y preguntas de onboarding en la web, donde los cambios se publican sin necesidad de lanzar una nueva versión de la app. FunnelFox se conecta a tres productos de Adapty, cada uno configurado por separado: - **[Core](funnelfox)**: Las compras web crean perfiles, registran transacciones y otorgan niveles de acceso, por lo que un comprador web obtiene acceso de pago en tu app. - **[Adapty Attribution](ua-funnelfox)**: Las transacciones del funnel incluyen datos de campaña, funnel y experimento, para trazar el camino desde el clic en el anuncio hasta el suscriptor de pago. - **[Adapty Mail](mail-funnelfox)**: Los emails, compras y eventos de suscripción del funnel alimentan tus campañas de email. ### 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. ## Siguientes pasos \{#next-steps\} - **[¿Es Adapty la opción adecuada para mí?](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 programación con IA. --- # File: initial_ios --- --- title: "Integración inicial con el App Store" description: "Empieza a usar Adapty en iOS para simplificar la configuración y gestión de suscripciones." --- Conecta tu app del App Store con Adapty para que pueda validar compras, recibir eventos de suscripción y leer tus productos del store. Cada store se conecta por separado, y Adapty valida las compras solo para las stores que hayas conectado. Si tu app también está en Google Play, [conéctala también](initial-android) — son independientes, y hacer una no acorta la otra. La configuración abarca App Store Connect y el Adapty Dashboard, y tarda unos 30 minutos. El onboarding integrado en el Adapty Dashboard cubre los mismos pasos si prefieres hacerlo todo desde una sola ventana. ## Guía para la integración inicial \{#guide-for-the-initial-integration\} - [ ] Una vez que crees una cuenta en Adapty e indiques el nombre y la categoría de tu app, configuramos la app en nuestra plataforma Adapty. - [ ] [Genera la clave de compra in-app](generate-in-app-purchase-key) en App Store Connect. - [ ] [Configura la integración con App Store](app-store-connection-configuration) en el Adapty Dashboard y en App Store Connect. - [ ] Si tu app tiene pruebas gratuitas u otras ofertas promocionales, [configura las ofertas promocionales de App Store](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers) en el Adapty Dashboard. - [ ] [Activa las notificaciones de servidor de App Store](enable-app-store-server-notifications) en App Store Connect. - [ ] Instala los SDK de Adapty para los frameworks que uses: - [ ] [Instala el SDK de Adapty para iOS nativo](sdk-installation-ios) - [ ] [Instala el SDK de Adapty para React Native](sdk-installation-reactnative) - [ ] [Instala el SDK de Adapty para Flutter](sdk-installation-flutter) - [ ] [Instala el SDK de Adapty para Unity](sdk-installation-unity) - [ ] [Instala el SDK de Adapty para Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [ ] Compila tu aplicación y ejecútala en modo sandbox. Una vez completada la integración inicial, [puedes empezar a usar las funciones de Adapty](product). Ten en cuenta que, para que los paywalls y productos se muestren en tu aplicación móvil y para que funcionen los análisis, necesitas hacer cambios en el código de tu app. En concreto, debes [mostrar los paywalls](ios-quickstart-paywalls) como mínimo y, si usas paywalls que no se crearon con el Paywall Builder, [gestionar el proceso de compra](making-purchases) dentro de tu app. :::danger Revisa la lista de verificación antes de publicar tu app Antes de publicar tu aplicación, asegúrate de revisar detenidamente la [Lista de verificación de publicación](release-checklist). Esto garantizará que hayas completado todos los pasos necesarios antes de que tu app salga al público con el SDK de Adapty. ::: --- # File: generate-in-app-purchase-key --- --- title: "Generar clave de compra in-app en App Store Connect" description: "Genera una clave de compra in-app para transacciones seguras." --- La **clave de compra in-app** es una clave API especializada que se crea en App Store Connect para validar las compras confirmando su autenticidad. :::note Para generar claves API para la App Store Server API, debes tener el rol de Admin o Account Holder en App Store Connect. También puedes leer sobre cómo generar claves 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. Luego 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 la ventana **Generate in-App Purchase Key** se cierre, 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 que hayas generado tu clave 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 en un lugar seguro para poder subirlo al Adapty Dashboard más adelante. Ten en cuenta que el archivo generado solo se puede descargar una vez, así que guárdalo en un lugar seguro hasta que lo subas. La clave .p8 generada desde la sección **In-App Purchase section** se utilizará al [configurar la integración inicial de Adapty con App Store](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file). :::warning Si esta clave se revoca o elimina más adelante, Adapty no podrá firmar ofertas promocionales y esos descuentos dejarán de aplicarse en la compra. Adapty no lo notifica — consulta [Si las actualizaciones dejan de llegar](store-sync#if-updates-stop-arriving). ::: **Próximos pasos:** - [Configurar la integración con App Store](app-store-connection-configuration) --- # File: app-store-connection-configuration --- --- title: "Configurar la integración de App Store" description: "Configura tu conexión con App Store para un seguimiento de suscripciones sin interrupciones." --- Esta sección describe cómo establecer la conexión entre App Store y Adapty para tu app de iOS. Esto es necesario para que podamos mostrar análisis 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. Cada credencial de esta página habilita una parte diferente del intercambio. [Cómo Adapty sincroniza datos con los stores](store-sync#what-adapty-reads-from-the-stores) muestra qué datos aporta cada una. Aunque es posible que hayas configurado inicialmente la integración de tu app 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 de Sandbox, hasta que tu app esté en producción con el SDK de Adapty instalado. Los cambios realizados después del lanzamiento pueden romper el flow de compras en tu app. Si una clave es revocada, eliminada o rotada en App Store Connect, sube el reemplazo en cuanto lo detectes: todo lo que depende de ella permanecerá roto hasta que lo hagas. Consulta [Si las actualizaciones dejan de llegar](store-sync#if-updates-stop-arriving). ::: ## 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**. Copia solo el identificador — en formato DNS inverso sin espacios, por ejemplo, `com.company.app`. App Store Connect muestra el nombre de la app junto a él en otras vistas, como la lista de apps; pegar eso en lugar del identificador hará que la conexión no pueda validarse. <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). :::note App Store Connect tiene dos tipos de claves cuyos campos se etiquetan como **Issuer ID** y **Key ID**. Los pasos 2–3 usan la clave de **In-App Purchase** de la pestaña **Integrations** → **In-App Purchase**. La clave de App Store Connect API de la pestaña **Team keys** es una clave diferente con sus propios IDs — la añadirás en el [Paso 6](#step-6-add-app-store-connect-api-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 la 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). :::note Esta es un par de claves diferente a la clave de compras in-app que añadiste en los [pasos 2–3](#step-2-provide-issuer-id-and-key-id). Genera la clave en la pestaña **Team keys** y copia su **Issuer ID** y **Key ID** desde ahí — los valores de la clave de compras in-app no funcionarán aquí. ::: 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**, introduce un nombre para la clave y concédele 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 las 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 al instante desde App Store, incluida información sobre reembolsos y otros eventos. :::important Se requiere Adapty iOS SDK 2.10.0 o posterior para la compatibilidad completa con 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 app 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**. Después de guardar las URLs, el estado de las notificaciones en el Adapty Dashboard solo cambia cuando Apple envía la primera notificación real, es decir, cuando ocurre el primer evento de suscripción, como una compra o una renovación. La notificación de prueba de Apple no lo cambia. Un estado de espera justo después de la configuración no significa que la conexión esté rota. <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 raw \{#raw-events-forwarding\} A veces puede que quieras seguir recibiendo eventos S2S raw directamente desde Apple. Para continuar recibiéndolos mientras usas Adapty, simplemente añade tu endpoint en el campo **URL for forwarding raw Apple events** y nosotros reenviaremos los eventos tal cual 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 */ }} /> :::warning Si las notificaciones dejan de llegar a Adapty, las nuevas transacciones, renovaciones y reembolsos dejan de registrarse. Adapty no lo señala — consulta [Si las actualizaciones dejan de llegar](store-sync#if-updates-stop-arriving). ::: ## Autenticidad de las notificaciones \{#notification-authenticity\} Adapty no verifica el origen de las notificaciones entrantes del servidor de App Store más allá de la propia URL de notificación: no comprueba la dirección IP del remitente ni valida la firma JWS de Apple. Trata la URL de notificación como una credencial: cualquier persona que la tenga puede enviar un payload a ella. El tipo de notificación **TEST** del Sandbox de Apple nunca se almacena, independientemente de quién la envíe, por lo que enviarla manualmente nunca crea un evento en el [Event Feed](event-feed). **Qué hacer a continuación** Configura el SDK 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. ## "The provided field values are incorrect" al guardar las credenciales \{#the-provided-field-values-are-incorrect-when-saving-credentials\} Cuando guardas las credenciales de compra in-app, Adapty construye un token de autenticación a partir del **In-app purchase Issuer ID**, el **In-app purchase Key ID** y el **archivo .p8**, y lo valida con Apple. Este error significa que Apple rechazó el token. Comprueba los valores en este orden: 1. **Tipo de clave.** La clave debe ser una clave de **In-App Purchase** de **Users and Access** → **Integrations** → **In-App Purchase** en App Store Connect. Una clave de la pestaña **Team keys** (la clave de la API de App Store Connect) tiene su propio **Issuer ID** y **Key ID**, pero Apple la rechaza aquí — esa clave pertenece al [Paso 6 de la configuración](app-store-connection-configuration#step-6-add-app-store-connect-api-key). 2. **Misma cuenta y equipo.** El Issuer ID, el Key ID y el archivo .p8 deben provenir del mismo equipo de App Store Connect que la app. Una clave generada en la cuenta de otro equipo — por ejemplo, la cuenta de una agencia — falla en la validación. 3. **El archivo .p8 en sí.** Sube el archivo exactamente tal como lo descargaste de App Store Connect. Un archivo recreado a partir del contenido de la clave pegado manualmente suele fallar. App Store Connect solo permite descargar la clave una vez, así que si perdiste el archivo original, genera una nueva clave. 4. **Bundle ID.** El Bundle ID debe coincidir exactamente con el bundle ID de tu app en App Store Connect — en formato DNS inverso sin espacios, por ejemplo, `com.company.app`. 5. **Caracteres ocultos.** Vuelve a copiar cada ID directamente desde App Store Connect. Espacios en blanco o caracteres similares — por ejemplo, una `l` minúscula en lugar del dígito `1` — hacen que la validación falle. Esta validación utiliza únicamente los tres campos de la clave de compra in-app y el Bundle ID. El App-Specific Shared Secret y la [clave de la API de App Store Connect](app-store-connection-configuration#step-6-add-app-store-connect-api-key) no participan en ella, por lo que volver a introducir cualquiera de estos dos no cambiará el resultado. ## 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. ## Una compra se realiza con éxito pero el cliente pierde el acceso \{#a-purchase-succeeds-but-the-customer-loses-access\} El cliente paga y tiene acceso brevemente, pero lo pierde la próxima vez que tu app actualiza el perfil desde Adapty. Apple procesó el pago, pero la transacción nunca llegó a Adapty; hasta que eso ocurra, el perfil no tiene ningún nivel de acceso y la venta no aparece en tus informes. Normalmente la causa es una de estas: - **Una credencial dejó de funcionar**: Adapty no pudo validar la transacción con Apple. Sigue los pasos de [Si las actualizaciones dejan de llegar](store-sync#if-updates-stop-arriving) y reemplaza la credencial que cubre la validación de compras. - **El producto no está vinculado en Adapty**: El ID de producto de la store que el cliente compró no está vinculado a ningún producto de Adapty, por lo que no existe ningún nivel de acceso que otorgar. Confirma que el producto existe con el **App Store Product ID** exacto y que tiene un [nivel de acceso](access-level) asignado. El SDK de Adapty sigue reintentando la transacción, por lo que el acceso se restaura en cuanto Adapty puede registrarla. ## 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: initial-android --- --- title: "Integración inicial con Google Play" description: "Conecta tu app de Google Play con Adapty: activa las APIs de desarrollador, crea una cuenta de servicio y sube su clave para validar las compras de Android." --- Conecta tu cuenta de Google Play con Adapty para que nuestros servidores puedan leer los productos de Play Store, validar tus compras y recibir los eventos de suscripción de Android. :::important Cada store se conecta de forma independiente, y estas conexiones son independientes entre sí. Si tu app también está disponible en el App Store, [configura la integración con App Store Connect](initial_ios). ::: La configuración tarda aproximadamente una hora. El onboarding integrado en el Adapty Dashboard cubre los mismos pasos si prefieres trabajar en una sola ventana. ## Antes de empezar \{#before-you-start\} A diferencia de Apple, Google divide su ecosistema de desarrolladores en dos productos separados: Google Play Console y Google Cloud Console, y esta configuración involucra a ambos. ### Qué implica \{#whats-involved\} - **Cuenta de servicio**: Una cuenta de Google para máquinas en lugar de personas. Necesitas crear una nueva cuenta de servicio para conceder a Adapty acceso a Google Cloud y a Google Play Console. - **Google Cloud Console**: La plataforma de infraestructura de uso general de Google. La Play Store no tiene credenciales de API propias, por lo que se crean aquí. Usa la Cloud Console para habilitar las Developer APIs, crear la cuenta de servicio y concederle algunos de los permisos necesarios. Adapty los necesita para recibir renovaciones, cancelaciones y reembolsos. El archivo de clave de la cuenta de servicio, que permite a Adapty autenticarse con Google, se puede descargar desde aquí. - **Google Play Console**: Donde publicas tu app y vendes suscripciones. Usa la Play Console para conceder a la cuenta de servicio el resto de los permisos necesarios. Adapty los necesita para leer tus productos y validar compras. Además, aquí es donde activas las notificaciones de desarrollador en tiempo real (RTDN) para establecer una línea de comunicación entre la Play Store y Adapty. ### Privilegios de acceso requeridos \{#required-access-privileges\} | Consola | Qué necesitas | | --- | --- | | Google Cloud Console | **Project Creator** (`roles/resourcemanager.projectCreator`) para crear un proyecto, si no tienes uno. **Service Account Creator** (`roles/iam.serviceAccountCreator`) para crear la cuenta de servicio, y **Project IAM Admin** (`roles/resourcemanager.projectIamAdmin`) para asignarle permisos de proyecto. Los **Owners** pueden hacer las tres cosas sin configuración adicional. | | Google Play Console | **Admin (all permissions)**, que incluye el derecho a invitar usuarios — necesitas este permiso para invitar a la cuenta de servicio. | Google otorga **Project Creator** a todos los miembros de una organización nueva de forma predeterminada, así que probablemente ya lo tengas. Una organización corporativa con políticas restrictivas puede haberlo revocado. ### Recursos necesarios \{#required-resources\} **Un proyecto de Google Cloud.** [Créalo](https://console.cloud.google.com/projectcreate) si aún no tienes uno, o reutiliza un proyecto que ya tengas en uso. Este proyecto te permite habilitar las APIs de desarrollador y crear la cuenta de servicio para Adapty. Un proyecto es independiente de tu app en Play Console. Un único proyecto puede gestionar las comunicaciones de varias apps. Cada app tiene su propio topic —una cola de comunicación— dentro del proyecto. Cuando conectes los servicios de Google a Adapty, debes **usar el mismo proyecto** de principio a fin. Elige un proyecto que sea de tu equipo en lugar de uno personal: la clave y el topic vivirán allí mientras exista la app. ## Lista de verificación para la integración inicial \{#checklist-for-the-initial-integration\} 1. [Activa las APIs de desarrollador](enabling-of-devepoler-api) en Google Cloud Console. 2. [Crea una cuenta de servicio](create-service-account) en Google Cloud Console. 3. [Otorga los permisos necesarios a la cuenta de servicio](grant-permissions-to-service-account) en Google Play Console. 4. [Genera el archivo de clave de la cuenta de servicio](create-service-account-key-file) en Google Cloud Console. 5. [Sube el archivo de clave y el nombre del paquete a Adapty](google-play-store-connection-configuration) en el Adapty Dashboard. Una cuenta de servicio nueva tarda al menos 24 horas en activarse, y Adapty no puede leer tus productos hasta entonces — para saltarte esa espera, [edita y guarda cualquier descripción de producto](create-service-account-key-file#activate-the-account-immediately) en Google Play Console. 6. [Activa las notificaciones de desarrollador en tiempo real (RTDN)](enable-real-time-developer-notifications-rtdn) en Google Play Console, usando el nombre de tema que genera Adapty. 7. [Instala el SDK de Adapty](installation-of-adapty-sdks) para cada framework que use tu app: - [Android](sdk-installation-android) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) - [Capacitor](sdk-installation-capacitor) 8. [Prueba tus compras in-app](testing-on-android) en un dispositivo real antes de publicar. ## Próximos pasos \{#next-steps\} Una vez que la integración esté lista, puedes [empezar a usar las funciones de Adapty](product). Mostrar paywalls requiere código adicional en la app además de la integración: como mínimo [muestra los paywalls](android-quickstart-paywalls) y [gestiona el proceso de compra](android-making-purchases) si renderizas los paywalls tú mismo. :::tip Antes de publicar tu app, revisa el [checklist de lanzamiento](release-checklist). ::: --- # File: enabling-of-devepoler-api --- --- title: "Habilitar las APIs de desarrollador en Google Cloud Console" description: "Habilita las tres APIs de Google que Adapty utiliza para leer tus productos, validar compras y recibir eventos de suscripción." --- Adapty lee tus productos, valida compras y recibe eventos de suscripción a través de las siguientes APIs de Google: - Google Play Android Developer API - Google Play Developer Reporting API - Cloud Pub/Sub API ## Antes de empezar \{#before-you-start\} Comprueba que tienes los [privilegios de acceso y el proyecto de Google Cloud que necesita la integración con Google Play](initial-android#before-you-start). Sin ellos, no podrás completar los pasos siguientes. ## Habilitar las APIs \{#enable-the-apis\} 1. Abre la [Google Cloud Console](https://console.cloud.google.com/). 2. Selecciona el proyecto que usarás para la integración. 3. Abre la página de la [**Google Play Android Developer API**](https://console.cloud.google.com/apis/library/androidpublisher.googleapis.com). 4. Haz clic en **Enable** y espera a que el estado muestre **Enabled**. Si el botón indica **Manage** en lugar de eso, la API ya está activa y no hay nada que hacer. 5. Abre la página de la [**Google Play Developer Reporting API**](https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com). 6. Haz clic en **Enable** y espera a que el estado muestre **Enabled**. 7. Abre la página de la [**Cloud Pub/Sub API**](https://console.cloud.google.com/apis/library/pubsub.googleapis.com). 8. Haz clic en **Enable** y espera a que el estado muestre **Enabled**. Para confirmarlo, abre la página [**APIs & Services**](https://console.cloud.google.com/apis/dashboard) en Google Cloud Console. La tabla que aparece en la parte inferior lista todas las APIs habilitadas en el proyecto, y las tres deberían aparecer: - Google Play Android Developer API - Google Play Developer Reporting API - Cloud Pub/Sub API ## Próximos pasos \{#next-steps\} [Crea una cuenta de servicio](create-service-account) para que Adapty pueda llamar a estas APIs en tu nombre. --- # File: create-service-account --- --- title: "Crear una cuenta de servicio en Google Cloud Console" description: "Crea la cuenta de Google con la que Adapty inicia sesión y otórgale los roles de Pub/Sub Admin y Monitoring Viewer que necesita." --- Una cuenta de servicio es una cuenta especial de Google que Adapty utilizará para acceder a tus proyectos y aplicaciones de Google. ## Antes de empezar \{#before-you-start\} [Habilita las APIs de desarrollador](enabling-of-devepoler-api) en el mismo proyecto de Google Cloud. La cuenta de servicio no tiene nada que llamar hasta que estén activadas. ## Crear la cuenta de servicio \{#create-the-service-account\} 1. Abre [**IAM & Admin** > **Service accounts**](https://console.cloud.google.com/iam-admin/serviceaccounts) en Google Cloud Console y confirma que estás en el proyecto correcto. 2. Haz clic en **Create service account**. 3. Introduce un **Service account name**. Incluye "Adapty" en él para que el propósito de la cuenta sea evidente. El campo **Service account ID** se rellena automáticamente. 4. Copia la **Email address** de la cuenta. La usarás para compartir los permisos necesarios de Play Console. 5. Haz clic en **Create and continue**. 6. Asigna a la cuenta recién creada el rol **Pub/Sub Admin**. Adapty lo necesita para recibir notificaciones de desarrollador en tiempo real. Haz clic en **Select a role**, luego en **Pub/Sub** > **Pub/Sub Admin**. 7. Asígnale el rol **Monitoring Viewer**. Adapty lo necesita para supervisar la cola de notificaciones. Haz clic en **Add another role** y luego en **Monitoring** > **Monitoring Viewer**. 8. Haz clic en **Continue**. 9. Omite **Principals with access** y haz clic en **Done**. ## Usar un rol personalizado en lugar de Pub/Sub Admin \{#using-a-custom-role-instead-of-pubsub-admin\} Si tu política de seguridad restringe los roles de administrador predefinidos, concede un rol de IAM personalizado con estos permisos en lugar de **Pub/Sub Admin**: - `pubsub.topics.list` - `pubsub.topics.create` - `pubsub.topics.setIamPolicy` - `pubsub.topics.attachSubscription` - `pubsub.subscriptions.list` - `pubsub.subscriptions.create` La configuración propia de Adapty solo llama a las APIs de topic y suscripción de Pub/Sub: nunca llama a la API de Monitoring. El rol **Monitoring Viewer** separado es para que tú puedas ver la cola de notificaciones, no es algo que las llamadas de Adapty necesiten. ## Próximos pasos \{#next-steps\} [Concede permisos a la cuenta de servicio](grant-permissions-to-service-account) en Google Play Console para que pueda acceder a los datos de tu app. --- # File: grant-permissions-to-service-account --- --- title: "Conceder permisos a la cuenta de servicio en Google Play Console" description: "Invita la cuenta de servicio a tu cuenta de desarrollador en Play Console y otórgale los cuatro permisos que Adapty necesita." --- Adapty lee tus productos y valida las compras como la cuenta de servicio. Hasta que invites esa cuenta a Google Play Console y le otorgues permisos, no tendrá acceso a los datos de tu app. ## Antes de empezar \{#before-you-start\} [Crea la cuenta de servicio](create-service-account) y ten a mano su dirección de correo electrónico. La necesitarás para enviar la invitación. ## Conceder los permisos \{#grant-the-permissions\} 1. Abre [Google Play Console](https://play.google.com/console/) y ve a **Users and permissions**. 2. Haz clic en **Invite new users**. 3. En **Email address**, pega la dirección de correo electrónico de la cuenta de servicio. 4. Abre la pestaña **Account permissions**. Los permisos concedidos aquí se aplican a todas las apps de tu cuenta de desarrollador, mientras que la pestaña **App permissions** solo cubre las apps que selecciones individualmente. 5. Selecciona estos cuatro 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 6. Haz clic en **Invite user** y confirma con **Send invite**. La cuenta de servicio aparecerá en la lista de usuarios. ## Próximos pasos \{#next-steps\} [Genera el archivo de clave de cuenta de servicio](create-service-account-key-file), que Adapty utiliza para autenticarse con esta cuenta. --- # File: create-service-account-key-file --- --- title: "Generar el archivo de clave de cuenta de servicio en Google Cloud Console" description: "Descarga el archivo JSON que permite a Adapty autenticarse como tu cuenta de servicio y activa la cuenta sin esperar 24 horas." --- El archivo de clave es la credencial de la cuenta de servicio. Adapty lo utiliza para autenticarse como esa cuenta y llamar a las APIs de Google en tu nombre. ## Antes de empezar \{#before-you-start\} [Concede los permisos de Play Console a la cuenta de servicio](grant-permissions-to-service-account). Una clave solo tiene los accesos que ya tiene la cuenta. ## Genera el archivo de clave \{#generate-the-key-file\} 1. Abre [**IAM & Admin** > **Service accounts**](https://console.cloud.google.com/iam-admin/serviceaccounts) en Google Cloud Console y asegúrate de estar en el proyecto correcto. 2. Haz clic en la cuenta de servicio que creaste. 3. Abre la pestaña **Keys**, luego haz clic en **Add key** > **Create new key**. 4. Deja **Key type** en **JSON** y haz clic en **Create**. El archivo se descargará en tu ordenador. :::warning Guarda el archivo en un lugar seguro. Google no te permitirá descargarlo de nuevo, y cualquier persona que lo tenga puede actuar como tu cuenta de servicio. ::: ## Activa la cuenta de inmediato \{#activate-the-account-immediately\} Una nueva cuenta de servicio tarda al menos 24 horas en activarse, y hasta entonces Adapty no puede leer tus productos. Para saltarte la espera, en [Google Play Console](https://play.google.com/console/) ve a **Monetize with Play** > **Products** > **Subscriptions**, luego edita y guarda la descripción de cualquier producto. Puedes revertir el cambio de inmediato. :::warning Si este archivo de clave se elimina o rota más adelante, Adapty deja de leer tus productos y no puede validar las compras. Adapty no lo notifica — consulta [Si las actualizaciones dejan de llegar](store-sync#if-updates-stop-arriving). ::: ## Próximos pasos \{#next-steps\} [Configura la integración con Google Play Store](google-play-store-connection-configuration) subiendo este archivo de clave a Adapty. --- # File: google-play-store-connection-configuration --- --- title: "Configurar la integración con Google Play Store" description: "Introduce el nombre del paquete y sube el archivo de clave de cuenta de servicio para que Adapty pueda validar las compras de Android y crear tu tema Pub/Sub." --- Este es el único paso de la integración con Google Play que se realiza en Adapty en lugar de en una consola de Google. Le proporcionas a Adapty el nombre de tu paquete y el archivo de clave de cuenta de servicio, y Adapty los usa para validar las compras y leer tus productos. :::danger No cambies el nombre del paquete ni el archivo de clave una vez que tu app esté en producción con el SDK de Adapty. Esto romperá la validación de compras para todos los usuarios que ya tengan tu app. Una clave de un proyecto Cloud diferente también cambia el nombre del topic, lo que detiene las notificaciones en tiempo real. Una clave que ha dejado de funcionar es el caso contrario. Si la clave de cuenta de servicio se elimina o rota en Google Cloud, sube el reemplazo en cuanto lo detectes: la lectura de productos y la validación de compras seguirán rotas hasta que lo hagas. Consulta [Si las actualizaciones dejan de llegar](store-sync#if-updates-stop-arriving). ::: ## Antes de empezar \{#before-you-start\} [Genera el archivo de clave de cuenta de servicio](create-service-account-key-file). Ese archivo es el que subirás aquí. ## Conecta la app \{#connect-the-app\} 1. Abre [Google Play Console](https://play.google.com/console/), selecciona tu app y copia el nombre de su paquete. La página **Dashboard** lo muestra bajo el nombre de la app. 2. Abre [**App settings** > **Android SDK**](https://app.adapty.io/settings/android-sdk) en el Adapty Dashboard. 3. Pega el nombre del paquete en **Package name**. 4. Sube el archivo de clave JSON en **Service account key file**. 5. Haz clic en **Save**. Al guardar el archivo de clave también se crea el tema Pub/Sub en tu proyecto de Cloud, que el siguiente paso necesita. ## Pasos siguientes \{#next-steps\} [Activa las notificaciones para desarrolladores en tiempo real (RTDN)](enable-real-time-developer-notifications-rtdn) para que Google Play informe a Adapty sobre renovaciones, cancelaciones y reembolsos. --- # File: enable-real-time-developer-notifications-rtdn --- --- title: "Habilitar notificaciones en tiempo real para desarrolladores (RTDN) en Google Play Console" description: "Pega el nombre del tema Pub/Sub de Adapty en Google Play Console para que las renovaciones, cancelaciones y reembolsos lleguen a Adapty en tiempo real." --- Google Play informa a Adapty sobre renovaciones, cancelaciones, reembolsos y problemas de facturación a través de notificaciones en tiempo real para desarrolladores. Estos eventos ocurren mientras tu app está cerrada, por lo que el SDK no puede reportarlos. ## Antes de empezar \{#before-you-start\} [Configura la integración con Google Play Store](google-play-store-connection-configuration). Adapty genera el nombre del topic a partir de tu archivo de clave, por lo que no hay nada que copiar hasta que lo hayas subido. ## Activar notificaciones \{#enable-notifications\} 1. Abre [**App settings** > **Android SDK**](https://app.adapty.io/settings/android-sdk) en el Adapty Dashboard y copia el nombre del tema en **Google Play RTDN topic name**. Empieza por `projects/`. 2. Abre [Google Play Console](https://play.google.com/console/), selecciona tu app y ve a **Monetize with Play** > **Monetization setup**. 3. En la sección **Google Play Billing**, marca la casilla **Enable real-time notifications**. 4. Pega el nombre del tema en **Topic name**. 5. Haz clic en **Save changes**. ## Verificar la conexión \{#verify-the-connection\} 1. En la página **Monetization setup**, haz clic en el botón **Send test notification** bajo el campo **Topic name**. 2. Vuelve a [**App settings** > **Android SDK**](https://app.adapty.io/settings/android-sdk) en Adapty. Una vez que Adapty reciba la notificación, la etiqueta de estado en la sección **Google Play RTDN topic name** aparecerá como **Active**. :::note La notificación de prueba solo verifica la conexión. No crea eventos de compra, por lo que el [Event Feed](event-feed) permanece vacío después de una prueba; los eventos aparecen cuando se producen transacciones reales. ::: :::warning Si el RTDN deja de llegar a Adapty, las nuevas transacciones, renovaciones y reembolsos dejan de aparecer. Adapty no lo notifica — consulta [Si las actualizaciones dejan de llegar](store-sync#if-updates-stop-arriving). ::: ## Enviar notificaciones a dos apps de Adapty \{#send-notifications-to-two-adapty-apps\} Google Play envía las notificaciones de una app a un único topic. Si una app de Google Play reporta a dos apps de Adapty —por ejemplo, una app de producción y una de desarrollo—, añade una segunda suscripción a ese topic para que ambas apps reciban las notificaciones. 1. En la segunda app de Adapty, [configura la integración con Google Play Store](google-play-store-connection-configuration) para la misma app de Google Play. Adapty crea un topic y una push subscription para la segunda app, y les asigna el mismo nombre: la parte del **Google Play RTDN topic name** de la segunda app que aparece tras `topics/`, visible en [**App settings** > **Android SDK**](https://app.adapty.io/settings/android-sdk). 2. En la [Google Cloud Console](https://console.cloud.google.com/), selecciona el proyecto del archivo de clave de la segunda app y ve a **Pub/Sub** > **Subscriptions**. Abre la suscripción con ese nombre y copia su URL de endpoint. 3. Selecciona el proyecto del topic que utiliza Google Play Console y ve a **Pub/Sub** > **Topics**. Abre ese topic y crea una suscripción con el tipo de entrega **Push** y la URL de endpoint que copiaste. La segunda app de Adapty recibirá entonces una notificación por cada compra realizada en la app de Google Play, incluidas las compras realizadas a través de la primera app de Adapty. ## Reenviar eventos brutos de Google \{#forward-raw-google-events\} Adapty decodifica cada notificación de Google y la convierte en su propio evento, vinculado a un perfil y un producto, con los ingresos asociados. Si también quieres recibir el mensaje original de Google, añade tu endpoint en **URL for forwarding raw Google events to your webhook** en **App settings** > **Android SDK**. Adapty publica el mensaje de Pub/Sub exactamente como Google lo envió, antes de analizar nada: codificado en base64, usando los nombres de campo de Google y los tipos de notificación numéricos, e identificando la compra por token en lugar de por perfil de Adapty. Google documenta el formato en su [referencia de RTDN](https://developer.android.com/google/play/billing/rtdn-reference). ## Solución de problemas de RTDN \{#rtdn-troubleshooting\} ### Falta el nombre del tema \{#the-topic-name-is-missing\} La configuración de RTDN comienza copiando un nombre de tema desde Adapty. Si no hay nada, no puedes continuar: **Topic name** en Play Console es como Google Play sabe dónde enviar los eventos, y Adapty es el único lugar del que proviene ese nombre. Adapty crea el topic en tu proyecto de Cloud al guardar el archivo de clave. Un nombre válido empieza por `projects/`, como `projects/your-project-id/topics/adapty-prod-3f7a1c2e-9b4d-4e8f-a1b2-c3d4e5f60718`. Si Adapty no pudo crearlo, la sección muestra un mensaje breve y un botón **Recreate Pub/Sub topic** en lugar del nombre, con una explicación del problema: | Lo que ves | Lo que significa | Qué hacer | | --- | --- | --- | | **To set the Google Play RTDN topic name first upload a valid service account key file above.** | Adapty no tiene ningún archivo de clave para esta aplicación | [Sube el archivo de clave](google-play-store-connection-configuration) | | **Enable Pub/Sub API** | La API de Cloud Pub/Sub está desactivada en ese proyecto de Cloud | Sigue el enlace que muestra Adapty y haz clic en **Recreate Pub/Sub topic** | | **Grant permissions** | La cuenta de servicio no tiene el rol **Pub/Sub Admin** en ese proyecto | En [**IAM & Admin** > **IAM**](https://console.cloud.google.com/iam-admin/iam), edita la cuenta de servicio y añade **Pub/Sub** > **Pub/Sub Admin**, luego haz clic en **Recreate Pub/Sub topic** | Hasta que el topic exista, no hay nada que pegar en Play Console, Google Play no envía eventos y Adapty no recibe tus renovaciones, cancelaciones ni reembolsos. **Recreate Pub/Sub topic** vuelve a ejecutar la configuración con el archivo de clave que ya subiste, sin necesidad de volver a cargarlo. ### Recreación del tema falla \{#recreating-the-topic-fails\} Google Play necesita permiso para publicar en tu tema, por lo que Adapty otorga ese permiso a `google-play-developer-notifications@system.gserviceaccount.com`. Esa cuenta pertenece a Google, no a tu empresa. Las empresas suelen configurar Google Cloud para permitir el acceso solo a cuentas de su propio dominio. Estas reglas a nivel de organización se denominan políticas de organización, y dos de ellas rechazan la concesión de permisos a una cuenta externa, por lo que el tema no se crea. Relaja ambas políticas para este proyecto, vuelve a crear el tema y, después, restáuralas. Modificar las políticas de organización suele requerir permisos de administrador, así que puede que necesites a quien gestione el Google Cloud de tu empresa. 1. Abre la [Google Cloud Console](https://console.cloud.google.com/) y selecciona el proyecto del que proviene el archivo de clave. 2. Ve a **IAM & Admin** > **Organization Policies**. 3. Busca la política **Domain restricted contacts**, abre su menú **Actions** y haz clic en **Edit policy**. 4. Selecciona **Override parent's policy**, establece **Policy enforcement** en **Replace** y haz clic en **Add a rule**. 5. En **Policy values**, elige **Allow All** y haz clic en **Set policy**. 6. Repite el proceso para la política **Domain restricted sharing**. 7. Vuelve a crear el topic en Adapty. :::warning Restablece ambas políticas a **Inherit parent's policy** en cuanto exista el topic. Mientras aparezca **Allow All**, este proyecto acepta concesiones de acceso y uso compartido para cualquier cuenta de Google fuera de tu dominio, no solo para el servicio de notificaciones de Google. ::: ## Próximos pasos \{#next-steps\} [Instala el SDK](installation-of-adapty-sdks) para cada framework que use tu app. Hasta que lo hagas, tu app no podrá mostrar paywalls ni registrar compras en Adapty. - [Android](sdk-installation-android) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) - [Capacitor](sdk-installation-capacitor) --- # File: payment-integrations --- --- title: "Web" description: "Integra sistemas de pago web con Adapty para gestionar suscripciones entre plataformas de forma sencilla." --- Adapty admite la integración con sistemas de pago web para ayudarte a gestionar suscripciones y compras tanto desde tu app móvil como desde tu sitio web en un único lugar. Esto te permite: - Recopilar datos de suscripción tanto de compras in-app como de compras en sitio web en un único sistema - Conceder acceso a funciones de pago en tu app móvil a usuarios que compraron en tu sitio web - Ver analíticas y datos de suscripción de todos los canales de venta en un solo dashboard Integraciones disponibles: - [Stripe](stripe) - [Paddle](paddle) - [FunnelFox](funnelfox) --- # File: stripe --- --- title: "Integración inicial con Stripe" description: "Integra Stripe con Adapty para procesar pagos de suscripciones sin fricciones." --- Adapty admite flows de web2app rastreando los pagos y suscripciones realizados a través de [Stripe](https://stripe.com/). Esta integración cubre las compras iniciadas desde la web (Stripe Checkout, páginas de pago alojadas, Payment Links o flujos web personalizados) y las sincroniza con el acceso en la app móvil y los análisis. Es útil en los siguientes escenarios: - Proporcionar automáticamente acceso a las funciones de pago a usuarios que compraron en la web pero que posteriormente instalaron la app e iniciaron sesión en su cuenta - Tener todos los análisis de suscripciones en un único Adapty Dashboard (incluidas cohortes, predicciones y el resto de nuestras herramientas de análisis) Aunque las compras web son cada vez más populares para las apps, Apple App Store permite un sistema diferente al de las compras in-app únicamente para bienes digitales y solo 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 eliminada. Los pasos a continuación explican cómo configurar la integración con Stripe. :::important Esta integración se centra en el seguimiento y la sincronización de compras web realizadas con 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 distintas para los modos de prueba y producción. Usa una clave del **Test mode** de Stripe, no de un **Sandbox** de Stripe — Adapty no es compatible con los Sandboxes de Stripe. <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 al final 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 la **Latest API version** en el campo **Version** del webhook. A continuación, selecciona los siguientes eventos: - charge.refunded - checkout.session.completed - 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 utiliza 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, en el campo "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: :::warning Adapty relaciona los eventos entrantes de Stripe mediante el par exacto `product_id` + `price_id`. Cambiar un precio en Stripe crea un nuevo `price_id`, y los eventos de compras con el nuevo precio dejan de coincidir silenciosamente: Stripe muestra el webhook como entregado, pero Adapty no registra las transacciones. Después de cualquier cambio de precio, asegúrate de que cada `price_id` que vendas esté añadido a un producto en 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 se basa en los webhooks de Stripe para proporcionar y actualizar los niveles de acceso de los usuarios como única fuente de información. Sin embargo, debes proporcionar información adicional desde tu lado cuando trabajes con Stripe para que esta integración funcione correctamente. Para que los niveles de acceso sean coherentes entre plataformas (web o móvil), debes asegurarte de que haya un único ID de usuario en el que basarte, que Adapty pueda reconocer a través de los webhooks. Puede ser el correo electrónico del usuario, su número de teléfono o cualquier otro ID del sistema de autenticación que estés utilizando. Adapty denomina este valor `customer_user_id`. :::warning El ID de usuario es obligatorio Sin él, no tenemos forma de identificar a este usuario y otorgarle el nivel de acceso en el móvil. ::: Adapty lee el ID de usuario desde **una** fuente — la seleccionada en **Profile creation behavior** en [App Settings → Stripe](https://app.adapty.io/settings/stripe). No es una cadena de respaldo: si la fuente seleccionada está vacía para una transacción concreta, la compra queda anónima aunque el ID esté presente en otro lugar de los datos de Stripe. Consulta [Profile creation behavior](stripe#profile-creation-behavior) para ver todas las fuentes disponibles. Elige la opción que coincida con cómo creas las compras en Stripe. ### Sesiones de pago y suscripciones creadas a través de la API de Stripe \{#checkout-sessions-and-subscriptions-created-via-the-stripe-api\} Mantén **Profile creation behavior** configurado en **Use customer_user_id from metadata (default)**. 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 de [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 simple adición es lo único que tienes que hacer en tu código. Después de eso, Adapty procesará todos los webhooks que reciba de Stripe, extraerá este `metadata` y asociará correctamente las suscripciones con tus clientes. :::note También se requiere un cliente en Stripe Si utilizas Checkout Sessions, [asegúrate de crear un Stripe Customer](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-customer_creation) configurando `customer_creation` en `always`. ::: ### Links de pago (sin código) \{#payment-links-no-code\} Si vendes mediante [Stripe Payment Links](https://docs.stripe.com/payment-links) y no tienes backend para establecer `metadata`, pasa el ID de usuario en el parámetro de consulta `client_reference_id` del enlace: ```text title="Payment Link with a user ID" https://buy.stripe.com/your_link?client_reference_id=YOUR_USER_ID ``` Stripe almacena este valor en la Checkout Session y lo envía a Adapty en el evento `checkout.session.completed`. Funciona tanto para suscripciones como para compras únicas. :::warning Cambia primero el comportamiento de creación de perfiles Adapty solo lee `client_reference_id` si **Profile creation behavior** en [App Settings → Stripe](https://app.adapty.io/settings/stripe) está configurado como **Use client_reference_id**. De lo contrario, la compra crea un perfil anónimo. La configuración se aplica a toda la app: una vez que cambias a `client_reference_id`, Adapty deja de leer `customer_user_id` de los metadatos para el resto de tus flows de Stripe. ::: :::important Asegúrate de que tu webhook envíe `checkout.session.completed` Adapty habilita este evento automáticamente cuando crea el endpoint de webhook para una nueva conexión de Stripe, pero nunca actualiza los endpoints que ya existen. Si conectaste Stripe antes de que se admitieran los Payment Links, abre [Developers → Webhooks](https://dashboard.stripe.com/webhooks) en Stripe, selecciona el endpoint de Adapty, haz clic en **Edit destination** y añade `checkout.session.completed` a los eventos. Mantén el signing secret tal como está. ::: ## 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. ::: ## Solución de problemas \{#troubleshooting\} ### Stripe muestra una entrega del 100%, pero ningún evento llega a Adapty \{#stripe-shows-100-delivery-but-no-events-reach-adapty\} Adapty solo captura un secreto de firma de webhook cuando crea el endpoint por sí mismo. Si ya existe un endpoint en la URL de Adapty dentro de tu cuenta de Stripe —por ejemplo, de un intento de conexión anterior o de haber seguido la configuración manual obsoleta indicada arriba—, al volver a conectar se omite la creación de uno nuevo y Adapty nunca recibe (ni vuelve a recibir) el secreto de firma. Todos los eventos siguen llegando al endpoint existente y devuelven un `200`, pero fallan la verificación de firma en el lado de Adapty y se descartan. Para solucionarlo, en [Developers → Webhooks](https://dashboard.stripe.com/webhooks) elimina el endpoint que apunta a tu URL de webhook de Adapty y, a continuación, vuelve a conectar la integración de Stripe desde [App Settings → Stripe](https://app.adapty.io/settings/stripe) para que Adapty pueda crear uno nuevo y capturar su secreto. ## 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. **Por defecto y recomendado: Usar customer_user_id de los metadatos** — 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. **Usar el email del objeto Customer de Stripe** (ver [documentación de Stripe](https://docs.stripe.com/api/customers/object#customer_object-email)) 3. **Usar client_reference_id** del objeto Session de Stripe (ver [documentación de Stripe](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-client_reference_id)) — la opción a usar con [Payment Links](stripe#payment-links-no-code) Puedes configurar qué ID quieres usar en [**App Settings** → **Stripe**](https://app.adapty.io/settings/stripe). Adapty utiliza únicamente el origen que selecciones aquí para cada transacción de Stripe en la app; no recurre a los demás orígenes como alternativa. :::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. ### Ingresos netos e impuestos \{#net-revenue-and-tax\} Los ingresos y los ingresos netos de una transacción de Stripe pueden diferir entre los gráficos y los eventos de integración: - **Gráficos**: Para suscripciones, Adapty deduce la comisión de Stripe y los impuestos que Stripe reporta. Para compras únicas, Adapty deduce un 4,5% fijo del precio como comisión de Stripe. Los datos de pago de Stripe para estas compras no incluyen el importe del impuesto, por lo que Adapty no lo deduce. - **Eventos de integración** (enviados a webhooks, Amazon S3, Google Cloud Storage y otras integraciones): Adapty deduce un 4,5% fijo del precio como comisión de Stripe. No deduce ningún impuesto, por lo que `tax_amount_usd` y `tax_amount_local` siempre son `0`. ### Cupones y códigos promocionales Los ingresos en Adapty corresponden al importe que Stripe cobró, una vez aplicado cualquier descuento por cupón o código promocional. Adapty no almacena qué cupón o código promocional utilizó cada cliente. Para consultarlo, busca la suscripción o el pago en tu Stripe Dashboard. ### 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 mediante Stripe Checkout o Payment Links \{#one-time-purchases-via-stripe-checkout-or-payment-links\} Adapty registra las compras únicas (que no son suscripciones) realizadas a través de Stripe Checkout (`mode=payment`) o Payment Links a partir del evento `checkout.session.completed`. Asegúrate de que este evento esté habilitado en tu endpoint de webhook de Adapty en Stripe — los endpoints creados antes de que Adapty añadiera compatibilidad con Payment Links no lo incluyen. Consulta el [paso 4](stripe#payment-links-no-code) para saber cómo comprobarlo. Adapty toma el producto del primer elemento de línea de la sesión, por lo que una sesión que vende varios productos solo registra el primero. Los reembolsos para estas compras aún no se aplican: Stripe entrega `charge.refunded`, pero Adapty no revoca el acceso para una compra única realizada a través de Checkout o un Payment Link. Los reembolsos de suscripciones y de compras únicas facturadas a través de una factura de Stripe funcionan con normalidad. ### Una cuenta de Stripe conectada a varias aplicaciones de Adapty El endpoint de webhook que recibe un evento determina qué aplicación de Adapty lo procesa — cada aplicación de Adapty tiene su propia URL de endpoint —, no la clave API restringida. Cada endpoint que registres en Stripe recibe todos los eventos, y cada aplicación procesa su copia de forma independiente. Una aplicación ignora los eventos de productos que no están añadidos a ella, por lo que las aplicaciones con conjuntos de productos separados coexisten sin problemas. Sin embargo, si el mismo producto y precio de Stripe se añaden a más de una aplicación, cada una de esas aplicaciones registrará la transacción. En [Developers → Webhooks](https://dashboard.stripe.com/webhooks), la ruta de cada URL de endpoint de Adapty termina en `/<Adapty app ID>/production/` para el modo en vivo o `/<Adapty app ID>/sandbox/` para el modo de prueba. Al guardar una clave restringida para una app, Adapty reutiliza el endpoint con la URL de esa app para ese modo y solo crea uno nuevo si no existe, por lo que los endpoints de otras apps no cambian. ## 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 desde 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: - [Adjust](adjust) - [Amplitude](amplitude/) - [AppsFlyer](appsflyer) - [Webhook](webhook) - [Firebase](firebase-and-google-analytics) - [Mixpanel](mixpanel) - [Posthog](posthog) Adjust no tiene campos de Stripe: Adapty envía una compra de Stripe a Adjust con el token de la aplicación de la plataforma en la que el comprador instaló tu app. Consulta [Compras web (Stripe y Paddle)](adjust#web-purchases-stripe-and-paddle). ### Eventos de Stripe compatibles \{#supported-stripe-events\} Adapty admite los siguientes eventos de Stripe: - charge.refunded - checkout.session.completed - 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 integres Paddle, Adapty estará listo para ofrecer insights 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: - [Adjust](adjust) - [Amplitude](amplitude) - [AppsFlyer](appsflyer) - [Firebase / Google Analytics](firebase-and-google-analytics) - [Mixpanel](mixpanel) - [Posthog](posthog) - [Webhook](webhook) Amplitude, Firebase / Google Analytics y Mixpanel mantienen credenciales separadas por plataforma. Rellena los campos de **Paddle** en la página de integración — los eventos de Paddle no usan tus credenciales de iOS ni de Android. Adjust no tiene campos de Paddle: Adapty envía una compra de Paddle a Adjust con el token de la aplicación de la plataforma en la que el comprador instaló tu app. Consulta [Compras web (Stripe y Paddle)](adjust#web-purchases-stripe-and-paddle). ## 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: funnelfox --- --- title: "Conectar FunnelFox a Adapty" description: "Conecta los funnels web de FunnelFox a Adapty para que las compras realizadas en la web concedan niveles de acceso en tu app móvil." --- [FunnelFox](https://funnelfox.com) crea funnels web: páginas de aterrizaje y cuestionarios que venden tus suscripciones fuera de la App Store y Google Play. Conecta FunnelFox a Adapty para dar a los compradores web acceso a las funciones de pago en tu app móvil, y para ver sus compras en el mismo dashboard que las compras de la store. Conectar requiere una clave y un ID de nivel de acceso. Tu aplicación debe identificar al comprador para alcanzar ese nivel de acceso. Consulta [Proporciona acceso en tu aplicación](#provide-access-in-your-app). ## Cómo funciona la integración \{#how-the-integration-works\} FunnelFox gestiona el cobro en la web. A continuación, llama a la [API server-side de Adapty](getting-started-with-server-side-api) para crear el [perfil](profiles-crm), registrar la transacción y otorgar el [nivel de acceso](access-level) cuyo ID introduces en la configuración de la integración de FunnelFox. Las renovaciones, cancelaciones y pagos fallidos actualizan el mismo perfil. Las compras web aparecen en [Adapty analytics](analytics) junto con los ingresos de App Store y Google Play. Crea un [producto](product) correspondiente en Adapty por cada producto que vende tu embudo. La compra llega a Adapty de cualquier manera, pero sin un producto Adapty no tiene la duración de la suscripción, por lo que los análisis pueden reportar una duración incorrecta o dejar la compra sin categorizar. Adapty vincula una compra web con un usuario de la app mediante el customer user ID. FunnelFox utiliza por defecto la dirección de correo electrónico introducida en el checkout como ese ID. Si tu funnel factura a través de Stripe o Paddle, no tienes que conectar ese proveedor a Adapty también. Conéctalo solo si además vendes a través de él fuera de tus funnels, y Adapty deduplicará las transacciones que lleguen desde ambas fuentes. Para construir el mismo flow con tu propio checkout web en lugar de un funnel de FunnelFox, consulta [Sincronizar compras entre web y móvil](sync-subscribers-from-web). ## Conectar FunnelFox \{#connect-funnelfox\} La configuración se realiza desde el lado de FunnelFox, siguiendo la [guía de integración de Adapty de FunnelFox](https://funnelfox.com/docs/integrations/subscription-management/adapty#setup). Se necesitan dos valores de Adapty: - **Secret API key**: Abre [**App Settings**](https://app.adapty.io/settings/general) en Adapty y copia la clave secreta. - **Access level ID**: El [nivel de acceso](access-level) que otorga una compra. Todas las apps de Adapty comienzan con uno llamado `premium`; si necesitas otro, [créalo](create-access-level) primero. ## Otorga diferentes niveles de acceso dentro de un mismo funnel \{#grant-different-access-levels-within-one-funnel\} Un funnel que vende un único nivel no necesita nada aquí: cada compra otorga el nivel de acceso definido en la configuración de la integración. Para vender más de un nivel desde un mismo funnel, usa el campo **Custom entitlement** en un elemento de checkout. Este campo sobreescribe el ID del nivel de acceso de la configuración de la integración para las compras realizadas a través de ese elemento. Un funnel puede contener más de un elemento de checkout, como un botón Wallet y un checkout en acordeón. Cada uno tiene su propio campo **Custom entitlement**, así que rellénalo en cada elemento que deba otorgar el nivel de acceso personalizado. :::important Republica el funnel después de cambiar la configuración de la integración o cualquier valor de **Custom entitlement**. Un funnel publicado sigue sirviendo su configuración anterior hasta que lo republiques. ::: ## Proporcionar acceso en tu app \{#provide-access-in-your-app\} Para desbloquear funciones de pago a un comprador web, llama a `Adapty.activate()` o `Adapty.identify()` con el mismo customer user ID que usó FunnelFox. Por defecto, ese ID es la dirección de correo electrónico que el comprador introdujo en el proceso de pago. Consulta <InlineTooltip tooltip="Identificación de 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-identifying-users) y [Capacitor](capacitor-identifying-users)</InlineTooltip> para ver la llamada correspondiente. Instalar la app desde un enlace del funnel no identifica al comprador. Hasta que tu app llame a `Adapty.identify()` con el ID correspondiente, el perfil que tiene el nivel de acceso y el perfil que la app creó permanecen separados, y la app no muestra ningún acceso de pago. Iniciar sesión con el correo del checkout los vincula. Para desbloquear el acceso sin un paso de inicio de sesión, pasa el customer user ID del funnel a la app mediante un deferred deep link y llama a `Adapty.identify()` con ese ID en el primer lanzamiento. Luego lee el nivel de acceso del perfil para desbloquear las funciones. Consulta <InlineTooltip tooltip="Comprobación del estado de la suscripción">[iOS](ios-check-subscription-status), [Android](android-check-subscription-status), [React Native](react-native-check-subscription-status), [Flutter](flutter-check-subscription-status), [Unity](unity-check-subscription-status), [Kotlin Multiplatform](kmp-check-subscription-status), y [Capacitor](capacitor-check-subscription-status)</InlineTooltip>. ## Prueba la integración \{#test-the-integration\} 1. Completa una compra en tu funnel. 2. En Adapty, abre [**Profiles**](https://app.adapty.io/profiles/users) y busca el perfil por el ID de usuario cliente que usa FunnelFox. 3. Confirma que el nivel de acceso cuyo ID introdujiste en la configuración de la integración de FunnelFox está activo en ese perfil. 4. Abre tu app, identifica al mismo usuario y confirma que las funciones de pago se desbloquean. ## Solución de problemas \{#troubleshooting\} ### Un comprador web abre la app sin acceso de pago \{#a-web-buyer-opens-the-app-without-paid-access\} Tu app no ha identificado al comprador. FunnelFox otorgó el nivel de acceso a un perfil vinculado al ID que utilizó, que por defecto es el correo electrónico del checkout, y tu app está leyendo un perfil diferente. Llama a `Adapty.identify()` con ese ID, tal como se describe en [Proporcionar acceso en tu app](#provide-access-in-your-app). ## Otras integraciones de FunnelFox \{#other-funnelfox-integrations\} FunnelFox se conecta a tres productos de Adapty, cada uno configurado por separado. | Producto | Qué envía FunnelFox a Adapty | Guía de configuración | |---|---|---| | Core | Perfiles, transacciones y niveles de acceso, para que las compras web desbloqueen la app | Este artículo | | Adapty Attribution | Eventos de transacción con datos de campaña, funnel y experimento | [Integrar FunnelFox con Adapty Attribution](ua-funnelfox) | | Adapty Mail | Direcciones de correo electrónico, historial de compras y eventos de suscripción | [Conectar FunnelFox a Adapty Mail](mail-funnelfox) | --- # File: custom-store --- --- title: "Integración inicial con otras stores" description: "Integración inicial de Adapty con App Store: Guía rápida" --- 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 tiendas de aplicaciones ni procesa compras realizadas a través de ellas. Sin embargo, puedes marcar los productos vendidos a través de otras tiendas para que Adapty otorgue acceso al contenido de pago tras una compra exitosa, refleje las transacciones en tus análisis 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 utilizando la [API server-side de Adapty](getting-started-with-server-side-api). Adapty solo proporcionará acceso, activará un evento de transacción, lo enviará a las integraciones y lo reflejará en los análisis 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 crear una: 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: manage-adapty-with-ai --- --- title: "Gestiona Adapty con agentes de IA y herramientas de programación" description: "Todas las formas de usar Adapty con IA: integra el SDK con un agente de programación, descubre qué probar en tus analíticas y alimenta tu herramienta de IA con 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\} La 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) ## Migra desde RevenueCat con IA \{#migrate-from-revenuecat-with-ai\} Tu agente de código migra tu catálogo de RevenueCat y los diseños de tus paywalls a una nueva app de Adapty con la CLI de Adapty, y luego reemplaza el SDK de RevenueCat en el código de tu app con los IDs que ese catálogo obtuvo en Adapty. Consulta [Migración desde RevenueCat](migration-from-revenuecat). ## Crea y edita flows con una herramienta de codificación con IA \{#build-and-edit-flows-with-an-ai-coding-tool\} El [skill flow-generator](flow-generator-skill) edita tus flows de Adapty desde la terminal — añade un idioma, reescribe el contenido, agrega o elimina pantallas, configura ramificaciones o crea un paywall a partir de una descripción. Previsualiza y valida cada cambio en local y luego guarda un borrador para que lo publiques. Antes de publicar, el [skill flow-audit](flow-audit-skill) lee el mismo flow y comprueba si está listo para producción — triggers, cumplimiento con la store, productos y localización — y termina con un veredicto de aprobado/fallido. Si tu app ya utiliza paywalls, la [skill migrate-placements](migrate-placements-skill) se encarga de la migración: inventaría tus placements de paywall, los agrupa según el paywall que sirve cada uno y crea un placement de flow para cada uno junto al original. ## Administra Adapty desde la línea de comandos \{#manage-adapty-from-the-command-line\} El [CLI de Adapty para desarrolladores](developer-cli-quickstart) te permite gestionar tus entidades de Adapty — apps, niveles de acceso, productos, paywalls, flows y placements — desde la terminal, sin abrir el dashboard. Al ser una herramienta de línea de comandos, tu agente de código con IA puede ejecutarla directamente. ## Encuentra qué probar a continuación \{#find-what-to-test-next\} El [skill growth-advisor](export-analytics-with-ai) lee tus datos de suscripción a través de los comandos de análisis de la CLI de Adapty, identifica dónde pierde ingresos tu app y sugiere hipótesis para pruebas A/B, agrupadas por placement. También responde preguntas en lenguaje natural sobre tus métricas: ingresos, retención, LTV y más. No requiere servidor MCP ni clave de API. ## Ejecuta Adapty Mail desde un chat \{#run-adapty-mail-from-a-chat\} Adapty Mail tiene su propio [servidor MCP](mail-mcp). Conéctalo a Claude, ChatGPT, Claude Code, Codex, Cursor u otro cliente MCP, y tu asistente podrá generar y traducir campañas, lanzar flows, configurar un dominio de envío y leer analíticas de campañas, con tu sesión iniciada. No necesitas ninguna clave de API. En un agente de código, añade el [skill de Adapty Mail](mail-skill): conecta el servidor, pide confirmación antes de cualquier acción que envíe correos a personas reales y rastrea por qué alguien no recibió un email. ## Ejecuta pruebas A/B \{#run-ab-tests\} El [skill ab-tests](ab-tests-skill) crea, inicia, detiene y reemplaza pruebas A/B de paywall y flow desde tu herramienta de codificación con IA. Antes de cada paso que no se puede deshacer, te muestra quién ve qué y espera tu confirmación. ## 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: flow-generator-skill --- --- title: "Skill de agente para trabajar con flows" description: "Instala la skill flow-generator para que tu herramienta de IA pueda crear, traducir y reestructurar flows de Adapty a través de la CLI de Adapty." --- La [skill flow-generator](https://github.com/adaptyteam/adapty-skills) permite que una herramienta de codificación con IA cree y edite tus flows desde la terminal. Dile qué quieres construir o cambiar, en lenguaje natural, y la herramienta leerá la configuración del builder del flow a través de la [Adapty CLI](developer-cli-quickstart), la reescribirá, renderizará una vista previa y guardará un borrador una vez que lo apruebes. Una skill es un conjunto de instrucciones que tu herramienta de IA carga bajo demanda. Esta cubre las partes de la configuración de flow que son más fáciles de equivocarse: qué ediciones detienen una publicación, qué referencias se rompen al eliminar una pantalla, y qué puede y qué no puede demostrar una vista previa renderizada. :::important La skill del generador de flows es experimental y guarda los cambios directamente en los flows de tu dashboard. Ejecútala primero contra un borrador o una copia, y revisa cada cambio en el Flow & Paywall Builder antes de publicar. ::: ## Antes de empezar \{#before-you-start\} - **Una herramienta de codificación con IA que admita skills**: Claude Code, GitHub Copilot CLI, OpenAI Codex y Gemini CLI funcionan. - **Productos en tu catálogo de Adapty**: Antes de diseñar una pantalla de venta, la skill lista tus productos y pregunta cuáles deben aparecer en ella. Si ninguno encaja, puede crear uno a partir de los IDs de producto del store que le indiques, y lo pregunta primero. ## Qué puedes hacer con esto \{#what-you-can-do-with-it\} ### Crear un flow desde cero \{#author-a-flow-from-scratch\} - **Desde una descripción**: "Un paywall para mi app de fitness, dos planes, el anual preseleccionado." La skill elige la composición, lo construye a partir de patrones verificados por renderizado — tabs, tarjetas de plan, toggles, cuentas atrás, barras de progreso, bottom sheets — y usa los productos que elegiste de tu catálogo. - **Desde una captura de pantalla o un diseño**: Pásale una referencia y ajusta el layout, los colores, la tipografía y el estilo de iconos, indicándote además cada diferencia que no pudo cerrar. La referencia puede ser el paywall de un competidor, una de tus propias pantallas o un frame de tu herramienta de diseño. Si tu herramienta de IA está conectada a esa herramienta de diseño a través de MCP, lee el frame directamente — nadie tiene que exportar una captura ni volver a describir el diseño con palabras. - **Evaluado según lo que convierte**: Cuando no le proporcionaste un diseño, la skill ejecuta la librería `paywall-teardown` de Adapty sobre la pantalla que construyó y corrige lo que tiene mayor impacto, antes de que tú la veas siquiera. Obtienes una pantalla construida según los patrones que venden en tu categoría, no un layout genérico. ### Cambiar un flow existente \{#change-a-flow-you-already-have\} - **Localizar en nuevos mercados**: Añade un idioma y rellena todos los campos localizables — titulares, etiquetas de botones, descripciones de planes, espacios de imágenes y marcadores de posición de entrada. Es la forma más rápida de llevar un paywall que ya funciona a diez idiomas. - **Reescribir el texto en todos los idiomas a la vez**: Cambia la redacción sin tocar la estructura y edita alrededor de las variables de precio en lugar de a través de ellas, para que los precios sigan resolviéndose correctamente. - **Reestructurar el flow**: Añade, elimina y reordena pantallas, luego repara la navegación que ese cambio rompe e informa de cualquier pantalla que quede inaccesible. - **Añadir cuestionarios y ramificaciones**: Crea grupos seleccionables, IDs de opciones y las acciones condicionales que dirigen cada respuesta a su propia pantalla — el paso de personalización que convierte un onboarding en una visita al paywall ya cualificada. - **Crear variantes para una prueba**: Genera un flow nuevo a partir de uno que ya funciona y aplica el cambio que quieres probar. El tema, las fuentes, los idiomas y los productos se mantienen, de modo que la variante solo difiere donde tú lo decidiste. - **Cambiar la imagen para una temporada o campaña**: Toma muestras de colores de una imagen de referencia y aplica un nuevo tema a todo el flow, en lugar de rediseñar cada pantalla a mano. ## Qué produce \{#what-it-produces\} ### Un paywall con una línea de tiempo de planes \{#a-paywall-with-a-plan-timeline\} Una línea de tiempo numerada sobre dos tarjetas de planes. Renderizado en un dispositivo, por lo que el store ha resuelto los precios, incluido el precio por mes que Adapty calcula para el plan anual. ### Un paywall a partir de una frase El prompt fue «añadir una pantalla de paywall» — sin referencia, sin flow de origen. El `$0.00` es un marcador de vista previa, y la etiqueta muestra **Best value** porque nadie proporcionó un descuento. ### Un flow de tres pantallas \{#a-three-screen-flow\} Cuestionario, plan, oferta, con la navegación conectada entre ellos. La cuenta atrás se muestra, pero solo un dispositivo puede confirmar que se activa. ## Instala el skill \{#install-the-skill\} El skill forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener nuevos skills a medida que se publican, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Luego describe lo que quieres en lenguaje natural. La mayoría de las solicitudes son de este tipo: - **Describe el flow**: qué hace la app, qué vende la pantalla, qué plan preseleccionar. - **Apunta a un diseño**: una captura de pantalla, una de tus propias pantallas o un frame en tu herramienta de diseño. - **Pide un cambio concreto**: indica el flow y el cambio — una nueva localización, copy diferente, una pantalla extra, una rama en una respuesta de quiz. - **Responde sus preguntas**: te preguntará qué productos poner en una pantalla y, ante cualquier número real, se negará a inventarlo. ## Cómo funciona \{#how-it-works\} Todo lo que construye la skill converge en un archivo local. Nada llega a tu dashboard hasta el último paso. 1. **Configuración y autenticación**. La skill instala o actualiza el [Adapty CLI](developer-cli-quickstart) si es necesario, verifica tu inicio de sesión en Adapty y lee tu ID de app. Si no has iniciado sesión, abre el navegador para que te identifiques y espera a que termines. 2. **Elige o crea el flow**. Para ediciones, obtiene la configuración del flow y reporta lo que encontró — pantallas, idiomas, productos y el grafo de navegación — para detectar cualquier flow incorrecto antes de realizar cambios. Primero hace una copia de seguridad de la configuración obtenida. Para trabajo nuevo, crea un flow desde cero en lugar de generarlo sobre uno que ya tiene contenido. 3. **Valida la configuración**. La skill ejecuta dos validaciones contra el archivo local y una contra Adapty. El par local confirma que las referencias internas siguen siendo coherentes y que las propiedades tienen el formato correcto. `adapty flows config validate` ejecuta entonces el mismo servicio que controla la publicación, por lo que un resultado verde significa que la configuración es publicable. Ninguna de las tres guarda nada. 4. **Vista previa e iteración**. La skill renderiza cada pantalla modificada como una captura de pantalla y la examina. Si proporcionaste una imagen de referencia, compara el render con el archivo elemento a elemento —color, tipografía, estilo de iconos, proporciones— y corrige las diferencias que pueda resolver. Si algo no encaja, la skill vuelve al paso 3, lo que consume una captura de pantalla en lugar de un guardado. 5. **Aprobar y guardar**. Sobrescribir una configuración existente requiere tu confirmación explícita. La skill te muestra un antes y un después renderizado de cada pantalla que ha modificado, enumera los cambios que no tienen representación visual, abre una pantalla en vivo en el navegador y te indica el nombre del archivo de copia de seguridad. Solo entonces escribe. ## Después de la ejecución \{#after-the-run\} La skill te deja un borrador guardado. Para que llegue a los usuarios: 1. **Abre el flow en el Flow & Paywall Builder.** Si ya tenías el flow abierto en una pestaña del navegador, recarga la página — el Flow & Paywall Builder no detecta los cambios realizados mediante la CLI. 2. **Revisa qué cambió.** Adapty marca un flow que estaba **Published** antes de guardar como **Dirty**, y todo tu equipo verá ese estado. Tus usuarios siguen viendo la última versión publicada. 3. **Previsualiza en un dispositivo real.** [Vista previa de flows](paywall-device-compatibility-preview) explica el proceso con código QR a través de la app móvil de Adapty. Prueba todo lo que el skill indicó como no verificado: ramificaciones, interruptores y cualquier idioma distinto al que renderizó. 4. **Publica.** Haz clic en **Publish to Live** en la esquina superior derecha del Flow & Paywall Builder, o ejecuta [`adapty flows publish`](developer-cli-flows#adapty-flows-publish) desde el terminal. Un flow que no esté en un [placement](create-placement) no llegará a nadie, y un flow en estado Draft no se puede añadir a ninguno, así que publica primero. Consulta [Guardar y publicar flows](builder-save-publish). Si el resultado es incorrecto, restaura desde la copia de seguridad: la skill copia la configuración antes de su primera edición y nombra el archivo en su mensaje de aprobación. Pídele que escriba ese archivo de vuelta, o corrige el flow manualmente en el Flow & Paywall Builder. De cualquier manera, un borrador guardado no cambia nada para tus usuarios. ## Ejemplos de prompts \{#example-prompts\} La skill lleva incorporados los comandos, el formato de configuración y sus propios pasos de verificación, así que no hace falta detallarlos en el prompt. Indica qué flow quieres y qué debe cumplirse cuando termine. #### Traducir un paywall \{#translate-a-paywall\} ``` Add Brazilian Portuguese to my "Summer 2026" paywall. Translate every localizable field, not just the headline, and tell me what you couldn't. ``` #### Construir desde un diseño \{#build-from-a-design\} ``` Build a new paywall flow from the "Winter promo" frame in our Figma file. Show me my products first and let me pick which ones go on it. ``` #### Añadir un cuestionario con ramificaciones \{#add-a-quiz-that-branches\} ``` Add a quiz screen before my paywall: "What's your main goal?" with three options, each routing to its own benefit screen, then on to the paywall. ``` #### Reescribir el copy ``` Rewrite the headline and CTA on my onboarding to be shorter and more direct. Keep the price variables working, and apply it to every locale. ``` #### Crear una variante de prueba ``` Copy my "Main paywall" into a new flow and make the monthly plan the preselected one. Leave the original alone. ``` #### Rediseñar para una campaña ``` Re-theme my paywall flow to match the colors in ./design/spring.png. Copy and layout stay as they are. ``` #### Añade una pantalla \{#add-a-screen\} ``` Add a social proof screen between the quiz and the paywall in my activation flow — star rating and two testimonials. Ask me for the real numbers. ``` ## Dale tus imágenes \{#give-it-your-images\} La skill sube una imagen desde tu ordenador y vincula la URL devuelta al flow, para que no tengas que añadir contenido multimedia en el Flow & Paywall Builder a mano. Necesita una **ruta de archivo**, no una imagen: cuando pegas o adjuntas una imagen en el chat, tu herramienta de IA recibe los píxeles pero no el archivo, así que no hay nada que subir. Coloca los recursos en una carpeta de tu ordenador y luego indícale al tool dónde están: ``` The hero images are in ./design/paywall/. Use hero-calm.png on the first screen and the three icons in ./design/paywall/icons/ for the benefit rows. ``` Los formatos y límites de tamaño coinciden con los de una carga manual en el Flow & Paywall Builder — consulta [Imágenes, vídeos e iconos](custom-media). El SVG es la única excepción: falla al subirse, así que la herramienta dibuja glifos de un solo color de forma inline en su lugar. Cada carga añade un recurso independiente a tu biblioteca de medios, y ningún comando de la CLI elimina ninguno, por lo que volver a ejecutar una compilación deja copias acumuladas. Si indicas un archivo que la herramienta no puede encontrar, te lo pregunta en lugar de sustituirlo por uno parecido. Un archivo incorrecto genera una pantalla que se renderiza correctamente pero con la imagen equivocada, algo que ningún paso del flujo de trabajo detecta. ## Trae tu propio agente \{#bring-your-own-agent\} Adapty también tiene un [Editor de IA](flow-ai-editor) dentro del Flow & Paywall Builder. La diferencia no está en cuál tiene más funciones, sino en el entorno donde se realiza el trabajo. El Editor de IA es el agente de Adapty, que trabaja en la pantalla que tienes abierta. La skill es el arreglo contrario: tu agente, tu modelo, tus herramientas, y Adapty como una herramienta más que sabe cómo operar. Todo lo que tu herramienta de IA ya puede alcanzar viene con ella: tu repositorio, tus archivos de diseño, tus servidores MCP, tus propios prompts y contexto. | | Editor de IA | skill flow-generator | |---|---|---| | Agente de quién | De Adapty, en el navegador | Tuyo, en tu terminal | | Qué puede ver | La pantalla que tienes abierta, más las imágenes que adjuntas | Todo lo que tu herramienta de IA pueda alcanzar | | Alcance de un prompt | Una pantalla | El flow completo: pantallas, navegación, idiomas | | Localización | No está cubierta | Añade un idioma y rellena cada campo localizable | | Deshacer | Un clic por prompt | Un archivo de copia de seguridad, más tu aprobación antes de cada guardado | | Configuración | Ninguna | CLI de Adapty más el skill | Usa el AI Editor cuando ya estés en el builder y quieras rediseñar la pantalla que tienes delante. Usa la skill cuando prefieras no salir de tu propio entorno para trabajar en el flow. ## Lo que la skill no hará \{#what-the-skill-wont-do\} La skill trata tus flows como contenido en producción, ya que un flow publicado está sirviendo a usuarios reales. - **La publicación es tuya**: El skill se detiene en un borrador guardado y nunca publica. Publícalo tú mismo en el Flow & Paywall Builder o con [`adapty flows publish`](developer-cli-flows#adapty-flows-publish). - **Sobrescribir requiere tu aprobación**: Al guardar se reemplaza toda la configuración, sin escrituras parciales ni opción de deshacer. La aprobación indica el nombre del flow, su ID, su estado y el archivo de copia de seguridad que el skill creó antes de editar. - **Una edición simultánea hace fallar la escritura**: Cada escritura posterior a la primera envía el timestamp `updated_at` de la configuración de vuelta a Adapty. Si un compañero guardó el flow mientras tanto, Adapty rechaza la escritura en lugar de sobrescribir su trabajo. - **Eliminar un flow sigue siendo una acción del dashboard**: La CLI no tiene comando de eliminación, así que un flow creado por el skill permanecerá hasta que lo elimines desde el dashboard. - **Los números reales y los recursos los aportas tú**: Las valoraciones, el número de reseñas, los porcentajes de descuento y los precios de la store nunca se inventan. Cuando un patrón requiere uno que no has proporcionado, el skill te lo pide o prescinde del elemento, e indica cuál de las dos opciones tomó. :::note Las lecturas, validaciones y previsualizaciones no tienen ningún coste ni cambian nada. Guardar una configuración es la única acción que llega a tu dashboard, y requiere tu aprobación. ::: ## Limitaciones \{#limitations\} #### Las ejecuciones llevan tiempo \{#runs-take-time\} La skill valida y vuelve a renderizar tras cada cambio que realiza, lo que evita que una configuración rota llegue a tu dashboard — y eso hace que una ejecución sea más lenta que una respuesta de chat. Una edición pequeña termina en pocos minutos. Crear un flow con múltiples pantallas y ramificaciones lleva **entre 10 y 40 minutos**. Inícialo y vuelve más tarde en lugar de quedarte mirando. #### Los vídeos y las fuentes personalizadas siguen siendo manuales \{#videos-and-custom-fonts-stay-manual\} La skill sube imágenes, no [vídeos](custom-media), y no sube archivos de [fuentes personalizadas](using-custom-fonts-in-flow-builder). En ambos casos, construye el slot y te deja la subida a ti: - **Video**: La skill coloca un elemento multimedia donde corresponde el vídeo, así que el layout ya es correcto cuando intercambies el archivo en el Flow & Paywall Builder. - **Fuentes**: La skill crea los estilos de texto que requiere tu diseño y los apunta a una tipografía de marcador de posición. Sube la fuente en el Flow & Paywall Builder y asígnala a esos estilos. #### La vista previa muestra una pantalla, en un estado \{#the-preview-renders-one-screen-in-one-state\} La página de vista previa de la skill muestra una sola pantalla y no recorre el flow completo. No puede mostrarte ninguno de los siguientes aspectos. Prueba cada uno en un dispositivo real — [Vista previa de flows](paywall-device-compatibility-preview) explica cómo hacerlo: - **Navegación y ramificación**: Un botón con una acción de navegación permanece donde está en la previsualización, así que una ruta condicional no puede activarse. Cada pantalla se renderiza por separado. - **Selección de toggle y opción múltiple**: Una fila de toggle y una fila de opción múltiple se muestran en su estado desactivado según su valor predeterminado. Las filas de opción única y las tarjetas de producto sí renderizan su estado seleccionado. - **Cualquier idioma que no sea el que muestra**: La previsualización ignora el idioma predeterminado del flow y el orden de la lista de idiomas, por lo que no es posible verificar visualmente una traducción. Cambia el idioma en el Flow & Paywall Builder y comprueba si algún texto desborda su contenedor. - **Todo lo que se resuelve en tiempo de ejecución**: Precios reales, moneda del store y entrada del usuario. Una variable de precio se muestra como su token literal, que es más largo que cualquier precio y puede empujar texto por debajo de un botón anclado —una superposición que desaparece una vez que el precio se resuelve. - **Tamaños de pantalla que la página desconoce**: Un diseño cuya seguridad depende de la altura del viewport necesita un teléfono pequeño para demostrarlo. ## ¿Qué sigue? \{#whats-next\} - [Skill flow-audit](flow-audit-skill) — comprobación de solo lectura para verificar si el borrador que este skill te deja está listo para producción, antes de publicarlo. - [Guardar y publicar flows](builder-save-publish) — termina el borrador que el skill te deja. - [Problemas comunes](flow-common-issues) — qué impide que un flow se publique. - [Añadir un idioma en Adapty Flow & Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) — usa la interfaz del builder para agilizar el proceso de traducción. - [Gestionar Adapty con agentes de IA y herramientas de código](manage-adapty-with-ai) — los demás skills de Adapty y las herramientas de IA. --- # File: flow-screen-map --- --- title: "Ver cómo se conectan las pantallas de un flow" description: "Abre un mapa de cada pantalla de un flow y los caminos entre ellas — ramas, saltos y callejones sin salida incluidos — generado por el skill flow-generator en tu navegador." --- El Flow & Paywall Builder muestra una pantalla a la vez, por lo que en un onboarding largo con ramas de quiz es difícil ver adónde lleva cada respuesta. El [skill flow-generator](flow-generator-skill) puede dibujar el flow completo como un mapa: cada pantalla en orden, cada camino entre ellas y cada punto donde el camino se divide o termina. El mapa es un único archivo HTML que se abre en el navegador. Incluye las previsualizaciones de las pantallas, así que puedes enviárselo a un compañero y podrá abrirlo sin ninguna configuración previa. :::important El mapa forma parte del skill flow-generator, por lo que no hay nada adicional que instalar: sigue los [pasos de instalación del flow-generator](flow-generator-skill#install-the-skill). Dibujar el mapa solo lee el flow: no guarda nada en tu dashboard. ::: ## Para qué sirve \{#what-its-for\} - **Revisar la ramificación antes de publicar**: comprueba qué pantalla abre cada respuesta del cuestionario y dónde se vuelven a unir las ramas. - **Detectar errores de conexión**: el mapa señala las pantallas a las que nada dirige y las que no tienen salida. Ambas pasan la comprobación de publicación, pero dejan a los usuarios bloqueados en el dispositivo. - **Revisar un flow creado por otra persona**: lee un onboarding de 20 pantallas de un vistazo, sin tener que recorrerlo pantalla por pantalla. - **Compartir la estructura**: envía el archivo a un diseñador o a un product manager que no use el Adapty Dashboard. ## Cómo usarlo \{#how-to-use-it\} El skill abre el mapa por sí solo después de construir o reconstruir un flow de diez o más pantallas, o después de añadir o modificar una rama. Para abrirlo con cualquier flow, pregunta en lenguaje natural: ``` How are the screens in my "Sleep onboarding" flow connected? ``` ``` Where does the "Not really" button on the Busy mind screen lead? ``` El skill obtiene el flow, renderiza cada pantalla y abre el mapa. Para desplazarte, arrastra el fondo o usa el scroll. Para hacer zoom, usa **+** y **−**, o haz clic en **Fit** para ver el flow completo. Para ampliar una pantalla, haz clic en ella. ## Qué muestra el mapa \{#what-the-map-shows\} Las pantallas van de izquierda a derecha desde la primera. Las pantallas que una rama coloca una al lado de la otra aparecen en la misma columna. | En el mapa | Significado | | :--- | :--- | | Flecha gris | Un camino que no depende de una respuesta. | | Flecha morada con etiqueta | Un caso de una rama. La etiqueta indica la respuesta que la activa, por ejemplo `goal = Falling asleep faster`. La etiqueta **otherwise** marca el caso por defecto. | | Flecha discontinua | Vuelve a una pantalla con un número inferior. | | Etiqueta **after timer** | La pantalla avanza sola cuando termina su [temporizador](flow-timer). | | Etiqueta **branches** | El camino se divide en esta pantalla. | | Etiquetas **buys**, **restores**, **closes flow**, **back** | Acciones que abandonan la pantalla sin abrir otra. | | Etiqueta **nothing leads here** | Ninguna pantalla conduce a esta, por lo que los usuarios no llegan a ella. | | Etiqueta **no way out** | Ningún botón abre otra pantalla, realiza una compra ni cierra el flow. Un botón **Back** por sí solo no cuenta. | Para resaltar los paths de una pantalla, coloca el cursor sobre ella. Las demás pantallas y flechas se atenúan. ## Ejemplos \{#examples\} **Ramificación según las respuestas del cuestionario.** La pantalla **Goal** envía cada respuesta a su propia pantalla, y **Busy mind** vuelve a ramificarse. Los tres caminos convergen en **What keeps you up**. **Una rama sin salida.** Los usuarios que responden **After midnight** en la pantalla **Bedtime** van a **Name**, pero el botón **Continue** de **Name** no tiene ninguna acción asignada. El mapa lo marca como **no way out**. ## Sin navegador \{#without-a-browser\} Si tu herramienta de IA se ejecuta en una máquina remota, el skill imprime el mismo mapa como texto en el chat: ``` ◆ Bedtime bedtime = After midnight → Name otherwise → Wind-down back ▢ Name back (no way out) ▢ Wind-down "Continue" → Reaction: wind-down ``` El símbolo ◆ marca una pantalla donde el camino se divide. Etiquetas como `back` listan las acciones que salen de una pantalla sin abrir otra. ## Limitaciones \{#limitations\} - **Muestra la configuración, no el dispositivo**: El mapa dibuja los caminos que define el flow. Para confirmar que un temporizador se activa o que una rama abre la pantalla correcta en un teléfono, usa la [vista previa en dispositivo](paywall-device-compatibility-preview). - **Las vistas previas de pantalla tienen las mismas limitaciones que la vista previa del skill**: Los precios se muestran como nombres de variables y los toggles aparecen en su estado desactivado. Consulta [La vista previa muestra una pantalla, en un estado](flow-generator-skill#the-preview-renders-one-screen-in-one-state). - **Las acciones personalizadas terminan en el límite del flow**: Cuando un botón activa una [**Custom Action**](onboarding-actions#custom-actions), el mapa etiqueta la pantalla con el ID de la acción. La pantalla que abre tu app en respuesta no forma parte del flow, por lo que el mapa no la muestra. - **El mapa es de solo lectura**: Para cambiar un camino, pídele al skill que lo haga o edita el flow en el Flow & Paywall Builder y vuelve a abrir el mapa. --- # File: flow-audit-skill --- --- title: "Skill de agente para auditar flows" description: "Instala la skill flow-audit para que tu herramienta de IA compruebe si un flow está listo para producción antes de publicarlo." --- La skill flow-audit responde una sola pregunta sobre un flow: ¿está listo para producción? Apunta tu herramienta de IA a un flow y esta leerá la configuración a través de la [Adapty CLI](developer-cli-quickstart), la comprobará con tu catálogo de productos y emitirá un veredicto, sin dejarte una lista que tengas que interpretar tú mismo. Un flow puede superar todas las validaciones de publicación existentes y aun así no ser seguro para mostrárselo a los usuarios. Las comprobaciones de publicación del propio Flow & Paywall Builder y `flows config validate` se preguntan "¿es este documento correcto formalmente?" — ninguna de las dos pregunta "¿un usuario real podría quedarse bloqueado, o ver algo falso?" Un flow en estado `publication_failed` puede superar ambas comprobaciones; flow-audit es lo que detecta el resto. :::note flow-audit nunca escribe en un flow. Lee la configuración y tu catálogo de productos, ejecuta sus comprobaciones y te entrega todo lo que encuentra — a ti o al [skill flow-generator](flow-generator-skill) — para que lo corrijas. ::: ## Instalar el skill \{#install-the-skill\} flow-audit forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener nuevos skills a medida que se publican, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Luego pregunta, por ejemplo, "Antes de publicar este flow, ¿me falta algo?" Indica el nombre del flow, o deja que liste tus flows y pregunta cuál quieres revisar. ## Cómo funciona \{#how-it-works\} Cinco fases, todas de solo lectura: 1. **Resolución y autenticación.** Confirma que la CLI es lo suficientemente reciente y que tu sesión de Adapty es válida. 2. **Selección del flow.** Lista tus flows (`adapty flows list`) y pregunta cuál quieres comprobar. 3. **Obtención de datos.** Descarga la configuración del flow y tu catálogo de productos — el snapshot contra el que se ejecutan todas las comprobaciones. 4. **Ejecución de comprobaciones.** Cada comprobación se ejecuta únicamente contra ese snapshot. 5. **Informe.** Un veredicto, no una lista. ## Qué comprueba \{#what-it-checks\} - **Disparadores**: Un elemento cuyo texto promete una acción pero no tiene ninguna interacción en toda su cadena de ancestros. Se muestra exactamente igual que un control funcional y no hace nada. - **Cumplimiento de la store**: Una acción de restauración accesible en algún punto del flow, un enlace a tus términos, un enlace a tu política de privacidad y una forma de que el usuario abandone un paywall sin comprar. - **Productos**: Un producto vinculado que falta en tu catálogo, un producto sin vínculo a la store, una tarjeta que indica un período de facturación que el producto no tiene, o un precio fijo donde debería ir una variable de precio. - **Variables**: Una variable que el flow define pero nunca lee, de modo que una pantalla recoge una respuesta que nada utiliza. - **Localización**: Traducciones vacías, valores sin traducir y cobertura por idioma. - **Marcadores de posición**: Texto que nunca se terminó — `Lorem ipsum`, `TODO`, `Your headline here` — y un flow que sigue llamándose `Untitled`. ## Gravedad \{#severity\} Cada hallazgo recibe uno de tres niveles: | Nivel | Qué significa | |---|---| | **Bloqueante** | El flow no funcionará para un usuario real, o le mostrará algo falso — un precio inventado puede renderizarse perfectamente y aun así mentir. | | **Riesgo** | Funciona, pero probablemente no es lo que nadie pretendía: textos de relleno, un valor idéntico al del locale predeterminado, una variable que nadie lee. | | **Pregunta** | No es posible decidir con los datos disponibles. El informe indica qué vio, a qué no pudo acceder, y la única cosa que verificar — en lugar de adivinar. | ## Ejemplo de informe \{#example-report\} ``` Flow: Nimbus onboarding · publication_failed 7 screens · 3 locales · 2 products NOT READY FOR PRODUCTION — 2 blockers: no restore action, no way off the paywall BLOCKERS 1. This row is dead text. scr_paywall / el_089T reads "Restore purchase · Terms · Privacy" and carries no interaction at all. It renders exactly like a working row, and none of the following work: · no restorePurchases action anywhere in the flow → App Store 3.1.1 · no link to terms/EULA → App Store 3.1.2 · no link to a privacy policy → App Store 3.1.2 2. a user who does not buy cannot leave this screen: no closeFlow or navigateBack is reachable from it. ``` El estado de este flow es `publication_failed`, y tanto las comprobaciones de publicación del Flow & Paywall Builder como `flows config validate` ya lo aprobaron — ninguno lee el texto de una pantalla ni recorre sus interacciones. El informe comienza con el veredicto, seguido de una lista numerada de bloqueantes con el elemento exacto y la pantalla que hay que abrir. El veredicto de un flow limpio dice `READY FOR PRODUCTION`, sin nada añadido después. Si flow-audit no encontró bloqueantes ni riesgos pero aún tiene preguntas abiertas, el veredicto dice `READY, PENDING n CHECKS I CANNOT MAKE` en lugar de certificar algo que no pudo verificar. ## Qué no puede verificar \{#what-it-cant-check\} flow-audit no puede indicarte si el flow está vinculado a un placement. `adapty flows get` solo devuelve el `id`, el `name`, el `status` y el `updated_at` del flow, y la CLI no dispone de ningún comando que liste los placements en los que aparece un flow. Este es un recordatorio fijo en todos los informes, nunca una comprobación: confirma el placement tú mismo en la página [Placements](placements) o consulta [Crear placement](create-placement). ## Quién arregla qué \{#who-fixes-what\} Cada informe termina con su propia sección `WHAT TO DO NEXT`, dividida según quién realiza el trabajo: qué puede cambiar un agente en el flow, qué solo tú puedes cambiar en el Adapty Dashboard, y qué debes confirmar tú mismo antes de publicar. flow-audit no realiza ninguno de esos cambios por sí mismo — es de solo lectura por diseño, así que un flow que falla permanece exactamente como lo dejaste. Pasa el informe al [skill flow-generator](flow-generator-skill), que se encarga de la copia de seguridad, tu aprobación y la escritura real. --- # File: migrate-placements-skill --- --- title: "Skill de agente para migrar placements a flows" description: "Instala la skill migrate-placements para que tu herramienta de codificación con IA migre una app de placements de paywall a placements de flow, uno nuevo por cada antiguo." --- La skill migrate-placements traslada una app que utiliza paywalls a flows. Apunta tu herramienta de codificación con IA a tu cuenta, lee cada placement a través de la [CLI de Adapty](developer-cli-quickstart), calcula cuántos flows necesita la migración y crea un nuevo placement de flow por cada placement de paywall que estés migrando. Lo que obtienes es una tabla de ID de placement antiguo a ID de placement nuevo, y la única llamada que tu app debe cambiar. **Crea placements nuevos en lugar de convertir los existentes.** El tipo de contenido de un placement se fija en el momento de su creación, por lo que un placement de paywall nunca puede convertirse en un placement de flow — consulta [Crear un nuevo placement para tu flow](migrate-to-flows#create-a-new-placement-for-your-flow). Cada nuevo placement de flow funciona en paralelo al placement de paywall que reemplaza, y el antiguo sigue activo hasta que tu app implemente el cambio. La skill no diseña el contenido de los flows — eso lo hace la [skill flow-generator](flow-generator-skill) — y tampoco escribe el código de tu app, que es lo que hace la [skill de integración del SDK](manage-adapty-with-ai#skill-based-integration) para tu plataforma. Lee tus placements de paywall y nunca edita ninguno. ## Antes de empezar \{#before-you-start\} - **Una herramienta de codificación con IA que admita skills**: Claude Code, GitHub Copilot CLI, OpenAI Codex y Gemini CLI funcionan. - **Un flow para cada paywall que vayas a migrar, o un plan para obtenerlo**: La skill pregunta por cada paywall de dónde viene el flow, y una de las opciones es un clic que haces en el dashboard. Consulta [De dónde viene el contenido del flow](#where-the-flow-content-comes-from). - **Adapty SDK v4 en tu hoja de ruta**: Los flows solo se renderizan en SDK v4 o superior, por lo que la migración termina con una publicación de la app. Hasta que esa versión salga, nada de lo que crea la skill llegará a tus usuarios. ## Instalar el skill \{#install-the-skill\} migrate-placements forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). :::tip **¿Ya instalaste el plugin para otro skill de Adapty? No lo instales de nuevo.** Si tu agente no muestra migrate-placements, actualiza el plugin con el comando correspondiente a tu agente en el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). ::: Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener nuevos skills a medida que se publican, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Luego describe la migración en lenguaje natural — consulta los ejemplos de prompts a continuación. ## Ejemplos de prompts \{#example-prompts\} #### Migrar los placements que sirven a usuarios \{#move-the-placements-that-serve-users\} ``` Migrate the active paywall placements in my app to flows. Skip the inactive ones for now and tell me how many you left out. ``` #### Reutilizar los flows que ya convertiste \{#reuse-the-flows-you-already-converted\} ``` Migrate my paywall placements to flows. I've already converted three of the paywalls with Move to new builder — reuse those flows instead of building new ones. ``` #### Ensayar con los placements tranquilos primero \{#rehearse-on-the-quiet-placements-first\} ``` Start with my inactive paywall placements so I can watch the whole thing run without exposing users, then do the live ones. ``` #### Migrar un área de la app \{#migrate-one-app-area\} ``` Migrate only the four onboarding placements in my app, not the in-app upsells. Show me the placement IDs you'd create before you create anything. ``` ## Cómo funciona \{#how-it-works\} Ocho fases, y solo la séptima escribe algo en tu cuenta. 1. **Resolver y verificar.** Instala o actualiza la [CLI de Adapty](developer-cli-quickstart), confirma tu sesión en Adapty y lee el ID de tu app. 2. **Inventario.** Lee todos los placements de la app, paginando hasta el final de la lista en lugar de confiar solo en la primera página. 3. **Clasificar y agrupar.** Ordena cada audiencia en: migrables, ya asociadas a un flow, o sin nada que mover; luego agrupa las migrables según el paywall al que sirven. Ese agrupamiento es el plan de flows: **un flow por paywall distinto**, compartido por todos los placements que lo usan, de modo que refinás un solo flow en lugar de cinco copias. 4. **Hacer dos preguntas.** Cuánto de la cuenta mover y de dónde viene el contenido de cada flow. Consulta [De dónde viene el contenido del flow](#where-the-flow-content-comes-from). 5. **Publicar los flows.** Para un paywall sin flow asignado, la skill crea uno, guarda una configuración, lo publica y espera a que el estado sea `published`. Un flow que ya está publicado se registra tal cual. La publicación es importante porque un flow en borrador no se puede asociar a un placement. 6. **Mostrar la puerta.** La lista completa de IDs de placement que propone crear, y lo que aún no ha ocurrido. 7. **Crear los placements.** Un `placements create` por placement de paywall en scope, tras tu confirmación. 8. **Informe y traspaso.** El mapeo de antiguo a nuevo, y el cambio de llamada al SDK para tus desarrolladores. Proposed IDs follow your existing ones — `main` becomes `main-flow` — and each is verified against every placement ID already in the app. ## De dónde viene el contenido del flow \{#where-the-flow-content-comes-from\} Un nuevo placement necesita un flow publicado para funcionar, y la skill pregunta para cada paywall qué ruta seguir. Nunca elige por ti, porque las cuatro rutas difieren en lo que verán tus usuarios. | Ruta | Qué ocurre | Cuándo encaja | |---|---|---| | **Reuse** | La skill adjunta un flow que ya tienes. Si sigue en borrador, lo publica primero. | Ya has creado o convertido el flow. Se ofrece en primer lugar cuando existe un candidato. | | **Convert** | Haces clic en **Move to new builder** en la propia página del paywall; la skill espera, vuelve a leer tu cuenta y lee el flow que ha producido. | El paywall se creó en el Paywall Builder heredado. Consulta [Convertir un paywall en un flow](convert-paywall-to-flow). | | **Build** | La skill pasa el paywall a [flow-generator](flow-generator-skill), que diseña el flow. | La conversión no puede reproducir el paywall, o quieres que la pantalla se rediseñe en lugar de copiarse. | | **Stub** | La skill publica un flow mínimo de una sola pantalla para que el placement tenga algo que mostrar. | Último recurso. Un stub muestra un marcador de posición a tus usuarios, por lo que requiere tu confirmación explícita. | La skill busca flows existentes antes de construir nada, y los asocia a tus paywalls **solo por nombre** — Adapty no registra de qué paywall proviene un flow. Por eso, cada candidato que encuentra es una propuesta que debes confirmar, y un flow al que hayas cambiado el nombre no se encontrará. Compruébalo contra tu [lista de flows](https://app.adapty.io/flows) — la CLI no tiene ningún comando para eliminar un flow, así que si se crea uno por error, tendrás que borrarlo desde el dashboard. Si **Move to new builder** está disponible en un paywall concreto es algo que la skill no puede leer — el botón aparece únicamente en paywalls del Paywall Builder heredado, y la CLI no indica con qué builder se creó cada paywall. La skill describe el botón y dónde buscarlo; tú nos cuentas lo que encontraste. ## Qué es lo que apruebas \{#what-you-approve\} Antes de crear nada, la skill imprime un bloque para que lo leas y respondas. En él aparece el nombre de tu app, el número de placements que va a crear y la tabla completa de antiguo-a-nuevo, no un recuento abreviado. El mismo bloque indica qué es lo que un "sí" no hace: - **Tus placements de paywall no se ven afectados**: Siguen funcionando con normalidad, lo que también actúa como mecanismo de rollback: nada cambia para los usuarios hasta que tu app implemente la nueva llamada, así que no enviar nada revierte la migración. - **Esto aún no llega a los usuarios**: Tu app tiene que llamar a `getFlow` en el nuevo placement ID donde actualmente llama al antiguo. - **Un "no" detiene los placements, no los flows**: Cualquier flow que la ejecución ya haya creado permanece en tu cuenta, y ningún comando de CLI elimina un flow. El asistente indica el recuento y dónde eliminarlos. ## Qué obtienes \{#what-you-get\} La ejecución termina con los recuentos —creados, omitidos y fallidos con sus motivos— y luego el mapeo con el que trabajan tus desarrolladores: | Placement del paywall antiguo | Placement del nuevo flow | |---|---| | `onboarding_main` | `onboarding_main-flow` | | `paywall_settings` | `paywall_settings-flow` | En SDK v4, `getFlow` lee tanto placements de flow como placements de paywall, por lo que tu app llama a un único método en ambos casos — lo que cambia es el ID del placement, no el método. Delega la implementación a la guía de integración del SDK para tu plataforma, que gestiona los puntos de llamada específicos de cada 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). Mantén ambos placements activos hasta que la adopción de v4 sea suficientemente alta. Los usuarios con versiones ya publicadas tienen el ID del placement anterior compilado en su app y no pueden acceder al flow hasta que actualicen, por lo que cada placement se mide con sus propias métricas mientras ambos están activos: compáralos como cohortes separadas. [Migrar a flows](migrate-to-flows) cubre toda la transición. ## Lo que no hará \{#what-it-wont-do\} - **Convertir un placement en otro tipo directamente**: Esa operación no existe. Adapty rechaza los cambios de tipo, por lo que toda migración implica crear un placement nuevo. - **Reutilizar el ID del placement antiguo**: Los IDs de placement son únicos en toda tu app, independientemente del tipo, así que el nuevo placement de flow necesita un ID que ningún otro placement esté usando, incluido el placement de paywall al que reemplaza. - **Modificar nada de lo que ya existe**: No se edita ni elimina ningún placement, paywall o flow existente. La skill solo los lee y crea nuevos junto a ellos. - **Crear un placement sin tu confirmación**: Todo ID propuesto pasa primero por la [revisión de aprobación](#what-you-approve). - **Crear un segundo flow para un paywall que ya tiene uno**: No sin que tú rechaces reutilizar el que ya existe, porque el duplicado sería un flow que tendrías que eliminar manualmente. - **Escribir el código de tu app**: La ejecución termina en la entrega. ## Limitaciones \{#limitations\} #### Una audiencia con varios segmentos debe resolverse primero \{#an-audience-with-several-segments-has-to-be-resolved-first\} Una audiencia que apunta a más de un [segmento](segments) puede leerse pero no escribirse de nuevo, por lo que la skill no puede recrearla en un nuevo placement. Informa esos placements como bloqueados e indica el motivo, y no ofrecerá dividir ni eliminar un segmento — eso cambiaría quién ve qué, y esa decisión es tuya. Resuélvelos en el dashboard, y el resto de la migración continúa sin ellos. #### Las audiencias de las pruebas A/B no son visibles para la skill \{#ab-test-audiences-arent-visible-to-the-skill\} Leer un placement solo devuelve sus audiencias de paywall y flow, y omite en silencio cualquier otro tipo. Una audiencia de [prueba A/B](ab-tests) es uno de esos casos, y la respuesta no indica que se haya omitido ninguna entrada, por lo que no hay forma de detectarlo. Los recuentos describen lo que devolvió la API. Para cualquier placement que sepas que ejecuta una prueba A/B, revisa la lista de audiencias en el dashboard — el nuevo placement de flow no llevará consigo una audiencia que la skill nunca vio. #### Nada se verifica de extremo a extremo hasta que lo publicas \{#nothing-is-verified-end-to-end-until-you-ship\} Las comprobaciones propias de esta skill se detienen en "el flow está publicado y el placement existe". Si el flow funciona para un usuario real es una pregunta aparte: ejecuta [flow-audit](flow-audit-skill) en cada flow antes de publicarlo, y haz una compra en sandbox en el nuevo placement una vez que la app lo llame. ## Qué hacer a continuación \{#whats-next\} - [Migrar a flows](migrate-to-flows) — la migración completa, incluida la actualización del SDK que el skill delega. - [Convertir un paywall en un flow](convert-paywall-to-flow) — el clic en **Move to new builder** del que depende la ruta de conversión. - [Skill flow-generator](flow-generator-skill) — crea y edita el contenido del flow que este skill adjunta. - [Skill flow-audit](flow-audit-skill) — verifica que un flow esté listo para producción antes de asignarlo a un placement. - [Referencia de Adapty CLI](developer-cli-placements) — los comandos de placement que ejecuta el skill. --- # File: export-analytics-with-ai --- --- title: "Skill de agente para hacer crecer los ingresos" description: "Instala el skill growth-advisor para que tu herramienta de IA lea tus análisis de Adapty, detecte dónde pierde ingresos tu app y sugiera qué probar en la siguiente prueba A/B." --- El skill growth-advisor responde a "¿qué debería probar a continuación?" a partir de tus propios números de suscripción. Tu herramienta de codificación con IA lee tus análisis, placements, paywalls y productos a través de la [CLI de Adapty](developer-cli-analytics), sigue la señal más importante hasta su causa y devuelve una lista corta de hipótesis para hacer pruebas A/B, ordenadas por las compras que está en juego en cada una. :::note growth-advisor no cambia nada en tu cuenta sin tu confirmación. Todas las lecturas de análisis son de solo lectura, y cada escritura — una nueva copia de flow, un nuevo segmento, una prueba A/B, un cambio de placement — espera a que la confirmes. ::: ## Qué puedes preguntar \{#what-you-can-ask\} Pregunta como si le explicaras algo a un compañero. No hace falta que menciones el skill: ``` Audit my app. What should I test next to grow revenue? Revenue dropped last month. Why? My A/B test on the onboarding paywall ended and the new version won. What now? I have three ideas: a weekly plan, a trial on annual, and a second placement. Which one should I test first? Which country refunds the most, and do people who start a trial renew better? We launch next month and have no data yet. Where should we start? ``` Una pregunta sobre tus números obtiene una respuesta de tus números, sin auditoría adjunta. Una app sin datos aún recibe un plan inicial basado en medianas del mercado del informe State of Paywalls 2026 de Adapty, con cada número etiquetado como estimación. *Ejemplo de conversación con una app de muestra. Tus números serán diferentes.* **User:** Los ingresos cayeron el mes pasado. ¿Por qué? **Agent ran:** Ejecutó 4 comandos de solo lectura - `adapty analytics chart --chart revenue --compare-from 2026-08-01 --compare-to 2026-08-31` - `adapty analytics chart --chart revenue --group-by store` - `adapty analytics chart --chart subscriptions_new --group-by store` - `adapty analytics chart --chart subscriptions_expired --group-by cancellation_reason` **Agent:** **Los ingresos cayeron porque menos personas renovaron en Google Play, no porque menos personas compraran.** Las nuevas suscripciones se mantuvieron estables en septiembre. Las renovaciones en Google Play cayeron un tercio, mientras que en App Store no hubo cambios. La mayoría de las renovaciones perdidas corresponden a suscriptores mensuales cuyo pago falló. **Próximos pasos** 1. Tú: ¿cambió algo en tu configuración de facturación de Google Play a principios de septiembre? 2. Yo: sugerir hipótesis para recuperar a los suscriptores mensuales que se fueron. ¿Quieres que lo haga? ## Antes de empezar \{#before-you-start\} - **Una cuenta de Adapty con datos**: El skill lee los mismos análisis que muestra tu dashboard. Sin datos, solo puede darte un plan inicial. - **Una herramienta de IA que pueda ejecutar comandos de terminal**: Por ejemplo, Claude Code, Codex, Copilot CLI o Gemini CLI. Las herramientas de chat como claude.ai o ChatGPT no pueden ejecutar la CLI, por lo que no pueden acceder a tu cuenta de Adapty. ## Instalar el skill \{#install-the-skill\} growth-advisor forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener nuevos skills a medida que se publican, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). ## Cómo funciona la auditoría \{#how-the-audit-works\} Cuando pides orientación sin una pregunta concreta, el skill ejecuta una auditoría. Lee tus métricas y la configuración de tus placements, determina qué has probado ya y sigue el cambio más significativo hasta el store, canal, país o placement que lo explica. Antes de opinar sobre una pantalla, revisa el flow activo o te pide una captura de pantalla de un paywall que tu app renderiza por su cuenta. ## Qué obtienes como respuesta \{#what-you-get-back\} El skill responde con una breve explicación y luego una lista de hipótesis agrupadas por placement, ordenadas según el orden en que conviene probarlas. Cada hipótesis indica por qué tu app la necesita, qué resultado esperar y de dónde procede esa estimación, cuánto tardaría la prueba A/B y quién hace qué. Una hipótesis puede dirigirse a todos los usuarios de un placement o a un [segmento](segments) concreto, como por ejemplo los exsuscriptores. *Ejemplo de conversación con una app de muestra. Tus números e hipótesis serán diferentes.* **User:** Audita mi app. ¿Qué debería probar a continuación para aumentar los ingresos? **Agent ran:** Se ejecutaron 9 comandos de solo lectura - `adapty apps list` - `adapty analytics chart --chart revenue --group-by country` - `adapty analytics chart --chart revenue --group-by duration` - `adapty analytics chart --chart subscriptions_new --group-by placement_id` - `adapty analytics chart --chart subscriptions_expired --group-by cancellation_reason` - `adapty analytics conversion --conversion trial-paid --granularity month` - `adapty placements list` - `adapty flows list` - `adapty flows config get <flow-id>` **Agent:** **La mayor parte de tus ingresos proviene del paywall de onboarding, y casi todo el mundo elige el plan semanal.** De tus datos: dos paywalls de onboarding se ejecutaron en paralelo en julio, y mantuviste "Main". El plan anual aparece en pantalla, pero solo representó el 6% de las compras en septiembre. La mayoría de las suscripciones terminan con los usuarios desactivando la renovación, y ningún placement muestra algo diferente a los antiguos suscriptores. **Hipótesis sugeridas** En el placement `onboarding`: **1. Preseleccionar el plan anual en "Main"** - **Por qué:** La opción semanal está preseleccionada en "Main", así que la mayoría de los compradores nunca se plantea el precio anual. - **Resultado esperado:** ARPU aumenta un 25–40%, medido por ingresos por vista de paywall. Es una de las victorias más fiables en la biblioteca de pruebas A/B de suscripciones reales de Adapty. - **Tiempo:** Unas 3 semanas con tu volumen de compras. - **Responsables:** Yo: creo el copy del flow y configuro la prueba A/B. Tú: publicas el copy. En el placement `settings`: **2. Muestra a los ex suscriptores un paywall de recuperación** - **Por qué:** El 71% de las bajas en septiembre fueron usuarios que desactivaron la renovación, y cuando vuelven ven el mismo paywall que todos los demás. - **Resultado esperado:** No hay precedentes en la biblioteca de Adapty, así que trátalo como una apuesta. Se juzgará por las compras del segmento. - **Tiempo:** Unas 8 semanas. - **Quién:** Yo: creo el flow de recuperación, el segmento "Exsuscriptores" y configuro la prueba A/B. Tú: publicas el flow. **Próximos pasos** 1. Tú: ¿por cuál quieres empezar? Yo elegiría la 1, porque la ve cada nuevo usuario. 2. Yo: creo una copia de "Main" con el plan anual preseleccionado. ¿Lo hago? **User:** Ve con la 1. **Agent asks:** Crear una copia del flow ¿Crear **Main – anual preseleccionado**, una copia de "Main" con el plan anual preseleccionado? No cambia nada más y el flow publicado sigue igual. 1. Sí 2. No, cambiar algo antes Tú decides con qué hipótesis empezar. Si no estás seguro, el skill indica su propia elección y el motivo. El skill no guarda ningún registro pendiente, así que guarda la respuesta si quieres volver a las otras hipótesis más adelante. ## De la hipótesis a la prueba A/B \{#from-hypothesis-to-ab-test\} Para cada hipótesis, el skill sugiere una prueba A/B y se ofrece a configurarla. Antes de hacerlo, verifica que todo lo necesario para la prueba existe, e indica qué falta y quién debe crearlo: | Lo que necesita la prueba | Quién lo hace | |---|---| | Un flow modificado: nuevo diseño, copy o conjunto de productos | El skill crea una copia del flow a través del [skill flow-generator](flow-generator-skill), con un cambio por hipótesis, para que la prueba muestre qué causó ese cambio. Antes de publicar la copia, el skill ofrece revisarla con el [skill flow-audit](flow-audit-skill). Un flow solo se incorpora a una prueba una vez publicado. | | Un nuevo período de plan, trial u oferta | Tú creas el producto en App Store Connect o Google Play Console y lo añades a Adapty. En el App Store, un trial requiere un producto nuevo; en Google Play, es una oferta sobre el plan existente. Tú fijas el precio. | | Un paywall con productos ya existentes en Adapty | El skill lo crea. | | Un paywall con un nuevo diseño | Tú lo haces en el [Paywall Builder](adapty-paywall-builder). | | Un segmento | El skill lo crea a través del skill de audiencias. | | Un nuevo placement | El skill lo crea una vez que apruebas su ID de desarrollador. Tu app necesita un cambio en el código y un lanzamiento para cargarlo. | Cada pieza que genera el skill espera tu confirmación. Cuando una pieza es tuya para crear, la lista **Next steps** del skill te lo indica, y el skill configura el test en cuanto le confirmas que la pieza está lista. Nunca configura un test con una pieza que todavía no existe. Cuando todas las piezas existen, el [skill ab-tests](ab-tests-skill) crea el test como borrador. Antes de iniciarlo, te muestra exactamente quién verá qué. Una [prueba A/B](ab-tests) es la opción por defecto y la única forma de medir el impacto del cambio. Para mostrar el cambio sin una prueba, pídelo. El skill puede mostrarlo en un nuevo placement, o a algunos o todos los usuarios de un placement existente. Antes de que el skill modifique un placement existente, lo guarda en un archivo para que puedas restaurarlo. También indica el nombre de cualquier prueba A/B que esté activa en ese placement. El cambio no afecta a esa prueba. Cuando termine una prueba A/B, dile al skill qué versión ganó. Si la prueba sigue activa, ofrecerá detenerla con ese ganador. Luego sugerirá la siguiente hipótesis. ## Límites \{#limits\} - **Sin análisis de precios de momento**: El skill no lee, compara ni sugiere precios. Cuando los datos apuntan al precio, lo indica y aclara que el análisis de precios aún no está disponible. - **Sin resultados de pruebas A/B**: El skill no lee los resultados de las pruebas A/B. Infiere las pruebas pasadas a partir de los paywalls que se ejecutaron en paralelo y depende de que tú le indiques qué versión ganó. - **Sin gasto publicitario**: El skill puede desglosar instalaciones, pruebas y revenue por canal de atribución, pero no el gasto ni el ROAS. Para eso, usa el [skill adapty-attribution](developer-cli-attribution-skill). Para consultar tus analíticas directamente, sin el skill, consulta la [referencia de analíticas de la CLI](developer-cli-analytics). Para obtener analíticas desde un servidor o una tarea programada, donde nadie puede iniciar sesión a través de un navegador, usa la [API de exportación de analíticas](export-analytics-api) con una clave de API secreta. --- # File: ab-tests-skill --- --- title: "Skill de agente para pruebas A/B" description: "Instala el skill ab-tests para que tu herramienta de IA cree, inicie, detenga y reemplace pruebas A/B de paywalls y flows en Adapty, y te muestre quién ve qué antes de cada paso irreversible." --- La skill ab-tests ejecuta [pruebas A/B](ab-tests) de paywalls o flows directamente desde tu herramienta de codificación con IA. El agente planifica la prueba contigo, crea los elementos que faltan, genera la prueba como borrador y la inicia, la detiene con un ganador o la reemplaza con una versión modificada. Todo funciona a través de los [comandos CLI `adapty ab-tests`](developer-cli-ab-tests). :::note No puedes deshacer el inicio, la detención, el reemplazo ni el archivado de una prueba. Antes de cada uno de estos pasos, la skill te explica con palabras sencillas quién verá qué y desde cuándo, y solo ejecuta el comando después de que confirmes. ::: ## Qué puedes preguntar \{#what-you-can-ask\} Pregunta como si le estuvieras dando instrucciones a un compañero. No necesitas mencionar el skill por su nombre: ``` Run a 50/50 test of my Monthly and Annual paywalls for users in Germany. Test my new onboarding flow against the current one. Which A/B tests are running in my app? Stop the price test. B wins. Change the weights of my flow test to 70/30. Archive the old tests on the settings placement. ``` *Ejemplo de conversación con una aplicación de muestra. Tus placements y paywalls serán diferentes.* **User:** Ejecuta una prueba 50/50 de mis paywalls Monthly y Annual para usuarios en Alemania en el placement Main. **Agent ran:** Ejecutó 5 comandos de solo lectura y creó un borrador - `adapty paywalls list` - `adapty placements list` - `adapty segments list` - `adapty ab-tests create --title "Monthly vs Annual" --paywalls '[…]'` - `adapty ab-tests get <test-id>` - `adapty placements get <placement-id>` **Agent:** Creé la prueba "Monthly vs Annual" como borrador. Ningún usuario la ve todavía. **Agent asks:** Iniciar la prueba A/B Iniciar "Monthly vs Annual" en el placement Main para usuarios en Alemania. - Ahora: los usuarios en Alemania ven el paywall Monthly. - A partir de ahora: verán el paywall Monthly (50%) o el paywall Annual (50%). Un cambio de segmento puede tardar hasta 20 minutos en llegar a todos los usuarios. - Se detiene: el paywall Monthly deja de mostrarse a los usuarios en Alemania. No hay ninguna prueba activa allí ahora, así que ninguna se detiene. - **No se puede deshacer.** Detener la prueba más adelante no restaura lo que reemplazó. ¿Iniciarlo? 1. Yes 2. No ## Antes de empezar - **Una herramienta de IA que pueda ejecutar comandos de terminal**: Por ejemplo, Claude Code, Codex, Copilot CLI o Gemini CLI. Las herramientas de chat como claude.ai o ChatGPT no pueden ejecutar la CLI, por lo que no pueden acceder a tu cuenta de Adapty. - **La CLI de Adapty, versión 0.13.0 o posterior**: El skill instala la CLI si no está presente. Si tu CLI no tiene los comandos `ab-tests`, el skill te lo indica y te redirige al [dashboard](run_stop_ab_tests). ## Instala el skill \{#install-the-skill\} El skill ab-tests forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). ## Cómo el skill ejecuta una prueba \{#how-the-skill-runs-a-test\} 1. El skill acuerda el plan contigo: una prueba A/B de paywall o de flow, los paywalls o flows a comparar y sus pesos, el placement, la audiencia y el objetivo. Si faltan datos, los pide todos en un solo mensaje. 2. Verifica cada elemento y crea lo que puede. Consulta [Qué necesita la prueba](#what-the-test-needs). 3. Crea la prueba como borrador. Ningún usuario ve un borrador, así que este paso no requiere confirmación. 4. Lee la prueba y el placement, y luego te muestra el efecto de iniciarla: qué ve la audiencia ahora, qué verá tras el inicio, qué paywall, flow o prueba en curso se detiene, y qué usuarios pasan a la prueba. 5. Espera tu confirmación. Un "adelante" previo sobre el plan no cuenta. Si dices no, no envía nada. 6. Vuelve a leer el placement justo antes de iniciar. Si el placement cambió desde que viste el efecto, te muestra el nuevo efecto y vuelve a preguntar. 7. Inicia la prueba y la vuelve a leer para confirmar qué está activo ahora. Detener, reemplazar y archivar una prueba siguen los pasos 4–7, con esa acción en lugar del inicio. El skill no vuelve a iniciar una prueba después de que se ejecutó, porque los usuarios que participaron en ella recibirían de nuevo su variante anterior. En su lugar, propone una nueva prueba con las mismas variantes. ## Lo que necesita la prueba \{#what-the-test-needs\} Una prueba solo puede comparar contenido que existe. El skill verifica cada elemento y crea los que el CLI puede generar: | Pieza | Qué hace el skill | |---|---| | Un flow | Verifica que el flow esté publicado. Un flow se une a una prueba A/B solo una vez publicado. Para construirlo o publicarlo, el [skill flow-generator](flow-generator-skill) se encarga. | | Un paywall con productos ya presentes en Adapty | Lo crea, tras tu confirmación. | | Un paywall con un diseño nuevo | Te pide que lo hagas en el [Paywall Builder](adapty-paywall-builder), o en el código de tu app si es un paywall que la propia app dibuja. | | Un producto nuevo | Te pide que lo crees en App Store Connect o Google Play Console y lo añadas a Adapty. Consulta [Productos](product). | | Un segmento | Lo crea a través del skill de audiencias, tras tu confirmación. | | Un placement | Lo crea una vez que apruebas su ID de desarrollador, que no se puede cambiar después. Tu app no muestra nada de un placement nuevo hasta que el código lo solicite. Una prueba A/B en ese placement no llega a ningún usuario hasta que publiques una versión de la app cuyo código lo consulte. | ## Sobre qué audiencia se ejecuta la prueba \{#which-audience-the-test-runs-on\} La prueba se ejecuta sobre una audiencia del placement: - Cuando el placement ya tiene una audiencia para tu segmento, o la prueba es para todos los usuarios, la prueba toma esa audiencia. Cualquier prueba que esté ejecutándose allí en ese momento se detiene. - Cuando el placement no tiene ninguna audiencia para tu segmento, el skill añade una audiencia para tu segmento con la máxima prioridad en el placement. Los usuarios del segmento que actualmente coinciden con otra audiencia pasan a la prueba, y el skill nombra esas audiencias antes de que confirmes. Para dar al test una prioridad inferior a la de otra audiencia, pídele al skill. El skill primero añade tu segmento como audiencia con esa prioridad y con el paywall o flow que esos usuarios ven actualmente, de modo que no cambia nada para ellos. Luego inicia el test en esa audiencia. ## Cambiar, detener o archivar una prueba \{#change-stop-or-archive-a-test\} - **Renómbralo o cambia su objetivo**: El skill actualiza la prueba. Los usuarios conservan sus variantes y la prueba mantiene su fecha de inicio. - **Cambia las variantes o los pesos de una prueba en curso**: El skill reemplaza la prueba. La prueba en curso se detiene y conserva sus resultados, y una nueva prueba comienza con la misma audiencia. Adapty reasigna a todos los usuarios de la audiencia a una variante. En el dashboard, esto es **Modify**. - **Detenerla**: Indica al skill qué paywall o flow debe ver la audiencia a continuación: el ganador, otro contenido o ninguno. Para elegir el ganador, consulta los resultados en el dashboard: [Resultados y métricas de pruebas A/B](results-and-metrics). - **Archivarla**: El skill archiva cualquier prueba que no esté activa. No es posible restaurar una prueba archivada. ## Con otras skills - **Encuentra qué probar**: La [skill growth-advisor](export-analytics-with-ai) lee tus datos y sugiere hipótesis. Cuando eliges una, growth-advisor verifica que todo lo que necesita el test existe y luego pasa el test a la skill ab-tests. - **Crea el contenido**: La [skill flow-generator](flow-generator-skill) crea una copia de tu flow con el cambio que quieres probar. - **Muestra contenido sin un test**: Para mostrar diferentes paywalls o flows a distintos usuarios sin un test, pídele a tu agente. La skill audiences realiza el cambio. ## Límites \{#limits\} - **Sin resultados**: El skill no lee resultados de pruebas. Consúltalos en el dashboard y luego indícale al skill qué variante ganó. - **Un placement por prueba**: Para ejecutar una [prueba entre varios placements](ab-test-types), usa el dashboard. - **Constructor de onboarding antiguo**: Gestiona las pruebas de onboarding creadas con el constructor antiguo directamente en el dashboard. - **Sin ejecuciones desatendidas**: En una ejecución programada o en un script, sin nadie que responda, el skill solo lista y lee pruebas, y crea y modifica borradores. Para iniciar, detener, reemplazar o archivar, informa del efecto y del comando que debes ejecutar tú. Para ejecutar los comandos tú mismo, sin el skill, consulta [Ejecutar pruebas A/B desde la CLI](developer-cli-ab-tests). --- # 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 solicitudes**: Se aplica por clave API y se asigna por app, por lo que no hay una cifra única válida para todas — consulta [Límites de solicitudes](https://adapty.io/docs/es/ss-authorization.md#rate-limits). Es una tasa sostenida, no un presupuesto por minuto, y algunos endpoints tienen un límite inferior: [createVirtualCurrencyTransaction](https://adapty.io/docs/es/api-adapty/operations/createVirtualCurrencyTransaction.md) permite 600 solicitudes por minuto por app por defecto. Indica a tu agente que reintente con backoff al recibir un `429` y que respete el encabezado `Retry-After` cuando esté presente. - **Claves específicas por app**: Cada clave funciona para una sola app; usa la clave correspondiente en cada caso. - **Se requiere un identificador**: Cada solicitud necesita `adapty-customer-user-id` o `adapty-profile-id`. --- # File: installation-of-adapty-sdks --- --- title: "Instalación del SDK de Adapty" description: "Instala los SDK de Adapty para iOS, Android y aplicaciones multiplataforma." --- Tienes tres opciones para 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 funcionalidades 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)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/BasicExample) — una configuración mínima que muestra las funciones principales del SDK. - [React Native (Expo Go y Expo Web)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) — la más rápida de probar: se ejecuta en un navegador con datos simulados, por lo que no necesita una clave API de Adapty. Incluye tanto un paywall personalizado como el Flow & Paywall Builder. - [React Native (Focus Journal, una app con Expo)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) — una app con funciones premium detrás de un paywall del Flow & Paywall Builder. Incluye una [guía en vídeo](https://www.youtube.com/watch?v=TtCJswpt2ms). - [React Native (Adapty DevTools)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/AdaptyDevtools) — una herramienta de desarrollo e informes de errores para trabajar con casos más complejos. La misma app también está disponible [con Swift Package Manager en lugar de CocoaPods](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/AdaptyDevtoolsSpm). - [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 avanzadas de desarrollo)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/adapty-devtools) --- # File: flows-and-paywalls --- --- title: "Flows y paywalls" description: "Compara las dos formas de vender en tu app con Adapty: flows y paywalls creados en el Flow & Paywall Builder, o un paywall personalizado que renderizas en tu propio código." --- Una pantalla de paywall muestra tus productos y permite al usuario comprarlos. Adapty te ofrece dos formas de crearla: - **Flows y paywalls**: Diseñas las pantallas en el [Flow & Paywall Builder](adapty-flow-builder), y el SDK de Adapty las renderiza de forma nativa en tu app. Crea un solo paywall o un onboarding completo que termine en uno. - **Paywall personalizado**: Construyes la pantalla en el código de tu propia app y guardas los productos que vende en Adapty como un [paywall personalizado](paywalls). Adapty gestiona la compra. ## Comparar flows y paywalls con paywalls personalizados \{#compare-flows--paywalls-with-custom-paywalls\} | | Flows y paywalls | Paywalls personalizados | |---|---|---| | **Quién construye la interfaz** | Tú, en un editor visual sin código. El SDK la renderiza de forma nativa, sin web views. | Tus desarrolladores, en el código de tu app. | | **Cómo empezar** | Una [plantilla](paywall-builder-templates), un lienzo en blanco o una [importación desde Figma](import-from-figma). | Tu pantalla existente y sistema de diseño. | | **Construir con IA en el dashboard** | El [Editor de IA](flow-ai-editor) rediseña, reescribe o reorganiza una pantalla a partir de una solicitud en lenguaje natural o una captura de pantalla de referencia. | No disponible. | | **Construir con tu herramienta de IA** | El [skill flow-generator](flow-generator-skill) construye un flow o paywall a partir de una descripción o captura de pantalla, y edita textos, locales y pantallas. El [skill flow-audit](flow-audit-skill) lo revisa en busca de problemas antes de publicarlo. | El [skill de integración del SDK](adapty-sdk-integration-skill) crea el paywall personalizado mediante la [CLI para desarrolladores](developer-cli-paywalls#adapty-paywalls-create) y escribe el código de compra en tu app. | | **Cambiar sin publicar una nueva versión** | Diseño, textos, pantallas, lógica y productos. | Los valores de [Remote Config](customize-paywall-with-remote-config) que lee tu código. Los cambios de diseño requieren una nueva versión. | | **Diseño diferente por audiencia** | Cualquier flow que construyas, asignado a una audiencia en el placement. | Solo los diseños integrados en la versión de la app que ejecuta el usuario. | | **Flujos multipantalla** | Pantallas de onboarding, [quizzes](onboarding-quizzes) y [ramificaciones](onboarding-navigation-branching) antes del paywall, con las respuestas del usuario [enviadas a tu app](ios-flow-input). | Lo que construya tu código. | | **Localización** | [Locales](add-paywall-locale-in-adapty-paywall-builder) por pantalla en el builder. | [Remote config](add-remote-config-locale) por locale, con traducción por IA en el dashboard. | | **Pruebas A/B** | Entre flows o paywalls en un placement de flow. | Entre paywalls personalizados en un placement de paywall. | | **Analíticas** | [Métricas de flow](flow-metrics), con ingresos atribuidos a cada pantalla. Las vistas se registran automáticamente. | [Métricas de paywall personalizado](paywall-metrics). Tu código reporta cada vista a Adapty. | | **Compras web** | Sí, con un [web paywall](web-paywall). | Sí, con un [web paywall](web-paywall). | | **Tamaño de la app** | Las pantallas e imágenes se cargan desde Adapty cuando tu app obtiene el flow, por lo que un nuevo paywall no aumenta el bundle de la app. | Cada pantalla, imagen y cadena de texto se incluye en el bundle de la app. | | **Tipo de placement** | [Placement de flow](create-placement). | [Placement de paywall](create-placement). | | **SDK de Adapty** | v4.0 o posterior. | Cualquier versión. | Una paywall del Flow & Paywall Builder y una paywall personalizada no pueden compartir una prueba A/B, ya que cada una necesita su propio tipo de placement. Para compararlas, [despliega gradualmente la paywall del builder](flow-gradual-rollout) en su propio placement. ## ¿Cuál elegir? \{#which-one-to-choose\} - **Elige flows y paywalls** cuando quieras diseñar, probar y cambiar la pantalla de compra sin escribir código de UI ni publicar una nueva versión de la app, o cuando el paywall forme parte de un onboarding. - **Elige un paywall personalizado** cuando quieras construir la pantalla con tu propio código, por ejemplo, para reutilizar los componentes de tu app. Cuando tu app muestra la pantalla, el paywall personalizado en Adapty es el conjunto fijo de productos que vende esa pantalla, como un plan semanal y uno mensual. Adapty registra la conversión y los ingresos de cada paywall, para que puedas comparar qué conjunto de productos vende mejor. Puedes mostrar diferentes paywalls a diferentes audiencias en un mismo placement y hacer pruebas A/B entre ellos. Para vender productos distintos, reemplaza el paywall en el placement desde el dashboard. No necesitas publicar una nueva versión de la app. Ambos se encuentran en **Flows & Paywalls** en el menú principal de Adapty: los flows y paywalls en la pestaña **Flow & Paywall builder**, y los paywalls personalizados en la pestaña **Custom Paywalls**. Los paywalls creados en el [antiguo Paywall Builder](adapty-paywall-builder) siguen siendo editables en la pestaña **Custom Paywalls & Legacy**, pero no puedes crear nuevos ahí. Para recrear un paywall creado para el SDK de Adapty 3.x como un flow en borrador mientras el original sigue activo, usa la acción **Move to new builder** — consulta [Convertir un paywall en un flow](convert-paywall-to-flow). Para la migración completa, incluyendo placements y la actualización del SDK, consulta [Migrar a flows](migrate-to-flows). <CustomDocCardList ids={['adapty-flow-builder', 'paywalls']} /> --- # File: adapty-flow-builder --- --- title: "Flow & Paywall Builder" description: "Constructor visual sin código para flows interactivos. Actualiza textos, diseño y precios sin publicar una nueva versión de la app." --- :::important Antes de que un flow llegue a tus usuarios, necesita un SDK más reciente y un placement propio: - **Adapty SDK v4.0 o posterior**: Las versiones anteriores del SDK no pueden renderizar flows. - **Su propio placement de flow**: Los flows y los paywalls son tipos de contenido diferentes, por lo que cada uno vive en su propio tipo de placement. Un flow no puede ir en un placement de paywall, y un nuevo placement de flow no puede reutilizar el ID de un placement de paywall — los IDs de placement son únicos en toda la app. Consulta [Crear placement](create-placement). ::: <CustomDocCardList ids={['builder-ui', 'flow-builder-recipes', 'builder-navigation-actions']} /> En Adapty, puedes crear flows en un editor visual sin código: el Flow & Paywall Builder. Para encontrar tus flows, ve a **Flows & Paywalls** en el menú principal de Adapty y abre la pestaña **Flow & Paywall builder**. Si prefieres construir la pantalla de compra con tu propio código, crea un [paywall personalizado](paywalls) en su lugar. - **Crea paywalls, onboardings y más**: Crea flows dinámicos de una o varias pantallas. - **Plantillas completas de flow**: Comienza el flow con una [plantilla diseñada profesionalmente](paywall-builder-templates). - **Tus diseños de Figma**: [Importa frames desde Figma](import-from-figma) y conviértelos en un flow editable. - **Diseño flexible**: Recrea pantallas complejas visualmente: sin código. - AI Editor **[AI Editor](flow-ai-editor)**: Describe un cambio en lenguaje natural o adjunta una captura de pantalla de referencia. Cambia el estilo, reescribe o reorganiza la pantalla sin editar los ajustes de cada elemento a mano. - **Renderizado nativo**: El SDK de Adapty renderiza los flows de forma nativa, sin web views, para garantizar una experiencia de usuario fluida. - **Actualiza sin redesplegar**: Cambia textos, diseño o lógica en cualquier momento. Las actualizaciones llegan a tus usuarios sin necesidad de publicar una nueva versión de la app. ## Qué puedes crear \{#what-you-can-build\} Puedes colocar flows en cualquier parte de tu app: al primer lanzamiento, antes de una función premium o tras una acción clave. Cada pantalla es totalmente personalizable, así que cada flow puede adaptarse a su momento. Estas son algunas de las cosas más populares que puedes crear: - **Onboarding de la app**: Muestra las funciones de tu app, haz preguntas y personaliza las funciones destacadas y los CTAs según las respuestas del cuestionario. Consulta [Crea un flow de onboarding personalizado](onboarding-flow-tutorial). - **Pantallas de paywall**: Coloca una restricción de acceso frente al contenido premium y presenta tus productos con listas de funciones, precios y un botón de compra. Consulta [Crea una pantalla de paywall básica](basic-paywall-screen). - **Planes por niveles en pestañas**: Muestra los niveles de suscripción uno al lado del otro en pestañas, cambiando la lista de funciones y el CTA cuando el usuario cambia entre ellos. Consulta [Crea un paywall con pestañas](paywall-with-tabs) y [Muestra diferentes funciones por producto](paywall-features-per-product). - **Listas de planes expandibles**: Destaca una oferta principal y muestra la lista completa de productos en una hoja inferior cuando el usuario quiera comparar. Consulta [Muestra todos los planes en una hoja inferior](show-plans-bottom-sheet). - **Encuestas y cuestionarios**: Recoge objetivos, preferencias o nivel de experiencia, y usa las respuestas para ramificar el flow, adaptar pantallas posteriores o [procesarlas en tu app](ios-flow-input). Consulta [Inputs y formularios](builder-inputs-and-forms). - **Prueba social**: Genera confianza con tarjetas de reseñas, valoraciones y testimonios justo antes de la decisión de compra. Consulta [Reseñas y testimonios](builder-reviews-and-testimonials). - **Pantallas de promociones y recuperación**: Lanza descuentos por tiempo limitado, ofertas de prueba gratuita o pantallas de recuperación para reactivar a usuarios inactivos. Estos son solo los patrones más habituales. Los flows se construyen con elementos flexibles y reutilizables, así que puedes combinarlos para crear prácticamente cualquier pantalla que necesite tu producto, y rediseñarla cuando tu producto evolucione. :::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-a-flow\} [Crea un nuevo flow](paywall-builder-templates) desde una plantilla de galería personalizada, un lienzo en blanco o [tus diseños de Figma](import-from-figma). Luego personalízalo con las siguientes funcionalidades: - **[Biblioteca de elementos](builder-elements)** — bloques predefinidos para texto, multimedia, botones, formularios y otro contenido. - **[Acciones](onboarding-actions)** — navega entre pantallas, abre URLs, ejecuta compras y más. - **[Variables](onboarding-variables)** — usa valores de variables en texto o para habilitar lógica de flow basada en eventos. - **[Custom tags](flow-custom-tags)** — pasa un valor desde tu app a un flow y muéstralo en un elemento de texto. - **[Navegación condicional](onboarding-navigation-branching)** — ramifica el flow según la entrada del usuario. - **[Cuestionarios e inputs](builder-inputs-and-forms)** — recopila la entrada del usuario, úsala en el flow o pásala a tu app. - AI Editor **[AI Editor](flow-ai-editor)** — describe un cambio en lenguaje natural y la IA lo aplica a la pantalla en la que estás. - **[Modo oscuro](paywall-dark-mode)** — aplica estilos a los elementos para que coincidan con el tema del dispositivo. - **[Localización](add-paywall-locale-in-adapty-paywall-builder)** — traduce manualmente, con IA o con un servicio de traducción externo. [Guarda y publica](builder-save-publish) tu borrador y, a continuación, vincúlalo a un [placement de flow](create-placement) — uno nuevo, con un ID de placement que no use ningún otro placement de la app. Un placement de flow puede servir distintos flows a distintas [audiencias](add-audience-paywall-ab-test). Una vez que el flow esté activo, consulta sus vistas, finalizaciones e ingresos en su página de [métricas](flow-metrics). La mayoría de los gráficos de la página de Analytics, desde el MRR hasta el churn, también se pueden [filtrar por un flow concreto](filter-flows#filter-analytics-by-flow) — o por pantalla de flow. Cada cambio que publiques es reversible, así que puedes experimentar libremente: [restaura una versión anterior](flow-version-history#restore-an-earlier-version) cuando lo necesites. ## Próximos pasos \{#next-steps\} <CustomDocCardList /> --- # File: paywall-builder-templates --- --- title: "Crear un flow" description: "Empieza un nuevo flow desde una plantilla de galería personalizada o desde una base mínima." --- Puedes crear un flow de las siguientes maneras: - **[Desde una plantilla](#use-a-template)**: Empieza con un flow completo de múltiples pantallas y edita cualquier elemento. - **[Desde un diseño de Figma](#import-a-design-from-figma)**: Selecciona frames en Figma, envíalos a Adapty y edita el resultado como un flow. - **[Con una herramienta de IA](flow-generator-skill)**: Carga la skill del generador de flows, describe el flow en lenguaje natural y la herramienta lo construye a través del CLI de Adapty. - **[Desde cero](#start-from-scratch)**: Empieza con una sola pantalla en blanco. - **[Desde un paywall existente](#migrate-a-paywall-from-the-legacy-paywall-builder)**: Recrea un paywall del Paywall Builder como un flow, con su diseño, texto, productos y localizaciones. :::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. Ve a [**Flows & Paywalls**](https://app.adapty.io/flows) en el menú principal de Adapty y abre la pestaña **Flow & Paywall builder**. 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. Cambia el nombre de cada nuevo flow o acabarás creando varios flows **Untitled** difíciles de distinguir entre sí. ::: ### 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). ## Importar un diseño desde Figma \{#import-a-design-from-figma\} :::link Artículo principal: [Importar un diseño desde Figma](import-from-figma) ::: Si ya tienes un mockup de paywall en Figma, puedes enviarlo a Adapty en lugar de reconstruir las pantallas a mano: 1. Instala el plugin de Adapty para Figma. 2. Selecciona los frames y activa el plugin. 3. Adapty recibe tu flow con su diseño, texto e imágenes. Los elementos interactivos deben configurarse manualmente. ## Migrar un paywall desde el Paywall Builder antiguo \{#migrate-a-paywall-from-the-legacy-paywall-builder\} :::link Artículo principal: [Convertir un paywall en un flow](convert-paywall-to-flow) ::: En lugar de crear un flow desde una plantilla, puedes convertir un paywall antiguo que ya tengas. Abre la página del paywall en el dashboard y haz clic en **Move to new builder**. Adapty recrea el paywall como un nuevo flow en borrador, conservando el diseño, el texto, los productos y las localizaciones. El paywall anterior sigue funcionando para permitir una transición fluida. ## 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 las plantillas \{#custom-fonts-in-templates\} :::link Artículo principal: [Fuentes personalizadas en el Flow & Paywall 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 tal como está diseñada 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 usarán una fuente del sistema como alternativa. Para cambiar una fuente sin afectar versiones anteriores, [copia el flow](copy-flows), cambia la fuente en la copia y restringe la copia a [versiones de la app que incluyan la fuente](segments). --- # File: builder-ui --- --- title: "Interfaz del Flow & Paywall Builder" description: "Descripción general de la interfaz y el espacio de trabajo del Flow & Paywall Builder." --- La interfaz principal del Flow & Paywall Builder incluye todas las herramientas necesarias para añadir elementos visuales, editar sus propiedades y modificar la lógica del flow de usuario. 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 superior) \{#project-controls-and-useful-shortcuts-top-toolbar\} * **Close** Close: Salir del builder y volver a la página de flows. * **App name** App: Identifica la aplicación a la que pertenece el flow. * **All flows** Flows: Abrir la lista de todos los flows de esta aplicación. * **Flow status**: El icono a la izquierda del nombre del flow indica el [estado del flow](builder-save-publish#flow-status) actual: - **Draft** Draft - **Publishing** (indicador giratorio) - **Failed** Failed - o **Live** Live. * **Rename the flow**: Haz clic en el nombre del flow para renombrarlo. Varios flows pueden tener el mismo nombre: [asigna un nombre único a cada flow nuevo](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. * **Version history**: [Ver o restaurar una versión anterior del flow](flow-version-history). * **Flow actions** Flow actions: Abrir el menú del flow: - **View metrics**: Abrir las [métricas](flow-metrics) del flow en una nueva pestaña. - **View in Analytics**: Abrir el gráfico de [Revenue](revenue), filtrado por el flow, en una nueva pestaña. - **Duplicate flow**: [Copiar el flow](copy-flows). - **Archive**: [Archivar el flow](archive-flows). **Ver métricas** y **Ver en Analytics** estarán disponibles una vez que publiques el flow. * **Guardar borrador / Publicar**: Haz clic en **Save draft** para guardar tu progreso sin publicar (⌘ / Ctrl+S). Abre el desplegable Open dropdown para acceder al botón [**Publish**](builder-save-publish). Solo puedes añadir tu 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 vista (barra de herramientas inferior) \{#view-controls-bottom-toolbar\} Las herramientas de la barra de herramientas 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. * Light theme **Light theme** y Dark theme **Dark theme**: [Previsualiza el flow en cada tema](paywall-dark-mode#preview-the-flow-in-dark-mode). Los botones solo aparecen si el flow [tiene un tema oscuro añadido](paywall-dark-mode#add-a-dark-theme). * **Locale**: Selecciona una configuración regional para previsualizar tu flow con contenido localizado. * **View options**: Activa o desactiva el bisel del dispositivo y las guías del área segura. ## Propiedades de pantalla y elemento (panel derecho) \{#screen-and-element-properties-right-panel\} ### Configuración y diseño 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 permite ajustar las propiedades de la [pantalla del flow](paywall-layout-and-products) activa, entre las que se incluyen: * Interacciones con la interfaz del sistema (por ejemplo, si la barra de estado es visible) * Reglas de diseño automático * Fondo (color, imagen o vídeo) * Tamaño del relleno * Comportamiento del 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 interactividad. #### 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 fuente, peso, tamaño, color, alineación, decoración y truncado. * **Spacing**: Define el margen y el relleno del elemento. * **Effects**: Añade sombras externas, sombras internas o desenfoque de capa. * **Animation**: Añade efectos animados (p. ej., Pulse) y configura su duración e intensidad. * **Appearance**: Ajusta la opacidad y la rotación. * **Layout**: Elige la dirección del layout (vertical u horizontal) y determina cómo se distribuyen los elementos hijos. #### 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 se compone de un **trigger** y una o más **actions**: * **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 seleccionar entre: * [Pantallas y capas](#screens-and-layers) * [Añadir elemento](#element-selection) * [Productos](#products) * [Estilos](#styles) * [Variables](#variables) * [Localización](#localization) * [Editor de IA](#ai-editor) ### 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 & Paywall Builder). Muestra cada pantalla como un árbol de capas. Cada elemento de una pantalla es una capa, y los contenedores tienen sus elementos secundarios 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 UI disponibles y sus variaciones. Haz clic en una entrada para añadirla 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. Puedes usar esta vista para [gestionar los productos y ofertas del flow](paywall-product-block#manage-flow-products). ### Estilos \{#styles\} :::info Más información: - [Estilos reutilizables](builder-reusable-styles) - [Contenido de texto](onboarding-text) - [Modo oscuro](paywall-dark-mode) ::: El botón **Styles** Styles abre el panel **Styles**. Crea un estilo para guardar configuraciones de texto o un relleno (como un color, un degradado o una imagen de fondo) y reutilízalo en elementos de varias pantallas. El panel tiene una pestaña **Text** para estilos de texto y una pestaña **Colors** para estilos de color. ### Variables :::link Artículo principal: [Variables](onboarding-variables) ::: El botón con 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 datos reales: [valores que tu app pasa para etiquetas personalizadas](flow-custom-tags), precios de productos, cadenas localizadas y más. Las variables se agrupan en dos pestañas: * **Personalizadas**: Variables que tú creas, de dos tipos: * **Variables regulares**: Sus valores cambian mediante acciones **Set variable** en el flow. * **[Etiquetas personalizadas](flow-custom-tags)**: Sus valores provienen de tu app, a través del SDK de Adapty. * **Elementos**: Valores determinados por la interacción del usuario, como respuestas a cuestionarios, estados de interruptores 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. * **Mostrar datos de tu app**: Muestra un valor de tu app, como el saldo de monedas de un jugador, con una [etiqueta personalizada](flow-custom-tags). * **Controlar la visibilidad**: Muestra u oculta elementos según condiciones (por ejemplo, mostrar una línea de tiempo de prueba gratuita solo a los usuarios que sean elegibles para ella). * **Interactuar con el usuario**: Accede a datos de campos de entrada del usuario, como formularios o cuestionarios. ### Localización \{#localization\} :::link Artículo principal: [Añadir idioma en Flow & Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) ::: El panel **Localizations** muestra el contenido localizable de tu flow en una tabla, organizada por pantalla, con una columna por cada idioma: * [Texto](add-paywall-locale-in-adapty-paywall-builder#text-content), incluido cada rama de [texto condicional](add-paywall-locale-in-adapty-paywall-builder#conditional-text) y los marcadores de posición de los campos de entrada. * El título y el mensaje de cada acción **Show alert**. * El [destino de cada acción **Open URL**](add-paywall-locale-in-adapty-paywall-builder#action-urls). * Imágenes y vídeos, con un [archivo por idioma](add-paywall-locale-in-adapty-paywall-builder#localize-images-and-videos). Desde este panel puedes: * Añade nuevas traducciones y edita los textos localizados directamente. * Traduce con IA. * Selecciona la traducción predeterminada para tu flow. * Controla el estado de las traducciones: cada fila se marca como **Done**, **Missing** o, en el caso de URLs de acción, **Default**. * Filtra por pantalla o estado, o busca por palabra clave. * Usa **Import** o **Export** para todo el flow o para una sola traducción, y traduce en bloque fuera de Adapty. ### Editor de IA \{#ai-editor\} :::link Artículo principal: [Editor de IA](flow-ai-editor) ::: El botón de destellos AI Editor abre el chat del Editor de IA. Describe un cambio en lenguaje natural y Adapty reescribirá, cambiará el estilo o reorganizará la pantalla que estás viendo. Adapty te indicará qué cambió y, con un clic, lo deshará. Para igualar un diseño existente, [adjunta una captura de pantalla](flow-ai-editor#work-with-images) en lugar de describirlo con palabras. --- # File: flow-ai-editor --- --- title: "Editor IA" description: "Describe un cambio con tus propias palabras y el Editor IA lo aplica a la pantalla de tu flow: restilos, reescrituras y reorganizaciones sin tocar una sola propiedad." --- El Editor IA es un panel de chat dentro del Flow & Paywall Builder. Describe un cambio con tus propias palabras y Adapty reescribe, restilos o reorganiza la pantalla que estás viendo. Adapty te indica qué ha cambiado y, con un solo clic, puedes revertirlo. Puedes ajustar el resultado a mano, seguir haciendo prompts o deshacer todos los cambios. ## Cómo funciona \{#how-it-works\} - **Abre el editor de IA**: Haz clic en el botón con el icono de destellos AI Editor en el panel izquierdo. - **Un chat por pantalla**: El encabezado del panel indica qué pantalla se está editando, y cada prompt se aplica solo a esa pantalla. Cambia de pantalla desde el desplegable y la vista previa se actualiza. - **Historial de prompts compartido**: Cualquier miembro del equipo que abra una pantalla del flow verá los prompts que enviaste — retoma el trabajo de un compañero sin repetirlo desde cero. - **Inspírate**: Abre la Prompts library **Prompts library** y elige una categoría de prompt: Visual, Content, Layout o Products. Las [ideas de prompts](#prompt-ideas) de esta página muestran lo que puede hacer el editor. - **Un prompt, un paso de deshacer**: La IA aplica todos los cambios de un prompt a la vez, y **Undo** los revierte todos juntos. - **El lienzo se bloquea mientras la IA trabaja**: No puedes seleccionar ni modificar elementos en la pantalla hasta que el prompt termine de procesarse. Siempre puedes interrumpir la generación. - **Ajusta prompts anteriores**: Para ajustar un prompt que ya enviaste, haz clic en **Retry** Retry. El editor del flow lo devuelve al campo de entrada. - **Nada se publica hasta que tú lo decidas**: Tus cambios llegan a los usuarios solo después de que [guardes y publiques](builder-save-publish) el flow. ## Trabajar con imágenes \{#work-with-images\} Adjunta imágenes Attach image e indica a la IA que las use como referencia visual, o colócalas en la pantalla. Un solo mensaje puede incluir hasta 5 imágenes: archivos JPEG, PNG y WebP, de hasta 5 MB cada uno. - **Construir una pantalla a partir de una captura de pantalla**: Adjunta una captura de pantalla de un paywall de referencia e indica `Build a screen like this`. La IA rediseña la pantalla abierta para que se parezca a ella. - **Colocar la imagen adjunta**: Adjunta la imagen e indica dónde va, por ejemplo `Use this as the hero image` o `Make this the background`. - **Depurar la pantalla**: Adjunta una captura de pantalla para ilustrar qué está fallando y descríbelo. ## Ideas de prompts \{#prompt-ideas\} Prueba estos prompts para ver cómo el Editor de IA puede ayudarte a iterar. Siempre puedes deshacer los cambios. ### Prueba una nueva dirección visual \{#test-a-new-visual-direction\} **Prompt:** `Give the paywall a dark, premium feel` La IA reescribe el texto, cambia el estilo de los elementos y aplica un nuevo tema de color, todo con un solo prompt. Prueba una nueva dirección visual para tu flow y decide si conservarla o deshacerla. ### Genera confianza con una sección de valoraciones y testimonios \{#build-trust-with-a-ratings-and-testimonials-section\} **Prompt:** `Add a social proof section with star rating and short testimonials` La IA añade un contenedor, una valoración de estrellas y tarjetas de testimonios encima del botón de compra — todos elementos nuevos, no un rediseño. Pide una sección que aún no esté en la pantalla y la IA la construye. ### Haz que una pantalla densa sea más fácil de escanear en un solo prompt \{#make-a-dense-screen-scannable-in-one-prompt\} **Prompt:** `Shorten all the text` La IA acorta todos los elementos de texto de la pantalla a la vez conservando el significado de cada uno. Recorta una pantalla entera en un solo prompt en lugar de editar los elementos uno a uno. ### Mantén el CTA en pantalla con un pie de página fijo \{#keep-the-cta-on-screen-with-a-sticky-footer\} **Prompt:** `Add a sticky footer with the main subscribe button` El AI crea un pie de página anclado en la parte inferior de la pantalla y mueve el botón de suscripción dentro de él. Nombra un patrón de diseño móvil y el AI lo construye por ti. ### Haz que el descuento sea irresistible con un precio tachado \{#make-a-discount-unmissable-with-a-strikethrough-price\} **Prompt:** `Show the original price crossed out on the discounted plan` La IA encuentra el plan con oferta, tacha su precio original y muestra el precio con descuento junto a él. El mismo resultado [hecho a mano](strikethrough-price) requiere varios pasos. ### Dirige a los usuarios hacia tu mejor plan con una sola frase \{#push-users-toward-your-best-plan-in-one-sentence\} **Prompt:** `Push users towards the yearly plan` Nombra el resultado y la IA elige los mecanismos. Preselecciona la tarjeta anual, añade un distintivo de **Best Value** y reescribe la etiqueta del plan para reforzar la oferta: tres cambios coordinados, aplicados todos juntos o ninguno. ### Haz que el CTA domine sin elegir un color ni un tamaño \{#make-the-cta-dominate-without-picking-a-color-or-size\} **Prompt:** `Make the subscribe button the most prominent element` La IA aumenta el contraste del botón, incrementa su tamaño y añade espacio alrededor — sin que especifiques un solo valor. Describe el resultado que quieres y deja que la IA decida los números. ### Reducir el precio anual a un coste diario \{#shrink-an-annual-price-to-a-daily-cost\} **Prompt:** `Break down the annual plan price to a daily cost` Adapty ya calcula el precio por día de cada suscripción, y la IA lo añade debajo del precio anual. También están disponibles los precios por semana, por mes y por año. ### Añadir una selección de productos con planes y precios reales \{#add-a-product-selection-with-real-plans-and-prices\} **Prompt:** `Add a products group with real prices` Si añadiste productos en el [panel **Products**](paywall-product-block#add-products-in-the-products-panel), la IA puede generar un grupo de productos con los productos asignados, sus nombres y las variables de producto correspondientes. Si la pantalla no tiene productos, la IA añade tarjetas de plan con precios de muestra y etiquetas. Para mostrar los precios de tu store, [selecciona un producto](paywall-product-block) en el desplegable de producto de cada tarjeta y luego pídele a la IA que conecte los precios. ### Dar estilo a una tarjeta de plan y replicarlo en el resto \{#style-one-plan-card-and-match-the-rest\} **Prompt:** `Apply the selected state of the first card to the other cards` Dale estilo manualmente al estado seleccionado de una tarjeta de producto y luego pide a la IA que aplique ese estilo a las demás tarjetas. La IA actualiza el estado seleccionado de cada una de las otras tarjetas, así no tienes que repetir la configuración una por una. ### Sigue ajustando sin empezar de cero \{#keep-refining-without-starting-over\} **Prompt:** `Softer background, and move the trial line closer to the button` El chat recuerda la pantalla y tu último prompt, así que no hace falta repetir nada en el siguiente mensaje. Ajusta hasta que la pantalla quede como quieres — cada prompt es su propio paso de deshacer. ## Qué no puede hacer el editor de IA \{#what-the-ai-editor-cant-do\} El Editor de IA rediseña, reescribe y reorganiza la pantalla a la que se le indica. Lo que no puede hacer: - **Editar varias pantallas a la vez**: Cambia la pantalla activa y vuelve a escribir el prompt. - **Generar o obtener imágenes**: La IA no puede crear imágenes ni descargarlas desde un enlace. Solo usa las imágenes que [adjuntes al chat](#work-with-images). Cuando reconstruye una pantalla a partir de una captura de pantalla, deja los espacios de imagen vacíos. El chat no acepta vídeos, así que [añádelos manualmente](custom-media). - **Crear variables personalizadas o lógica condicional**: La IA escribe en tu texto [variables de producto](onboarding-variables) integradas, como el precio por día. Las variables personalizadas y las bifurcaciones las configuras tú. - **Configurar navegación y enlaces**: Conecta las [acciones de navegación y enlace](onboarding-actions) manualmente. - **Elegir qué productos ofreces**: Añade productos en el panel [**Products**](paywall-product-block#add-products-in-the-products-panel) tú mismo. La IA solo genera las tarjetas de plan para los productos que aparecen allí. Adapty puede eliminar estos límites en versiones futuras. --- # File: import-from-figma --- --- title: "Importar un diseño desde Figma" description: "Envía frames de tu archivo de Figma a Adapty y conviértelos en un flow editable. El diseño, el texto y las imágenes se importan tal cual, para que empieces desde tu diseño y no desde una pantalla en blanco." --- El plugin de Adapty para Figma convierte los frames de tu archivo de diseño en un flow. Selecciona los frames en Figma, envíalos a Adapty y la importación crea un flow en borrador que puedes editar en el flow builder como cualquier otro. ## Cómo funciona la importación \{#how-the-import-works\} El plugin lee los frames de Figma seleccionados y los sube a Adapty, que los convierte en las pantallas de un nuevo flow en borrador. El plugin nunca modifica tu archivo de Figma, y nada sale de Figma hasta que hagas clic en **Export to Adapty**. El resultado se parece a tu diseño y funciona como un flow básico: los botones llevan a la siguiente pantalla, las opciones de los cuestionarios son seleccionables y el paywall está listo para que vincules tus productos. Consulta [Limitaciones de la importación](#import-limitations) para conocer los ajustes que puede que tengas que hacer manualmente. ## Antes de empezar \{#before-you-start\} Para importar un diseño, necesitas: - **Un archivo de diseño en Figma**: El plugin solo funciona en archivos de diseño. Los tableros de FigJam y las diapositivas de Figma no son compatibles. - **Acceso de edición al archivo**: El plugin no puede ejecutarse en un archivo en el que solo tienes permiso de visualización. Tus propios borradores funcionan bien. - **Una app en Adapty** con privilegios de edición de flows: La importación crea el flow dentro de una de tus apps. Asegúrate de poder editar los flows dentro de la app de Adapty de destino. ## 1. Instala el plugin \{#install-the-plugin\} Para instalar el plugin: 1. Abre la página del plugin [Import to Adapty](https://www.figma.com/community/plugin/1674065404672377144). 2. Haz clic en **Save**. El plugin estará disponible en el menú **Plugins** de Figma. Figma actualiza los plugins publicados automáticamente, así que siempre ejecutas la versión más reciente. ## 2. Envía tu diseño a Adapty \{#2-send-your-design-to-adapty\} Para enviar pantallas a Adapty: 1. En Figma, selecciona los frames que quieres importar. Selecciona varios a la vez para enviar una secuencia completa de pantallas. 2. Ve a **Plugins** > **Import to Adapty**. 3. Revisa la selección que muestra el plugin y haz clic en **Export to Adapty**. 4. Haz clic en **Open in Adapty**. El plugin muestra el nombre del elemento seleccionado y el número de pantallas resultantes. Confírmalo antes de continuar. Una vez finalizada la carga, el plugin muestra la pantalla **Done**. Para continuar: 1. Inicia sesión en Adapty. Si abres el enlace de importación en un navegador sin autenticar, la importación no funcionará. 2. Abre el enlace de importación en un navegador: - Haz clic en **Open in Adapty** para abrir la importación en tu navegador predeterminado. - Haz clic en **Copy link** para copiar el enlace al portapapeles e iniciar la importación en otro navegador o perfil. ### Cómo los frames se convierten en pantallas \{#how-frames-become-screens\} Lo que selecciones determina cuántas pantallas obtienes: - **Varios frames**: Cada frame se convierte en su propia pantalla. - **Una sección**: Cada frame dentro de la sección se convierte en una pantalla. - **Un único frame**: El frame completo se convierte en una sola pantalla, aunque contenga otros frames. Para importar esos frames internos como pantallas independientes, activa **Split into separate screens**. Los pantallas siguen el orden de los fotogramas en el panel **Layers** de Figma, de abajo a arriba, no su posición en el lienzo ni el orden en que los seleccionaste. La navegación sigue el mismo orden, así que organiza los fotogramas en el panel **Layers** antes de exportar, o reordena las pantallas en el builder después. Selecciona fotogramas individuales o una sección en lugar de una página completa. De lo contrario, importarás también todos los demás fotogramas de esa página. ### Configuración de la exportación \{#export-settings\} Para cambiar qué incluye la exportación, haz clic en **Customize export**: - **Export images**: Incluye recursos de imagen y foto. Activado por defecto. - **Image resolution**: Exporta las imágenes a 1×, 2× o 3×. Una resolución mayor aumenta la nitidez y el tamaño del archivo. 2× por defecto. - **Split into separate screens**: Importa cada fotograma dentro de tu selección como su propia pantalla. Desactivado por defecto. ## 3. Finaliza la importación en Adapty \{#3-finish-the-import-in-adapty\} **Open in Adapty** abre una pestaña del navegador en tu Adapty Dashboard. La conversión se ejecuta en los servidores de Adapty; la pestaña muestra el progreso y crea el flow al finalizar. 1. Selecciona la app a la que pertenece este flow. La conversión comienza en cuanto eliges. 2. Mantén la pestaña abierta hasta que termine la importación (puede tardar varios minutos). Si la cierras antes de tiempo, abre el mismo enlace de nuevo para reanudarla. Adapty convierte el diseño e informa del progreso de la importación. El flow terminado se abre en el flow builder como borrador, con el nombre del archivo de Figma del que procede y un nombre descriptivo para cada pantalla. Adapty te indica qué partes del diseño requieren una revisión. ## Limitaciones de la importación \{#import-limitations\} Una importación reproduce el aspecto de tu diseño y conecta los elementos básicos: navegación, grupos seleccionables, campos de entrada y acciones de compra. Estas partes de un diseño no se importan: - **Products**: La importación crea tarjetas de producto, añade acciones de compra e inserta marcadores de posición para las variables de producto. Debes vincular tus productos reales y reemplazar los marcadores tú mismo. Consulta [Configurar compras](paywall-product-block). - **Typefaces**: La importación no asigna ninguna fuente a tu texto. Para restaurar tu tipografía, [sube el archivo de fuente](using-custom-fonts-in-flow-builder) y selecciónala para el texto afectado. - **Videos**: Un vídeo se exporta como marcador de posición. [Añade el vídeo](custom-media) en el constructor después. - **Hidden layers**: Una capa oculta no se exporta. Una exportación incluye como máximo **50 pantallas** y **20 MB** de datos de diseño. La resolución de las imágenes es el principal factor que determina ese tamaño, por lo que una exportación grande se reduce al ajustar **Image resolution** a 1×. Cada exportación genera un único flow. Para importar el mismo diseño una segunda vez, vuelve a exportarlo desde Figma. Un enlace de importación es válido durante una hora tras la exportación. Completa la importación de una sola vez o empieza de nuevo. ## Resolución de problemas \{#troubleshooting\} ### Exportar errores \{#export-errors\} | Mensaje | Qué hacer | |---|---| | Too many screens to export | Divide tu selección en lotes más pequeños de 50 pantallas o menos. | | Export data is too large | Reduce la **Image resolution**, o disminuye el número de capas en tus pantallas. | | This frame is too large to export | Exporta una sección más pequeña del diseño. | | Upload didn't complete | Tu diseño cambió mientras se subía. Vuelve a exportarlo. | | This import link has expired | Un enlace de importación es válido durante una hora. Vuelve a exportar el diseño desde Figma. | | This design was already imported | Esa exportación ya generó un flow. Vuelve a exportar el diseño desde Figma. | ### Después de una importación \{#after-an-import\} | Lo que ves | Causa y solución | |---|---| | Una pantalla se ve lavada, como si hubiera una neblina sobre ella | Se importó una imagen de fondo como un elemento normal encima del contenido. Muévela detrás del resto de la pantalla. | | El panel **Products** está vacío y los precios muestran marcadores de posición `{{figma_import_…}}` | La importación no vincula productos. Añade los tuyos en el panel **Products**, selecciónalos en cada tarjeta de producto y acción de compra, y reemplaza los marcadores de posición. | | Las pantallas están desordenadas o los botones abren la pantalla incorrecta | Las pantallas y la navegación siguen el orden de los fotogramas en el panel **Layers** de Figma. Reordena las pantallas en el builder y revisa la acción **Navigate to screen** de cada botón. | | Falta una imagen y solo queda su fondo | La capa era un vídeo, estaba oculta o se construyó con una operación de forma. Reemplaza la imagen en el builder. | | Dos opciones de un grupo de selección única aparecen seleccionadas a la vez | El estado no seleccionado de la opción predeterminada no tiene el estilo correcto. Cambia el estilo manualmente. | | Una cuenta atrás muestra un tiempo fijo y nunca cuenta | La cuenta atrás se importó como texto. Reemplázala por un [temporizador de cuenta atrás](flow-timer). | | El control de fuente muestra **Select font** | El tipo de letra no está disponible en el builder. [Sube el archivo de fuente](using-custom-fonts-in-flow-builder) y selecciónalo. | ## Pasos siguientes \{#next-steps\} El flow importado es un borrador, no disponible para los usuarios. Mejora el flow en el builder y sigue los pasos a continuación para publicarlo: 1. [Guarda y publica el flow](builder-save-publish). 2. [Previsualiza el flow en un dispositivo real](paywall-device-compatibility-preview). 3. Crea un [placement de flow](create-placement) y añade el flow a él. 4. Recupera ese placement en tu app con Adapty SDK v4.0 o posterior. Las versiones anteriores del SDK no pueden renderizar flows. --- # File: flow-agent-skills --- --- title: "Skills de agente para flows" description: "Instala las skills de agente de Adapty para que tu propia herramienta de codificación con IA pueda crear, auditar y servir flows de Adapty desde el terminal." --- Las skills de agente permiten que tu propia herramienta de IA trabaje con flows desde el terminal, a través del [Adapty CLI](developer-cli-quickstart) en lugar del editor visual. Describes lo que quieres en lenguaje natural; la herramienta lee tu cuenta, hace el trabajo y te muestra el resultado antes de guardar nada. Tres skills, y cada una se encarga de una parte: una crea y modifica flows, otra comprueba si un flow está listo para publicarse, y la tercera migra una app que todavía usa paywalls a placements con flows. Las tres vienen en el mismo plugin, así que con una sola instalación tienes todo. <CustomDocCardList /> Usa **flow-generator** cuando sepas qué quieres cambiar: un nuevo idioma, textos reescritos, una pantalla añadida o eliminada, o una rama reconectada. Edita la configuración y guarda un borrador para tu aprobación; nunca publica. Usa **flow-audit** cuando quieras saber si un flow está terminado. Lee el flow y tu catálogo de productos, informa sobre lo que fallaría para un usuario real y pasa las correcciones a flow-generator. Usa **migrate-placements** cuando tu app todavía funcione con paywalls. No toca el contenido del flow — lee tus placements de paywall y crea un placement de flow para cada uno, luego te devuelve los IDs de placement y la llamada al SDK para realizar el cambio. Ninguno de ellos reemplaza al [Editor de IA](flow-ai-editor), que funciona dentro del propio Flow & Paywall Builder. Las skills del agente se ejecutan en tu terminal, en la misma sesión que el código de tu app. --- # File: flow-builder-recipes --- --- title: "Recetas de flow habituales" description: "Guías paso a paso para crear plantillas de pantallas habituales en el Flow & Paywall Builder." --- Esta sección explica cómo crear las plantillas de pantalla más comunes en el Flow & Paywall Builder — elemento a elemento, desde las opciones de diseño hasta las interacciones. Algunas guías continúan en el código de tu app, donde un flow devuelve su resultado. Cada guía es independiente y utiliza los elementos estándar del Flow & Paywall Builder. <CustomDocCardList /> Sigue este vídeo de inicio rápido para crear un flow personalizado básico: :::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: reuse-flow-elements --- --- title: "Reutilizar elementos entre flows" description: "Guarda una selección de plan, una línea de tiempo o toda una pantalla en un archivo y añádela a cualquier otro flow — en la misma app o en una diferente." --- La mayoría de los paywalls de una app comparten los mismos bloques: la selección de plan, la línea de tiempo de prueba, el bloque de comparación. Reconstruirlos a mano en cada flow es lento y las copias acaban desincronizadas. La [skill flow-generator](flow-generator-skill) puede guardar cualquier parte de un flow en un archivo y añadirla a otro flow —en la misma app o en una diferente— resolviendo los colores, fuentes, iconos y enlaces de productos de los que depende esa parte. El archivo guardado es JSON plano. Puedes guardarlo junto al código de tu app, incluirlo en el repositorio o enviárselo a un compañero de equipo. :::important Esto forma parte de la skill flow-generator, por lo que no hay nada adicional que instalar — sigue los [pasos de instalación del flow-generator](flow-generator-skill#install-the-skill) y ya lo tienes. Guarda los cambios en los flows de tu dashboard, así que ejecútalo primero sobre un borrador y revisa el resultado en el Flow & Paywall Builder antes de publicar. ::: ## Para qué sirve \{#what-its-for\} - **Una biblioteca de componentes que tu equipo comparte de verdad.** Guarda la selección de planes una vez. Cada nuevo flow parte de algo real, no de una aproximación. - **Corrígelo una vez, aplícalo en todas partes.** Corrige la cronología del período de prueba en un flow, guárdalo y añádelo a los demás. - **Lleva un bloque probado a una nueva app.** Un bloque que convierte en una app es un buen punto de partida en la siguiente. La skill vuelve a vincular sus productos al catálogo de la app de destino. - **Mantén un sistema de diseño coherente.** Guarda solo los colores y estilos de texto —sin ningún elemento— y aplícalos a otro flow. ## Cómo usarlo \{#how-to-use-it\} Pídelo en lenguaje natural. La skill se encarga del resto. ### Guardar un fragmento de un flow \{#save-a-piece-of-a-flow\} ``` Save the plan selection from my Premium paywall so I can reuse it. ``` La skill escribe un archivo e imprime su ruta completa, para que siempre sepas qué archivo confirmar o enviar. También resume lo que contiene: cuántos elementos, y cuántos colores, estilos de texto, grupos y productos incluye. Hace una sola pregunta: si lo vas a reutilizar dentro de la misma app o en cualquier sitio. El archivo es completo en cualquier caso, así que una respuesta equivocada no tiene ningún coste. Puedes guardar un elemento y todo lo que contiene, una pantalla completa, un componente reutilizable o un tema por sí solo. ### Agrégalo a otro flow \{#add-it-to-another-flow\} ``` Add the two-plan selection to my onboarding paywall, above the CTA. ``` Antes de escribir nada, la función te muestra un plan: exactamente dónde quedará el bloque, qué añade, qué colores y estilos de texto toma del destino en lugar de los suyos propios, y qué IDs tiene que renombrar para evitar conflictos. También imprime el contenido que está importando, para que puedas detectar textos que pertenecían al flow anterior. Anything it can't resolve by itself — a product missing from this app's catalog, a variable with no source here — is listed separately, as something for you to finish rather than something it guessed at. Read the plan, then say go. The skill writes the draft and renders the screen so you can see the result. ## Ejemplos \{#examples\} Cada fila muestra un mismo elemento guardado añadido a tres flows distintos. No se reconstruyó nada: el mismo archivo generó los tres, y el sistema lo adaptó a cada destino. **Una selección de dos planes**, en una app de inversión, una app de idiomas y una app de música. Las tarjetas mantienen su estructura y adoptan el color de acento de cada app. **Una línea de tiempo de prueba.** La estructura se reutiliza; el texto es específico de cada producto, porque tres pasos numerados no significan nada si describen la aplicación equivocada. **Una comparación de prueba.** Esta no tiene productos ni variables, así que se puede mover entre aplicaciones sin cambios. ## Cómo funciona \{#how-it-works\} Un elemento guardado raramente existe de forma independiente. Hace referencia a colores, estilos de texto, fuentes, iconos, productos y variables que viven en otro lugar del flow. La skill los clasifica en dos tipos. **Lo que puede incluir.** Los colores, estilos de texto, fuentes e iconos viajan dentro del archivo, junto con sus definiciones. En el destino: | El destino | Qué ocurre | | :--- | :--- | | Ya tiene el mismo significado con ese nombre | Lo reutiliza | | Usa ese nombre para algo distinto | **Adopta el valor del destino**, y te lo indica | | No lo conoce | Añade la definición del archivo | Adoptar suele ser lo que quieres: una tarjeta movida a otra aplicación debería verse como esa aplicación. Ambos valores aparecen en el plan, por lo que puedes cambiar la elección. **Lo que no puede trasladar.** Un ID de producto, la fuente de una variable, el destino de una acción **Navigate to screen** — estos apuntan a algo específico de un flow o una app. La skill los reasigna donde puede, y lista lo que no puede bajo `NEEDS YOU` en lugar de adivinar. Antes de guardar el borrador, la skill vuelve a comprobar el flow completo: cada referencia se resuelve, cada idioma está relleno, ningún ID colisiona y el resultado sigue pasando la verificación de publicación. Todo lo que quede se muestra, nunca se corrige en silencio. ## Qué comprobar \{#what-to-check\} - **El copy hace referencia a un producto.** Un bloque que funciona bien en el flow de origen puede ser incorrecto en el flow de destino. El plan muestra el texto que va a importar, bajo `WILL SAY`, para que puedas detectarlo antes de escribir. - **Los precios son variables, no texto.** Si el destino no tiene el producto al que hace referencia un precio, el precio se renderiza vacío. El plan así lo indica. - **El tamaño puede depender del contexto.** Un elemento dimensionado para llenar su contenedor original puede estirarse en el nuevo. La vista previa renderizada es lo que permite detectarlo. ## Limitaciones \{#limitations\} - **Los productos no se transfieren entre apps automáticamente.** La skill vincula tus IDs de productos de la store para reasignarlos; si un producto no existe en la app de destino, elimina la vinculación y te pide que asignes uno. - **Los vídeos y las fuentes personalizadas deben añadirse manualmente.** Ninguno puede subirse desde la terminal. - **La vista previa muestra una sola pantalla, en un solo estado.** Un cambio de idioma o un estado seleccionado no aparecen en ella — consulta el plan para esos casos. --- # File: flow-analytics-ids --- --- title: "Nombra los IDs que ve tu analítica" description: "Asigna IDs legibles a las pantallas, campos y opciones de quiz de tu flow para que los eventos que envían a Amplitude o Mixpanel aparezcan como nombres en lugar de códigos." --- El SDK de Adapty reporta cada visualización de pantalla y cada respuesta a tu app. Cada evento incluye el ID de la pantalla o elemento del que proviene, y esos IDs son los que muestra tu propio sistema de analíticas. Por defecto, el ID de una pantalla tiene un aspecto como `scr_oAPBHPa7`, así que un funnel en Amplitude o Mixpanel aparece como una columna de códigos, y alguien tiene que mantener a mano un mapeo de código a nombre. Puedes nombrar estos IDs tú mismo. Los inputs y grupos seleccionables se nombran en el Flow Builder. Los IDs de pantalla se configuran mediante la [skill flow-generator](flow-generator-skill), que renombra una pantalla en todos los lugares del flow donde se hace referencia a ella. :::important Renombrar pantallas forma parte de la skill del generador de flows, por lo que no necesitas instalar nada extra: sigue los [pasos de instalación del generador de flows](flow-generator-skill#install-the-skill) y ya lo tendrás. Guarda los cambios en los flows de tu dashboard, así que ejecútalo primero sobre un borrador y revisa el resultado en el Flow Builder antes de publicarlo. ::: ## Para qué sirve \{#what-its-for\} - **Un funnel que tu equipo entiende**: La secuencia `welcome → signup → paywall` cuenta lo que ocurrió. Tres códigos generados, no. - **Un vocabulario común en todas tus herramientas**: Cada evento que tu app reenvía nombra la pantalla de la misma forma, así un funnel, una cohorte y un filtro del dashboard coinciden sin necesidad de una tabla de referencia. - **Respuestas que puedes consultar**: Una opción de quiz llamada `beginner` es un valor que un analista puede filtrar directamente. Un ID generado hay que decodificarlo primero. ## Qué IDs ve tu sistema de análisis \{#which-ids-your-analytics-sees\} Dos eventos llevan IDs. [`flow_screen_showed`](ios-flow-screen-views) se dispara cuando un usuario abre una pantalla, y [`flow_user_input`](ios-flow-input) se dispara cuando un usuario rellena un campo, elige una respuesta en un cuestionario o activa un interruptor. Cada guía documenta todos los parámetros de su evento. Esta tabla cubre únicamente los parámetros que identifican una pantalla o un elemento. | Parámetro del evento | Qué contiene | Dónde se configura | | :--- | :--- | :--- | | `instanceId` | El ID de la pantalla | La skill del generador de flows | | `element_id` | El **Element ID** de un campo de entrada, o el **Group ID** de un grupo seleccionable | Flow Builder | | `item_ids` | Los **Element IDs** de las opciones seleccionadas | Flow Builder | ## Nombres para inputs y opciones de quiz \{#name-inputs-and-quiz-options\} Los inputs y los grupos seleccionables tienen campos de ID en el builder. El **Element ID** de un input está en **Input settings**, y es el mismo ID que usas para referenciar su valor en otras partes del flow — consulta [Inputs y formularios](builder-inputs-and-forms). El **Group ID** de un grupo está en **Screen settings > Selectable groups**, y cada opción dentro de él tiene su propio **Element ID**. Dale a cada uno un nombre que tenga sentido por sí solo, porque esa es la cadena que verás en tus analytics. `birthday` y `beginner` no necesitan explicación. `el_024F` sí. Establece el ID cuando añades el elemento, no después. Las condiciones y variables de texto apuntan a un **ID de elemento**, y cambiar ese ID no las actualiza — dejan de resolverse, sin ningún error. Para renombrarlo en un flow que ya funciona, actualiza cada condición y variable que lo utilice. Consulta [Las condiciones y variables se rompen tras cambiar el ID de un elemento](flow-common-issues#conditions-and-variables-break-after-an-element-id-change). La skill de generación de flows los nombra automáticamente en cualquier flow que construye. La [skill de auditoría de flows](flow-audit-skill) reporta los que faltan. ## Cambiar nombre a las pantallas \{#rename-screens\} El Flow Builder no tiene ningún campo para el ID de una pantalla, así que pídele a la skill: ``` Give the screens in my onboarding flow readable IDs. ``` La skill propone un nombre para cada pantalla y luego lo renombra en todos los lugares donde el flow hace referencia a ella: la propia pantalla, cada acción de navegación que lleva a ella y los productos vinculados a ella. Los renombra todos a la vez, porque una pantalla renombrada en un sitio pero no en otro es un flow que no se podrá publicar. Te indica qué nombres cambió y cuántas referencias se movieron, y no ejecutará un cambio de nombre que colisione con una pantalla que ya existe. Renombrar un ID de pantalla es seguro en un flow que ya funciona, algo que no ocurre con un ID de elemento: la herramienta mueve todas las referencias junto con él. ## Qué verificar \{#what-to-check\} - **Renombrar antes de publicar el flow**: Los eventos ya recopilados conservan el ID anterior. Un embudo que abarque el cambio mostrará dos pantallas a medio llenar, y revertir el nombre no las fusiona. Las métricas de flow de Adapty se dividen de la misma forma, porque agrupa las filas de pantalla por ID: la pantalla renombrada obtiene una segunda fila con la misma etiqueta que la primera. - **Cada opción de un grupo necesita su propio Element ID**: Si a una opción le falta uno, si dos opciones comparten uno, o si el **Group ID** se reutiliza en otra pantalla, todo el grupo deja de reportar datos, no solo esa opción. El builder no señala este problema, así que revisa el grupo cada vez que añadas una opción. - **Los IDs llegan a tus herramientas de análisis exactamente como los escribiste**: Elige un estilo, como `plan_selection` o `planSelection`, y úsalo en todo el flow. Los estilos mezclados son más difíciles de consultar que los códigos generados. ## Limitaciones \{#limitations\} - **Los IDs de pantalla solo se renombran mediante el skill**: No hay ningún campo para ellos en el Flow Builder. - **Las métricas de flow de Adapty no mostrarán estos nombres**: [Las métricas de flow](flow-metrics) etiquetan una pantalla como `<position>. <caption>` a partir de la versión publicada del flow, por lo que aparece como `9. Paywall A` independientemente del ID de la pantalla. Los IDs aquí solo llegan a tus propias analíticas. - **Renombrar no migra el historial**: Los eventos anteriores conservan el ID antiguo. - **Algunos elementos no envían eventos de entrada**: Los campos de contraseña, las selecciones de producto y los cambios de pestaña nunca se reportan. Nombrar uno afecta al flow, no a tus analíticas. --- # File: basic-paywall-screen --- --- title: "Crear una pantalla de paywall básica" description: "Guía paso a paso para crear una pantalla de paywall estándar en el Flow & Paywall 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 \{#1-set-up-reusable-styles\} :::link Artículo principal: [Estilos reutilizables](builder-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 configurar los estilos: 1. En el panel izquierdo, abre el panel **Styles** Styles. 2. En la pestaña **Text**, haz clic en una entrada de estilo predeterminado, cambia sus ajustes y haz clic en **Save**. Añade nuevos estilos solo si los predeterminados no cubren tus necesidades. 3. [Crea un estilo de color](builder-reusable-styles#create-a-color-style) para cada color 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 un preset de diseño. Vertical List es el más habitual. 2. Selecciona cada tarjeta de producto en el canvas y elige un producto en el desplegable de la pestaña **Design**. Los productos ya añadidos a esta pantalla en el panel **Products** aparecen primero. El resto del catálogo se muestra a continuación, en **Other products**. 3. Para cambiar la selección por defecto, selecciona la tarjeta que quieras y activa **Set as default** 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 con 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 muestra la lista completa de productos." --- Esta plantilla muestra primero una oferta destacada con un enlace discreto a la lista completa de planes. Al pulsar **Show all plans**, se despliega un bottom sheet con el resto de 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 distintas 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 tiene su propia lista de características, lista de productos y botón de compra. Al pulsar 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 funciones según el producto" description: "Muestra una lista de funciones diferente 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 inicial con sentido cuando se cargue por primera vez. Para más información 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 un preset de diseño. 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 quieras que esté seleccionada por defecto — por ejemplo, Pro+ — y activa **Set as default** 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 el usuario pulsa cerrar" description: "Intercepta el primer toque en cerrar para mostrar una oferta de última oportunidad antes de que el usuario abandone 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 de última oportunidad. Cuando el usuario descarta la oferta, el botón de cerrar funciona con normalidad y cierra el flow. La lógica requiere 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 Variables 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 cierre del paywall — el que aparece en la pantalla, no el botón para descartar el overlay. 2. Abre la pestaña **Interactions**, haz clic en la acción preconfigurada **Close Flow** 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**. - **Mostrar elemento**: Establece el destino en la superposición 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 se vea de un vistazo." --- Un precio tachado junto al precio real hace que el descuento se vea de un vistazo. Esta receta transforma una tarjeta de producto anual para mostrar lo que costaría la suscripción al mes en 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: respond-to-purchase-outcomes --- --- title: "Responder a los resultados de una compra" description: "Reacciona a cada resultado de compra en el código de tu app y abre un segundo flow cuando la compra tiene éxito." --- Cuando finaliza una compra, el SDK de Adapty notifica a tu app con el resultado. Nada cambia en pantalla hasta que tu app reaccione. Tu app puede cerrar el paywall, dejarlo abierto para un nuevo intento, o abrir un segundo flow que confirme la compra. El builder no puede ramificar en función del resultado de una compra: ningún [trigger](onboarding-actions#trigger-types) se dispara cuando finaliza una compra, y ninguna [variable](onboarding-variables) contiene su resultado. Por eso, la transición debe gestionarse en tu código, donde llega el resultado. El trabajo se divide en tres partes: - Crear el flow posterior a la compra y asignarlo a su propio placement. - En el código de tu app, ramificar según el resultado de la compra. - En caso de éxito, cerrar el paywall y mostrar el flow posterior a la compra. ## Antes de empezar \{#before-you-start\} - Los flows requieren Adapty SDK v4.0 o posterior. - Crea una pantalla de paywall; por ejemplo, sigue la receta [Crear una pantalla de paywall básica](basic-paywall-screen). ## 1. Construye el flow posterior a la compra \{#build-the-post-purchase-flow\} [Crea el flow](paywall-builder-templates). Se ejecuta después de la compra, por lo que no necesita productos. Para devolver al usuario a la interfaz propia de tu app, añade un botón con la acción **Close Flow** en la pantalla final. Usa una [**Acción personalizada**](onboarding-actions#custom-actions) cuando el flow tenga más de una salida. Crea varios botones, cada uno con su propio ID de acción, para que tu app pueda distinguir **Set up now** de **Set up later**. ## 2. Añade el flow post-compra a su propio placement \{#2-add-the-post-purchase-flow-to-its-own-placement\} Para obtener el flow post-compra desde tu app, asígnalo a su propio placement. El placement de tu flow no puede servir también el flow post-compra: un placement sirve un solo flow por audiencia. 1. [Publica el flow](builder-save-publish). 2. [Crea un placement de flow](create-placement). Anota el **Placement ID**: tu app lo usa para obtener el flow. 3. Asigna el flow post-compra al nuevo placement. ## 3. Ramifica según el resultado de la compra \{#3-branch-on-the-purchase-result\} Tu app recibe el resultado de la compra a través de dos callbacks: - **Completion**: La compra llegó a una conclusión: tuvo éxito, el usuario la canceló, o el pago está pendiente de aprobación (la confirmación de un padre, por ejemplo). Lee el tipo del resultado para distinguir los tres casos. En caso de éxito, el resultado incluye el `AdaptyProfile` actualizado. - **Failure**: Un error detuvo la compra, y el callback incluye un `AdaptyError`: restricciones de pago, productos inválidos, fallos de red o errores de verificación de transacciones. Nombres y firmas de los callbacks: [iOS](ios-handling-events) | [Android](android-handling-events) | [React Native](react-native-handling-events-1) | [Flutter](flutter-handling-events) | [Unity](unity-handling-events) | [Kotlin Multiplatform](kmp-handling-events) | [Capacitor](capacitor-handling-events) Actúa según el resultado: - Si tiene éxito, cierra el paywall y muestra el flow posterior a la compra. - Si hay una cancelación, un pago pendiente o un error, deja el paywall abierto. :::note Las restauraciones se reportan a través de su propio callback, que cubre a los usuarios que reinstalan la app o cambian de dispositivo. Confirma que el perfil devuelto tiene tu [nivel de acceso](access-level) activo, luego cierra y presenta un flow de la misma manera. Apunta ese callback a un flow distinto: una pantalla que agradece al usuario por una compra no encaja en una restauración. ::: ## 4. Cargar y presentar el flow post-compra \{#4-load-and-present-the-post-purchase-flow\} Para abrir el flow post-compra sin demoras, recupéralo junto con el paywall, no dentro del handler. Solicita también su configuración de UI — esa llamada es la que inicia el almacenamiento en caché de las imágenes del flow en segundo plano. En [iOS](ios-optimize-paywall-fetching#preload-placements) y [Android](android-optimize-paywall-fetching#preload-placements), `preloadFlows` (SDK 4.1+) precarga varios placements en una sola llamada. Solo almacena en caché el JSON del placement, así que combínalo con la política de fetch `ReturnCacheDataElseLoad` y solicita igualmente la configuración de UI con antelación. Obtener flows y paywalls: [iOS](get-pb-paywalls) | [Android](android-get-pb-paywalls) | [React Native](react-native-get-pb-paywalls) | [Flutter](flutter-get-pb-paywalls) | [Unity](unity-get-pb-paywalls) | [Kotlin Multiplatform](kmp-get-pb-paywalls) | [Capacitor](capacitor-get-pb-paywalls) Mostrar flows y paywalls: [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) --- # File: paywall-with-trial-toggle --- --- title: "Crear un paywall con un interruptor de prueba" description: "Cambia un paywall entre productos con y sin prueba gratuita, y mantén el producto seleccionado sincronizado con el interruptor." --- Un interruptor de prueba te permite cambiar un paywall entre productos con prueba gratuita y productos sin ella. Pero el interruptor en sí es solo un conmutador: activado o desactivado. Tú escribes las reglas que muestran las tarjetas correctas y las reglas que cambian qué producto está seleccionado. Esta receta te muestra cómo configurar estas reglas y crear un paywall completamente funcional con toggle. ## Antes de empezar \{#before-you-start\} - Construye una pantalla de paywall — por ejemplo, sigue la receta [Crear una pantalla de paywall básica](basic-paywall-screen). - [Crea tus productos](create-product) y [añádeles la oferta de prueba](create-offer). :::important Si quieres que la prueba sea opcional (lo cual es necesario para usar el toggle de prueba), no crees una oferta introductoria. En iOS, las ofertas introductorias se aplican **automáticamente** a todos los usuarios elegibles. No puedes desactivarlas. Usa una oferta promocional en su lugar. Consulta [Cómo funciona Adapty con las ofertas](create-offer#how-adapty-works-with-offers). ::: ## 1. Añadir las tarjetas de producto \{#1-add-the-product-cards\} 1. Haz clic en el menú Añadir elemento **+** > **Paywall & Commerce** > **Products** y elige un preset. 2. Selecciona una tarjeta — en el canvas o en el panel **Layers**. Asigna un producto a la tarjeta y deja el desplegable de oferta en **No offer**. 3. Repite el proceso para cada plan que vendas — una tarjeta por plan, sin el periodo de prueba. 4. Duplica cada tarjeta. En cada copia, selecciona la oferta de prueba en el desplegable de oferta. Cada plan tendrá ahora dos tarjetas que apuntan al mismo producto: una con la oferta y otra sin ella. ## 2. Añadir el interruptor de prueba \{#2-add-the-trial-toggle\} 1. Haz clic en **+** > **Paywall & Commerce** > **Toggle** > **Trial toggle**. 2. Asegúrate de que el interruptor pertenece a un grupo seleccionable del tipo correcto. Haz clic en un área vacía del canvas para abrir **Screen settings**. En **Selectable groups**, abre `trial_toggle`, establece **Group Type** en **Toggle** y haz clic en **Save**. El diálogo muestra la variable que usaremos para detectar si el interruptor está activado: `trial_toggle.isActive`. 3. Edita la etiqueta del toggle. Deja el toggle desactivado por defecto. Un toggle de prueba preseleccionado puede provocar el rechazo en la App Store por considerarse un dark pattern — consulta [Toggles y checkboxes](builder-toggles). ## 3. Muestra las tarjetas correctas según el estado del toggle \{#show-the-right-cards-for-each-toggle-state\} En la pestaña **Design** de cada tarjeta, en **Visibility**, selecciona **Conditional** y define su condición: | Tarjeta | Condición | |---|---| | Con el trial | `trial_toggle.isActive` **Equals** **True** | | Sin el trial | `trial_toggle.isActive` **Equals** **False** | ## 4. Selecciona una tarjeta visible cuando cambia el toggle \{#4-select-a-visible-card-when-the-toggle-changes\} Las tarjetas ahora cambian con el toggle, pero el producto seleccionado no. Una tarjeta que se vuelve invisible sigue seleccionada, y la acción **Purchase** sigue apuntando a ella — así que un usuario que activa el período de prueba podría seguir siendo cobrado. Añade una **Conditional Action** al toggle para que reinicie la selección a una tarjeta visible en cada toque. 1. En el panel **Layers**, selecciona el contenedor **Trial toggle**. 2. Abre la pestaña **Interactions**, añade el disparador **On tap** y una **Conditional Action**: | Bloque | Qué configurar | |---|---| | **if** | `trial_toggle.isActive` **Equals** **True** | | **then** | **Select Product**, apuntando a una tarjeta con el trial | | **else** | **Select Product**, apuntando al mismo producto sin el trial | ## 5. Actualiza la etiqueta del botón \{#update-the-button-label\} La llamada a la acción debe mostrar un texto diferente cuando el período de prueba está activo. 1. Selecciona el elemento de texto del botón. 2. En la pestaña **Design**, dentro de **Content**, selecciona **Conditional** y haz clic en **Edit conditions**. 3. Rellena los bloques: | Bloque | Qué configurar | |---|---| | **if** | `trial_toggle.isActive` **Equals** **True** | | **then** | La etiqueta para el período de prueba, por ejemplo `Start your free trial` | | **else** | La etiqueta estándar, por ejemplo `Purchase` | Para más información sobre el texto condicional, consulta [Añadir texto condicional](onboarding-text#add-conditional-text). --- # File: onboarding-flow-tutorial --- --- title: "Crear un flow de onboarding personalizado" description: "Recorre el proceso completo de crear un flow de onboarding de múltiples pantallas — pantallas, contenido, navegación y ramificación condicional — con un ejemplo práctico." --- Un flow multipantalla en el Flow & Paywall 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 recopilada en una pantalla anterior. Este tutorial recorre el proceso completo — 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 la personalización. - Un **cuestionario de opción única** cuya respuesta determina qué pantalla ve el usuario a continuación. - **Dos rutas ramificadas** con contenido adaptado para cada segmento de audiencia. - Un **paywall** como pantalla final. El mismo patrón se aplica a cualquier flow que personalice 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: ## 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\} :::link Artículo principal: [Estilos reutilizables](builder-reusable-styles) ::: Los estilos reutilizables te permiten aplicar tipografía y colores coherentes en todas las pantallas con un solo clic. Los estilos de color pueden tener un valor para modo claro y otro para modo oscuro, de modo que el flow puede admitir ambos temas. Para configurar los estilos: 1. En el panel izquierdo, abre el panel **Styles** Styles. 2. En la pestaña **Colors**, [añade un tema oscuro](paywall-dark-mode#add-a-dark-theme). 3. [Crea un estilo de color](builder-reusable-styles#create-a-color-style) para cada color que vayas a reutilizar. 4. Para cada estilo de color, establece el valor **Light** y el valor **Dark**. 5. En la pestaña **Text**, haz clic en una entrada de estilo predeterminado, cambia su configuración y haz clic en **Save**. Para añadir ajustes preestablecidos personalizados, [crea un estilo de texto](builder-reusable-styles#text-styles). ## 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 elige **Vertical List**. Asigna a cada tarjeta un producto desde el menú desplegable en la pestaña **Design**. 4. Haz clic en la tarjeta del producto predeterminado y activa **Set as default** 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: 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 & Paywall 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 del producto. Los elementos de cada pantalla se organizan 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** | | Vista previa de las animaciones configuradas en esta pantalla | | **Copy** | ⌘C / Ctrl+C | Copiar la pantalla al portapapeles | | **Paste here** | ⌘V / Ctrl+V | Pegar una pantalla copiada previamente | | **Duplicate** | ⌘D / Ctrl+D | Crear una copia de la pantalla y añadirla al flow | | **Rename** | | Cambiar el nombre visible de la pantalla | | **Delete** | ⌘⌫ / Ctrl+Del | Eliminar la pantalla del flow | :::tip El portapapeles funciona también para elementos individuales, no solo para pantallas completas. Además, funciona entre flows distintos: consulta [Copiar pantallas entre flows](copy-flows#copy-screens-between-flows) para saber qué deja atrás una operación de pegado. ::: Cuando copias o duplicas una pantalla, las acciones de la copia apuntan a los elementos copiados, no a los originales. Esto se aplica a los destinos definidos en las acciones **Show**, **Hide** y **Select product**, incluidas las anidadas dentro de [acciones condicionales](onboarding-actions#conditional-actions). Las acciones que hacen referencia a elementos externos a la copia mantienen sus destinos originales; revísalos después de pegar. :::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](flow-common-issues#a-flow-wont-preview-or-publish). ::: ## 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 secundarios. Estas propiedades están disponibles en cualquier elemento contenedor. * **Free**: Los elementos hijos se superponen entre sí. Al seleccionar **Free**, cada hijo se convierte en posición [**Absolute**](manage-paywall-ui-elements#position-in-a-free-container). * **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 vertical y horizontal, también puedes configurar el espaciado y la alineación. * **Alignment**: Posición del elemento a lo largo del eje transversal. * **Gap**: Espacio entre elementos adyacentes. * **Distribution**: Distribución del espacio entre los elementos secundarios y a su alrededor. #### 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) en un color sólido, degradado, imagen o vídeo. El fondo ocupa todo el viewport del dispositivo, incluidas las áreas detrás del notch y las barras del sistema, incluso cuando **Safe area** está activado. Para mostrar un fondo diferente en modo oscuro, [aplica un estilo de color con un valor oscuro](paywall-head-picture#show-a-different-background-in-dark-mode) al relleno. #### Reproducir el vídeo de fondo en bucle \{#loop-background-video\} Activa el botón **Loop** para reproducir el vídeo de fondo en un bucle continuo. #### 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 la pantalla un ID de medio personalizado para referenciarlo en tu SDK. ### Espaciado de pantalla \{#screen-spacing\} Ajusta el relleno de pantalla para cada lado (superior, derecho, inferior, izquierdo). Una pantalla configurada con la [dirección](#screen-layout) **Free** no tiene sección **Spacing**, ya que cada elemento hijo se posiciona de forma individual. ### Desplazamiento \{#scroll\} Controla el comportamiento del desbordamiento. Activa **Vertical scroll** para permitir que el contenido de la pantalla se desplace cuando supera la altura del viewport. :::link Usa un [Footer](builder-containers#footer) para anclar contenido en la parte inferior de la pantalla cuando el contenido desborda. El footer necesita que **Vertical scroll** esté activado para renderizarse. ::: ### Grupos seleccionables \{#selectable-groups\} :::link Artículo principal: [Elementos y grupos seleccionables](flow-selectable-elements) ::: La sección **Selectable groups** lista todos los grupos seleccionables de la pantalla actual — de [cuestionarios](onboarding-quizzes), [productos](paywall-product-block), [pestañas](builder-tabs), [toggles y casillas](builder-toggles), o cualquier [elemento seleccionable personalizado](flow-selectable-elements#make-an-element-selectable). Haz clic en un grupo para renombrarlo, cambiar su tipo, ver las variables que expone o eliminarlo. ## 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 de á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 previamente 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: "Diseño y posicionamiento" description: "Organiza elementos en pantalla con diseño, modo de posición, tamaño y espaciado." --- El Flow & Paywall Builder crea layouts responsivos. No arrastras los elementos a coordenadas exactas; en cambio, los anidas dentro de **contenedores** que organizan automáticamente sus elementos hijos. El contenedor define la dirección (vertical u horizontal), la alineación y el espaciado de los elementos. Los elementos individuales pueden ajustar su tamaño y márgenes, o bien —cuando sea necesario— salir del flujo con posicionamiento absoluto o fijo. :::link Para propiedades visuales como rellenos, bordes y efectos, consulta [Estilos y apariencia](builder-styling). ::: <AIPromo title="Reorganiza sin arrastrar nada."> Pídele al editor de IA una insignia de "Más popular", un pie de página fijo o toda una nueva sección de prueba social — él crea los contenedores por ti. </AIPromo> <Tabs groupId="video"> <TabItem value="align" label="Alineación y posicionamiento"> </TabItem> <TabItem value="layout" label="Diseño, tamaño y espaciado"> </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 * Free **Free**: Sin diseño automático. Los elementos secundarios se superponen entre sí en lugar de alinearse. Al seleccionar **Free**, cada elemento secundario se convierte en posición [**Absolute**](#position-in-a-free-container), y su [orden de apilamiento](#stacking-order) determina qué cubre qué * Vertical **Vertical**: Los elementos secundarios se apilan de arriba a abajo, como filas en una columna * Horizontal **Horizontal**: Los elementos secundarios se organizan de izquierda a derecha, como elementos 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 superior de la lista aparece en la parte superior de la pantalla. En un contenedor horizontal, el elemento superior aparece a la izquierda. Arrastra los elementos en el panel Layers para reordenarlos, o utiliza las acciones de capa **Move up** y **Move down** en [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 define el [orden de apilamiento](#stacking-order) del elemento respecto al resto de elementos posicionados 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 elemento padre, establece el anclaje horizontal en **Left**, luego añade un desplazamiento **Right** de 0. El elemento se fija a ambos bordes y su ancho cambia a [**Auto**](#sizing). ::: ### 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 el posicionamiento fijo para los elementos que deben flotar sobre el contenido al hacer scroll en lugar de reservar espacio: botones de cierre o restauración flotantes, banners superiores persistentes, controles para volver al inicio y barras de navegación. Para un área de acción dedicada en la parte inferior, usa un [Footer](builder-containers#footer). Para superponer contenido sobre una imagen hero, usa una [Sliding sheet](builder-containers#sliding-sheet). ### Posición en un contenedor Free \{#position-in-a-free-container\} Un contenedor Free no ordena sus elementos secundarios. Por eso, debes establecer manualmente la posición de cada uno. La posición **Relative** no está disponible. Los elementos secundarios siguen las siguientes reglas: - Si el elemento secundario también es un contenedor **Free**, solo puede tener una posición [absoluta](#absolute). Adapty oculta su selector **Position**. Para que vuelva a aparecer, establece la [**Direction**](#direction) de ese elemento secundario en **Vertical** o **Horizontal**. - El resto de elementos secundarios pueden tener la posición [absoluta](#absolute) o [fija](#fixed). ### 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 de cualquiera de los dos para seleccionar el modo de tamaño. | Modo | Tamaño del elemento | Disponible | | --- | --- | --- | | **Fill** | Se expande para ocupar el espacio disponible en su contenedor padre. El valor mostrado se calcula automáticamente | Solo elementos relativos | | **Hug** | Se contrae para ajustarse a su contenido. El valor mostrado se calcula automáticamente | En todos los contextos excepto un [elemento Free dentro de un contenedor Free](#position-in-a-free-container) | | **Fixed** | Coincide con el valor en píxeles que escribes, independientemente del tamaño del padre o del contenido | En todos los contextos | | **Auto** | Expande el contenido entre dos desplazamientos | Solo elementos [absolutos o fijos](#position), una vez que se han definido tanto **Left** como **Right**, o tanto **Top** como **Bottom**. Adapty lo aplica automáticamente — no puedes seleccionarlo | Aplicar la dirección **Free** al elemento padre convierte el tamaño **Fill** de ese elemento en **Fixed**. Si el elemento también usa la dirección **Free**, su tamaño **Hug** pasa a ser **Fixed** igualmente. Las imágenes tienen sus propias proporciones, por lo que la altura que les asignes determina si se recortan. Consulta [Tamaño de imagen](custom-media#image-sizing). ## Espaciado \{#spacing\} Establece los valores de espaciado de forma independiente para cada lado del elemento. | Control | Qué hace | Disponible | | --- | --- | --- | | **Margin** | Añade espacio entre el elemento y sus vecinos. Un valor grande nunca empuja el elemento fuera de su contenedor | Cualquier elemento con posicionamiento relativo. Las pantallas no tienen margen | | **Padding** | Añade espacio entre el borde del elemento y su contenido. Un ancho o alto **Hug** crece para incluirlo | Contenedores, pantallas y otros elementos con contenido hijo. Excepciones: hijos directos de contenedores **Free** y las propias pantallas **Free**. | Seleccionar la dirección **Free** [dirección](#direction) borra el relleno que tuviera el contenedor. ## Orden de apilamiento \{#stacking-order\} Los elementos relativos nunca se superponen entre sí: cada contenedor distribuye sus hijos en secuencia. La superposición solo ocurre cuando un elemento rompe el flujo normal con posicionamiento **Absolute** o **Fixed**. Un elemento **Absolute** o **Fixed** se renderiza por encima de sus hermanos relativos, independientemente del orden de capas. Los elementos relativos no tienen campo **Z-index**, porque nunca se superponen. Cuando más de un elemento **Absolute** o **Fixed** ocupa el mismo espacio, su orden de apilamiento se determina por dos factores, en orden de importancia: - **Z-index**. Valor predeterminado: 0. El elemento con el valor más alto aparece encima, independientemente del orden de capas. - **Orden de capas**. Si dos elementos comparten el mismo z-index, el elemento que aparece más abajo en la lista de capas se muestra encima. --- # File: builder-styling --- --- title: "Estilos y apariencia" description: "Configura la apariencia visual de los elementos: relleno, bordes, efectos, tipografía, estados y estilos de proyecto." --- La pestaña **Design** del panel derecho controla el aspecto visual de cada elemento. Las propiedades disponibles dependen del tipo de elemento, pero la mayoría comparte opciones de estilo comunes. :::link Para tamaño, espaciado y posicionamiento, consulta [Diseño y posicionamiento](manage-paywall-ui-elements). ::: <AIPromo title="Olvídate de la pestaña Design por completo."> Dile al Editor de IA que "le dé al paywall un aspecto oscuro y premium" y rediseñará todos los elementos a la vez: rellenos, esquinas, tipografía y espaciado. </AIPromo> ## 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 eliminar temporalmente un elemento del flow sin borrarlo. ## Relleno \{#fill\} La sección **Fill** establece el fondo de un elemento. Para rellenar toda la pantalla, configura el [fondo de pantalla](paywall-head-picture) en su lugar. Un relleno se compone de una o más capas. Cada capa puede ser de uno de los siguientes tipos: - **Color sólido** Solid color: Usa el selector de color o introduce un valor hexadecimal. Ajusta la **opacidad** para que el fondo sea semitransparente. - **Degradado** Gradient: Dos o más puntos de color. Arrastra los puntos para ajustar la transición y establece el ángulo con el control deslizante **Direction**. Para cambiar un punto, edita su fila en **Stops**. Para añadir un punto, haz clic en la barra de degradado o en Add gradient stop junto a **Stops**. El control deslizante en la fila de la capa cambia la opacidad de todos los puntos a la vez. - **Imagen** Image o **Vídeo** Video: Una [imagen o vídeo](custom-media) como fondo. Para gestionar las capas: - Para añadir una capa, haz clic en Plus junto a **Fill**. - Para cambiar el tipo de capa, haz clic en su muestra y selecciona un tipo. - Para cambiar el orden de las capas, arrastra sus filas. - Para eliminar una capa, haz clic en Close en su fila. La capa superior cubre las que están debajo, por lo que una capa semitransparente sobre una imagen [le aplica un tinte](paywall-head-picture#tint-a-background). Los campos de entrada y los selectores de fecha/hora solo muestran la capa inferior. Para reutilizar una capa en otros elementos o crear una versión oscura, [guárdala como estilo de color](builder-reusable-styles#create-a-color-style). Para reutilizar una imagen junto con su tinte, [crea un estilo único de varias capas](builder-reusable-styles#multi-layer-styles). ## 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 o introduce un valor hexadecimal. Para aplicar un [estilo de color](builder-reusable-styles#color-styles), selecciónalo desde la pestaña **Styles**. Un borde ignora las [capas de imagen y vídeo](builder-reusable-styles#multi-layer-styles) de un estilo. - **Width**: El grosor del borde en píxeles. :::tip Para aplicar un degradado a un borde, aplica un [estilo de color](builder-reusable-styles#color-styles) con una capa de degradado en su **Color**. ::: ## 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 de 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 * **Layer blur**: Desenfoca el elemento y sus elementos secundarios Puedes combinar varios efectos en el mismo elemento. Activa o desactiva su visibilidad Show para deshabilitar temporalmente un efecto. ## 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 fuentes Font select para abrir el selector de fuentes. Tiene dos pestañas: * **Styles**: Muestra los [estilos de texto](builder-reusable-styles#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 consistente, sube una [fuente personalizada](using-custom-fonts-in-flow-builder). Para guardar la configuración tipográfica actual como un nuevo [estilo de texto](builder-reusable-styles#text-styles), haz clic en Create text style junto a las pestañas. El elemento usará entonces el nuevo estilo. ### Tamaño y grosor \{#size-and-weight\} :::warning **Negrita** y **Cursiva** no tienen efecto en una [fuente personalizada](using-custom-fonts-in-flow-builder) a menos que su familia incluya una variante en negrita o cursiva. [Sube la variante correcta](using-custom-fonts-in-flow-builder#manage-a-custom-font) tú mismo. ::: * **Weight**: Selecciona un grosor de fuente en el desplegable * **Size**: Selecciona un tamaño en el desplegable o escribe un valor personalizado :::note El tamaño del texto se renderiza de forma diferente según la plataforma: Android escala el texto del flow con la configuración de tamaño de fuente del sistema, mientras que iOS renderiza exactamente el tamaño que hayas definido. Prueba tu layout en Android con un tamaño de fuente del sistema aumentado — el texto que encaja en iOS puede desbordar o salirse allí. ::: ### Color \{#color\} Haz clic en la muestra de color e introduce un valor hexadecimal o usa la paleta. Para aplicar un [estilo de color](builder-reusable-styles#color-styles) en su lugar, selecciónalo desde la pestaña **Styles**. El texto solo muestra las [capas de color sólido](builder-reusable-styles#multi-layer-styles) de un estilo. ### 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, interruptores y casillas de verificación) 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 el relleno, el color del borde y el color del 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\} :::link Artículo principal: [Estilos reutilizables](builder-reusable-styles) ::: Para mantener la coherencia de propiedades de texto y color en todo un flow, guárdalas como estilos reutilizables. Al editar un estilo, todos los elementos y pantallas que lo usen se actualizarán automáticamente. --- # File: builder-reusable-styles --- --- title: "Estilos reutilizables" description: "Guarda configuraciones de texto y color como estilos reutilizables, aplícalos a cualquier elemento y actualiza todos los elementos que los usen a la vez." --- Crea un estilo para almacenar configuraciones de texto o un relleno (como un color, un degradado o una imagen de fondo) y reutilízalo en elementos de varias pantallas. Cuando editas un estilo, el cambio se aplica a todos los elementos y pantallas que lo usan. Cada flow tiene su propio conjunto de estilos. Abre el panel **Styles** Styles para crear, explorar y editar los estilos de tu flow: - **Text styles**: ajustes tipográficos (fuente, peso, tamaño). - **Color styles**: rellenos como colores, degradados e imágenes. Un mismo estilo de color puede incluir versiones separadas para modo claro y modo oscuro. ## Estilos de texto \{#text-styles\} :::link Artículo principal: [Contenido de texto](onboarding-text) ::: Los estilos de texto almacenan un conjunto de configuraciones tipográficas: familia de fuente, grosor, tamaño, altura de línea y espaciado entre letras. Cada plantilla de flow incluye ajustes predeterminados, y puedes crear estilos personalizados. Para crear un estilo de texto: 1. Abre el panel **Styles** Styles. 2. En el encabezado del panel, haz clic en Create style **Create style**. 3. Establece **Type** en **Text**, introduce un nombre y configura los ajustes tipográficos. 4. Haz clic en **Create**. Para guardar la configuración tipográfica actual de un elemento de texto como un nuevo estilo de texto, haz clic en Create text style junto a las pestañas del selector de fuente. El elemento usará entonces el nuevo estilo. 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**. Para editar un estilo de texto, haz clic en su entrada en el panel **Styles**, cambia su configuración y haz clic en **Save**. ## Estilos de color \{#color-styles\} Un estilo de color es un [relleno](builder-styling#fill) guardado: un color, un degradado, una imagen, un vídeo o una combinación de ellos. Si usas un [tema oscuro](#dark-mode), puede tener versiones específicas para cada tema. El número que aparece en la entrada del estilo indica cuántos elementos y pantallas lo usan. ### Crear un estilo de color \{#create-a-color-style\} Para crear un estilo de color: 1. Abre el panel **Styles** Styles. 2. En la cabecera del panel, haz clic en Create style **Create style**. 3. Establece **Type** en **Color** e introduce un nombre. 4. Configura el estilo. Puedes cambiar el color o el [tipo de relleno](#edit-a-color-style) de su capa, y añadir [más capas](#multi-layer-styles) encima. 5. Haz clic en **Save**. Para convertir el valor actual de una capa de relleno o un color en un estilo, haz clic en su muestra, luego haz clic en el icono Create color style junto a las pestañas **Custom** y **Styles**, y haz clic en **Save**. La capa de relleno o el color ahora usa el estilo. ### Editar un estilo de color \{#edit-a-color-style\} Para editar un estilo de color: 1. Abre el panel **Styles** Styles. 2. Selecciona la pestaña **Colors**. 3. Haz clic en una entrada de estilo. 4. Cada versión puede tener varias [capas](#multi-layer-styles). Haz clic en la muestra para configurar la capa según su tipo de relleno: - [Color sólido](#solid-colors) - [Degradado](#gradients) - [Imagen o vídeo](#images-and-videos) #### Colores sólidos \{#solid-colors\} 1. Establece el color: introduce un valor hexadecimal o haz clic en la muestra y elige uno. 2. Ajusta la opacidad con el deslizador. 3. Haz clic en **Save**. #### Degradados \{#gradients\} 1. Selecciona el tipo de relleno Gradient **Gradient**. 2. Configura las paradas, los colores de las paradas y la **Direction**. 3. Haz clic en **Save**. :::tip Para facilitar la lectura de los elementos de texto sobre una imagen de fondo, [oscurece el borde de la imagen](paywall-head-picture#tint-a-background) que hay detrás de ellos. Añade una [capa](#multi-layer-styles) de degradado sobre la imagen. Haz que el degradado vaya desde un punto de parada opaco en ese borde hasta un punto de parada transparente en el otro extremo. ::: #### Imágenes y vídeos \{#images-and-videos\} Para usar una imagen o video como relleno, por ejemplo como fondo de una pantalla o un contenedor, configura una capa de imagen o video en cada versión del estilo. Para un elemento de imagen o video, [añade un archivo oscuro](paywall-dark-mode#show-a-different-image-or-video-in-dark-mode) al elemento en su lugar. Para configurar una capa de imagen o video: 1. Selecciona el tipo de relleno Image **Image** o Video **Video**. 2. Sube el archivo fuente para el [locale](custom-media#localize-media) seleccionado en la barra de herramientas inferior. Puedes hacer clic en **Replace** si ya hay un archivo subido. 3. Haz clic en **Save**. :::tip Para añadir un tinte a una imagen o vídeo, agrega una [capa](#multi-layer-styles) de color semitransparente encima. ::: ### Estilos multicapa \{#multi-layer-styles\} Cada versión de un estilo de color puede tener varias capas. Puedes usar estilos multicapa para crear efectos visuales más complejos, como imágenes con tinte. Para añadir una capa, haz clic en la entrada de estilo. Luego, en la sección **Properties**, **Light** o **Dark**, haz clic en el botón Plus. La nueva capa aparece encima de las capas existentes. Para cambiar el orden de las capas, arrastra sus filas. Configura la nueva capa y haz clic en **Save**. No todas las capas aparecen en todos los lugares donde aplicas el estilo. Algunas capas solo son visibles si aplicas el estilo a ciertas propiedades o elementos: - **Relleno (inputs y pickers)**: Solo la capa inferior. El resto de capas se ignoran. - **Relleno (todos los demás elementos)**: Todas las capas. - **Borde (inputs y pickers)**: Solo un color sólido o degradado. Las capas de imagen y vídeo se ignoran. - **Borde (todos los demás elementos)**: Colores sólidos y degradados. Las capas de imagen y vídeo se ignoran. - **Color de texto**: Solo capas de color sólido. Las capas de degradado, imagen y vídeo se ignoran. Si una versión no tiene ninguna capa de color sólido, el texto no tiene color en ese modo y es invisible. - **Icono, sombra y otros colores**: No admite estilos de varias capas. Solo puedes aplicar un estilo de capa única con un color sólido. ### Aplicar un estilo de color \{#apply-a-color-style\} Para aplicar un estilo de color a un elemento, haz clic en la muestra de su capa de relleno, color de texto, color de borde o color de icono. Reemplaza el valor estático por el estilo haciendo clic en la pestaña **Styles** y seleccionando la entrada del estilo. Si ningún estilo tiene el valor que necesitas, [guarda el valor actual como estilo](#create-a-color-style). Para aplicar un estilo a muchos elementos de una pantalla, pídelo al [AI Editor](flow-ai-editor), por ejemplo: `Apply the Card background style to all cards`. Para configurar estilos y aplicarlos en todo el flow, usa el [skill flow-generator](flow-generator-skill). No puedes aplicar un estilo de color a un elemento de imagen o vídeo, ya que estos elementos no tienen relleno. Para cambiar el archivo del elemento en modo oscuro, [añade un archivo oscuro](paywall-dark-mode#show-a-different-image-or-video-in-dark-mode) al elemento. ## Gestionar estilos \{#manage-styles\} En el panel **Styles** Styles: - Para renombrar un estilo, haz clic en su entrada, cambia el campo **Name** y haz clic en **Save**. - Para duplicar un estilo, haz clic derecho en su entrada y selecciona **Duplicate**. - Para eliminar un estilo, haz clic en su entrada y luego en **Delete style**. Si el estilo está en uso, selecciona el estilo que lo reemplazará. - Para cambiar el orden de los estilos, arrastra sus entradas hacia arriba o hacia abajo en la lista. - Para copiar estilos a otro flow, selecciona sus entradas (Shift+clic para seleccionar varios) y pulsa ⌘C / Ctrl+C. Abre la misma pestaña (**Text** o **Colors**) en el otro flow y pulsa ⌘V / Ctrl+V. ## Modo oscuro \{#dark-mode\} :::link Artículo principal: [Modo oscuro](paywall-dark-mode) ::: Un estilo de color puede tener un valor independiente para el modo oscuro. Cuando el dispositivo del usuario está configurado en modo oscuro, el flow utiliza ese valor en su lugar. Para usar valores oscuros, [añade un tema oscuro](paywall-dark-mode#add-a-dark-theme) al flow. Luego [cambia el valor oscuro](paywall-dark-mode#configure-the-dark-theme) de cada estilo que deba ser diferente en modo oscuro. --- # File: paywall-product-block --- --- title: "Configurar compras" description: "Añade productos a una pantalla, conecta un botón de compra y gestiona los productos del flow en el Flow & Paywall Builder." --- Tu flow puede mostrar los productos disponibles y activar compras in-app: 1. Añade los productos de la store al Adapty Dashboard. [Conecta productos de la store existentes](create-product#create-product-and-connect-existing-store-products) o [crea nuevos en Adapty y publícalos en las stores](create-product#create-product-and-push-to-store). 2. Selecciona productos desde tu Dashboard y añádelos a una pantalla del flow. Las tarjetas, variables y acciones de esa pantalla podrán usarlos. 3. (Opcional) Configura un grupo de tarjetas de producto para presentar varias opciones de compra. 4. Añade una acción **Purchase** a un botón y configúrala. El destino de la acción puede ser un producto específico o el producto seleccionado por el usuario. 5. Más adelante, usa el panel **Products** para añadir, cambiar o eliminar productos en el flow. Los usuarios verán el cambio cuando publiques el flow, sin necesidad de actualizar la app. <AIPromo title="Reformula tu precio en una sola frase."> Pide al Editor de IA que desglose el plan anual en coste diario, tache el precio anterior o marque el mejor valor. Rediseña las tarjetas de producto en esta pantalla, nunca tu catálogo ni tus precios. </AIPromo> ## Añadir productos a una pantalla \{#add-products-to-a-screen\} Cada pantalla tiene su propia lista de productos. Una tarjeta, una variable o una acción solo puede usar un producto de esa lista. El panel **Products** muestra la lista de cada pantalla del flow. Puedes añadir productos desde el panel **Products**, o seleccionarlos al configurar un elemento. Ambas opciones añaden el producto a la lista de la pantalla. ### Añadir productos en el panel de Productos \{#add-products-in-the-products-panel\} El panel añade un producto a la lista de la pantalla. Cualquier elemento de la pantalla puede usarlo: una tarjeta de producto, una variable de texto o una acción. Una variable de texto, por ejemplo, puede mostrar el precio de un producto que no tiene tarjeta. Para añadir un producto a una pantalla: 1. En el panel lateral izquierdo, abre el panel **Products** y haz clic en **+**. 2. **Screen** muestra la pantalla actual. Para añadir el producto a una pantalla distinta, selecciónala aquí. 3. En **Product**, selecciona un producto de tu Dashboard. Un producto anual con un [plan de compromiso de 12 meses](#sell-a-yearly-product-with-monthly-payments) aparece dos veces: etiquetado como **Upfront** y **Monthly**. 4. (Opcional) En **Offer**, selecciona la oferta con la que vender el producto. Consulta [Añadir oferta a un flow](create-offer#add-offer-to-flow). 5. (Opcional, solo **Monthly**) En **Fallback offer**, selecciona la oferta inicial para los usuarios que no puedan comprar el plan de compromiso. 6. Haz clic en **Add product**. El panel **Products** muestra los productos que añadiste, agrupados por pantalla. La oferta que selecciones aquí quedará preseleccionada en todas las tarjetas que utilicen ese producto más adelante. Nada cambia en el flow hasta que asignes el producto a un elemento o lo uses en una acción. ### Añadir productos al configurar un elemento \{#add-products-while-configuring-an-element\} Cuando seleccionas un producto en un elemento, el builder lo añade a la lista de la pantalla. Esto ocurre cuando: - Asignas un producto a una tarjeta de producto. - Insertas una variable de producto en un elemento de texto. - Estableces el **Product** de una acción **Purchase**. Cada desplegable muestra primero los productos ya añadidos a esta pantalla, seguidos del resto de tu catálogo. Un producto seleccionado con una oferta diferente genera su propia entrada: el panel y los desplegables muestran una entrada por par producto/oferta. Para configurar los propios elementos, continúa con [Añadir tarjetas de producto](#add-product-cards) y [Activar una compra](#trigger-a-purchase). ### Vender un producto anual con pagos mensuales \{#sell-a-yearly-product-with-monthly-payments\} Un producto anual de App Store con un [plan de compromiso de 12 meses](set-up-12-month-commitment) puede venderse bajo dos planes de facturación: **Upfront**, donde el usuario paga el precio anual de una sola vez, y **Monthly**, donde el usuario paga cada mes durante 12 meses. Cada plan es una entrada independiente en la lista de la pantalla, y los selectores etiquetan la entrada mensual como **· Monthly plan**. Para ofrecer ambos planes en una misma pantalla, añade el producto dos veces. Cada entrada solo muestra las ofertas de su propio plan. Para la entrada **Monthly**, selecciona también una **Fallback offer**: los usuarios que no puedan comprar el plan de compromiso comprarán el plan por adelantado con esta oferta. Esto ocurre en dispositivos Apple con versiones anteriores a iOS 26.4, en países donde no ofreces el plan y en apps con un SDK anterior a v4.3. Sin una oferta de respaldo, estos usuarios compran el plan por adelantado sin ninguna oferta. :::warning Las aplicaciones Android no reciben la entrada **Monthly**. Si el flow también se ejecuta en Android, añade también la entrada **Upfront** a la pantalla. ::: Para mostrar el pago mensual y el total anual que Apple exige, utiliza las [variables del plan de compromiso](onboarding-variables#12-month-commitment-plan). ## Añadir tarjetas de producto \{#add-product-cards\} Para ofrecer a los usuarios una selección de productos, añade un elemento **Products**. Viene como un conjunto de tres tarjetas de producto en un diseño predefinido. Las tarjetas forman un [grupo de productos](flow-selectable-elements): el usuario selecciona una y, a continuación, una acción **Purchase** puede comprar el producto seleccionado. El conjunto es la única forma de obtener tarjetas de producto o un grupo de productos. Para terminar con más o menos tarjetas, duplica o elimina algunas después de añadir el conjunto. ### Añade un grupo de productos \{#add-a-product-group\} 1. Abre el panel **Add element**. 2. Selecciona la categoría **Products**. 3. Elige un diseño predefinido: **Vertical List**, **Horizontal List**, **Feature Carousel**, **Feature Cards** o **Banner List**. ### Configurar una tarjeta de producto \{#configure-a-product-card\} Para cada tarjeta del grupo: 1. Selecciona la tarjeta. En la pestaña **Design**, dentro de **Product**, asígnale un producto. Los productos ya añadidos a esta pantalla aparecen en la sección **Added**, seguidos del resto de tu catálogo. 2. Confirma la oferta activa. Si anteriormente vinculaste el producto con una oferta, esa oferta aparece automáticamente. Puedes seleccionar una oferta diferente de forma manual, pero el producto aparecerá duplicado en el panel **Products**. 3. Para preseleccionar esta tarjeta al abrir la pantalla, selecciona **Set as default**. 4. (Opcional) Para mostrar a los usuarios qué plan está seleccionado, dale estilo a los [**estados**](builder-styling#state-specific-settings-interactive-elements) **Selected** de las tarjetas. :::important Una tarjeta sin producto [bloquea la publicación](flow-common-issues#a-flow-wont-preview-or-publish). Lo mismo ocurre con una acción o variable que todavía apunta a un grupo de productos que eliminaste: después de reemplazar un grupo, apunta cada acción y variable al nuevo. ::: Termina de configurar la tarjeta con cuatro funciones opcionales: - **Precio dinámico**: Sustituye el precio de marcador de posición por la [variable de precio](onboarding-variables#product-variables) del producto. La tarjeta mostrará entonces el precio de la store en la moneda del usuario. - **Términos de la oferta**: Para detallar la oferta, usa variables de oferta en el texto de la tarjeta: `offer_price`, `offer_billing_period` y `offer_full_duration`. - **Insignia**: Coloca una [insignia](builder-elements#badge) que destaque un plan concreto. - **Precio de referencia**: Un [elemento Old Price](onboarding-text#add-an-old-price) dentro de la tarjeta muestra el precio real multiplicado por un factor que tú defines, tachado, de modo que el precio real se percibe como un descuento. La moneda y el formato siempre coinciden con el precio real, y el número se actualiza automáticamente ante cualquier cambio de precio. ## Activar una compra \{#trigger-a-purchase\} La acción **Purchase** inicia la compra de un producto en el store. Puedes asociarla a cualquier elemento del flow, aunque los botones y las tarjetas de producto son las opciones más habituales. ### Añadir un botón de compra \{#add-a-purchase-button\} Para añadir un botón de compra: 1. Abre el panel **Add element**. 2. Elige un preset de la categoría **Button**. 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 una de las siguientes 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. ### Iniciar una compra desde una tarjeta de producto \{#trigger-purchase-from-a-product-card\} Una tarjeta de producto puede iniciar la compra directamente, sin necesidad de un botón separado. Para ello, añade la acción **Purchase** a cada tarjeta individualmente. ### 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\} Los suscriptores existentes necesitan una forma de restaurar sus compras anteriores. Añade un botón con la acción **Restore purchases**: - Usa el preset **Links** de la categoría **Button**. Incluye una fila de tres botones de texto: **Restore** (preconfigurado), **Terms of use** y **Privacy policy**. - Añade la acción **Restore purchases** a otro botón. 1. Selecciona el botón y abre la pestaña **Interactions**. 2. Haz clic en **Add trigger** > **On tap** y luego en **Add action**. 3. En el desplegable **Action**, selecciona **Restore purchases**. La ausencia de un botón de restauración *no* impide la publicación, pero puede provocar el rechazo en la store. Usa el [skill flow-audit](flow-audit-skill) para identificar los paywalls sin funcionalidad de restauración. ## Cambiar el contenido de la pantalla según el producto seleccionado \{#change-screen-content-based-on-the-selected-product\} :::link Artículo principal: [Visibilidad condicional](onboarding-element-visibility) ::: Si una pantalla tiene tarjetas de producto, puedes mostrar u ocultar otros elementos según la tarjeta 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 menú desplegable **Action**, selecciona **Show** u **Hide**. 5. Selecciona el elemento que quieres mostrar u ocultar cuando se seleccione ese producto. ## Cambiar el producto seleccionado desde otros elementos \{#change-the-selected-product-from-other-elements\} :::link Artículo principal: [Seleccionar producto](onboarding-actions#select-product) ::: Normalmente los usuarios cambian el producto seleccionado tocando una tarjeta. Sin embargo, otros elementos, como el [botón de prueba](paywall-with-trial-toggle), pueden usar la acción **Select product** para cambiar la selección en su lugar. Para configurar la acción: 1. Selecciona el elemento que debe cambiar la selección: un botón, un toggle o cualquier otro elemento. La card que elijas tiene que estar en la misma pantalla. 2. Abre la pestaña **Interactions** del panel derecho y haz clic en **Add trigger**. 3. Selecciona el trigger y haz clic en **Add action**. 4. En el desplegable **Action**, selecciona **Select product**. 5. En **Product**, indica la product card que debe seleccionarse con esta acción. ## Gestionar productos del flow \{#manage-flow-products\} El panel **Products** muestra todos los productos del flow, agrupados por pantalla. - El número junto al nombre del producto cuenta las referencias a ese producto en la pantalla: cada tarjeta, cada variable de producto en un elemento de texto y cada acción. - El precio a la derecha es el precio base del producto. Si hay una oferta asociada, incluye el precio con y sin descuento. Una entrada **Monthly** muestra el precio mensual y su oferta de respaldo. - Los productos predeterminados aparecen en una sección separada. Para añadir una entrada, haz clic en **+** y sigue las instrucciones en [Añadir productos en el panel de productos](#add-products-in-the-products-panel). ### Acciones del producto \{#product-actions\} Abre el menú contextual de un producto para gestionarlo: - **Set as default**: Selecciona previamente la tarjeta de este producto cuando se abre la pantalla, igual que la casilla **Set as default** en la tarjeta. Solo disponible para productos con exactamente una tarjeta de producto en la pantalla. - **Replace in N places**: Reemplaza el producto en cada tarjeta, elemento de texto y acción que lo use en esta pantalla. El campo **Offer** se restablece. Si otras pantallas usan el mismo producto y oferta, el toast de confirmación ofrece repetir el cambio allí. - **Offer**: Cambia la oferta para cada uso del producto en esta pantalla. Si el producto existe en otras pantallas, también puedes aplicar el cambio allí. Para previsualizar cómo queda la pantalla para usuarios con distintos tipos de oferta, selecciona [**Preview as**](paywall-device-compatibility-preview#preview-products-and-offers) en el mismo submenú. Para una entrada **Monthly**, el submenú también tiene una sección **Plan**: selecciona **Monthly plan unavailable** para previsualizar el plan anticipado con la oferta de respaldo. - **Fallback offer**: Cambia la oferta anticipada para usuarios que no pueden comprar el plan de compromiso. Solo disponible para entradas **Monthly**. - **Remove**: Elimina el producto de la lista de la pantalla. Las tarjetas y las acciones **Purchase** que lo usaban pierden al instante su configuración de producto — reemplaza el producto antes de publicar. Las variables de texto y las condiciones mantienen la referencia y muestran un icono de advertencia. Su tooltip muestra **This product is off this screen** hasta que vuelvas a añadir el producto, reemplaces la referencia o elimines el texto. - **Missing**: Un producto u oferta eliminado en tu Dashboard mantiene su entrada, marcada como **Missing**, con su recuento de usos. Solo **Replace in N places** y **Remove** siguen disponibles, y cada uso restante bloquea la publicación. ## Solución de problemas de productos \{#product-troubleshooting\} Los siguientes problemas con productos y ofertas bloquean la publicación: - Una tarjeta de producto o acción de **Purchase** sin un producto vinculado. Vincula un producto o elimina la tarjeta/acción. - Un producto u oferta que no aparece en tu Dashboard. Ocurre si añades un producto a un flow y luego lo eliminas de tu cuenta de Adapty. Busca la entrada **Missing** del producto en el panel **Products** y [**reemplaza todas las ocurrencias**](#product-actions) por un producto existente. - Una variable de oferta usada con un producto que no tiene oferta. [**Asigna una oferta**](#product-actions) al producto en el panel **Products** o elimina la variable. - Una variable de texto o condición sigue haciendo referencia a un producto que eliminaste en el panel **Products**. Añade de nuevo el producto con **+** o cambia la referencia. - Una entrada **Monthly** de un producto sin un plan de compromiso de 12 meses. [Activa el plan](set-up-12-month-commitment#2-adapty-configuration) para el producto o [reemplaza la entrada](#product-actions). - Una oferta del otro plan de facturación, o una oferta de respaldo que es una oferta del plan mensual o no aparece en tu Dashboard. Selecciona una oferta del plan propio de la entrada, o una oferta de respaldo por adelantado. Puedes guardar el flow aunque el problema siga presente: itera hasta que la publicación sea exitosa. --- # File: builder-elements --- --- title: "Elementos" description: "Todos los elementos visuales disponibles en el Flow & Paywall Builder: contenedores de diseño, texto, multimedia, listas, botones, entradas, productos y más." --- Para abrir la biblioteca de elementos y añadir un nuevo elemento a una pantalla, haz clic en el botón más Plus del panel izquierdo o encima de la vista previa del dispositivo. Los elementos se dividen en las siguientes categorías: - [Básicos](#basics) (contenedores de diseño, texto, medios, listas, insignias, marcas de verificación) - [Formularios y Quiz](#forms--quiz) (botones, inputs, quizzes, pestañas) - [Paywall y Comercio](#paywall--commerce) (productos, toggles, interacción con el usuario, cuenta regresiva) - [Indicadores de progreso](#progress) y cargadores Para cada elemento, Adapty ofrece múltiples presets: plantillas con contenido de marcador de posición o interacciones predefinidas. ## Conceptos básicos \{#basics\} ### Diseño \{#layout\} :::link Artículo principal: [Elementos de diseño](builder-containers) ::: Los elementos de diseño son contenedores que organizan los elementos que contienen. - **Contenedor vertical**: organiza los elementos hijos de arriba hacia abajo - **Contenedor horizontal**: organiza los elementos hijos de izquierda a derecha - **Divisores (horizontal y vertical)**: líneas que separan visualmente las secciones de contenido - **Carrusel**: un contenedor deslizable - **Footer**: un panel fijado en la parte inferior de la pantalla, fuera del área de desplazamiento - **Sliding sheet**: un panel que superpone el contenido principal sobre una imagen hero - **Bottom Sheet**: un panel deslizante anclado a la parte inferior de la pantalla :::link Artículo principal: [Contenido de texto](onboarding-text) ::: Un elemento de texto puede contener texto estático, [variables](onboarding-variables) o [texto condicional](onboarding-text#add-conditional-text), y puedes [localizarlo](paywall-localization). Un elemento de texto también puede mostrar datos de tu app, como el saldo de monedas de un jugador o el número de días de una racha. Inserta una [etiqueta personalizada](flow-custom-tags) donde deben aparecer los datos. Tu app establece el valor de la etiqueta a través del SDK de Adapty, de modo que cada usuario ve su propio saldo o racha. Los siguientes preajustes de texto vienen incluidos por defecto. Para modificar esta lista, añade o elimina [estilos de texto guardados](builder-reusable-styles): - H1 - H2 - H3 - Button Label - Body - Caption - Small Label El menú **Text** también incluye el elemento **Old Price**: un precio de producto tachado que se calcula automáticamente a partir de un multiplicador. Consulta [Añadir un precio antiguo](onboarding-text#add-an-old-price). ### Medios \{#media\} :::link Artículo principal: [Imágenes, vídeos e iconos](custom-media) ::: :::note Esta sección describe los elementos de medios en primer plano. Cambia el [fondo de pantalla](paywall-layout-and-products#screen-background) para rellenar toda la pantalla con una imagen o un vídeo. ::: - **Icon**: un icono vectorial de la biblioteca de iconos integrada, con tamaño y color personalizables - **Image**: una imagen — sube la tuya propia o proporciona una URL - **Video**: un reproductor de vídeo integrado para archivos de hasta 50 MB. Admite reproducción en bucle. ### Lista \{#list\} Los elementos de lista organizan el contenido en filas y columnas para mostrar datos con formato uniforme. Internamente, una lista es un [contenedor](manage-paywall-ui-elements#layout). - **Icon List**: filas con un icono inicial y una etiqueta de texto - **Timeline**: secuencia vertical con indicadores de pasos conectados - **Image List**: filas con una imagen inicial y texto - **Icon Cards**: cuadrícula de tarjetas con iconos centrados - **Image Cards**: cuadrícula de tarjetas con imágenes - **Comparison Table**: tabla de varias columnas que compara características entre planes (p. ej., Free vs Pro) ### Insignia \{#badge\} Una pequeña etiqueta superpuesta para destacar un elemento — se usa habitualmente para promocionar descuentos o planes específicos (p. ej., "Ahorra un 5%"). Usa el [posicionamiento absoluto](manage-paywall-ui-elements#absolute) para colocar una insignia sobre otro elemento. ### Marcas de verificación \{#checkmarks\} Iconos de indicador para usar dentro de [elementos seleccionables](flow-selectable-elements), como tarjetas de producto. Cada forma tiene un preset de activado y otro de desactivado. - Checkbox - Circle - Radiobutton - Toggle Para un interruptor o casilla que el usuario pulsa directamente, usa los elementos [Toggle](#toggle) en su lugar. ## Formularios y cuestionarios \{#forms--quiz\} ### Botones \{#buttons\} :::link Artículo principal: [Botones](paywall-buttons) ::: Los botones desencadenan acciones al pulsarlos: navegar a otra pantalla, abrir una URL o cerrar el flow. Cada plantilla es un punto de partida: personaliza su estilo y asígnale cualquier acción. Configura el comportamiento de los botones en la pestaña [Interactions](builder-ui#interactions-properties). - **Base**: botón normal con texto centrado - **Right Icon**: botón con un icono a la derecha - **With Subtitle**: incluye dos líneas de texto - **Pulse Animation**: incluye un efecto de pulso animado - **Purchase**: activa una compra - **Secondary Outline**: botón con contorno para acciones secundarias - **Back**: vuelve a la pantalla anterior - **Close flow**: sale del flow - **View more plans**: muestra opciones de producto adicionales - **Links**: conjunto de botones de pie de página (Restore, Terms of Service, Privacy Policy) ### Entradas \{#inputs\} :::link Artículo principal: [Entradas y formularios](builder-inputs-and-forms) ::: Los campos de entrada permiten a los usuarios introducir datos. Cada uno aplica un método de entrada y reglas de validación adecuados. - Texto - Correo electrónico - Contraseña - Número - Número de teléfono - Fecha - Hora - Fecha y hora Los campos de Fecha, Hora y Fecha y hora abren el selector nativo del dispositivo (rueda o calendario) al pulsarlos. Usa [variables](onboarding-variables) para procesar la entrada del usuario e influir en la lógica condicional. ### Cuestionarios \{#quizzes\} :::link Artículo principal: [Encuestas y cuestionarios](onboarding-quizzes) ::: Los elementos de cuestionario presentan pantallas de selección múltiple para encuestas, recopilación de preferencias y segmentación de usuarios. Configura [interacciones](onboarding-navigation-branching) para ramificar el flow según la respuesta del usuario. - **Icon Options**: lista de una columna con iconos - **Emoji Options**: lista de una columna con emojis - **Emoji Grid**: cuadrícula multicolumna con emojis - **Image Options**: lista de una columna con imágenes - **Icon Grid**: cuadrícula multicolumna con iconos - **Image Grid**: cuadrícula multicolumna con fotos - **Rating**: escala numérica (p. ej., 1–5) ### Pestañas \{#tabs\} :::link Artículo principal: [Pestañas](builder-tabs) ::: Las pestañas dividen una sección de pantalla en paneles de contenido intercambiables. El usuario selecciona una pestaña y el contenido inferior se actualiza en consecuencia. Se usan habitualmente para agrupar planes de productos o alternar entre precios mensuales y anuales. - **Segment control**: selector en forma de píldora con 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 ## Paywall y comercio \{#paywall--commerce\} ### Productos \{#products\} :::link Artículo principal: [Productos](paywall-product-block) ::: Los elementos de producto muestran los detalles de las compras in-app y gestionan la selección de productos. Cada preset organiza los datos del producto en un diseño diferente. Vincula tus productos para rellenar los elementos con datos reales de Adapty. - **Vertical List**: tarjetas de productos apiladas - **Horizontal List**: tarjetas de productos en fila horizontal - **Feature Carousel**: tarjetas deslizables con listas de características - **Feature Cards**: tarjetas estáticas con listas de características - **Banner List**: filas compactas con etiquetas en línea ### Toggle :::link Artículo principal: [Toggles y casillas de verificación](builder-toggles) ::: Interruptores y casillas de verificación que los usuarios pulsan para activar o desactivar algo. Cada uno es un [elemento seleccionable](flow-selectable-elements), por lo que otros elementos pueden reaccionar a su estado. - **Trial toggle**: cambia el producto mostrado entre su precio estándar y su oferta de prueba gratuita - **Checkbox**: una casilla de verificación con etiqueta de texto, prellenada con una línea de consentimiento de términos y privacidad - **Toggle**: un interruptor con etiqueta de texto ### Participación de usuarios \{#user-engagement\} :::link Artículo principal: [Reseñas y testimonios](builder-reviews-and-testimonials) ::: Bloques con estilo predefinido que incluyen valoraciones de usuarios para generar confianza y fomentar la conversión. Las plantillas son contenedores básicos con contenido de marcador de posición. - **Review**: una valoración con estrellas, el texto de la reseña y el nombre del autor - **Review Carousel**: tarjetas de reseñas deslizables con puntos de paginación - **Rating**: una puntuación numérica con visualización de estrellas - **App Rating**: una puntuación destacada con barra de estrellas y número de reseñas - **Social Proof**: un grupo de avatares con el número de usuarios ### Cuenta regresiva \{#countdown\} :::link Artículo principal: [Temporizador de cuenta regresiva](flow-timer) ::: Muestra horas, minutos y segundos contando hacia atrás hasta llegar a cero. Úsalo para crear urgencia en ofertas por tiempo limitado. El temporizador puede desencadenar acciones al llegar a cero, como navegar a otra pantalla u ocultar el distintivo de descuento. - **Inline**: visualización numérica compacta - **Inline with units**: visualización numérica con etiquetas de unidad - **Badge**: pequeña superposición de temporizador resaltada - **Blocks**: tarjetas separadas para días, horas, minutos y segundos ## Progreso \{#progress\} :::link Artículo principal: [Cargadores y barras de progreso](builder-loaders-and-progress-bars) ::: ### Indicadores de progreso \{#progress-indicators\} Barras de progreso por pasos que muestran la posición del usuario en un flow de varias pantallas. Resultan útiles en secuencias de onboarding donde el usuario necesita ver cuántos pasos quedan. - **Linear**: una barra continua que se va llenando conforme el usuario avanza - **Segmented**: una barra dividida en segmentos discretos, uno por paso - **Connectors**: marcadores de paso numerados unidos por líneas conectoras cortas ### Cargadores \{#loaders\} Indicadores de carga animados para transiciones. Usa cargadores cuando tu app procese datos del usuario, por ejemplo, tras enviar un cuestionario. - **Spinner**: indicador circular giratorio - **Spinner con etiqueta**: spinner acompañado de una etiqueta de texto (p. ej., "Cargando...") - **Loader**: barra de progreso horizontal --- # 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 de un 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, toggles y casillas de verificación son seleccionables desde el principio - [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, 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 del quiz es un elemento seleccionable dentro del grupo de quiz. Consulta [Quizzes](onboarding-quizzes). - **Productos**: Tarjetas de producto en 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 y checkboxes**: Los toggles de prueba, toggles y checkboxes pertenecen cada uno a un grupo y tienen un estado seleccionado. Consulta [Toggles y checkboxes](builder-toggles). ## Hacer un elemento seleccionable \{#make-an-element-selectable\} En algunos casos, puede que quieras hacer que elementos adicionales sean seleccionables. Por ejemplo, puedes añadir una casilla **Don't ask me again** 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)**: Dirige a los usuarios a diferentes pantallas o activa distintas acciones según los elementos seleccionados. - **[Navegación dinámica](onboarding-navigation-branching)**: Ramifica el flow según las respuestas del cuestionario, estados de alternancia u otras selecciones. - **[Visibilidad condicional](onboarding-element-visibility)**: Muestra u oculta elementos según lo que el usuario haya seleccionado en pantallas anteriores. - **[Tu app](ios-flow-input)**: Gestiona el callback de análisis del SDK para recibir un array con las opciones actualmente seleccionadas cada vez que el usuario interactúa con el cuestionario. Procesa la selección en tu app o en un servidor backend. Para los grupos de productos, también puedes establecer la selección en lugar de solo leerla. Una acción [Seleccionar producto](onboarding-actions#select-product) mueve la selección a un elemento de producto específico dentro de un grupo en la misma pantalla. --- # File: builder-element-states --- --- title: "Estados de los elementos" description: "Aplica estilos a los elementos según su estado y usa una condición para deshabilitar un elemento en tiempo de ejecución." --- Los elementos interactivos de un flow cambian de apariencia según las acciones del usuario: una opción de quiz al pulsarla pasa a estado **Selected**, un campo de texto al enfocarlo pasa a estado **Active**. Algunos estados dependen de condiciones; por ejemplo, puedes **deshabilitar** un botón. Aplica estilos distintos a cada estado para dar feedback visual al usuario sin necesidad de código. ## 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 deseado. Añade el [estado Disabled basado en condiciones](#condition-driven-disabled-state) si es necesario. 3. Cambia cualquier propiedad — relleno, borde, tipografía, etc. El cambio quedará limitado 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 y checkboxes, 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 pulsa el elemento. El Builder vuelve a Default cuando el usuario deselecciona el elemento. 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 los [tipos de grupo](flow-selectable-elements#group-types). :::tip Para reutilizar estilos específicos de estado, añádelos a un elemento, como una tarjeta de producto. Luego pídele al [Editor de IA](flow-ai-editor) que los aplique a los demás elementos. Los estilos específicos de estado no se propagan automáticamente entre elementos hermanos. ::: ## 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, email, contraseña, número, teléfono, fecha y/o hora. - **Elementos seleccionables**: Opciones de quiz, productos, pestañas, interruptores y casillas de verificación, y cualquier [elemento seleccionable personalizado](flow-selectable-elements#make-an-element-selectable). - **Cualquier elemento con interacción táctil**: Por ejemplo, un botón, imagen o icono que desencadene 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, hojas inferiores" description: "Agrupa elementos en contenedores, carruseles y hojas inferiores en el Flow & Paywall Builder." --- Los elementos de maquetación agrupan otros elementos y controlan cómo se distribuyen en la pantalla. El Flow & Paywall Builder incluye seis tipos de elementos de maquetación: - **Contenedores**: organizan elementos secundarios a lo largo de un eje, vertical u horizontalmente - **Carrusel**: 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 anclado a la parte inferior de la pantalla, fuera del área de desplazamiento - **Sliding sheet**: un panel que superpone el contenido principal sobre una imagen de fondo - **Divisores**: líneas delgadas que separan filas o columnas :::link **Las pestañas** también forman parte de este grupo, pero tienen su propio artículo. Consulta [Pestañas](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. En cualquier momento puedes cambiar entre **Vertical**, **Horizontal** y **Free** en la sección **Layout** del panel derecho. Al cambiar a **Free**, todos los elementos hijos se convierten automáticamente a posición [**Absolute**](manage-paywall-ui-elements#position-in-a-free-container). Los elementos hijos se renderizan en el orden en que aparecen en el panel **Layers** — arrástralos 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. ### 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. Activa la hoja 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. ### 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. :::warning Mantén [**Vertical scroll**](paywall-layout-and-products#scroll) activado en cualquier pantalla que tenga pie de página. Si está desactivado, el pie de página no se renderiza. ::: ### 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. ## Hoja deslizante \{#sliding-sheet\} Una **Hoja deslizante** coloca el contenido principal sobre una imagen de portada. El panel cubre la parte inferior de la pantalla y sube sobre el [fondo de pantalla](paywall-head-picture#use-a-background-as-a-hero-image) a medida que el usuario se desplaza. El fondo se mueve en iOS y permanece fijo en otras plataformas. Este comportamiento no se puede modificar. Los elementos que añades fuera de la hoja se renderizan sobre el fondo de pantalla. Úsalos para un titular o un logo sobre la imagen hero. - **Start position** define cuánto espacio ocupa la hoja desde la parte inferior de la pantalla. Con el valor predeterminado, la hoja cubre el 55% inferior y el fondo se ve en el 45% superior. No puedes definir la altura de la hoja en valores absolutos. Para que sea más alta, sube **Start position**. - **Fill** establece el fondo detrás de tu contenido. Aumenta la opacidad para dejar que la imagen hero se transparente. Solo se permite una hoja deslizante por pantalla. No puedes duplicar una hoja deslizante existente ni añadir una nueva. :::tip Puedes colocar un [pie de página](#footer) dentro de la hoja. El pie de página se fijará entonces en la parte inferior de la hoja en lugar de en la parte inferior de la pantalla. ::: ### Hoja deslizante vs. Bottom Sheet \{#sliding-sheet-vs-bottom-sheet\} Ambos paneles ocupan la parte inferior de la pantalla. Elige el que se ajuste a tus necesidades: - **Usa una hoja deslizante** para superponer el contenido principal de la pantalla sobre una imagen hero. La hoja es visible cuando se abre la pantalla y solo se mueve cuando el usuario hace scroll. - **Usa un [Bottom Sheet](#bottom-sheet)** para contenido que aparece bajo demanda, como una lista de planes detrás de un enlace **Show all plans**. Se muestra con una acción **Show**. ## 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: onboarding-text --- --- title: "Añadir y dar estilo a textos y listas en el Flow & Paywall Builder" description: "Añade y da estilo a títulos, subtítulos, párrafos y listas en el Flow & Paywall Builder de Adapty, y personaliza el texto para experiencias de usuario acordes con tu marca." --- Añade títulos, párrafos o listas con un solo clic, dales estilo para que encajen con tu marca y usa [variables dinámicas](onboarding-variables) para personalizar el contenido para cada usuario. <AIPromo title="Reescribe tu titular con un solo prompt."> Pide al Editor de IA que reescriba tu copy, mencione la prueba gratuita o acorte todos los elementos de texto de la pantalla a la vez. </AIPromo> ## Configurar estilos de texto \{#set-up-text-styles\} El panel **Styles** incluye estilos de texto preconfigurados para encabezados y funciones comunes. Cada estilo de texto tiene los siguientes ajustes: - Familia tipográfica - Peso - Tamaño - Altura de línea - Espaciado entre letras Para editar un estilo, haz clic en su entrada, cambia los ajustes y pulsa **Save**. Cuando [añades un elemento de texto](#add-text), puedes seleccionar uno de los estilos que hayas configurado aquí. :::important Los cambios en un estilo se aplican a todos los elementos de texto que lo usen, en todas las pantallas. ::: Para crear un nuevo estilo: 1. Abre el panel **Styles** Styles. 2. En la cabecera del panel, haz clic en Create style **Create style**. 3. Establece **Type** en **Text**, escribe un nombre y configura los ajustes tipográficos. 4. Haz clic en **Create**. ## Añadir texto \{#add-text\} Para añadir un elemento de texto: 1. Haz clic en **+** en la parte superior izquierda. Selecciona **Text**. Elige uno de los [estilos de texto](#set-up-text-styles) que estaban preconfigurados o que hayas definido tú. 2. Haz clic en el nuevo elemento y edita su contenido en la sección **Content** del panel **Design** a la derecha. 3. Si es necesario, ajusta las [propiedades de tipografía](#typography-properties) en el panel **Design**. O bien, selecciona el texto en la vista previa para abrir un tooltip de personalización rápida de estilos. 4. Opcionalmente, en los paneles **Design** e **Interaction**, también puedes aplicar cualquier otra configuración disponible para los componentes del flow. Para más detalles, consulta [Estilos y apariencia](builder-styling). :::tip Si necesitas usar el mismo elemento de texto en varias pantallas, cópialo y pégalo: selecciona el elemento, pulsa Ctrl+C (o ⌘+C en Mac), navega a otra pantalla y pulsa Ctrl+V (o ⌘+V en Mac) para pegarlo. ::: ### Cambiar el estilo de partes del texto \{#change-styling-for-parts-of-text\} :::warning **Negrita** e **Cursiva** no tienen efecto en una [fuente personalizada](using-custom-fonts-in-flow-builder) a menos que su familia incluya una variante en negrita o cursiva. [Sube la variante correcta](using-custom-fonts-in-flow-builder#manage-a-custom-font) tú mismo. ::: Para cambiar el estilo solo en algunas partes de un elemento de texto: 1. Selecciona una parte de un elemento de texto en la sección **Content**. 2. En el tooltip que aparece, cambia el color del texto, aplica negrita, subrayado, cursiva o tachado, o añade una URL. La vista previa se actualiza de inmediato. ## Añadir un precio anterior \{#add-an-old-price\} El elemento Precio Anterior muestra un precio "antes" inflado junto al precio real de un producto, de modo que el descuento sea visible de un vistazo. Muestra el precio del producto multiplicado por un factor que tú eliges y lo tacha automáticamente. No editas el contenido del elemento directamente. El valor se calcula a partir del precio del producto en el store en tiempo de ejecución, por lo que la moneda siempre coincide con el precio real. Para añadir un precio anterior: 1. Haz clic en **+** en la parte superior izquierda. Selecciona **Text** y luego **Old Price**. 2. En el panel **Layers**, arrastra el elemento dentro de una tarjeta de producto. El elemento también funciona dentro de tarjetas de producto guardadas como componentes reutilizables. :::important El elemento Old Price debe estar dentro de una tarjeta de producto, ya que el precio se toma del producto de esa tarjeta. Fuera de una tarjeta de producto, el builder muestra el marcador de posición "Old price" en lugar de un precio. ::: 3. En el panel **Design**, en la sección **Price**, establece el **Multiplier**. Adapty multiplica el precio del producto por este valor. Por ejemplo, con el multiplicador predeterminado de 2, un producto de $12.99 muestra un $25.98 tachado. 4. Si es necesario, ajusta las [propiedades de tipografía](#typography-properties). El elemento admite el mismo estilo que el texto normal. :::tip Para tachar el precio real de otro producto —por ejemplo, el precio mensual en una tarjeta anual— construye la fila de precio con elementos de texto y variables. Consulta [Mostrar un precio tachado con un distintivo de descuento](strikethrough-price). ::: ## Propiedades de tipografía \{#typography-properties\} Los elementos de texto tienen los siguientes controles de tipografía: - **Font family**: Elige un tipo de letra — una fuente integrada o una [fuente personalizada](using-custom-fonts-in-flow-builder). :::warning Los distintos dispositivos incluyen fuentes diferentes, o pueden renderizar la misma fuente de forma distinta. Si la fuente no está presente en el dispositivo, el sistema usará una fuente predeterminada (SF Pro / Roboto). Para mostrar la misma fuente de forma consistente en todos los dispositivos, sube una [fuente personalizada](using-custom-fonts-in-flow-builder). ::: - **Weight**: Establece el grosor de la fuente. - **Size**: Establece el tamaño de la fuente en píxeles. - **Color**: Establece el color del texto. - **Line height**: Establece el espaciado entre líneas, o déjalo en **Auto**. - **Alignment**: Establece la alineación horizontal (izquierda, centro, derecha) y vertical (arriba, centro, abajo). - **Decoration**: Aplica subrayado o tachado. - **Truncate**: Limita el número de líneas que se muestran. El texto que supere ese límite se truncará. Útil cuando la longitud del contenido varía por variables dinámicas o localización. Los siguientes ajustes no se pueden guardar en un [estilo de texto](#set-up-text-styles) reutilizable: - Color - Alineación - Decoración - Truncar Defínalos para cada elemento de texto individualmente. ## Añadir enlaces \{#add-links\} Los flows admiten dos formas de convertir texto en enlaces clicables. Elige según el papel que desempeña el texto: - **Enlace en línea** — para una URL dentro de texto continuo, como una referencia "Más información" incrustada en un párrafo. Siempre se abre en el navegador integrado de la app. - **Una acción Open URL** — para elementos interactivos independientes, como un botón de Términos de servicio. Puede abrirse en el navegador integrado o en el externo. ### Enlace en línea \{#inline-link\} Para convertir parte de un elemento de texto en un enlace: 1. Selecciona el texto en la sección **Content**. 2. En el tooltip de formato, haz clic en el icono de enlace. 3. Pega la URL de destino en la ventana emergente. ### Acción de abrir URL \{#open-url-action\} :::link Artículo principal: [Acciones](onboarding-actions#open-url) ::: Para convertir un botón completo en un enlace: 1. Añade un [Button](builder-elements#buttons) — o usa el preset **Links**, una fila predefinida con los botones Restore / Terms of Service / Privacy Policy. 2. Selecciona el botón en el panel **Layers** y abre la pestaña **Interactions** en el panel derecho. 3. Establece el destino para la acción [**Open URL**](onboarding-actions#open-url). :::important Una acción Open URL vacía [bloquea la previsualización y publicación](flow-common-issues#a-flow-wont-preview-or-publish). ::: ## Añadir texto condicional \{#add-conditional-text\} El texto condicional cambia lo que muestra un elemento de texto según una condición. Por ejemplo, un encabezado puede mostrar un mensaje cuando el usuario selecciona el plan anual y otro diferente para el plan mensual. El texto condicional funciona igual que la [visibilidad condicional](onboarding-element-visibility), pero en lugar de mostrar u ocultar el elemento, intercambia su contenido. Para configurar el texto condicional: 1. Selecciona un elemento de texto en el canvas. 2. En el panel **Design**, en la sección **Content**, selecciona **Conditional**. 3. Crea la condición **if**. Elige una propiedad de la pestaña **Custom**, **Products** o **Elements**, establece el operador e introduce el valor a comparar. Para más detalles sobre los tipos de propiedades, consulta [Visibilidad condicional](onboarding-element-visibility). 4. En **then**, introduce el texto que se mostrará cuando la condición sea verdadera. El formato de texto enriquecido funciona igual que para el [texto normal](#change-styling-for-parts-of-text). Para insertar una [variable](onboarding-variables) o una [etiqueta personalizada](flow-custom-tags), haz clic en Variables **Add variable**. 5. En **else**, introduce el texto alternativo que se mostrará cuando ninguna condición coincida. 6. (Opcional) Haz clic en **+ Add else/if** para añadir más condiciones, cada una con su propio texto. Para traducir texto condicional, abre el [panel **Localizations**](add-paywall-locale-in-adapty-paywall-builder#translate-strings-manually), donde cada rama tiene su propia fila. ## Agregar listas \{#add-lists\} :::note Los elementos de lista son contenedores formados por componentes de elementos individuales. Para listas simples con viñetas o numeradas dentro de texto continuo, usa un elemento de texto y aplica el formato deseado desde el panel **Design**. ::: 1. Haz clic en **+** en la parte superior izquierda. Selecciona **List** y elige una de las plantillas de lista. 2. Ve al panel **Design** de la derecha para editar los elementos de la lista o subir una imagen como marcador de elemento. --- # File: using-custom-fonts-in-flow-builder --- --- title: "Fuentes personalizadas en el Flow y Paywall Builder" description: "Sube y usa fuentes personalizadas en el Flow y Paywall Builder." --- Al crear flows, puede que quieras usar una fuente personalizada que encaje con el resto de tu app. Aquí te explicamos cómo añadir fuentes personalizadas y usarlas en tus flows. Todos los flows de una misma app comparten las fuentes que subas. Esto incluye los flows que [copies](copy-flows). Las demás apps de tu cuenta de Adapty necesitan sus propias fuentes. :::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 en el 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 las del sistema, puedes añadir una fuente personalizada. Para añadir una fuente personalizada: 1. Elige **Upload new font** en cualquiera de los menús desplegables de fuentes. 2. En la ventana **Add custom font**, arrastra y suelta los archivos de fuente o haz clic en **Select files**: - **Formatos admitidos**: `.ttf` y `.otf`. - **Fuentes variables**: Adapty crea una cara independiente por cada estilo con nombre del archivo, como **Regular** o **Bold**. - **Varias tipografías a la vez**: Añade todas tus fuentes en una sola carga. Adapty lee el nombre de familia almacenado en cada archivo y agrupa los archivos en familias. Por ejemplo, 10 archivos de 2 tipografías se convierten en 2 familias de fuentes con 5 caras cada una. - **Familias existentes**: Un archivo con el mismo nombre de familia que una fuente ya subida se une a ella como una nueva cara. 3. Haz clic en **Add fonts**. Adapty sube los archivos y muestra una tarjeta por familia de fuentes. Para cambiar el nombre de una familia, haz clic en el lápiz Edit junto al nombre. 4. Configura las propiedades de cada variante de fuente individual, si es necesario: - **Font Family**: La familia a la que pertenece la tipografía; puedes moverla a una familia diferente. - **Font Face**: El peso y estilo de la tipografía, que se detectan automáticamente al subir el archivo. - **iOS font name** y **Android font name**: Los nombres de la fuente en tu app. Deben [coincidir con el bundle de tu app](#add-the-font-files-to-your-apps-bundle). Al subir el archivo de fuente a Adapty, confirmas que tienes derecho a usarla en tu app. ### Gestionar una fuente personalizada \{#manage-a-custom-font\} Haz clic en **Edit custom font** Edit custom font para abrir la pantalla de edición de propiedades. - Para cambiar el nombre de la familia, edita **Font family name in Builder**. - Para añadir un nuevo peso o estilo, haz clic en **Add font face**. - Para corregir el nombre de fuente de iOS o Android de un estilo, haz clic en su fila. - Para subir un nuevo archivo para un estilo, haz clic en su fila y luego en **Replace**. ### Eliminar una fuente personalizada \{#delete-a-custom-font\} Para eliminar una familia de fuentes o una cara concreta, haz clic en **Delete font family** o **Delete font face** en la pantalla de edición de propiedades. Si alguno de tus flows utiliza esa cara, Adapty no puede eliminarla directamente. En su lugar, se te pedirá que selecciones una fuente de reemplazo de tu biblioteca. Los elementos que usaban la cara anterior pasarán a utilizar la nueva fuente. Haz clic en **Replace & delete** para confirmar tu decisión. ## 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. ## Añade los archivos de fuentes al bundle de tu app \{#add-the-font-files-to-your-apps-bundle\} Adapty no envía fuentes personalizadas a los dispositivos de los usuarios, por lo que tu app debe incluir el archivo de cada tipografía que usen tus flows. Si tu app ya usa fuentes personalizadas, añádelas de la misma forma. Si no es así, sigue la guía de cada plataforma: - **iOS**: [Añadir una fuente personalizada a tu app](https://developer.apple.com/documentation/uikit/adding-a-custom-font-to-your-app) - **Android**: [Fuentes en XML](https://developer.android.com/develop/ui/views/text-and-emoji/fonts-in-xml) El flow identifica cada fuente en tu app por su nombre, por lo que los nombres en Adapty deben coincidir con los de tu app. Para revisar los nombres, abre el estilo en los [ajustes de fuentes](#manage-a-custom-font): - **iOS font name**: El nombre PostScript almacenado dentro del archivo de fuente. iOS encuentra la fuente por este nombre, no por el nombre del archivo. Debe ser único entre todas las fuentes personalizadas de la app. Adapty lo rellena cuando el archivo lo declara. Los archivos de fuentes variables a menudo no lo declaran, así que introdúcelo manualmente. Para encontrarlo, abre Font Book > PostScript name, o usa la [API `UIFont`](https://developer.apple.com/documentation/uikit/uifont). - **Android font name**: El nombre del archivo en `res/font/`, sin la extensión. Usa solo letras minúsculas, números y guiones bajos. Adapty nunca lo rellena. Todas las variantes que Adapty crea a partir de un archivo de fuente variable comparten este nombre. --- # 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 & Paywall Builder." --- Dale a cualquier pantalla un fondo en la sección **Fill** de [**Screen settings**](paywall-layout-and-products#screen-settings): - **Fondo de una sola capa**: Un color, degradado, imagen o vídeo. - **Fondo multicapa**: Por ejemplo, una imagen con un [tinte](#tint-a-background). Si [añades un tema oscuro](paywall-dark-mode#add-a-dark-theme) y aplicas un [estilo reutilizable](builder-reusable-styles#color-styles), puedes [establecer fondos diferentes](#show-a-different-background-in-dark-mode) para el tema claro y el tema oscuro. :::tip Para convertir un fondo en una imagen hero, [coloca tu contenido en una hoja deslizante](#use-a-background-as-a-hero-image). ::: ## Imagen \{#image\} Sube un archivo `.JPG`, `.PNG` o `.WEBP` de hasta 20 MB. La imagen se escala para cubrir todo el fondo. :::note Las imágenes y los vídeos de fondo ocupan toda la ventana gráfica, 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 medio personalizado](custom-media#custom-media-id). ## Video \{#video\} Sube un archivo `.MP4` o `.WEBM` de hasta 50 MB. La vista previa muestra un fotograma fijo, pero el video se reproduce en el dispositivo en tiempo de ejecución. Activa **Loop** para reproducir el video en bucle de forma continua. Para cambiar el video de fondo en tiempo de ejecución, habilita un [ID de medio personalizado](custom-media#custom-media-id). ## Color sólido \{#solid-color\} Introduce un valor hexadecimal de seis dígitos, como `#FFFFFF`, y ajusta la opacidad del 0 al 100%. Un [valor más corto bloquea la publicación](flow-common-issues#publishing-fails-after-entering-a-shorthand-hex-color). ## Degradado \{#gradient\} Crea un degradado lineal con múltiples paradas: - **Direction** — gira el degradado de 0 a 360°. - **Stops** — para reposicionar un stop, arrástralo a lo largo de la barra. Para añadir un stop, haz clic en la barra o en Add gradient stop junto a **Stops**. Para cambiar la posición, el valor hexadecimal de seis dígitos o la opacidad de un stop, edita su fila. - **Opacity** — el control deslizante de la fila de la capa escala todos los stops a la vez, de modo que el fundido entre ellos se mantiene intacto. ## Usar un fondo como imagen hero \{#use-a-background-as-a-hero-image\} :::link Artículo principal: [Sliding sheet](builder-containers#sliding-sheet) ::: En los flows, la imagen hero se construye a partir del fondo de una pantalla y un **Sliding sheet**. El sheet cubre la parte inferior de la pantalla y sube sobre el fondo a medida que el usuario hace scroll. 1. Establece el **Fill** de la pantalla en **Image** o **Video** y sube la imagen hero. 2. Abre el panel **Add element** y selecciona **Layout** > **Sliding sheet**. 3. Configura la **Start position** de la hoja — la proporción de la pantalla que cubre la hoja, medida desde abajo. Con el valor predeterminado del 55%, el fondo se ve a través del 45% superior. 4. Añade tu contenido dentro del sheet. 5. Para superponer un titular o logotipo sobre la imagen hero, añade esos elementos fuera del sheet. Se renderizan sobre el fondo de pantalla. 6. Cambia el **Start position** para redimensionar el sheet si es necesario. No puedes establecer una altura absoluta para este elemento. ## Colorear un fondo \{#tint-a-background\} El texto colocado sobre una imagen o vídeo solo se lee bien donde el contenido multimedia detrás es suficientemente oscuro. Para uniformar esa superficie, apila una capa semitransparente sobre el contenido multimedia: 1. En **Screen settings**, ajusta la capa **Fill** con tu imagen o vídeo. 2. Para añadir una segunda capa, haz clic en Plus junto a **Fill**. La nueva capa aparece en la parte superior de la sección **Fill** y cubre el contenido multimedia. 3. Haz clic en la muestra de color de la capa y selecciona **Gradient** Gradient. Para oscurecer solo el borde donde está tu texto, aplica el degradado desde un punto opaco en ese borde hasta un punto transparente en el otro extremo. 4. Para ajustar la intensidad del tinte, arrastra el control deslizante en la fila de la capa. Para cambiar el orden de las capas, arrastra sus filas. Para eliminar una capa, haz clic en Close en su fila. ## Mostrar un fondo diferente en el modo oscuro \{#show-a-different-background-in-dark-mode\} :::link Artículo principal: [Modo oscuro](paywall-dark-mode) ::: Para mostrar un fondo diferente en el modo oscuro: 1. Si el flow no tiene tema oscuro, [añádelo](paywall-dark-mode#add-a-dark-theme). 2. [Guarda la capa de fondo como estilo de color](builder-reusable-styles#create-a-color-style). 3. Si el fondo tiene un [tinte](#tint-a-background), guarda también la capa de tinte como estilo de color. 4. [Cambia el valor oscuro](paywall-dark-mode#configure-the-dark-theme) de cada estilo. --- # 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 & Paywall Builder, y cambia los medios en tiempo de ejecución con IDs de medios personalizados." --- El Flow & Paywall 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, incluyendo las áreas detrás del notch 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 `.WEBP` de hasta 20 MB. Los GIFs y WEBPs animados no son compatibles. Para añadir movimiento, usa un elemento [Vídeo](#video) en su lugar. Un elemento de imagen puede tener una imagen diferente para cada idioma. Para mostrar una imagen distinta en modo oscuro, [añade un archivo oscuro](paywall-dark-mode#show-a-different-image-or-video-in-dark-mode) al elemento. :::tip Para colocar contenido sobre una imagen a pantalla completa, [crea una imagen hero](paywall-head-picture#use-a-background-as-a-hero-image) a partir de un fondo de pantalla y una hoja deslizante. ::: - **Content mode** — controla cómo la imagen rellena su contenedor cuando estableces su altura. Ver [Tamaño de imagen](#image-sizing). - **Use custom media ID** — ver [Custom media ID](#custom-media-id) más abajo. ### Tamaño de la imagen \{#image-sizing\} Una imagen tiene sus propias proporciones, y el contenedor que le asignas puede no coincidir con ellas. Establece primero el ancho y luego decide el alto: - **Alto automático**. La imagen mantiene su forma: el alto se calcula a partir del ancho que establezcas. El **Content mode** se desactiva porque no hay nada que recortar. - **Alto fijo o de relleno**. El contenedor adquiere sus propias proporciones y el **Content mode** resuelve el desajuste: - **Fit**. Escala la imagen completa dentro del contenedor y deja espacio vacío. - **Fill**. Recorta lo que sobresale. Valor predeterminado. Para mantener una imagen sin recortar en cualquier ancho de pantalla, usa el ancho **Fill** con la altura **Auto**. Con una altura fija, el recorte en la parte superior e inferior varía de un tamaño de pantalla a otro. ## 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** — controla cómo el vídeo rellena su contenedor cuando estableces su altura. Consulta [Tamaño del vídeo](#video-sizing). - **Loop** — reproduce el vídeo en bucle continuo. 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 canvas muestra un fotograma estático. En el dispositivo en tiempo de ejecución, el vídeo se reproduce sin sonido por defecto. Con Loop activado, se repite indefinidamente. Un elemento de vídeo puede tener un vídeo diferente para cada idioma. Para mostrar un vídeo distinto en modo oscuro, [añade un archivo oscuro](paywall-dark-mode#show-a-different-image-or-video-in-dark-mode) al elemento. ### Tamaño del vídeo \{#video-sizing\} Un vídeo tiene sus propias proporciones, y el contenedor que le asignes puede no coincidir con ellas. La altura determina lo que ocurre: - **Hug height**. La altura sigue las proporciones propias del vídeo. - **Fixed o Fill height**. La propiedad **Aspect** resuelve el desajuste: - **Fit**. Escala el vídeo completo dentro del contenedor y deja espacio vacío. - **Fill**. Recorta lo que se desborde. Valor por defecto. Cuando Adapty no puede leer las proporciones de un vídeo al subirlo, muestra una advertencia: "Could not determine video dimensions. Set a fixed height to ensure the video displays correctly." Dale al vídeo una altura fija, o asegúrate de que el contenedor que lo aloja tenga una. De lo contrario, su altura puede colapsar a 0. ### 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. Define el color del icono en el selector **Color**: elige un [estilo de color](builder-reusable-styles#color-styles) guardado o establece un color personalizado. Solo puedes aplicar un estilo de capa con un color sólido. ## Localizar medios \{#localize-media\} :::link Artículo principal: [Añadir una configuración regional](add-paywall-locale-in-adapty-paywall-builder) ::: Los siguientes elementos y capas pueden tener un archivo distinto por configuración regional: - Elementos de imagen y vídeo. - Capas de imagen y vídeo en un [relleno](builder-styling#fill), como el fondo de una pantalla. - Capas de imagen y vídeo de un [estilo de color](builder-reusable-styles#color-styles), en cada versión clara u oscura por separado. Para subir un archivo localizado: 1. Si el panel **Localizations** está abierto, ciérralo para mostrar el panel de propiedades. 2. En la [barra de herramientas inferior](builder-ui#view-controls-bottom-toolbar), establece **Locale** en la configuración regional de destino. 3. Selecciona el elemento, o haz clic en un área vacía de la vista previa para el fondo de pantalla. 4. En el panel de propiedades, sube el archivo localizado. 5. Vuelve a establecer **Locale** en la configuración regional predeterminada, para que las ediciones posteriores se apliquen a la configuración regional predeterminada de nuevo. Para un estilo de color, [reemplaza la imagen o el vídeo en el estilo](builder-reusable-styles#images-and-videos) mientras la configuración regional de destino esté activa. En una configuración regional sin archivo propio, los usuarios ven el archivo de la configuración regional predeterminada. Para un estilo de color, cada versión recurre a la configuración regional predeterminada de forma independiente. También puedes subir un archivo para cada configuración regional en el [panel **Localizations**](add-paywall-locale-in-adapty-paywall-builder#localize-images-and-videos). ### Limitaciones \{#limitations\} No puedes localizar iconos ni IDs de medios personalizados. Para localizar una imagen o vídeo que tiene un ID de medio personalizado, usa el ID para [sustituir un archivo localizado en tiempo de ejecución](#custom-media-id). ## ID de medio personalizado \{#custom-media-id\} :::important Puedes establecer un ID de medio personalizado para una imagen o vídeo en un [relleno](builder-styling#fill), como un [fondo de pantalla](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 [elementos visuales personalizados](get-pb-paywalls#customize-assets) — por ejemplo, para mostrar el avatar seleccionado por el usuario. El medio que subas en el Flow & Paywall Builder actúa como respaldo. Si tu código no proporciona un medio para ese ID en tiempo de ejecución, se muestra el respaldo en su lugar. Un ID de medio personalizado solo puede sustituir un archivo por otro del mismo tipo: una imagen por una imagen, un vídeo por un vídeo. Si los tipos no coinciden, 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 el medio por su ID. Consulta [Personalizar assets](get-pb-paywalls#customize-assets) para ver la API del SDK. :::tip Para que la primera pantalla de un flow se renderice al instante, etiqueta sus imágenes y vídeos pesados con IDs de media personalizados y pide a tus desarrolladores que incluyan esos archivos en el bundle de la app. Consulta [Mostrar media de la primera pantalla desde el bundle de la app](ios-optimize-paywall-fetching#show-first-screen-media-from-the-app-bundle). ::: --- # File: paywall-buttons --- --- title: "Botones" description: "Añade y configura botones de acción en el Flow & Paywall Builder." --- :::info Esta sección describe el nuevo Flow & Paywall Builder, que funciona con los SDK de Adapty versión 4.0 o superior. ::: Los botones son los elementos interactivos en el Flow & Paywall Builder que responden a las pulsaciones 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 cubre los elementos desplazables que quedan 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 cambia los paneles de contenido en un flow." --- **Tabs** divide una sección de pantalla en paneles de contenido intercambiables: el usuario toca una cabecera de pestaña y el panel inferior se actualiza en consecuencia. ## 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 & Paywall Builder ofrece tres plantillas de pestañas listas para usar: - **Segment control** — un selector en forma de pastilla con 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 y checkboxes" description: "Añade toggles de prueba, toggles y checkboxes a tus flows." --- La categoría **Toggle** ofrece tres elementos que los usuarios pulsan para activar o desactivar algo: - **Trial toggle**: un interruptor que alterna el paywall entre productos estándar y productos de prueba. - **Checkbox**: una casilla de verificación con una etiqueta de texto, para consentimiento y confirmación. - **Toggle**: un interruptor con una etiqueta de texto, para cualquier otra opción de activar/desactivar. Para añadir uno, haz clic en **+** en la pantalla de destino, abre **Paywall & Commerce** > **Toggle** y selecciona el elemento. Aparecerá en la raíz del árbol de capas — arrástralo al contenedor de destino en el panel **Layers**. :::note **Basics** > **Checkmarks** también ofrece gráficos de checkbox, radio y toggle. Son iconos estáticos para decorar filas dentro de otro elemento seleccionable, como una tarjeta de producto. Usa los elementos de este artículo cuando sea el usuario quien seleccione algo por sí mismo. ::: ## Alternancia de prueba \{#trial-toggle\} :::warning Apple puede rechazar aplicaciones que usen un interruptor de prueba preseleccionado. Un interruptor configurado en "activado" por defecto puede considerarse 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 interruptor en **desactivado** por defecto y permite que los usuarios activen la prueba por sí mismos. ::: Un interruptor de prueba 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 —como intercambiar grupos de productos, actualizar variables o mostrar y ocultar elementos— de forma instantánea. Para ver una guía completa, incluidas las condiciones que mantienen el producto seleccionado sincronizado con el interruptor, consulta [Crear un paywall con interruptor de prueba](paywall-with-trial-toggle). ## Casilla de verificación \{#checkbox\} Una casilla de verificación emparejada con una etiqueta de texto, prellenada con `I agree to the Terms and Privacy Policy`. Úsala cuando el usuario necesite confirmar algo antes de que el flow continúe. ## Toggle \{#toggle\} Un interruptor emparejado con una etiqueta de texto. Úsalo para opciones de activar/desactivar que no cambian el producto — por ejemplo, activar recordatorios antes del paywall o revelar un bloque de contenido adicional. ## Leer el estado en condiciones \{#read-the-state-in-conditions\} Cada uno de estos elementos es un [elemento seleccionable](flow-selectable-elements), y su grupo determina la variable que refleja su estado: - Grupo **Toggle**: `<group>.isActive` — **True** o **False**. - Grupo **Single choice**: `<group>.selectedOptionId` — el ID del elemento seleccionado, vacío mientras no se haya seleccionado nada. Selecciona el elemento y abre la pestaña **Interactions** para ver a qué grupo pertenece. Para que otros elementos dependan del estado, establece una [acción](onboarding-actions) condicional o una [visibilidad condicional](onboarding-element-visibility) basada en esta variable. Tu app también puede [recibir el estado a través del SDK](ios-flow-input). Cada vez que el usuario lo cambie, el SDK informará del nuevo valor, que tu app podrá procesar internamente o en tu back-end. --- # 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 cinco plantillas para mostrar reseñas, valoraciones y prueba social en un paywall. Cada plantilla es una composición totalmente editable: sustituye el texto de ejemplo 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. ## Carrusel de reseñas \{#review-carousel\} Una fila de tarjetas de reseña por las que el usuario puede deslizarse, con puntos de paginación debajo. Úsalo para mostrar varias citas sin apilarlas en la pantalla. Cada tarjeta es una copia de la tarjeta de reseña, así que edítalas una a una en el panel **Layers**: expande el carrusel, selecciona una tarjeta y reemplaza su puntuación, cita y pie de autor. ## 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** realiza una cuenta regresiva desde una duración fija hasta cero — cuando 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 finaliza \{#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 llegue a cero; por ejemplo, navegar a otra pantalla u ocultar una insignia de descuento. ## Retrasar la aparición del botón de cierre \{#delay-when-a-close-button-appears\} El Flow & Paywall Builder no tiene una opción directa para retrasar la aparición de un botón, a diferencia del antiguo Paywall Builder, que sí contaba con la opción **Show after delay**. Puedes recrear este retraso con un temporizador de cuenta atrás oculto: 1. Añade el botón **Close** a la pantalla (consulta [Botones](paywall-buttons#close-flow)) y establece su **Visibility** en **Hide** en el panel **Design**. 2. Añade un elemento Countdown timer a la misma pantalla y ocúltalo de la misma forma: solo necesitas su lógica de cuenta atrás, no su visualización. 3. Establece la duración del temporizador con el retraso tras el cual debe aparecer el botón de cierre (consulta [Set the duration](#set-the-duration)). 4. En el temporizador, añade un disparador **On timer end** con una acción **Show** que apunte al botón de cierre (consulta [Actions — Show/hide elements](onboarding-actions#showhide-elements)). Cuando el temporizador llega a cero, la acción **Show** muestra el botón de cierre. Sustituye esta solución alternativa por una configuración de retardo nativa si el builder lo incorpora en el futuro. --- # File: onboarding-quizzes --- --- title: "Cuestionarios en flows" description: "Añade cuestionarios interactivos a tus flows de Adapty para recopilar preferencias de usuarios y crear flows personalizados, sin necesidad de código." --- Usa 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. Tu app también puede [recibir las respuestas a través del SDK](ios-flow-input) y procesarlas internamente o en tu back-end. ### 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 & Paywall Builder" description: "Añade elementos de formulario interactivos como campos de texto y casillas de verificación." --- Usa inputs para recopilar datos escritos de los usuarios, como un nombre, una dirección de correo electrónico o una fecha de nacimiento. Almacena las respuestas y referencíalas en otras partes del flow, como 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 valores de entrada \{#use-input-values\} :::tip Tu app también puede recibir valores de entrada. Para recopilarlos, [gestiona el callback de análisis del SDK](ios-flow-input). Cada vez que el usuario sale de un campo de entrada, el callback entrega su valor y tu app puede procesarlo internamente. ::: Cada campo de entrada está disponible automáticamente como variable; no necesitas ninguna configuración ni 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 lugar del flow (por ejemplo, para personalizar textos, rellenar otro campo o condicionar la navegación), 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: builder-navigation-actions --- --- title: "Navegación e interacción" description: "Mueve al usuario entre pantallas e interactúa con él" --- <CustomDocCardList /> El Flow & Paywall Builder te da control total sobre cómo los usuarios se mueven por tu flow y cómo tu app intercambia datos con él, todo sin escribir código. - **[Navegación](onboarding-navigation-branching)**: Guía a los usuarios por las pantallas con rutas estáticas o ramificación dinámica según sus elecciones - **[Acciones](onboarding-actions)**: Define qué ocurre cuando los usuarios pulsan botones o interactúan con elementos - **[Loaders y barras de progreso](builder-loaders-and-progress-bars)**: Muestra indicadores de carga y seguimiento del progreso entre pantallas - **[Variables](onboarding-variables)**: Muestra contenido dinámico referenciando datos recopilados durante el flow - **[Elementos y grupos seleccionables](flow-selectable-elements)**: Haz que los elementos sean seleccionables, organízalos en grupos y usa su estado en condiciones - **[Visibilidad condicional](onboarding-element-visibility)**: Muestra u oculta elementos y pantallas según las respuestas del usuario o condiciones --- # File: onboarding-navigation-branching --- --- title: "Navegación y bifurcación" description: "Guía a los usuarios por las pantallas con rutas estáticas y bifurcación dinámica." --- La navegación y bifurcación te permite guiar a los usuarios a lo largo 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). ::: ## 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 las acciones desencadenadas por las interacciones del usuario en el builder." --- El panel **Interactions** te permite definir cómo responden los elementos de un flow a los 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. **Elemento**: El componente de pantalla que inicia la interacción — un botón, una respuesta de quiz, un campo de texto o cualquier otra cosa. 2. **Disparador**: El evento que activa la lógica, como un toque, la aparición de un elemento o el envío de un formulario. 3. **Acción**: La tarea que el flow ejecuta en respuesta. Un solo disparador puede ejecutar múltiples 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 acciones \{#action-types\} ### 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 redirigir a los usuarios a diferentes páginas según su respuesta en el cuestionario o con los datos que han enviado. Para ello, haz clic en Variable icon y selecciona la variable que quieras usar. Para definir una URL diferente para un idioma concreto, cambia el valor de la celda correspondiente en el [panel **Localizations**](add-paywall-locale-in-adapty-paywall-builder#set-locale-specific-action-urls). - **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 concreto en la pantalla. Esta acción anula el estado inicial definido en **Visibility** en el panel **Design**. Si **Visibility** está configurada como **Hide**, la acción **Show** hará que aparezca. :::important Una acción **Show** o **Hide** sin elemento de destino [bloquea la previsualización y la publicación](flow-common-issues#a-flow-wont-preview-or-publish). Selecciona un destino o elimina la acción. ::: ### Mostrar alerta \{#show-alert\} Muestra una ventana emergente nativa que se puede cerrar. La ventana emergente no pausa el flow. Las acciones en cola con la alerta continúan ejecutándose mientras la alerta está activa. Para las alertas, debes configurar su **Title** y **Message**. En ambos campos puedes usar variables para hacer el contenido 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 previsualización y la publicación](flow-common-issues#a-flow-wont-preview-or-publish). 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 previsualización y la publicación](flow-common-issues#a-flow-wont-preview-or-publish). 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 de **Purchase** sin un **Product** o **Web Paywall URL** de destino [impide la vista previa y la publicación](flow-common-issues#a-flow-wont-preview-or-publish). 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 usan cuando ya han comprado una suscripción en otro dispositivo o tras reinstalar la app, y necesitan recuperar el acceso a sus productos. 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 viene preconfigurada en el enlace **Restore** del preset de botón **Links** (consulta [Configurar compras](paywall-product-block#restore-purchases)). ### Seleccionar producto \{#select-product\} Establece qué producto está seleccionado en un grupo de productos en la pantalla. Úsalo cuando un elemento distinto a la tarjeta de producto deba cambiar la selección; por ejemplo, un toggle que cambia al usuario entre un plan anual y uno semanal. Cuando la acción se ejecuta, la selección se mueve al producto de destino. El resaltado de la tarjeta, cualquier texto vinculado a `products.selectedProduct` y el destino de la acción [Purchase](#purchase) siguen la nueva selección. Para esta acción, establece **Product** en el elemento de producto de destino. El selector muestra únicamente los elementos de producto que pertenecen a un [grupo seleccionable](flow-selectable-elements) de producto en la misma pantalla. Si la pantalla tiene varios grupos de productos, el selector muestra los productos válidos de todos ellos. Los productos de otras pantallas, los elementos de producto que están fuera de un grupo y otros tipos de elementos no aparecen. Cada producto se muestra con su título de producto en Adapty, no con el nombre del elemento. Puedes añadir también **Select product** dentro de una [acción condicional](#conditional-actions) — por ejemplo, para seleccionar un producto cuando un interruptor está activado y otro cuando está desactivado. **Select product** no está disponible en dos lugares: - **Dentro de componentes reutilizables**: La selección de producto funciona por pantalla, por lo que una acción dentro de un componente no puede resolver un producto. Si una acción guardada termina dentro de un componente, el campo **Product** muestra una advertencia en lugar del selector. - **En el propio disparador On tap de una tarjeta de producto**: Al pulsar una tarjeta ya se selecciona, por lo que una selección explícita en la misma interacción generaría un conflicto. La acción sigue disponible en esa tarjeta para otros disparadores, y en cualquier otro elemento. :::important Una acción **Select product** [bloquea la previsualización y publicación](flow-common-issues#a-flow-wont-preview-or-publish) cuando el producto de destino falta, está en otra pantalla o ya no pertenece a un grupo de productos. Asigna un producto válido o elimina la acción. ::: ## Orden de las acciones \{#action-order\} Las acciones se inician en el orden en que aparecen en el panel **Interactions**, de arriba a abajo. El flow lanza cada acción y continúa sin esperar a que termine, por lo que una navegación o alerta nunca bloquea las acciones que hay debajo. Esto afecta principalmente a las siguientes acciones: - **Set variable**: El nuevo valor se establece antes de que comience la siguiente acción, por lo que una acción condicional ubicada después leerá el valor actualizado. - **Navigate to screen**: Las acciones que vienen después siguen ejecutándose, pero cuando el flow lanza la siguiente acción, el usuario puede que ya esté en la pantalla siguiente. Si esa acción muestra u oculta un elemento de la pantalla que acaba de dejar, el usuario no lo verá. Coloca las acciones que modifiquen la pantalla actual antes de la navegación. - **Show alert**: Las acciones que vienen después se ejecutan mientras la alerta está abierta. Si una de ellas navega a una pantalla diferente, la alerta permanece abierta durante la navegación. ## 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. ### Configura 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 de paywall](handle-paywall-actions) para ver 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](flow-common-issues#a-flow-wont-preview-or-publish). 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\} Usa las acciones condicionales para dividir el flow en diferentes rutas según los datos del usuario. Algunos casos de uso habituales son: - Tienes un cuestionario en la pantalla y quieres dirigir a los usuarios a distintas pantallas según sus respuestas. En ese caso, añade una acción condicional a un botón. - Quieres ofrecer diferentes productos y ofertas a distintos grupos de usuarios. Colócalos en pantallas separadas y configura condiciones para un botón de navegación. - Quieres omitir ciertos pasos para los usuarios que ya han completado 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, cuando se cumple la condición no ocurre nada. - Una regla incompleta (sin operador o valor) [bloquea la previsualización y publicación](flow-common-issues#a-flow-wont-preview-or-publish). ::: Para cada regla, selecciona una variable a evaluar y una acción a ejecutar. Puedes definir más de una acción por regla. No puedes seleccionar un atributo de perfil ni una [etiqueta personalizada](flow-custom-tags) en una condición. Para mostrar pantallas distintas según un atributo de perfil, crea un flow independiente para cada grupo de usuarios. Luego, en el placement, muestra cada flow a una [audiencia](audience) definida a partir de ese atributo. Si necesitas usar un valor de tu app, como el valor de una etiqueta personalizada, [defínelo como atributo personalizado](setting-user-attributes) antes de crear la audiencia. :::important El flow ejecuta solo una regla: la primera que coincida. Si necesitas ejecutar tanto **IF** como **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). ## Solución de problemas \{#troubleshooting\} Cualquier acción con campos obligatorios sin completar bloquea la vista previa y la publicación. Consulta [Problemas comunes](flow-common-issues#a-flow-wont-preview-or-publish) para ver la lista completa. --- # File: builder-loaders-and-progress-bars --- --- title: "Indicadores de progreso y cargadores" description: "Muestra el progreso por pasos y los estados de carga en un flow." --- La categoría **Progress** proporciona dos tipos de elementos: uno para el progreso por pasos a través de un flow de varias pantallas, 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. :::warning Un loader requiere un **disparador** para aparecer y desaparecer. Abre la pestaña **Interactions** para configurar esta lógica — por ejemplo, muéstralo después de que el usuario envíe un cuestionario. ::: La configuración de **Duration** de un loader controla la duración de su animación de relleno o giro, no el tiempo que la pantalla permanece visible. Un disparador **On screen appear** con una acción **Navigate to screen** avanza de inmediato, sin esperar a que termine la animación. Para mantener una pantalla de carga durante un tiempo determinado, añade un [Timer](flow-timer) con un disparador **On timer end** que navegue a la siguiente pantalla. La duración del timer se establece en segundos enteros. --- # 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 los elementos y personalizar el contenido de las pantallas. Para abrir la vista de gestión de variables, haz clic en Variables en el panel izquierdo. La vista incluye tres pestañas: - **[Personalizado](#custom-variables)**: Variables in-flow y [etiquetas personalizadas](flow-custom-tags) — variables que puedes definir desde tu app. - **[Producto](#product-variables)**: Variables integradas que obtienen datos de productos y ofertas localizadas del store. - **[Elemento](#element-variables)**: Variables vinculadas a los estados de los elementos del canvas. ## Variables personalizadas \{#custom-variables\} ### Crear una variable personalizada \{#create-a-custom-variable\} 1. Abre el panel Variables Variables en el flow builder. 2. Añade una nueva variable: haz clic en Plus. 3. Configura el **Name** de la variable. 4. (Opcional) Para distinguir entre distintas variables, rellena el campo **Description**. Al pasar el cursor sobre el nombre de la variable en el builder, verás la descripción. 5. Selecciona el **Value Type**: **String**, **Number** o **Boolean**. Solo puedes insertar una variable en un elemento de texto si su tipo es String o Number. 6. En el campo **Initial value**, introduce el valor que tendrá la variable cuando arranque el flow. 7. (Opcional, solo para variables **String**) Para establecer el valor de la variable desde el código de tu app, marca la casilla **Value comes from outside the app**. Esta acción es irreversible y bloquea el **Value Type**. La variable se convierte en un [custom tag](flow-custom-tags): tu app deberá asignarle un valor al cargar el flow. Si no se pasa ningún valor, los usuarios verán el valor inicial como respaldo. 8. Haz clic en **Create variable**. Las variables son específicas de cada flow. Para usar una variable en otro flow, créala también allí. :::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 [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 variable** 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 variable**. 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 extraen datos localizados directamente de las tiendas. Úsalas en campos de texto para mostrar precios, títulos y detalles de ofertas localizados, o en condiciones, para mostrar u ocultar contenido según la elegibilidad de la oferta. Cuando insertas una variable para un producto específico, el builder añade ese producto a la lista de la pantalla en el panel [**Products**](paywall-product-block#add-products-to-a-screen). | Variable | Description | Example | | :--- | :--- | :--- | | `prod_title` | Título localizado del producto | Premium Subscription | | `prod_price` | Precio localizado de un período de facturación | $9.99 | | `prod_price_per_day` | Precio de la suscripción convertido a un día. Vacío para los productos que no son suscripciones. | $0.33 | | `prod_price_per_week` | Precio de la suscripción convertido a una semana. Vacío para los productos que no son suscripciones. | $2.33 | | `prod_price_per_month` | Precio de la suscripción convertido a un mes. Vacío para los productos que no son suscripciones. | $9.99 | | `prod_price_per_year` | Precio de la suscripción convertido a un año. Vacío para los productos que no son suscripciones. | $119.88 | | `prod_billing_price` | Precio localizado de un pago. Difiere de `prod_price` solo para el [plan de compromiso de 12 meses](#12-month-commitment-plan). | $9.99 | | `prod_billing_period` | Período localizado de un pago. Difiere del período de suscripción solo para el plan de compromiso de 12 meses. Vacío para los productos que no son suscripciones. | 1 month | | `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 las ofertas de prueba gratuita y de 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 | | `is_monthly_plan` | Devuelve `true` si el usuario adquiere el producto bajo el plan de compromiso de 12 meses. | true | Las variables `prod_price_per_*` convierten el precio del período de facturación del producto al período que indiques. La conversión utiliza duraciones de período fijas, no fechas de calendario: - 1 semana = 7 días - 1 mes = 30 días - 1 año = 12 meses, o 365 días Para una suscripción mensual de $59,99, `prod_price_per_week` devuelve $14,00: $59,99 × 7 ÷ 30 = $13,9977, redondeado para su visualización. Un mes equivale a 30 / 7 ≈ 4,2857 semanas, por lo que multiplicar el valor semanal por 4 no devuelve el precio original. :::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 tipo Pago único anticipado y Prueba gratuita, `offer_billing_period` y `offer_full_duration` devuelven el mismo valor. Para Pago progresivo, difieren porque el período de facturación es de una semana, pero la duración total es de tres semanas. `offer_price`, `offer_billing_period` y `offer_full_duration` requieren un producto con una oferta. Sin ella, el flow [no se publica](flow-common-issues#a-flow-wont-preview-or-publish). Todos estos valores provienen del store, por lo que la vista previa del builder solo resuelve algunos de ellos: los valores de oferta permanecen sin resolver en el canvas, y `is_free_trial`, `is_pay_up_front` e `is_pay_as_you_go` siguen la oferta que seleccionaste en la tarjeta de producto. Consulta [Vista previa de productos y ofertas](paywall-device-compatibility-preview#preview-products-and-offers) para previsualizar cada tipo de oferta mientras diseñas. :::note Para obtener más información sobre las ofertas y cómo configurarlas, consulta [Offers](offers). ::: ### Plan de compromiso de 12 meses \{#12-month-commitment-plan\} Con un [plan de compromiso de 12 meses](set-up-12-month-commitment), los suscriptores pagan un producto anual en 12 cuotas mensuales. Apple requiere que el paywall muestre tanto el pago mensual como el total anual antes de la compra. Para mostrar ambos, inserta `prod_billing_price` y `prod_billing_period` para el pago mensual, y `prod_price` para el total anual. Por ejemplo: "$9.99 al mes durante 12 meses, $119.88 en total". Los valores dependen de si el usuario puede comprar el plan de compromiso: | Variable | Se aplica el plan de compromiso | El plan de compromiso no aplica, o plan por adelantado | | :--- | :--- | :--- | | `prod_billing_price` | $9.99 | Igual que `prod_price` | | `prod_billing_period` | 1 mes | 1 año | | `prod_price` | $119.88 | $99.99 | | `is_monthly_plan` | `true` | `false` | Los usuarios que [no pueden comprar el plan de compromiso](set-up-12-month-commitment#4-add-to-a-flow-or-custom-paywall) adquieren el plan por adelantado — por ejemplo, en dispositivos Apple anteriores a iOS 26.4, o en países donde no ofreces el plan. :::important `prod_billing_price` y `prod_billing_period` requieren Adapty SDK v4.3.1 o posterior. En versiones anteriores del SDK, el texto con estas variables muestra `prod_price` y el período de suscripción en su lugar. Para una compra de plan con compromiso en Adapty SDK v4.3.0, eso corresponde al total anual y "1 year". ::: ## Variables de elemento \{#element-variables\} Las variables de elemento capturan las elecciones del usuario: qué seleccionó en los cuestionarios, en qué pestaña se encuentra y si un toggle está activado. Los tipos de variables de elemento dependen del grupo: - **Single choice**: Cuestionarios de opción única, pestañas y toggles: - `selectedOptionId`: ID del elemento para usar en condiciones - `selectedOptionTitle`: Título del elemento para usar en texto dinámico - **Multi-choice**: Cuestionarios de opción múltiple: - `selectedOptionIds`: IDs del elemento para usar en condiciones - `selectedOptionTitles`: Títulos del elemento para usar en texto dinámico - **Toggle**: Elementos que alternan entre activado y desactivado: - `isActive`: Valor booleano - **Product**: Tarjetas de producto: - `selectedProduct`: El producto seleccionado, para usar en condiciones 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 ## Usa variables en el texto \{#use-variables-in-text\} Para insertar una variable en un elemento de texto: 1. Selecciona un elemento de texto en el lienzo. 2. En la pestaña **Design**, busca el campo **Content** y escribe tu texto. 3. Haz clic en Variables en el campo. 4. Selecciona una variable. En la pestaña **Product**, los productos ya incluidos en el panel **Products** aparecen primero. El resto de tu catálogo se muestra a continuación en **Other products**. :::tip También puedes usar variables en otros elementos: - Usa variables en enlaces y alertas para hacerlas dinámicas - 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. Los valores de las variables personalizadas no se pueden localizar: la variable muestra el mismo valor en todos los idiomas. Para traducir el texto reutilizado, en el [panel **Localizations**](add-paywall-locale-in-adapty-paywall-builder#translate-strings-manually), reemplaza la variable con la traducción en cada fila que la contenga. --- # File: flow-custom-tags --- --- title: "Etiquetas personalizadas" description: "Pasa un valor desde tu app a un flow y muéstralo en un elemento de texto." --- Una etiqueta personalizada es una [variable personalizada](onboarding-variables#custom-variables) cuyo valor proviene de tu app. Cuando seleccionas **Value comes from outside the app** en la configuración de una variable, tu app puede establecer su valor a través del SDK de Adapty. Por ejemplo, un elemento de texto contiene "Coins:" y una etiqueta personalizada. Si tu app pasa "120" para la etiqueta, los usuarios verán "Coins: 120". Usa etiquetas personalizadas para mostrar: - **Quién es el usuario**: Un nombre de usuario en un saludo, como "¡Bienvenido de nuevo, DragonSlayer42!" - **Qué tiene el usuario**: El número de días en una racha, o el nivel de un jugador. - **Qué calcula tu app**: Un descuento para un pack más grande, o un [IMC](ios-flow-custom-tags#example-show-a-bmi-from-onboarding-answers) basado en las respuestas del usuario en un flow anterior. ## Valores y valores de respaldo \{#values-and-fallbacks\} Tu app identifica cada valor por el nombre de su etiqueta personalizada. El valor siempre es una cadena de texto. Adapty no formatea ni traduce la cadena, así que tu app debe convertir cada valor en legible antes de que aparezca en el flow. Por ejemplo, pasa "1,250" en lugar de "1250.0". Los usuarios ven el **Valor inicial** de una etiqueta personalizada como valor de respaldo en los siguientes casos: - Tu app no pasa ningún valor para la etiqueta. - El usuario no ha actualizado tu app a una versión que envíe el valor. - El nombre en el código de tu app no coincide con el nombre completo de la etiqueta, incluidas las mayúsculas y minúsculas. - Otra etiqueta personalizada en el mismo elemento de texto no tiene valor. En ese caso, todas las etiquetas personalizadas de ese elemento de texto muestran su valor inicial. Para no mostrar nada en lugar de una etiqueta, pasa una cadena vacía. Adapty trata una cadena vacía como un valor, por lo que el valor de respaldo no aparece. ## 1. Crea una etiqueta personalizada \{#create-a-custom-tag\} 1. Abre el panel Variables Variables en el flow builder. 2. Añade una nueva variable haciendo clic en Plus. 3. Configura el **Name** de la variable. El código de tu app debe referenciarlo exactamente, incluyendo mayúsculas y minúsculas. - **Characters**: Usa letras latinas (A–Z y a–z), dígitos, `_` y `-`. Para colocar la etiqueta en un grupo, empieza su nombre con el grupo y un punto, por ejemplo `user.streak`. - **Reserved names**: En cualquier combinación de mayúsculas y minúsculas, no uses un nombre reservado como `PRICE`, `TITLE`, `VALUE`, ni uno que empiece por `TIMER_`. - **Uniqueness**: Usa un nombre único dentro del flow. 4. (Opcional) Para distinguir entre distintas variables, rellena el campo **Description**. Al pasar el cursor sobre el nombre de la variable en el builder, verás la descripción. 5. En el campo **Initial value**, introduce el valor de respaldo que verán los usuarios cuando tu app no pase ningún valor: - **Una palabra que encaje con todos los usuarios**: Para la etiqueta `username` en un saludo, introduce `friend`. Los usuarios no autenticados verán "Welcome back, friend!". Ten en cuenta que el valor inicial no puede localizarse. - **Un guion para un saldo o un contador**: Para evitar mostrar un valor de saldo incorrecto a los usuarios no autenticados, introduce un guion (`—`). 6. (Solo variables de tipo **String**) Selecciona la casilla **Value comes from outside the app**. Esta acción es irreversible y bloquea el **Value Type**. 7. Haz clic en **Create variable**. Las variables son específicas de cada flow. Para usar una etiqueta personalizada en otro flow, créala allí con el mismo nombre. ### Reemplazar una variable con una etiqueta personalizada \{#replace-a-variable-with-a-custom-tag\} Solo puedes marcar la casilla **Value comes from outside the app** al crear una variable. Para mostrar el valor de tu app en lugar de una variable personalizada normal, como la de un [paywall convertido](convert-paywall-to-flow), reemplaza la variable con una etiqueta personalizada: 1. Elimina la variable y todas sus referencias en el flow. 2. [Crea una etiqueta personalizada](#create-a-custom-tag) con el mismo nombre. 3. [Inserta la etiqueta personalizada](#add-a-custom-tag-to-a-text-element) en cada elemento de texto que usaba la variable. ## 2. Añade una etiqueta personalizada a un elemento de texto \{#add-a-custom-tag-to-a-text-element\} Selecciona el elemento de texto. En la sección **Content**, haz clic en Variables **Add variable** y selecciona la etiqueta en la pestaña **Custom**. Puedes insertar etiquetas personalizadas en cada rama del [texto condicional](onboarding-text#add-conditional-text), pero no puedes seleccionarlas en sus condiciones **if**. ## 3. Pasa los valores desde tu app \{#pass-the-values-from-your-app\} El código de tu app debe usar el nombre exacto de cada etiqueta personalizada, incluido su grupo. Por ejemplo, si el grupo `user` contiene una etiqueta `streak`, el nombre exacto de la etiqueta es `user.streak`. El código de configuración depende de tu plataforma: - [iOS](ios-flow-custom-tags) - [Android](android-flow-custom-tags) - [React Native](react-native-flow-custom-tags) - [Flutter](flutter-flow-custom-tags) - [Unity](unity-flow-custom-tags) - [Kotlin Multiplatform](kmp-flow-custom-tags) - [Capacitor](capacitor-flow-custom-tags) Para probar tus etiquetas personalizadas, abre el flow en tu app después de añadir el código que establece sus valores. El canvas del builder y la [vista previa en dispositivos](paywall-device-compatibility-preview#preview-on-devices) solo muestran los valores iniciales: la app móvil de Adapty no establece los valores de las etiquetas personalizadas. ## Limitaciones \{#limitations\} - **Solo en elementos de texto**: Solo puedes insertar tags personalizados en elementos de texto. No puedes insertarlos en: - Acciones de **Set variable** - Acciones de **Show alert** - Acciones de **Open URL** - Marcadores de posición de inputs - **Sin condiciones en tags personalizados**: No puedes seleccionar un tag personalizado en una condición ni en visibilidad condicional. Para decidir qué frases, elementos y pantallas ven los usuarios según un valor de tu app: - **Frase**: Construye la frase en el código de tu app y pásala como valor del tag. Por ejemplo, para un tag `coins_message`, pasa "You have 120 coins" a los jugadores con monedas y "Get your first coins" al resto. - **Elementos y pantallas**: [Establece el valor como atributo personalizado](setting-user-attributes). Luego muestra un flow independiente a una [audiencia](audience) creada a partir de ese atributo. - **No hay método SDK para actualizar los valores en un flow abierto**: Para mostrar un resultado basado en lo que los usuarios introducen en un flow, pasa el resultado al siguiente flow, como en el [ejemplo de IMC](ios-flow-custom-tags#example-show-a-bmi-from-onboarding-answers). --- # File: onboarding-element-visibility --- --- title: "Visibilidad condicional" description: "Muestra u oculta elementos según condiciones." --- Puedes controlar si un elemento aparece 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 mediante la [acción](onboarding-actions) **Show** o **Hide**, dicha acción anula la condición de **Visibility** establecida en ese elemento. Usa las condiciones de **Visibility** para los elementos que siempre deben mostrarse u ocultarse 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 del 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: "Muestra diferentes colores y fondos en tu flow en dispositivos con modo oscuro activado." --- Cuando el dispositivo del usuario tiene el modo oscuro activado, tu flow puede cambiar sus colores y fondos en consecuencia — mostrar un degradado más oscuro o una imagen acorde al tema. Para configurarlo, usas [estilos](builder-reusable-styles#color-styles) con una versión independiente para cada tema: claro y oscuro. Cada versión es un relleno: un color sólido, degradado, imagen o vídeo. Un único relleno puede combinar varios elementos: una capa de color semitransparente puede teñir la imagen que tiene debajo. Si no hay diferencia entre los dos temas, ambas versiones pueden ser idénticas. Todo lo que tienes que hacer es aplicar el estilo a un elemento, y el flow usa automáticamente la versión correcta según el modo del dispositivo. ## Añadir un tema oscuro \{#add-a-dark-theme\} De forma predeterminada, un nuevo flow tiene un tema claro. Para añadir un tema oscuro: 1. Abre el panel **Styles** Styles y selecciona la pestaña **Colors**. 2. Haz clic en el icono Color theme actions para abrir el menú de gestión de temas. 3. Selecciona **Add dark theme**. La variante oscura se añade a cada estilo de color, con dos valores idénticos. Ahora puedes [configurar el tema oscuro](#configure-the-dark-theme). ## Configura el tema oscuro \{#configure-the-dark-theme\} Después de añadir el tema oscuro, cada entrada de la lista Color Style tiene dos configuraciones específicas por tema: **Light** y **Dark**. Para editar un estilo: 1. Abre el panel **Styles** Styles 2. Selecciona la pestaña **Colors**. 3. Haz clic en una entrada de estilo. Si el estilo necesario aún no existe, [crea uno nuevo](builder-reusable-styles#create-a-color-style). 4. El valor Light y el valor Dark aparecen uno tras otro. Cada valor puede contener varias [capas](builder-reusable-styles#multi-layer-styles). Haz clic en la muestra de color para configurar la capa según su tipo de relleno: - [Color sólido](builder-reusable-styles#solid-colors) - [Degradado](builder-reusable-styles#gradients) - [Imagen o vídeo](builder-reusable-styles#images-and-videos) 5. Haz clic en **Save**. Los valores Light y Dark pueden usar [diferentes tipos de relleno](#mix-and-match-fill-types). ### Mezclar tipos de relleno \{#mix-and-match-fill-types\} Puedes combinar tipos de relleno entre las dos versiones de un estilo: - **Colores sólidos, degradados e imágenes**: En cualquier combinación. Por ejemplo, un fondo puede ser un color sólido en el modo claro y una imagen en el modo oscuro. - **Vídeos**: Si la versión clara incluye un vídeo, la versión oscura también debe incluir un vídeo, y viceversa. Los dos vídeos pueden ser diferentes. ## Aplicar el tema a un elemento \{#apply-the-theme-to-an-element\} Si una propiedad de un elemento no usa un estilo de color, el tema oscuro no se aplica a ella. Los elementos de imagen y vídeo no tienen estilo de color: para cambiar su archivo en modo oscuro, [añade un archivo oscuro](#show-a-different-image-or-video-in-dark-mode). Para que un elemento siga el tema, haz clic en la muestra de su capa de relleno, color de texto, color de borde o color de icono. Sustituye el valor estático por el estilo haciendo clic en la pestaña **Styles** y seleccionando la entrada de estilo. Si ningún estilo tiene el valor que necesitas, [guarda el valor actual como estilo](builder-reusable-styles#create-a-color-style). El fondo de una pantalla sigue el tema de la misma manera: aplica el estilo a su capa de relleno en [**Screen settings**](paywall-layout-and-products#screen-background). Para ver los pasos, consulta [Show a different background in dark mode](paywall-head-picture#show-a-different-background-in-dark-mode). Para [aplicar un estilo](builder-reusable-styles#apply-a-color-style) a muchos elementos de una pantalla, consulta al [AI Editor](flow-ai-editor), por ejemplo: `Apply the Card background style to all cards`. ## Vista previa del flow en modo oscuro \{#preview-the-flow-in-dark-mode\} Haz clic en los iconos de tema que aparecen debajo del lienzo para cambiar entre Dark theme **Dark mode** y Light theme **Light mode**. Los iconos solo aparecen si el flow tiene un tema oscuro, o un elemento de imagen o vídeo con un archivo oscuro. ## Mostrar una imagen o vídeo diferente en modo oscuro \{#show-a-different-image-or-video-in-dark-mode\} Un elemento de imagen o vídeo muestra su archivo en ambos modos hasta que añades un archivo específico para el modo oscuro. Para mostrar un archivo diferente en modo oscuro: 1. Selecciona el elemento de imagen o vídeo. 2. En la sección **Image** o **Video** del panel de propiedades, selecciona la pestaña Moon **Dark**. El lienzo cambia a la vista previa del modo oscuro. 3. Sube el archivo para el modo oscuro. Para editar el archivo en modo claro de nuevo, selecciona la pestaña Sun **Light**. ## Configura el tema de la barra de estado \{#set-the-status-bar-theme\} Los iconos de hora y batería de la barra de estado pueden no combinar con los colores de tu flow en alguno de los modos. Para que los iconos se vean bien, configura el **Status bar theme** en [**Screen settings**](paywall-layout-and-products#screen-settings) con el interruptor **Status bar** activado: - Sun: Iconos blancos, para un fondo que es oscuro en ambos modos. - Moon: Iconos negros, para un fondo que es claro en ambos modos. - Phone: Iconos que siguen la configuración del dispositivo, para un fondo que es claro en un modo y oscuro en el otro. ## Eliminar un tema \{#delete-a-theme\} 1. Abre el panel **Styles** Styles y selecciona la pestaña **Colors**. 2. Haz clic en el icono de ajustes Color theme actions para abrir el menú de gestión de temas. 3. Selecciona una opción del menú: - **Delete Dark theme**: Conserva la versión clara de cada estilo de color en ambos modos. - **Delete Light theme**: Conserva la versión oscura de cada estilo de color en ambos modos. --- # File: paywall-localization --- --- title: "Localización" description: "Traduce las pantallas de un flow y los valores de su Remote Config para cada mercado en el que vendes." --- Añade idiomas a un flow para que cada usuario lea en su propio idioma. La localización se hace en dos lugares: 1. [En el builder](add-paywall-locale-in-adapty-paywall-builder): traduce el texto y cambia los archivos multimedia en cada pantalla. 2. [En el Remote Config](add-flow-remote-config-locale): traduce los valores que tu app lee en tiempo de ejecución. Para controlar qué traducción muestra el SDK, pasa un locale al cargar la configuración de la vista. Consulta [Localizaciones y códigos de locale](localizations-and-locale-codes). --- # File: add-paywall-locale-in-adapty-paywall-builder --- --- title: "Añadir idioma en Flow & Paywall Builder" description: "Añade contenido localizado en Flow & Paywall Builder de Adapty para llegar a usuarios de todo el mundo en su idioma." --- Puedes localizar tu flow para mostrar contenido específico según el idioma de cada usuario internacional. Puedes traducir o personalizar lo siguiente: - Todos los textos del flow. - La [URL de cada acción **Open URL**](#action-urls). - Imágenes y vídeos, con un [archivo independiente por idioma](#images-and-videos). En el panel Localizations **Localizations**, todo este contenido aparece en una sola tabla, con una columna para cada idioma. Desde allí, puedes traducir las celdas a mano, con IA o exportándolas para un traductor externo. Para mostrar un idioma a los usuarios, tu app [debe pasar el código de idioma](localizations-and-locale-codes) en la [solicitud de configuración de vista del flow](get-pb-paywalls#fetch-the-view-configuration). Los valores sin traducción utilizan como respaldo el contenido especificado para el idioma predeterminado (inglés, a menos que [establezcas otro idioma como predeterminado](#set-the-default-locale)). :::tip Termina de configurar tu flow en el idioma predeterminado antes de añadir otros idiomas. ::: ## Qué puedes localizar \{#what-you-can-localize\} ### Contenido de texto El panel **Localizations** tiene una fila para cada cadena de texto del flow, incluyendo: - El contenido de cada elemento de texto, como la etiqueta de un botón. - El **Placeholder** de cada campo de entrada. - El título y el mensaje de cada acción **Show alert**. :::note Lo que muestra una [variable](onboarding-variables) en otros idiomas depende de dónde provenga su valor: - **Variables de producto**: La store devuelve sus valores ya localizados. Una cadena que solo contiene una variable de producto, como un precio, no necesita traducción. - **Variables de elemento y personalizadas**: Sus valores no pueden localizarse. Por ejemplo, `selectedOptionTitle` muestra el **Título del elemento** de la opción seleccionada exactamente como lo introdujiste. Usa texto estático o condicional en su lugar. ::: ### Texto condicional \{#conditional-text\} Los elementos de texto pueden mostrar [contenido diferente](onboarding-text#add-conditional-text) según las condiciones que establezcas. Puedes localizar cada rama del texto condicional. :::note Las condiciones son las mismas en todos los idiomas. Si una condición compara una variable con una cadena de texto, esa cadena no se localiza. ::: ### URLs de acción \{#action-urls\} Una acción **Open URL** puede abrir una URL diferente en cada idioma del flow; por ejemplo, una página de ayuda en el idioma del usuario. Si no configuras una URL alternativa para el idioma del usuario, verá la URL predeterminada. ### Imágenes y vídeos \{#images-and-videos\} Lo siguiente puede mostrar un [archivo diferente en cada idioma](#localize-images-and-videos): - Elementos de imagen y vídeo. - Capas de imagen y vídeo en el [fondo](builder-styling#fill) de una pantalla o elemento. - Capas de imagen y vídeo de un [estilo de color](builder-reusable-styles#color-styles). En un flow con [tema oscuro](paywall-dark-mode), las versiones clara y oscura tienen cada una su propio archivo por idioma. No se pueden localizar los iconos. Si no subes un archivo para el idioma de un usuario, verán el archivo predeterminado. En el panel **Localizations**, la fila seguirá mostrando el indicador **Missing**. Si el archivo predeterminado funciona en todos los idiomas, [marca la fila como Hecha](#manage-a-row) para eliminar el indicador. ## Gestionar localizaciones y traducciones \{#manage-localizations-and-translations\} ### Añadir y configurar un idioma \{#add-and-set-up-a-locale\} 1. Abre el panel Localizations Localizations a la izquierda. Haz clic en **Add locale**. 2. En el campo **Select languages**, selecciona uno o más idiomas. 3. (Opcional) Para traducir tu flow a los nuevos idiomas de inmediato, selecciona **Translate content from default locale using AI**. 4. Haz clic en **Add locales**. En la tabla de localización, cada locale es una columna y cada fragmento de [contenido localizable](#what-you-can-localize) es una fila. La tabla agrupa las filas en secciones por cada pantalla. El panel lateral muestra tu progreso en cada idioma, expresado como el porcentaje de celdas traducidas. El encabezado de sección de cada pantalla muestra el progreso de esa pantalla en todos los idiomas. El panel cuenta una celda como traducida en cuanto tiene un valor, aunque ese valor sea el texto del idioma predeterminado sin traducir. Si [marcas una fila](#manage-a-row) como Hecha o Como faltante, la marca determina el estado de la fila con independencia del contenido de las celdas. Una fila con una traducción faltante muestra el distintivo **Missing**. Una celda sin un valor específico para el idioma muestra el contenido del idioma predeterminado. ### Traducir cadenas con IA \{#translate-strings-with-ai\} La IA traduce las cadenas del idioma predeterminado a los demás idiomas. No traduce [URLs de acción](#action-urls), imágenes ni vídeos. Abre **AI Translate** en el encabezado del panel y selecciona una de las siguientes opciones: - **Translate missing only**: Traduce las celdas que no tienen traducción y mantiene las existentes. Si [marcas una fila como Missing](#manage-a-row), vuelve a traducir todas las celdas de esa fila. Si marcas una fila como Done, omite esa fila. - **Translate all**: Traduce todas las celdas y reemplaza las traducciones existentes, incluidas las que editaste manualmente. Solo puedes traducir las filas que muestren los [filtros](#filter-rows). Para traducir solo algunas filas o locales, selecciona primero sus casillas de verificación. Si una traducción de IA modifica una variable o el formato del texto, Adapty descarta esa traducción y conserva el valor anterior de la celda. ### Establecer la configuración regional predeterminada \{#set-the-default-locale\} El icono de chincheta Pin marca la configuración regional predeterminada. Contiene el contenido de origen y sirve como alternativa para las traducciones que falten. Todos los flows comienzan con inglés como configuración regional predeterminada y única. Antes de cambiar la configuración regional predeterminada, rellena su sustituta con las traducciones. Si una configuración regional está incompleta cuando pasa a ser la predeterminada, Adapty la rellena con el contenido de la configuración regional predeterminada anterior. Los campos dejarán de mostrar las traducciones como **Missing**. Para cambiar la configuración regional predeterminada, en el encabezado de columna de la configuración regional de destino, abre **Locale actions** y selecciona **Set as default**. Si borras el contenido de un elemento en la configuración regional predeterminada, los usuarios de otras configuraciones regionales seguirán viendo su contenido específico, si lo tienen. Para eliminar también ese contenido, [restablece la fila](#manage-a-row) en el panel **Localizations**. ### Eliminar un idioma \{#delete-a-locale\} Para eliminar un idioma, abre **Locale actions** en el encabezado de su columna y selecciona **Delete locale**. Adapty eliminará todo el contenido específico de ese idioma, incluidas imágenes y vídeos. Para restaurarlo, deshaz la eliminación. Para eliminar el idioma predeterminado, primero [convierte otro idioma en el predeterminado](#set-the-default-locale). ### Acciones de la tabla \{#table-actions\} Puedes modificar varias celdas en una sola operación, y un único deshacer revierte todos los cambios. #### Filtrar filas \{#filter-rows\} Establece los filtros en la barra lateral del panel para limitar las filas que muestra la tabla: - **Screens** — Limita la selección de pantallas . - **Status** > **Missing only** — Muestra solo las filas con el distintivo **Missing**. Para buscar filas por palabra clave, haz clic en **Search** en el encabezado del panel. La tabla mostrará únicamente las filas que contengan esa palabra clave en el texto o nombre de archivo de cualquier idioma. Las acciones masivas solo afectan a las filas visibles. Al activar el filtro, reduces el alcance de las acciones masivas. Para traducir o restablecer un locale completo o el flow completo, desactiva los filtros y borra la búsqueda primero. #### Gestionar un locale \{#manage-a-locale\} Para gestionar de forma masiva el contenido de un solo locale, pasa el cursor por encima de su encabezado de columna y abre el menú contextual de la columna Locale actions: - **Set as default**: Establece el locale como el [locale predeterminado](#set-the-default-locale). - **Import** y **Export**: Importa o exporta el [archivo de traducción](#localize-content-externally) del locale. - **AI Translate locale**: Traduce toda la columna del locale desde el locale predeterminado [con IA](#translate-strings-with-ai). - **AI Translate missing only**: Traduce las celdas vacías del locale [con IA](#translate-strings-with-ai). - **Reset to default (…)**: Elimina todo el contenido específico del locale de su columna. - **Delete locale**: [Elimina el locale y su contenido](#delete-a-locale). #### Gestionar una fila \{#manage-a-row\} Para gestionar de forma masiva el contenido de una sola fila, abre el menú contextual de la fila Menú contextual de fila: - **Mark as Done** o **Mark as Missing**: Establece el estado de la fila manualmente, independientemente del contenido de las celdas. Por ejemplo, marca un nombre de marca como **Done** si no necesita traducción. - **AI Translate missing only**: Traduce las celdas vacías de la fila [con IA](#translate-strings-with-ai). - **AI Translate row**: Traduce toda la fila desde el idioma predeterminado [con IA](#translate-strings-with-ai). - **Reset to default**: Elimina todo el contenido específico del idioma de la fila. Cuando editas el texto del idioma predeterminado, Adapty borra la marca **Done** de la fila. #### Acciones de celda \{#cell-actions\} Abre el menú contextual de celda Menú contextual de celda para gestionar el valor de una celda individual: - **Add variable**: Inserta una [variable](onboarding-variables) en la celda. - **Copy** y **Paste here**: Copia el valor de la celda, con su formato y variables, y lo pega en otra celda. - **AI Translate**: Traduce la celda [con IA](#translate-strings-with-ai). - **Push value to all locales**: Copia el valor de la celda en todos los demás idiomas excepto el predeterminado. Si editas la celda después, tendrás que volver a hacer push del valor. - **Clear translation**: Vacía la celda, de modo que los usuarios de ese idioma ven el texto del idioma predeterminado. ## Localizar contenido en el builder \{#localize-content-in-the-builder\} Para traducir cadenas de texto y establecer URLs de acción, abre el panel **Localizations**. Puedes [traducirlas manualmente](#translate-strings-manually), [con IA](#translate-strings-with-ai), o [exportar el archivo de localización](#localize-content-externally) para usarlo con un traductor externo. Para localizar imágenes y vídeos, [sube un archivo para cada idioma](#localize-images-and-videos). ### Traducir cadenas manualmente \{#translate-strings-manually\} La tabla del panel **Localizations** incluye todas las cadenas del flow: - Texto estático (una fila por elemento) - Texto condicional (una fila por rama) - Destinos de la acción **Open URL** (una fila por URL) Para traducir una cadena, haz clic en la celda e introduce la traducción. Para insertar una [variable](onboarding-variables), haz clic en **{ }** en la celda. Para encontrar la fila de una cadena, [selecciona su elemento](builder-ui#preview-area-center) en la vista previa. Cada elemento de texto condicional es un grupo de filas independiente. Haz clic en la flecha Expand para expandir el grupo y ver todas las ramas de condición. ### Establecer URLs de acción específicas por idioma \{#set-locale-specific-action-urls\} Cada acción **Open URL** tiene su propia fila. Para definir una [URL](#action-urls) diferente para un idioma concreto, haz clic en el área en blanco de la celda de ese idioma e introduce la URL. No hagas clic directamente sobre la URL: se abrirá en una pestaña nueva. Para saber a qué elemento pertenece una URL, haz clic en su fila: el editor seleccionará ese elemento en la vista previa. ### Localizar imágenes y vídeos \{#localize-images-and-videos\} Para mostrar una imagen o vídeo diferente en un idioma, sube un archivo independiente para ese idioma. En el panel **Localizations**, pasa el cursor sobre la celda del idioma en la fila de imagen o vídeo y haz clic en **Replace**. Para volver al archivo del idioma predeterminado, haz clic en **Reset**. En un flow con tema oscuro, puedes localizar las capas de imagen y vídeo de un estilo de color de forma independiente para el modo claro y el modo oscuro. En la tabla, el modo claro y el modo oscuro tienen filas separadas. Para [subir un archivo desde el panel de propiedades](custom-media#localize-media), primero selecciona el idioma en la barra de herramientas inferior. Para un estilo de color, [reemplaza la imagen o el vídeo en el panel **Styles**](builder-reusable-styles#images-and-videos) mientras el idioma de destino está activo. ## Localizar contenido de forma externa \{#localize-content-externally\} Para traducir todo el texto de tu flow fuera de Adapty, por ejemplo con un servicio de traducción o tus propios traductores, [exporta el archivo de localización](#export-the-localization-file). Cuando la traducción esté lista, [importa](#import-the-file-with-a-translation) el archivo de vuelta. ### Exportar el archivo de localización \{#export-the-localization-file\} Para exportar el flow completo con todos sus idiomas, en el encabezado del panel, haz clic en **Import / Export** y selecciona **Export**. Para exportar un idioma individual junto con el idioma de origen, selecciona **Export** en su [menú contextual de columna](#manage-a-locale). ### Formato del archivo de importación / exportación \{#import--export-file-format\} El archivo de localización es un archivo `.tsv` (separado por tabulaciones) en UTF-8, con una fila por cadena o URL de acción. Un archivo exportado cubre el flow completo, independientemente de los filtros que hayas aplicado en el panel. Para importar un archivo, mantén su formato sin cambios. Las columnas son: | Columna | Descripción | |--------|-------------| | `id` | El identificador de la fila. Adapty hace coincidir las filas por este valor en la importación, así que no lo modifiques. Adapty ignora las filas con un `id` que no esté en el flow. | | `Screen` | La pantalla a la que pertenece el texto. Adapty ignora esta columna en la importación. | | `[locale]` | Una columna por idioma, nombrada por su código (p. ej., `en`, `fr`). Adapty ignora las columnas de idiomas que no están en el flow. | Ejemplo: ``` id Screen en fr es lc_1 Welcome Turn words into art Transformez les mots en art lc_2 Welcome Start your <b>free trial</b> Commencez votre <b>essai gratuit</b> lc_3 Quiz What will you create? ``` En el archivo exportado: - El formato aparece como etiquetas HTML, como `<b>`, y las variables como `{{name}}`. Mantenlas intactas: Adapty las restaura al importar. - Cada rama del [texto condicional](#conditional-text) tiene su propia fila, sin su condición. - Las imágenes y vídeos no se incluyen: los [localizas en el editor](#localize-images-and-videos). Para traducir las cadenas que añades después de la exportación, vuelve a exportar el archivo. Adapty no puede importar un archivo en el formato antiguo, con las columnas `Screen`, `Element` y `Property`. Para reutilizar sus traducciones, exporta el flow de nuevo y cópialas en el archivo nuevo. ### Importar el archivo con una traducción \{#import-the-file-with-a-translation\} :::warning Si una celda del archivo está vacía, Adapty elimina la traducción de esa celda al importar, excepto en la columna del idioma predeterminado. Para conservar las traducciones de un idioma, deja sus celdas tal como se exportaron o elimina su columna del archivo. Si una importación borra traducciones que querías conservar, deshaz la importación: toda la importación es un único paso de deshacer. ::: Para importar el flow completo, en el encabezado del panel haz clic en **Import / Export** y selecciona **Import**. Para importar un idioma concreto, selecciona **Import** en su [menú contextual de columna](#manage-a-locale). Luego, selecciona el archivo traducido. Las cadenas sin fila en el archivo conservan sus valores actuales. Una importación completa del flow actualiza el texto del idioma predeterminado donde el archivo lo cambia. Una importación de un solo idioma solo cambia el idioma que importas. Para importar un nuevo idioma, [añade primero su configuración regional](#add-and-set-up-a-locale). ## Vista previa de diferentes idiomas \{#preview-different-locales\} El texto en el área de vista previa siempre muestra el idioma activo. Si editas el contenido de texto en el panel de propiedades, esos cambios solo se aplican a ese idioma. Vuelve al idioma predeterminado para editar el contenido original. - Para cambiar el idioma activo, selecciónalo en el desplegable **Locale** de la [barra de herramientas inferior](builder-ui#view-controls-bottom-toolbar), o haz clic en su pestaña en el panel **Localizations**. - Para cambiar la pantalla activa desde el panel **Localizations**, haz clic en **View screen** View screen en el encabezado de su sección. - Para expandir la tabla de localización a pantalla completa, haz clic en **Hide preview**. Para encontrar texto sin traducir sin previsualizar cada pantalla, ejecuta el [skill flow-audit](flow-audit-skill). --- # File: add-flow-remote-config-locale --- --- title: "Localizar un flow con Remote Config" description: "Añade idiomas al Remote Config de un flow para servir distintos valores según el idioma o la región." --- El Remote Config de un flow puede contener un payload JSON independiente para cada idioma. En tiempo de ejecución, el SDK devuelve el payload que coincide con el idioma del usuario, por lo que puedes servir textos traducidos, imágenes distintas u otros valores específicos de cada idioma 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 el Flow & Paywall Builder. 2. Haz clic en el icono de Remote Config que aparece sobre la vista previa de la pantalla. 3. Haz clic en **Add locale** sobre el editor. 4. Rellena el diálogo: - **Code**: El código de la configuración regional, por ejemplo `en`, `fr` o `de`. - **Name**: El nombre para mostrar, 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 el flow con Remote Config" description: "Personaliza tu flow con un payload JSON de Remote Config." --- :::important Esta guía cubre el Remote Config para el Flow & Paywall Builder. Para paywalls personalizados creados sin él, consulta [Diseñar paywall personalizado 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 la pantalla en el builder. En la vista **JSON**, puedes introducir cualquier dato con 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 [Problemas comunes](flow-common-issues#a-flow-wont-preview-or-publish) 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` ya parseado o el `jsonString` en bruto 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, diseños o comportamientos alternativos. 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), [Android](present-remote-config-paywalls-android), [React Native](present-remote-config-paywalls-react-native), [Flutter](present-remote-config-paywalls-flutter), [Unity](present-remote-config-paywalls-unity), [Kotlin Multiplatform](present-remote-config-paywalls-kmp), o [Capacitor](present-remote-config-paywalls-capacitor). ## 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: "Vista previa de flows" description: "Previsualiza la compatibilidad de los flows en distintos dispositivos para una experiencia optimizada." --- Tienes dos formas de previsualizar tu flow en distintos tipos de pantalla: - **Vista previa en dispositivos**: Comprueba cómo se ve tu flow en dispositivos reales en cualquier etapa del desarrollo. - **Vista previa 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 móvil de Adapty para [iOS](https://apps.apple.com/us/app/adapty/id6739359219) o [Android](https://play.google.com/store/apps/details?id=io.adapty.dashboard) e inicia sesión con la cuenta de Adapty que usas en el dashboard. 2. En el Flow & Paywall 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 abre tu flow en la app móvil de Adapty. :::note La vista previa del flow en la app móvil de Adapty muestra precios de muestra, ya que no puede acceder a tus productos en los stores. Tampoco pasa valores para las [etiquetas personalizadas](flow-custom-tags), por lo que verás el valor inicial de cada etiqueta. ::: ### Solución de problemas \{#troubleshooting\} Si el flow no se abre en la vista previa, la causa más habitual es una acción incompleta, una pantalla vacía o un producto no vinculado. Consulta [Problemas comunes](flow-common-issues#a-flow-wont-preview-or-publish) para ver la lista completa. Si la vista previa falla con un error HTTP 401, significa que la app móvil de Adapty no tiene sesión iniciada. Inicia sesión con la cuenta de Adapty que usas en el dashboard y vuelve a escanear el código QR. ## Vista previa en el Adapty Dashboard \{#preview-in-the-adapty-dashboard\} :::tip Para asegurarte de que tu flow está listo para publicarse, [previsualízalo en un dispositivo real](#preview-on-devices) y confirma que se muestra sin errores. ::: Puedes previsualizar tu flow en distintos tipos de pantalla usando el área de vista previa del Flow & Paywall Builder. Esto te ayuda a garantizar que tu flow se vea bien en diferentes dispositivos y tamaños de pantalla. Con los controles de vista previa situados debajo de la previsualización, puedes: - Seleccionar el dispositivo en el que previsualizar tu flow. - Cambiar entre los modos de vista previa horizontal y vertical. - Cambiar entre el modo claro y el oscuro. - Cambiar entre idiomas. :::tip - Previsualiza siempre los distintos idiomas, ya que diferentes lenguas pueden tener palabras más largas y el diseño de la pantalla puede verse distinto en cada una. - En el área de previsualización del builder, cada variable personalizada muestra su valor inicial, como `Jane Doe` para una variable `name`. Para una [etiqueta personalizada](flow-custom-tags), no introduzcas datos de muestra: los usuarios verán su valor inicial como alternativa cuando tu app no pase ningún valor. Introduce una palabra que encaje con cualquier usuario, como `amigo` en "¡Bienvenido de nuevo, amigo!" ::: ### Vista previa de productos y ofertas \{#preview-products-and-offers\} La vista previa lee las [variables de producto](onboarding-variables#product-variables) de tu catálogo de Adapty, que cubre solo una parte de lo que resuelve un dispositivo: - `prod_title` muestra el título del producto tal como está almacenado en Adapty. `prod_price` y las variables `prod_price_per_*` muestran el precio almacenado en Adapty, en dólares estadounidenses — los usuarios ven el precio localizado del store. - `offer_price`, `offer_billing_period` y `offer_full_duration` permanecen sin resolver, por lo que la vista previa muestra el nombre de la variable entre llaves — por ejemplo, `{{products.selectedProduct.offer_price}}`. Estos valores llegan al flow cuando el SDK lo renderiza en tu app. - `is_free_trial`, `is_pay_up_front` e `is_pay_as_you_go` siguen la oferta seleccionada en la tarjeta del producto. Sin oferta, los tres son `false`. Con una oferta, la vista previa la trata como pago por uso. Las condiciones sobre el tipo de oferta cambian en la vista previa, pero solo se muestra la rama de pago por uso de forma predeterminada. Para ver las otras ramas, simula el modo de pago: 1. En el lienzo, selecciona la tarjeta de producto que tiene la oferta. 2. En el panel derecho, en **Product**, abre el desplegable de oferta. 3. En **Preview as**, selecciona **Free trial**, **Pay up front** o **Pay as you go**. Este ajuste solo se aplica a la tarjeta actual. --- # File: builder-save-publish --- --- title: "Guardar y publicar flows" description: "Guarda flows como borradores y publícalos para los usuarios" --- El [Flow & Paywall Builder](adapty-flow-builder) separa el guardado de la publicación. Los borradores conservan tu trabajo en el Adapty Dashboard, y publicar pone la versión actual a disposición de 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) inválido impide guardar el borrador. ::: El Flow & Paywall 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 & Paywall Builder, o pulsa **Cmd/Ctrl + S**. Los borradores son privados en el Dashboard. No cambian lo que ven los usuarios en tu app, aunque el flow ya esté asignado a un [placement](placements). ## Publicar un flow \{#publish-a-flow\} Publicar hace que la versión actual de tu flow esté disponible para los usuarios a través del SDK, en lugar de cualquier versión publicada anteriormente. Para volver a una versión anterior, [restáurala](flow-version-history#restore-an-earlier-version). :::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 & Paywall 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 está en un placement**: Añade el flow a un [placement de flow](create-placement) para empezar a mostrárselo a los usuarios. Un flow necesita su propio placement — no puede ir en un placement que ya uses para un paywall o un onboarding. :::tip Un flow está listo para publicar cuando todas las acciones, pantallas y productos están completamente configurados. De lo contrario, el diálogo **Can't publish yet** muestra lo que hay que corregir, agrupado por pantalla, y aún puedes guardar el borrador. Los problemas más comunes están en [Solución de problemas](#troubleshooting). ::: :::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 de la aplicación: [copia el flow](copy-flows), cambia la fuente en la copia y dirige la copia a una [audiencia](add-audience-paywall-ab-test) en las versiones de la aplicación que incluyan la fuente. ::: ## Estado del flow \{#flow-status\} Cada flow muestra un estado en la [lista de flows](filter-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, por lo que los usuarios aún no lo ven. Necesitas 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 vuelvas a publicar. | | **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 [archivado](archive-flows#archive-a-flow) y no se puede editar hasta que lo [restaures](archive-flows#restore-a-flow). | ## Solución de problemas \{#troubleshooting\} Una acción incompleta, una pantalla vacía, un producto u oferta faltante o eliminado, o un JSON de Remote Config no válido bloquean tanto la vista previa como la publicación. La lista completa y la solución para cada caso están en [Problemas comunes](flow-common-issues#a-flow-wont-preview-or-publish). Previsualiza tu flow en la [app de Adapty](paywall-device-compatibility-preview) para detectar cualquier problema antes de publicar. Si el flow no carga en la vista previa, consulta el mensaje de error para más detalles. Un flow puede superar todo esto y aún no estar listo para publicarse: un botón de restauración que no funciona, un enlace a los términos de servicio que falta, una pantalla sin traducir. La [skill de auditoría de flow](flow-audit-skill) comprueba exactamente eso, desde el terminal, y concluye con un veredicto en lugar de una lista. --- # File: flow-version-history --- --- title: "Historial de versiones de un flow" description: "Consulta las versiones anteriores de un flow y restaura una dentro del mismo flow o como un flow nuevo. Nada cambia para los usuarios hasta que publiques." --- Consulta el historial de versiones de un flow y explora cada versión en detalle. Mientras navegas por versiones anteriores, tu borrador actual y la versión publicada actualmente permanecen sin cambios. Cuando estés listo, restaura la versión que quieras y publícala. Para abrir la lista de versiones, en la barra superior del Flow & Paywall Builder, haz clic en **Version history**. La lista reemplaza el panel derecho. Para cerrarla, vuelve a hacer clic en **Version history**. ## Encuentra la versión que necesitas \{#find-the-version-you-need\} Si nunca has [publicado el flow actual](builder-save-publish#publish-a-flow), el historial de versiones estará vacío. De lo contrario, las versiones publicadas aparecen en orden cronológico, con la más reciente en la parte superior. La lista resalta la versión que está abierta en este momento. Lee la etiqueta para ver el estado de cada versión: - **Draft**: La versión que estás editando. - **Publishing…**: Un borrador que se está publicando. - **Publish failed**: Un borrador que no se publicó. [Encuentra y soluciona el problema](builder-save-publish#troubleshooting) y vuelve a publicarlo. - **Live**: La versión que ven los usuarios. - Sin etiqueta: Una versión que estaba activa anteriormente. Para encontrar una versión concreta en la lista, búscala por nombre. Cuando publiques una actualización importante, ponle un nombre descriptivo a la nueva versión para poder encontrarla y restaurarla fácilmente más adelante. Para renombrar una versión, abre el menú contextual Context menu de su entrada y selecciona **Rename**. ## Ver una versión anterior \{#view-an-earlier-version\} Para previsualizar una versión, haz clic en su fila. La versión se abre en modo de solo lectura: puedes revisar sus pantallas y elementos, pero no puedes modificar nada. Para inspeccionar las propiedades de un elemento, cierra el panel **Version history**. La previsualización permanece abierta. Para abrir la versión en la [app móvil de Adapty](paywall-device-compatibility-preview#preview-on-devices), haz clic en **Test on device** y escanea el código QR o abre el enlace. Para volver al último borrador, haz clic en **Back to current version**. ## Restaurar una versión anterior \{#restore-an-earlier-version\} :::tip Restaurar una versión dentro del mismo flow reemplaza el borrador actual. Si quieres conservarlo, duplícalo como nuevo flow antes de restaurar. ::: Hay dos formas de restaurar una versión anterior de un flow. Busca la entrada de la versión que necesites, abre su menú contextual Context menu y selecciona una de las opciones: - **Duplicate as draft**: Restaura la versión dentro del mismo flow. Si el flow ya tiene un borrador, confirma con **Replace draft**. La versión restaurada no entra en producción hasta que la [publiques de nuevo](builder-save-publish#publish-a-flow). Disponible para la versión **Live** y las versiones que estuvieron en producción anteriormente. - **Duplicate as new flow**: Restaura la versión como un nuevo flow. El flow original no cambia y conserva el último borrador. El nuevo flow se abre en una pestaña nueva. ### Restaurar una pantalla o elemento individual \{#restore-one-screen-or-element\} Para restaurar una sola pantalla o elemento, [previsualiza](#view-an-earlier-version) una versión anterior, selecciona el elemento que necesitas y cópialo con el atajo de teclado (Cmd/Ctrl+C). Vuelve al borrador más reciente y pégalo allí. Si la copia usa algo que eliminaste desde esa versión, como una fuente o un estilo de texto, reemplaza las referencias; de lo contrario, no podrás publicar el flow. ## Limitaciones \{#limitations\} No puedes: - Restaurar un estado anterior de tu borrador. Para revertir cambios recientes, usa [deshacer](builder-ui#project-controls-and-useful-shortcuts-top-toolbar). Para descartar todos los cambios realizados desde que se publicó la versión, [restaura la versión **Live**](#restore-an-earlier-version). - Renombrar, restaurar ni duplicar una versión de un flow archivado. Para usar estas acciones, [activa el flow primero](archive-flows#restore-a-flow). ## Solución de problemas \{#troubleshooting\} | Síntoma | Causa | Solución | | :------ | :---- | :-- | | Al publicar la versión restaurada, aparece el diálogo **Can't publish yet**. | La versión recién restaurada utiliza un producto u oferta que ya no existe. | [Reemplaza la referencia al producto](flow-common-issues#a-flow-wont-preview-or-publish) y corrige cualquier otro problema existente. | | Al restaurar una versión anterior, aparece el error `This flow configuration was already updated by …`. | Un compañero de equipo modificó el borrador después de que abrieras el flow. | Recarga la página y [restáuralo de nuevo](#restore-an-earlier-version). | --- # File: filter-flows --- --- title: "Buscar y filtrar flows" description: "Busca, filtra y ordena tus flows, y accede a las acciones disponibles para cada uno." --- La pestaña **Flow & Paywall builder** de [**Flows & Paywalls**](https://app.adapty.io/flows) muestra todos los flows de tu app con su [estado](#filter-by-status) y las principales [métricas](flow-metrics). Haz clic en un flow para ver sus [métricas](flow-metrics) completas o, si tienes el rol **Developer**, el [Flow & Paywall Builder](adapty-flow-builder). ## Buscar por nombre \{#search-by-name\} **Buscar por nombre de flow** coincide con cualquier parte del nombre de un flow. No busca placements, productos ni contenido de pantallas. ## Filtrar por estado \{#filter-by-status\} Abre el filtro **All states** sobre la lista, selecciona uno o más estados y haz clic en **Apply**. [Guardar y publicar flows](builder-save-publish#flow-status) explica qué significa cada estado y cómo un flow pasa de uno a otro. ## Filtrar por placement \{#filter-by-placement\} Abre el filtro **All placements** que aparece encima de la lista, selecciona uno o más [placements](placements) y haz clic en **Apply**. Este filtro aparece cuando tienes al menos un flow asignado a un placement. ## Ordenar \{#sort\} Puedes ordenar por **Flow name** o **Status**. Las columnas de métricas no se pueden ordenar. Por defecto, los flows publicados aparecen primero y los archivados al final. ## Acciones del flow \{#flow-actions\} Abre el menú contextual Context menu en la fila de un flow. | Acción | Qué hace | | :----- | :------- | | **Metrics** | Abre las [métricas](flow-metrics) del flow. | | **Edit** | Abre el flow en el [Flow & Paywall Builder](adapty-flow-builder). | | **Copy** | [Copia el flow](copy-flows) como nuevo borrador. | | **Archive** | [Archiva el flow](archive-flows). | El menú de un flow archivado muestra **Metrics** y **Back to active**, que [restaura el flow](archive-flows#restore-a-flow). ## Filtrar análisis por flow \{#filter-analytics-by-flow\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds#filter-and-group-data) ::: La página de métricas de flow cubre vistas, finalizaciones, conversión e ingresos. Métricas como MRR, suscripciones activas, churn, período de gracia y problemas de facturación solo están disponibles en la página [Charts](charts). Para verlas para un flow concreto, filtra estas métricas por **Flow** o **Flow screen**: - **Flow**: Un flow completo, con todas sus pantallas contadas en conjunto. - **Flow screen**: Una pantalla de un flow. Las pantallas aparecen anidadas bajo su flow y usan la siguiente convención de nombres: `<position>. <screen name>`, por ejemplo `9. Paywall A`. Para comparar flows o pantallas en paralelo en un mismo gráfico, agrúpalos por los mismos atributos en lugar de filtrarlos. :::important La lista de filtros solo muestra flows y pantallas con al menos una transacción. ::: | Gráfico | Filtrar por flow | Agrupar por flow | |:------|:--------------:|:-------------:| | [Revenue](revenue) | ✅ | ✅ | | [MRR](mrr) | ✅ | ✅ | | [ARR](arr) | ✅ | ✅ | | [ARPU](arpu) | ❌ | ❌ | | [ARPPU](arppu) | ✅ | ✅ | | [Installs](installs) | ❌ | ❌ | | [Active subscriptions](active-subscriptions) | ✅ | ✅ | | [New subscriptions](reactivated-subscriptions) | ✅ | ✅ | | [Subscription renewal cancelled](cancelled-subscriptions) | ✅ | ✅ | | [Expired (churned) subscriptions](churned-expired-subscriptions) | ✅ | ✅ | | [Non-subscriptions](non-subscriptions) | ✅ | ✅ | | [Active trials](active-trials) | ✅ | ✅ | | [New trials](new-trials) | ✅ | ✅ | | [Trial renewal cancelled](trials-renewal-cancelled) | ✅ | ✅ | | [Expired (churned) trials](expired-churned-trials) | ✅ | ✅ | | [Grace period](grace-period) | ✅ | ✅ | | [Grace period converted](grace-period-converted) | ✅ | ✅ | | [Grace period converted revenue](grace-period-converted-revenue) | ✅ | ✅ | | [Billing issue](billing-issue) | ✅ | ✅ | | [Billing issue converted](billing-issue-converted) | ✅ | ✅ | | [Billing issue converted revenue](billing-issue-converted-revenue) | ✅ | ✅ | | [Refund events](refund-events) | ✅ | ✅ | | [Refund money](refund-money) | ✅ | ✅ | | [LTV](ltv) | ✅ | ❌ | --- # File: copy-flows --- --- title: "Copiar flows y pantallas" description: "Duplica un flow completo para adaptarlo a una audiencia o una variante de prueba A/B, o copia pantallas individuales para reutilizarlas." --- Copia el flow y modifica algunos elementos para adaptarlo a una [audiencia](add-audience-paywall-ab-test), [prueba A/B](ab-tests) o versión de la app específica. Copia una pantalla individual para reutilizarla en otro flow. ## Copiar un flow \{#copy-a-flow\} ### Desde el Flow & Paywall Builder 1. Abre el flow en el Flow & Paywall Builder. 2. En la barra superior, haz clic en el botón de acciones del flow Flow actions y selecciona **Duplicate flow**. La copia se abre en una nueva pestaña. Para copiar una versión anterior, [duplícala como un nuevo flow](flow-version-history#restore-an-earlier-version). ### Desde la lista de flows 1. Ve a [**Flows & Paywalls**](https://app.adapty.io/flows) en el menú principal de Adapty y abre la pestaña **Flow & Paywall builder**. 2. En la fila del flow, abre el menú contextual Context menu y selecciona **Copy**. No puedes copiar un flow archivado — primero [restáuralo](archive-flows#restore-a-flow). ### Qué contiene la copia El Flow & Paywall Builder abre la copia como un nuevo **Draft** con el nombre del original más el sufijo `_copy`. [Dale a la copia un nombre único](paywall-builder-templates#create-flow) para evitar confusiones. La copia contiene todo lo almacenado en el flow en sí: - **Pantallas y elementos**: Cada pantalla, con su diseño, elementos e interacciones. - **Productos asignados**: Las conexiones entre tus tarjetas de producto y los productos de tu store. - **Diseño**: Estilos de color y texto guardados, fondos y valores del modo oscuro. - **Fuentes personalizadas**: Todos los flows de una misma app comparten las mismas [fuentes personalizadas](using-custom-fonts-in-flow-builder), por lo que la copia las conserva. - **Variables**: [Variables personalizadas, de elemento y de producto](onboarding-variables). - **Localizaciones**: Cada idioma, con su texto traducido y sus archivos multimedia. - **Remote config**: El [payload JSON](customize-flow-with-remote-config) de cada idioma. Todo lo que esté almacenado fuera del flow se queda atrás: - **Placements**: La copia no pertenece a ningún [placement](placements). - **A/B tests**: La copia no se incorpora a ninguna [prueba A/B](ab-tests). - **Métricas**: La copia comienza con un [historial de análisis](flow-metrics) vacío. ### Mostrar la copia a los usuarios \{#show-the-copy-to-users\} 1. [Publica la copia](builder-save-publish#publish-a-flow). 2. Añade la copia a un [placement de flow](placements). ## Copiar pantallas entre flows \{#copy-screens-between-flows\} Para mover una pantalla a otro flow, [**Cópiala**](paywall-layout-and-products#screen-actions) al portapapeles, abre el flow de destino y pégala en el panel **Screens**. El portapapeles te permite transferir la pantalla entre distintos flows o incluso entre navegadores. Adapty no copia lo que la pantalla referencia a nivel de flow. Una pantalla pegada puede quedar incompleta e incluir referencias rotas. Un flow con referencias rotas puede [no publicarse correctamente](builder-save-publish#troubleshooting). Revisa cada uno de estos elementos tras pegar: - **Variables**: Los bindings de texto, condiciones y acciones dejan de funcionar. Sustituye las referencias por variables del flow de destino. - **Estilos de texto**: Los elementos pierden sus estilos de texto. Aplica un estilo de texto del flow de destino. - **Acciones de navegación**: Las acciones **Navigate to screen** pierden su destino. Apunta cada una a una pantalla del flow de destino. - **Localizaciones**: Solo los idiomas que ambos flows comparten conservan sus traducciones. Los usuarios con cualquier otro idioma verán el predeterminado del flow de destino. - **Productos y fuentes personalizadas**: Todos los flows de una app pueden usar los mismos productos y fuentes. Una pantalla pegada desde otro flow de la misma app conserva sus productos y fuentes. Pegar una pantalla de otra app romperá sus referencias a productos y fuentes. :::tip Reemplaza cada referencia rota con un equivalente funcional en el flow de destino. Copiar a mano es la forma más rápida de mover una pantalla una sola vez. Para reutilizar el mismo bloque una y otra vez —la selección de plan, la línea de tiempo del período de prueba, la tabla comparativa— la [skill flow-generator](reuse-flow-elements) lo guarda en un archivo y lo añade a cualquier otro flow, resolviendo los estilos, fuentes, iconos y vínculos de productos al importarlo. Enumera todo lo que no pueda resolver en lugar de dejarte una referencia rota que tengas que buscar. ::: --- # File: archive-flows --- --- title: "Archivar y restaurar flows" description: "Archiva los flows que ya no necesitas y conserva sus métricas. Restaura un flow archivado para volver a trabajar con él." --- Archiva los flows que hayas dejado de usar para mantener la pestaña **Flow & Paywall builder** ordenada. Los flows archivados aparecen al final y puedes filtrarlos por estado. Sus métricas se conservan intactas. :::warning Un flow archivado no se puede editar ni [copiar](copy-flows). Para volver a trabajar con él, primero [restáuralo](#restore-a-flow). ::: ## Archivar un flow \{#archive-a-flow\} Ve a [**Flows & Paywalls**](https://app.adapty.io/flows) en el menú principal de Adapty y abre la pestaña **Flow & Paywall builder**. Busca el flow en la lista. Abre el menú contextual Context menu y selecciona **Archive**. :::warning No archives un flow activo. ::: Antes de archivar el flow: - Elimínalo de cada [placement](placements). - Archiva las [pruebas A/B](ab-tests) que aún lo usen. Los flows archivados permanecen en la pestaña **Flow & Paywall builder** con el estado **Archived**. Para ocultarlos, [filtra la lista por estado](filter-flows#filter-by-status) y deja **Archived** sin seleccionar. ## Restaurar un flow \{#restore-a-flow\} Para restaurar un flow, encuéntralo en la [lista de flows](filter-flows) y selecciona **Back to active** en el menú contextual Context menu. Adapty restaura el flow a su estado anterior al archivado, pero no lo añade a ningún placement. --- # File: flow-metrics --- --- title: "Métricas de flow" description: "Realiza un seguimiento 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 el seguimiento de finalización, para que puedas ver cuántos de los usuarios que inician un flow llegan al final. Para ver en qué pantalla abandonan los demás, abre el [embudo de flow](flow-funnel). 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 el flow fue mostrado. ::: Las métricas de los flows están disponibles en la [lista de flows](filter-flows), donde puedes ver el rendimiento de todos tus flows de un vistazo. 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 información más profunda sobre su rendimiento. :::tip Para métricas que esta página no calcula, como MRR o suscripciones activas, [filtra la página de Gráficos por flow](#filter-analytics-by-flow). ::: ## Controles de métricas \{#metrics-controls\} El sistema muestra las métricas según el período de tiempo seleccionado y las organiza como un árbol expandible en la columna izquierda. En la vista basada en placement, los niveles son placement, audiencia, prueba A/B, variación de flow, pantalla y producto. La vista basada en audiencia elimina el nivel de placement. Una fila de pantalla muestra los ingresos, compras y pruebas atribuidos a esa pantalla. Sus **Views** y **Completions** no forman un embudo por pantalla: **Views** cuenta únicamente a los usuarios que iniciaron el flow en esa pantalla, y **Completions** solo a quienes lo finalizaron allí. Cada pantalla intermedia muestra cero en ambos campos. Para un embudo por pantalla, abre la pestaña [**Funnel**](flow-funnel) del flow. Para 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 ninguna métrica. ### 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 varios rangos de tiempo, opciones de agrupación y posibilidades de filtrado. - Filtrar por: Atribución (reportado por, estado, canal, campaña, grupo de anuncios, conjunto de anuncios, creativo), País, Store. - Agrupar por: Flow (predeterminado), 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 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 completados únicos \{#unique-completions-rate\} El número de completados únicos dividido entre el número de vistas únicas. Usa esta métrica para comparar cuántos usuarios terminan un flow frente a otro, o una variante frente a otra. :::note Adapty convierte otras divisas a USD utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo 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. ## Filtrar análisis por flow \{#filter-analytics-by-flow\} :::link Artículo principal: [Controles de análisis](controls-filters-grouping-compare-proceeds#filter-and-group-data) ::: La página de métricas de flow cubre vistas, finalizaciones, conversión e ingresos. Métricas como MRR, suscripciones activas, churn, período de gracia y problemas de facturación solo están disponibles en la página [Charts](charts). Para verlas para un flow concreto, filtra estas métricas por **Flow** o **Flow screen**: - **Flow**: Un flow completo, con todas sus pantallas contadas en conjunto. - **Flow screen**: Una pantalla de un flow. Las pantallas aparecen anidadas bajo su flow y usan la siguiente convención de nombres: `<position>. <screen name>`, por ejemplo `9. Paywall A`. Para comparar flows o pantallas en paralelo en un mismo gráfico, agrúpalos por los mismos atributos en lugar de filtrarlos. :::important La lista de filtros solo muestra flows y pantallas con al menos una transacción. ::: | Gráfico | Filtrar por flow | Agrupar por flow | |:------|:--------------:|:-------------:| | [Revenue](revenue) | ✅ | ✅ | | [MRR](mrr) | ✅ | ✅ | | [ARR](arr) | ✅ | ✅ | | [ARPU](arpu) | ❌ | ❌ | | [ARPPU](arppu) | ✅ | ✅ | | [Installs](installs) | ❌ | ❌ | | [Active subscriptions](active-subscriptions) | ✅ | ✅ | | [New subscriptions](reactivated-subscriptions) | ✅ | ✅ | | [Subscription renewal cancelled](cancelled-subscriptions) | ✅ | ✅ | | [Expired (churned) subscriptions](churned-expired-subscriptions) | ✅ | ✅ | | [Non-subscriptions](non-subscriptions) | ✅ | ✅ | | [Active trials](active-trials) | ✅ | ✅ | | [New trials](new-trials) | ✅ | ✅ | | [Trial renewal cancelled](trials-renewal-cancelled) | ✅ | ✅ | | [Expired (churned) trials](expired-churned-trials) | ✅ | ✅ | | [Grace period](grace-period) | ✅ | ✅ | | [Grace period converted](grace-period-converted) | ✅ | ✅ | | [Grace period converted revenue](grace-period-converted-revenue) | ✅ | ✅ | | [Billing issue](billing-issue) | ✅ | ✅ | | [Billing issue converted](billing-issue-converted) | ✅ | ✅ | | [Billing issue converted revenue](billing-issue-converted-revenue) | ✅ | ✅ | | [Refund events](refund-events) | ✅ | ✅ | | [Refund money](refund-money) | ✅ | ✅ | | [LTV](ltv) | ✅ | ❌ | --- # File: flow-funnel --- --- title: "Embudo de flow" description: "Consulta cuántos usuarios llegan a cada pantalla de un flow, dónde abandonan y en qué pantalla del paywall compran." --- La pestaña **Funnel** de un flow muestra cómo los usuarios avanzan por sus pantallas y en cuál de ellas abandonan. Las [métricas de flow](flow-metrics) indican cuántos usuarios inician un flow, cuántos lo completan y cuánto genera. El funnel muestra el recorrido intermedio, pantalla a pantalla. Úsalo para identificar la pantalla donde se pierden usuarios y corregirla, en lugar de modificar todo el flow. El gráfico de [análisis de embudo](analytics-funnels) de la página de Analytics es un funnel diferente: cubre instalaciones, vistas de paywall, pruebas y pagos de toda la aplicación. El embudo se construye a partir de las visualizaciones de pantalla de una versión de flow publicada. El SDK de Adapty registra una visualización de pantalla cada vez que renderiza una pantalla de flow, por lo que no se requiere ningún código. Solo se cuentan los flows mostrados desde un placement. Para abrir el embudo, en la página [**Flows**](https://app.adapty.io/flows), haz clic en un flow. En su página, selecciona la pestaña **Funnel**. La página tiene tres partes: - **Tarjetas de resumen**: **Entered**, **Completed**, **Drop rate** y **Revenue** del flow completo. - **Gráfico**: Una barra por paso, con la proporción de usuarios que lo alcanzaron, la conversión al siguiente paso y el abandono tras él. - **Tabla**: Una fila por paso: su tipo, los usuarios que lo alcanzaron, los usuarios que lo completaron o abandonaron el flow en él, y los ingresos de sus pantallas de paywall. ## Pasos del embudo \{#funnel-steps\} Un paso es una posición en el recorrido del usuario a través del flow, no una posición en el Flow & Paywall Builder. El primer paso es la pantalla que los usuarios vieron primero, el segundo paso es la pantalla que vieron a continuación, y así sucesivamente. El primer paso se llama **Start screen**. Adapty construye un único recorrido por usuario: - Cada vez que un usuario abre el flow, una pantalla se cuenta una sola vez, en la primera visualización. Si el usuario vuelve a una pantalla, no se añade ningún paso. - Si un usuario abre el flow varias veces, Adapty conserva una sola ejecución: la ejecución en la que el usuario completó el flow, si la hay, o de lo contrario la ejecución con más pantallas. - Como cada usuario tiene un único recorrido, cada recuento del embudo corresponde a usuarios únicos. Hasta que la versión tenga datos, la tabla muestra sus pantallas en el orden del builder con valores en cero. ### Ramas \{#branches\} Las pantallas de diferentes ramas en la misma posición comparten un paso. El paso toma el nombre de la pantalla que vio la mayoría de los usuarios. Un indicador muestra cuántas pantallas lo comparten: **N routes** cuando el paso tiene una pantalla de paywall, **N variants** cuando no la tiene. Expande la fila del paso para ver cada pantalla con su proporción de usuarios, completaciones y abandonos. Una pantalla de paywall en el paso se etiqueta como **Paywall path**, con numeración cuando el paso tiene varias. Cuando un flow tiene ramas, aparece una nota encima del embudo indicándolo. ### Finalizaciones y abandonos \{#completions-and-drop-offs\} Un usuario completa el flow de una de estas dos formas: - El usuario inicia una prueba o realiza una compra en una pantalla de paywall. Una compra cierra el flow, por lo que el usuario lo completa en ese paso de paywall, independientemente de dónde esté ese paso en el flow. Un reembolso posterior elimina la compra de las métricas **Purchases** y **Revenue** del paso, pero el usuario sigue contando como completado. - El recorrido termina en una última pantalla. Una última pantalla es aquella desde la que no hay navegación a otra pantalla. Un flow puede tener varias, por ejemplo una pantalla de éxito y una de cancelación. Cada una aparece etiquetada como **Last screen**. Cuando la última pantalla es un paywall, solo completan el flow los usuarios que compran en ella. Cualquier otra ruta se considera un abandono, contabilizado en el paso donde terminó el recorrido. Un usuario que cierra un paywall sin comprar, incluyendo tras un pago cancelado o fallido, abandona en el paso de ese paywall, a menos que el recorrido continúe hasta una última pantalla. Cualquier abandono superior a cero se muestra en rojo. ## Controles del embudo \{#funnel-controls\} - **Period**: El rango de fechas. El funnel incluye cada ejecución del flow que comenzó en ese período, completa, aunque la ejecución haya continuado después del fin del período. - **Flow version**: La versión para la que se calcula el funnel. El funnel se abre en la versión publicada. La lista muestra todas las versiones que se publicaron en algún momento, y la versión publicada aparece etiquetada como **Published**. Las pantallas y métricas varían entre versiones, y cada versión tiene datos a partir de la fecha en que se publicó. Tras modificar una pantalla y publicar el flow, la nueva versión tiene su propio funnel desde esa fecha. Para comprobar si el cambio fue positivo, selecciona la versión anterior y compara el paso. - **Filter metrics by install date**: Cuenta los usuarios que instalaron la aplicación en el período en lugar de las ejecuciones que comenzaron en él. Consulta [Filter metrics by install date](#filter-metrics-by-install-date). - **Filters**: País, Store y atribución, igual que en las [métricas de flow](flow-metrics#filters-and-groups), más dos filtros exclusivos del funnel: - **Platform**: Muestra solo los usuarios que vieron el flow en esa plataforma. - **Subscription state**: Muestra solo los usuarios cuyo estado de suscripción actual coincide, por ejemplo **Never subscribed** o **Active trial**. El estado se actualiza aproximadamente cada hora. - **Absolute / Relative**: Indica si la tabla muestra recuentos, porcentajes o ambos. El gráfico siempre muestra ambos. - **Download CSV**: Exporta una fila de totales con los valores del resumen y, a continuación, una fila por cada paso y una fila por cada pantalla dentro de un paso. Un paso de paywall tiene un botón **View Details** que abre las [métricas](paywall-metrics) del paywall, donde puedes consultar sus tasas de conversión y renovaciones. Cuando varias pantallas de paywall comparten un paso, el botón aparece en cada fila de pantalla en su lugar. ### Filtrar métricas por fecha de instalación \{#filter-metrics-by-install-date\} Las métricas de paywall, trial y compra pueden agruparse por dos fechas diferentes: - **La fecha del evento**: cuándo se vio el paywall, cuándo empezó el trial o cuándo se realizó la compra. - **La fecha de instalación**: cuándo el usuario abrió la app por primera vez. Ambas vistas pueden mostrar números muy distintos para el mismo rango de fechas. La casilla **Filter metrics by install date** controla cuál usa el dashboard: - **Sin marcar (predeterminado)**: 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 consultas los trials. - **Sin marcar**: Muestra los trials que *empezaron* en abril, independientemente de cuándo instalaron esos usuarios. - **Marcada**: Muestra los trials de usuarios que *instalaron* en abril, independientemente de cuándo empezaron sus trials. La vista por fecha de instalación sigue creciendo después de que termina el período. Un usuario que instaló en abril y empieza un trial en julio se añade a abril en ese momento, por lo que las métricas de abril son más altas cada vez que las abres. La vista por fecha de evento no cambia de esa manera: cada evento cuenta en el día en que ocurrió. Usa la vista por fecha de instalación para medir el rendimiento de adquisición de usuarios de una cohorte específica. Usa la vista por fecha de evento para medir la actividad de paywall u onboarding en un período concreto. ## Definiciones de métricas \{#metrics-definitions\} Los valores que no aplican se muestran como un guion, por ejemplo, los ingresos de una pantalla de onboarding o la tasa de abandono de la última pantalla cuando no es un paywall. ### Introduced Usuarios únicos que iniciaron el flow, es decir, que llegaron a la primera pantalla. ### Completado \{#completed\} Usuarios únicos que completaron el flow: iniciaron una prueba o realizaron una compra en una pantalla de paywall, o llegaron a la última pantalla. La tarjeta muestra el porcentaje de usuarios que entraron y el recuento. En la tabla, el valor **Completed** de un paso indica los usuarios que completaron el flow en ese paso. En la fila de una pantalla dentro de un paso, las finalizaciones de esa pantalla son un porcentaje de los usuarios del paso. Esto difiere de **Completions** en las [métricas de flow](flow-metrics#completions--unique-completions), que solo cuentan a los usuarios que llegan a la última pantalla. Para un flow con un paywall en el medio, el embudo muestra más completions. ### Tasa de abandono \{#drop-rate\} Porcentaje de usuarios que iniciaron el flow pero no lo completaron. ### Alcanzado \{#reached\} Usuarios únicos que llegaron al paso, como porcentaje de los usuarios que entraron. En una fila de pantalla dentro de un paso, el porcentaje de usuarios del paso que vieron esa pantalla. El gráfico también muestra la conversión al siguiente paso, que es el porcentaje de usuarios que alcanzaron ese paso respecto al paso anterior. Los usuarios que no llegaron al siguiente paso o bien completaron el flow o bien abandonaron en este paso. ### Tasa de abandono por paso \{#drop-rate-per-step\} Usuarios cuyo recorrido terminó en ese paso sin completar el flow, como porcentaje de los usuarios que llegaron a él. En una fila de pantalla dentro de un paso, indica la proporción de abandono de esa pantalla respecto al paso. Pasa el cursor sobre el valor para ver la tasa de abandono propia de la pantalla. ### Ingresos \{#revenue\} Ingresos brutos en USD procedentes de las compras iniciales en las pantallas de paywall del flow, menos los reembolsos de esas compras. No se incluyen las renovaciones, a diferencia de los **Revenue** en [métricas de flow](flow-metrics#revenue), que contabilizan cada transacción realizada después de que se mostró el flow. Una compra se contabiliza en la fecha en que se realizó el pago. Cada paso y pantalla del paywall también muestra sus **Trials**, los trials iniciados en él, y sus **Purchases**, las compras iniciales realizadas en él menos sus reembolsos. Los trials y las compras se contabilizan de la misma manera que en las [métricas de paywall](paywall-metrics). Un usuario que inicia un trial y convierte en el mismo período se contabiliza tanto en **Trials** como en **Purchases**, y una vez en **Completed**. La tarjeta **Revenue** puede ser superior a la suma de la tabla. La tarjeta también cuenta las compras que un usuario realizó en la pantalla de paywall del flow en una ejecución distinta a la que se conserva para la ruta del usuario, mientras que la tabla solo muestra las compras de esa ruta. :::note Adapty convierte otras divisas a USD utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: ## Limitaciones \{#limitations\} - **Las ramas de distinta longitud desplazan los pasos**: Un paso es una posición en el recorrido. Cuando una rama es más larga que otra, o se omite una pantalla, la misma pantalla cae en pasos distintos para distintos usuarios. El embudo muestra entonces esa pantalla en más de un paso, y su fila aparece etiquetada como **Also on steps** con los números de los otros pasos. - **Las conversiones tardías no se contabilizan**: Una prueba que convierte después de que termina el período no aparece en **Purchases** ni en **Revenue** de ese período. Los últimos días de un período muestran por tanto valores más bajos de los que mostrarán más adelante. - **Los datos son por versión**: El embudo cubre una versión del flow a la vez, desde la fecha en que se publicó esa versión. Para ver un período anterior, selecciona la versión que estaba publicada en ese momento. --- # 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 & Paywall 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> ## Después de la descarga \{#after-the-download\} Añade el archivo al código de tu app y sigue la guía de configuración específica de cada 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) - [Flutter](flutter-use-fallback-paywalls) - [Unity](unity-use-fallback-paywalls) - [Kotlin Multiplatform](kmp-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 muestra 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. --- # File: flow-common-issues --- --- title: "Problemas comunes" description: "Soluciona los problemas que impiden que un flow funcione correctamente en el Flow & Paywall Builder: previsualización, publicación o comportamiento inesperado." --- Este artículo recoge los problemas más frecuentes al crear un flow en el [Flow & Paywall Builder](adapty-flow-builder), con su causa y su solución. ## Un flow no se puede previsualizar ni publicar \{#a-flow-wont-preview-or-publish\} Puede que encuentres los siguientes errores al intentar previsualizar o publicar el flow: | Mensaje | Causa y solución | |---|---| | **No product selected** | Una tarjeta de producto no tiene ningún producto asignado. Selecciona uno o elimina la tarjeta. | | **This product no longer exists**, **This offer no longer exists** | El producto u oferta fue eliminado del dashboard. Selecciona otro en los lugares donde se usa, o utiliza **Replace in N places** en la entrada **Missing** del panel **Products** para corregir todos los usos en la pantalla de una vez. | | **This product is off this screen**, **This offer is off this screen** | Una variable de texto, condición o acción hace referencia a un producto que no está en la lista de la pantalla, por ejemplo, después de haberlo eliminado en el panel **Products**. Vuelve a añadir el producto en el panel o edita la referencia. | | **Offer required** | Se usa `offer_price`, `offer_billing_period` u `offer_full_duration` con un producto que no tiene oferta. Selecciona una oferta para el producto o usa una variable `prod_*` en su lugar. | | **Product reference is invalid** | La referencia al producto está mal formada. Vuelve a seleccionar el producto. | | **Purchase action has no product to buy** | Una acción **Purchase** no tiene producto: está vacía o apunta a una tarjeta eliminada. En la pestaña **Interactions** del elemento, establece **Product** en un producto o en `products.selectedProduct`. | | **Select product action has no product to select** | La tarjeta a la que apuntaba una acción **Select product** ya no existe: fue eliminada, movida fuera de su grupo de productos o está en otra pantalla. En la pestaña **Interactions** del elemento, apunta la acción a una tarjeta de esta pantalla. | | **Flow has no screens**, **Screen "…" has no elements** | Añade contenido a la pantalla vacía o elimínala. Un flow que solo contiene contenido de Remote Config está exento. | | **Invalid interaction action** | Una acción está incompleta: **Open URL** sin URL, **Navigate to screen** sin destino, **Set variable** sin asignación, **Custom** sin ID de acción, **Show alert** sin título ni mensaje, **Show** o **Hide** sin elemento, o una **Purchase** de pago web sin URL de Web Paywall. Abre las interacciones del elemento y completa o elimina la acción. | | **Missing navigation target** | Una acción **Navigate to screen** apunta a una pantalla que fue eliminada. Selecciona una pantalla existente o elimina la acción. | | **Missing product target**, **Invalid product target**, **Product target on another screen**, **Ambiguous product target**, **Select Product inside a component** | Una acción **Select product** apunta a una tarjeta que fue eliminada, no está en un grupo de productos, está en otra pantalla, comparte su ID de elemento con otro elemento en otra pantalla, o está dentro de un encabezado o pie de página compartido entre pantallas. Apunta la acción a una tarjeta de un grupo de productos en la misma pantalla, con un ID único. | | **Missing selectable group** | Un elemento apunta a un grupo seleccionable que ya no existe. Vuelve a conectarlo o recrea el grupo. | | **Invalid visibility condition** | Una regla de visibilidad condicional está incompleta. Complétala en el generador de condiciones. | | **Missing Default Locale Content** | Una acción **Open URL** tiene una URL en otro idioma pero no en el idioma predeterminado. Añade la URL predeterminada en el [panel **Localizations**](add-paywall-locale-in-adapty-paywall-builder). | | **Missing product reference** | Un flow antiguo usa un producto que no está registrado en la pantalla. Abre la pantalla y añade el producto en el panel **Products**. | [Previsualiza el flow en la app de Adapty](paywall-device-compatibility-preview) con regularidad mientras construyes. Un flow que no se puede publicar tampoco se cargará allí, y el mensaje de error indica el problema. :::tip Para saber si tu flow está listo para producción, ejecuta el [skill flow-audit](flow-audit-skill). Comprueba lo que los errores de publicación no detectan: botones sin función, cumplimiento con la store, textos incompletos y cobertura de traducciones. El informe identifica cada problema con el nombre del elemento y su pantalla. ::: ## La publicación falla con "Something went wrong" y sin detalles La solicitud de publicación falló en el lado de Adapty, no por culpa de tu flow. Tu versión en producción no ha cambiado. Espera unos minutos y vuelve a publicar. Si vuelve a fallar, copia el **Request ID** de la notificación y envíalo a [support@adapty.io](mailto:support@adapty.io) para que el equipo pueda localizar la solicitud fallida. ## Una oferta no se aplica en la compra \{#an-offer-doesnt-apply-at-purchase\} El builder solo comprueba que la oferta siga existiendo en Adapty. Si el descuento se aplica o no lo decide el store en el momento de la compra: las ofertas introductorias dependen de la elegibilidad del cliente, y las ofertas promocionales de App Store necesitan una In-App Purchase Key válida. Ambos casos se tratan en [Discrepancias de productos y precios](store-sync#product-and-price-discrepancies). ## El precio de una oferta está vacío en la vista previa \{#an-offer-price-is-empty-in-the-preview\} La vista previa del builder no puede conectarse a los stores, por lo que `offer_price`, `offer_billing_period` y `offer_full_duration` muestran el nombre de la variable entre llaves en lugar del valor real. Se resolverán cuando el SDK renderice el flow en tu app. Para previsualizar las condiciones que dependen del tipo de oferta, consulta [Previsualizar productos y ofertas](paywall-device-compatibility-preview#preview-products-and-offers). ## La publicación falla al introducir un color hex abreviado \{#publishing-fails-after-entering-a-shorthand-hex-color\} Introduce siempre los colores con exactamente seis dígitos hexadecimales, como `#FFFFFF`. El campo de color acepta valores más cortos pero nunca los expande, por lo que un valor abreviado como `#FFF` llega mal formado a la configuración del SDK y la publicación se rechaza. La opacidad no tiene relación: el mismo valor falla con opacidad al 100%. El caso más problemático es el de cuatro dígitos: se publica, pero muestra un color distinto al que seleccionaste. ## Las ediciones de tipografía no llegan a un estado de elemento \{#typography-edits-dont-reach-an-element-state\} Una vez que un estado, como **Selected**, tiene su propia configuración de tipografía, las ediciones en la tipografía del estado **Default** —incluida la aplicación de un estilo de texto diferente— no afectan a ese estado. Edita la tipografía del estado directamente. ## Faltan los controles de posición, tamaño y espaciado para un elemento \{#position-size-and-spacing-controls-are-missing-for-an-element\} El elemento y su contenedor usan la dirección **Free**. Eso deja solo una posición y un tamaño posibles: **Absolute**, con ancho y alto fijos. Adapty oculta los controles para todo lo demás: el selector **Position**, la sección **Spacing**, y las opciones **Fill** y **Hug**. Para recuperar los controles, cambia la [**Direction**](manage-paywall-ui-elements#direction) del elemento a **Vertical** u **Horizontal**. ## Las condiciones y variables se rompen al cambiar un ID de elemento \{#conditions-and-variables-break-after-an-element-id-change\} Las condiciones y las variables de texto hacen referencia a los elementos por su **Element ID**, y cambiar el ID no actualiza las referencias — dejan de resolverse silenciosamente. Establece el Element ID antes de referenciarlo. Si tienes que cambiarlo, actualiza todas las referencias de condiciones y variables para que coincidan. La excepción son los segmentos de barra de progreso: renombrar un segmento actualiza sus referencias automáticamente. ## Una condición o variable se resuelve como un valor vacío \{#a-condition-or-variable-resolves-to-an-empty-value\} El selector de variables muestra campos de todas las pantallas, por lo que una condición o variable de texto puede hacer referencia a un campo que el usuario aún no ha rellenado. En tiempo de ejecución, esa referencia se resuelve como un valor vacío. Referencia solo los campos recopilados en pantallas anteriores del flow. ## Los usuarios ven el valor inicial de una etiqueta personalizada en lugar del valor de tu app \{#users-see-a-custom-tags-initial-value-instead-of-your-apps-value\} Los usuarios ven el valor inicial de una [etiqueta personalizada](flow-custom-tags) en los siguientes casos: - **Tu app no pasa ningún valor**: El código que abre el flow no pasa ningún valor para el tag, o el usuario no ha actualizado tu app a la versión que lo hace. - **El nombre no coincide**: El nombre en el código de tu app es diferente al nombre del tag en el builder. Los nombres distinguen mayúsculas de minúsculas e incluyen el grupo. Por ejemplo, si el grupo `user` contiene un tag `streak`, el nombre exacto del tag es `user.streak`. - **Otro tag en el mismo elemento de texto no tiene valor**: Si un tag personalizado en un elemento de texto no tiene valor, todos los tags de ese elemento muestran su valor inicial. Pasa un valor para cada tag personalizado en el elemento de texto. La app móvil de Adapty no pasa valores para las etiquetas personalizadas, por lo que la vista previa solo muestra los valores iniciales. Para ver los valores que pasa tu app, abre el flow en una build de tu app. ## Los usuarios no ven nada en lugar de una etiqueta personalizada \{#users-see-nothing-in-place-of-a-custom-tag\} Los usuarios no ven nada en lugar de una [etiqueta personalizada](flow-custom-tags) en los siguientes casos: - **Tu app envía una cadena vacía**: Adapty trata una cadena vacía como un valor, por lo que el valor inicial no aparece como alternativa. Para mostrar el valor inicial, no envíes ningún valor para la etiqueta. - **La etiqueta no tiene valor inicial**: Para mostrar un valor alternativo cuando tu app no envía ningún valor, introduce un valor inicial para la etiqueta. ## La acción Set variable no cambia el producto seleccionado \{#a-set-variable-action-doesnt-change-the-selected-product\} El producto seleccionado no es una variable que puedas asignar, por lo que **Set variable** no puede cambiar la selección. Usa la acción [**Select product**](onboarding-actions#select-product) en su lugar. ## Una imagen o vídeo no se sube \{#an-image-or-video-wont-upload\} Un archivo que supere los límites de medios del Flow & Paywall Builder no se subirá. Verifica el archivo según los requisitos de [Imagen](custom-media#image) y [Vídeo](custom-media#video): | Elemento | Formatos | Tamaño máximo del archivo | Otros límites | | :--- | :--- | :--- | :--- | | Imagen | `.JPG`, `.PNG`, `.WEBP` | 20 MB | — | | Vídeo | `.MP4`, `.WEBM` | 50 MB | 30 segundos máximo; 640×640 píxeles mínimo | Estos formatos no son compatibles: - **GIFs animados y WEBPs animados**: Para añadir movimiento, usa un elemento de vídeo en su lugar. - **SVG**: Exporta el archivo como PNG o WEBP. Para los iconos, no necesitas subir nada — el elemento de icono usa la biblioteca [Tabler Icons](custom-media#icon) incluida. ## El pie de página no se renderiza \{#a-footer-doesnt-render\} El pie de página solo se renderiza en una pantalla donde **Vertical scroll** está habilitado. Activa **Vertical scroll** en [**Screen settings** > **Scroll**](paywall-layout-and-products#scroll). --- # File: paywall-onboarding-builder-deprecation --- --- title: "Obsolescencia del Paywall Builder y el Onboarding Builder" description: "El 1 de octubre de 2026, Adapty deja obsoletos el antiguo Paywall Builder y el Onboarding Builder. Los paywalls y onboardings publicados siguen funcionando — crea los nuevos en el Flow & Paywall Builder." --- El 1 de octubre de 2026, Adapty depreca el antiguo Paywall Builder y el Onboarding Builder en favor del [Flow & Paywall Builder](adapty-flow-builder). Todo lo que ya has publicado sigue funcionando: tus usuarios siguen viendo tus paywalls y onboardings, y tus datos permanecen intactos. La deprecación desactiva la creación de nuevos paywalls y onboardings, y pone fin al soporte de ambos builders. Aún puedes editar lo que ya has creado. ## Qué sigue funcionando \{#what-keeps-working\} La deprecación deja lo siguiente en su lugar: - **Paywalls y onboardings publicados**: El SDK sigue renderizándolos en tus apps, en los placements que tienen asignados hoy. - **Tus datos**: Los paywalls, onboardings y sus métricas permanecen en tu dashboard. No se elimina nada. - **Edición**: Puedes seguir editando los paywalls y onboardings que ya has creado. ## Qué deja de funcionar el 1 de octubre de 2026 \{#what-stops-on-october-1-2026\} A partir de esa fecha, en ambos builders: - **Sin nuevos paywalls ni onboardings**: No podrás crearlos en ninguno de los dos builders. Crea los nuevos en [Flow & Paywall Builder](adapty-flow-builder). - **Sin soporte**: Adapty deja de publicar correcciones para ambos builders. ## Los paywalls personalizados siguen disponibles \{#custom-paywalls-stay-available\} La obsolescencia afecta a los dos editores visuales, no al paywall en sí. Si construyes la interfaz de tu paywall en el código de tu propia app y usas Adapty como infraestructura de compras, esta obsolescencia no te afecta. Sigues creando [paywalls personalizados](paywalls) para definir qué productos vendes y asignándolos a placements. Se encuentran en la pestaña **Custom Paywalls** de **Flows & Paywalls** en el menú principal de Adapty. Puedes añadir un [Remote Config](customize-paywall-with-remote-config) opcional a un paywall personalizado para cambiar textos y recursos multimedia sin publicar una nueva versión de la app. Tu app obtiene los productos del paywall y llama a `makePurchase` — consulta <InlineTooltip tooltip="Renderiza tu propia interfaz de paywall y delega las compras en Adapty">[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) y [Capacitor](capacitor-implement-paywalls-manually)</InlineTooltip>. ## Migrar al Flow y Paywall Builder \{#move-to-flow--paywall-builder\} Un [flow](adapty-flow-builder) combina pantallas de onboarding y un paywall en una sola experiencia, creada en un único editor y servida desde un único placement — un nuevo placement de flow, ya que un flow es un tipo de contenido diferente al paywall o al onboarding que reemplaza. Para recorrer todo el proceso — crear el flow, previsualizarlo en un dispositivo, crear un placement de flow y actualizar tu app al SDK v4 de Adapty — consulta [Migrar a flows](migrate-to-flows). ### Convierte tu paywall con un solo clic \{#convert-your-paywall-in-one-click\} No tienes que reconstruir tu paywall a mano. La acción **Move to new builder** lo recrea como un flow en borrador, conservando el diseño, el texto en todos los idiomas y los productos con precios como variables. Tu paywall original sigue activo y sin cambios mientras editas y pruebas el flow — consulta [Convertir un paywall en un flow](convert-paywall-to-flow). Para un flow que va más allá del paywall original — un cuestionario, ramificaciones, varios idiomas — la [skill del agente flow-generator](flow-generator-skill) construye y edita flows desde tu terminal a través de tu propia herramienta de IA. Prepárate para dos pasos que el antiguo builder no te pedía: - **Un lanzamiento de app**: Los flows se renderizan en el SDK de Adapty v4 y versiones posteriores, así que los usuarios verán tu flow solo después de actualizar a una versión de la app que lo incluya. Comienza la actualización del SDK ahora, para que ya haya una versión v4 publicada cuando tu primer flow esté listo. - **Un nuevo placement**: Un flow convertido no hereda el placement del paywall, y no puede reutilizar el ID de ese placement — los IDs de placement son únicos en toda tu app. Crea un placement de flow con un ID nuevo y mantén activo el placement del paywall para las versiones de la app que todavía lo usen. Si tienes más placements de los que quieres crear a mano, la [skill del agente migrate-placements](migrate-placements-skill) los genera a partir de los placements de paywall que ya tienes. Si un paywall genera la mayor parte de tus ingresos y no quieres cambiar todo su tráfico de golpe, [despliégalo gradualmente](flow-gradual-rollout): muéstralo a una pequeña parte de los usuarios junto con el paywall existente, compara la conversión y aumenta el porcentaje conforme los resultados se mantengan. --- # File: migrate-to-flows --- --- title: "Migrar a flows" description: "Mueve tu onboarding y paywall separados a un único flow de Adapty: qué cambia y cómo desplegarlo sin interrumpir a los usuarios en versiones anteriores de la app." --- En Adapty, un *flow* combina un onboarding y un paywall en una sola entidad bajo un único placement. Un flow reemplaza el onboarding y el paywall separados que construyes y sirves de forma independiente. :::warning A partir del 1 de octubre de 2026, no podrás crear nuevos paywalls ni onboardings en los builders heredados, y dejará de ofrecerse soporte para ellos. Todo lo que ya tienes seguirá funcionando y podrá editarse — consulta [qué cambia en esa fecha](paywall-onboarding-builder-deprecation). ::: Esta guía explica qué cambia al migrar a flows y cómo aplicar el cambio sin afectar a los usuarios con versiones antiguas de la app. :::important Los flows requieren el SDK de Adapty v4.0 o posterior. ::: ## Flows frente a onboardings y paywalls \{#flows-vs-onboardings-and-paywalls\} Con onboardings y paywalls por separado, gestionas dos builders y dos placements. Además, tienes que pasar al usuario del onboarding al paywall desde tu propio código. Un flow reemplaza ambos con una sola experiencia — pantallas de introducción, un cuestionario y la pantalla de compra — creada en un único editor y servida desde un único placement. Ese placement es nuevo: un flow es su propio tipo de contenido, por lo que no puede ejecutarse en el placement de onboarding o paywall que reemplaza. La tabla siguiente 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\} **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)**: Convierte un paywall existente con un clic, o crea un flow desde una plantilla; sin necesidad de desarrollador. 2. **[Previsualiza en dispositivo](#preview-on-device)**: Comprueba el flow en un dispositivo real a través de la app móvil de Adapty; sin necesidad de 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 convive con tus placements existentes. 4. **[Actualiza el SDK](#update-the-sdk)**: Actualiza al SDK de Adapty 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\} **Si tu paywall fue creado en el Paywall Builder heredado, conviértelo en lugar de reconstruirlo.** La acción **Move to new builder** lo recrea como un flow en borrador con un solo clic — diseño, textos en todos los idiomas y productos con precios como variables — y el original permanece activo mientras editas y pruebas. Consulta [Convierte un paywall en un flow](convert-paywall-to-flow). Para crear algo nuevo, elige la opción que mejor se adapte: - **[Empieza desde una plantilla](paywall-builder-templates)**: Una plantilla inicial o un flow en blanco en el Flow & Paywall Builder, sin código. - **[Usa la skill del agente](flow-generator-skill)**: Tu herramienta de programación con IA crea y edita flows desde el terminal — ideal para cuestionarios, ramificaciones y añadir locales en bloque. Referencia mientras construyes: - **[Documentación de Flow & Paywall Builder](adapty-flow-builder)**: Te guía a través del builder y lo que puedes crear. - **[Recetas de flow más comunes](flow-builder-recipes)**: Guías paso a paso para las pantallas más habituales. - **Pregunta a la IA**: Usa el chat en cualquier página de la documentación cuando te quedes atascado. ### Vista previa en dispositivo \{#preview-on-device\} Puedes previsualizar el flow en un dispositivo real sin tocar la app. Descarga la app móvil de Adapty para [iOS](https://apps.apple.com/us/app/adapty/id6739359219) o [Android](https://play.google.com/store/apps/details?id=io.adapty.dashboard), e inicia sesión con la cuenta de Adapty que usas en el dashboard. Luego, en el Flow & Paywall 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, la ramificación, los textos y el diseño. :::note En el modo de vista previa, Adapty no puede acceder a tus productos en los stores, por lo que los precios mostrados no son reales. Las compras reales se verifican más adelante, en el 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\} Los flows, paywalls y onboardings son tres tipos de contenido diferentes, y cada uno se mide con su propio conjunto de métricas — por eso un placement sirve solo para un tipo. El tipo de un placement queda fijado en el momento de crearlo, así que no hay forma de convertir un placement de onboarding o paywall en un placement de flow (consulta los [tipos de placement](create-placement)). Cada flow que publiques necesita su propio placement nuevo. :::warning Asigna al nuevo placement de flow un ID de placement completamente nuevo y único. Los IDs de placement son únicos en toda la aplicación y no por tipo, por lo que un placement de flow no puede reutilizar el ID de un placement de paywall o de onboarding, ni siquiera el del que sustituye su contenido. Reutilizar el ID no es una cuestión de preferencia: el Dashboard lo rechaza. ::: :::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 el onboarding y paywall existente hasta que actualicen. Retira los placements antiguos solo cuando la adopción de SDK v4 sea suficientemente alta. ::: No tienes que mover todas las ubicaciones a flows de una vez. En SDK v4, el método `getFlow` obtiene datos tanto de placements de flows como de placements de paywalls, por lo que tu app llama al mismo método en todas partes. 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 ejecutando pruebas A/B con flows. Ejecuta una [prueba A/B regular](ab-tests) entre variantes de flow en un placement de flow, o una [prueba A/B entre placements](ab-test-types) en varios placements de flow. No puedes incluir un flow y un paywall en la misma prueba A/B, ni regular ni entre placements: son tipos de contenido distintos con métricas diferentes. #### Crea los placements con un agent skill \{#create-the-placements-with-an-agent-skill\} С более чем несколькими placements для переноса, [навык migrate-placements](migrate-placements-skill) создаёт их прямо из вашего AI-инструмента для написания кода. Он считывает все placements в вашем приложении через [Adapty CLI](developer-cli-quickstart), группирует их по paywall, который каждый из них обслуживает, спрашивает для каждого paywall, откуда берётся flow, затем создаёт один flow placement для каждого paywall placement в области видимости и возвращает сопоставление старых и новых ID, а также вызов `getFlow`, который необходимо добавить в приложение. Wait, I need to translate to Spanish (es-ES), not Russian. Let me redo this. Si tienes más de unos pocos placements que migrar, el [skill migrate-placements](migrate-placements-skill) los crea directamente desde tu herramienta de codificación con IA. Lee todos los placements de tu aplicación a través del [Adapty CLI](developer-cli-quickstart), los agrupa por el paywall al que sirve cada uno, pregunta por cada paywall de dónde viene el flow, luego crea un flow placement por cada paywall placement en el alcance y devuelve el mapeo de IDs antiguos a nuevos y la llamada `getFlow` que tu app debe implementar. Tu paywall placements se leen pero nunca se editan, así que siguen sirviendo mientras trabajas, y cada ID de placement que propone la skill espera tu aprobación. ### Actualiza el SDK \{#update-the-sdk\} Con el placement de tu 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), [Flutter](migration-to-flutter-sdk-v4), [Unity](migration-to-unity-sdk-v4), [Kotlin Multiplatform](migration-to-kmp-sdk-v4) o [Capacitor](migration-to-capacitor-sdk-v4) — para conocer los pasos específicos de actualización. Una vez que el flow esté conectado, verifícalo como cualquier otro flow de compra: ejecútalo en un dispositivo o simulador y realiza una compra en sandbox para confirmar que los productos, la compra y el nivel de acceso funcionan correctamente. Consulta la guía de pruebas de tu plataforma: [iOS](ios-test), [Android](testing-on-android), [React Native](react-native-test), [Flutter](flutter-test), [Unity](unity-test), [Kotlin Multiplatform](kmp-test) o [Capacitor](capacitor-test). :::note Los usuarios solo ven los flows después de instalar la app compilada con SDK v4+. Quienes tengan una versión anterior de la app seguirán viendo el onboarding y el paywall existentes, por eso los placements antiguos permanecen activos durante la transición. ::: ## Etiquetas personalizadas del antiguo Paywall Builder \{#custom-tags-from-the-old-paywall-builder\} Si [conviertes un paywall](convert-paywall-to-flow) que usa etiquetas personalizadas, el flow tendrá una variable personalizada normal en lugar de cada una de ellas. Para volver a mostrar los valores de tu aplicación, [reemplaza cada variable con una etiqueta personalizada](flow-custom-tags#replace-a-variable-with-a-custom-tag). Las [etiquetas personalizadas](flow-custom-tags) en los flows difieren de las antiguas de las siguientes maneras: - **Creación de etiquetas**: No puedes escribir una etiqueta personalizada directamente en un elemento de texto. Crea la etiqueta en el panel **Variables** y luego selecciónala desde el desplegable Variables **Add variable**. - **Fallback**: Cada etiqueta personalizada tiene un [Valor inicial](flow-custom-tags#values-and-fallbacks) para todos los idiomas, en lugar de una línea de fallback por cada idioma. Para reutilizar los valores que tu app envía a los paywalls del Paywall Builder antiguo, asigna a cada etiqueta personalizada del flow el mismo nombre que la etiqueta anterior. --- # File: convert-paywall-to-flow --- --- title: "Convertir un paywall en un flow" description: "Recrea un paywall existente como un flow editable con un solo clic. El original sigue activo mientras construyes y pruebas la nueva versión." --- Si tu paywall actual fue creado en el Paywall Builder heredado, puedes convertirlo en un flow. Con un solo clic, la acción **Move to new builder** recrea tu paywall como un draft flow editable. La conversión copia lo siguiente: - **Diseño y maquetación**: Las pantallas, elementos y estilos del paywall original. - **Texto**: Todo el contenido, en cada idioma que hayas añadido. - **Productos**: Cada producto, con precios y descuentos como variables en lugar de valores fijos. - **Custom tags**: La conversión crea una variable personalizada normal por cada custom tag del paywall. Cada variable tiene el nombre del tag y ningún valor inicial. Para mostrar los valores de tu app, [reemplaza cada variable con un custom tag](flow-custom-tags#replace-a-variable-with-a-custom-tag). El paywall original permanece activo y sin cambios, así que puedes personalizar y probar el flow antes de hacer el cambio. Lo que no se traslada en la conversión es el placement del paywall: un flow es un tipo de contenido diferente, por lo que necesita un [placement de flow](#serve-your-flow) propio. :::warning Algunas funciones del Paywall Builder heredado no tienen equivalente en flows, por lo que un paywall convertido puede no coincidir exactamente con el original. El panel [Revisión de importación](#import-review) te guía a través de cada diferencia y cómo resolverla. ::: ## Convierte tu paywall \{#convert-your-paywall\} Para convertir un paywall en un flow, abre la página de resumen del paywall y haz clic en **Move to new builder**. Este botón solo aparece en paywalls creados con el Paywall Builder para el SDK de Adapty 3.x — el convertidor necesita la configuración de ese builder. Para convertir un paywall más antiguo, primero muévelo al Paywall Builder de SDK 3.x. Adapty abre el borrador del flow en el editor, donde puedes editarlo antes de publicarlo. Si algo no se trasladó correctamente, el builder inicia una [Revisión de importación](#import-review). ## Revisión de la importación \{#import-review\} El panel **Import review** muestra todo lo que la conversión no pudo reproducir y sugiere una solución para cada discrepancia. Revisa el panel y, a continuación, [previsualiza el flow en un dispositivo real](migrate-to-flows#preview-on-device) antes de publicarlo. ## Sirve tu flow \{#serve-your-flow\} :::link Artículo principal: [Migrar a flows](migrate-to-flows) ::: Un flow convertido empieza como borrador y no reemplaza el placement en el que se ejecuta el paywall original. Para mostrárselo a tus usuarios: 1. [Publica el flow](builder-save-publish). 2. Crea un nuevo placement de flow y añade el flow a él. 3. Obtén ese placement de flow en tu app con el SDK de Adapty v4. :::warning El flow no puede ir en el placement del paywall, ni puede usar el ID de placement del paywall. Los flows y los paywalls son tipos de contenido distintos, así que cada uno vive en su propio tipo de placement, y el tipo de un placement queda fijado en el momento de crearlo. Los IDs de placement son únicos en toda tu app, por lo que el nuevo placement del flow necesita un ID que ningún otro placement use, incluido el paywall al que este flow reemplaza. Consulta [Crear un nuevo placement para tu flow](migrate-to-flows#create-a-new-placement-for-your-flow). ::: Mantén el placement del paywall activo mientras las versiones antiguas de la app sigan llamándolo — esos usuarios tienen su ID compilado en la app y no pueden acceder al flow hasta que actualicen. ## Convertir muchos paywalls a la vez \{#convert-many-paywalls-at-once\} **Move to new builder** convierte un paywall a la vez, y cada flow que genera necesita su propio placement. Cuando estás migrando una aplicación completa en lugar de un solo paywall, el [skill del agente migrate-placements](migrate-placements-skill) lista todos los placements de paywall de tu app, los agrupa según el paywall que sirve cada uno para que sepas cuántas conversiones te esperan, te indica qué paywalls ya tienen un flow y crea los nuevos placements de flow una vez que los flows estén publicados. La conversión del clic es tuya. No hay ningún comando de CLI para **Move to new builder**, y el panel [Import review](#import-review) es tuyo para gestionar: la skill espera, luego vuelve a leer tu cuenta y encuentra los flows que produjo la conversión. --- # File: paywalls --- --- title: "Paywalls personalizadas" description: "Qué es un paywall personalizado en Adapty y cómo tu app lo obtiene." --- Un **paywall personalizado** es la configuración de productos que alimenta una pantalla de paywall que construyes con tu propio código. Tú construyes y mantienes la pantalla, y Adapty gestiona la compra. Para comparar los paywalls personalizados con los flows, que Adapty diseña y renderiza por ti, consulta [Flows y paywalls](flows-and-paywalls). <CustomDocCardList ids={['create-paywall', 'paywall-metrics']} /> Para mostrar un paywall personalizado en tu app: 1. [Crea el paywall personalizado](create-paywall) y guarda tus productos en él: su orden, sus ofertas promocionales y un [Remote Config](customize-paywall-with-remote-config) opcional para texto y recursos multimedia. 2. Sírvelo desde un [placement de paywall](placements). 3. Obtén el paywall personalizado con sus productos y llama a `makePurchase` para completar la compra; consulta [Implementar paywalls personalizados](ios-implement-paywalls-manually). <CustomDocCardList ids={['fallback-paywalls', 'paywall-localization', 'customize-paywall-with-remote-config', 'web-paywall']} /> --- # File: create-paywall --- --- title: "Crear paywall personalizado" description: "Crea un paywall personalizado: la configuración de producto que tu app obtiene cuando implementas la pantalla de compra tú mismo." --- Si implementas tu pantalla de compra por tu cuenta, necesitas crear un **custom paywall** en Adapty. Este define qué productos vendes, en qué orden y con qué ofertas promocionales. Los flows funcionan de manera diferente: los productos se vinculan directamente al flow. Una vez creado, asigna el custom paywall a un [placement](placements): el punto de tu app donde insertas contenido de Adapty. :::tip También puedes crear custom paywalls mediante programación usando la [CLI para desarrolladores](developer-cli-paywalls#adapty-paywalls-create). ::: <details> <summary>Antes de empezar a crear paywalls personalizados (Haz clic para expandir)</summary> 1. [Crea al menos un producto](create-product). 2. (opcional) [Crea una oferta](create-offer). </details> ## Crear un paywall personalizado \{#create-custom-paywall\} Para crear un paywall personalizado en el Adapty Dashboard: 1. Ve a [**Flows & Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty y abre la pestaña **Custom Paywalls**. 2. Haz clic en **Create custom paywall**. 3. En la página **Custom Paywalls / New paywall**, introduce un **Paywall name** para identificar este paywall en 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 organízalos en el orden que prefieras. - Una vez que el paywall se muestre en producción, no podrás cambiar los productos en él, ya que esto podría afectar las métricas del paywall. ::: 6. Si ofreces pruebas gratuitas u otras ofertas para tus productos, añádelas aquí; de lo contrario, no estarán disponibles. Elige una oferta que hayas [creado anteriormente](create-offer) para este producto en la lista **Offer**. La lista solo está disponible para productos que tienen ofertas. 7. Haz clic en **Create as a draft** para confirmar la creación del paywall. ¡Tu paywall personalizado 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 personalizado: 1. Añádelo a un [placement](placements) — el ID del placement es el único identificador de Adapty que escribes directamente en tu app. 2. Implementa la pantalla de compra en tu app: [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) | [Capacitor](capacitor-implement-paywalls-manually) --- # File: customize-paywall-with-remote-config --- --- title: "Diseña un paywall personalizado con Remote Config" description: "Personaliza tu paywall personalizado con Remote Config en Adapty para un mejor targeting." --- :::important Esta guía cubre Remote Config para paywalls personalizados. Para el Flow & Paywall Builder, consulta [Personalizar flow con Remote Config](customize-flow-with-remote-config). ::: El Remote Config de paywall es una herramienta muy potente que ofrece opciones de configuración flexibles. Permite usar payloads JSON personalizados para ajustar tus paywalls personalizados con precisión. Con él puedes definir parámetros como títulos, imágenes, fuentes, colores y mucho más. <details> <summary>Antes de empezar a personalizar un paywall personalizado (haz clic para ampliar)</summary> 1. [Crea un producto](create-product). 2. [Crea un paywall personalizado y añade el producto](create-paywall). </details> Para empezar a personalizar un paywall personalizado usando el Remote Config: 1. Ve a [**Flows & Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty y abre la pestaña **Custom Paywalls**. 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 personalizados">[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), [Unity](present-remote-config-paywalls-unity), [Kotlin Multiplatform](present-remote-config-paywalls-kmp), y [Capacitor](present-remote-config-paywalls-capacitor)</InlineTooltip> en tu app. --- # File: add-remote-config-locale --- --- title: "Localizar paywalls personalizados con Remote Config" description: "Añade locales de Remote Config para personalizar los paywalls personalizados de Adapty." --- Adaptar tus 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, asegurándote de que tu producto conecte con las audiencias locales. Si no usas el antiguo Adapty Paywall Builder para diseñar paywalls, igualmente puedes localizar tus paywalls personalizados y gestionar las localizaciones sin necesidad de redesplegar tu 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 las variables para cada idioma. 3. Gestionas las variables en el código de la app. 4. Cuando obtienes un paywall con productos y envías un idioma, recibes los valores de las variables correspondientes. De este modo, las localizaciones no están hardcodeadas 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 la configuración de cada idioma. Por ejemplo, traducir claves de cadena, alternar valores booleanos (por ejemplo, `TRUE` para inglés, `FALSE` para italiano) o incluso cambiar imágenes de fondo. ## Configura la localización para paywalls personalizados \{#set-up-localization-for-custom-paywalls\} 1. Ve a [**Flows & Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty y abre la pestaña **Custom Paywalls**. 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 estos locales 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 idiomas a un paywall, asegúrate de implementar correctamente los códigos de idioma en el código de tu app. Consulta <InlineTooltip tooltip="las guías sobre cómo usar localizaciones y códigos de idioma en tu app">[iOS](localizations-and-locale-codes), [Android](android-localizations-and-locale-codes), [React Native](react-native-localizations-and-locale-codes), [Flutter](flutter-localizations-and-locale-codes), [Unity](unity-localizations-and-locale-codes), [Kotlin Multiplatform](kmp-localizations-and-locale-codes), y [Capacitor](capacitor-localizations-and-locale-codes)</InlineTooltip> --- # File: web-paywall --- --- title: "Paywall web" description: "Configura un paywall web para recibir pagos sin las tarifas y auditorías del App Store." --- :::important Antes de comenzar, asegúrate de tener instalada la versión del SDK de Adapty requerida: - **Paywall Builder o paywalls personalizados**: 3.6.1 o posterior (iOS), 3.15 o posterior (Android y multiplataforma) - **Flow y Paywall Builder**: 4.0 o posterior (todas las plataformas) ::: Con Adapty, puedes crear un paywall o [flow](adapty-flow-builder) con un botón que redirige a tus usuarios al navegador para realizar el pago. Cuando vuelven a la app tras una compra exitosa, la suscripción se activa. En el storefront de la App Store de EE. UU., esto te permite evitar la comisión de la tienda mientras Adapty sigue rastreando los pagos de los usuarios. :::tip En el App Store, los paywalls web funcionan sin requisitos adicionales ni comisión de Apple solo en la tienda de EE. UU. Para otras regiones, consulta [Otras regiones del App Store](#other-app-store-regions). Para usar un paywall exclusivamente en EE. UU., duplica tu paywall actual y configura un paywall web. Así tendrás dos paywalls casi idénticos en uso: uno para EE. UU. y otro para el resto del mundo. ::: ## Cómo funciona \{#how-it-works\} Un web paywall es una URL única generada para cada uno de tus paywalls in-app. Se abre en el navegador para realizar el pago y funciona con distintos proveedores de pago (Stripe, Paddle y otros), tanto para páginas sencillas con un botón de Apple Pay como para flows más complejos con ofertas adicionales. Los paywalls web funcionan de la siguiente manera: 1. **Configura el aspecto y el funcionamiento de la página del paywall web** en el editor de paywall web. 2. **Vincula el paywall web** en la configuración del paywall. 3. En el paywall de tu app, **añade un botón** que redirija a los usuarios al navegador. 4. Cuando los usuarios pulsan el botón, el SDK de Adapty **genera una URL única** que incluye el ID de perfil de Adapty del usuario. 5. Los usuarios **van a la página del paywall web** y **pagan** la suscripción mediante un método de pago externo. 6. Cuando vuelven a la app, el SDK de Adapty **consulta las actualizaciones del perfil** para confirmar que la suscripción se ha activado. 7. Adapty registra la compra y supervisa la suscripción para detectar cambios de estado, como renovaciones o cancelaciones. El ID de perfil en la URL vincula la compra web con el usuario de la aplicación. La compra llega al perfil que tu app ya utiliza, por lo que no necesitas llamar a `Adapty.identify()` para un comprador web. ## Paso 1. Crear un paywall web \{#step-1-create-a-web-paywall\} 1. Consigue un paywall con el que trabajar: - Para habilitar los pagos externos en un paywall existente, [duplícalo](duplicate-paywalls). Así podrás mostrar el web paywall a tu segmento objetivo y el original al resto. - Para empezar desde cero, [crea](create-paywall) un nuevo paywall. 2. En la página del paywall, cambia a la pestaña **Web paywall** y haz clic en **Create web paywall**. Serás redirigido a una nueva página. 3. Configura el web paywall y conecta un método de pago. :::tip Para obtener ayuda con la configuración del editor externo y la conexión de un proveedor de pagos, consulta la [guía de inicio rápido](web-paywall-configuration). ::: 4. Vuelve a la página **Web paywall** y pega el enlace del paywall. :::important Al lanzar tu paywall al entorno de producción, asegúrate de usar el enlace correcto generado tras publicar tu web paywall. El formato del enlace es `paywalls-....fnlfx.com`. ::: 5. Haz clic en **Save**. ## Paso 2. Activar el paywall \{#step-2-trigger-the-paywall\} Para usar tu paywall web, necesitas activarlo, y la forma de hacerlo depende de tu configuración: - Si estás usando el Flow & Paywall Builder, solo necesitas [añadir un nuevo botón](#step-2a-add-a-web-purchase-button) que usará el enlace que has proporcionado para rastrear las compras y enviar los datos de vuelta a Adapty. - Si estás usando el SDK, debes configurar el método [`openWebPaywall`](#step-2b-call-the-sdk-method) para gestionar los paywalls web. ### Paso 2a. Añadir un botón de compra web \{#step-2a-add-a-web-purchase-button\} Si usas el **Flow & Paywall Builder**, necesitas añadir un botón de paywall web. El botón utilizará el enlace que has proporcionado para rastrear las compras y enviar los datos de vuelta a Adapty. 1. Abre el flow y añade un botón. Si usas una plantilla o un paywall existente, añade un botón de paywall web junto al botón de compra existente. Puedes configurarlo de la misma manera. 2. En el panel **Interactions** de la derecha, haz clic en **Add trigger**. Luego, asigna la acción **Purchase** a este trigger. 3. En la configuración de la acción, cambia a la pestaña **Web payment**. Allí, selecciona un producto y, opcionalmente, una oferta para asociar al botón de compra web. 4. Pega el enlace del paywall web en el campo **Web paywall URL**. 5. Por defecto, los paywalls web se abren en un navegador integrado en la app para que los usuarios no tengan que salir de ella. Si prefieres abrirlos en un navegador externo, selecciona **Open in external browser**. ### Paso 2b. Llama al método del SDK \{#step-2b-call-the-sdk-method\} Si trabajas con un paywall que has desarrollado tú mismo, necesitas gestionar los paywalls web usando el método del SDK. Consulta las guías específicas de cada plataforma: - [iOS](ios-web-paywall) - [Android](android-web-paywall) - [React Native](react-native-web-paywall) - [Flutter](flutter-web-paywall) - [Unity](unity-web-paywalls) ## Paso 3. Configura un placement \{#step-3-set-up-a-placement\} Dado que App Store solo permite paywalls web sin requisitos adicionales en la tienda de EE. UU., crea un segmento de usuarios separado para usuarios de iOS en EE. UU. y configura un placement para dirigir diferentes paywalls a distintos segmentos. Para usuarios de Android, no aplica ninguna restricción geográfica: crea un segmento de Android separado sin filtro de país. 1. [Crea un nuevo segmento](segments) con los siguientes atributos: - **Country from store account**: United States - **Platform**: iOS and iPadOS - **App version**: La última que utiliza el SDK de Adapty. 2. [Crea](create-placement) un placement o [edita](edit-placement) uno existente. [Añade una nueva audiencia](add-audience-paywall-ab-test) con el paywall web y el segmento creado. ### Otras regiones de App Store \{#other-app-store-regions\} La Unión Europea, Japón y Brasil también permiten que las aplicaciones enlacen a compras en la web, con estas condiciones: - Tu cuenta de desarrollador necesita la habilitación de Apple para la región. - Antes de que tu aplicación abra el enlace, debe mostrar la hoja de divulgación de Apple. El SDK de Adapty no muestra esta hoja. - Apple cobra una comisión del 15 % en las compras realizadas en los 7 días siguientes al toque del enlace. La tarifa reducida para los miembros del Small Business Program y para las suscripciones después de su primer año es del 10 %. - En Japón y Brasil, tu aplicación también debe ofrecer compras in-app en la misma pantalla. Para ver las reglas completas, consulta las páginas de Apple para la [Unión Europea](https://developer.apple.com/support/payment-options-on-the-app-store-in-the-eu), [Japón](https://developer.apple.com/support/app-distribution-in-japan/) y [Brasil](https://developer.apple.com/support/app-distribution-in-brazil/). ## Añade un embudo de cancelación \{#add-a-cancellation-funnel\} Un embudo de cancelación es una página web que un suscriptor abre para cancelar su suscripción. Antes de que la cancelación se complete, el embudo puede ofrecer una pausa, un plan con descuento o un período gratuito. Lo construyes en el mismo editor que tu paywall web, y tu app lo abre mediante una URL. El embudo cancela la suscripción con tu proveedor de pagos, por lo que tu app no necesita hacer ninguna llamada a la API de cancelación. Para crear un embudo de cancelación, en el editor de paywall web, abre la pestaña **Cancellation**. Las acciones de retención solo están disponibles en los embudos de cancelación, por lo que no puedes añadirlas al embudo que sirve tu paywall web. Para más información sobre el embudo en sí, consulta la [guía de FunnelFox sobre embudos de cancelación](https://funnelfox.com/docs/dashboard/funnels#cancellation-funnels). ### Obtén el ID de perfil de FunnelFox del suscriptor \{#get-the-subscribers-funnelfox-profile-id\} Un funnel de cancelación identifica al suscriptor mediante su ID de perfil de FunnelFox. Tras una compra de suscripción web, FunnelFox escribe ese ID en el [perfil](profiles-crm) de Adapty como el atributo personalizado `fnlfx_profile_id`, junto con `fnlfx_funnel_id`, `fnlfx_session_id`, `vendor_profile_id` y `vendor`. FunnelFox escribe estos atributos una sola vez, en la primera compra de suscripción web. Las compras posteriores los dejan tal como están. En el caso de una compra consumible, FunnelFox solo los escribe cuando el elemento de checkout tiene un nivel de acceso personalizado. Tu app lee `fnlfx_profile_id` de `customAttributes` en el perfil del usuario. Para ver el mismo valor en Adapty, abre **Profiles** y busca el perfil del usuario. :::note Adapty almacena hasta 30 atributos personalizados por perfil. Cuando un perfil está lleno, Adapty descarta los atributos adicionales sin devolver un error, y FunnelFox no los reenvía. Si tu app define sus propios atributos personalizados, deja espacio para los cinco de FunnelFox. ::: ### Abre el funnel desde tu app \{#open-the-funnel-from-your-app\} Tu app construye la URL de cancelación por sí misma. No va en el campo **Web paywall URL** — ese campo pertenece a un paywall que vende un producto, y Adapty añade `adapty_profile_id`, no `fpid`. Combina la URL publicada de tu funnel de cancelación con el `fnlfx_profile_id` del suscriptor: ``` https://<your-project>.fnlfx.com/cancel?fpid=<fnlfx_profile_id> ``` Pasa el valor del atributo tal como está almacenado, sin ningún prefijo. Abre la URL cuando el usuario pulse tu botón de cancelar. Los usuarios pueden cerrar la página en cualquier momento, incluso después de que el flujo cancele la suscripción. No trates su regreso a la app como una confirmación. Recupera el perfil para obtener el estado actual. Una suscripción cancelada mantiene su nivel de acceso hasta que finalice el período pagado. --- # 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." --- :::important Este artículo trata sobre los paywalls de respaldo para Adapty SDK v3 y versiones anteriores. En SDK v4, un único archivo de respaldo incluye datos de flows y paywalls — consulta [Fallback flows](fallback-flows). El formato antiguo del archivo de respaldo no es compatible con SDK v4, así que descarga la versión correcta después de actualizar. ::: 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 a nuestros servidores. ¿Pero qué ocurre si el dispositivo no puede conectarse a Adapty por problemas de red o caídas 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). El archivo siempre usa la configuración regional `en`, por lo que los usuarios sin conexión solo ven el contenido en inglés. [Actualiza al SDK v4](migrate-to-flows#update-the-sdk) para eliminar esa limitación: su archivo de respaldo incluye todas las configuraciones regionales. * Cada placement solo puede tener un paywall de respaldo. Si tu configuración incluye diferentes configuraciones de paywall para distintas [audiencias](audience), Adapty utiliza 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, debes 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 personalizadas de paywall" description: "Realiza un seguimiento y analiza métricas de rendimiento personalizadas del paywall para mejorar los ingresos por suscripción." --- Adapty recopila una serie de métricas para ayudarte a medir mejor el rendimiento de tus paywalls personalizados. Todas las métricas se actualizan en tiempo real, excepto las vistas, que se actualizan cada varios minutos. Todas las métricas, salvo 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 pestaña **Custom Paywalls** de **Flows & Paywalls**, donde encontrarás una visión general del rendimiento de todos tus paywalls personalizados. 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 acceder a las métricas de detalle del paywall. En esta sección encontrarás métricas completas 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, trial y compra pueden agruparse por dos fechas diferentes: - **La fecha del evento**: cuándo se vio el paywall, cuándo empezó el trial o cuándo se realizó la compra. - **La fecha de instalación**: cuándo el usuario abrió la app por primera vez. Ambas vistas pueden mostrar números muy distintos para el mismo rango de fechas. La casilla **Filter metrics by install date** controla cuál usa el dashboard: - **Sin marcar (predeterminado)**: 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 consultas los trials. - **Sin marcar**: Muestra los trials que *empezaron* en abril, independientemente de cuándo instalaron esos usuarios. - **Marcada**: Muestra los trials de usuarios que *instalaron* en abril, independientemente de cuándo empezaron sus trials. La vista por fecha de instalación sigue creciendo después de que termina el período. Un usuario que instaló en abril y empieza un trial en julio se añade a abril en ese momento, por lo que las métricas de abril son más altas cada vez que las abres. La vista por fecha de evento no cambia de esa manera: cada evento cuenta en el día en que ocurrió. Usa la vista por fecha de instalación para medir el rendimiento de adquisición de usuarios de una cohorte específica. Usa la vista por fecha de evento para medir la actividad de paywall u onboarding en un período concreto. ### 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 utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo 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 construir un nuevo 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 te permite copiar todas las configuraciones visuales: - Ajustes de diseño del paywall y todos sus elementos - Contenido multimedia - Localización La migración solo aplica a la configuración del builder y no copia productos ni Remote Config. :::note Si migras una configuración del Paywall Builder con fuentes personalizadas, pruébalas en un dispositivo, ya que pueden mostrarse incorrectamente. ::: ## Migrar paywall \{#migrate-paywall\} :::important Solo puedes migrar paywalls creados en el Paywall Builder para el SDK 3.x de Adapty. Para migrar un paywall creado para el SDK 2.x o anterior, primero muévelo al Paywall Builder del SDK 3.x. ::: 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 personalizado" description: "Aprende a gestionar paywalls duplicados y optimizar el rendimiento de los paywalls en Adapty." --- Duplicar un paywall es la forma de cambiar los productos u ofertas en un paywall que Adapty ya no te permite editar; consulta [Añadir oferta al paywall](create-offer#add-offer-to-paywall) para saber cuándo aplica esa restricción. Duplicar también mantiene intactas las analíticas del paywall original cuando realizas cambios menores en un paywall que ya está activo en tu app. Luego puedes usar los duplicados para reemplazar los paywalls originales en algunos o todos los placements según sea necesario. Esto crea una copia del paywall con todos sus detalles, como el nombre, los productos y las promociones. Al nuevo paywall se le añadirá "Copy" al nombre para que puedas distinguirlo fácilmente del original. Para duplicar un paywall en el Adapty Dashboard: 1. Ve a [**Flows & Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty y abre la pestaña **Custom Paywalls**. 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 en algún placement. Si eliges **Create and replace original**, los nuevos paywalls pasarán inmediatamente a estado **Live**. También puedes crearlos como nuevos paywalls en estado **Draft** y añadirlos a los placements más adelante. --- # File: archive-paywalls --- --- title: "Archivar un paywall personalizado" description: "Aprende cómo archivar paywalls obsoletos en Adapty sin perder datos." --- A medida que te vas adentrando en Adapty y ajustando 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, con estado `Inactive`, pueden saturar tu espacio de trabajo y dificultar que encuentres los que más importan. Para abordar esto, Adapty ofrece la opción de archivar estos paywalls innecesarios. El archivado garantiza que se almacenen de forma segura sin eliminación permanente, listos para consultarse cuando sea necesario en el futuro. Además, los paywalls archivados se pueden filtrar en la vista predeterminada, lo que despeja tu espacio de trabajo y simplifica la interfaz. En esta guía, te explicamos cómo archivar paywalls en Adapty de forma eficiente para que tengas mayor control sobre el proceso de gestión de 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 quítalo de todos los placements. :::note No puedes archivar un paywall si está en uso en una prueba A/B que no está archivada. Así, el usuario puede ver métricas detalladas de una prueba A/B completada, y el paywall vinculado forma parte de esos datos. ::: **Para archivar un paywall:** 1. Ve a [**Flows & Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty y abre la pestaña **Custom Paywalls**. 2. Haz clic en el botón de **3 puntos** 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. 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 personalizado desde el archivo" description: "Restaura paywalls en Adapty para garantizar servicios de suscripción ininterrumpidos para los usuarios." --- Tener la posibilidad de archivar paywalls es una función muy útil para agilizar 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 quedar fuera de la vista predeterminada. Para verlos, selecciona **Archived** en el filtro **State**. **Para devolver un paywall desde el archivo** 1. Ve a [**Flows & Paywalls**](https://app.adapty.io/paywalls) en el menú principal de Adapty y abre la pestaña **Custom Paywalls**. 2. Asegúrate de que los paywalls archivados aparecen 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 **3-dot** 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: product --- --- title: "Productos" description: "Explora la configuración de productos de Adapty para configurar y optimizar las compras in-app y suscripciones." --- Un producto es cualquier artículo o contenido disponible para comprar dentro de una aplicación móvil. Puede incluir diversos bienes digitales como objetos virtuales, suscripciones, funciones adicionales o mejoras de contenido que los usuarios pueden adquirir para enriquecer su experiencia en la app. Por ejemplo, en una app de juegos, los productos pueden ser moneda del juego, potenciadores o paquetes de expansión. En una app de productividad, pueden ser funciones premium o acceso a contenido exclusivo. Estos productos se gestionan y venden a través del sistema de compras in-app que proporciona la plataforma (por ejemplo, Apple App Store o Google Play Store). En Adapty puedes combinar productos similares de App Store y Play Store en un único producto interno. Esto te permite usar un solo producto de Adapty en todas las plataformas, en lugar de trabajar con los productos de cada proveedor por separado. Los dos registros no se sincronizan campo a campo. El estado de revisión de un producto llega a Adapty desde el store, pero [un cambio de precio no](store-sync#product-and-price-discrepancies). :::note Lista de verificación para mostrar productos correctamente en tu app móvil 1. [Crea productos en el Adapty Dashboard](create-product). 2. [Crea un paywall en el Adapty Dashboard y añade productos](create-paywall) 3. Muestra los paywalls usando los placements a los que pertenecen en tu app móvil: - [iOS](ios-quickstart-paywalls) - [Android](android-quickstart-paywalls) - [React Native](react-native-quickstart-paywalls) - [Flutter](flutter-quickstart-paywalls) - [Unity](unity-quickstart-paywalls) ::: Una vez que creas productos en el Adapty Dashboard, son visibles en la sección **[Products](https://app.adapty.io/products)**. <img src="/assets/shared/img/poducts-list.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # 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 en que creas productos en Adapty depende de si ya los tienes en los stores: - **[Si los productos no existen todavía en App Store y/o Google Play, créalos en Adapty y publícalos en los stores directamente](#create-product-and-push-to-store)**. - **[Si los productos ya existen en App Store y/o Google Play, créalos en Adapty y conecta los productos existentes de los stores.](#create-product-and-connect-existing-store-products)** Para que los usuarios de App Store paguen una suscripción anual en 12 cuotas mensuales, crea primero un producto **Annual**. Luego ábrelo para editarlo y [añade un plan de compromiso de 12 meses](set-up-12-month-commitment#2-adapty-configuration). El precio que introduzcas aquí no puede modificarse tras la creación, y un cambio de precio en la consola del store [no se refleja en Adapty](store-sync#product-and-price-discrepancies). :::tip También puedes crear productos mediante programación usando el [CLI para desarrolladores](developer-cli-products#adapty-products-create). ::: ## Crear un producto y publicarlo en el store \{#create-product-and-push-to-store\} :::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) Si configuraste la integración con App Store hace 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). ::: 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 todos los tipos 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 qué funciones 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 de la lista. No puedes cambiarla una vez creado el producto. - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**: la duración de la suscripción. Adapty no envía [planes de compromiso de 12 meses](app-store-12-month-commitment) a App Store Connect. Para ofrecer uno, crea la suscripción en App Store Connect y [conéctala](#create-product-and-connect-existing-store-products). - **Lifetime**: usa el período de por vida para los productos que desbloquean las funciones premium de la app de forma permanente. - **Non-Subscriptions**: para productos de compra única sin duración. Una compra no-suscripción no otorga un nivel de acceso — aparece en el array `nonSubscriptions` del perfil del usuario, y tu app decide qué desbloquear. - **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. Una compra consumible tampoco otorga un nivel de acceso. Para otorgar un nivel de acceso desde una compra única, usa **Lifetime**. - **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 un producto y conectar productos existentes del store \{#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 todavía no tienes ningún producto creado**, considera seguir la guía [Push to stores](#create-product-and-push-to-store) para crearlos tanto en Adapty como en los stores al mismo tiempo. ::: 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 todos los tipos de productos: suscripciones, no consumibles \(incluido el 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. El nombre es principalmente para tu referencia, así que elige el que te resulte más cómodo para utilizar 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 de la lista. No puedes cambiarla después de crear el producto. - **Semanal/Mensual/2 meses/3 meses/6 meses/Anual**: La duración de la suscripción. Para añadir un [plan de compromiso de 12 meses](set-up-12-month-commitment), selecciona **Annual** y configura los plazos una vez creado el producto. - **Lifetime**: Usa el período de por vida para los productos que desbloquean las funciones premium de la app para siempre. - **Non-Subscriptions**: Para productos de compra única sin duración. Una compra de no-suscripción no otorga un nivel de acceso — aparece en el array `nonSubscriptions` del perfil de usuario, y tu app decide qué desbloquear. - **Consumables**: Los artículos consumibles se pueden comprar varias veces y se pueden agotar durante la vida de la aplicación. Ejemplos son la moneda del juego y los extras. Una compra consumible tampoco otorga un nivel de acceso. Para otorgar un nivel de acceso desde una compra única, usa **Lifetime**. - **Price (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 diferentes 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. :::warning Cada ID de producto de la store debe coincidir exactamente con la store. Si no coincide, no se generará ningún error: la compra se procesará, pero el cliente no recibirá ningún nivel de acceso. ::: 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 de tu producto, el nivel de acceso, los precios regionales y los IDs del store conectados, además de consultar el registro de auditoría para hacer seguimiento de los cambios de precio. La duración de la suscripción no se puede editar una vez creado el producto, por lo que tendrás que crear un producto nuevo para cambiarla. :::warning Aunque puedes editar cualquier producto, es fundamental asegurarte de que los cambios en productos que ya están en paywalls activos no generen discrepancias en tu analítica. **No se recomienda editar el nivel de acceso, el App Store Product ID ni el Play Store Product ID**, ya que puede afectar la claridad de la analítica. Edítalos solo 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 en consecuencia. ::: ## 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 realizas en App Store Connect o Google Play Console no se sincronizan con Adapty. El precio que se muestra en Adapty se establece al crear el producto y no se actualizará si cambias el precio en el store. Esto no afecta a tus análisis de ingresos, ya que los [informes de ingresos usan el importe que el cliente pagó realmente](store-sync#product-and-price-discrepancies). ::: :::note Si cambias el nivel de acceso, el cambio solo se aplica a las nuevas suscripciones. Para los suscriptores actuales, el nivel de acceso vigente se mantiene sin cambios y se actualiza automáticamente en la siguiente 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 */ }} /> ## Añadir un plan de compromiso de 12 meses \{#add-a-12-month-commitment-plan\} Para que los usuarios de App Store puedan pagar una suscripción anual en 12 cuotas mensuales, [activa un plan de compromiso de 12 meses](set-up-12-month-commitment#2-adapty-configuration) en la pestaña **App Store** del producto. Esta opción requiere un producto **Annual** con un ID de producto de App Store y un **Price (USD)**. Configura el plan primero en App Store Connect: Adapty no lo crea allí. ## 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: app-store-12-month-commitment --- --- title: "Planes de compromiso de 12 meses" description: "Ofrece una suscripción anual de App Store como 12 pagos mensuales con la suscripción mensual de Apple con compromiso de 12 meses, y ve cómo Adapty la muestra en análisis y eventos." --- :::important Para ofrecer un plan de compromiso, necesitas una suscripción anual de App Store y un [flow](adapty-flow-builder) o un [paywall personalizado](paywalls). Los flows requieren el SDK de Adapty v4.3.1 o posterior en iOS y v4.3 o posterior en Android. Los paywalls personalizados requieren el SDK de Adapty v4.3 o posterior. ::: Un plan de compromiso no es un producto independiente, sino una configuración opcional para un producto anual tanto en App Store Connect como en Adapty. Se configura como complemento del *plan de pago anticipado* estándar. Ofrécelo a los usuarios que dudan ante el precio del pago anticipado. En lugar de pagar por adelantado un producto anual de la App Store, los usuarios fuera de Estados Unidos pueden comprarlo con un pequeño recargo: en cuotas mensuales con un compromiso de 12 meses. Esta opción requiere [configuración adicional](set-up-12-month-commitment) tanto en App Store Connect como en Adapty. Apple denomina este plan [suscripción mensual con compromiso de 12 meses](https://developer.apple.com/documentation/storekit/supporting-monthly-subscriptions-with-a-12-month-commitment). ## Cómo funciona un plan de compromiso \{#how-a-commitment-plan-works\} Con un plan por adelantado, Apple cobra el precio anual una vez al año. Con un plan de compromiso, Apple divide el precio en 12 cuotas. Tú defines el precio de cada cuota, que puede incluir un recargo. Apple aplica las siguientes [reglas de ciclo de vida](https://developer.apple.com/documentation/storekit/managing-lifecycle-of-monthly-subscriptions-with-a-12-month-commitment-): - **Plan de pago**: Cada pago mensual es una transacción independiente. - **Acceso**: La compra inicial *no* desbloquea un año de acceso. En cambio, cada pago desbloquea un mes de acceso. - **Renovación**: Cuando termina el mes 12, la suscripción se renueva con un nuevo compromiso de 12 meses, a menos que se cancele. - **Cancelación**: Cancelar solo desactiva la renovación tras el 12.º pago. El suscriptor sigue pagando los pagos restantes y mantiene el acceso hasta que finaliza el compromiso. - **Pagos fallidos**: Apple reintenta un pago fallido durante un máximo de 90 días. Los períodos de gracia de facturación no se aplican, por lo que el suscriptor no tiene acceso mientras Apple reintenta. Si Apple recupera el pago, las fechas de los pagos posteriores y la fecha de fin del compromiso se desplazan para seguir a la fecha de recuperación. Si Apple no recupera el pago en 90 días, el compromiso finaliza y el acceso no se restaura. - **Mejoras y rebajas de plan**: Una mejora a una suscripción de nivel superior dentro del mismo grupo de suscripciones tiene efecto inmediato y pone fin al compromiso, independientemente de la duración de la nueva suscripción. El suscriptor puede recibir un reembolso prorrateado por el resto del mes en curso. Un cambio a una suscripción de nivel inferior tiene efecto cuando finaliza el compromiso. El momento de un cambio lateral depende de la duración y el plan de facturación de ambas suscripciones. - **Reembolsos**: Un reembolso por un mes pasado no afecta al compromiso. Un reembolso por el mes en curso pone fin al compromiso. - **Cambios de precio**: Los cambios de precio siguen las [reglas de Apple para los aumentos de precio](https://developer.apple.com/documentation/storekit/managing-price-increases-for-auto-renewable-subscriptions) para las suscripciones anuales, aunque el suscriptor paga mensualmente. El plan por adelantado y el plan de compromiso tienen precios independientes: cambiar uno no cambia el otro. La primera vez que un suscriptor contrata un plan con compromiso, Apple muestra el número de pagos mensuales, una vez por cuenta de Apple. La configuración de la suscripción y la pantalla de cancelación muestran cuántos pagos quedan. Tu app no necesita mostrar estos avisos. ### Ventajas y desventajas \{#pros-and-cons\} #### Ventajas \{#pros\} - **En comparación con el pago único inicial**: Cada pago individual es una fracción del precio inicial. Sin embargo, el recargo anual permitido de hasta un 50 % sigue aumentando los ingresos por cliente. - **En comparación con una suscripción mensual**: El suscriptor se compromete con los 12 pagos. Cancelar solo desactiva la renovación tras el 12.º pago. En [4 países](https://support.apple.com/en-us/108098), los suscriptores también pueden cancelar con efecto inmediato a través del soporte de Apple. #### Desventajas \{#cons\} - **Ingresos diferidos**: Los ingresos llegan en 12 pagos en lugar de uno. - **Alcance limitado**: La compra requiere iOS 26.4 o posterior, y no está disponible en Estados Unidos ni en watchOS. - **Comportamiento no estándar ante fallos de pago**: Los períodos de gracia no aplican, por lo que el suscriptor pierde el acceso en cuanto falla el pago. - **El valor total no está garantizado**: El compromiso dura únicamente mientras funcione el método de pago del suscriptor. Si un pago falla y Apple no puede recuperarlo en 90 días, el compromiso termina y la suscripción expira, por lo que no recibirás los pagos restantes. ## Analytics \{#analytics\} Adapty Analytics trata los planes de compromiso de forma diferente: - **ID de producto**: Los análisis y exportaciones de datos muestran el plan de compromiso con su propio ID de producto de la store, `<product_id>:monthly`. Sigue formando parte del mismo producto de Adapty que el plan anticipado. - **Ingresos**: A pesar de la duración anual del compromiso, cada pago mensual se registra de forma independiente. - **Duración**: Cuando filtras o agrupas por **Duration**, el plan de compromiso cuenta como una suscripción anual. Las cohortes, el LTV y las predicciones también lo tratan como anual. - **[Cancelación de renovación de suscripción](cancelled-subscriptions)**: La cancelación se atribuye al día en que el suscriptor cancela. El suscriptor sigue pagando hasta que finaliza el compromiso. - **[Retención](analytics-retention)**: Cada pago mensual cuenta como una renovación, por lo que la retención del plan de compromiso se mantiene cerca del 100% hasta que finaliza el compromiso. - **[Pruebas A/B](ab-tests)**: Los ingresos del plan de compromiso se acumulan a lo largo de 12 meses, por lo que los ingresos tempranos favorecen al plan anticipado. Compara ambos planes por conversión en su lugar. Si omites la [configuración de Adapty](set-up-12-month-commitment#2-adapty-configuration), las compras del plan de compromiso seguirán apareciendo en analytics. Cuando Adapty recibe la primera compra de un plan de compromiso, añade el plan de compromiso a tu producto anual. El producto debe estar previamente conectado a la suscripción de App Store. ## Eventos de suscripción \{#subscription-events\} Los siguientes [eventos de suscripción](webhook-event-types-and-fields) funcionan de forma diferente en los planes de compromiso: | Qué ocurre | Evento | |---|---| | Cada pago mensual después del primero | `subscription_renewed`, aunque el propio compromiso solo se renueva tras el 12.º pago | | El suscriptor cancela | `subscription_renewal_cancelled`, seguido de `subscription_renewed` por cada pago restante | | El suscriptor cambia entre el plan anticipado y el plan de compromiso | `subscription_refunded` por el plan antiguo, luego `subscription_renewed` por el nuevo. El reembolso tiene ingresos cero cuando el cambio se aplica al final de un período de facturación, y un importe prorrateado en caso contrario | | Falla un pago mensual | `billing_issue_detected`. Los períodos de gracia de facturación no se aplican a los planes de compromiso | En los eventos de webhook e integración, el plan de compromiso lleva el ID de producto `<product_id>:monthly`, como en los análisis. ## Solución de problemas \{#troubleshooting\} - **Un suscriptor perdió el acceso tras un pago fallido**: Los períodos de gracia de facturación no aplican a los planes de compromiso. El acceso se recupera cuando Apple cobra el pago. - **Adapty muestra menos ingresos para el producto anual que App Store Connect**: Adapty registra el plan de compromiso como un producto independiente, `<product_id>:monthly`. Para comparar los ingresos con App Store Connect, [filtra un gráfico de Adapty](discrepancies-and-troubleshooting#12-month-commitment-plans) por el producto anual y su plan de compromiso. - **Tu backend no puede saber si un suscriptor de plan de compromiso canceló**: En el perfil que devuelve la [API del lado del servidor](api-adapty/operations/getProfile), el campo `renewal_cancelled_at` de la suscripción permanece en `null`, porque los pagos mensuales continúan. Para saber si se iniciará un nuevo compromiso tras este, consulta `commitment.will_renew`. Su valor es `false` cuando el suscriptor cancela. --- # File: set-up-12-month-commitment --- --- title: "Configurar un plan de compromiso de 12 meses" description: "Configura la suscripción mensual de Apple con un compromiso de 12 meses en App Store Connect y Adapty, añádela a un flow o a un paywall personalizado, y pruébala en el sandbox." --- :::important Para ofrecer un plan de compromiso, necesitas una suscripción anual de App Store y un [flow](adapty-flow-builder) o un [paywall personalizado](paywalls). Los flows requieren el SDK de Adapty v4.3.1 o posterior en iOS y v4.3 o posterior en Android. Los paywalls personalizados requieren el SDK de Adapty v4.3 o posterior. ::: Para que los suscriptores se comprometan con un año completo y paguen mensualmente, añade un [plan de compromiso de 12 meses](app-store-12-month-commitment) a tu producto anual en App Store Connect y en Adapty. ## Antes de empezar \{#before-you-begin\} Antes de implementar los planes de compromiso, asegúrate de que: - **Dirige al mercado correcto**: Los planes de compromiso no están disponibles en Estados Unidos. En Alemania, Israel, Sudáfrica y Türkiye, los suscriptores pueden cancelar con efecto inmediato a través del Soporte de Apple. - **Dirige a la versión de OS correcta**: Los suscriptores necesitan iOS 26.4 o posterior, o la misma versión de iPadOS, macOS, tvOS o visionOS. Compila tu app con Xcode 26.5 o posterior. - **Dirige al rango de precio correcto**: La suma de los 12 pagos mensuales debe estar entre 1x y 1,5x el valor de la suscripción anual por adelantado. - **Usa una versión de SDK de Adapty compatible**: Los flows necesitan Adapty SDK v4.3.1 o posterior en iOS y v4.3 o posterior en Android. Los paywalls personalizados necesitan Adapty SDK v4.3 o posterior: en versiones de la app con un SDK anterior, los usuarios solo pueden comprar el plan por adelantado. - **Actualiza el código que lee los IDs de producto**: Los webhooks, integraciones, exportaciones de datos y la API del lado del servidor reportan las compras de planes de compromiso como `<product_id>:monthly`. Si tu código procesa IDs de producto, actualiza la lógica antes de la primera compra de un plan de compromiso. ## 1. Configuración de App Store Connect \{#1-app-store-connect-configuration\} 1. Abre una suscripción anual en App Store Connect, o [crea una nueva suscripción](app-store-products) con duración de **1 año**. 2. En la sección **Availability**, añade los mismos países en **1 Year Upfront** y en **Monthly with 12-Month Commitment**. Apple solo ofrece facturación por compromiso donde la facturación por adelantado está disponible. Lo inverso no es obligatorio: puedes ofrecer el plan por adelantado en países sin el plan por compromiso. 3. En la sección **Subscription Prices**, establece el precio por adelantado para tu territorio base si la suscripción aún no tiene uno. Apple requiere el precio por adelantado aunque solo muestres el plan por compromiso a los usuarios. 4. Establece el precio mensual para tu territorio base. El precio mensual debe ser entre 1/12 y 1/8 del precio por adelantado. Por ejemplo, con un precio por adelantado de €59,99, el precio mensual puede ser de €5,00 a €7,49. App Store Connect calcula los precios para el resto de países a partir de los precios de tu territorio base. Para instrucciones completas, consulta la guía de Apple sobre cómo [ofrecer suscripciones mensuales con un compromiso de 12 meses](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-availability-for-an-auto-renewable-subscription#offer-monthly-subscriptions-with-12-month-commitment). ## 2. Configuración en Adapty \{#2-adapty-configuration\} Para mostrar el plan de compromiso en flows o paywalls personalizados, actívalo para el producto anual en Adapty. Si no usas ninguno de los dos, puedes saltarte este paso. 1. Abre el producto anual. Si aún no existe, [créalo y conéctalo](create-product#create-product-and-connect-existing-store-products) a la suscripción que configuraste en App Store Connect. No crees un nuevo producto desde Adapty: eso generaría una suscripción independiente sin el plan de compromiso. 2. Abre la pestaña **App Store** y selecciona **Offer monthly payments with a 12-month commitment**. 3. En el pop-up, en la lista **Monthly price, USD**, selecciona el precio a plazos que coincida con App Store Connect. La lista solo contiene puntos de precio de App Store dentro del rango permitido. Este precio es un valor de referencia: Adapty no lo envía a App Store Connect. ## 3. Configura las ofertas \{#3-set-up-offers\} En App Store Connect, cada plan de facturación tiene sus propias ofertas introductorias, ofertas promocionales, ofertas de recuperación y códigos de oferta. Una oferta para el plan anticipado no se aplica al plan de compromiso. - **Pruebas gratuitas y otras ofertas introductorias**: En App Store Connect, configura la oferta para el plan de compromiso. Adapty aplica las [ofertas introductorias](app-store-offers#introductory-offers) automáticamente. Cuando finaliza una oferta introductoria, comienza el compromiso de 12 meses del suscriptor. - **Ofertas promocionales y de recuperación**: Configura las [ofertas promocionales](app-store-offers#promotional-offers) o las [ofertas de recuperación](app-store-offers#win-back-offers) para el plan de compromiso en App Store Connect. Luego [añade las ofertas en Adapty](create-offer) y establece su **App Store plan** en **Monthly with 12-month commitment**. Un flow o un producto de paywall solo muestra las ofertas de su propio plan de facturación. Si no usas flows ni paywalls personalizados, no necesitas añadir las ofertas en Adapty. - **Códigos de oferta**: Configura los códigos de oferta para el plan de compromiso en App Store Connect. ## 4. Añadir a un flow o paywall personalizado \{#4-add-to-a-flow-or-custom-paywall\} :::warning Apple exige que muestres tanto el precio a plazos como el total anual antes de la compra. ::: ### Flow 1. En el flow builder, abre el panel **Products** y [añade el producto anual](paywall-product-block#sell-a-yearly-product-with-monthly-payments) etiquetado como **Monthly** a la pantalla. Para vender también el plan de pago único en la misma pantalla, añade también el producto etiquetado como **Upfront**. - Para ofrecer una oferta anticipada a los usuarios que compran el plan de pago único en lugar del plan de cuotas —en una versión anterior del SDK o donde el plan de compromiso no aplique—, selecciónala en **Fallback offer**. 2. Asigna el producto **Monthly** a una [tarjeta de producto](paywall-product-block#configure-a-product-card) o a una [acción **Purchase**](paywall-product-block#trigger-a-purchase). 3. Para mostrar el precio por cuota, inserta las [variables](onboarding-variables#12-month-commitment-plan) `prod_billing_price` y `prod_billing_period`. Para mostrar el total anual, inserta `prod_price`. Las variables de cuotas requieren Adapty SDK v4.3.1 o posterior. 4. Para previsualizar la pantalla para usuarios que no pueden comprar el plan de compromiso, en el panel **Products**, abre el menú del producto **Monthly** y selecciona **Offer** > **Monthly plan unavailable**. ### Paywall personalizado \{#custom-paywall\} 1. En el paywall personalizado, [añade el producto anual](add-product-to-paywall) y establece su **Billing plan** en **Monthly**. Para vender también el plan por adelantado en el mismo paywall, añade de nuevo el producto anual con el plan de facturación **Upfront**. - Para ofrecer una oferta por adelantado a los usuarios que compren el plan por adelantado en lugar del plan de compromiso — en una versión anterior del SDK o donde el plan de compromiso no aplique — selecciónala en **Fallback offer**. 2. En tu app, muestra el precio de `product.subscriptionPricingTerms`. Para los usuarios que pueden comprar el plan de compromiso, el SDK de Adapty devuelve sus precios. Para todos los demás, el SDK devuelve los precios del plan por adelantado: - Para saber qué plan devolvió el SDK, comprueba `product.subscriptionPricingTerms?.billingPlan`. El valor es `.monthly` para el plan de compromiso y `.upFront` para el plan por adelantado. - Para mostrar el precio por cuota, usa `subscriptionPricingTerms.localizedBillingPrice` y `subscriptionPricingTerms.billingPeriod`. - Para mostrar el total anual, usa `subscriptionPricingTerms.commitmentInfo.localizedPrice` y `subscriptionPricingTerms.commitmentInfo.period`. En el plan de compromiso, `product.localizedPrice` y `product.subscriptionPeriod` devuelven los mismos valores anuales. Los usuarios no pueden comprar el plan de compromiso en los siguientes casos: - **Versión de SO o país no compatible**: el dispositivo Apple del usuario tiene una versión del sistema anterior a la 26.4, o el plan no está disponible en su país de App Store. El SDK de Adapty devuelve el precio del plan inicial en su lugar. - **Suscripción inicial existente**: StoreKit informa de que el usuario ya está suscrito. Para cambiar de plan, los suscriptores usan la configuración de suscripciones de Apple. - **[Compra in-app promocionada](making-purchases#in-app-purchases-from-the-app-store)**: el usuario selecciona el producto en el App Store y, a continuación, el SDK de Adapty inicia la compra en tu app. Para estas compras, el SDK solo admite el plan inicial. Para dejar de ofrecer el plan de compromiso, en tus flows, reemplaza o elimina el producto **Monthly**. En tus paywalls personalizados, establece el **Billing plan** del producto anual en **Upfront**, o elimina la fila **Monthly**. No puedes desmarcar la casilla **Offer monthly payments with a 12-month commitment** en Adapty. Los suscriptores actuales siguen renovando con un nuevo compromiso cada año, a menos que hagas clic en **Remove Monthly Billing** para la suscripción anual en App Store Connect. Los suscriptores que ya se comprometieron siguen pagando hasta que finalice su período de compromiso. ## 5. Prueba el plan en un sandbox \{#5-test-the-plan-in-a-sandbox\} Sigue la [guía de pruebas en sandbox](test-purchases-in-sandbox) con dos cuentas sandbox: - **Una cuenta de un país donde ofreces el plan de compromiso**: El paywall debe mostrar el plan de compromiso con el precio mensual junto al precio total. Tras la compra, el perfil del usuario sandbox debe tener el nivel de acceso del producto anual. - **Una cuenta de EE. UU.**: El paywall solo debe mostrar el plan por adelantado. Si tu código procesa IDs de producto procedentes de webhooks o de la API server-side, verifica que recibe las compras de planes con compromiso como `<product_id>:monthly`. Para probar la lógica de compra en local, utiliza [StoreKit Testing en Xcode](local-sk-files) 26.5 o posterior. Las compras en StoreKit Testing no llegan a Adapty. ## Solución de problemas \{#troubleshooting\} - **Falta la casilla para pagos mensuales**: La casilla **Offer monthly payments with a 12-month commitment** solo aparece en un producto **Annual** con un ID de producto de App Store. Permanece inactiva hasta que el producto tenga un **Price (USD)**. - **Los usuarios no ven una prueba gratuita u otra oferta para el plan de compromiso**: En App Store Connect, cada oferta pertenece a un plan de facturación. [Configura la oferta](#3-set-up-offers) para el plan de compromiso. Para una oferta promocional o de recuperación, configura también el **App Store plan** de la oferta en Adapty como **Monthly with 12-month commitment**. - **Todos los usuarios ven el precio por adelantado**: Verifica ambos puntos: - La app está compilada con Xcode 26.5 o posterior. Con una versión anterior de Xcode, el SDK de Adapty siempre devuelve el precio del plan por adelantado. - En el flow, la pantalla usa el producto anual etiquetado como **Monthly**. En el paywall personalizado, el **Billing plan** del producto anual es **Monthly**. - **Algunos usuarios ven el precio por adelantado**: Esos usuarios [no pueden comprar el plan de compromiso](#4-add-to-a-flow-or-custom-paywall). - **El paywall muestra el total anual en lugar del precio de la cuota**: - En un flow, `prod_price` devuelve el total anual. Para mostrar el precio de la cuota, usa `prod_billing_price`. En el SDK de Adapty v4.3.0, `prod_billing_price` también muestra el total anual: actualiza el SDK a v4.3.1 o posterior. - En un paywall personalizado, `product.localizedPrice` devuelve el total anual. Para mostrar el precio de la cuota, usa `subscriptionPricingTerms.localizedBillingPrice`. --- # 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 un producto cuando no lo usa nada: los paywalls, flows, grupos de compra y monedas virtuales bloquean la eliminación. :::note Un paywall archivado libera sus productos, pero un **flow** archivado no: sus paywalls siguen contando como uso, y el error de eliminación los nombra. Si un producto parece sin usar pero no se puede eliminar, revisa tus flows archivados: en la página **Flows**, abre el filtro **All states** y selecciona **Archived**. ::: 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 cómo añadir y gestionar productos en los paywalls de Adapty." --- Para que un producto sea visible y seleccionable dentro de un [paywall](paywalls) para los usuarios de tu app, sigue estos pasos: 1. Mientras [configuras un paywall](create-paywall), haz clic en **Add product** bajo el título **Products**. 2. En la lista desplegable que se abre, selecciona los productos que se mostrarán a tus clientes. La lista contiene solo los productos creados previamente. El orden de los productos se respeta en el lado del SDK, por lo que es importante tener en cuenta el orden deseado al configurar el paywall. Además, puedes seleccionar una oferta para un producto. En Google Play, la oferta que selecciones aquí es la que se aplica en la compra: si no seleccionas ninguna oferta, se cobra a los usuarios el precio base completo del plan sin período de prueba ni precio introductorio, aunque la oferta esté activa en Google Play Console. <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, una vez creado, no se recomienda editar, añadir ni eliminar productos del paywall, ya que esto puede afectar a las métricas del paywall. --- # File: store-sync --- --- title: "Cómo Adapty sincroniza datos con los stores" description: "Qué datos de productos, precios y ofertas se mueven entre Adapty y el App Store o Google Play, en qué dirección y cuándo." --- Adapty intercambia datos de productos con App Store y Google Play, pero el intercambio no es constante ni completo. La mayoría de los valores solo se actualizan cuando tú actúas: cuando publicas un producto en los stores o subes nuevos precios. Algunos nunca se sincronizan, por lo que [Adapty y el store pueden mostrar precios distintos para el mismo producto](#product-and-price-discrepancies). ## Qué lee Adapty de los stores \{#what-adapty-reads-from-the-stores\} Adapty lee listas de productos, planes base, precios y transacciones del store. Para cambiar un producto o un precio, hazlo en la consola del store. Para ver los precios por país que tiene almacenados, descarga el CSV de precios desde [Editar producto](edit-product#set-country-specific-prices). | Lo que Adapty lee | Cómo llega (App Store) | Cómo llega (Google Play) | |---|---|---| | Lista de productos y estado de revisión | A través de la [clave de API de App Store Connect](app-store-connection-configuration#step-6-add-app-store-connect-api-key) | A través de la [cuenta de servicio](google-play-store-connection-configuration) | | Planes base | No aplica | Parte del producto de suscripción | | Precios por país | Al vincular o editar un producto de la store | Al vincular o editar un producto de la store | | Transacciones, renovaciones y reembolsos | A través de las [notificaciones del servidor de App Store](enable-app-store-server-notifications) | A través de las [notificaciones en tiempo real para desarrolladores](enable-real-time-developer-notifications-rtdn) | | El precio que pagó el cliente | Con cada transacción | Con cada transacción | ## Qué escribe Adapty en los stores \{#what-adapty-writes-to-the-stores\} Los productos, precios y períodos de prueba introductorios se configuran en Adapty y llegan al store cuando realizas una acción, nunca de forma automática. | Qué escribe Adapty | Cuándo llega al App Store | Cuándo llega a Google Play | |---|---|---| | Nuevos productos | Cuando [envías un producto a los stores](create-product#create-product-and-push-to-store) | Cuando envías un producto a los stores | | Precios por país | Cuando [subes el CSV de precios](edit-product#set-country-specific-prices) | Cuando subes el CSV de precios | | Período de prueba gratuito introductorio | Cuando envías un nuevo producto | Nunca — créalo en [Google Play Console](google-play-offers) | Adapty compara tu CSV con los precios actuales del store y actualiza solo los que sean diferentes, por lo que puedes subir el archivo completo sin problema. Los suscriptores existentes conservan el precio con el que se dieron de alta, a menos que selecciones **Apply to existing subscribers**. ## Lo que configuras solo en Adapty El store nunca cambia ninguna de estas configuraciones. | Elemento | Por qué se mantiene en Adapty | |---|---| | El precio de un producto del store que hayas conectado | Es un marcador de posición, fijo en el momento de creación. Te ayuda a distinguir los productos entre sí, pero nunca llega al store. Los análisis, las integraciones y el SDK siempre usan los precios reales del store. | | [Niveles de acceso](access-level) | Adapty concede acceso a tu contenido de pago. Los stores no tienen un concepto equivalente. | | IDs de ofertas promocionales y de recuperación | Adapty necesita el ID para firmar la compra. La oferta en sí la creas en la consola del store. | ## Qué ocurre en el dispositivo \{#what-happens-on-the-device\} El precio que un cliente ve en un paywall proviene de StoreKit o Google Play Billing cuando se abre el paywall, no de Adapty. Un precio que cambias en la consola del store llega a tus clientes en cuanto el store lo publica. No necesitas publicar una nueva versión de la app ni cambiar nada en Adapty. En el otro sentido, el SDK de Adapty envía cada compra a Adapty con el precio, la divisa y el país asociados. Adapty usa esos valores para registrar los ingresos en la divisa propia del cliente. ## Stripe, Paddle y stores personalizadas \{#stripe-paddle-and-custom-stores\} Las integraciones de Stripe y Paddle transfieren transacciones, no tu catálogo de productos. Las compras llegan a Adapty a través del webhook del proveedor o, en el caso de las [stores personalizadas](custom-store), desde tu propio backend. Debes añadir manualmente cada ID de producto y de precio. Un ID que no exista en Adapty no coincidirá con nada, por lo que esas transacciones y sus ingresos nunca aparecerán. En Stripe y Paddle, lo mismo ocurre cuando el producto existe pero no tiene **Subscription duration** configurada. Ninguno de estos casos genera ningún aviso, así que añade un ID y una duración para cada producto que vendas. ## Discrepancias entre productos y precios \{#product-and-price-discrepancies\} Algunas diferencias entre Adapty y la consola del store son intencionadas y no afectan en nada lo que pagan tus clientes. Otras implican que hay ingresos que no se registran. | Lo que ves | Causas probables | Qué hacer | |---|---|---| | El precio en Adapty difiere del store | La pantalla **Products** muestra el precio que introdujiste al crear el producto. Si lo enviaste a los stores, Adapty lo usó para establecer el precio inicial allí. No puedes cambiarlo después, así que una vez que los stores tienen el precio, ambos se desincronizarán. Los precios que pagan los clientes ahora son [precios por país](edit-product#set-country-specific-prices). | Nada. A tus clientes se les cobra el precio del store, y los informes de ingresos usan el importe realmente pagado. Para cambiar lo que pagan, edita el precio en la consola de cada store o [sube un CSV de precios](edit-product#set-country-specific-prices) para cambiar ambos stores a la vez. | | Tu producto no aparece en el desplegable de productos | Hay tres posibles razones: las credenciales del store que han dejado de funcionar devuelven una lista vacía; un producto creado en los últimos 10 minutos puede que no haya llegado aún a la copia en caché de la lista de App Store de Adapty; la **Subscription duration** seleccionada filtra la lista: **Lifetime**, **Non-Subscriptions** y **Consumables** muestran productos de compra única, y cualquier otro valor muestra suscripciones. | Establece la **Subscription duration** para que coincida con el tipo del producto en el store. Si creaste el producto hace unos minutos, inténtalo de nuevo en breve. De lo contrario, verifica tus [credenciales del store](#if-updates-stop-arriving). | | Una oferta introductoria no aparece en Adapty | Adapty crea una oferta introductoria solo cuando envías un nuevo producto al store, y solo muestra las ofertas que creó. Una oferta que hayas configurado tú mismo en la consola del store nunca aparecerá en la ventana **Edit product**. | Nada. Las ofertas introductorias se aplican automáticamente cuando el cliente cumple los requisitos, por lo que no es necesario añadirlas en Adapty. | | Un descuento no se aplicó en la compra | Es posible que el cliente no cumpliera los requisitos: la decisión la toma el store, no Adapty. También puede que una oferta promocional de App Store no se haya aplicado porque Adapty no pudo firmarla con tu [In-App Purchase Key](offers). | Confirma que la clave está subida y es válida, y revisa las condiciones de la oferta en el store. | | Las transacciones de Stripe dejaron de aparecer tras un cambio de precio | Cambiar un precio en Stripe crea un nuevo `price_id`, y Adapty relaciona los eventos con el par exacto `product_id` + `price_id`. Los eventos del nuevo precio no coinciden con nada, mientras que Stripe sigue notificando el webhook como entregado. | Añade el nuevo `price_id` al producto en Adapty; los ingresos no se registrarán hasta que lo hagas. Consulta [Stripe](stripe). | | Un cliente compró un producto pero no obtuvo ningún nivel de acceso | El ID del producto del store que compraron no está vinculado a ningún producto en Adapty. Adapty registra la compra contra una fila de producto que crea automáticamente, y esa fila no tiene ningún nivel de acceso asignado. | Crea el producto en Adapty con el ID exacto del producto del store y asígnale un [nivel de acceso](access-level). | | Las actualizaciones ya no llegan | Una de tus credenciales del store ha dejado de funcionar. Cada una impulsa una parte distinta del intercambio. | Identifica la parte que ha fallado y reemplaza solo esa credencial; consulta [Si las actualizaciones dejan de llegar](#if-updates-stop-arriving). | ## Si las actualizaciones dejan de llegar \{#if-updates-stop-arriving\} Adapty no marca las credenciales que han dejado de funcionar. Cada credencial es responsable de una parte del intercambio, así que el síntoma indica cuál hay que reemplazar. | Qué dejó de funcionar | Credencial a reemplazar | |---|---| | La lista de productos, los estados de revisión o la publicación de productos y precios en el store | App Store: la [clave de la App Store Connect API](app-store-connection-configuration#step-6-add-app-store-connect-api-key). Google Play: el [archivo de clave de cuenta de servicio](create-service-account-key-file). | | Nuevas transacciones, renovaciones y reembolsos | App Store: [notificaciones del servidor](enable-app-store-server-notifications). Google Play: [notificaciones de desarrollador en tiempo real](enable-real-time-developer-notifications-rtdn). | | Ofertas promocionales en el momento de la compra | App Store: la [clave de compra in-app](offers). | | Validación de compras para cada cliente | App Store: el [secreto compartido](app-store-connection-configuration). Google Play: el [archivo de clave de cuenta de servicio](google-play-store-connection-configuration). | Reemplazar una credencial en la consola de la store no la actualiza en Adapty. Sube la nueva en **App settings**. --- # File: virtual-currencies --- --- title: "Monedas virtuales" description: "Define monedas in-app en Adapty, vincúlalas a productos para otorgar créditos automáticamente y lleva un seguimiento del saldo de cada usuario." --- <CustomDocCardList ids={['virtual-currency-quickstart', 'create-virtual-currency', 'grant-initial-balance', '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 vez, vincúlala a los productos que deben otorgarla, y Adapty acredita a cada usuario automáticamente y mantiene su saldo. Tu app 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 suscripciones y define 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 del lado del servidor. Usa la misma API para [conceder un saldo inicial](grant-initial-balance) a los usuarios que aún no han comprado nada. 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 balance pertenece a un perfil. Un usuario anónimo conserva su balance solo en la instalación donde lo obtuvo, por lo que se pierde si reinstala la app o la abre en otro dispositivo. Identifica a los usuarios para que el balance esté disponible en todos los dispositivos y tras cada reinstalación. Consulta [Balances, perfiles y dispositivos](virtual-currency-balance#balances-profiles-and-devices). - **Solo en el lado del servidor**: Todavía no hay ningún método del SDK para leer o gastar un balance, por lo que tu app necesita un backend. Este lee los balances y otorga o gasta créditos a través de la API del lado del servidor. - **Hasta 20 divisas 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 nada cambia. Incluye un encabezado `Idempotency-Key` para reintentar solicitudes de forma segura. 4. Para ejecutar una promo de oferta de recuperación, otorga tokens a través del mismo endpoint con un `amount` positivo. Los créditos que otorgues de esta manera nunca caducan. Para dar a los usuarios un saldo antes de su primera compra, consulta [Otorgar un saldo inicial](grant-initial-balance). 5. Revisa cada cambio en el [perfil](virtual-currency-balance) del usuario o en el [historial de transacciones](api-adapty/operations/listVirtualCurrencyTransactions), para poder 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. Usa letras latinas, dígitos y guiones bajos, hasta 32 caracteres. Las letras minúsculas se convierten a mayúsculas automáticamente. - **Name**: El nombre que se mostrará, 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. El **Name** sí puedes editarlo después. ::: Cuando introduces una nueva moneda, el saldo inicial de cada perfil de usuario es 0. Para asignar un saldo a los usuarios antes de que realicen cualquier compra, consulta [Otorgar un saldo inicial](grant-initial-balance). ## 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. ## Cómo se gasta un saldo mixto \{#how-a-mixed-balance-is-spent\} El saldo de una divisa puede contener créditos de más de una fuente a la vez — por ejemplo, una concesión de suscripción con caducidad y una concesión de compra única sin caducidad. Cuando un gasto debita el saldo, Adapty descuenta primero los créditos con caducidad, empezando por la concesión más antigua, y solo llega a los créditos sin caducidad una vez que no quedan créditos con caducidad. Por eso, cuando una suscripción expira, solo se restablece la parte no gastada que provenía de esa suscripción — los créditos concedidos por una compra única permanecen en el saldo. ## 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: grant-initial-balance --- --- title: "Otorgar un saldo inicial" description: "Dale a los nuevos usuarios un saldo inicial de moneda virtual mediante la API del servidor, y rellena el saldo de usuarios existentes cuando lances una nueva moneda." --- :::link Artículo principal: [Monedas virtuales](virtual-currencies) ::: Cada perfil comienza con un saldo de 0 en cada moneda, y los [productos vinculados](create-virtual-currency#link-products) solo otorgan créditos en una compra o renovación. Por lo tanto, un usuario que no ha comprado nada nunca recibe créditos automáticamente. Aun así, puedes dar a los nuevos usuarios un saldo inicial: un bono de bienvenida, una asignación de prueba gratuita o un conjunto de vidas. Concédelo desde tu backend a través de la [API del servidor](getting-started-with-server-side-api), después de identificar al usuario. ## Conceder saldo a un nuevo usuario \{#grant-the-balance-for-a-new-user\} 1. **Identifica primero al usuario**: Un saldo pertenece a un perfil y nunca se transfiere a otro, así que concede los créditos solo después de que el perfil tenga un customer user ID. Identifica al usuario en tu app mediante el SDK (consulta [Identificar usuarios](ios-quickstart-identify)) o desde tu backend a través de la API server-side. Otorgar créditos a un perfil anónimo es el error más habitual en este punto. Los perfiles anónimos están vinculados a una instalación concreta de la app, por lo que el usuario pierde los créditos al reinstalarla o abrirla en otro dispositivo. Consulta [Saldos, perfiles y dispositivos](virtual-currency-balance#balances-profiles-and-devices) para una explicación completa. 2. **Otorga los créditos**: Llama a [Create virtual currency transaction](api-adapty/operations/createVirtualCurrencyTransaction) con un `amount` positivo. Este ejemplo otorga 500 tokens: ```bash title="Grant an initial balance" 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 "Idempotency-Key: 6f2c0b34-9c3a-4f5e-8a1d-2b7e5c9d0a11" \ -H "Content-Type: application/json" \ -d '{ "items": [{"currency_code": "TOKENS", "amount": 500}], "metadata": {"reason": "initial_balance"} }' ``` La respuesta devuelve el nuevo saldo: ```json title="Response" { "transaction_id": "3a9f8e21-5d4c-4b7a-9e01-8c6d2f4b1a37", "balances": [ { "code": "TOKENS", "name": "Tokens", "balance": 500, "held": 0, "available": 500 } ] } ``` Los créditos concedidos de esta forma nunca caducan, a diferencia de los créditos por ciclo de una suscripción vinculada. El campo `metadata` es opcional. Una etiqueta como `reason: initial_balance` facilita identificar la concesión en el [historial de transacciones](api-adapty/operations/listVirtualCurrencyTransactions) y separarla de los créditos por compra en tus propios informes. 3. **Registra la concesión en tu base de datos**: Guarda que este usuario ya recibió su saldo inicial, para que un nuevo inicio de sesión o un trabajo reintentado no lo conceda dos veces. La siguiente sección explica por qué este registro es necesario. ## Concédelo exactamente una vez \{#grant-it-exactly-once\} Tu propia base de datos es la fuente de verdad para saber si un usuario ya ha recibido su saldo inicial. Hay dos enfoques que pueden parecer suficientes pero no lo son: - **`Idempotency-Key` protects one call, not one user**: Al reintentar una solicitud con la misma clave se devuelve el resultado original en lugar de volver a conceder el saldo, por lo que una sola llamada puede reintentarse con seguridad tras un timeout. Adapty recuerda cada clave hasta una hora, por perfil. Pasado ese tiempo, la misma clave concede de nuevo, así que la cabecera no puede indicarte meses después si un usuario ya recibió su saldo inicial. Genera una clave nueva para cada nueva concesión. - **Leer el saldo primero no demuestra nada**: Un saldo de 0 significa que el usuario nunca recibió créditos, o que los recibió y los gastó todos. [Listar saldos de moneda virtual](api-adapty/operations/listVirtualCurrencyBalances) no puede distinguir entre ambos casos. Así que comprueba tu propio registro antes de llamar a la API, y escribe el registro después de una respuesta exitosa. ## Rellenar saldo de usuarios existentes \{#backfill-existing-users\} Cuando creas una moneda, todos los perfiles existentes parten de 0, y los [vínculos de productos solo se aplican desde ese momento en adelante](create-virtual-currency#link-products). Para dar a tus usuarios actuales un saldo inicial, ejecuta un relleno puntual desde tu backend. 1. En tu propia base de datos, recopila los IDs de usuario de los clientes a los que quieres otorgar moneda. 2. Para cada uno, llama a [Crear transacción de moneda virtual](api-adapty/operations/createVirtualCurrencyTransaction) como se muestra arriba. No existe un endpoint masivo: una solicitud otorga moneda a un perfil, y una sola solicitud puede cubrir hasta 20 monedas para ese perfil. 3. Mantén el registro por usuario de la sección anterior a medida que avanzas, para poder detener el proceso y reanudarlo sin otorgar moneda dos veces. 4. Respeta el límite de 600 solicitudes por minuto por app. Una solicitud que supere el límite falla con `rate_limited`, así que regula el proceso y reintenta los usuarios fallidos. :::tip Haz el backfill solo de los usuarios a los que quieras llegar, como los activos o de pago. Los créditos otorgados a través de la API nunca caducan, así que el saldo que entregues ahora permanecerá en el perfil hasta que el usuario lo gaste. ::: ## Próximos pasos \{#next-steps\} - Gasta el saldo y léelo en tiempo de ejecución: [Inicio rápido de moneda virtual](virtual-currency-quickstart). - Consulta lo que tiene un usuario y audita cada cambio: [Saldo de moneda virtual](virtual-currency-balance). --- # 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 cliente siempre apunta al mismo perfil, por lo que el usuario ve el mismo saldo en todos los dispositivos donde inicia sesión y lo conserva tras reinstalar la app. - **Usuarios anónimos**: Un perfil anónimo puede acumular y gastar créditos, pero está vinculado a una instalación de la app en un dispositivo concreto. Si el mismo usuario reinstala la app o la abre en otro dispositivo sin iniciar sesión, Adapty crea un nuevo perfil anónimo con saldo cero. Los créditos permanecen en el perfil anterior, al que el usuario ya no puede acceder. Para gestionar una economía de moneda virtual en distintos dispositivos y reinstalaciones, identifica a tus usuarios. Puedes identificarlos desde el código de la aplicación a través del SDK (consulta [Identificar usuarios](ios-quickstart-identify) en la guía de inicio rápido) o desde tu backend mediante 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 del lado del servidor](getting-started-with-server-side-api), identifica el perfil con el encabezado `adapty-profile-id` o `adapty-customer-user-id` — ambos se resuelven en un único perfil. Para perfiles anónimos, usa `adapty-profile-id`. --- # File: offers --- --- title: "Ofertas" description: "Configura y gestiona ofertas de suscripción en Adapty para impulsar las conversiones." --- Las ofertas en el App Store y Google Play Store son promociones especiales, pruebas gratuitas o descuentos en suscripciones. Ayudan a atraer nuevos usuarios, recuperar suscriptores perdidos y aumentar la conversión. :::tip Para añadir ofertas a productos desde el terminal o un agente de código, usa el [CLI para desarrolladores](developer-cli-products#product-offers). ::: Las ofertas solo se aplican a suscripciones. Los productos con duración **Lifetime**, **Non-Subscriptions** o **Consumables** no pueden llevar una oferta en ningún store, y Adapty oculta los campos de oferta para ellos. Para descontar un producto de compra única, [crea un segundo producto con el precio rebajado y dirígelo con audiencias](create-offer#how-adapty-works-with-offers). ## Habilitar ofertas en tu app \{#enable-offers-in-your-app\} Para procesar ofertas, Adapty necesita la siguiente configuración: 1. Crea ofertas en el store: - [App Store](app-store-offers) - [Google Play](google-play-offers) 2. Para App Store: [sube tu clave de compra in-app desde App Store Connect a Adapty](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers). 3. [Añade las ofertas a Adapty y muéstralas en un flow o paywall](create-offer). :::note Las ofertas introductorias en iOS se aplican automáticamente si el usuario es elegible. No las añadas a Adapty. ::: Una excepción al paso 1: Adapty crea una prueba gratuita introductoria en el App Store por ti, pero solo cuando [envías un nuevo producto a las stores](create-product#create-product-and-push-to-store). Una oferta introductoria que configures tú mismo funciona con normalidad, pero [nunca aparece en Adapty](store-sync). --- # File: offers-in-stores --- --- title: "Añadir trials y ofertas promo en los stores" description: "Configura ofertas en App Store y Google Play para mejorar la monetización y retención de tu app." --- Primero, tienes que configurar las ofertas en el store. Consulta las guías según tu plataforma: - [App Store](app-store-offers) - [Google Play](google-play-offers) --- # 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 es necesario 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 configurarlas en tus productos y paywalls. - Las ofertas promocionales incluyen períodos de suscripción gratuitos, descuentos porcentuales y descuentos de precio fijo. Solo son elegibles los usuarios que tienen o han tenido una suscripción. Los usuarios que nunca se han suscrito no pueden recibir ofertas promocionales. - Las ofertas de recuperación incluyen períodos de suscripción gratuitos o descuentos porcentuales. Solo son elegibles los usuarios que han cancelado su suscripción. - **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 \{#promotional-offers\} 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 pruebas o descuentos a suscriptores nuevos, existentes o que hayan abandonado. :::tip Para añadir ofertas a productos desde el terminal o un agente de código, usa el [CLI para desarrolladores](developer-cli-products#product-offers). ::: Después de configurarlos en App Store Connect o Google Play Console, debes 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-add-offer-to-product-in-adapty) 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 cumple los requisitos. No las añadas a los productos en Adapty. Esta guía explica cómo configurar las ofertas promocionales (App Store), las ofertas de recuperación (App Store) y todas las ofertas de Google Play. ::: ## 0. Antes de empezar \{#before-you-start\} Las ofertas solo se aplican a suscripciones: los productos con duración **Lifetime**, **Non-Subscriptions** o **Consumables** no pueden incluirlas. Para descontar una compra única, [crea un segundo producto al precio descontado y dirígelo con audiencias](#how-adapty-works-with-offers). Antes de empezar a 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 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 & Paywall Builder](adapty-flow-builder), puedes asociar una oferta con un producto en el panel **Products** o en una tarjeta de producto. #### En el panel Products \{#in-the-products-panel\} - **Al añadir el producto**: Haz clic en **+**, selecciona el producto y elige la oferta en **Offer**. Las tarjetas que usen el producto después tendrán esta oferta preseleccionada. Los pasos completos están en [Añadir productos en el panel de productos](paywall-product-block#add-products-in-the-products-panel). - **Para un producto ya en la pantalla**: Abre el menú contextual de la entrada del producto y selecciona [**Offer**](paywall-product-block#product-actions). Cada tarjeta, variable de texto y acción que use el producto cambiará a la nueva oferta. #### En una tarjeta de producto \{#on-a-product-card\} 1. En el canvas, selecciona la tarjeta de producto que debe mostrar la oferta. 2. En el panel derecho, bajo **Product**, selecciona el producto y luego elige la oferta en el desplegable de ofertas. Si añadiste el producto con una oferta en el panel **Products**, esa oferta ya estará preseleccionada. Si eliges una oferta diferente, se añade una segunda entrada para el producto en el panel. ### Añadir una oferta al paywall \{#add-offer-to-paywall\} :::info Puedes cambiar los productos y ofertas de un paywall mientras esté en estado **Draft**, o dentro de los 30 días siguientes a su creación si todavía no tiene transacciones. Pasado ese plazo, Adapty rechaza el cambio y el dashboard desactiva los campos de producto explicando el motivo. Sustituir una oferta por otra en un producto que ya está en el paywall cuenta como tal cambio. Para cambiar productos u ofertas en un paywall después de ese punto, [duplícalo](duplicate-paywalls) y configura la copia. Los flows no tienen esta restricción: puedes cambiar sus productos y ofertas en cualquier momento. ::: Para que una oferta sea visible y seleccionable en un [paywall](paywalls) para los usuarios de tu aplicación, sigue estos pasos: 1. Al crear o editar un paywall, en la pestaña **General**, añade un producto al que acabas de agregar la oferta. 2. Elige una oferta que hayas creado previamente para este producto en la lista **Offer**. La lista solo está disponible para los productos que tienen ofertas. 3. Si es necesario, añade más productos y ofertas, pero solo puedes añadir una oferta por cada 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: access-level --- --- title: "Niveles de acceso" description: "Aprende qué son los niveles de acceso en Adapty y cómo configurarlos para la gestión de usuarios." --- Los niveles de acceso te permiten controlar lo que los usuarios pueden hacer en tu aplicación móvil sin codificar IDs de productos específicos. Cada producto define durante cuánto tiempo el usuario obtiene un determinado nivel de acceso. Por tanto, cuando un usuario realiza una compra, Adapty concede acceso a la aplicación durante un período específico (para suscripciones) o de forma permanente (para compras de por vida). Al crear una aplicación en el Adapty Dashboard, se genera automáticamente el nivel de acceso `premium`. Este es el nivel de acceso predeterminado y no se puede eliminar. Puedes tener múltiples niveles de acceso por aplicación. Aquí tienes algunos ejemplos de cuándo pueden ser útiles: - En una app de noticias donde vendes suscripciones a diferentes temas de forma independiente, puedes crear niveles de acceso como `sports` y `science`. - En una app de fitness que ofrece entrenamientos en vídeo grabados con una suscripción regular (usando el nivel de acceso `premium` por defecto), los clientes pueden optar por una opción más cara que dé acceso a entrenamientos en directo con un entrenador. En este caso, puedes crear un nivel `live_coach_access`. - En una app de aprendizaje de idiomas, puedes crear un nivel de acceso para cada idioma disponible. :::note Los niveles de acceso pueden compartirse o transferirse entre los perfiles de un usuario; el saldo de moneda virtual no puede hacerlo: permanece en un perfil. Consulta [Saldos, perfiles y dispositivos](virtual-currency-balance#balances-profiles-and-devices). ::: Para empezar a trabajar con niveles de acceso en Adapty, ve a **[Products](https://app.adapty.io/access-levels)** desde el menú principal de Adapty y selecciona la pestaña **Access levels**. La lista **Access levels** muestra todos los niveles de acceso, incluido el `premium` que se añade automáticamente y los que tú hayas añadido en Adapty. <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 */ }} /> --- # File: create-access-level --- --- title: "Crear un 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 durante cuánto tiempo el usuario obtiene un determinado nivel de acceso. Así, cuando un usuario realiza una compra, Adapty concede acceso a la app durante un período específico (en el caso de suscripciones) o de forma permanente (en el caso de 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 mediante programación usando la [CLI para desarrolladores](developer-cli-apps#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 aplicación móvil, 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 aplicación. Asegúrate de que sea claro y fácil de entender. 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) requiere un nivel de acceso asociado. Para las suscripciones y los productos de acceso de por vida, la compra activa ese nivel de acceso: Adapty determina la duración de la suscripción y la usa como fecha de vencimiento del nivel de acceso, mientras que una compra de acceso de por vida deja el nivel de acceso activo sin fecha de vencimiento. Las compras consumibles y las compras únicas no activan un nivel de acceso, aunque el producto igualmente lleve uno asignado. Esas compras aparecen en el array `nonSubscriptions` del perfil del usuario, y es tu app quien decide qué desbloquear. Cada producto tiene exactamente un nivel de acceso. Para desbloquear varios conjuntos de funciones con una sola suscripción o compra de por vida, crea un nivel de acceso combinado (por ejemplo, `premium_extra`) y asígnalo a ese producto. En tu app, trata un nivel de acceso `premium_extra` activo como si desbloqueara todos los conjuntos de funciones que cubre. Para vincular un nivel de acceso a un producto: 1. Mientras [configuras 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**. ## Conceder un nivel de acceso a un cliente específico mediante la API \{#give-access-level-to-a-specific-customer-via-api\} También tienes la opción de conceder a un cliente un nivel de acceso desde tu servidor usando la API de Adapty. Esto resulta muy útil si tienes bonificaciones por referidos u otros eventos relacionados con tus productos. Puedes encontrar más detalles en la página [Conceder nivel de acceso con la API del servidor](api-adapty/operations/grantAccessLevel). ## Registrar concesiones manuales en tu backend \{#track-manual-grants-on-your-backend\} Si tu [integración de webhook](set-up-webhook-integration) tiene activado el evento [Access level updated](webhook-event-types-and-fields#for-access-level-updated-event), Adapty lo envía cuando concedes, actualizas o revocas un nivel de acceso manualmente. No se envía ningún evento cuando expira el acceso concedido: Adapty recalcula el acceso en cada solicitud en lugar de almacenar un cambio de estado, por lo que la expiración no desencadena nada. Si tu backend necesita revocar funciones cuando finaliza el acceso, programa la revocación a partir de la fecha de vencimiento que estableciste al conceder el nivel de acceso. --- # 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: placements --- --- title: "Placements" description: "Gestiona los placements en Adapty para optimizar la visibilidad y los ingresos de flows y paywalls." --- Un **placement** es un punto de tu app donde insertas contenido de Adapty. Puedes invocar un placement al lanzar la app, antes de una función premium, en una pantalla de ajustes o en cualquier otro lugar. Un placement tiene uno de tres tipos. Dos de ellos son para trabajo nuevo: - Un **placement de flow** sirve un [flow](adapty-flow-builder): una pantalla o secuencia de pantallas creadas en el [Flow & Paywall Builder](adapty-flow-builder). Un flow puede ser un onboarding, un paywall o un onboarding que termina en un paywall. Si quieres construir la pantalla tú mismo, usa un paywall placement. - Un **paywall placement** pasa un [paywall](paywalls) —una configuración de productos que creas en el dashboard— a una pantalla que tú construyes por tu cuenta. El tercer tipo, los **placements de onboarding**, sirve [onboardings](onboardings) desde el legacy Onboarding Builder. Adapty deprecará ese builder el 1 de octubre de 2026, así que crea los nuevos onboardings como flows. ## Reglas y errores comunes \{#rules-and-pitfalls\} - **El tipo de un placement se fija en el momento de crearlo**, porque cada tipo tiene su propio conjunto de métricas. - **Los IDs de placement son únicos en toda tu app**, así que el mismo ID no puede servir un flow en un sitio y un paywall en otro. - **Un paywall personalizado y un flow no pueden compartir una [prueba A/B](ab-tests)**, porque cada uno necesita su propio tipo de placement. Para compararlos, [despliega el flow de forma gradual](flow-gradual-rollout) en su propio placement y mide ambos entre sí. - **Para pasar un placement de paywall a un flow, crea un nuevo placement de flow con un nuevo ID**, luego publica una versión de la app que lo llame. Consulta [Migrar a flows](migrate-to-flows). - **Lo único que escribes directamente en tu app es el ID del placement.** Todo lo demás lo controlas desde el dashboard, sin publicar una nueva versión: qué flow o paywall sirve, la [segmentación por audiencia](audience) y las [pruebas A/B](ab-tests). - **El ID del placement debe coincidir exactamente con el ID del dashboard.** Cópialo sin espacios adicionales. Si el ID no coincide, la solicitud del SDK falla y el SDK utiliza el archivo de respaldo local si lo has configurado. - **Solo para el Paywall Builder antiguo**: desactivar **Show on device** no elimina un paywall de su placement. El placement sigue resolviéndose hacia él sin una configuración del builder, por lo que la app no tiene nada que renderizar. Para dejar de mostrar un paywall, elimínalo de la audiencia del placement. :::tip También puedes gestionar los placements de forma programática usando la [CLI para desarrolladores](developer-cli-placements). ::: ## Lista de placements \{#placements-list\} Para ver todos tus placements, ve a **Placements** desde el menú principal de Adapty. Los verás categorizados por tipo: **Paywalls**, **Onboardings** y **Flows**. Cada pestaña ofrece una vista completa de los distintos puntos del recorrido del usuario donde pueden aparecer flows, paywalls, onboardings o pruebas A/B. Cada elemento de la lista corresponde a un placement específico, lo que facilita su gestión y modificación. Puedes editar los detalles de un placement, asociarlo con el flow, paywall, onboarding o prueba A/B que desees para una audiencia específica, o eliminar los placements que no necesites. Los números de la tabla reflejan las métricas de cada placement desde su activación. <img src="/assets/shared/img/placements-list.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Desde aquí puedes: - [Crear un nuevo placement](create-placement) - [Editar un placement existente](edit-placement) - [Eliminar un placement existente](delete-placement) - Descargar [flows](fallback-flows), [paywalls](fallback-paywalls) u onboardings de respaldo locales. Estos archivos se utilizan cuando un usuario abre la app sin conexión al backend de Adapty (sin conexión a internet, o en el caso excepcional de que el backend esté caído) y no hay caché en el dispositivo. La misma descarga de **Fallbacks** cubre tanto flows como paywalls — la versión del SDK que elijas en el diálogo de descarga determina si el paquete incluye flows (SDK 4.0+) o solo paywalls. --- # 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 concreta 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 selección de suscripción podría aparecer en el 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. Puedes mostrar los mismos o diferentes flows, paywalls, onboardings o pruebas A/B en distintos placements o a diferentes segmentos de usuarios, llamados "audiencias" en Adapty. 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-placements#adapty-placements-create). ::: :::info Aunque el proceso de creación de placements es similar para flows, paywalls y onboardings, no puedes crear un único placement que sirva más de un tipo: cada tipo de placement procesa métricas diferentes. El tipo queda fijado una vez creado el placement, por lo que servir un flow donde antes servías un paywall implica crear un nuevo placement de tipo flow con un nuevo ID de placement. ::: ## 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. Introduce un **Placement name**. Es un identificador interno en el Adapty Dashboard. Puedes editarlo más adelante si es necesario. 4. Introduce 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. Los IDs de placement son únicos en todos los placements de la app, independientemente de su tipo, y no se pueden editar una vez creados. Además, distinguen entre mayúsculas y minúsculas: `onboarding` y `Onboarding` son dos placements distintos. Tu app debe solicitar exactamente el ID que introduzcas aquí. 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 desees 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 de 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á para 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 de placement - Nombre de audiencia - Nombre de segmento - Nombre de prueba A/B de placement cruzado - Nombre de prueba A/B - Nombre de flow, nombre de paywall o nombre de onboarding (según la pestaña desde la que exportaste) --- # 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: audience --- --- title: "Audiencias" description: "Aprende a segmentar y gestionar audiencias en Adapty para ofertas de suscripción dirigidas." --- Las **audiencias** en Adapty son grupos de usuarios basados en [segmentos](segments), que te ayudan a personalizar flows, paywalls, onboardings o pruebas A/B para grupos de usuarios específicos. Puedes definir estos segmentos usando filtros para asegurarte de que los usuarios correctos vean el flow, paywall u onboarding adecuado en tu app. En Adapty, un **Placement** es donde puedes mostrar flows, paywalls, onboardings o pruebas A/B. Cuando añades una audiencia a un placement, estás dirigiendo contenido personalizado a grupos de usuarios específicos. Por ejemplo, puedes mostrar distintos flows o paywalls según la edad, el dispositivo o el estado de suscripción de un usuario. Si un usuario pertenece a varios grupos, puedes elegir qué grupo tiene prioridad para decidir qué contenido verá. En el ejemplo siguiente, tenemos un flow de onboarding para mostrar tu placement con el identificador `Onboarding`. En el código de tu app, accederás al placement usando este identificador. Si el usuario pertenece a la audiencia "Yoga beginners", verá el primer paywall. Los que no encajen en la audiencia "Yoga beginners" verán el segundo paywall. <img src="/assets/shared/img/6bf7797-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 */ }} /> Para mostrar un flow, paywall, onboarding o prueba A/B a una audiencia específica, haz lo siguiente: 1. [Crea un segmento de usuario](segments#creation). Puedes omitir este paso si quieres mostrar el flow, el paywall o la prueba A/B a todos los usuarios. En ese caso, usa la audiencia "All users" creada por defecto. 2. [Añade este segmento como audiencia a un placement y define qué flow, paywall o prueba A/B se mostrará](add-audience-paywall-ab-test). La audiencia "All users" se añade automáticamente a cada placement; solo tienes que especificar qué flow, paywall o prueba A/B se debe mostrar. 3. [Establece las prioridades correctas](change-audience-priority) si tienes más de una audiencia en un placement. Esto garantiza que los usuarios que pertenezcan a más de una audiencia vean el contenido más relevante. Cuando un usuario forma parte de varias audiencias, se mostrará el contenido de la audiencia con mayor prioridad. 4. <InlineTooltip tooltip="Muestra el flow o el paywall asociado a este placement en el código de la app móvil">[iOS](ios-quickstart-paywalls), [Android](android-quickstart-paywalls), [React Native](react-native-quickstart-paywalls), [Flutter](flutter-quickstart-paywalls) y [Unity](unity-quickstart-paywalls)</InlineTooltip>. --- # 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." --- 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 desees 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 de 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á para 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, trial y compra pueden agruparse por dos fechas diferentes: - **La fecha del evento**: cuándo se vio el paywall, cuándo empezó el trial o cuándo se realizó la compra. - **La fecha de instalación**: cuándo el usuario abrió la app por primera vez. Ambas vistas pueden mostrar números muy distintos para el mismo rango de fechas. La casilla **Filter metrics by install date** controla cuál usa el dashboard: - **Sin marcar (predeterminado)**: 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 consultas los trials. - **Sin marcar**: Muestra los trials que *empezaron* en abril, independientemente de cuándo instalaron esos usuarios. - **Marcada**: Muestra los trials de usuarios que *instalaron* en abril, independientemente de cuándo empezaron sus trials. La vista por fecha de instalación sigue creciendo después de que termina el período. Un usuario que instaló en abril y empieza un trial en julio se añade a abril en ese momento, por lo que las métricas de abril son más altas cada vez que las abres. La vista por fecha de evento no cambia de esa manera: cada evento cuenta en el día en que ocurrió. Usa la vista por fecha de instalación para medir el rendimiento de adquisición de usuarios de una cohorte específica. Usa la vista por fecha de evento para medir la actividad de paywall u onboarding en un período concreto. ### 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 utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo 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: 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 perfiles, puedes buscar un usuario específico 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 has configurado uno. - **Email**: el correo electrónico del usuario, si se ha enviado como atributo personalizado. - **Transaction ID**: el ID de transacción del store correspondiente a una compra. La búsqueda coincide exactamente con el valor completo: las coincidencias parciales no funcionan, y los espacios en blanco alrededor del identificador hacen que la búsqueda no devuelva nada — pega el identificador completo sin espacios adicionales. El ID de usuario personalizado, el correo electrónico y el ID de transacción también distinguen entre mayúsculas y minúsculas. 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 | Identificador de tu usuario final en tu sistema. | | Adapty ID | Identificador interno de Adapty para tu usuario final, llamado Profile ID. | | IDFA | El Identificador para Anunciantes, asignado por Apple al dispositivo del usuario. Requiere el permiso de App Tracking Transparency (ATT) en iOS 14+. No disponible en Android. | | IP country | País del usuario final, determinado por su dirección IP más reciente. | | Store country | País de la cuenta de App Store o Google Play del usuario final, según lo indicado por el store. Adapty lo recibe con las transacciones del store, por lo que estará vacío para usuarios sin compras. | | 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 fue registrado 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 con 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' }} /> Para modificar o eliminar un atributo, haz clic en los tres puntos que aparecen junto a él y selecciona **Edit** o **Delete**. Eliminar un atributo solo afecta a ese perfil; los demás perfiles que tengan el mismo atributo lo conservarán. ## 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: "Descubre cómo Adapty crea, rastrea y vincula perfiles de usuario, incluidos perfiles anónimos, usuarios identificados y relaciones entre perfiles padre e hijo." --- Cada usuario de tu app recibe 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 evitar errores de integración, prevenir la fragmentación de datos e interpretar los datos 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). **Habilitado (por defecto)** 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á registrado con el mismo Apple/Google ID. Esto es útil cuando un usuario reinstala la app e inicia sesión con un email 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 análisis coherentes y conservar un historial completo de transacciones, incluyendo períodos de prueba, compras de suscripciones, renovaciones y más, vinculados 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é registrado con el 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 análisis coherentes y conservar un historial completo de transacciones, incluyendo períodos de prueba, compras de suscripciones, renovaciones y más, vinculados al mismo perfil. Tras cambiar a **Transferir acceso al nuevo usuario**, los niveles de acceso no se transferirán entre perfiles de inmediato. El proceso de transferencia para cada nivel de acceso específico solo se activa cuando Adapty recibe un evento del store, como la renovación de una suscripción, una restauración o la validación de una transacción. **Deshabilitado** El primer perfil de usuario identificado que obtenga un nivel de acceso lo conservará de forma permanente. Esta es la mejor opción si la lógica de tu 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. Deshabilitar el uso compartido solo afecta a los nuevos usuarios. Las suscripciones que ya se compartan entre usuarios seguirán compartiéndose incluso después de deshabilitar esta opción. :::warning Apple y Google requieren 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. Deshabilitar el uso compartido puede impedir que los usuarios recuperen el acceso al iniciar sesión. Recomendamos deshabilitar el uso compartido solo si tus usuarios **deben iniciar sesión** antes de realizar una compra. De lo contrario, un usuario identificado podría comprar una suscripción, iniciar sesión con 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 usa 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 vincular las compras 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 aun así 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 **Deshabilitado** 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`. ::: :::warning La transferencia mueve el nivel de acceso en sí, no solo la compra de la store. Cuando una restauración transfiere el acceso a un nuevo perfil, Adapty revoca ese nivel de acceso del perfil anterior aunque haya sido otorgado por una store diferente; por ejemplo, una compra web (Stripe) asignada al mismo nivel de acceso. Si las compras web y las compras in-app deben mantenerse independientes, asigna sus productos a niveles de acceso separados. ::: ## 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. | Una vez guardado un atributo personalizado, su **Key** y **Type** son permanentes: ambos campos quedan deshabilitados si vuelves a abrir el atributo para editarlo. Si necesitas una clave o un tipo diferentes, crea un nuevo atributo personalizado en lugar de intentar editar el existente; solo el **Name** y los **Values** (en un atributo de tipo String) se pueden modificar después de la creación. Una vez completados los campos obligatorios, puedes usar los atributos personalizados en tus segmentos, [pruebas A/B](ab-tests) y más. Cada perfil puede tener hasta 30 atributos personalizados. ### Cómo los operadores tratan los atributos ausentes \{#how-operators-match-missing-attributes\} El operador **≠** solo coincide con perfiles en los que el atributo está definido: un perfil sin valor para ese atributo no coincide con `attribute ≠ value`. Lo mismo se aplica a los operadores de comparación (`>`, `<`, `≥`, `≤`, `between`). Para encontrar perfiles que no tienen el atributo, usa el operador **is not set**. Para seleccionar todos los perfiles excepto los que tienen un valor concreto, combina `≠` con **is not set** — o asigna un valor explícito a cada perfil y filtra solo con `≠`. La comparación también es de tipo estricto: si un perfil almacena el atributo con un tipo distinto al que el segmento espera —por ejemplo, un valor de cadena donde el segmento espera un número—, el perfil se considera como si no tuviera ese atributo. ## 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. :::note El estado de envío de AppsFlyer, Facebook Ads y Branch puede ser inexacto, ya que no siempre devuelven errores cuando se producen. ::: Para ver el perfil del usuario que inició la transacción, haz clic en el botón **View Profile** en los detalles del evento. ## Falta un evento en el feed \{#an-event-is-missing-from-the-feed\} Hay tres razones por las que un evento puede no aparecer en el feed. - **Adapty nunca lo creó.** Para la mayoría de eventos, desactivar el tipo en una integración sigue creando el evento, solo que con el [estado gris](event-statuses). Pero hay dos excepciones: 1. Adapty no crea `access_level_updated` a menos que la [integración de webhook](webhook) esté configurada y habilitada. 2. Adapty no crea [eventos sintéticos](events#synthetic-events) a menos que el periodo de prueba o la suscripción siga activo cuando Adapty lo comprueba. Consulta [Un evento sintético nunca llegó](handle-integration-errors#a-synthetic-event-never-arrived). - **El evento existe, pero no aparece en el feed.** Las transacciones de la API server-side v1 nunca aparecen — usa la [API server-side (versión 2)](api-adapty/operations/setTransaction) en su lugar. Tampoco aparecen los eventos sin perfil: transacciones que llegaron antes de que el SDK identificara a un usuario, como las notificaciones del servidor del 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). Tampoco aparecen las renovaciones con marcas de tiempo de Apple registradas con antelación — estas [aparecen una vez que la marca de tiempo ha pasado](how-profiles-work), aunque Adapty las reenvía a tus integraciones de inmediato. - **El evento tiene más de dos semanas de antigüedad.** El feed solo conserva eventos durante dos semanas. Por eso, un [evento sintético](events#synthetic-events) con un retraso prolongado puede aparecer sin el evento `trial_started` o `subscription_started` que inició su temporizador. --- # File: retention-messaging --- --- title: "Retention Messaging" description: "Muestra a los suscriptores un mensaje personalizado en la pantalla de cancelación de suscripción de Apple. Crea y localiza mensajes de retención en Adapty — sin backend, sin publicar la app." --- [Retention Messaging](https://developer.apple.com/documentation/retentionmessaging) es una función de Apple que muestra a los suscriptores un mensaje personalizado en la pantalla de iOS **Cancel Subscription**, justo antes de que confirmen la cancelación. Úsala para reducir la pérdida de suscriptores dándoles un motivo — o una oferta — para quedarse. Adapty se conecta a la API de Retention Messaging de Apple, de modo que Apple solicita el mensaje adecuado en tiempo real cada vez que un suscriptor pulsa **Cancel Subscription**. Adapty gestiona ese endpoint por ti: escribes, localizas y pruebas los mensajes en el Adapty Dashboard, y Adapty responde a Apple de forma automática. Sin backend que construir, sin publicar una nueva versión de la app. Hay dos tipos de mensajes. Un mensaje *predeterminado* es texto simple y sirve como respaldo cuando no se puede mostrar un mensaje promocional. Un mensaje *promocional* añade un incentivo concreto: un descuento, una prueba gratuita o un cambio a un plan más adecuado. Apple muestra a cada suscriptor el mensaje que coincide con su producto e idioma. <CustomDocCardList ids={['retention-messaging-setup', 'retention-messaging-create', 'retention-messaging-analytics']} /> ## Inicio rápido \{#quickstart\} Antes de empezar, necesitas: - Una [clave de API de App Store Connect](app-store-connection-configuration) en Adapty. - [La aprobación de Apple para tu app](retention-messaging-setup#request-access-from-apple). La revisión de Apple puede tardar hasta un mes, así que envía la solicitud con tiempo. - Al menos un [producto](create-product) al que tus mensajes hagan referencia. Luego: 1. [Conecta Adapty a la API de Mensajería de Retención en modo sandbox](retention-messaging-setup). 2. Compra tu producto de mayor duración con una cuenta sandbox o a través de TestFlight, y mantén la suscripción activa. 3. [Supera el test de rendimiento de Apple](retention-messaging-setup#pass-the-performance-test). Puede tardar hasta una hora. 4. Cambia al modo de producción. 5. [Crea tus mensajes de producción](retention-messaging-create) — primero los mensajes predeterminados, luego los mensajes promocionales — y envía cada uno para la revisión de Apple. 6. [Haz seguimiento del rendimiento de cada mensaje](retention-messaging-analytics) una vez que Apple lo apruebe. --- # File: retention-messaging-setup --- --- title: "Configurar Retention Messaging" description: "Conecta Adapty con Retention Messaging de Apple: solicita acceso a Apple, configúralo en sandbox, supera el test de rendimiento de Apple y cambia a producción." --- Antes de que los suscriptores puedan ver tus mensajes de retención, Apple necesita una forma de contactar a Adapty cuando alguien cancela. Este artículo explica cómo solicitar acceso a Apple y configurar esa conexión — primero en modo sandbox y luego en producción. ## Antes de empezar \{#before-you-begin\} - Añade una [clave de API de App Store Connect](app-store-connection-configuration) a Adapty. Adapty la usa para registrar la URL en tiempo real y comunicarse con Apple. - [Solicita acceso a Apple](#request-access-from-apple) y espera su aprobación. Sin ella, Apple rechaza la conexión. - Crea al menos un [producto](create-product) para que lo cubran tus mensajes. ## Solicitar acceso a Apple \{#request-access-from-apple\} Apple concede acceso a la Retention Messaging API por aplicación y solicita la URL del endpoint que responde a sus peticiones. Adapty gestiona ese endpoint por ti, así que no tienes que construir ni alojar nada. Solo tienes que enviar la URL que Adapty te proporciona. 1. En el Adapty Dashboard, ve a [**App settings > iOS SDK**](https://app.adapty.io/settings/ios-sdk). Copia la URL de la sección **Apple Retention Messaging API**. La misma URL sirve tanto para sandbox como para producción. 2. Abre el [formulario de solicitud de Apple](https://developer.apple.com/documentation/retentionmessaging) y envíalo con la URL copiada. 3. Espera la decisión de Apple. Apple realiza la revisión, que puede tardar hasta un mes. Adapty te notificará una vez que tu app sea aprobada. Hasta que Apple apruebe tu app, la página de Retention Messaging muestra estos mismos pasos y la misma URL. ## Configuración del sandbox \{#sandbox-setup\} Apple requiere que primero pruebes la conexión en un entorno sandbox para verificar su fiabilidad antes de enviar mensajes en producción. ### Configurar la URL de Sandbox \{#configure-sandbox-url\} Haz clic en **Configure Sandbox URL** para comenzar la configuración. Si la página muestra la introducción **Give subscribers a reason to stay**, haz clic primero en **Configure Sandbox URL** para abrir la tarjeta de configuración. Adapty conecta tu cuenta de App Store Connect a su propio endpoint en tiempo real. Si ya hay otra URL configurada en App Store Connect, el botón mostrará **Replace URL** en su lugar. Cuando la conexión se realiza correctamente, Adapty lo confirma con "Sandbox URL has been configured." ### Superar la prueba de rendimiento \{#pass-the-performance-test\} Antes de que puedas enviar mensajes de producción, Apple realiza una prueba para confirmar que Adapty responde rápidamente a sus solicitudes. Adapty ejecuta la prueba con una suscripción activa de sandbox o TestFlight, que tú mismo debes adquirir. :::important Las suscripciones de sandbox se renuevan según un [calendario acelerado](test-purchases-in-sandbox#test-subscriptions) y se detienen después de un número determinado de renovaciones. Por ello, un producto mensual puede caducar a mitad de la prueba y hacer que esta falle. Cancelar la suscripción manualmente tiene el mismo efecto. ::: 1. Compra tu producto de mayor duración — un plan anual, si tienes uno — con [una cuenta sandbox](test-purchases-in-sandbox) o a través de TestFlight. Usa tu producto normal de App Store Connect: no crees uno exclusivo para sandbox. 2. Haz clic en **Start Test** justo después de la compra. La prueba puede tardar hasta una hora, y la suscripción debe permanecer activa hasta que finalice. 3. Si la prueba falla, realiza una nueva compra y haz clic en **Try Again**. Si sigue fallando, contacta con [support@adapty.io](mailto:support@adapty.io). ## Cambiar a producción \{#switch-to-production\} Una vez que la URL de sandbox esté configurada y la prueba de rendimiento haya pasado, Adapty te permite desactivar el toggle **Sandbox mode** y entrar en modo de producción. Haz clic en **Configure Production URL**. Adapty registra su endpoint con Apple para producción y confirma con "Production URL has been configured." Si ya hay una URL de producción diferente configurada en App Store Connect, haz clic en **Replace URL** en su lugar. ## Próximos pasos \{#next-steps\} - [Crear un mensaje de retención](retention-messaging-create) que los suscriptores vean cuando cancelen. --- # File: retention-messaging-create --- --- title: "Crear un mensaje de retención" description: "Crea mensajes de retención predeterminados y promocionales en Adapty. Redacta el contenido, añade una oferta o cambio de plan, localízalo y envíalo para revisión de Apple." --- Los mensajes de retención te permiten llegar a los suscriptores que están a punto de cancelar durante el proceso de cancelación. Cuando un cliente llega a la pantalla de cancelación de suscripción de iOS, Apple le muestra el mensaje que corresponde a su producto y configuración regional. Hay dos tipos de mensajes de retención: - **Default**: Un mensaje de recuperación simple con un titular, cuerpo y una imagen opcional. Se usa como respaldo cuando no hay un mensaje promocional disponible. - **Promo**: Un mensaje de recuperación que ofrece a los suscriptores una oferta especial o un plan diferente. Solo puedes crear mensajes promocionales para productos que ya tengan asignado un mensaje Default. ## Antes de empezar \{#before-you-begin\} - Crea al menos un [producto](create-product) que cubra el mensaje. - [Prueba la mensajería de retención](retention-messaging-setup) en modo sandbox. ## Crear un mensaje \{#create-a-message\} Abre la página Retention Messaging en el Adapty Dashboard. Haz clic en **Create Default** para un mensaje predeterminado, o en **Create Message** para un mensaje promocional. Selecciona el entorno: un mensaje sandbox para pruebas o un mensaje de producción para usuarios reales. La opción de producción permanece desactivada hasta que [configures la URL de producción](retention-messaging-setup#switch-to-production) y superes la prueba de rendimiento en sandbox. ### General Ponle nombre al mensaje y añade los productos que lo activarán cuando se cancelen. La lista de productos muestra en gris los que no se pueden añadir, con la razón al lado: **used** significa que otro mensaje del mismo tipo ya cubre ese producto, y **no default message** significa que el producto necesita un mensaje predeterminado antes de poder tener uno promocional. Para un mensaje de tipo **promo**, debes vincular un incentivo a cada producto que añadas. Cada producto recibe un incentivo, y el botón **Next** permanece desactivado hasta que todos los productos tengan uno: - **Link Promo Offer**: Una oferta promocional que [configuraste previamente para el producto](app-store-offers). - **Link Plan Switch**: Un producto diferente de tu catálogo. Si la lista de planes está vacía, comprueba que hayas añadido una [clave de API de App Store Connect](app-store-connection-configuration): Adapty la necesita para buscar los planes. ### Contenido \{#content\} Escribe el mensaje en inglés. La vista previa de la derecha muestra el mensaje en la pantalla de cancelación de suscripción mientras escribes. El mensaje **Default** puede incluir una imagen. Añade un **Alt text** para describir la imagen a los lectores de pantalla. El alt text no se localiza: Apple recibe el alt text en inglés para todos los idiomas. De forma predeterminada, el título y el cuerpo aparecen debajo de la imagen. Para mostrar el título encima de la imagen, selecciona **Above the image** en **Title position**. El cuerpo siempre permanece debajo de la imagen. :::important Elige la posición del título al crear el mensaje. No podrás cambiarlo después: si quieres usar una posición diferente, crea un nuevo mensaje. Si creas un mensaje sin imagen y añades una más tarde, el título quedará debajo de la imagen. ::: ### Localización \{#localization\} El inglés es el idioma de origen. Añade los demás idiomas que usen tus suscriptores y traduce el contenido. 1. Haz clic en **Add Locale** y elige qué idiomas quieres incluir. 2. Haz clic en **AI Translate All Cells** para traducir automáticamente el [contenido](#content). La traducción reemplaza el título y el cuerpo en todos los idiomas añadidos, incluidas tus ediciones manuales, así que traduce primero y edita después. 3. Haz clic en cualquier celda para editar una traducción. Cada traducción debe respetar los mismos límites que el texto en inglés: 66 caracteres para el título y 144 para el cuerpo. Si una traducción supera esos límites, el botón **Submit To Review** quedará bloqueado hasta que la acortes. Pasa el cursor sobre un idioma para previsualizarlo en la pantalla de cancelación de suscripción. Si Apple aún no ha aprobado una traducción para el idioma de destino, Adapty usará la versión `en-US` como respaldo. ## Enviar para revisión \{#submit-for-review\} Cuando el mensaje esté listo, haz clic en **Submit To Review**. Adapty sube el mensaje a Apple en todos los idiomas que tengan un título o cuerpo, y Apple comienza la revisión. Adapty descarta los idiomas cuyas celdas estén ambas vacías. Consulta el [estado](#message-states) de tu envío en la página Retention Messaging. Adapty guarda el mensaje solo cuando lo envías. ## Estados del mensaje \{#message-states\} Tras enviar el mensaje, Apple revisa cada idioma por separado. El mensaje se publica en cuanto Apple aprueba al menos un idioma. Para los idiomas que Apple aún no ha aprobado, Adapty [recurre a `en-US`](#localization). La columna **State** de la página Retention Messaging te permite hacer un seguimiento del progreso del mensaje: | Estado | Qué significa | | --- | --- | | **In review** | Apple no ha recibido el mensaje todavía, o no ha aprobado ninguno de sus idiomas. | | **Live** | Apple aprobó el mensaje y Adapty lo muestra a los suscriptores que están cancelando. | | **Rejected** | Apple rechazó el envío en al menos un idioma — prueba con una versión alternativa. | | **Inactive** | Has [desactivado](#deactivate-and-reactivate) el mensaje. Ahora puedes crear uno nuevo para los mismos productos. | Para ver qué idiomas aún no ha aprobado Apple, abre el mensaje y ve al paso **Localization**. Cada idioma añadido muestra su propio estado: **Live**, **In review** o **Rejected**. Puedes editar cualquier mensaje en cualquier momento, incluso los que se están mostrando activamente a tus clientes. Si editas un mensaje **Live** o le añades un nuevo idioma, el mensaje pierde su estado activo de inmediato y vuelve a quedar como **In review**. Si cambias el título o el cuerpo en inglés de un mensaje existente, el botón del paso **Content** cambia a **Update Locales**. Haz clic en él para abrir el paso **Localization**, actualiza las traducciones y luego haz clic en **Submit To Review**. ## Desactivar y reactivar \{#deactivate-and-reactivate\} Para eliminar un mensaje de Apple sin borrarlo de Adapty, abre el menú en la fila del mensaje y haz clic en **Deactivate**. Para recuperarlo, haz clic en **Activate**. La reactivación omite la revisión de Apple y el mensaje puede tardar unos minutos en llegar a todos los suscriptores. Un producto puede tener como máximo un mensaje predeterminado y un mensaje promocional. Si otro mensaje del mismo tipo reclamó el producto mientras este estaba inactivo, la activación fallará. ## Próximos pasos \{#next-steps\} - [Haz un seguimiento del rendimiento del mensaje](retention-messaging-analytics) una vez que esté activo. --- # File: retention-messaging-analytics --- --- title: "Seguimiento del rendimiento de los mensajes" description: "Lee las columnas Shown, Retained users, Saved users, Retention rate, Save rate y Saved revenue en la tabla de Retention Messaging de Adapty, y aprende por qué no suman el total de Shown." --- Para determinar qué mensajes mejoran la retención y cuáles permiten que los usuarios abandonen, consulta sus analíticas en la página de Retention Messaging. La tabla muestra cuántos suscriptores ha alcanzado cada mensaje activo, cuántos de ellos se quedaron y cuántos ingresos generaron. La tabla no tiene rango de fechas: cada métrica cubre toda la vida útil de cada mensaje. El botón **Sandbox mode** alterna la tabla entre producción y sandbox. ## Qué significa cada métrica \{#what-each-metric-means\} | Métrica | Qué mide | | --- | --- | | **Shown** | El número de veces que un suscriptor vio este mensaje en la pantalla de cancelación de suscripción. Las visualizaciones repetidas del mismo suscriptor en el mismo día no incrementan el contador. | | **Retained users** | Suscriptores de la cohorte **Shown** que pagaron el siguiente período, o compraron el producto de cambio que ofrecía el mensaje, antes de cancelar o de que caducara la suscripción. Adapty hace la correspondencia por la transacción original de la suscripción y no por el producto, por lo que un suscriptor que cambia a un plan diferente en lugar de cancelar también se contabiliza como retenido. | | **Saved users** | Suscriptores de la cohorte **Shown** que no cancelaron en las 24 horas siguientes a ver el mensaje. Una visualización solo se contabiliza una vez transcurridas esas 24 horas. | | **Retention rate** | **Retained users** dividido entre todos los de la cohorte **Shown** que ya han pagado, cancelado o cuya suscripción ha caducado. Se excluyen los que aún están esperando su próximo pago. | | **Save rate** | **Saved users** dividido entre todas las visualizaciones con más de 24 horas de antigüedad. | | **Saved revenue** | Lo que pagaron en USD los usuarios retenidos después de ver el mensaje, antes de la comisión de Apple. Adapty suma sus pagos hasta que la suscripción se cancela o caduca. | Cada recuento de la tabla corresponde a intentos de cancelación, no a personas. Adapty cuenta un intento por suscriptor, por mensaje y por día. Alguien que vuelve en tres días distintos cuenta tres veces, y cada intento tiene su propio resultado según lo que haga el suscriptor después. ## Cómo decide Adapty el resultado \{#how-adapty-decides-the-outcome\} **Los usuarios retenidos** y los **usuarios guardados** responden a preguntas distintas: - **Usuarios guardados**: el suscriptor no desactivó la renovación automática en las primeras 24 horas tras la visualización. Adapty conoce la respuesta un día después de la visualización. - **Usuarios retenidos**: el suscriptor pagó antes de que se produjera alguna cancelación o expiración. Adapty conoce la respuesta solo después de la siguiente fecha de facturación, que puede estar a un mes o a un año de distancia. Un suscriptor se cuenta como retenido únicamente cuando primero se registra una transacción de pago. Estos casos **no** se cuentan como retenidos: - El suscriptor acepta una oferta del mensaje y luego cancela antes del primer pago. - El siguiente período del suscriptor es gratuito, por ejemplo, debido a una oferta promocional gratuita. - El suscriptor cancela, reactiva la renovación automática y luego paga. Como la cancelación llegó primero, el intento se contabiliza como churn. - La suscripción vence y el suscriptor compra una nueva suscripción más tarde. - Apple realizó el cargo antes de que el suscriptor viera el mensaje. - Apple reembolsó el pago. **Los ingresos guardados** incluyen solo los pagos de los intentos retenidos. Si un mensaje posterior retiene al mismo suscriptor, Adapty atribuye cada pago al último mensaje que el suscriptor vio antes de ese pago. Los pagos de los miembros de Compartir en familia cuentan para los **Usuarios retenidos**, pero no para los **Ingresos guardados**. ## Solución de problemas \{#troubleshooting\} ### Los números no cuadran \{#the-numbers-dont-add-up\} Adapty no puede ver qué ocurre en la pantalla de cancelación de suscripción. Utiliza los eventos de suscripción para determinar si el usuario llegó a cancelar o no. - Si la suscripción fue cancelada o expiró, el usuario se contabiliza como baja. - Si la suscripción se renovó, el usuario se contabiliza como retenido. - Las personas que cerraron la pantalla de cancelación sin cancelar ni renovar la suscripción no contribuyen a la tasa de retención. Si un usuario cancela y luego vuelve —por ejemplo, porque le enviaste un código promocional—, el usuario **no** cuenta como retenido. :::note Las cancelaciones de pruebas también están sujetas a los mensajes de retención. Una prueba cuenta como "retenida" si el usuario no cancela la prueba y se convierte en suscriptor de pago. ::: ### El número de usuarios guardados es mayor que el de usuarios retenidos \{#saved-users-is-higher-than-retained-users\} **Saved users** cuenta a todos los suscriptores que mantuvieron la renovación automática activada durante 24 horas. **Retained users** cuenta solo a los suscriptores que han pagado el siguiente período. Un suscriptor guardado cuya próxima fecha de facturación aún no ha llegado no se considera retenido y puede cancelar en cualquier momento. ### Un mensaje predeterminado muestra menos visualizaciones que el mensaje promocional \{#a-default-message-shows-fewer-views-than-the-promo-message\} El mismo producto puede tener tanto un mensaje promocional como un mensaje predeterminado (fallback). El mensaje predeterminado siempre tendrá menos visualizaciones. --- # 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. ::: Impulsa 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, copy y estructura del paywall - Períodos de prueba y duraciones de suscripción - Diseños de onboarding :::tip También puedes crear, iniciar y detener pruebas A/B desde el terminal con la [CLI de Adapty](developer-cli-ab-tests), o desde tu herramienta de codificación con IA con el [skill ab-tests](ab-tests-skill). ::: ## 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 & Paywall Builder](adapty-flow-builder) ni el [antiguo Adapty Paywall Builder](adapty-paywall-builder), [envía las vistas del paywall a Adapty](present-remote-config-paywalls#track-paywall-view-events) con `.logShowFlow()` (SDK iOS v4+) / `.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. ::: ## Tipos de pruebas A/B \{#ab-test-types\} Adapty admite dos tipos principales de pruebas A/B: - **Regular**: Se ejecuta en un único placement de flow/paywall/onboarding. - **Crossplacement**: Se ejecuta en múltiples placements de flow o 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 pruebas 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 entre placements:** Una prueba A/B creada para múltiples placements de flow o 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 van a probar.</InlineTooltip>, muestra esa variante de forma consistente en todas las secciones seleccionadas de tu app. Cada flow/paywall/onboarding recibe un peso que divide el tráfico durante la prueba. Por ejemplo, con pesos de 70 % y 30 %, el primer paywall se muestra a aproximadamente 700 de 1.000 usuarios y el segundo a unos 300. En las pruebas Crossplacement, los pesos se establecen por variante, no por flow ni por paywall. Esta configuración te permite comparar diferentes flows y paywalls, y tomar decisiones basadas en datos para la estrategia de monetización de tu app. :::tip También puedes crear, iniciar y detener pruebas A/B desde la terminal con el [Adapty CLI](developer-cli-ab-tests), o desde tu herramienta de IA con el [skill ab-tests](ab-tests-skill). ::: ## 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 app. - Quieres ejecutar la 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 con usuarios antiguos (aquellos que ya han visto al menos un paywall de Adapty). - **Prueba A/B multiplacement**: - Quieres sincronizar variantes entre varios placements. Por ejemplo, puedes cambiar los precios en el flow de onboarding y en los ajustes de la app al mismo tiempo. - Quieres evaluar la economía global de tu app. Ejecutar la prueba en todos los placements facilita el análisis de las estadísticas de la prueba A/B en comparación con probar placements de forma aislada. - Quieres ejecutar una prueba A/B solo con usuarios nuevos, es decir, usuarios que nunca han visto ningún paywall de Adapty. - Quieres usar varios flows o 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\} | Feature | Regular A/B Test | Crossplacement A/B Test | | ------------------------------- |--------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------| | **What is being tested** | Un flow/paywall/onboarding | Conjunto de flows o paywalls pertenecientes a una variante | | **Variant consistency** | La variante se determina por separado para cada placement | La misma variante se usa en todos los placements de la prueba | | **Audience targeting** | Definida por placement de flow/paywall/onboarding | Compartida entre todos los placements de la prueba | | **Analytics** | Analizas un placement de flow/paywall/onboarding | Analizas toda la app en los placements que forman parte de la prueba | | **Variant weight distribution** | Por flow/paywall/onboarding | Por conjunto de flows o paywalls | | **Users** | Para todos los usuarios | Solo usuarios nuevos (los que no han visto un paywall de Adapty) | | **Adapty SDK version** | 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) | 4.0.0+ para flows, 3.5.0+ para paywalls | | **Best for** | 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 multiplacement tienen prioridad sobre las pruebas A/B regulares.** Sin embargo, las pruebas multiplacement solo se muestran a **nuevos usuarios** — aquellos que aún no han visto ningún flow ni paywall de Adapty (la app nunca ha llamado a `getFlow` ni a `getPaywall` para ellos). Esto garantiza la coherencia de los resultados entre placements. El siguiente diagrama muestra la lógica que usa Adapty para seleccionar una prueba A/B para un placement: <img src="/assets/shared/img/ab-tests-scheme.webp" alt="Diagrama que muestra la lógica de selección de pruebas A/B para un placement de paywall" 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 flow, paywall y onboarding aparecen en pestañas separadas. Las pruebas entre placements tienen dos pestañas propias: **Crossplacement (Flows)** y **Crossplacement (Paywalls)**. <img src="ab-tests-tabs.webp" alt="Página de lista de pruebas A/B con pestañas para pruebas de Flow, Paywall, Crossplacement (Flows) y Crossplacement (Paywalls)" 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 crossplacement \{#crossplacement-ab-test-limitations\} :::warning Las pruebas A/B crossplacement no pueden incluir placements de onboarding. Una misma prueba no puede combinar placements de flow y paywall. ::: Las pruebas A/B crossplacement garantizan que cada usuario vea la misma variante en todos los placements de la prueba. Esto genera las siguientes limitaciones: * Solo pueden participar usuarios nuevos. Un usuario nuevo es aquel que no ha visto ningún flow ni paywall de Adapty y cuya app nunca ha llamado a `getFlow` ni a `getPaywall`. Adapty no puede garantizar una cadena consistente de flows o paywalls para el resto de usuarios. * El primer placement que encuentra el usuario determina qué flow o paywall muestra Adapty. No puedes cambiar la asignación de un usuario ni inscribir al mismo usuario en más de una prueba A/B entre placements. * Un dispositivo se entera de las asignaciones entre placements realizadas en otro lugar —por ejemplo, en otro dispositivo del mismo usuario— solo cuando la app pasa a primer plano, y como máximo una vez cada 60 segundos. Esta sincronización no se puede forzar, por lo que una asignación puede tardar hasta un minuto, o hasta el siguiente paso a primer plano, en llegar a una sesión activa prolongada. :::warning Una vez que un usuario recibe un flow o paywall de Crossplacement, lo verá durante 90 días, incluso después de que detengas 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 una prueba, ejecutarla y detenerla cuando estés listo para revisar los resultados. :::tip También puedes crear, iniciar y detener pruebas A/B desde el terminal con la [CLI de Adapty](developer-cli-ab-tests), o desde tu herramienta de codificación con IA con el [skill ab-tests](ab-tests-skill). ::: ## 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 utilizas el [Adapty Flow & Paywall Builder](adapty-flow-builder) ni el [antiguo 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. ::: :::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. En cuanto añades un segundo placement, la prueba se convierte en una prueba A/B Crossplacement. Todos los placements de una prueba Crossplacement deben ser del mismo tipo: flow o paywall. Cada variante también puede tener un nombre, que aparece en la lista de pruebas A/B y en las métricas de la prueba. Introdúcelo en la primera columna de la fila de la variante. Si dejas el campo vacío, Adapty etiqueta esa variante por posición: `Variant A`, `Variant B`, etc. Para leer el nombre de la variante en tu app, usa la propiedad `variationName` del flow obtenido (SDK v4.2+). Para el código de obtención, consulta la guía de obtención de flows para [iOS](get-pb-paywalls#fetch-flowpaywall), [Android](android-get-pb-paywalls#fetch-flowpaywall) o [React Native](react-native-get-pb-paywalls#fetch-flowpaywall). Para la propiedad en sí, consulta `AdaptyFlow` en la referencia del SDK para [iOS](https://swift.adapty.io/documentation/adapty/adaptyflow/variationname), [Android](https://android.adapty.io/adapty/com.adapty.models/-adapty-flow/) o [React Native](https://react-native.adapty.io/interfaces/adaptyflow#variationname). Una variante sin nombre no tiene valor en `variationName`: la etiqueta `Variant A` solo existe en el Adapty Dashboard. Un nombre puede tener hasta 255 caracteres, y dos variantes en una misma prueba no pueden compartir el mismo. No puedes cambiar el nombre de una variante una vez que la prueba ha comenzado. Para renombrarla en una prueba en curso, usa **Modify**, descrito en [Editar una prueba A/B](#edit-an-ab-test). <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 [A/B tests](ab-tests) desde el menú principal de Adapty. 2. Asegúrate de estar viendo la lista correcta: cada tipo de prueba tiene su propia pestaña: **Flow**, **Paywall**, **Onboarding**, **Crossplacement (Flows)** y **Crossplacement (Paywalls)**. 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 flows/paywalls/onboardings probados | Elige el flow, paywall u onboarding ganador en función de los resultados del test, como ingresos, probabilidad de ser el mejor (**P2BB**) e ingresos por cada 1.000 usuarios. Se mostrará para el placement y la audiencia seleccionados. En una prueba entre placements, eliges un ganador para cada placement por separado. | | Seleccionar flows/paywalls/onboardings que no participan en la prueba A/B | Elige cualquier flow, paywall u onboarding que no forme parte de la prueba A/B actual. Úsalo cuando ninguna de las variantes probadas haya cumplido tus objetivos. | | No mostrar ningún flow/paywall/onboarding específico | Para el placement y la audiencia seleccionados, no se elegirá ningún flow, paywall u onboarding específico al finalizar la prueba A/B. En su lugar, se mostrará el siguiente disponible según la prioridad de audiencia. Es una buena opción si prefieres que tu configuración actual decida qué mostrar, sin tener que 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 flows ni paywalls" description: "Ejecuta una prueba A/B en la que una variante omite el flow o el paywall, usando una marca en el Remote Config para controlar si se muestra." --- Puedes medir el impacto de tu flow o paywall ejecutando una prueba A/B contra una variante vacía. Una variante muestra tu flow/paywall; la otra no muestra nada. Tu app lee una marca del Remote Config para decidir si renderizar o no. :::tip También puedes crear, iniciar y detener pruebas A/B desde el terminal con la [CLI de Adapty](developer-cli-ab-tests), o desde tu herramienta de código con IA con el [skill ab-tests](ab-tests-skill). ::: ## 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. Ve a [**Flows & Paywalls**](https://app.adapty.io/flows) en el menú principal de Adapty. Abre la pestaña **Flow & Paywall builder** para un flow o la pestaña **Custom Paywalls** para un paywall personalizado, 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 tiene este aspecto: ```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 [Personaliza el flow con Remote Config](customize-flow-with-remote-config) o [Diseña un paywall personalizado con Remote Config](customize-paywall-with-remote-config). :::tip Configurar `show_paywall` en ambas variantes mantiene el flujo de código idéntico para ambos grupos y facilita la extensión de 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 que Adapty lo renderice), 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 podrá 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 de tu app y la participación de los usuarios." --- 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 utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo 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 de la prueba A/B seleccionada, lo que te permite analizar el rendimiento de las variantes individuales. Cada fila de variante está etiquetada con el nombre que le diste a la variante cuando [configuraste la prueba](run_stop_ab_tests#create-an-ab-test). 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. ### Vistas \{#views\} Las vistas son el número de veces que se han mostrado los paywalls u onboardings que componen la prueba A/B. Si el usuario los visita dos veces, se contarán como dos visitas. Adapty registra una vista únicamente cuando tu app muestra una variante: al llamar a `logShowFlow` o `logShowPaywall`, o cuando aparece en pantalla un flow del Flow & Paywall Builder. Obtener un flow o paywall asigna al usuario a una variante, pero no cuenta como vista. Las ponderaciones de las pruebas A/B dividen a los usuarios entre variantes, no las vistas. Si tu app no muestra una variante cada vez que la obtiene, esa variante recibe menos vistas de las que su ponderación indica, y sus tasas de conversión basadas en vistas también cambian. ### 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. ### Usuarios \{#users\} Solo para pruebas A/B de paywalls y flows, regulares y entre placements. Los usuarios son los perfiles únicos que Adapty asignó a la variante en el período seleccionado. Un perfil se cuenta una sola vez, independientemente de cuántas veces la app solicite el placement. En una prueba A/B entre placements, un perfil se cuenta una sola vez para toda la prueba, aunque el usuario llegue a varios de sus placements. Para comprobar si el tráfico se divide entre variantes según los pesos que estableciste, compara los Usuarios de cada variante. ### Instalaciones \{#installs\} Solo para pruebas A/B de paywalls y flows, tanto regulares como entre placements. Las instalaciones son los usuarios de la variante que instalaron la app después de que comenzó la prueba. Los perfiles que instalaron la app antes de que comenzara la prueba no se contabilizan, por lo que las Instalaciones no pueden superar a los Usuarios. Para comparar qué tan bien convierten los nuevos usuarios cada variante, usa las Instalaciones como base de la conversión desde instalación hasta compra. :::note Los usuarios e instalaciones aparecen en la lista de pruebas A/B y en la página de métricas de las pruebas A/B, tanto en la tabla como en el gráfico. Las filas de pantallas y productos muestran un guion, porque Adapty asigna perfiles a variantes, no a pantallas o productos. Estas columnas no están disponibles en las páginas de placement ni en la exportación CSV. ::: ### 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. :::tip También puedes crear, iniciar y detener pruebas A/B desde la terminal con la [CLI de Adapty](developer-cli-ab-tests), o desde tu herramienta de codificación con IA con el [skill ab-tests](ab-tests-skill). ::: ## 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 --- --- title: "AI Growth Advisor" description: "Optimiza tus paywalls para generar el máximo de ingresos." --- Growth Advisor es una herramienta de crecimiento basada en IA para apps de suscripción. Elabora un plan a largo plazo para aumentar tus ingresos mediante pruebas A/B, respaldado por datos de mercado de más de 20.000 apps rastreadas por Adapty. ## Requisitos previos \{#prerequisites\} - **Acceso a App Store Connect** — AI Growth Advisor compara contra datos de App Store. [Sube tus credenciales de App Store Connect](set-up-app-store-connect) antes de iniciar tu primer análisis. - **Al menos un producto de App Store con precio en el paywall seleccionado** — los paywalls configurados solo para Google Play no pueden analizarse. Añade un producto de App Store en la sección **Products** antes de empezar. - **Placement del paywall** — AI Growth Advisor todavía no puede analizar placements de tipo [flow](adapty-flow-builder). - Nota: AI Growth Advisor funciona para todas las apps, pero cuantos más datos financieros pueda analizar Adapty, más fiables serán las recomendaciones. ## Cómo funciona el AI Growth Advisor \{#how-ai-growth-advisor-works\} :::link El artículo [AI Growth Advisor: Cómo funciona](autopilot-how-it-works) explica cómo Adapty genera sus recomendaciones. ::: 1. AI Growth Advisor [analiza el contenido y diseño de tu paywall](autopilot-analysis#paywall-analysis), destaca los elementos que funcionan y detecta **oportunidades de mejora en la conversión**. - Las recomendaciones comparan tu diseño con patrones de las apps de mayor rendimiento en tu categoría. - El análisis se centra únicamente en productos de suscripción. Las compras in-app consumibles no están incluidas. 2. En tus principales mercados, AI Growth Advisor [recomienda ajustes de precio por país](autopilot-growth-plan#geo-pricing-hypotheses) y [compara tu conversión con la de apps similares](autopilot-analysis#market-and-competitor-analysis). - Los benchmarks de conversión se basan en datos anonimizados de la red de 20.000 apps de Adapty. - Las recomendaciones de precios se basan en el [Adapty Pricing Index](https://uploads.adapty.io/adapty_pricing_index.pdf), un benchmark por país de lo que los suscriptores realmente pagan. 3. Una vez completado el análisis, AI Growth Advisor presenta un [plan de crecimiento accionable](autopilot-growth-plan) para el placement seleccionado. Cada hipótesis es una prueba A/B para probar un nuevo enfoque. A medida que cambian las condiciones del mercado, puedes volver a ejecutar el análisis y ampliar tu plan con nuevas hipótesis. 4. [Ejecuta las pruebas sugeridas](autopilot-execute-plan) una a una. Según nuestras estimaciones, ejecutar el conjunto completo de pruebas recomendadas podría aumentar tus ingresos **hasta un 80%**. ## Ejecutar el análisis \{#run-the-analysis\} :::link Artículo principal: [Análisis de paywall y mercado](autopilot-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. [Selecciona un Placement y un Paywall](autopilot-analysis#select-a-paywall-for-analysis). Confirma que los productos mostrados son correctos. Sube una captura de pantalla y haz clic en **Confirm & Analyze**. 3. Adapty [analiza](autopilot-analysis#paywall-analysis) el diseño, el contenido y las métricas de ingresos de tu paywall. Revisa el informe para saber qué funciona y qué necesita mejorar. 4. [Selecciona hasta 5 competidores](autopilot-analysis#select-competitors) para una comparación en profundidad. Haz clic en **Generate report**. 5. Lee el informe de [información de mercado](autopilot-analysis#market-and-competitor-analysis). Compara el rendimiento de tu paywall con el de la competencia y con los benchmarks de mercado por país. Puedes cambiar entre distintos países para ajustar el contexto del análisis. * Distribución geográfica de ingresos * Precios de la competencia (por país) * Benchmarks del embudo de conversión (por país, media del mercado) * Distribución de ingresos por duración de suscripción (por país, media del mercado) * ARPU de activación (por país, media del mercado) ## Gestiona tu plan de crecimiento \{#manage-your-growth-plan\} :::link Artículo principal: [Gestiona tu plan de crecimiento](autopilot-growth-plan) ::: 6. Abre el [plan de crecimiento](autopilot-growth-plan) y revisa las hipótesis basadas en datos de Adapty. Cada una es una prueba A/B que apunta a un único cambio de precio o diseño. Añade tus propias hipótesis para ampliar el plan. 7. Para mantener tu plan de crecimiento relevante, [vuelve a ejecutar el análisis](autopilot-growth-plan#get-new-ai-generated-hypotheses) con regularidad. Cada nueva ejecución incorpora datos de mercado actualizados y añade nuevas hipótesis sobre tu plan existente. ## Ejecuta tu plan de crecimiento \{#execute-your-growth-plan\} :::link Artículo principal: [Ejecuta tu plan de crecimiento](autopilot-execute-plan) ::: 8. [Ejecuta las pruebas](autopilot-execute-plan) una por una. Crea nuevos productos, duplica tu paywall con los cambios sugeridos y lanza la prueba A/B. La configuración ganadora participará en la siguiente prueba. --- # 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 una estadística concreta (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 diseño y otros ajustes visuales. Estas recomendaciones se incorporan directamente a tu [plan de crecimiento](autopilot-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 basadas en datos de las apps con mejor rendimiento en tu categoría. Cada recomendación incluye una estadística de referencia (por ejemplo, "Utilizado por el 72% de las apps de Educación con mejor rendimiento") y una descripción del cambio sugerido. - **Recomendaciones de análisis visual**: Sugerencias generadas por IA a partir de una 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) incluirá hipótesis basadas en las recomendaciones de benchmarks. Puedes añadir las sugerencias del 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. ## 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: analytics --- --- title: "Analytics" description: "Capacidades de Adapty Analytics" --- Adapty Analytics es una completa suite de herramientas para la visualización y el análisis de datos. Diseñada para equipos de desarrollo de aplicaciones, se centra en el rendimiento financiero de tu app y en el comportamiento de los usuarios. Además de las métricas estándar disponibles en el análisis de tu store (como ingresos o instalaciones), Adapty calcula métricas compuestas: valor del ciclo de vida del usuario, análisis de cohortes y más. Secciones dedicadas analizan el comportamiento de los usuarios que provoca pérdida de ingresos: gráficos de retención, datos de resolución de problemas de facturación. Explora los gráficos para obtener información sobre el comportamiento de los usuarios y optimiza tu aplicación con decisiones basadas en datos. Consulta la guía [Cómo funciona Adapty Analytics](how-adapty-analytics-works) para obtener más información sobre cómo se obtienen y procesan los datos. Para una referencia rápida de todas las métricas disponibles y sus diferencias, consulta el artículo [Tabla de comparación de métricas](metric-comparison-table). :::tip Los mismos números están disponibles desde el terminal a través de los [comandos CLI de Analytics](developer-cli-analytics). Úsalos para dar a una herramienta de codificación con IA acceso en tiempo real a tus gráficos, conversiones, embudo, retención, LTV y cohortes, para que pueda responder preguntas como "¿Qué país impulsó el crecimiento del MRR este trimestre?" ::: :::tip Habilita [Adapty Attribution](adapty-user-acquisition) para ampliar las capacidades analíticas de Adapty. Adapty Attribution utiliza datos de marketing para analizar la relación entre las campañas publicitarias y el comportamiento de los usuarios. Sus gráficos muestran un panorama completo de la economía de tu aplicación, con cálculos de ROI y datos de atribución precisos. ::: ## Resumen \{#overview\} :::link Artículo principal: [Página de Resumen de Analytics](overview) ::: La página de Resumen presenta un vistazo rápido de las métricas de rendimiento clave de tus aplicaciones. Para personalizar los gráficos mostrados, haz clic en **Edit Metrics**. ## Gráficos \{#charts\} :::link Artículo principal: [Gráficos de Analytics](charts) ::: La pestaña de Gráficos visualiza métricas relacionadas con ingresos, suscripciones, pruebas y problemas de facturación. ## LTV \{#ltv\} :::link Artículo principal: [Valor de Vida (LTV)](ltv) ::: La pestaña de LTV muestra un gráfico del valor de vida de tus usuarios a lo largo del tiempo. ## Cohortes \{#cohorts\} :::link Artículo principal: [Análisis de Cohortes](analytics-cohorts) ::: La pestaña de Cohortes te permite aislar grupos de usuarios según la fecha de instalación y muestra las tasas de conversión de cada grupo. ## Embudos \{#funnels\} :::link Artículo principal: [Embudos](analytics-funnels) ::: La pestaña de Embudos visualiza las etapas de interacción de los usuarios con tu app. Puedes ver qué proporción de instalaciones se convierten en suscripciones y durante cuánto tiempo permanecen suscritos los usuarios. Identifica los puntos de abandono más comunes para optimizar tu estrategia. ## Retención \{#retention\} :::link Artículo principal: [Retención](analytics-retention) ::: La pestaña de Retención muestra un gráfico de la retención de usuarios a lo largo del tiempo. ## Conversión \{#conversion\} :::link Artículo principal: [Conversión](analytics-conversion) ::: La pestaña de Conversión muestra gráficos de las tasas de conversión en distintas etapas del recorrido del usuario. Destaca las decisiones más habituales de los usuarios en los puntos de transición, como de prueba a suscripción o de problema de facturación a renovación exitosa. ## Analítica predictiva \{#predictive-analytics\} :::link Artículo principal: [LTV e Ingresos Predichos](predicted-ltv-and-revenue) ::: Los usuarios de los planes de pago pueden consultar predicciones de LTV (valor de vida) e ingresos por cohorte. Adapty analiza datos históricos con algoritmos avanzados de aprendizaje automático para estimar valores futuros. ## Controles de filtros y agrupación \{#filters-and-grouping-controls\} :::link Artículo principal: [Controles](controls-filters-grouping-compare-proceeds) ::: Usa los controles de filtros y agrupación para ver datos de segmentos de usuarios específicos. Por ejemplo, puedes filtrar el gráfico de retención por store y comparar el comportamiento de usuarios entre Android e iOS. Desde cualquier vista de Adapty Analytics, puedes: * Cambiar el intervalo de fechas para la visualización de datos. * Filtrar datos por store, atribución, [flow](filter-flows#filter-analytics-by-flow) u otros parámetros. * Agrupar datos dentro del gráfico por país, [segmento de usuario](segments), flow u otros criterios. [Los gráficos](charts) también permiten comparar la misma métrica en dos períodos de tiempo diferentes. ## Recibe informes de analíticas automáticos \{#receive-automated-analytics-reports\} :::link Artículo principal: [Informes](reports) ::: Adapty puede enviarte informes automáticos con datos de analíticas directamente a tu bandeja de entrada. Puedes configurar el contenido y la frecuencia de estos informes. :::tip Para volcar los mismos datos en una hoja de cálculo o un script, añade `--csv` a cualquier [comando de Analytics CLI](developer-cli-analytics). Por ejemplo, `adapty analytics chart --app <app-id> --chart revenue --date-from 2026-07-01 --date-to 2026-09-30 --group-by country --csv` devuelve una fila por país, con el total primero. ::: ## Ver también * [Atribución de Adapty](adapty-user-acquisition) * [Exportar análisis con API](export-analytics-api) * [Consultar análisis desde la CLI](developer-cli-analytics) * [Integraciones de terceros](configuration) --- # 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. Estos retrasos afectan la rapidez con la que aparece un evento, no cuánto tiempo sigue cambiando un gráfico después. La mayoría de los gráficos fechan un evento en el día en que ocurrió, por lo que el total de ese día es definitivo en cuanto llega el evento. Las métricas basadas en cohortes funcionan de manera distinta: las [tasas de conversión](analytics-conversion), las [cohortes](analytics-cohorts) y la [retención](analytics-retention) agrupan a los usuarios según una acción que realizaron, y luego siguen midiendo lo que esos usuarios hacen después. La métrica de una fecha pasada no deja de crecer durante semanas o meses. [Por qué cambian los valores pasados](analytics-conversion#why-past-values-change) explica qué significa esto para los informes. ## Comisiones e impuestos \{#commissions-and-taxes\} Los gráficos de ingresos tienen un desplegable que define cómo Adapty calcula los ingresos: - **Gross revenue** (predeterminado): Lo que pagaron los usuarios. - **Proceeds after store commission**: Lo que pagaron los usuarios, menos la [comisión del store](#commissions). - **Proceeds after store commission and taxes** (ingresos netos): Lo que pagaron los usuarios, menos la comisión del store y los [impuestos](#taxes). ### Comisiones \{#commissions\} Los stores deducen una comisión de cada transacción. Adapty aplica la tasa de comisión que corresponde al store, el país del store, la fecha de compra y la antigüedad de la suscripción. Adapty no puede detectar la pertenencia a un programa de comisión reducida. Si tu organización está inscrita en uno, añade el período de membresía en tu configuración de Adapty: - [App Store Small Business Program](app-store-small-business-program) - [Programa de tarifa de servicio reducida](google-reduced-service-fee) de Google #### Tasas de comisión del App Store \{#app-store-commission-rates\} Adapty aplica estas tarifas a las transacciones de App Store. La tarifa reducida se aplica a los miembros del Small Business Program y a las [suscripciones de renovación automática tras su primer año](https://developer.apple.com/app-store/subscriptions/) de servicio de pago. | Storefront | Tarifa estándar | Tarifa reducida | Aplica a transacciones desde | | --- | --- | --- | --- | | [Unión Europea](https://developer.apple.com/support/payment-options-on-the-app-store-in-the-eu#commissions) (27 estados miembros) | 26% | 15% | 1 de octubre de 2026 | | [China continental](https://developer.apple.com/news/?id=dadukodv) | 25% | 12% | 15 de marzo de 2026 | | [Japón](https://developer.apple.com/support/app-distribution-in-japan/#business-terms) | 26% | 15% | 18 de diciembre de 2025 | | [Brasil](https://developer.apple.com/support/app-distribution-in-brazil/) | 26% | 15% | 6 de julio de 2026 | | Todos los demás storefronts | 30% | 15% | — | - Cada nueva tarifa se aplica únicamente a las compras realizadas en su fecha o después de ella según la tabla. Las compras anteriores a esa fecha mantienen la tarifa previa. Por ejemplo, una compra en Alemania el 30 de septiembre de 2026 tiene la tarifa estándar del 30%, no del 26%. - Una actualización de tarifa afecta de forma distinta a los análisis y a las integraciones: - Adapty recalcula los ingresos en los análisis para todas las compras a partir de la fecha indicada en la tabla, incluidas las compras pasadas. - Los eventos que Adapty ya envió a integraciones y webhooks conservan sus ingresos originales. - Las tarifas de la Unión Europea no se aplican a Noruega, Islandia ni Liechtenstein. - Para Japón y Brasil, Apple divide cada tarifa en una comisión y una tarifa de procesamiento de pagos del 5%. Adapty deduce ambas, por lo que la tabla muestra su suma. - Adapty no tiene información sobre la membresía en el Mini Apps Partner Program ni en el Video Partner Program, y aplica la tarifa estándar a estas transacciones. Adapty selecciona la tasa según el país de la store: el país del App Store donde se realizó la compra. Este es el país de la cuenta de Apple del comprador, donde este realiza el pago. La ubicación del usuario y la dirección IP no afectan a la tasa. Por ejemplo, un usuario con una cuenta de Apple de EE. UU. que se suscribe estando en Alemania realiza la compra en el App Store de EE. UU., por lo que Adapty aplica la tasa del 30%, no la tasa del 26% de la Unión Europea. :::note En ocasiones, Adapty no conoce el país de la store de un usuario. Para estas compras, los análisis y las integraciones utilizan distintos valores de respaldo: - Los gráficos, las pruebas A/B y los onboardings usan el país de la dirección IP del usuario, o la moneda de la compra si el país por IP también es desconocido. Es el mismo país que los gráficos usan para el [filtro de país](controls-filters-grouping-compare-proceeds#how-country-is-determined). - Las integraciones y webhooks usan el país de App Store que Apple incluye en la transacción. Por este motivo, los ingresos brutos y netos de una misma compra pueden diferir entre los análisis y una integración. Esto afecta a una proporción muy pequeña de compras. ::: ### Impuestos \{#taxes\} Para calcular **Proceeds after store commission and taxes**, Adapty deduce los impuestos de las transacciones. El importe depende del store y del país: - **App Store y Google Play**: Adapty [estima el impuesto por su cuenta](#how-adapty-estimates-the-tax). - Adapty aplica una tasa impositiva por país, la misma para cada app y categoría fiscal. - Si un store aplica una tasa diferente en el pago, los ingresos netos en Adapty diferirán de los informes en App Store Connect o Google Play Console. - **Stripe y Paddle**: El procesador de pagos informa el importe del impuesto de antemano. Adapty lo deduce de cada transacción, excepto las [compras únicas a través de Stripe](stripe#net-revenue-and-tax). #### Cómo Adapty estima el impuesto \{#how-adapty-estimates-the-tax\} Adapty tiene su propia suscripción de prueba en el App Store y Google Play. Cada día, Adapty descarga los precios de esa suscripción en todos los países: - **App Store**: Apple indica el precio que pagan los clientes y los ingresos que reciben los desarrolladores. Adapty conoce la comisión, por lo que calcula el impuesto a partir de la diferencia. - **Google Play**: Google indica el importe del impuesto junto a cada precio. Cuando cambia un tipo impositivo, el nuevo tipo se aplica a las compras realizadas a partir de ese día. Para conocer las normas fiscales oficiales, consulta la documentación del App Store y Google Play: - [App Store: Entendiendo los impuestos](https://developer.apple.com/help/app-store-connect/making-payments-to-apple/understanding-taxes/) - [Google Play: Tasas impositivas e IVA](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 analíticos, consulta [Cómo funciona el análisis de Adapty](how-adapty-analytics-works). Ten en cuenta que algunas métricas funcionan de forma diferente para los [planes de compromiso de 12 meses](app-store-12-month-commitment#analytics). :::note Este artículo no cubre las métricas de [Adapty Attribution](adapty-user-acquisition). Lee [Análisis de Adapty Attribution](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 | <InlineTooltip tooltip="¿Basada en cohortes?">Una métrica basada en cohortes agrupa a los usuarios por un evento anterior (una instalación, una visualización de paywall, un inicio de prueba) y sigue contando lo que esos usuarios hacen después, por lo que su valor para una fecha pasada sigue aumentando. El resto de métricas cuenta cada evento en el día en que ocurrió.</InlineTooltip> | |--------|-------------|----------------|:---:| | [Ingresos](revenue) | Ingresos totales de suscripciones y compras únicas, menos reembolsos | Ingresos reales generados. Puede mostrar ingresos brutos, ingresos después de la comisión, o ingresos después de impuestos y comisión según los [controles del gráfico](controls-filters-grouping-compare-proceeds) | No | | [MRR](mrr) | Ingresos recurrentes mensuales de suscripciones activas | Ingresos mensuales predecibles de tu app. Excluye compras únicas y suscripciones no recurrentes. | No | | [ARR](arr) | Ingresos recurrentes anuales de suscripciones activas | Se calcula como el MRR pero en escala anual. Útil para proyectar los ingresos anuales | No | | [ARPU](arpu) | Ingresos medios por usuario | Divide los ingresos entre el número total de usuarios, tanto de pago como gratuitos. Muestra cuántos ingresos genera cada usuario de media | No | | [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 superior al ARPU | No | | [LTV (valor de vida del cliente)](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 de calendario | Sí | | [LTV predicho](predicted-ltv-and-revenue) | Valor de vida estimado 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 la cohorte. Disponible para 3, 6, 9, 12, 18 y 24 meses | Sí | | [Ingresos predichos](predicted-ltv-and-revenue) | Ingresos totales estimados que generará una cohorte | Estimación prospectiva. A diferencia de los Ingresos realizados, predice el total que generará una cohorte durante el período de tiempo seleccionado. Se actualiza diariamente | Sí | | [Compras no recurrentes](non-subscriptions) | Recuento de compras in-app: consumibles, no consumibles y suscripciones no renovables | Excluye las suscripciones de renovación automática. | No | | [Eventos de reembolso](refund-events) | Recuento de compras o suscripciones reembolsadas | Se atribuyen a la fecha del reembolso, no a la fecha de compra original. | No | | [Dinero reembolsado](refund-money) | Importe total reembolsado durante el período seleccionado | Impacto económico de los reembolsos. Se calcula antes de las comisiones del store. A diferencia de los Eventos de reembolso (un recuento), aquí se muestra el importe monetario | No | ### 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 | <InlineTooltip tooltip="¿Basada en cohortes?">Una métrica basada en cohortes agrupa a los usuarios por un evento anterior —una instalación, una vista de paywall, el inicio de un período de prueba— y continúa contando lo que esos usuarios hacen después, por lo que su valor para una fecha pasada sigue aumentando. El resto de métricas cuentan cada evento en el día en que ocurrió.</InlineTooltip> | |--------|-------------|----------------|:---:| | [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 por dispositivo (un usuario que reinstala la app se cuenta de nuevo) <br /> • Usuarios únicos (solo cuenta usuarios que hayan definido `customer_user_id`. Los usuarios anónimos se excluyen por completo — si no hay usuarios identificados, el recuento es 0) | No | | [Nuevos períodos de prueba](new-trials) | Períodos de prueba activados durante el período | Cuenta cada inicio de prueba, aunque el período ya haya expirado o se haya convertido a pago en el momento de ver el gráfico | No | | [Períodos de prueba activos](active-trials) | Número de pruebas que aún no han expirado | Solo cuenta las pruebas activas al final del período | No | | [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 es lo mismo que el evento de integración `subscription_started`, que solo cuenta las primeras compras sin prueba — las conversiones de prueba disparan `trial_converted` en su lugar | No | | [Suscripciones activas](active-subscriptions) | Número de suscripciones de pago que aún no han expirado | Excluye pruebas y suscripciones con renovación cancelada | No | | [Instalación a prueba](analytics-conversion#install---trial) | Porcentaje de usuarios que instalaron la app y comenzaron un período de prueba | Fechado por instalación. El denominador incluye a todos los que instalaron, no solo a quienes vieron el paywall, por lo que la tasa puede ser inferior a Vista de paywall a prueba. Las dos métricas también pueden divergir si la app no registra las vistas de paywall. Esto puede ocurrir con un paywall personalizado que no llame a `logShowFlow` (iOS SDK 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). | Sí | | [Vista de paywall a prueba](analytics-conversion#paywall-view---trial) | Porcentaje de usuarios que vieron el paywall y comenzaron un período de prueba | Fechado por vista de paywall. Solo cuenta usuarios que vieron un paywall, por lo que la tasa puede ser superior a Instalación a prueba | Sí | | [Prueba a pago](analytics-conversion#trial---paid) | Porcentaje de usuarios en prueba que compraron una suscripción | Fechado por inicio de prueba, por lo que la misma prueba cae en una fecha diferente a la de Instalación a prueba. Mide la calidad de la prueba y la eficiencia de conversión. A diferencia de Instalación a pago, se centra solo en los usuarios que completaron una prueba | Sí | | [Instalación a pago](analytics-conversion#install---paid) | Porcentaje de usuarios que instalaron la app y compraron una primera suscripción | Fechado por instalación. Cuenta a todos los que instalaron, no solo a quienes vieron el paywall. La tasa puede ser inferior a Vista de paywall a pago. Incluye tanto compras directas como conversiones de prueba a pago | Sí | | [Vista de paywall a pago](analytics-conversion#paywall-view---paid) | Porcentaje de usuarios que vieron el paywall y acabaron comprando una suscripción | Fechado por vista de paywall. 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 | Sí | ### 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. Todas son <InlineTooltip tooltip="basadas en cohortes">Una métrica basada en cohortes agrupa a los usuarios por un evento anterior —una instalación, una visita al paywall, el inicio de una prueba— y sigue registrando lo que hacen esos usuarios después, por lo que su valor para fechas pasadas sigue aumentando. El resto de métricas cuentan cada evento en el día en que ocurrió.</InlineTooltip>, por lo que sus valores para fechas pasadas siguen aumentando a medida que más usuarios de cada cohorte renuevan. | 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. | Realiza el 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 puedes ver el panorama completo de un vistazo | | [Pagado al 2.º período](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 | | [2.º al 3.er período](analytics-conversion#2nd-period---3rd-period) | Porcentaje que renueva del 2.º al 3.er período | Indica la estabilidad de la retención temprana tras la renovación inicial | | [3.er al 4.º período](analytics-conversion#3rd-period---4th-period) | Porcentaje que renueva del 3.er al 4.º período | Indicador de retención a medio plazo | | [4.º al 5.º período](analytics-conversion#4th-period---5th-period) | Porcentaje que renueva del 4.º al 5.º período | Indicador de fidelización a largo plazo | | [6 meses+](analytics-conversion#6-months-) | Porcentaje de suscriptores nuevos que permanecen suscritos durante más de 6 meses | Calculado desde el primer pago, no desde la marca de 6 meses. Mide el tiempo en calendario, no el número de renovaciones — un suscriptor anual cuenta como retenido a los 6 meses aunque no haya renovado | | [1 año+](analytics-conversion#1-year-) | Porcentaje de suscriptores nuevos que permanecen suscritos durante más de 12 meses | Calculado desde el primer pago. Hito de retención anual | | [2 años+](analytics-conversion#2-years-) | Porcentaje de suscriptores nuevos que permanecen suscritos durante más de 24 meses | Calculado desde el primer pago. Hito de retención a largo plazo | ### Cancelaciones \{#churn\} Estas métricas miden cuántos suscriptores y usuarios en período de prueba pierde la app. Ninguna de ellas es <InlineTooltip tooltip="basada en cohortes">Una métrica basada en cohortes agrupa a los usuarios por un evento anterior —una instalación, una visualización de paywall, el inicio de un período de prueba— y sigue registrando lo que esos usuarios hacen después, por lo que su valor para una fecha pasada sigue aumentando. Todas las demás métricas cuentan cada evento el día en que ocurrió.</InlineTooltip>: cada evento se cuenta el día en que ocurrió. | 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 | <InlineTooltip tooltip="¿Basada en cohorte?">Una métrica basada en cohorte agrupa a los usuarios por un evento anterior —una instalación, una vista de paywall, el inicio de una prueba— y sigue registrando lo que hacen esos usuarios después, por lo que su valor para una fecha pasada sigue aumentando. El resto de métricas cuentan cada evento en el día en que ocurrió.</InlineTooltip> | |--------|-------------|----------------|:---:| | [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 | No | | [Período de gracia a pago](analytics-conversion#grace-period---paid) | Porcentaje de usuarios en período de gracia que renovaron antes de que finalizara | Una tasa (%). Fechada por el día en que la suscripción entró en período de gracia. Responde a "¿qué proporción de usuarios en período de gracia se recuperó?" | Sí | | [Período de gracia convertido](grace-period-converted) | Número absoluto de suscripciones en período de gracia que renovaron correctamente | Mismos eventos que Período de gracia a pago, pero fechado por el pago de recuperación y mostrado como recuento en lugar de porcentaje | No | | [Ingresos por período de gracia convertido](grace-period-converted-revenue) | Ingresos procedentes de recuperaciones en período de gracia | Impacto financiero de la función de período de gracia | No | | [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 usuarios que ya han perdido el acceso premium | No | | [Problema de facturación a pago](analytics-conversion#billing-issue---paid) | Porcentaje de usuarios con problema de facturación que renovaron antes de que finalizara el ciclo | Una tasa (%). Fechada por el día en que ocurrió el problema de facturación. Responde a "¿qué proporción de usuarios con problema de facturación se recuperó?" | Sí | | [Problema de facturación convertido](billing-issue-converted) | Número absoluto de suscripciones con problema de facturación que renovaron correctamente | Mismos eventos que Problema de facturación a pago, pero fechado por el pago de recuperación y mostrado como recuento en lugar de porcentaje | No | | [Ingresos por problema de facturación convertido](billing-issue-converted-revenue) | Ingresos procedentes de recuperaciones por problema de facturación | Impacto financiero de la recuperación por problema de facturación | No | ## 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 app en su conjunto. La columna **Associated global metric** muestra la métrica correspondiente en la sección de analíticas globales. Todas ellas son basadas en cohortes únicamente cuando activas **Filter metrics by install date**. Por defecto, cuentan cada evento en el día en que ocurrió. | 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 | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ### Establecer el intervalo de fechas \{#set-the-date-range\} Usa el calendario **Date range** situado encima de cada gráfico para elegir el período de tiempo. Los gráficos siguen la [zona horaria de informes](general#3-reporting-timezone) de tu app; la [página Overview](overview) tiene su propia zona horaria configurable. Las integraciones con terceros siempre usan UTC, independientemente de lo que configures ahí. #### Rangos preestablecidos \{#preset-ranges\} Usa la opción **Custom** para especificar fechas de inicio y fin arbitrarias. Los rangos preestablecidos 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 | | --- | :---: | :---: | --- | | Attribution | ✅ | ✅ | 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 próximo 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 siguientes). Se cuenta por cadena de suscripción, o por producto cuando también filtras por producto o agrupas por producto — así, un usuario que se movió entre dos productos cuenta una vez en el primer caso y una vez por producto en el segundo. | | País | ✅ | ✅ | El país de la store del usuario. Si no está disponible, Adapty usa el país por IP y luego la moneda de compra. Consulta [Cómo se determina el país](#how-country-is-determined). | | Tipo de oferta | ✅ | ✅ | La oferta aplicada a la transacción: <ul><li>**Introductory** — una oferta introductoria en el período inicial de suscripción. Usa **Offer Discount Type** para distinguir entre intros de pago y trials gratuitos.</li><li>**Promotional** — Ofertas promocionales de App Store y equivalentes.</li><li>**Offer Code** — códigos promocionales que el cliente introduce en la 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 trial gratuito introductorio de uno de pago. | | Paywall | ✅ | ✅ | El [paywall](paywalls) usado para la compra. Una pantalla creada en [Flow Builder](adapty-flow-builder) también aparece aquí, listada una vez por pantalla con un nombre como `Checkout flow: 9. Paywall A` — consulta [Cómo se nombran las pantallas de flow](#how-flow-screens-are-named). | | Flow | ✅ | ✅ | El [flow](adapty-flow-builder) del que proviene la compra. Úsalo para comparar flows completos entre sí. | | Pantalla de flow | ✅ | ✅ | La pantalla individual del flow de la que proviene la compra. Úsala para saber qué pantalla dentro de un flow genera los ingresos. En el filtro, las pantallas están anidadas bajo su flow y solo se puede seleccionar una pantalla; al agrupar, cada serie aparece con la etiqueta `Checkout flow: 9. Paywall A`. | | Pruebas A/B | ✅ | ❌ | La [prueba A/B](ab-tests) activa durante la compra. | | Placement | ✅ | ✅ | El [placement](placements) donde se realizó la compra. | | Store | ✅ | ✅ | La 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 del 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 use, Adapty puede excluir al usuario del segmento en los análisis. Los datos siguen 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 trial: **Cancelled by customer**, **Billing issue**, **Customer hasn't agreed to price increase**, **Unknown** o **Refund**. Disponible en suscripciones Expired (Churned) y trials Expired (Churned). | | Cohorte (solo LTV) | ❌ | ✅ | En el gráfico de LTV, agrupa por duración de cohorte: **Day**, **Week**, **Month** o **Year**. Reemplaza Agrupar por atribución en este gráfico. | No todas las vistas de análisis admiten todos los filtros o atributos de agrupación anteriores. ARPU e Installs en la pestaña Charts están limitados a Attribution, Country, Segment, Store y (solo como filtro) pruebas A/B — cuentan instalaciones, y Flow, Flow screen, Paywall, Placement y Product describen una compra. LTV tampoco ofrece Flow ni Flow screen. Las pestañas LTV, Cohorts, Funnels, Retention y Conversion admiten cada una un subconjunto diferente. Para ver la compatibilidad exacta, consulta el artículo de ese gráfico o pestaña. :::note Los desplegables de filtros solo muestran valores que Adapty ya ha recibido: una opción aparece después de que Adapty procesa al menos una transacción que contiene ese valor. Por ejemplo, si una campaña existe en tu red de publicidad o MMP pero todavía no ha llegado ninguna transacción atribuida a Adapty, esa campaña no aparece en la lista. Los segmentos son la excepción: provienen de tus definiciones de segmento y aparecen antes de que haya transacciones. ::: ### Cómo se determina el país \{#how-country-is-determined\} Cada transacción se etiqueta con un país en el momento en que se crea. La fuente de ese país, en orden de preferencia, es: 1. El **país del store** del usuario: el país de su cuenta de App Store o Google Play. 2. El **país por IP** del usuario. 3. El país de la **moneda de compra**. El país del store no está disponible para pagos web (Stripe, Paddle), accesos concedidos manualmente ni transacciones en las que el store no lo proporcionó. En esos casos, Adapty recurre al país por IP. El mismo país determina la [tasa de comisión de App Store](how-adapty-analytics-works#app-store-commission-rates) para la transacción. Como el país se registra por transacción, un usuario que cambie su país en el App Store después de instalar la app tendrá valores de país diferentes en las transacciones anteriores y posteriores al cambio. Las transacciones pasadas conservan su país original. **GB y Reino Unido.** Los datos de país se almacenan como códigos ISO 3166-1 alfa-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'`, lo que explica por qué ambos pueden aparecer como opciones al crear un segmento. ### Cómo se nombran las pantallas del flow \{#how-flow-screens-are-named\} Los nombres de pantalla en los atributos de pantalla de flow y paywall provienen de la versión **publicada** del flow, con el formato `<position>. <screen name>` — por ejemplo, `9. Paywall A`. La posición es el lugar que ocupa la pantalla en la lista de pantallas del builder, por lo que la numeración coincide con lo que ves al abrir el flow. Adapty resuelve el nombre en el momento en que ejecutas el gráfico, no cuando ocurrió la transacción: - Cambiar el nombre o el orden de una pantalla en el builder también actualiza los datos históricos de esa pantalla, de modo que una corrección puntual se refleja en todo el historial. - Una pantalla que ya no existe en la versión publicada sigue etiquetando las transacciones que generó, pero muestra su identificador en bruto, por ejemplo, `scr_a1b2c3`. Cada pantalla es una entrada, independientemente de las veces que hayas publicado el flow. Al publicar una nueva versión se crea una nueva copia de cada pantalla entre bastidores, y Adapty agrupa esas copias de vuelta en la pantalla de la que proceden. Los paywalls creados en el [Paywall Builder](adapty-paywall-builder) conservan su propio nombre y nunca se fusionan con las pantallas del flow. Las listas de filtros y grupos muestran solo las pantallas que tienen al menos una transacción. Una pantalla desde la que tus usuarios nunca compraron no aparece, aunque el flow en sí esté listado. ### 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: charts --- --- title: "Gráficos de analytics" description: "Usa los gráficos y analytics de Adapty para hacer seguimiento del rendimiento de las suscripciones." --- La pestaña **[Charts](https://app.adapty.io/analytics/charts/)** muestra los ingresos, suscripciones, pruebas y problemas de facturación de tu app a lo largo del tiempo. :::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. ::: Para descripciones y comparaciones detalladas de cada gráfico, consulta la [tabla de comparación de métricas](metric-comparison-table). Para controles de filtrado, agrupación y rango de tiempo, consulta [Controles de análisis](controls-filters-grouping-compare-proceeds). Los paywalls, placements y flows tienen sus propias páginas de métricas: [Métricas de paywall personalizadas](paywall-metrics), [Métricas de placement](placement-metrics) y [Métricas de flow](flow-metrics). La mayoría de los gráficos de esta página también se pueden [filtrar o agrupar por flow](filter-flows#filter-analytics-by-flow). ### Gráficos de suscripciones y compras in-app \{#subscription-and-in-app-charts\} - [Revenue](revenue): Ingresos totales de suscripciones y compras únicas, menos reembolsos. - [MRR](mrr): Ingresos recurrentes mensuales de suscripciones activas. - [ARR](arr): Ingresos recurrentes anuales de suscripciones activas. - [ARPU](arpu): Ingresos medios por usuario. - [ARPPU](arppu): Ingresos medios por usuario de pago. - [Installs](installs): Número de instalaciones de la app. - [Active subscriptions](active-subscriptions): Suscripciones de pago que no han expirado. - [New subscriptions](reactivated-subscriptions): Suscripciones activadas por primera vez. - [Subscription renewal cancelled](cancelled-subscriptions): Suscripciones con la renovación automática desactivada. - [Expired (churned) subscriptions](churned-expired-subscriptions): Suscripciones que han finalizado. - [Non-subscriptions](non-subscriptions): Recuento de consumibles, no consumibles y suscripciones no renovables. ### Gráficos de pruebas \{#trial-charts\} - [Active trials](active-trials): Pruebas que no han expirado. - [New trials](new-trials): Pruebas activadas. - [Trial renewal cancelled](trials-renewal-cancelled): Pruebas en las que se desactivó la renovación automática. - [Expired (churned) trials](expired-churned-trials): Pruebas que finalizaron sin convertirse. ### Gráficos de facturación y reembolsos \{#billing-and-refund-charts\} - [Período de gracia](grace-period): Suscripciones que entraron en el período de gracia tras un pago fallido. - [Período de gracia convertido](grace-period-converted): Suscripciones en período de gracia que se renovaron con éxito. - [Ingresos por período de gracia convertido](grace-period-converted-revenue): Ingresos procedentes de recuperaciones en período de gracia. - [Problema de facturación](billing-issue): Suscripciones que entraron en estado de problema de facturación. - [Problema de facturación convertido](billing-issue-converted): Suscripciones con problema de facturación que se renovaron con éxito. - [Ingresos por problema de facturación convertido](billing-issue-converted-revenue): Ingresos procedentes de recuperaciones de problemas de facturación. - [Eventos de reembolso](refund-events): Número de compras o suscripciones reembolsadas. - [Dinero reembolsado](refund-money): Importe total reembolsado. --- # File: revenue --- --- title: "Revenue" description: "Track and analyze your app’s revenue using Adapty’s subscription insights." --- 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 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, Flow, Pantalla de flow, 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, Flow, Pantalla de flow, 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: "Understand and optimize Monthly Recurring Revenue (MRR) in 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, Flow, Pantalla de flow, 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, Flow, Pantalla de flow, 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: "Track Annual Recurring Revenue (ARR) and optimize your subscription strategy." --- 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, Flow, Pantalla de flow, 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, Flow, Pantalla de flow, 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: "Understand ARPPU (Average Revenue Per Paying User) and how it impacts your app’s monetization." --- 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, Flow, Pantalla de flow, 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, Flow, Pantalla de flow, 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 diferentes. 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 quedan excluidas. 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 cuenta las suscripciones de pago no expiradas al final de cada período. Para las suscripciones sin período de gracia, la expiración se produce cuando pasa la siguiente fecha de renovación sin que se haya realizado una renovación exitosa. Por ejemplo: 500 suscripciones activas al final del mes pasado, más 50 nuevas este mes, menos 25 que expiraron este mes = 525 suscripciones activas al final de este mes. Las cifras de este gráfico y el número de eventos de integración `subscription_active` que Adapty envía no coincidirán. Consulta [Discrepancias entre métricas y eventos](discrepancies-and-troubleshooting#metric-and-event-mismatches). ## 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, Flow, Pantalla de flow, 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, Flow, Pantalla de flow, 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 activaciones de suscripción por primera vez durante el período: tanto las suscripciones que empiezan desde cero como los períodos de prueba gratuita que se convierten en suscripciones de pago. Una reactivación de pago —una nueva compra en una suscripción que había caducado por completo— se cuenta como nueva suscripción únicamente si la transacción inmediatamente anterior, dentro del historial de esa misma suscripción, fue un período de prueba. Si esa transacción previa era en sí misma un período de pago, Adapty clasifica la reactivación como una renovación y la excluye de esta métrica. ## 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Estado de renovación, Producto, País, Store, Paywall, Flow, Pantalla de flow, 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Segmento, Store y Producto. - ✅ Agrupar por: Producto, País, Store, Paywall, Flow, Pantalla de flow, 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 las suscripciones canceladas de forma eficiente 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, significa que no se renovará automáticamente al siguiente período. Sin embargo, el usuario conserva el acceso a las funciones premium de la app hasta el final del período actual. :::tip **Retén suscriptores antes de que cancelen.** [Retention Messaging](retention-messaging) muestra un mensaje personalizado dentro de la pantalla de cancelación de suscripción de Apple — un motivo para quedarse, justo cuando el suscriptor toca Cancelar. ::: ## 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Flow, Pantalla de flow, 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, lo que significa que el usuario ya no tiene acceso a las funcionalidades premium de la app. Por lo general, esto ocurre cuando el usuario decide dejar de pagar al final del período de suscripción o cuando se produce un problema de facturación. Agrupa por motivo de expiración para distinguir la cancelación voluntaria de la motivada por problemas de pago. :::tip **Retén suscriptores antes de que cancelen.** [Retention Messaging](retention-messaging) muestra un mensaje personalizado dentro de la pantalla de cancelación de suscripción de Apple — un motivo para quedarse, justo cuando el suscriptor toca Cancelar. ::: ## 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 análisis](controls-filters-grouping-compare-proceeds) ::: - ✅ Filtrar por: Atribución, Audiencia, País, Paywall, Flow, Pantalla de flow, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Motivo de expiración, Producto, País, Store, Paywall, Flow, Pantalla de flow, 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 conteo: solo lo hace su vencimiento. Por ejemplo: 100 pruebas activas ayer, más 10 nuevas hoy, menos 5 que vencieron hoy = 105 pruebas activas hoy. Las cifras de este gráfico y el número de [eventos de integración](events) `trial_active` que Adapty envía no coincidirán. Consulta [Discrepancias entre métricas y eventos](discrepancies-and-troubleshooting#metric-and-event-mismatches). ## 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Período, Estado de renovación, Producto, País, Store, Paywall, Flow, Pantalla de flow, 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 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Flow, Pantalla de flow, 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Flow, Pantalla de flow, 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Motivo de expiración, Producto, País, Store, Paywall, Flow, Pantalla de flow, 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 cuándo comenzó la suscripción. Los reembolsos de pruebas gratuitas se excluyen porque las pruebas no generan ingresos. Una actualización en App Store también se registra como un reembolso. Cuando un suscriptor actualiza su plan, Apple reembolsa la parte no utilizada del período actual de la suscripción anterior, y Adapty lo registra con «actualización» como motivo del reembolso. Para excluir las actualizaciones, filtra por **Refund Reason**. Los cambios entre el plan de pago por adelantado y el [plan de compromiso de 12 meses](app-store-12-month-commitment) de un mismo producto también cuentan, con el mismo motivo de reembolso. Este tipo de reembolso tiene ingresos cero cuando el cambio entra en vigor al final de un período de facturación. ## 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 negativa? | Notas | | --- | --- | --- | --- | --- | | [Revenue](revenue) | Sí | Fecha del reembolso — no la fecha de compra original | Sí — en días donde los reembolsos superan los ingresos nuevos | Revenue = transacciones totales − reembolsos. | | [MRR](mrr) | Sí, retroactivamente | 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í, retroactivamente | 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 hacen bajar el ARPPU más rápido de lo esperado. | | [Suscripciones activas](active-subscriptions) | Sí, retroactivamente | La suscripción se elimina del recuento | No | | | [Nuevas suscripciones](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** los datos | Fecha del reembolso | No (siempre ≥ 0) | | | [Retención](analytics-retention) | **No** | — | No | Los usuarios reembolsados siguen contando en la curva de retención. Esto puede hacer que la Retención parezca mayor que las [Suscripciones activas](active-subscriptions) o el [Revenue](revenue) para la misma cohorte. | | [Revenue de cohorte](analytics-cohorts) | Sí, acumulativamente | Fecha del reembolso | No (las sustracciones acumuladas no llevan el revenue de cohorte por debajo de cero) | Los reembolsos se restan del revenue de cohorte cuando ocurren. Para las demás métricas de cohorte, consulta [Cohortes > Gestión de reembolsos](analytics-cohorts#refund-handling). | | Métricas de [paywall personalizado](paywall-metrics) / [prueba A/B](results-and-metrics) (recuentos) | **No** | — | No | Los recuentos de Subscribers, Paying Subscribers y ARPPU en estas páginas no deducen 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Motivo de reembolso, Producto, País, Store, Paywall, Flow, Pantalla de flow, 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 solo cuenta las transacciones que generan ingresos: nuevas suscripciones de pago, renovaciones y compras únicas. Las pruebas gratuitas, que no generan ingresos y no pueden reembolsarse, quedan excluidas. Cada importe reembolsado se vincula a la fecha en que se procesó, por lo que la reducción de ingresos aparece en ese mismo período. Cuando un suscriptor de App Store realiza una mejora de plan, Apple reembolsa la parte no utilizada del período actual de la suscripción anterior. El gráfico muestra ese importe prorrateado, no el precio completo de la suscripción. Para excluir las mejoras de plan, filtra por **Refund Reason**. :::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: Atribución, Audiencia, Motivo de reembolso, País, Tipo de oferta, ID de oferta, Tipo de descuento de oferta, Paywall, Flow, Pantalla de flow, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Motivo de reembolso, Producto, País, Store, Paywall, Flow, Pantalla de flow, Audiencia, Placement, Duración, Tipo de oferta, Tipo de descuento de oferta, ID de oferta, Segmento y Atribución. ## Gestión de solicitudes de reembolso \{#refund-request-management\} El Refund Saver responde automáticamente a las solicitudes de reembolso de App Store y Google Play. Ahorra tiempo y reduce la pérdida de ingresos: los reembolsos que evita no aparecen en el gráfico de Refund money. 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 de 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. Su duración la establece el desarrollador de la app en la configuración del store: - **App Store**: 3, 16 o 28 días. Para suscripciones semanales, 3 o 6 días. - **Google Play**: el período de gracia que configures para el plan base en Google Play Console. Si el pago se realiza correctamente 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Flow, Pantalla de flow, 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 suscripción de usuarios que se encuentran en un período de gracia. El período de gracia comienza cuando falla el pago de una renovación. Termina cuando el pago se recibe correctamente o cuando expira el período de gracia configurado en el store. Para consultar las duraciones de período de gracia disponibles, consulta [Período de gracia](grace-period#calculation). El gráfico ofrece información sobre la efectividad de la función de período de gracia y puede ayudar a identificar posibles problemas con el procesamiento de pagos o 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Flow, Pantalla de flow, 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 del período de gracia muestra los ingresos diarios generados por las renovaciones de suscripciones de usuarios en un período de gracia. El período de gracia comienza cuando falla un pago de renovación. Termina cuando el pago se recibe correctamente o cuando expira el período de gracia establecido en el store. Para consultar las duraciones disponibles del período de gracia, consulta [Período de gracia](grace-period#calculation). El gráfico ofrece información sobre la efectividad de la función de período de gracia y puede ayudar a identificar posibles problemas con el procesamiento de pagos o 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Flow, Pantalla de flow, 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 de problemas de facturación muestra el número de suscripciones que han entrado en el estado de problema de facturación. Este estado se activa normalmente cuando el store, como Apple o Google, no puede cobrar al suscriptor por algún motivo, como una tarjeta de crédito caducada o fondos insuficientes. Estos son solo ejemplos: los stores no comparten el motivo específico del rechazo, por lo que Adapty no puede desglosar los problemas de facturación por motivo. ## 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Flow, Pantalla de flow, 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Flow, Pantalla de flow, 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, Flow, Pantalla de flow, Pruebas A/B, Placement, Período, Segmento, Store, Producto y Duración. - ✅ Agrupar por: Producto, País, Store, Paywall, Flow, Pantalla de flow, 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 su suscripción en ese período. M3 corresponde al mes 3 e indica que los suscriptores han realizado 3 renovaciones consecutivas hasta ese punto; W7 corresponde a la semana 7, e Y2 al año 2. En ocasiones, en las cohortes puede aparecer P2. La P representa el período de la suscripción. Adapty la muestra en lugar de W/M/Y cuando en la misma cohorte hay varios productos con diferentes períodos de renovación. En el modo **por días**, los encabezados de columna muestran horizontes de días en su lugar: `7d`, `28d`, `183d`, etc. Una columna de días es acumulativa: `183d` cuenta los usuarios que realizaron al menos una transacción de pago dentro de sus primeros 183 días tras la instalación, o los ingresos que generaron en ese período, no los pagos realizados el día 183. Una cohorte no necesita haber alcanzado esa antigüedad para que la columna muestre datos: para una cohorte más reciente, el valor refleja lo ocurrido hasta el momento y sigue creciendo. :::note Las columnas de días acumulados se aplican a valores absolutos. Si muestras valores relativos, las columnas de Ingresos permanecen acumuladas, mientras que las columnas de Suscripciones y Pagadores muestran la proporción de cada columna respecto al total de la cohorte. ::: Usamos colores degradados para destacar las diferencias entre los valores de las cohortes. 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 en CSV \{#filters-metrics-cohort-segments-and-export-in-csv\} :::link Artículo principal: [Controles de analítica](controls-filters-grouping-compare-proceeds) ::: Por defecto, Adapty crea las cohortes con 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. De estos, solo **Country, Store, Segment** y **Attribution** reducen el recuento de instalaciones de la cohorte y cualquier métrica calculada a partir de ese recuento, como el ARPU. Filtrar por **Product duration, Product** o **Paywall** sí reduce Revenue, Payers y las demás métricas basadas en compras, pero el recuento de instalaciones sigue incluyendo a todos los usuarios que instalaron la app en el período, por lo que el ARPU puede aparecer artificialmente bajo, ya que el numerador de ingresos se reduce mientras que el denominador del recuento de instalaciones no cambia. A la derecha del panel de control, hay un botón para exportar los datos de cohorte a CSV. Luego puedes abrirlo en Excel, Google Sheets o importarlo en tu propio sistema analítico. La exportación incluye las filas de cohortes y los totales para la métrica seleccionada actualmente; no hay columna **Country** ni ningún otro desglose. Para dividir las cohortes por país, aplica un filtro **Country** y exporta una vez por país, o solicita los datos de cohortes con un filtro de país a través de la [API de exportación](export-analytics-api). 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 \{#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 los impuestos (que pueden variar según el país de la cuenta del store del usuario). Adapty actualmente soporta 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 análisis, consulta nuestra [documentación](how-adapty-analytics-works#commissions-and-taxes). ## Ingresos vs Ganancias netas \{#revenue-vs-proceeds\} Tanto los Ingresos como las Ganancias netas son métricas de dinero. Puedes entender los Ingresos como ingresos brutos y las Ganancias netas como ingresos netos. Los Ingresos no tienen en cuenta las comisiones de App Store / Play Store, mientras que las Ganancias netas sí. Por eso las Ganancias netas son siempre menores que los Ingresos. La comisión real deducida varía en función de varios factores: la participación en programas como el [Small Business Program](app-store-small-business-program), las tarifas reducidas para suscripciones después del primer año y el país de la tienda. La tarifa estándar de App Store es del 30% en la mayoría de los países, y menor en algunos, como el 26% en la Unión Europea. Adapty determina automáticamente la tasa de comisión aplicable a cada transacción que realizan tus clientes y calcula los Ingresos netos en función de ella. Para consultar las tarifas que aplica Adapty, consulta [Comisiones](how-adapty-analytics-works#commissions). ## 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 predicho** 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 crecimiento 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 sobre predicciones](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: "Entiende el 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. :::tip **Retén suscriptores antes de que cancelen.** [Retention Messaging](retention-messaging) muestra un mensaje personalizado dentro de la pantalla de cancelación de suscripción de Apple — un motivo para quedarse, justo cuando el suscriptor toca Cancelar. ::: ### ¿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. Adapty fecha a cada suscriptor por el día en que comenzó su suscripción: el inicio del periodo de prueba para los productos con prueba gratuita, y el primer pago para los productos sin ella. Alguien que comienza una prueba el 1 de marzo y paga el 1 de abril pertenece a marzo, independientemente de cómo esté configurado **Show trials**. :::info La retención cambia de forma retroactiva Independientemente de cuándo consultes el gráfico, el número base (100%) sigue siendo el mismo para el período de tiempo seleccionado. Sin embargo, la retención hacia el siguiente período puede crecer con el tiempo. Por ejemplo, para una suscripción mensual, si se realizan 20 primeras compras entre el 1 y el 31 de diciembre, se espera que la retención hacia el segundo período vaya aumentando 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 más tarde por diversas razones (por ejemplo, el período de gracia). ::: ### Productos con distintas duraciones \{#products-with-different-durations\} Los escalones del gráfico son períodos de suscripción, no intervalos de tiempo fijos. P2 representa el segundo período para cada producto, así que un suscriptor semanal lo alcanza al cabo de una semana y uno anual al cabo de un año. Ambos caen en la misma columna. Si vendes más de una duración, la curva predeterminada las combina, y se desplaza cada vez que cambia tu combinación de productos. Agrupa o filtra por **Duration** o **Product** para analizar una a la vez. ### 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. Obtén insights reales basados en datos. Varía por: 1. Tipo de producto. 2. Duración. 3. Rango de tiempo. 4. País. 5. Atribución de tráfico. 6. Store. 7. Flow y pantalla de flow. 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." --- Un gráfico de conversión mide cuántos usuarios que alcanzaron una etapa del recorrido del usuario (por ejemplo, la prueba) llegaron posteriormente a otra (por ejemplo, la segunda renovación). El intervalo de fechas seleccionado determina cuándo ocurrió el primer evento. La conversión puede producirse en cualquier momento después de eso, mucho después de que finalice el intervalo de fechas. Los gráficos de conversión responden preguntas como: 1. ¿Cómo cambian las conversiones de la app con el tiempo? ¿Hay tendencias estacionales? 2. ¿Cómo las afectan las campañas de marketing u otras circunstancias nuevas? 3. ¿Cómo responden los usuarios de distintas regiones a las actualizaciones de tu app? 4. ¿Qué tipos de productos convierten mejor con el tiempo? Para generar los gráficos, Adapty utiliza los datos de tu app y de los stores. ## Cómo se miden las conversiones \{#how-conversions-are-measured\} El gráfico de conversiones toma dos eventos (por ejemplo, instalación e inicio de prueba) y calcula cuántos usuarios que desencadenaron el primer evento también desencadenaron el segundo después. El rango de fechas que estableces determina cuándo ocurrió el primer evento. El segundo evento puede ocurrir en cualquier momento posterior, incluso mucho después de que finalice el rango de fechas. ¿Quieres saber cuántos trials iniciados en marzo se convirtieron en suscripciones de pago? Abre el gráfico **Trial -> Paid**, establece el rango de fechas en marzo. Adapty localiza a todos los que iniciaron un trial ese mes, cuenta cuántos se convirtieron en suscriptores y te muestra el porcentaje. Si alguien cancela el trial al principio, pero se suscribe en junio, igual cuenta: el número de marzo sube. Cada punto de datos en el gráfico es una cohorte independiente de usuarios, agrupados por el momento en que activaron el primer evento. ## Por qué cambian los valores pasados \{#why-past-values-change\} Las estadísticas de conversión de cualquier período de tiempo seguirán cambiando después de que ese período termine. Los usuarios no convierten todos a la vez. Cada vez que uno de ellos convierte, Adapty atribuye esa conversión a la fecha de su cohorte, que puede estar meses en el pasado. No hay forma de predecir cuánto tiempo seguirá creciendo una métrica. Por lo general, esto depende de la distancia entre sus dos eventos: - **Instalar -> Prueba** depende de la estrategia de monetización de tu app. Un paywall forzoso puede hacer que el usuario convierta o abandone la app en cuestión de días. En una app freemium, los usuarios pueden explorar durante meses. - **Instalar -> Pago** abarca todo desde la instalación hasta el primer pago, incluido el período de prueba, si lo hay. Una promoción realizada mucho después de la instalación inicial puede provocar un pico de conversiones en una cohorte pasada. - **Prueba -> Pago** depende de la duración de tu prueba y se alarga aún más cuando el store reintenta un cobro fallido. So one metric can settle quickly while another is still climbing for the same month. ### Compara meses en tus informes \{#compare-months-in-your-reports\} Para comparar meses de forma justa, registra las métricas de cada mes un tiempo fijo después de que termine. Adapty no ofrece ningún ajuste que limite el rango de fechas del segundo evento, así que el tiempo de espera es lo que hace comparables dos meses. Para saber cuánto tiempo esperar, monitoriza la métrica durante unos meses. Abre el mismo gráfico de conversión para el mismo rango de fechas una vez a la semana y observa cuándo deja de cambiar el número. Las apps con trials largos y decisiones de compra lentas necesitan más tiempo. :::warning Toma una instantánea de la métrica en cuanto termine su período de espera. Un gráfico de conversión siempre muestra el valor actual para un rango de fechas, por lo que no podrás volver más tarde a ver cómo estaba marzo 60 días después de que terminara. ::: ### Trabajar con períodos incompletos \{#working-with-incomplete-periods\} Los datos más recientes suelen ser más bajos que el resto, y rara vez es un descenso real. Esas cohortes no han terminado de convertir: un período de prueba que todavía está activo, o un período de suscripción que aún no ha finalizado, se cuenta en el total mientras el usuario todavía tiene tiempo de convertir. ## Elegir la métrica correcta \{#choosing-the-right-metric\} Adapty registra el mismo recorrido del usuario de varias formas, cada una con su propio propósito. La tabla siguiente resume las diferencias entre el gráfico de conversión y sus equivalentes: | Pestaña de Analytics | Qué determina el intervalo de fechas | Qué muestra | Cuándo dejan de cambiar los números | Para qué usarla | | --- | --- | --- | --- | --- | | **Conversion** | Usuarios que alcanzaron una etapa específica durante el período | Qué proporción de esos usuarios alcanzó la siguiente etapa | Mucho después de que termine el período, con una diferencia de hasta el tiempo transcurrido entre ambos eventos | Ver qué cohortes convierten mejor | | [Funnels](analytics-funnels) | Usuarios que instalaron la app durante el período | Cuántos de ellos alcanzan cada etapa del journey del usuario: instalación, vista del paywall, prueba, primer pago, primera renovación, etc. | Mucho después de que termine el período, mientras esas instalaciones siguen avanzando | Identificar la etapa con mayor abandono | | [Retention](analytics-retention) | Usuarios cuya prueba o suscripción comenzó durante el período | Cuántas veces renovaron su suscripción | Mucho después de que termine el período, mientras esos suscriptores siguen renovando | Detectar cuándo los suscriptores pierden el interés | | [Charts](charts) | El período de tiempo de los eventos | Cifras de eventos: ingresos, nuevas pruebas, etc. | Cuando finaliza el período de tiempo seleccionado, aunque los [reembolsos](refund-events#how-metrics-handle-refunds) revisan algunas métricas más tarde | Identificar tendencias y variaciones estacionales | :::important Las conversiones, retenciones y embudos están basados en cohortes. Los gráficos no. [Nuevas pruebas](new-trials) cuenta una prueba el día en que comenzó; [Instalación -> Prueba](#install---trial) cuenta esa misma prueba el día en que el usuario se instaló. ::: ## 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. Te permite elegir qué conversiones concretas seguir, como Install -> Trial, Trial -> Paid o Paid -> 2nd period. A continuación encontrarás la explicación de cada conversión, junto 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 registra cuántos usuarios iniciaron la prueba después de ver un paywall. Una pantalla de un [flow](adapty-flow-builder) cuenta como vista de paywall aquí. <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 de 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, otros 30 usuarios 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 -> Pagado \{#paywall-view---paid\} Esta métrica mide cuántos usuarios realizaron una compra después de ver un paywall. Una pantalla de un [flow](adapty-flow-builder) cuenta como vista de paywall aquí. <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 de paywall. - Para el 8 de enero, 20 de esos usuarios habían realizado una compra. - El 8 de enero, la conversión para el 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 para el 1 de enero = ((20 + 30) / 100) × 100% = 50% Esto indica que el 50% de los usuarios que vieron un paywall el 1 de enero realizaron una compra, hasta el momento actual. </details> ### Prueba a pago \{#trial---paid\} Esta métrica muestra el porcentaje de usuarios que iniciaron una prueba en una fecha concreta y posteriormente realizaron su primera compra de suscripción. La fecha del gráfico es el día en que comenzó la prueba, no el día en que el usuario instaló la app. Mientras una prueba sigue en curso, su fecha de inicio aparece como un [período incompleto](#working-with-incomplete-periods). <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, 100 usuarios iniciaron pruebas. - Para el 8 de enero, 20 de esos usuarios se habían suscrito. - El 8 de enero, la conversión para el 1 de enero = (20 / 100) × 100% = 20% - Para el 1 de febrero, 30 usuarios más del grupo de prueba del 1 de enero se habían suscrito. - El 1 de febrero, la conversión para el 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 el período de gracia, que la app tiene configurado en 16 días en App Store Connect, por lo que termina 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ó correctamente. - 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: - Product - Country - Store - Paywall - Flow - Flow screen - Duration - Marketing attribution 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. ## Métricas que cambian con el tiempo \{#metrics-that-change-over-time\} Las métricas basadas en cohortes agrupan a los usuarios según una acción que realizaron — una instalación, una visualización de paywall, el inicio de una prueba o un primer pago — y luego siguen midiendo lo que hacen esos usuarios después. Las [tasas de conversión](analytics-conversion), las [cohortes](analytics-cohorts), la [retención](analytics-retention#how-do-we-calculate-retention) y el [LTV](ltv) funcionan de esta manera; la [tabla de comparación de métricas](metric-comparison-table) indica cuáles métricas sí cambian y cuáles no. Un usuario que se instala en marzo y se suscribe en julio aumenta la tasa "De instalación a pago" de marzo en julio. Comparar un mes de hace dos años con el mes pasado equivale a comparar un número definitivo con uno provisional. Lo correcto es leer cada período tras el mismo tiempo transcurrido. [Instalación a prueba](analytics-conversion#install---trial) se estabiliza en días, mientras que [Instalación a pago](analytics-conversion#install---paid) y [Prueba a pago](analytics-conversion#trial---paid) siguen aumentando durante meses. [Comparar meses en tus informes](analytics-conversion#compare-months-in-your-reports) explica cuánto tiempo hay que esperar antes de comparar. ## 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\} Según la [configuración](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue), los gráficos de Adapty pueden mostrar tus **ingresos brutos**, **ingresos tras la comisión del store** o **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 los impuestos automáticamente. Si observas una discrepancia entre dos gráficos de ingresos distintos, asegúrate de que la comparación sea válida. Adapty [estima los impuestos por su cuenta](how-adapty-analytics-works#taxes) para las compras de App Store y Google Play. Por eso, los ingresos después de la comisión de la store e impuestos en Adapty pueden diferir de los que muestra App Store Connect y Google Play Console. ### 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. ## Planes de compromiso de 12 meses \{#12-month-commitment-plans\} Adapty registra un [plan de compromiso de 12 meses](app-store-12-month-commitment) como un producto independiente con el ID `<product_id>:monthly`. Los informes de App Store Connect no tienen un campo para el plan de facturación, por lo que muestran ambos planes bajo una misma suscripción. Para comparar los ingresos con App Store Connect, [filtra](controls-filters-grouping-compare-proceeds#filter-and-group-data) un gráfico de Adapty tanto por el producto anual como por su plan de compromiso. Luego compara el total del gráfico con la suscripción en App Store Connect. ## 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. ## Discrepancias entre métricas y eventos \{#metric-and-event-mismatches\} Una discrepancia entre el total de un gráfico y el número de eventos que recibió tu integración es algo esperado, no un fallo de entrega. Los nombres coinciden, pero las definiciones no. ### 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` de [eventos de integración](events) cuentan cosas diferentes. Es de esperar que la métrica de «nuevas suscripciones» supere el número de eventos `subscription_started`. | Qué diferencia | Métrica de nuevas suscripciones | Evento `subscription_started` | |---|---|---| | Qué registra | Cuántas suscripciones comenzaron en un período | Que comenzó una suscripción de pago, en el momento en que ocurre | | Para qué sirve | Hacer seguimiento de cuántos nuevos pagadores has conseguido | Registrar la compra en tu sistema de analíticas, MMP o backend propio | | Temporización | Cuenta las transacciones por fecha de compra en tu zona horaria de informes | Se envía cuando se produce la compra | | Conversiones de prueba | Contadas | No se envía — Adapty envía `trial_converted` en su lugar | | Transacciones retiradas | Excluidas una vez que Adapty las retira | Ya enviadas, y nunca se revocan | | Perfiles que el SDK nunca identificó | Sus compras se cuentan | Ausentes del Event Feed | ### Métrica de suscripciones activas frente al evento `subscription_active` \{#active-subscriptions-metric-vs-the-subscription_active-event\} La métrica [Suscripciones activas](active-subscriptions) y el evento de integración [`subscription_active`](events) difieren en su propósito, no solo en lo que cuentan. | Diferencia | Métrica de suscripciones activas | Evento `subscription_active` | |---|---|---| | Qué reporta | Cuántas suscripciones están activas al final de cada período | Que una suscripción sobrevivió sus primeros días | | Para qué sirve | Seguir el tamaño y crecimiento de tu base de suscriptores | Reenviar a un MMP para pujar por usuarios que permanecen | | Temporización | Se recalcula al final de cada período | Se envía un número de días configurado después de que comienza la suscripción, entre 1 y 90 | | Se repite | Sí, cada período | No — Adapty comprueba una vez y no vuelve a verificar | | Conversiones de prueba | Se cuentan una vez que la prueba se convierte en pago | Nunca se envía — el temporizador comienza solo en `subscription_started`, y una conversión envía `trial_converted` | | Renovaciones canceladas | Excluidas | Se sigue enviando, siempre que el usuario tenga acceso | | Reembolsos | Se eliminan, incluso retroactivamente de fechas pasadas | No se envía si el reembolso llega primero; nunca se revoca si llega después | | Disponibilidad | Siempre se calcula | Solo lo envían las integraciones donde lo hayas activado | ### Métrica de pruebas activas vs el evento `trial_active` \{#active-trials-metric-vs-the-trial_active-event\} La métrica [Pruebas activas](active-trials) y el evento de integración `trial_active` de [eventos de integración](events) difieren en su propósito, no solo en lo que contabilizan. | Qué difiere | Métrica de pruebas activas | Evento `trial_active` | |---|---|---| | Qué reporta | Cuántas pruebas gratuitas no han expirado | Que una prueba superó sus primeros días | | Para qué sirve | Ver cuántas pruebas están en curso | Optimizar las pujas en MMP para usuarios de prueba que permanecen | | Cuándo se actualiza | Se recalcula al final de cada período | Se envía un número de días configurado después de que empieza la prueba, de 1 a 90 | | Se repite | Sí, cada período | No: Adapty comprueba una vez y no vuelve a comprobar | | Conversiones de prueba | Se cuentan hasta que la prueba convierte, y se eliminan en el siguiente período | Nunca se envía para una prueba que ya convirtió | | Disponibilidad | Siempre se calcula | Solo lo envían las integraciones donde lo hayas activado | Los dos coinciden en la cancelación: un usuario que desactiva la renovación automática permanece en la métrica y el evento se sigue enviando hasta que expire el período de prueba. ## Un total filtrado supera el total sin filtro \{#a-filtered-total-exceeds-the-unfiltered-total\} Con un filtro de **Period** aplicado, añadir un filtro de producto o agrupar por producto cambia lo que significa un período. Sin un producto de por medio, **Activation** cuenta el primer pago de una cadena de suscripciones. Con uno, cuenta el primer pago de cada producto en esa cadena, de modo que un usuario que cambió de producto aporta una Activation por cada uno. Ninguna vista cuenta una transacción dos veces; simplemente cuentan poblaciones distintas. La elección entre uno u otro depende de la pregunta: todos los productos sin agrupación responde "¿cuántos nuevos pagadores hemos captado?", mientras que la vista filtrada por producto responde "¿cuántos usuarios iniciaron este producto por primera vez?". Eliminar el filtro **Period** reconcilia ambas con el total sin filtrar. ## Las tasas de conversión de vistas de paywall han bajado \{#paywall-view-conversion-rates-dropped\} Si tu app usa [Flow Builder](adapty-flow-builder), las tasas de [Paywall view -> Trial](analytics-conversion#paywall-view---trial) y [Paywall view -> Paid](analytics-conversion#paywall-view---paid) son más bajas de lo que solían ser, en toda tu historia. Una pantalla de flow que se muestra a un usuario ahora cuenta como una vista de paywall. Las vistas de pantallas de flow anteriormente llegaban solo a las [métricas de flow](flow-metrics), por lo que estas dos tasas dividían tus compras por un recuento de vistas de paywall que excluía todas las pantallas de flow. Los valores que ves ahora son los correctos; los que reemplazaron eran demasiado altos. Cada usuario cuenta una vez por fecha, independientemente de cuántas pantallas de un flow haya visto, y tanto si también vio un paywall creado en el [Paywall Builder](adapty-paywall-builder) como si no. --- # File: push-notifications --- --- title: "Recibir notificaciones push sobre nuevos eventos" description: "Recibir notificaciones push sobre nuevos eventos" --- Las notificaciones push en la app móvil de Adapty para [iOS](https://apps.apple.com/us/app/adapty/id6739359219) y [Android](https://play.google.com/store/apps/details?id=io.adapty.dashboard) te mantienen al día de lo que ocurre con tus apps en tiempo real. Tanto si haces seguimiento de nuevas pruebas, renovaciones de suscripciones o instalaciones, recibirás actualizaciones al instante sin tener que consultar el dashboard. Puedes configurar ajustes de notificación predeterminados que se apliquen a todas las apps y luego personalizarlos para apps específicas si necesitas más control. Esto es especialmente útil cuando lanzas una nueva app y quieres monitorear de cerca la actividad inicial. El número total de notificaciones para todas tus apps combinadas está limitado a 1000/día. ## Configurar notificaciones push \{#configure-push-notifications\} Para configurar las notificaciones push en la aplicación móvil de Adapty: 1. Abre la aplicación de Adapty. 2. Ve a **Settings → Notifications**. 3. Pulsa **Enable notifications** para permitir que la app te envíe notificaciones. 4. Elige si usar la configuración predeterminada o configurar las notificaciones por app. Ten en cuenta que la configuración individual de cada app anula la configuración predeterminada. 5. Selecciona los eventos para los que quieres recibir notificaciones. ## Eventos compatibles \{#supported-events\} Puedes recibir notificaciones push para los siguientes tipos de eventos: * Access level updated * Auto-renew turned off * Auto-renew turned on * Billing issue detected * Entered grace period * Non-subscription purchase * Non-subscription purchase refunded * Subscription cancelled * Subscription deferred * Subscription initial purchase * Subscription paused * Subscription refunded * Subscription renewed * Trial canceled * Trial converted * Trial started --- # 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 usuarios 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 el valor de vida del cliente (LTV) predicho y los ingresos predichos para cohortes de usuarios 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 aplicaciones con un historial muy limitado, el modelo recurre a promedios entre aplicaciones, por lo que las predicciones para apps más nuevas pueden no reflejar completamente 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 crecimiento de datos históricos de cohortes para proyectar los ingresos futuros y el LTV. Para cada combinación de app y tipo de producto, el modelo mide cómo crecen los ingresos acumulados y el número acumulado de usuarios de pago de una cohorte entre puntos fijos de su vida, contados en días desde que comenzó la cohorte. Los intervalos entre esos puntos son cortos cuando la cohorte es joven y se van alargando a medida que envejece: primero días, luego meses, luego trimestres. Adapty mide un par de coeficientes de crecimiento para cada intervalo: uno para los ingresos y otro para los usuarios de pago. Ambas métricas son netas de reembolsos: un reembolso resta su importe de los ingresos de la cohorte y elimina a ese usuario del recuento de usuarios de pago. Para proyectar una cohorte, Adapty toma el último de esos puntos que la cohorte ha alcanzado y encadena los coeficientes hacia adelante desde ahí hasta el día que corresponde al horizonte seleccionado: el día 90 para la predicción de 3 meses, el día 365 para la de 12 meses y hasta el día 730 para la de 24 meses. Los datos utilizados están completamente anonimizados. Los coeficientes se miden por separado según el tipo de producto: suscripciones semanales, mensuales, de 2 meses, de 3 meses, de 6 meses y anuales, además de compras de por vida y compras únicas. La predicción de una cohorte es la suma de las proyecciones de cada tipo de producto que compraron sus usuarios. El modelo produce dos valores para cada cohorte: - **Predicted revenue**: Los ingresos totales, menos los reembolsos, que se proyecta que generará una cohorte dentro del horizonte seleccionado. - **Predicted LTV**: Los ingresos previstos divididos por el número previsto de usuarios de pago en la cohorte. ### Coeficientes específicos de la app y entre apps \{#app-specific-and-cross-app-coefficients\} Por defecto, la predicción de una cohorte utiliza coeficientes calculados a partir de las propias cohortes anteriores de esa app para el mismo tipo de producto, lo que refleja el comportamiento específico de sus usuarios. Una app obtiene sus propios coeficientes para un tipo de producto en cuanto tiene al menos dos cohortes lo suficientemente grandes como para poder medirse. Los dos orígenes se combinan dentro de una única predicción: donde el historial propio de la app no cubre un intervalo, Adapty rellena ese paso con coeficientes promediados entre todas las apps que venden ese tipo de producto. Así, una app reciente puede proyectar sus primeras semanas a partir de sus propios datos y el resto del año a partir de promedios entre apps. ### Calibración y límites mínimos \{#calibration-and-floors\} Los coeficientes encadenados presentan una desviación que se acentúa en las cohortes con menos historial del que partir. Adapty mide esa desviación durante el entrenamiento: la curva de cada app vuelve a predecir sus propias cohortes completadas a partir de puntos anteriores de su evolución, y la diferencia entre la proyección y lo que esas cohortes generaron realmente se convierte en un factor de corrección para ese tipo de producto, ese horizonte y esa cantidad de historial observado. Cada predicción se multiplica por el factor correspondiente al tiempo que lleva observada la cohorte, de modo que la corrección es mayor en las cohortes más recientes, donde la proyección se extiende más lejos. La corrección se inclina deliberadamente hacia abajo para los tipos de producto que el modelo tiende a sobreestimar. Las suscripciones semanales, mensuales, de 2 meses, de 3 meses y de 6 meses reciben un factor de ingresos que tira la proyección hacia abajo en lugar de centrarla, y para las suscripciones semanales ese sesgo es más fuerte durante las primeras dos semanas de una cohorte. Las suscripciones anuales, los accesos de por vida y las compras únicas no reciben ningún sesgo. El factor de usuarios de pago nunca se corrige, de modo que la precaución se refleja en los ingresos previstos y, a través de ellos, en el LTV predicho. Tras la corrección se aplican dos límites mínimos: - Una predicción nunca es inferior a lo que la cohorte ya ha generado hasta ese horizonte. Si una cohorte ya ha generado más de lo que indicaba su proyección a 3 meses, se muestra el ingreso realizado como la predicción a 3 meses. - Un horizonte más largo nunca muestra menos que uno más corto. Los ingresos predichos y los usuarios de pago predichos son acumulativos, por lo que la predicción a 12 meses es al menos igual a la de 9 meses. ### Disponibilidad y actualizaciones \{#availability-and-updates\} Las predicciones se generan únicamente para cohortes semanales y mensuales. Si el control **Cohort length** está configurado en día, trimestre o año, las columnas de predicción aparecen vacías. Para las cohortes semanales y mensuales, la disponibilidad depende del tiempo que lleva existiendo la cohorte, no de los productos que hayan comprado sus usuarios. Una cohorte recibe su primera predicción una vez que ha transcurrido por completo su período de instalación y el período de prueba más largo que alguien pudo haber iniciado durante ese período ha tenido tiempo de convertir. El período de instalación dura una semana después del inicio de la cohorte para cohortes semanales, y un mes para las mensuales. Hasta que finaliza, la cohorte sigue captando instalaciones. Un usuario que instala el último día aún puede comenzar tu prueba más larga, y los ingresos de una cohorte solo se contabilizan cuando las pruebas se convierten, por lo que la espera es el período de instalación más la duración de tu prueba más larga. Cuanto más largas sean tus pruebas, más tarde aparecen las predicciones. A partir de entonces, las predicciones se recalculan diariamente con los datos de transacciones más recientes, para que se mantengan actualizadas con el comportamiento de la cohorte. ### Limitaciones \{#limitations\} - **Calidad de los datos**: Un comportamiento de cohorte inusual o un número reducido de usuarios de pago reduce la precisión. Las cohortes con menos de 30 usuarios de pago no reciben ninguna predicción y se excluyen de los datos de entrenamiento del modelo. - **Aplicaciones nuevas**: Las aplicaciones sin historial suficiente utilizan coeficientes de otras aplicaciones, que pueden no reflejar el comportamiento específico de los usuarios de esa aplicación. - **Antigüedad de la cohorte**: Las predicciones para un horizonte determinado se ocultan una vez que la cohorte supera ese horizonte. Por ejemplo, las predicciones a 3 meses dejan de mostrarse pasados 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, menos los reembolsos, que se espera que genere una cohorte de usuarios de pago durante el período de tiempo seleccionado tras la creación de la cohorte. Este valor se calcula utilizando el modelo de predicción de Adapty, basado en los patrones históricos de crecimiento 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: - **Longitud de cohorte no compatible**: El control **Cohort length** está configurado en día, trimestre o año. Las predicciones solo se generan para cohortes semanales y mensuales. - **Filtro o comparación no compatible**: La tabla está filtrada por placement, audiencia, paywall o prueba A/B, o hay un período de comparación activado. Los filtros por producto, duración del producto, país, store y datos de atribución mantienen las predicciones. - **Tiempo insuficiente desde la creación de la cohorte**: El período de instalación de la cohorte no ha transcurrido completamente, o el trial más largo que alguien pudo haber iniciado durante ese período todavía no ha tenido tiempo de convertirse. Consulta [Disponibilidad y actualizaciones](#availability-and-updates). - **Cohorte demasiado pequeña**: Menos de 30 usuarios de pago — muy pocos 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 unas semanas puede resolverlo a medida que se acumulen 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 de más de 24 meses. :::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: - **[Descripción general](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 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**: Por campañas, grupos de anuncios y palabras clave - **Seguimiento de ingresos de extremo a extremo**: De búsqueda → instalación → prueba → suscripción → LTV - **Predicciones y recomendaciones de IA**: Para escalar de forma 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 - **[Keyword Suggester](ads-manager-keyword-suggester)**: Frases de búsqueda relacionadas y contexto de categoría de Apple para cualquier término semilla - **[Search Popularity](ads-manager-search-popularity)**: Los términos de búsqueda de la App Store con mayor posicionamiento en un mercado, categoría y período seleccionados - **[Pruebas A/B de CPP](ads-manager-cpp-ab-tests)**: Compara páginas de producto personalizadas entre sí y encuentra la de mejor rendimiento - **[Historial de cambios](ads-manager-change-history)**: Cambios en tus campañas de Apple Ads, incluidos los realizados fuera de Adapty, con los valores antes y después - **[CLI](developer-cli-ads-manager)**: Gestiona campañas, palabras clave y reglas de automatización desde el terminal, y permite que las herramientas de codificación con IA lean tus métricas y actúen sobre ellas a través del [plugin de Apple Ads](developer-cli-ads-manager-skill) <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-keyword-suggester', 'ads-manager-search-popularity', 'ads-manager-cpp-ab-tests', 'ads-manager-change-history']} /> ## ¿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). Incluye los dos pasos necesarios: instalar el SDK de Adapty y conectar tu cuenta de Apple Ads. También puedes gestionar la misma cuenta desde el terminal — consulta [Gestionar Ads Manager desde la CLI](developer-cli-ads-manager). --- # 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 eso, 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 estén importados. Adapty Ads Manager solo muestra tus datos cuando tu cuenta de Apple Ads tiene al menos una campaña. Si la cuenta aún no tiene campañas, la página se quedará en **Syncing your historical data…** Para terminar de conectar, crea una campaña en Apple Ads en modo avanzado y espera a que se complete la sincronización. ## ¿Qué viene ahora? \{#whats-next\} Después de sincronizar correctamente los datos de tus 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) - [Trabajar con tu cuenta desde la terminal o una herramienta de IA para programar](developer-cli-ads-manager) --- # 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 meramente consultivo: analiza tu cuenta y recomienda qué hacer, pero no modifica campañas, pujas ni presupuestos por ti. :::tip Si quieres que una herramienta de IA también ejecute los cambios además de recomendarlos, instala el [plugin de Apple Ads](developer-cli-ads-manager-skill) en Claude Code, Copilot CLI, Codex o Gemini CLI. Funciona a través de la CLI de Adapty en tu terminal, por lo que puede actuar directamente sobre tu cuenta. ::: ## 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: ads-manager --- --- title: "Seguimiento y gestión de campañas en Adapty Ads Manager" description: "Obtén analíticas en tiempo real desde Apple Ads y optimiza tus campañas" --- :::important Antes de empezar a usar el Ads Manager, asegúrate de haber completado los pasos de [esta guía](adapty-ads-manager-get-started). ::: Adapty Ads Manager te permite hacer tres cosas importantes en un solo lugar: - **Ver qué genera dinero de verdad** – explora el rendimiento de campañas, grupos de anuncios y palabras clave directamente vinculado a instalaciones, trials, suscripciones e ingresos. - **Lanzar y reestructurar rápido** – crea y edita campañas, grupos de anuncios, palabras clave y negativos sin tener que cambiar a la consola nativa. - **Automatizar el trabajo rutinario** – configura reglas que ajusten automáticamente pujas y presupuestos para alcanzar tu CPA/ROAS objetivo en lugar de hacerlo manualmente. <CustomDocCardList /> --- # File: adapty-ads-manager-analytics --- --- title: "Analíticas en Adapty Ads Manager" description: "Consulta las analíticas de tu app en Adapty Ads Manager." --- [Ads Manager](https://app.adapty.io/asa/campaigns) es una sección del dashboard de Adapty Ads Manager que te permite ver todas las métricas clave en un solo lugar y organizarlas por apps, campañas, grupos de anuncios, palabras clave y términos de búsqueda. Puedes personalizar qué métricas ver y consultar los datos de todas tus campañas a la vez. ## Descripción general de la estructura \{#structure-overview\} En Apple Ads Manager puedes evaluar el rendimiento de tus anuncios en varios niveles. ### Campañas \{#campaigns\} En la pestaña **Campaigns**, puedes ver todas las métricas agrupadas por campañas. Haz clic en el nombre de una campaña para ver sus detalles. ### Apps \{#apps\} En la pestaña **Apps**, puedes ver la lista de tus apps. Haz clic en el nombre de una app para ver las métricas de campaña de esa app. ### Grupos de anuncios \{#ad-groups\} En la pestaña **Ad groups**, puedes ver todas las métricas agrupadas por grupos de anuncios. Haz clic en el nombre de un grupo de anuncios para ver sus detalles. ### Palabras clave \{#keywords\} En la pestaña **Keywords**, puedes ver todas las métricas agrupadas por palabras clave. Haz clic en una palabra clave para ver los términos de búsqueda asociados. ### Términos de búsqueda \{#search-terms\} En la pestaña **Search terms**, puedes ver todas las métricas agrupadas por términos de búsqueda y sus palabras clave asociadas. ### Palabras clave negativas \{#negative-keywords\} En la pestaña **Negative keywords**, puedes ver la lista de palabras clave negativas y sus métricas. ### Anuncios \{#ads\} En la pestaña **Ads**, puedes ver la lista de anuncios y sus métricas, para identificar qué configuración de anuncio está generando resultados. ## Métricas \{#metrics\} Las columnas disponibles para mostrar están agrupadas en varias categorías principales: - **General**: Propiedades meta de anuncios y campañas. - **Performance**: Información sobre cómo los usuarios responden a tus anuncios. - **Insights** (solo para palabras clave y términos de búsqueda): Cuota de impresiones, posición y popularidad de búsqueda. - **Advanced downloads**: Nuevas descargas y redownloads, incluyendo los valores Total, View-Through y Tap-Through. - **Conversions**: Métricas relacionadas con los ingresos vinculadas a anuncios y campañas. - **Cohort**: Análisis temporal de grupos de usuarios. Para consultar la lista completa de métricas y sus descripciones, consulta el [artículo](adapty-ads-manager-metrics). ## Gráficos \{#charts\} En cada pestaña puedes ver gráficos que muestran cómo cambian las distintas métricas a lo largo del tiempo durante el rango de fechas seleccionado. Ajusta el período usando el selector **Date range** en la parte superior derecha. 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. Para abrir el gráfico de métricas de una campaña o grupo de anuncios concreto: 1. Abre la pestaña **Campaigns** o **Ad groups** 2. Desplázate hacia abajo hasta la tabla 3. Encuentra la fila con la campaña o el grupo de anuncios que quieres ver 4. Haz clic en chart icon junto a su nombre Las insignias que aparecen sobre un gráfico indican los días en que se realizaron cambios en la campaña o el grupo de anuncios. Para [encontrar cambios que puedan explicar una subida o bajada en una métrica](ads-manager-change-history#discover-correlations-between-campaign-performance-and-setting-changes), haz clic en la insignia sobre la fecha que quieres investigar. El gráfico de cada pestaña solo muestra insignias después de que selecciones un grupo de campaña en el menú desplegable del encabezado. :::tip En la pestaña **Keywords**, también puedes [explorar gráficos a nivel de palabra clave](ads-manager-manage-keywords#explore-keyword-level-charts). ::: ## Personaliza qué métricas mostrar \{#customize-which-metrics-to-show\} En cada pestaña puedes personalizar qué columnas se muestran y en qué orden. También puedes guardar vistas para volver a ellas rápidamente en cualquier momento. Para personalizar las columnas mostradas y su orden, haz clic en **Columns > Edit columns** en la parte superior derecha. Después, selecciona las columnas de la lista y ordénalas arrastrándolas y soltándolas. Haz clic en **Save** para aplicar los cambios. Para guardar un preset de vista, vuelve a hacer clic en el desplegable **Columns** y haz clic en el icono **Save** junto a **Unsaved**. Escribe el nombre del preset y haz clic en **Save**. A partir de ahora, puedes cambiar entre distintas vistas usando el desplegable **Columns**. ## Controles \{#controls\} En la página **Analytics** tienes cinco controles principales: 1. **Pinned metrics**: Selecciona métricas para anclarlas en la barra sobre la vista de tabla. 2. **Date ranges**: Ver [más](controls-filters-grouping-compare-proceeds#set-the-date-range). 3. **Filters**: Filtra por valores de métricas o propiedades de campaña. 4. **Store commission and taxes**: Ver [más](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). 5. **Search**: Busca por nombres de campaña. --- # File: adapty-ads-manager-metrics --- --- title: "Métricas en Adapty Ads Manager" description: "Consulta el análisis de la app en Adapty Ads Manager." --- Adapty Ads Manager ofrece métricas completas para medir el rendimiento de las campañas y el comportamiento de los usuarios. :::tip Los nombres de métricas de esta página también son aceptados por el CLI de Adapty, que los devuelve como JSON para cualquier nivel de la cuenta — consulta [Comandos de Ads Manager](developer-cli-ads-manager-reference#metrics). ::: ## 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 del 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 dentro de 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 dentro de una ventana de atribución de 30 días. | | Redescargas (total) | El número total de redescargas por clic y por visualización dentro del período del informe. | | Redescargas (por visualización) | Redescargas 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. | | Redescargas (por clic) | Redescargas 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. | ## Insights \{#insights\} Dado que los resultados de búsqueda de App Store pueden mostrar anuncios en más de una posición, tanto Impression share como Rank existen en dos versiones. Las versiones son columnas separadas, por lo que puedes mostrar una u otra o ambas al mismo tiempo: - **(all)**: Todas las posiciones de anuncio, incluida la primera. - **(first)**: Solo la primera posición de anuncio, al principio de los resultados. | Métrica | Descripción | Período | |---------|-------------|---------| | Cuota de impresiones (todas) | Con qué frecuencia aparece tu anuncio en cualquier posición cuando alguien introduce este término de búsqueda. | Rango de fechas seleccionado | | Cuota de impresiones (primera) | Con qué frecuencia aparece tu anuncio en la primera posición cuando alguien introduce este término de búsqueda. | Rango de fechas seleccionado | | Posición (todas) | Cómo se compara tu app con otras para este término de búsqueda en el mismo país o región. 1 significa que tu anuncio aparece con más frecuencia que el de cualquier otra app, en cualquier posición. | Último valor diario reportado por Apple | | Posición (primera) | Cómo se compara tu app con otras para este término de búsqueda en el mismo país o región. 1 significa que tu anuncio aparece en la primera posición con más frecuencia que el de cualquier otra app. | Último valor diario reportado por Apple | | Popularidad de búsqueda (actual) | La popularidad de los términos de búsqueda según el país o región. La escala es de 1 a 5, siendo 5 el mayor volumen de búsqueda. | Último valor diario reportado por Apple | Apple informa el porcentaje de impresiones y la posición para los términos de búsqueda que escriben los usuarios, no para tus palabras clave. Con la concordancia amplia, tu anuncio para la palabra clave "photo editor" también puede aparecer cuando alguien escribe "photo editor free" o "edit pictures". En la pestaña **Keywords**, Adapty calcula los valores de cada palabra clave a partir de todos los términos de búsqueda con los que coincidió. Según las [definiciones de métricas de Apple](https://ads.apple.com/app-store/help/reporting/0023-reporting-options-and-definitions), el porcentaje de impresiones solo es exacto hasta el 90%, por lo que los valores más altos se muestran como **>90%**. En las pestañas **Keywords** y **Search terms**, un guion significa que Apple no tiene datos para el período de la métrica. --- # 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. :::tip Las campañas también se pueden crear y actualizar desde el terminal, lo que resulta útil para repetir la misma configuración en varias campañas. Consulta [Comandos de Ads Manager](developer-cli-ads-manager-reference#campaigns). ::: ## 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, pero conservando su configuración y sus 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. ::: :::tip Para añadir o pujar por palabras clave en grandes lotes, usa el CLI de Adapty. Lee las palabras clave desde un archivo y aplica hasta 100 por llamada — consulta [Comandos de Ads Manager](developer-cli-ads-manager-reference#keywords). ::: ## 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 tres pestañas: **Metrics**, **Bid History** y **Change history**. - La pestaña **Metrics** muestra un gráfico con métricas a lo largo del tiempo. Puedes añadir y eliminar métricas de la misma forma 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 un marcador 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 cayó o subió bruscamente tras un cambio, el marcador indica exactamente cuándo ocurrió. - La pestaña **Bid History** lista 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). - La pestaña **Change history** lista los cambios realizados en la palabra clave, incluyendo [los realizados fuera de Adapty](ads-manager-change-history), con los valores antes y después. --- # 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 la atribución de Apple Ads en Adapty Ads Manager" description: "Crea segmentos a partir de campañas, grupos de anuncios y palabras clave de Apple Ads en la página de segmentos de Adapty Ads Manager." --- Puedes crear [segmentos](segments) de usuarios directamente desde [Adapty Ads Manager](adapty-ads-manager) seleccionando las campañas, grupos de anuncios o palabras clave de origen. Esto facilita la personalización de 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 distintos productos y precios, ejecutar pruebas A/B y personalizar el aspecto del paywall. :::tip Para crear un segmento para cada campaña desde un script o una herramienta de codificación con IA, usa [`adapty asa segments create`](developer-cli-ads-manager-reference#segments) en la CLI de Adapty. ::: ## 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. ## Abrir la página de segmentos \{#open-the-segments-page\} La página **Segments** muestra los segmentos que creaste desde Apple Ads para una app, los anuncios que incluye cada segmento, el número de perfiles y los placements que lo usan. Para abrir la página, en la barra lateral de Adapty Ads Manager, haz clic en **Segments** y elige una app. Adapty crea los segmentos en la aplicación de Adapty que coincide con tu app de Apple Ads por **Apple app ID** o **Bundle ID**. Si ninguna app de Adapty coincide, la página te pedirá que elijas una en el desplegable **Choose Adapty app**. Tu navegador recuerda esta elección, pero otros navegadores y compañeros de equipo no la verán. Para que la coincidencia sea visible para todos, añade el **Apple app ID** en los [ajustes de integración con App Store](app-store-connection-configuration#step-1-provide-bundle-id-and-apple-app-id) de la app de Adapty. ## Crear un segmento \{#create-a-segment\} Un segmento incluye a los usuarios que instalaron tu aplicación tras pulsar un anuncio de las campañas, grupos de anuncios o palabras clave que añadas a él. Para crear un segmento: 1. Abre el editor de segmentos de una de estas formas: - En la página **Segments**, haz clic en **+ New segment**. - En **Ads Manager**, cambia a la pestaña **Campaigns**, **Ad groups** o **Keywords** y selecciona las filas que quieras incluir. A continuación, haz clic en **Actions > Create segment from campaigns/ad groups/keywords**. Todas las filas seleccionadas deben pertenecer a una misma aplicación. 2. En **Name**, introduce el nombre del segmento. Adapty añade el prefijo fijo `[ASA]` al nombre, para que puedas distinguir los segmentos de Apple Ads del resto de segmentos en Adapty. 3. Para añadir anuncios al segmento, en **Who's in it**, haz clic en **+ Add campaigns, ad groups or keywords**. Selecciona las campañas, grupos de anuncios o palabras clave y haz clic en **Add**. Un segmento puede incluir palabras clave, o campañas y grupos de anuncios, pero no ambos a la vez. 4. Haz clic en **Create segment**. Si añades tanto campañas como grupos de anuncios, Adapty guarda las campañas como los grupos de anuncios que contienen al guardar el segmento. Los grupos de anuncios que añadas posteriormente a esas campañas no se incorporan al segmento. :::tip Los chips de **Ideas from your ads** en la página **Segments** sugieren segmentos creados a partir de tus anuncios, como el nombre de un grupo de anuncios usado en varias campañas. Haz clic en una idea para abrirla en el editor y modifica lo que necesites antes de guardar. ::: ## Usar el segmento en un placement \{#use-the-segment-in-a-placement\} Adapty no añade un segmento nuevo a ningún placement. Después de crear un segmento, el diálogo **Segment created** muestra los placements de la aplicación de Adapty. Para mostrar a los usuarios del segmento su propio paywall, onboarding o prueba A/B, abre un placement y [añade el segmento como audiencia](add-audience-paywall-ab-test). Para añadir el segmento a un placement más adelante, en la página **Segments**, haz clic en **Use** en la columna **Used in** del segmento. ## Añadir anuncios a un segmento existente \{#add-ads-to-an-existing-segment\} Para añadir campañas, grupos de anuncios o palabras clave a un segmento que ya tienes: 1. En **Ads Manager**, cambia a la pestaña **Campaigns**, **Ad groups** o **Keywords** y selecciona las filas. 2. Haz clic en **Actions > Add to segment…**. 3. Selecciona el segmento. Un segmento de palabras clave no puede incluir campañas ni grupos de anuncios, y un segmento de campañas y grupos de anuncios no puede incluir palabras clave, por lo que el diálogo deshabilita los segmentos del otro tipo. 4. En el editor de segmentos, revisa los anuncios y haz clic en **Save**. Adapty no modifica el segmento hasta que lo guardes. ## Editar o eliminar un segmento \{#edit-or-delete-a-segment\} Para abrir un segmento en el editor, en la página **Segments**, haz clic en la fila del segmento. Desde allí puedes cambiarle el nombre y añadir o eliminar anuncios. Si un placement usa el segmento como audiencia, guardar el segmento cambia de inmediato qué usuarios ven el paywall u onboarding de esa audiencia. La sección **Used in** del editor muestra estos placements. Para eliminar un segmento, en el editor haz clic en **Delete** y confirma. Adapty elimina el segmento y lo quita de las audiencias de todos los placements que lo usen. ## Segmentos que no aparecen en la página \{#segments-that-dont-appear-on-the-page\} La página **Segments** muestra únicamente los segmentos con el prefijo `[ASA]` cuya única condición sea una lista de campañas, grupos de anuncios o palabras clave de Apple Ads. Si editas un segmento `[ASA]` en **Profiles & Segments** y añades otra condición, la página **Segments** lo ocultará. Para encontrar el segmento, haz clic en **open in Adapty** bajo la lista de segmentos. Esto abre **Profiles & Segments**. --- # File: ads-manager-automations --- --- title: "Automatizaciones en Adapty Ads Manager" description: "Automatiza tus campañas de Apple Ads con reglas de palabras clave, términos de búsqueda, grupos de anuncios y campañas." --- Las reglas de automatización del Adapty Ads Manager actúan sobre el rendimiento de tus Apple Ads sin intervención manual. Defines condiciones basadas en métricas de rendimiento, estableces una acción y programas cuándo se ejecuta la regla. Hay cuatro tipos de reglas de automatización, una para cada nivel de la estructura de tu cuenta: | Tipo de regla | Se aplica a | Acciones | Caso de uso | |---|---|---|---| | [Reglas de palabras clave](ads-manager-automations-keyword-rules) | Palabras clave | Cambiar puja, activar, pausar, añadir como palabra clave, añadir como negativas | Gestionar el ciclo de vida completo de las palabras clave según el rendimiento | | [Automatizaciones de términos de búsqueda](ads-manager-automations-search-terms) | Términos de búsqueda (consultas reales de usuarios) | Promover términos a palabras clave, añadir como negativas | Construir un embudo de palabras clave a partir del tráfico de descubrimiento | | [Reglas de grupos de anuncios](ads-manager-automations-ad-group-rules) | Grupos de anuncios | Cambiar puja predeterminada, cambiar objetivo de CPA, activar, pausar | Ajustar pujas y límites de gasto a nivel de grupo de anuncios | | [Reglas de campaña](ads-manager-automations-campaign-rules) | Campañas | Cambiar presupuesto diario, activar, pausar | Controlar el presupuesto y el estado a nivel de campaña | <CustomDocCardList ids={['ads-manager-automations-keyword-rules', 'ads-manager-automations-search-terms', 'ads-manager-automations-ad-group-rules', 'ads-manager-automations-campaign-rules']} /> Para empezar desde una regla prediseñada en lugar de crearla desde cero, haz clic en **Templates** en el encabezado de Automations. Las plantillas están disponibles para reglas de palabras clave, términos de búsqueda y campañas; las reglas de grupos de anuncios aún no tienen ninguna. :::tip Las reglas también se pueden gestionar desde el terminal, incluida una ejecución en modo de prueba que evalúa una regla sin cambiar nada en Apple Ads. Consulta [Comandos de Ads Manager](developer-cli-ads-manager-reference#automations). ::: ## Explorar registros \{#explore-logs\} La pestaña **Logs** muestra un historial completo de todas las ejecuciones de reglas, para que puedas hacer un seguimiento del rendimiento y solucionar problemas. Para ver los registros, en la barra lateral izquierda ve a **Automations** y cambia a la pestaña **Logs**. La tabla muestra: - **Rule name**: El nombre de la regla de automatización. - **Created at**: Fecha y hora en que se ejecutó la regla. - **Description**: Resumen de la acción realizada, las condiciones aplicadas y los objetivos (por ejemplo, "Decrease bid by 25%, IF Spend (previous 3 days) > $20 AND Installs (previous 3 days) is 0, Applied to: Keywords in 3 campaigns"). - **Status**: Estado de la ejecución: Success, Ran with errors o Failed. Para exportar los datos de ejecución detallados como CSV, haz clic en el icono de descarga junto a una entrada del registro. Los cuatro tipos de reglas generan un CSV descargable con los elementos que han afectado. Las columnas varían según el tipo; por ejemplo: - **Keyword rules**: Enumera todas las palabras clave afectadas con sus pujas anteriores y nuevas. - **Search term automations**: Enumera cada término de búsqueda evaluado con su campaña de origen y grupo de anuncios, grupo de anuncios de destino, resultado de la acción, resultado de la negación, puja y tipo de concordancia. Para filtrar la tabla por nombre de regla, utiliza el filtro situado encima de la tabla. Si no ves las últimas actualizaciones, haz clic en **Refresh table** para obtener los datos más recientes. También puedes hacer seguimiento del tiempo de ejecución de las reglas en la pestaña **Automations**, usando las columnas **Date last run** y **Date next run**. ## Reglas de inicio y pausa \{#launch-and-pause-rules\} Para iniciar o pausar una regla, activa el interruptor en la columna **Status** junto a la regla. Pausar una regla te permite desactivarla temporalmente durante períodos de campaña específicos o mientras realizas ajustes, sin perder la configuración de la regla. :::note Cuando una regla está habilitada, puede anular tus cambios de puja manuales la próxima vez que se ejecute si se cumplen las condiciones. Para depender temporalmente de la gestión manual, pausa la regla en lugar de eliminarla. ::: ## Ejecutar una regla de inmediato \{#run-a-rule-immediately\} Aunque las reglas se ejecutan según su programación, puedes activar cualquiera de ellas en cualquier momento: 1. Haz clic en los tres puntos junto a la regla. 2. Selecciona **Run rule right now**. La regla se ejecuta de inmediato y aplica la acción configurada a todos los elementos que coincidan. Úsalo tras añadir palabras clave, grupos de anuncios u otros cambios estructurales, cuando quieras que la regla se ejecute antes de la próxima hora programada. ## Duplicar una regla \{#duplicate-a-rule\} Para ahorrar tiempo al crear reglas similares: 1. Haz clic en los tres puntos junto a la regla. 2. Selecciona **Duplicate**. 3. Edita la regla duplicada según sea necesario y haz clic en **Save**. ## Eliminar una regla \{#delete-a-rule\} Para eliminar una regla que ya no necesitas: 1. Haz clic en los tres puntos junto a la regla. 2. Selecciona **Delete** y confirma. Eliminar una regla es permanente. Si es posible que la necesites más adelante, paúsala en su lugar. --- # 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: **Once**, **Every hour**, **Every 3 hours**, **Every 6 hours**, **Every 12 hours**, **Every day**, **Every 2 days**, **Every 5 days**, **Every 7 days**, **Every 14 days**, **Every 30 days** o **Every 90 days**. Las mismas frecuencias están disponibles para todos los tipos de regla. - Selecciona la hora de ejecución, en punto (todos los horarios están en UTC). - Para ejecutar la regla una vez en cuanto la guardes, además de según su programación, activa **Run this rule right now**. Una regla configurada como **Once** no tiene programación recurrente ni hora de ejecución: se ejecuta una única vez, justo después de guardarla. Las reglas se ejecutan a la hora programada en UTC. La ejecución suele finalizar 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. Search term rules offer the Performance metrics only — spend, impressions, taps, downloads, and the rates derived from them. Conditions on trials, suscripciones, and revenue are available in keyword, ad group, and campaign rules. Las reglas de términos de búsqueda ofrecen solo las métricas de rendimiento: gasto, impresiones, taps, descargas y las tasas derivadas de ellas. Las condiciones sobre pruebas, suscripciones e ingresos están disponibles en las reglas de palabras clave, grupo de anuncios y campaña. 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**. Use 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 50 o más impresiones y 0 taps, exclúyelo automáticamente. 8. En la sección **Schedule**: - Elige la frecuencia: **Once**, **Every hour**, **Every 3 hours**, **Every 6 hours**, **Every 12 hours**, **Every day**, **Every 2 days**, **Every 5 days**, **Every 7 days**, **Every 14 days**, **Every 30 days** o **Every 90 days**. Las mismas frecuencias están disponibles para cada tipo de regla. - Selecciona la hora de ejecución, en punto (todos los horarios están en UTC). - Para ejecutar la regla una vez en cuanto la guardes, además de según su programación, selecciona **Run this rule right now**. Una regla configurada como **Once** no tiene programación recurrente ni hora de ejecución: se ejecuta una sola vez, justo después de guardarla. 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. Escribe un **Rule name**. 3. En **Apply to**, elige un [ámbito](#available-conditions) y selecciona los grupos de anuncios. 4. En **Conditions**, añade una o más [condiciones](#available-conditions). Puedes combinarlas 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 — desde **Every hour** hasta **Every 90 days**, o **Once** — y su hora de inicio (UTC). Para ejecutar la regla también en cuanto la guardes, selecciona **Run this rule right now**. 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. Escribe 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 combinarlas 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 — desde **Every hour** hasta **Every 90 days**, o **Once** — y su hora de inicio (UTC). Para ejecutarla también en cuanto la guardes, selecciona **Run this rule right now**. 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. :::tip Para incorporar estos datos a un informe de forma programada, la CLI de Adapty los devuelve para hasta cinco apps en un solo comando — consulta los [comandos de Ads Manager](developer-cli-ads-manager-reference#competitors). ::: ## 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 un Share of Voice bajo 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 y que tú no hayas probado. Son términos 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 tracción. --- # 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-keyword-suggester --- --- title: "Sugeridor de palabras clave en Adapty Ads Manager" description: "Expande una frase semilla en frases de búsqueda relacionadas de App Store y contexto de categorías de Apple, y crea una lista corta de palabras clave para Apple Ads e investigación ASO." --- La función Keyword Suggester convierte una frase semilla en ideas de palabras clave para tus campañas de Apple Ads y te ayuda a descubrir términos en los que aún no estás pujando. Devuelve dos tipos de resultados a partir de datos de Apple: frases de búsqueda relacionadas con su popularidad, y el contexto de categoría en el App Store alrededor de tu semilla. Úsala para construir una lista de candidatos de palabras clave a partir de señales proporcionadas por Apple, en lugar de hacer una lluvia de ideas por tu cuenta. Úsala para: - **Expande una idea a lenguaje de búsqueda real**: Introduce un producto, función o intención de usuario y ve las frases que Apple asocia con él. - **Revisa una lista de palabras clave existente**: Consulta las frases que ya recopilaste en el índice de frases de Apple antes de investigarlas más a fondo. - **Descubre conceptos adyacentes**: El contexto de categoría muestra cómo Apple clasifica tu semilla, para que puedas detectar intenciones relacionadas y usarlas como próxima semilla. :::tip Los resultados del Keyword Suggester no están limitados a un mercado concreto. Para ver qué términos de búsqueda tienen mayor posicionamiento en un país y categoría de App Store específicos, utiliza [Search Popularity](ads-manager-search-popularity). Para ver en qué palabras clave pujan tus competidores, utiliza [Market Intelligence](ads-manager-market-intelligence). ::: ## Obtén sugerencias de palabras clave \{#get-keyword-suggestions\} Para iniciar una búsqueda, en la barra lateral izquierda, ve a **Keyword Suggester**. 1. Selecciona un modo de búsqueda: - **Explore a phrase**: Expande una semilla — un producto, función o intención de usuario — en frases relacionadas. - **Look up a list**: Busca frases que ya tienes — hasta 50 por búsqueda — en el índice de frases de Apple. Introduce una frase por línea o sepáralas con comas. 2. Selecciona las fuentes de búsqueda: **Phrases**, **Categories** o ambas. La búsqueda por lista solo devuelve coincidencias de frases, por lo que cambiar a **Look up a list** desmarca la casilla **Categories**. 3. Introduce la frase semilla y haz clic en **Find matches**. Cuando editas una búsqueda ya enviada, los resultados siguen mostrando la búsqueda anterior hasta que hagas clic en **Update results**. ## Coincidencias de frases \{#phrase-matches\} La sección **Phrase matches** muestra las frases de búsqueda que Apple devuelve para tu semilla, exactamente tal como las devuelve Apple. Cada frase tiene un valor de **Popularity** — una señal relativa que devuelve Apple, no es volumen de búsqueda, instalaciones ni tasa de conversión. Para recopilar candidatos para tu lista de selección, haz clic en el icono de copiar en la fila de la frase. Cuando estés listo para pujar por las frases recopiladas, añádelas a un grupo de anuncios tal como se describe en [Gestionar palabras clave](ads-manager-manage-keywords). ## Contexto de categoría \{#category-context\} La sección **Category context** muestra la taxonomía de Apple alrededor de tu seed: cada tarjeta incluye el nombre de la categoría, su popularidad y la ruta completa de la categoría. Las categorías son contexto que revela conceptos adyacentes, no palabras clave sobre las que pujar. Cada tarjeta de categoría tiene dos acciones: - **Use as seed**: Rellena el campo seed con el nombre de la categoría, para que puedas explorar el concepto adyacente a continuación. Para ejecutar la nueva búsqueda, haz clic en **Update results**. - **Copy ID**: Copia el ID de categoría cualificado, para que puedas referenciar la categoría exacta más adelante. ## Limitaciones \{#limitations\} - Los resultados de frases y categorías no aceptan filtros de mercado, categoría o período, por lo que no están acotados a un país ni a un intervalo de tiempo. Para rankings específicos por mercado, usa [Popularidad de búsqueda](ads-manager-search-popularity). - Los resultados no son específicos de tu app. - Que Apple no devuelva una frase no significa que nadie la busque. - Los resultados se almacenan en caché hasta seis horas y no son en tiempo real. --- # File: ads-manager-search-popularity --- --- title: "Popularidad de búsqueda en Adapty Ads Manager" description: "Consulta los términos de búsqueda de la App Store con mayor ranking en un mercado, categoría y período seleccionados, con señales de popularidad proporcionadas por Apple." --- Search Popularity muestra los términos de búsqueda del App Store con mayor ranking para un mercado, categoría y semana o mes completos seleccionados, junto con las señales de popularidad proporcionadas por Apple. Úsalo para ver el idioma que usan los usuarios al buscar en un mercado concreto antes de crear una lista de palabras clave, actualizar una campaña o entrar en un nuevo país. La popularidad es una señal de investigación relativa, no un volumen de búsqueda ni una previsión de rendimiento. Úsala para: - **Investiga un mercado antes de entrar en él**: Revisa los términos de búsqueda más populares en un país y categoría objetivo antes de invertir presupuesto allí. - **Prioriza la investigación de palabras clave con datos de Apple**: Compara los términos candidatos por posición y popularidad en lugar de guiarte por la intuición. - **Haz seguimiento de los cambios estacionales y por período**: Desplázate entre semanas o meses completados para comparar qué términos suben y cuáles bajan. :::tip Search Popularity muestra lo que destaca en un mercado concreto. Para ampliar un término que hayas encontrado aquí con frases relacionadas y contexto de categoría, úsalo como punto de partida en [Keyword Suggester](ads-manager-keyword-suggester). ::: ## Ver los términos de búsqueda más populares \{#view-popular-search-terms\} Para cargar un conjunto de resultados, en la barra lateral izquierda, ve a **Search Popularity**. 1. Selecciona un **Market**. La disponibilidad de datos puede variar según el mercado. 2. Selecciona una **App Store category**. 3. Selecciona el **Period type** — **Week** o **Month** — y desplázate al período con las flechas. Los datos semanales corresponden a semanas completas de domingo a sábado; los datos mensuales corresponden a meses naturales completos. 4. Haz clic en **Show results**. La página carga el conjunto de resultados completo — hasta 500 términos — para el mercado, la categoría y el período seleccionados. Apple puede seguir procesando la última semana completada. Si la página indica que la semana aún no está disponible, carga el período anterior. Tras cambiar los filtros, la tabla sigue mostrando la selección anterior hasta que hagas clic en **Update results**. Un aviso encima de la tabla indica a qué mercado, categoría y período pertenecen los resultados visibles. ## Explorar y exportar los resultados \{#explore-and-export-the-results\} - **Search**: Filtra los términos cargados por texto. El contador muestra cuántos términos coinciden. - **Sort**: Ordena la tabla por posición — **Highest rank first** o **Lowest rank first**. - **Rows**: Define cuántos términos muestra una página. - **Export page**: Descarga la página visible como archivo CSV. - **Export all**: Descarga el conjunto completo de resultados como archivo CSV, con el contexto de mercado, categoría y período. Si hay una búsqueda activa, descarga solo los términos que coinciden. Cuando estés listo para pujar por los términos de tu investigación, añádelos a un grupo de anuncios como se describe en [Gestionar palabras clave](ads-manager-manage-keywords). ## Cómo interpretar las métricas \{#how-to-read-the-metrics\} | Columna | Significado | |---|---| | **Rank** | La posición del término en el mercado, categoría y período seleccionados. Un rango menor indica una posición más alta. | | **Genre score** | Un valor de popularidad relativo que Apple devuelve para la categoría de App Store seleccionada. | | **Popularity** | Una puntuación de popularidad normalizada del 1 al 100. Es una señal relativa, no un porcentaje ni un volumen de búsqueda. | | **Level** | Un nivel de popularidad general del 1 al 5 que facilita identificar diferencias amplias de un vistazo. No es una valoración de la app. | ## Limitaciones de los datos \{#data-limitations\} - Los valores de popularidad son señales relativas de Apple. No son volumen de búsqueda, porcentajes, instalaciones, tasas de conversión ni una previsión de rendimiento. - Un conjunto de resultados contiene hasta 500 términos para el mercado, la categoría y el período seleccionados. - Solo están disponibles los períodos completados: semanas de domingo a sábado y meses naturales completos. - Los resultados se almacenan en caché hasta seis horas y no son en tiempo real. --- # File: ads-manager-change-history --- --- title: "Seguimiento de cambios en tus campañas de Apple Ads" description: "Descubre qué cambió en tus campañas de Apple Ads, en Adapty o fuera de él. Para cada cambio, consulta quién lo realizó y los valores antes y después." --- La página Change History registra los cambios realizados en tu configuración de Apple Ads. Cada cuenta (grupo de campañas) tiene su propio historial. Puedes seleccionar el grupo de campañas activo desde el menú desplegable. El historial cubre los últimos 6 meses hasta la fecha actual, independientemente de cuándo hayas conectado la cuenta o qué herramientas hayas utilizado para gestionarla. Muestra los cambios realizados en las siguientes entidades: - Campañas - Grupos de anuncios - Palabras clave - Palabras clave negativas - Anuncios Expande una entrada de la lista para ver [en qué consistió el cambio y quién lo autorizó](#entry-details). :::tip Para ver el Historial de cambios desde el terminal o con un agente de IA, utiliza los [comandos `change-history` de la CLI de Adapty](developer-cli-ads-manager-reference#change-history). ::: ## Búsqueda, filtros y controles \{#search-filters-and-controls\} Puedes reducir la lista de cambios con los siguientes filtros: | Control | Limita la lista a | Valor por defecto | |---|---|---| | Selector de rango de fechas | Un período | Último mes | | Selector de tipo de objeto | **Campaigns**, **Ad groups**, **Keywords**, **Negative keywords** o **Ads** | Todas las entidades | | Botones de tipo de cambio | Cambios de tipo **Created**, **Updated** o **Removed** | Todos los cambios | | Campo de búsqueda | Una palabra clave, campaña o grupo de anuncios concreto (nombre exacto o ID, distingue mayúsculas y minúsculas) | Vacío | ## Detalles de la entrada \{#entry-details\} Los cambios están agrupados por fecha, empezando por los más recientes. Cada entrada indica el objeto afectado y su ubicación, por ejemplo, una palabra clave y su grupo de anuncios. Una acción masiva aparece como una sola entrada, por ejemplo, "Se actualizaron 8 palabras clave en 2 grupos de anuncios". Haz clic en la entrada para ver: - Lo que cambió, según el tipo de cambio: - **Updated**: Cada configuración modificada, antes y después - **Created** y **Removed**: Todos los ajustes del objeto añadido o eliminado - El ID de usuario de Apple de la persona que realizó el cambio (Apple no comparte nombres de usuario) - El origen del cambio: - **Apple Ads UI**: El dashboard de Apple Ads - **API**: Todas las demás herramientas, incluido Adapty Ads Manager - **APPLE_SUPPORT**: Soporte de Apple ## Descubre correlaciones entre el rendimiento de las campañas y los cambios de configuración \{#discover-correlations-between-campaign-performance-and-setting-changes\} Para encontrar cambios que puedan explicar un aumento o una caída en una métrica: ### Campañas \{#campaigns\} 1. Abre el gráfico de métricas detalladas de una **Campaign** concreta 2. Localiza la fecha que quieres investigar 3. Haz clic en el badge que aparece sobre esa fecha. - Si no hay badge, nadie modificó la campaña ni sus grupos de anuncios ese día. - Si hay badge, al hacer clic se mostrará el historial de cambios de esa fecha. ### Palabras clave y grupos de anuncios \{#keywords-and-ad-groups\} Abre el gráfico de métricas correspondiente y busca la fecha que quieras investigar. Luego busca los cambios realizados ese día en la pestaña **Change history**. :::tip Cada campaña, grupo de anuncios y palabra clave tiene su propio historial. El historial de un grupo de anuncios no incluye los cambios en su campaña ni en sus palabras clave. Para ver todos los cambios, elimina los filtros. ::: ## Revertir un cambio \{#revert-a-change\} No puedes deshacer un cambio con un solo clic. 1. Inspecciona los detalles del cambio que quieres revertir 2. Anota la propiedad afectada y su valor anterior 3. Sigue las instrucciones específicas del objeto para establecer un nuevo valor: - **Campañas**: [Editar campañas](ads-manager-create-campaign#edit-campaigns) y [Lanzar y pausar campañas](ads-manager-create-campaign#launch--pause-campaigns) - **Grupos de anuncios**: [Editar grupos de anuncios](ads-manager-create-ad-group#edit-ad-groups) y [Lanzar y pausar grupos de anuncios](ads-manager-create-ad-group#launch--pause-ad-groups) - **Palabras clave**: [Editar palabras clave](ads-manager-manage-keywords#edit-keywords) - **Anuncios**: [Editar anuncios](ads-manager-manage-ads#edit-ads) y [Lanzar y pausar anuncios](ads-manager-manage-ads#launch--pause-ads) ## Limitaciones de atribución \{#attribution-limitations\} - Apple no distingue entre las diferentes herramientas que usan su API. Por eso, todos los cambios realizados con una herramienta de terceros aparecen marcados como **API** en la página Change History. No es posible diferenciar un cambio hecho en Adapty Ads Manager de uno hecho por otra herramienta. - Apple no informa los nombres de usuario internos de herramientas de terceros, incluido Adapty Ads Manager. Si un cambio se originó en el Ads Manager, puedes atribuirlo a un usuario específico o a una regla de automatización desde el [**Bid History**](ads-manager-manage-keywords#bid-history). --- # 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 Para preguntarle a una herramienta de IA cosas como "¿Qué canal fue más rentable el mes pasado?" y obtener respuestas de tus datos de atribución, instala el [skill de Attribution](developer-cli-attribution-skill). Ejecuta los [comandos CLI de Attribution](developer-cli-attribution) y lee los resultados por ti. ::: :::tip ¿Quieres saber más sobre cómo la atribución de Adapty puede serte útil? [Reserva una llamada](https://calendly.com/adapty-murat/meeting-1) 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 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 en publicidad y los ingresos se conectan.** Adapty extrae datos de gasto publicitario de las plataformas de anuncios compatibles (actualmente Meta Ads, TikTok for Business y [Google Ads](ua-google-ads)) y vincula 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. ::: ## Canales \{#channels\} A primera vista, tres canales de atribución pueden parecer idénticos, pero Adapty los asigna por razones distintas: - **Organic**: No se encontró ningún clic de ningún tipo para la instalación. - **Google Search**: La instalación llevaba el etiquetado orgánico propio de Google (`utm_medium=organic` de una superficie de Search, Discover, News, Images o Maps). Es tráfico no atribuido, igual que Organic, etiquetado por Google. - **Google Play**: Se recibió un ID de clic de Google Ads con la instalación, pero no coincidió con ninguna campaña en tu cuenta de Google Ads conectada. Es tráfico de pago que Adapty no pudo atribuir más detalladamente, no es orgánico. Cuando más de una fuente reclama la misma instalación —por ejemplo, un toque en Apple Search Ads y un clic en Google Ads ocurrieron antes de la misma instalación—, Adapty lo resuelve por último clic: Apple Search Ads gana los empates (su atribución es a nivel de dispositivo y determinista), un clic de Google Ads estrictamente posterior lo supera, y tu propio enlace de seguimiento u orgánico tiene menor prioridad que ambos. --- # 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 La Atribución de Adapty está disponible con: - iOS, Android y Flutter SDK versión 3.9.1 o superior. - React Native y Capacitor SDK versión 3.10.0 o superior. - Unity SDK versión 3.12.0 o superior. - Kotlin Multiplatform SDK versión 3.15.0 o superior. A partir de la versión 4.1 del SDK, la Atribución de Adapty está desactivada por defecto en el SDK. [Actívala en el código de tu app](#step-1-enable-adapty-attribution-in-the-sdk). ::: ## 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. Habilitar la Atribución de Adapty en el SDK \{#step-1-enable-adapty-attribution-in-the-sdk\} A partir de la versión 4.1 del SDK de Adapty, la Atribución de Adapty está deshabilitada por defecto. Para que el SDK registre instalaciones y entregue los detalles de instalación a tu app, habilítala al activar el SDK: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(adaptyAttributionEnabled: true) ``` </TabItem> <TabItem value="kotlin" label="Kotlin"> ```kotlin showLineNumbers val config = AdaptyConfig.Builder("YOUR_PUBLIC_SDK_KEY") .withAdaptyAttributionEnabled(true) .build() Adapty.activate(applicationContext, config) ``` </TabItem> <TabItem value="java" label="Java"> ```java showLineNumbers AdaptyConfig config = new AdaptyConfig.Builder("YOUR_PUBLIC_SDK_KEY") .withAdaptyAttributionEnabled(true) .build(); Adapty.activate(getApplicationContext(), config); ``` </TabItem> <TabItem value="rn" label="React Native (TS)"> ```typescript showLineNumbers adapty.activate('YOUR_PUBLIC_SDK_KEY', { adaptyAttributionEnabled: true, }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform"> ```kotlin showLineNumbers val config = AdaptyConfig .Builder("YOUR_PUBLIC_SDK_KEY") .withAdaptyAttributionEnabled(true) .build() Adapty.activate(configuration = config) ``` </TabItem> <TabItem value="capacitor" label="Capacitor (TS)"> ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { adaptyAttributionEnabled: true, } }); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)"> ```dart showLineNumbers await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAdaptyAttributionEnabled(true) ); ``` </TabItem> </Tabs> En versiones del SDK anteriores a la 4.1, Adapty Attribution se activa automáticamente — no se requieren cambios en el código. ## Paso 2. Abre Adapty Attribution \{#step-2-open-adapty-attribution\} :::important Si **Attribution** no aparece al hacer clic en el logotipo 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 logotipo de Adapty en el encabezado y selecciona **Attribution**. Los eventos de suscripción empiezan a fluir hacia Adapty Attribution de forma automática. Los datos de campaña aparecen después de que conectes una fuente de datos en el paso 3. 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 3. Conecta tu plataforma de anuncios y añade enlaces de seguimiento \{#step-3-connect-your-ad-platform-and-add-tracking-links\} Adapty utiliza enlaces de seguimiento para conectar las instalaciones de la app con los datos de las campañas. Debes usar un enlace de seguimiento como URL de destino en cada campaña publicitaria que quieras medir en Adapty Attribution. Si publicas anuncios en varias plataformas, configura los enlaces de seguimiento para cada una por separado. Hay dos formas en que Adapty trabaja con plataformas de anuncios: - **Integraciones nativas (Meta Ads, TikTok Ads, Google Ads).** Adapty se conecta directamente a 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. Las campañas de aplicaciones de Google Ads no necesitan ningún enlace de seguimiento — consulta [Integrar Google Ads con la atribución de Adapty](ua-google-ads). - **Solo enlaces de seguimiento (el resto de plataformas publicitarias).** Adapty no se conecta directamente con la plataforma de anuncios. Los enlaces de seguimiento se crean manualmente y todos los parámetros de campaña deben definirse de forma explícita al crear el enlace. Los datos de gasto en publicidad no están disponibles para 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 aplicación 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 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 Para que tu anuncio de Meta sea aprobado más fácilmente, divide el enlace por el signo `?`. En el campo **Website URL**, pega la parte que va antes del `?`, por ejemplo `https://api-ua.adapty.io/api/v1/attribution/c/YOUR_CODE`. Cada campaña web tiene su propio código, así que cópialo de la campaña que estás configurando, no de otro anuncio. Pega todo lo que va después del `?` en el campo **URL parameters** de la sección **Tracking**. Consulta [Crear anuncios](meta-create-campaign#step-5-create-ads) en la guía de Meta Ads. ::: 7. Ahora, cuando lances 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 aplicación 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 enlace** o para un enlace específico de plataforma. Luego, en TikTok Ads Manager, al crear tu anuncio, pega este valor en el campo **Destination URL**. Esto permitirá que Adapty conecte las instalaciones y compras con los anuncios en TikTok. Consulta [Añadir contenido del anuncio](tiktok-create-campaign#step-5-add-ad-content) en la guía de TikTok Ads. 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="Other ad platforms"> Para crear un enlace de seguimiento para otras plataformas de anuncios: 1. En el dashboard de Adapty Attribution, abre **Integrations** y selecciona la tarjeta **Custom** en el grupo **Network**. 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. 4. Por defecto, estás creando un One Link. Detecta automáticamente la plataforma del usuario y lo redirige a la App Store o a Google Play tras registrar el clic. Si prefieres usar URLs de redirección distintas para cada plataforma, desmarca la casilla **One Link** e introduce manualmente los enlaces a la store 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 a la store correcta. - **iOS link** o **Android link** — versiones opcionales específicas 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 del anuncio. </TabItem> </Tabs> ## Paso 4. Lanza tu campaña web-to-app y consulta los resultados \{#step-4-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 Adapty Attribution analytics](ua-analytics), verás métricas a nivel de campaña como: - Instalaciones y conversiones - Ingresos por suscripciones y compras - Desglose de rendimiento por plataforma de anuncios, 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-analytics --- --- title: "Analíticas en Adapty Attribution" description: "Consulta las analíticas de la app en Adapty Attribution." --- [Analytics](https://app.adapty.io/ua/analytics) es una sección del Adapty Attribution Dashboard que te permite ver múltiples métricas de campañas en un solo lugar. Puedes personalizar qué gráficos quieres ver y consultar los datos de todas tus campañas a la vez. La sección de Analytics tiene dos pestañas: - **Analytics**: Una tabla basada en cohortes con las métricas de tu campaña. - **Charts**: Gráficos de líneas para métricas individuales a lo largo del tiempo. :::tip Los mismos datos están disponibles desde el terminal a través de los [comandos CLI de Attribution](developer-cli-attribution). Para que una herramienta de codificación con IA los lea y responda preguntas como "¿Deberíamos cancelar esta campaña?", instala el [skill de Attribution](developer-cli-attribution-skill). ::: ## Métricas \{#metrics\} Adapty Attribution 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 las métricas clave también se ofrecen como **métricas de cohorte** para el análisis temporal de grupos de usuarios. :::note Las compras web —de [Stripe](ua-stripe), [Paddle](ua-paddle) o [FunnelFox](ua-funnelfox)— solo aparecen en Adapty Attribution analytics, no en los análisis principales de Adapty. Como no tienen evento de instalación de la app, Adapty las agrupa por cohortes según la fecha del primer inicio de prueba o compra: elige un rango de fechas que incluya esa fecha. Si filtras por plataforma, incluye el funnel web, o las compras web quedarán ocultas. ::: Consulta la lista completa de métricas [aquí](ua-metrics). ## Personaliza las métricas que se muestran \{#customize-which-metrics-to-show\} Puedes personalizar qué métricas mostrar y en qué orden. Para hacerlo, haz clic en el nombre del preset actual (al principio es **Default**) en la esquina superior derecha, selecciona **Edit columns** y luego elimina los gráficos que no necesites, añade más o reorganiza los existentes arrastrando y soltando. Para las métricas de cohorte, puedes añadir una o varias cohortes a la vez. Selecciónalas de las cohortes existentes o crea unas personalizadas. También puedes guardar las vistas que más usas como presets para cambiar entre ellas rápidamente. Haz clic en el nombre del preset actual y luego en el icono **Save**, o bien, en la ventana **Edit columns**, haz clic en **Save as preset**. ### Preset de Google Ads \{#google-ads-preset\} Si tu empresa tiene una cuenta de Google Ads conectada, la lista de presets también ofrece un preset integrado de **Google Ads**. Para usarlo, haz clic en el nombre del preset actual y selecciona **Google Ads**. Justo después de **Spend**, el preset añade **Impressions**, **Clicks** y las [métricas de Google Ads](ua-metrics#google-ads-metrics) excepto **Cost per Conversion (Network)**. Mantiene tu rango de fechas y la configuración de ingresos. Cuando seleccionas **Google Ads** por primera vez, Adapty lo guarda como un preset normal. A partir de entonces, cualquier persona de tu empresa puede seleccionarlo y editar sus columnas como cualquier otro preset. ## Controles \{#controls\} En la página **Analytics** tienes cuatro controles principales: - **Time ranges**: Ver [más](controls-filters-grouping-compare-proceeds#set-the-date-range). - **Filters**: Filtra por métricas para ver solo los valores que están por encima o por debajo de un umbral determinado. - **Group by**: Agrupa por parámetros de campaña (como ID de campaña o país) o períodos de tiempo para organizar mejor los resultados. - **Store commission and taxes**: Ver [más](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). ## Conjuntos de filtros \{#filter-sets\} Los conjuntos de filtros te permiten guardar un grupo de filtros y cambiar entre ellos con un solo clic. Úsalos para mantener a mano las vistas que consultas con más frecuencia, como "Campañas con más de $1.000 de gasto" o "Solo iOS". Debajo de los controles principales de filtros, cada conjunto guardado aparece como una etiqueta. Haz clic en una etiqueta para aplicar sus filtros, o haz clic en la **×** de una etiqueta para eliminarla. El conjunto activo aparece resaltado. Para guardar los filtros actuales como un nuevo conjunto: 1. Configura los filtros que quieras guardar. 2. Haz clic en **+** al final de la fila de conjuntos de filtros. 3. Escribe un nombre y haz clic en **Save**. ## Gráficos \{#charts\} La pestaña **Charts** muestra un gráfico de líneas para una sola métrica durante el rango de tiempo seleccionado. Usa la navegación de la izquierda para cambiar entre métricas. Métricas disponibles en los gráficos: - Spend - Impressions - Clicks - CPC - CPM - CTR - Installs - Total Revenue - ARPU - CPI - ARPPU - ROAS - ICR - Cost Per Trial - Cost Per Subscription El mismo rango de tiempo, agrupación, filtros y conjuntos de filtros se aplican tanto en la pestaña **Analytics** como en la de **Charts**. ## Exportar datos \{#export-data\} Para analizar los datos sin procesar de las analíticas, puedes exportarlos en formato CSV haciendo clic en el botón **Export**. --- # File: ua-metrics --- --- title: "Métricas en Adapty Attribution" description: "Conoce las métricas disponibles en Adapty Attribution." --- Adapty Attribution 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 analizar grupos de usuarios a lo largo del tiempo. :::note Las compras web no tienen evento de instalación de app. Adapty las agrupa en cohortes por la fecha del primer inicio de prueba o compra. ::: ## 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 de Google Ads \{#google-ads-metrics\} Google Ads reporta estas métricas por sí mismo, por lo que solo están disponibles en las filas de Google Ads. Las filas de otras redes muestran "—". Para añadir la mayoría de estas columnas a la tabla en un solo paso, selecciona el [preset de Google Ads](ua-analytics#google-ads-preset). | **Métrica** | Descripción | Cohorte | |---|---|---| | **Conversions (Network)** | Las conversiones que Google Ads contabiliza para las acciones de conversión en tu cuenta de Google Ads, por fecha del clic. Son los números de Google, no los trials o compras de Adapty, por lo que ambos no coinciden. | No | | **All Conv. (Network)** | Todas las conversiones que Google Ads contabiliza, por fecha del clic. Google Ads muestra este número en su columna **All conv.** | No | | **Cost per Conversion (Network)** | El gasto promedio por conversión reportado por Google Ads. Solo cuenta el gasto de las filas que reportan conversiones. <br/>**Fórmula**: `Spend / Conversions (Network)` | No | | **First Page Bid Est.** | La estimación de Google sobre la puja necesaria para mostrar tu anuncio en la primera página de resultados de búsqueda. | No | | **First Position Bid Est.** | La estimación de Google sobre la puja necesaria para mostrar tu anuncio en la primera posición de la primera página de resultados de búsqueda. | No | | **Status** | El estado de Google Ads de la campaña, grupo de anuncios, anuncio o palabra clave de la fila: **Enabled**, **Paused** o **Removed**. | No | ### Conversiones \{#conversions\} Adapty actualiza las conversiones de cada día durante 3 días y, a partir de ese momento, las mantiene tal como están. Google Ads sigue añadiendo conversiones tardías a fechas de clic anteriores, por lo que un día más antiguo puede mostrar menos conversiones en Adapty que en Google Ads. Adapty no importa las conversiones correspondientes a los días previos a que comenzara a recopilarlas, por lo que esos días muestran "—". ### Estimaciones de puja \{#bid-estimates\} Las dos columnas de estimaciones de puja aparecen en la tabla solo cuando agrupas por palabra clave. Para cada palabra clave, Adapty muestra la estimación de Google para el último día con tráfico de esa palabra clave en el rango de fechas seleccionado. Google no proporciona estimaciones de puja para palabras clave de Display y App. Tampoco las proporciona para palabras clave de Search en campañas con pujas automatizadas, como **Maximize clicks**, **Maximize conversions** o **Target CPA**. Esas palabras clave muestran "—". ### Estado \{#status\} Adapty actualiza los estados una vez al día. Una fila muestra un estado cuando se cumplen dos condiciones: - La tabla está agrupada por campaña, conjunto de anuncios, anuncio o palabra clave. - La fila tiene gasto de Google Ads en el rango de fechas seleccionado. Si varias campañas de Google Ads comparten el mismo nombre, su fila muestra **Enabled** si alguna de ellas está activa. De lo contrario, la fila muestra **Paused** si alguna está pausada, y **Removed** si todas están eliminadas. ## 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-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 llevarlos a una sección específica de tu app justo después de que la instalen y la abran. :::important A partir de la versión 4.1 del SDK de Adapty, Adapty Attribution está desactivado por defecto. El callback `onInstallationDetailsSuccess` solo se ejecuta después de que [habilites Adapty Attribution al activar el SDK](user-acquisition#step-1-enable-adapty-attribution-in-the-sdk). ::: 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. Si tu campaña se ejecuta en Meta Ads o TikTok, busca tu enlace de seguimiento en **Integrations** > **Meta Ads** o **TikTok Ads**. Si tu campaña apunta a una plataforma diferente, abre **Integrations** > **Custom**. Luego copia el enlace que quieras usar. 2. En tu plataforma publicitaria (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 a los usuarios de iOS a una pantalla de '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/c/YOUR_CODE?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__&ios_deferred_data=welcome ``` 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 relaciona una instalación con una campaña, devuelve los 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. :::important A partir de la versión 4.1 del SDK de Adapty, la atribución de Adapty está desactivada por defecto. El callback `onInstallationDetailsSuccess` solo se ejecuta después de que [habilites la atribución de Adapty al activar el SDK](user-acquisition#step-1-enable-adapty-attribution-in-the-sdk). ::: 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 la 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 / creativo | | `ad_name` | Nombre del anuncio / creativo | Todos los campos son opcionales. Para las 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-integrations --- --- title: "Integraciones en Adapty Attribution" description: "Consulta las integraciones disponibles para Adapty Attribution." --- ## Red \{#network\} <CustomDocCardList ids={['ua-facebook', 'ua-tiktok', 'ua-tracking-links']} /> ## Analytics \{#analytics\} <CustomDocCardList ids={['ua-funnelfox']} /> ## Pagos \{#payments\} <CustomDocCardList ids={['ua-web-pixel', 'ua-stripe', 'ua-paddle']} /> ## Exportar \{#export\} <CustomDocCardList ids={['ua-custom-s3', 'ua-amazon-s3', 'ua-google-cloud-storage']} /> --- # 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**. :::note Meta puede desconectar la integración cuando la cuenta de Facebook conectada alcanza un punto de control de seguridad, y el gasto deja de llegar a Adapty Attribution. Para restablecer la conexión, inicia sesión en [facebook.com](https://www.facebook.com), completa el punto de control y vuelve a conectar en **Integrations > Meta**. ::: </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ñade campañas \{#step-2-add-campaigns\} Una sola campaña puede cubrir el App Store, Google Play y tu funnel web al mismo tiempo. Dos elementos hacen que la conexión funcione: - **Enlace de seguimiento**: Va en tu anuncio. Cuando un usuario toca el anuncio, Adapty registra el clic, redirige al usuario al destino y, posteriormente, atribuye la instalación y las compras a ese anuncio. Cada destino tiene su propio enlace. - **Pixel**: Se selecciona en la campaña, a partir de los píxeles de tu cuenta de Meta. Envía esas conversiones de vuelta a Meta, para que Meta pueda optimizar la campaña con compras reales. Un solo píxel puede servir a los tres destinos, o cada destino puede tener el suyo propio. Para añadir una nueva campaña de Meta a Adapty Attribution: 1. Ve 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 divide el enlace entre los campos **Website URL** y **URL parameters**, tal como se describe en [Crear anuncios](meta-create-campaign#step-5-create-ads). Esto permitirá que Adapty conecte las instalaciones y compras con los anuncios de 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. La sección **Web funnel** funciona de la misma manera. Las compras en un web funnel ocurren fuera de tu app, por lo que Meta solo se entera de ellas a través del píxel que selecciones aquí. Esta misma sección te proporciona el snippet para [instalar el píxel en tu funnel](ua-web-pixel) y los parámetros para [etiquetar tus URLs de anuncios](ua-tag-ad-urls). Un píxel diferente por destino mantiene las compras de iOS, Android y web separadas para la optimización y los informes; el mismo píxel en todos los destinos las combina. La lista **Web Campaigns** muestra esa elección por configuración, bajo **Pixel (iOS)**, **Pixel (Android)** y **Pixel (Web)**, con el nombre e ID del píxel. Una sección sin píxel muestra un guion, y un píxel seleccionado en las tres secciones aparece tres veces. ## Paso 3. Mapear eventos \{#step-3-map-events\} Para enviar eventos de conversión a Meta y optimizar tus campañas, debes configurar el mapeo de eventos en la sección **Events mapping**. Esto permite que Adapty envíe automáticamente eventos de suscripción cuando los usuarios realizan acciones en tu app. En la sección **Events mapping**, activa los eventos que quieras rastrear en Meta Ads Manager. Para cada evento activado, selecciona el evento de Meta correspondiente en el menú desplegable o define uno personalizado. Por defecto, Adapty mapea sus eventos a los eventos estándar de Meta. Adapty envía el nombre del evento exactamente como lo escribas. Para que Meta lo cuente como un evento estándar, selecciónalo en el desplegable en lugar de escribirlo manualmente. Un nombre escrito que no coincida con el estándar, como `Add to wishlist` en lugar de `AddToWishlist`, llega a Meta como un evento personalizado, y un conjunto de anuncios optimizado para el evento estándar no lo contabilizará. Si usas un evento personalizado, crea una conversión personalizada en Meta Events Manager y optimiza el conjunto de anuncios en esa conversión. 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 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 una instalación con un clic en tu anuncio de Meta. La configuración se aplica únicamente a esta campaña. Adapty utiliza dos tipos de coincidencia: - **La coincidencia determinista** solo funciona en Android. Google Play pasa el ID del clic al que se hizo tap a tu app a través del Play Install Referrer, por lo que Adapty relaciona la instalación con ese clic exacto. iOS no tiene un equivalente, así que las instalaciones de iOS nunca se emparejan de forma determinista. - **La coincidencia probabilística** funciona en iOS y Android. Adapty compara la instalación con los clics desde el mismo rango de dirección IP y sistema operativo, y la relaciona con el clic que obtiene mejor puntuación en tiempo desde el clic, idioma del dispositivo y país. Si el clic se produjo más de 6 horas antes de la instalación, también deben coincidir el modelo de dispositivo y la versión del SO. Adapty no utiliza el IDFA ni el Google Advertising ID, por lo que la coincidencia probabilística cubre a todos los usuarios de iOS, incluidos los que denegaron el seguimiento en el aviso de App Tracking Transparency. En Android, Adapty intenta primero la coincidencia determinista y, si no lo consigue, recurre a la coincidencia probabilista. Para configurar el tiempo máximo que puede transcurrir entre un clic y una instalación para que sigan vinculándose, en la pestaña **Settings**, dentro de la sección **New user attribution**, introduce la ventana en horas: - **Deterministic Matching Window**: 168 horas (7 días) por defecto, de 1 a 720 horas. - **Probabilistic Matching Window**: 6 horas por defecto, de 1 a 24 horas. Una ventana más larga vincula más instalaciones, pero con más errores. Para campañas con alto volumen de clics, redúcela a 1–2 horas. Para detener la coincidencia determinista o probabilística de esta configuración, desactiva el interruptor junto a su ventana y guarda. El cambio surte efecto en 5 minutos. :::warning Las instalaciones de iOS solo se pueden hacer coincidir de forma probabilística. Si desactivas la coincidencia probabilística, las instalaciones de iOS procedentes de los enlaces de esta configuración no serán atribuidas. Si desactivas ambas, ninguna instalación procedente de sus enlaces será atribuida. ::: ### Ingresos enviados a Meta \{#revenue-sent-to-meta\} Meta utiliza el valor de cada evento para calcular el ROAS y optimizar campañas basadas en valor. Para elegir qué ingresos envía Adapty a Meta como ese valor, en el desplegable **Revenue sent to Meta** selecciona un tipo de ingresos y haz clic en **Save**. Para comparar el ROAS en Meta con el ROAS en Adapty, selecciona el tipo que [muestras en Adapty Analytics](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). | Tipo de ingresos | Valor de una compra en App Store o Google Play | |---|---| | **Gross revenue** (predeterminado) | El precio completo que pagó el usuario, antes de la comisión del store e impuestos. | | **Proceeds after store commission** | El precio menos la comisión de App Store o Google Play. Los impuestos siguen incluidos. | | **Proceeds after store commission and taxes** | El precio menos la comisión del store y los impuestos. | Para saber cómo Adapty calcula la comisión y los impuestos, consulta [Comisiones e impuestos](how-adapty-analytics-works#commissions-and-taxes). Cómo se aplica el tipo en otros casos: - **Compras web**: Para las compras de Stripe y Paddle, ambos tipos de ingresos envían el mismo valor, como en los análisis de atribución de Adapty: el precio menos el 4,5% para Stripe, y el precio completo para Paddle. Adapty no deduce impuestos de las compras web. - **Reembolsos**: Meta recibe un reembolso como un valor negativo del tipo seleccionado. - **Eventos de prueba**: El porcentaje de [anulación de ingresos](#revenue-override) se aplica al precio de suscripción del tipo seleccionado. - **Compras sin importe del tipo seleccionado**: Adapty envía a Meta los ingresos brutos de la compra. Un nuevo tipo de ingresos surte efecto en un plazo de 5 minutos tras guardar y se aplica solo a los eventos nuevos. Adapty no reenvía eventos que ya ha enviado, y un evento de prueba que Adapty ya tiene en espera para [Qualified trial](#qualified-trial) se envía con el tipo seleccionado cuando comenzó la espera. Si varias configuraciones envían eventos al mismo píxel, selecciona el mismo tipo de ingresos en todas ellas para que los valores de ese píxel sean de un mismo tipo. :::warning Los ingresos netos son inferiores a los ingresos brutos. Si cambias de **Gross revenue** y un conjunto de anuncios tiene un objetivo de ROAS mínimo, reduce el objetivo en Meta o el conjunto de anuncios puede dejar de publicarse. ::: ### Anulación de ingresos \{#revenue-override\} Si realizas un seguimiento de eventos de prueba y quieres que Meta atribuya ingresos a ellos, usa la sección **Revenue override**. Aparece cuando el evento **Trial started** está habilitado. La anulación se aplica únicamente a **Trial started**: **Trial converted** y **Subscription started** siempre llevan sus ingresos reales. Para cada objetivo de evento de prueba, establece el porcentaje del precio de la suscripción que se reportará como ingresos. Adapty toma el porcentaje del precio en el tipo seleccionado en [Ingresos enviados a Meta](#revenue-sent-to-meta). Por ejemplo, con el 30% y **Gross revenue**, 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 mapping**. 2. En **Revenue override**, haz clic en **Add event**. 3. Selecciona el evento objetivo e introduce un porcentaje de ingresos (0–100). 4. Haz clic en **Save**. ### Prueba calificada \{#qualified-trial\} :::warning La atribución de Adapty retrasa el evento de prueba hasta 24 horas y solo lo envía si el usuario no ha cancelado. Si un MMP también reenvía `trial_started` o [`trial_active`](events#synthetic-events), Meta recibe la misma prueba más de una vez. ::: Una prueba se califica una vez que transcurre un número determinado de horas sin que sea cancelada. Usa la sección **Qualified trial** para retener los eventos de prueba hasta que se califiquen, de modo que Meta solo reciba las pruebas que los usuarios mantuvieron. Para cada objetivo de evento de prueba, establece el número de horas de espera. Adapty cuenta las horas desde el inicio de la prueba y envía el evento cuando se cumple el plazo. Si el usuario desactiva la renovación automática antes del plazo, Adapty nunca envía el evento. Si el usuario vuelve a activar la renovación automática antes del plazo, Adapty envía el evento según lo previsto. Para retener un evento de prueba hasta que sea válido: 1. Activa **Trial started** en la sección **Events mapping**. 2. En **Qualified trial**, haz clic en **Add event**. 3. Selecciona el evento objetivo e introduce el número de horas de espera, de 1 a 24. 4. Haz clic en **Save**. Adapty envía el evento calificado con la hora de inicio original del período de prueba, por lo que Meta lo registra en el día en que comenzó. :::important Adapty relaciona los períodos de prueba calificados con su transacción de App Store o Google Play, por lo que la retención se aplica solo a los períodos de prueba in-app. Los eventos de prueba de compras web, como [FunnelFox](ua-funnelfox), se envían de inmediato. ::: **Revenue override** y **Qualified trial** funcionan de forma independiente, y puedes aplicar ambos al mismo evento de prueba. Revenue override cambia el valor que recibe Meta; Qualified trial cambia cuándo lo recibe. Con ambos activos, Adapty retiene el evento hasta que la prueba se cualifica y luego lo envía con el valor de ingresos sobreescrito. Ninguno tiene preferencia sobre el otro — prueba qué funciona mejor para tus campañas. ### LTV Predicho \{#predicted-ltv\} La optimización de valor de Meta puja por el valor de una conversión, pero por sí sola solo ve el pago que se está reportando — así que una prueba mensual y una suscripción anual parecen casi iguales en el momento de la puja. Usa la sección **Predicted LTV** para enviar a Meta un [valor de vida predicho](https://developers.facebook.com/documentation/ads-commerce/conversions-api/guides/predicted-lifetime-value) junto con la conversión, para que Meta puje por lo que probablemente valdrá el usuario en el horizonte que elijas. Adapty genera la predicción a partir de los ingresos reales de tu propia app, agrupados por país, store, dispositivo y localización, y la asocia a una conversión que ya ha enviado. El LTV Predicho se activa para una app cuando sus datos permiten generar una predicción significativa. Adapty recalcula esto a diario y muestra la situación actual de la app en la sección **Predicted LTV**. **Trial started** y **Subscription started** se miden por separado, y cualquiera de los dos activa la app al completar ambas barras: - Al menos 100 conversiones de ese evento atribuidas a Meta en cada una de las últimas 4 semanas completas. - Al menos 5 valores predichos distintos, con el más alto al menos 3 veces mayor que el más bajo. Meta añade dos requisitos que Adapty no puede verificar por ti: el dataset debe estar fuera de Core Setup y la cuenta publicitaria debe tener un modelo de valor validado en uso. Para enviar valores predichos a una campaña: 1. En **Events mapping**, activa **Trial started** o **Subscription started** y asígnalo a un evento de Meta. 2. En **Predicted LTV**, activa **Send predicted LTV to Meta**. 3. En **Anchor event**, selecciona el evento que mapeaste en el paso 1. 4. En **Prediction horizon**, elige el horizonte temporal del valor: 30, 90, 180 o 365 días. 5. En **Target event**, selecciona el evento de Meta al que se asociará el valor predicho. La lista muestra únicamente los eventos a los que esta campaña mapea el anchor. Meta acepta valores predichos solo en **Purchase**, **Subscribe**, **StartTrial**, **CompleteRegistration**, **AddPaymentInfo** y eventos personalizados. 6. Haz clic en **Save**. 7. Para que Meta pueda usar los valores, en Events Manager abre tu dataset y selecciona el evento objetivo en **Value and Currency Metrics**. Haz clic en **Set up pLTV** y elige **After [Event Name] events**, ya que Adapty entrega el valor después de la conversión, no junto con ella. :::warning Meta aprende del evento de anclaje y el horizonte que hayas seleccionado. Cambiar cualquiera de los dos en una configuración activa reinicia la fase de aprendizaje de Meta, y el ROAS antes y después del cambio no es comparable. Además, Meta calcula el ROAS de forma diferente según cada configuración de optimización, así que compara una campaña de pLTV únicamente con otras campañas de pLTV. ::: Meta acepta un solo evento de anclaje y un solo horizonte por dataset, por lo que las campañas que comparten un píxel deben enviar el mismo par. Si guardas una campaña cuyo par no coincide con el de otra campaña activa en ese píxel, el guardado se rechaza y el error indica la campaña con la que debes alinearte. Adapty prepara el valor predicho cuando envía la conversión de anclaje y lo entrega 24 horas después, dentro de la ventana de 7 días de Meta para asociar un valor a una conversión. Hay tres casos que cambian lo que recibe Meta: - Cuando el evento ancla lo gestiona [Qualified trial](#qualified-trial), las 24 horas comienzan una vez que el evento de prueba llega a Meta. Una prueba cancelada antes de cualificarse no envía ni la conversión ni un valor predicho. - Cuando Adapty no tiene predicción para el grupo de ese usuario — por ejemplo, un país en el que empezaste a comprar recientemente — la conversión se envía de todas formas, sin valor predicho. - Desactivar **Send predicted LTV to Meta** también elimina los valores predichos que aún estén pendientes de entrega. Adapty envía los valores predichos en USD. Una vez que Events Manager no muestre problemas abiertos para la integración, Meta necesita aproximadamente dos semanas para entrenar su modelo antes de que puedas lanzar la primera campaña de pLTV. Para leer los resultados, en Ads Manager, añade la columna pLTV del evento objetivo, como **StartTrial pLTV**, a través de **Columns > Customize columns > Results ROAS**. Adapty entrega cada valor un día después de la conversión, así que espera un día tras el fin de una campaña antes de leer sus cifras finales. ### Enviar eventos de todos los usuarios \{#send-events-from-all-users\} Por defecto, Adapty solo reporta los eventos de los usuarios que atribuye a esta configuración. La opción **Send events from all users** reporta los eventos de todos los usuarios de tu app, incluidos los usuarios orgánicos, los usuarios de otras redes publicitarias y los usuarios de tus otras configuraciones de Meta. Meta decide entonces a cuál de sus anuncios atribuir estas conversiones. Esta opción no cambia la atribución en Adapty. No lo recomendamos a menos que tus campañas envíen a Meta muy pocas conversiones para optimizar, ya que mezclar eventos orgánicos y sin atribución con los eventos de tus campañas reduce la precisión de la optimización de Meta. Si decides activarlo, en la configuración de la campaña selecciona **Send events from all users** 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ñade campañas \{#step-2-add-campaigns\} Una sola campaña puede cubrir el App Store, Google Play y tu funnel web al mismo tiempo. Dos elementos hacen que la conexión funcione: - **Tracking link**: Va en tu anuncio. Cuando un usuario pulsa el anuncio, Adapty registra el clic, redirige al usuario al destino y, posteriormente, atribuye la instalación y las compras a ese anuncio. Cada destino tiene su propio enlace. - **Pixel**: Se selecciona en la campaña, entre los píxeles de tu cuenta de TikTok. Traslada esas conversiones a TikTok, para que TikTok pueda optimizar la campaña con compras reales. Un píxel puede cubrir los tres destinos, o cada destino puede tener el suyo propio. Para añadir una campaña de TikTok for Business a Adapty Attribution y rastrear el 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 en 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 **Destination URL**. Esto permitirá que Adapty vincule las instalaciones y compras con los anuncios en TikTok. Consulta [Añadir contenido del anuncio](tiktok-create-campaign#step-5-add-ad-content). 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**. Después de seleccionar un píxel, puedes hacer clic en **Send test event** para verificar la conexión. La sección **Web funnel** funciona igual. Las compras en un web funnel ocurren fuera de tu app, por lo que TikTok solo se entera de ellas a través del píxel que selecciones aquí. Esta misma sección te proporciona el fragmento para [instalar el píxel en tu funnel](ua-web-pixel) y los parámetros para [etiquetar las URLs de tus anuncios](ua-tag-ad-urls). Un píxel diferente por destino mantiene las compras de iOS, Android y web separadas para la optimización y los informes; el mismo píxel en todos los destinos las combina. La lista de **Web Campaigns** muestra esa elección por configuración, bajo **Pixel (iOS)**, **Pixel (Android)** y **Pixel (Web)**, con el nombre e ID del píxel. Una sección sin píxel muestra un guion, y un píxel seleccionado en las tres secciones aparece tres veces. ## Paso 3. Mapear eventos \{#step-3-map-events\} Para enviar eventos de conversión a TikTok y optimizar tus campañas, necesitas configurar el mapeo de eventos en la sección **Events mapping**. Esto permite que Adapty envíe automáticamente eventos de suscripción cuando los usuarios realizan acciones en tu app. En la sección **Events mapping**, activa los eventos que quieras rastrear en TikTok Ads Manager. Para cada evento habilitado, selecciona el evento de TikTok correspondiente en el menú 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 vincula una instalación con un clic en tu anuncio de TikTok. La configuración se aplica únicamente a esta configuración de campaña. Adapty utiliza dos tipos de coincidencia: - **La coincidencia determinista** funciona solo en Android. Google Play pasa el ID del clic al que se hizo clic a tu app a través del Play Install Referrer, por lo que Adapty asocia la instalación con ese clic exacto. iOS no tiene un equivalente, por lo que las instalaciones de iOS nunca se asocian de forma determinista. - **La coincidencia probabilística** funciona en iOS y Android. Adapty compara la instalación con los clics del mismo rango de dirección IP y sistema operativo, y la asocia con el clic que obtiene la mejor puntuación en tiempo transcurrido desde el clic, idioma del dispositivo y país. Para un clic de más de 6 horas antes de la instalación, el modelo de dispositivo y la versión del SO también deben coincidir. Adapty no utiliza el IDFA ni el ID de publicidad de Google, por lo que la coincidencia probabilística cubre a todos los usuarios de iOS, incluidos los que denegaron el seguimiento en el aviso de App Tracking Transparency. En Android, Adapty primero intenta la coincidencia determinista y, si no es posible, recurre a la probabilista. Para definir durante cuánto tiempo después de un clic una instalación puede seguir asociándose a ese clic, en la pestaña **Settings**, dentro de la sección **New user attribution**, introduce la ventana en horas: - **Deterministic Matching Window**: 168 horas (7 días) por defecto, de 1 a 720 horas. - **Probabilistic Matching Window**: 6 horas por defecto, de 1 a 24 horas. Una ventana más amplia asocia más instalaciones, pero también aumenta el número de coincidencias incorrectas. Para campañas con un alto volumen de clics, redúcela a 1–2 horas. Para detener la coincidencia determinista o probabilística de esta configuración, desactiva el interruptor junto a su ventana y guarda. El cambio surte efecto en 5 minutos. :::warning Las instalaciones de iOS solo se pueden asociar de forma probabilística. Si desactivas la coincidencia probabilística, las instalaciones de iOS provenientes de los enlaces de esta configuración no se atribuirán. Si desactivas ambas, ninguna instalación proveniente de sus enlaces se atribuirá. ::: ### Ingresos enviados a TikTok \{#revenue-sent-to-tiktok\} TikTok utiliza el valor de cada evento para calcular el ROAS y para la optimización basada en valor. Para elegir qué ingresos envía Adapty a TikTok como ese valor, en el desplegable **Revenue sent to TikTok**, selecciona un tipo de ingresos y haz clic en **Save**. Para comparar el ROAS en TikTok con el ROAS en Adapty, selecciona el tipo que [muestras en Adapty Analytics](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). | Tipo de ingresos | Valor de una compra en App Store o Google Play | |---|---| | **Gross revenue** (predeterminado) | El precio completo que pagó el usuario, antes de la comisión de la store e impuestos. | | **Proceeds after store commission** | El precio menos la comisión de App Store o Google Play. Los impuestos siguen incluidos. | | **Proceeds after store commission and taxes** | El precio menos la comisión de la store y los impuestos. | Para saber cómo Adapty calcula la comisión y los impuestos, consulta [Comisiones e impuestos](how-adapty-analytics-works#commissions-and-taxes). Cómo se aplica el tipo en otros casos: - **Compras web**: Para las compras de Stripe y Paddle, ambos tipos de ingresos envían el mismo valor, como en Adapty Attribution analytics: el precio menos el 4,5% para Stripe y el precio completo para Paddle. Adapty no deduce impuestos de las compras web. - **Reembolsos**: TikTok recibe eventos de reembolso sin valor, independientemente del tipo. - **Eventos de prueba**: El porcentaje de [Modificación de ingresos](#revenue-override) se aplica al precio de suscripción del tipo seleccionado. - **Compras sin importe del tipo seleccionado**: Adapty envía a TikTok los ingresos brutos de la compra. Un nuevo tipo de ingresos entra en vigor en los 5 minutos posteriores al guardado y se aplica solo a los nuevos eventos. Adapty no reenvía eventos que ya ha enviado, y un evento de prueba que Adapty ya retiene para [Qualified trial](#qualified-trial) se envía con el tipo seleccionado cuando comenzó la retención. Si varias configuraciones envían eventos al mismo píxel, selecciona el mismo tipo de ingresos en todas ellas para que los valores de ese píxel sean de un mismo tipo. :::warning Los ingresos netos son inferiores a los ingresos brutos. Si cambias de **Gross revenue** y una campaña utiliza un ROAS mínimo, reduce ese ROAS mínimo en TikTok, o la campaña puede dejar de publicarse. ::: ### Anulación de ingresos \{#revenue-override\} Si haces seguimiento de eventos de prueba y quieres que TikTok atribuya ingresos a ellos, utiliza la sección **Revenue override**. Aparece cuando el evento **Trial started** está habilitado. La anulación se aplica solo a **Trial started**: los eventos **Trial converted** y **Subscription started** siempre llevan sus ingresos reales. Para cada objetivo de evento de prueba, establece el porcentaje del precio de suscripción que se reportará como ingresos. Adapty toma el porcentaje del precio en el tipo seleccionado en [Revenue sent to TikTok](#revenue-sent-to-tiktok). Por ejemplo, con un 30% y **Gross revenue**, Adapty envía el 30% del precio de 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 mapping**. 2. En **Revenue override**, haz clic en **Add event**. 3. Selecciona el evento objetivo e introduce un porcentaje de ingresos (0–100). 4. Haz clic en **Save**. ### Prueba calificada \{#qualified-trial\} :::warning La atribución de Adapty retrasa el evento de prueba hasta 24 horas y solo lo envía si el usuario no ha cancelado. Si un MMP también reenvía `trial_started` o [`trial_active`](events#synthetic-events), TikTok recibe la misma prueba más de una vez. ::: Una prueba se califica una vez que sobrevive un número determinado de horas sin ser cancelada. Usa la sección **Qualified trial** para retener los eventos de prueba hasta que se califiquen, de modo que TikTok solo reciba las pruebas que los usuarios conservaron. Para cada objetivo de evento de prueba, establece el número de horas de espera. Adapty cuenta las horas desde el inicio de la prueba y envía el evento cuando se cumple el plazo. Si el usuario desactiva la renovación automática antes del plazo, Adapty nunca envía el evento. Si el usuario vuelve a activar la renovación automática antes del plazo, Adapty envía el evento según lo previsto. Para retener un evento de prueba hasta que cumpla los requisitos: 1. Activa **Trial started** en la sección **Events mapping**. 2. En **Qualified trial**, haz clic en **Add event**. 3. Selecciona el evento objetivo e introduce el número de horas de espera, de 1 a 24. 4. Haz clic en **Save**. Adapty envía el evento cualificado con la hora de inicio original del período de prueba, de modo que TikTok lo reporta en el día en que comenzó dicho período. :::important Adapty empareja los períodos de prueba cualificados mediante su transacción de App Store o Google Play, por lo que la retención se aplica únicamente a los períodos de prueba in-app. Los eventos de prueba procedentes de compras web, como [FunnelFox](ua-funnelfox), se envían de inmediato. ::: **Revenue override** y **Qualified trial** funcionan de forma independiente y puedes aplicar ambos al mismo evento de prueba. Revenue override cambia el valor que recibe TikTok; Qualified trial cambia cuándo lo recibe. Con ambos activos, Adapty retiene el evento hasta que la prueba se cualifica y luego lo envía con el valor de ingresos sobreescrito. Ninguno se recomienda por encima del otro: prueba qué funciona mejor para tus campañas. ### Enviar eventos de todos los usuarios \{#send-events-from-all-users\} Por defecto, Adapty solo reporta los eventos de los usuarios que atribuye a esta configuración. La opción **Send events from all users** reporta los eventos de todos los usuarios de tu app, incluidos los usuarios orgánicos, los usuarios de otras redes publicitarias y los usuarios de otras configuraciones de TikTok. TikTok decide entonces a cuál de sus anuncios acreditar estas conversiones. Esta opción no modifica la atribución en Adapty. No lo recomendamos a menos que tus campañas envíen muy pocas conversiones a TikTok para optimizar, ya que mezclar eventos orgánicos y sin atribución con los eventos de tu campaña reduce la precisión de la optimización de TikTok. Si decides activarlo, en la configuración de la campaña selecciona **Send events from all users** y haz clic en **Save**. --- # File: ua-google-ads --- --- title: "Integrar Google Ads con Adapty Attribution" description: "Conecta Google Ads a Adapty Attribution para importar el gasto de tus campañas y enviar instalaciones, pruebas y compras de vuelta a Google para optimizar las campañas." --- La integración de Google Ads en Adapty Attribution funciona en ambas direcciones. Adapty obtiene gasto, impresiones y clics de tus cuentas de Google Ads, y envía instalaciones, trials y compras de vuelta a Google, para que Google pueda optimizar tus campañas con datos de ingresos reales. ## Cómo funciona \{#how-it-works\} Adapty se conecta a dos tipos de campañas de Google Ads. Cada uno atribuye instalaciones de forma diferente y tiene su propia pestaña y configuración. ### Campañas de aplicaciones (Android) \{#app-campaigns-android\} Adapty se conecta a Google Ads como proveedor externo de análisis de aplicaciones a través del **App Link ID** de tu app. Estas campañas no necesitan ningún enlace de seguimiento. 1. Un usuario instala tu app desde un anuncio de campaña de aplicaciones y la abre por primera vez. 2. Adapty le pregunta a Google si una campaña de aplicaciones impulsó la instalación. 3. Google atribuye la instalación a su campaña, grupo de anuncios, anuncio y palabra clave. 4. Adapty envía las pruebas y compras del usuario a Google a través del mismo ID de enlace. ### Otras campañas (iOS y Android) \{#other-campaigns-ios-and-android\} Añades la plantilla de seguimiento de Adapty a la campaña en Google Ads. 1. Un usuario hace clic en el anuncio y Google añade un ID de clic al clic. 2. El clic pasa por Adapty de camino al store o a tu embudo web. 3. Adapty atribuye la instalación o la compra web a ese clic. 4. Adapty envía las conversiones a la cuenta de Google Ads que selecciones, identificadas por el ID de clic. Para los usuarios de iOS que no permiten el seguimiento, Google usa un ID de clic respetuoso con la privacidad (`gbraid` o `wbraid`) en lugar de `gclid`. Adapty acepta los tres. ### SKAdNetwork Ninguno de los dos tipos de campaña utiliza SKAdNetwork (SKAN), el framework de Apple que reporta las instalaciones de anuncios sin identificar al usuario. Por tanto, para esta integración: - No necesitas configurar SKAN ni los valores de conversión de SKAN. - La configuración de SKAN que ya uses en Google Ads no afecta a los eventos que Adapty envía a Google ni a los datos que muestra Adapty Attribution. ## Tipos de campaña compatibles \{#supported-campaign-types\} | Tipo de campaña | Dónde se muestran los anuncios | Pestaña en Adapty Attribution | Plataformas | |---|---|---|---| | Campañas de aplicaciones para instalaciones (UAC) | Google Search, Google Play, YouTube y la Red de Display, para conseguir instalaciones de tu app | **App Campaigns** | Android | | Search | Resultados de Google Search, para las palabras clave que elijas | **Other Campaigns** | iOS, Android | | Performance Max | Todos los canales de Google desde una sola campaña: Search, YouTube, Display, Discover, Gmail y Maps | **Other Campaigns** | iOS, Android | | Demand Gen | YouTube in-feed y Shorts, Discover y Gmail, como anuncios de imagen y vídeo | **Other Campaigns** | iOS, Android | | Display | Anuncios de banner en sitios y apps de la Red de Display de Google | **Other Campaigns** | iOS, Android | | Video | Anuncios de vídeo en YouTube | **Other Campaigns** | iOS, Android | | Shopping | Anuncios de productos de tu feed de Google Merchant Center | **Other Campaigns** | iOS, Android | Para promocionar una app de iOS, utiliza Other campaigns. Las campañas Performance Max también aparecen en la pestaña **Other Campaigns** cuando su objetivo son instalaciones de apps, ya que Adapty las rastrea por la plantilla de seguimiento, no por el App Link ID. Las campañas de app para engagement no son compatibles: Adapty solo envía a Google los eventos de instalaciones que Google haya atribuido a una campaña de app, y las campañas de engagement se dirigen a usuarios que ya tienen la app instalada. ## Paso 1. Conecta tu cuenta de Google \{#step-1-connect-your-google-account\} 1. En Adapty Attribution, ve a **Integrations** en la barra lateral izquierda y selecciona **Google Ads**. 2. Haz clic en **Sign in with Google**. 3. Selecciona la cuenta de Google que tiene acceso a tus cuentas de Google Ads y haz clic en **Continue**. 4. Selecciona todos los permisos solicitados y haz clic en **Continue**. Después de eso, Adapty sincroniza todas las cuentas de Google Ads a las que tu cuenta de Google puede acceder, incluidas las cuentas de cliente bajo una cuenta de administrador. Para añadir cuentas publicitarias a las que pueda acceder otra cuenta de Google, haz clic en **Sign in with Google** de nuevo. ## Paso 2. Añade campañas \{#step-2-add-campaigns\} Configura los tipos de campaña que utilizas. Las campañas de aplicaciones y las otras campañas tienen configuraciones separadas, y puedes usar ambas en la misma app. ### Campañas de aplicación \{#app-campaigns\} Para enviar eventos in-app a tus campañas de aplicación de Google, crea una configuración en Adapty Attribution y luego vincúlala a tu app de Android en Google Ads con un App Link ID. **En Adapty Attribution, crea la configuración:** 1. En **Integrations > Google Ads**, cambia a la pestaña **App Campaigns** y haz clic en **Create configuration**. Adapty crea una configuración para tu app actual y la abre. 2. Copia el valor de **Provider ID**. Lo necesitarás para crear el App Link ID en Google Ads. **En Google Ads, crea el App Link ID:** 1. Haz clic en el icono **Tools** y selecciona **Data manager**. 2. Haz clic en **+ Connect Product**, selecciona **Third-party app analytics** y haz clic en **Continue**. 3. Haz clic en **Create link ID**. 4. En la lista de proveedores, selecciona **Other provider** y pega el **Provider ID** que copiaste desde Adapty Attribution. 5. Selecciona **Android** como plataforma y busca tu app en el campo **Look up your app**. 6. Haz clic en **Create link ID** y copia el link ID generado. :::note Google Ads muestra el ID de enlace como **Unverified** hasta que recibe el primer evento de Adapty. ::: **En Adapty Attribution, añade el ID de enlace:** 1. En la configuración, pega el ID de enlace en el campo **Android App Link ID**. Cada ID de enlace solo puede pertenecer a una configuración. 2. [Mapea los eventos](#step-3-map-events) y haz clic en **Save**. 3. Para verificar la conexión, haz clic en **Send test event**, selecciona un evento y envíalo. Google añade cada evento que recibe a las acciones de conversión de tu aplicación en Google Ads, para que puedas seleccionarlas como objetivo de optimización de tu campaña. :::note Google Ads necesita aproximadamente 24 horas para reconocer un nuevo ID de enlace, por lo que el evento de prueba puede fallar hasta entonces. Si ocurre, consulta [Solución de problemas](#test-event-fails-for-a-new-link-id). ::: :::note Para los usuarios del Espacio Económico Europeo y el Reino Unido, Adapty envía los eventos con las señales de consentimiento configuradas como denegadas. No es necesario configurar el consentimiento en Adapty Attribution. ::: ### Otras campañas \{#other-campaigns\} Para rastrear campañas de Search, Display, Shopping y otras campañas basadas en clics para iOS y Android, crea una configuración para ellas en Adapty Attribution y añade su plantilla de seguimiento a la campaña en Google Ads. Necesitas una configuración por cada cuenta de Google Ads que sirva tus anuncios. :::important El etiquetado automático debe estar activado en la configuración de tu cuenta de Google Ads. Adapty empareja las conversiones con los anuncios mediante el ID de clic que añade el etiquetado automático, y no envía ninguna conversión para un clic sin él. ::: 1. En **Integrations > Google Ads**, cambia a la pestaña **Other Campaigns** y haz clic en **Create configuration**. 2. En el desplegable **Google Ads account**, selecciona la cuenta de cliente que sirve los anuncios. Google solo acepta una conversión en la cuenta que sirvió el anuncio correspondiente. Las cuentas de administrador no aparecen en la lista porque no pueden almacenar conversiones. 3. Copia el valor de **Tracking template**. Luego, en Google Ads, abre la configuración de la campaña y pégalo en **Campaign URL options > Tracking template**. Una sola plantilla sirve tanto para iOS como para Android. 4. Si los anuncios llevan a un funnel web, despliega la sección **Web funnel**: - Copia el **Pixel snippet** e [instálalo en tu funnel](ua-web-pixel). - Copia los **Ad URL parameters** y, en la configuración de la campaña de Google Ads, pégalos en **Campaign URL options > Final URL suffix**. Sin ellos, Adapty atribuye la compra a Google Ads, pero no a su campaña, grupo de anuncios ni anuncio. 5. [Mapea los eventos](#step-3-map-events) y haz clic en **Save**. Cuando guardas la configuración, Adapty crea una acción de conversión en la cuenta de Google Ads seleccionada por cada evento que hayas habilitado. Si Google lo rechaza, Adapty muestra un error y no guarda la configuración. Selecciona una cuenta de cliente que puedas editar o vuelve a conectar tu cuenta de Google si el acceso ha caducado. :::note Si conectaste tu cuenta de Google antes del 11 de septiembre de 2026, vuelve a conectarla antes de añadir Other campaigns. Una conexión realizada antes de esa fecha no cuenta con el permiso que Adapty necesita para enviar conversiones de estas campañas. Haz clic en **Sign in with Google** y selecciona todos los permisos solicitados. ::: ## Paso 3. Mapea los eventos \{#step-3-map-events\} Para elegir qué eventos recibe Google, en la sección **Events mapping**, activa cada evento que quieras enviar y selecciona o escribe su nombre en Google Ads. Los nombres de los eventos no pueden contener comas. Los nombres funcionan de forma distinta según el tipo de campaña: - **App campaigns**: **Install** siempre está activado y se asigna a `first_open` de forma predeterminada. Para cualquier evento, puedes seleccionar uno de los eventos de aplicación estándar de Google: `first_open`, `session_start`, `in_app_purchase`, `ecommerce_purchase`, `view_item`, `view_item_list`, `add_to_cart` o `view_search_results`. Adapty envía cualquier otro nombre que escribas como evento de aplicación personalizado. - **Other campaigns**: El nombre predeterminado de cada evento es su nombre de evento en Adapty, como `trial_started`. El nombre que selecciones o escribas se convierte en el nombre de la acción de conversión que Adapty crea en Google Ads, exactamente como se ha escrito. Haz clic en **Save**. ## Configuración adicional \{#additional-configuration\} ### Ingresos enviados a Google Ads Google Ads utiliza el valor de cada evento para calcular el ROAS y para las pujas basadas en valor. Para elegir qué ingresos envía Adapty a Google Ads como ese valor, en el desplegable **Revenue sent to Google Ads**, selecciona un tipo de ingresos y haz clic en **Save**. El desplegable está disponible tanto para App campaigns como para Other campaigns. Para comparar el ROAS en Google Ads con el ROAS en Adapty, selecciona el tipo que [muestras en Adapty Analytics](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). | Tipo de ingresos | Valor de una compra en App Store o Google Play | |---|---| | **Gross revenue** (predeterminado) | El precio completo que pagó el usuario, antes de la comisión de la store e impuestos. | | **Proceeds after store commission** | El precio menos la comisión de App Store o Google Play. Los impuestos siguen incluidos. | | **Proceeds after store commission and taxes** | El precio menos la comisión de la store e impuestos. | Para ver cómo Adapty calcula la comisión y los impuestos, consulta [Comisiones e impuestos](how-adapty-analytics-works#commissions-and-taxes). Cómo se aplica el tipo en otros casos: - **Compras web**: Para compras con Stripe y Paddle, ambos tipos de ingresos envían el mismo valor, como en Adapty Attribution analytics: el precio menos un 4,5 % para Stripe y el precio completo para Paddle. Adapty no deduce impuestos de las compras web. - **Reembolsos**: Sea cual sea el tipo, las campañas de App reciben eventos de reembolso sin valor, y Adapty no envía eventos de reembolso a campañas de Other. - **Eventos de prueba**: El porcentaje de [Anulación de ingresos](#revenue-override) se aplica al precio de suscripción del tipo seleccionado. - **Compras sin importe del tipo seleccionado**: Adapty envía a Google Ads los ingresos brutos de la compra. Un nuevo tipo de ingresos entra en vigor en los 5 minutos siguientes a guardarlo y se aplica solo a los nuevos eventos. Adapty no reenvía los eventos que ya ha enviado, y un evento de prueba que Adapty ya tiene retenido para [Prueba cualificada](#qualified-trial) se envía con el tipo seleccionado cuando comenzó la retención. En Other campaigns, las configuraciones de distintas apps que envían el mismo nombre de evento a una cuenta de Google Ads alimentan la misma acción de conversión. Selecciona el mismo tipo de ingresos en todas ellas para que los valores de esa acción de conversión sean del mismo tipo. :::warning Los ingresos netos son inferiores a los ingresos brutos. Si cambias de **Gross revenue** y una campaña usa un ROAS objetivo, reduce el ROAS objetivo en Google Ads, o la campaña puede dejar de publicarse. ::: ### Anulación de ingresos \{#revenue-override\} Si haces seguimiento de eventos de prueba y quieres que Google atribuya ingresos a ellos, usa la sección **Revenue override**. Aparece cuando el evento **Trial started** está habilitado. La anulación solo se aplica a **Trial started**; **Trial converted** y **Subscription started** siempre llevan sus ingresos reales. Para cada objetivo de evento de prueba, establece el porcentaje del precio de la suscripción que se reportará como ingresos. Adapty toma el porcentaje del precio en el tipo seleccionado en [Ingresos enviados a Google Ads](#revenue-sent-to-google-ads). Por ejemplo, con un 30% en **Gross revenue**, 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 mapping**. 2. En **Revenue override**, haz clic en **Add event**. 3. Selecciona el evento objetivo e introduce un porcentaje de ingresos (0–100). 4. Haz clic en **Save**. ### Prueba calificada :::warning La atribución de Adapty retrasa el evento de prueba hasta 24 horas y lo envía solo si el usuario no ha cancelado. Si un MMP también reenvía `trial_started` o [`trial_active`](events#synthetic-events), Google recibirá la misma prueba más de una vez. ::: Una prueba se califica una vez que sobrevive un número determinado de horas sin ser cancelada. Usa la sección **Qualified trial** para retener los eventos de prueba hasta que se califiquen, de modo que Google reciba únicamente las pruebas que los usuarios conservaron. La sección está disponible tanto para campañas de aplicaciones como para otras campañas. Para cada objetivo de evento de prueba, establece el número de horas de espera. Adapty cuenta las horas desde el inicio de la prueba y envía el evento cuando vence el plazo. Si el usuario desactiva la renovación automática antes del plazo, Adapty nunca envía el evento. Si el usuario vuelve a activar la renovación automática antes del plazo, Adapty envía el evento según lo previsto. Para retener un evento de prueba hasta que sea válido: 1. Activa **Trial started** en la sección **Events mapping**. 2. En **Qualified trial**, haz clic en **Add event**. 3. Selecciona el evento objetivo e introduce el número de horas de espera, de 1 a 24. 4. Haz clic en **Save**. **Revenue override** y **Qualified trial** funcionan de forma independiente, y puedes aplicar ambos al mismo evento de prueba. Revenue override cambia el valor que recibe Google; Qualified trial cambia cuándo lo recibe. Con ambos activos, Adapty retiene el evento hasta que la prueba se califica y luego lo envía con el valor sobreescrito. ## Datos de Google Ads en Adapty Attribution \{#google-ads-data-in-adapty-attribution\} Adapty actualiza el gasto, las impresiones y los clics de Google Ads cada 4 horas. Cada actualización también vuelve a traer los 3 días anteriores, porque Google sigue ajustando los datos recientes. En [Adapty Attribution analytics](ua-analytics), los datos de Google Ads están disponibles en cuatro niveles: campaña, grupo de anuncios, anuncio y palabra clave. El gasto a nivel de palabra clave solo existe para las campañas de Search, y las campañas de Performance Max solo informan a nivel de campaña. El desglose por país depende del nivel: - **Campaña y grupo de anuncios**: Exactos, tal como los reporta Google. - **Anuncio**: Estimado. Google no reporta anuncios por país, por lo que Adapty distribuye los números de cada anuncio entre los países de la misma forma en que se distribuyen los números de su grupo de anuncios. - **Palabra clave**: No disponible. Adapty también importa los recuentos de conversiones propios de Google, las estimaciones de pujas por palabras clave y los estados de las campañas. Para verlos en la tabla de análisis, selecciona el [preset de Google Ads](ua-analytics#google-ads-preset). Para saber qué muestra cada columna, consulta las [métricas de Google Ads](ua-metrics#google-ads-metrics). Una instalación con un click ID de Google Ads que no coincide con ninguna campaña en tus cuentas de Google Ads conectadas aparece bajo el canal **Google Play**. Para ver cómo Adapty elige entre Google Ads y otras fuentes que reclaman la misma instalación, consulta [Canales](adapty-user-acquisition#channels). ## Solución de problemas \{#troubleshooting\} ### El evento de prueba falla para un nuevo ID de enlace \{#test-event-fails-for-a-new-link-id\} Google Ads necesita aproximadamente 24 horas para reconocer un nuevo ID de enlace. Hasta entonces, el evento de prueba falla y Google no atribuye ningún evento a tus campañas. Un evento de prueba fallido en las primeras 24 horas no significa que la configuración sea incorrecta. Espera 24 horas y vuelve a enviar el evento de prueba. No reemplaces el ID de enlace mientras tanto, ya que un nuevo ID de enlace inicia su propia espera. Si el evento de prueba sigue fallando después de 24 horas, asegúrate de haber pegado el ID de enlace que creaste en **Data manager**, y no otro identificador de Google Ads. ### La cuenta de Google aparece como Inactiva \{#google-account-shows-inactive\} Una cuenta de Google aparece como **Inactive** cuando Google deja de aceptar el acceso de Adapty, por ejemplo, si eliminas el acceso de Adapty en la configuración de tu cuenta de Google o si el acceso caduca. Mientras la cuenta está inactiva, Adapty no actualiza el gasto de sus cuentas publicitarias ni envía conversiones a Google. Para restablecer la conexión, en **Integrations** > **Google Ads**, haz clic en **Sign in with Google** y selecciona la misma cuenta de Google. --- # File: ua-tracking-links --- --- title: "Links de seguimiento en Adapty Attribution" description: "Haz seguimiento de tus campañas y mide su éxito desde cualquier lugar." --- :::important Los links de seguimiento ayudan a Adapty a atribuir las instalaciones de la app. Si tu anuncio lleva a las personas a un funnel web, [etiqueta su URL con los parámetros de Adapty](ua-tag-ad-urls) en su lugar. ::: El destino habitual de un anuncio es tu página de App Store o Google Play. Esa página pertenece al store, no a ti, por lo que no puede reportar clics a Adapty. Usa un enlace de seguimiento como destino. Adapty captura la campaña, el conjunto de anuncios y el anuncio antes de redirigir al usuario al store, y luego asocia ese clic con la instalación que reporta el SDK. Tus [análisis de atribución de Adapty](ua-analytics) muestran qué anuncios generan ingresos. Adapty genera los enlaces para las campañas de [Meta Ads](ua-facebook#step-2-add-campaigns) y [TikTok for Business](ua-tiktok#step-2-add-campaigns), y una plantilla de seguimiento para las campañas de [Google Ads](ua-google-ads#other-campaigns). Para cualquier otra red publicitaria, [crea un enlace de seguimiento personalizado](#custom-tracking-links) con la integración **Custom** en el Dashboard. ## Tipos de enlaces de seguimiento \{#tracking-link-types\} Adapty genera dos tipos de enlace de seguimiento: - **Enlaces específicos por plataforma** — un enlace distinto para cada store. Pueden dirigir a los usuarios a listados personalizados en el store, que coincidan con tu campaña. - **One Link** — un único enlace para ambos stores, que se adapta a la plataforma del usuario en el momento del clic. Una página de enlace de seguimiento incluye los enlaces de todos los stores que configures, por lo que no necesitas crear uno nuevo por cada store. Para mayor flexibilidad, recomendamos los enlaces específicos por plataforma. ## Enlaces de seguimiento personalizados \{#custom-tracking-links\} :::note Apple Search Ads no necesita enlace de seguimiento. Adapty recibe la atribución de Apple Search Ads sin uno, y gestionas esas campañas en [Adapty Ads Manager](adapty-ads-manager). ::: Para crear un enlace de seguimiento personalizado: 1. En el dashboard de Adapty Attribution, abre **Integrations** y selecciona la tarjeta **Custom** en el grupo **Network**. 2. Haz clic en **Create link**. 3. Selecciona tu app de la lista y haz clic en **Next**. 4. Rellena los parámetros del enlace para asociarlo a una campaña y un anuncio concretos. | 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 analytics. | | **Campaign ID** | El identificador único de la campaña en tu plataforma de anuncios. | | **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 de anuncios. | | **Ad set name** | El nombre del conjunto de anuncios. | | **Ad ID** | El identificador único del creativo de anuncio individual. | | **Ad name** | El nombre del creativo o variación del anuncio. | 5. De forma predeterminada, se crea un One Link. Para añadir [enlaces específicos por plataforma](#tracking-link-types) en su lugar, desmarca la casilla **One Link**. 6. Haz clic en **Create**. 7. Abre la página de tu enlace de seguimiento. En la sección correspondiente a tu [tipo de enlace](#tracking-link-types), copia el valor de **Click link**. :::tip También puedes configurar parámetros adicionales en el enlace para [trabajar con datos diferidos](ua-deferred-data). Por ejemplo, puedes implementar deep linking diferido. ::: 8. Ve a tu plataforma de anuncios y usa el enlace como URL de destino del anuncio. --- # 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 de funnels web2app que te permite captar y cobrar a usuarios fuera del App Store, evitando sus comisiones y otras restricciones. Configura esta integración para enviar los datos de compras de FunnelFox a Adapty. Adapty cruza tus ingresos web con tu gasto publicitario, de modo que puedes ver cuánto generó cada campaña, conjunto de anuncios y anuncio. ## Cómo funciona \{#how-it-works\} Cuando un usuario completa una compra en tu funnel de FunnelFox, FunnelFox envía el evento de transacción a Adapty Attribution. Adapty usa el **Project ID** para identificar a qué app pertenece la transacción. El evento se almacena y aparece en los [análisis de Adapty Attribution](ua-analytics). Una vez que la integración está en marcha, cada transacción en Adapty incluye: - **Evento del ciclo de vida de la suscripción**: iniciada, renovada, cancelada, prueba convertida, reembolsada, entre otros - **Canal de atribución**: Facebook, TikTok, Google u orgánico. Adapty lo determina a partir del ID de clic, que las redes publicitarias añaden automáticamente — no necesitas configurarlo. - **Datos de atribución**: campaña, conjunto de anuncios e identificadores de anuncios; parámetros UTM; IDs de clic de plataforma (fbclid, ttclid, gclid). La campaña, el conjunto de anuncios y el anuncio solo llegan si tus anuncios incluyen los [parámetros de seguimiento](#step-3-tag-your-ad-urls) de Adapty. - **Datos de embudo y experimento**: nombre del embudo y nombre del experimento de FunnelFox, para que puedas comparar las variantes de las pruebas A/B - **Fecha de cohorte**: la fecha del primer inicio de prueba o compra. Las compras web no tienen evento de instalación de la app, por lo que Adapty las agrupa en cohortes según esa fecha. ## Configurar la integración \{#configure-integration\} :::note No necesitas el [píxel web de Adapty](ua-web-pixel) en un funnel de FunnelFox — FunnelFox reporta las compras a Adapty directamente. ::: ### 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 Attribution 1. En Adapty Attribution, 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 este paso para cada uno. ### Paso 3. Etiqueta las URLs de tus anuncios \{#step-3-tag-your-ad-urls\} FunnelFox solo puede transmitir los datos de campaña que llegan a la URL de tu página de destino, y los lee cuando el visitante aterriza por primera vez, no en el momento del pago. Añade los parámetros de seguimiento de Adapty a tus anuncios, tal como se describe en [Etiqueta las URLs de tus anuncios para web funnels](ua-tag-ad-urls). ### Paso 4. Activa la atribución de campaña en FunnelFox \{#step-4-turn-on-campaign-attribution-in-funnelfox\} :::note Esta opción está desactivada por defecto y FunnelFox no recupera datos anteriores: las transacciones completadas antes de activarla nunca llegarán a Adapty. ::: 1. En FunnelFox, ve a [**Integrations > Adapty**](https://app.funnelfox.com/integrations/adapty). 2. Activa **Campaign attribution**. Actívalo en cada proyecto de FunnelFox que hayas conectado. Tu proyecto de FunnelFox debe pertenecer a una organización y tus cuentas publicitarias deben estar conectadas en Adapty. Para los eventos que FunnelFox envía a través de este canal, consulta [Integración con Adapty](https://funnelfox.com/docs/integrations/subscription-management/adapty#campaign-attribution) en la documentación de FunnelFox. --- # File: ua-web-pixel --- --- title: "Configurar el píxel web de Adapty" description: "Instala el píxel web de Adapty en tu funnel web o página de destino para que las compras realizadas a través de Stripe o Paddle se atribuyan a los anuncios que las generaron." --- Si captas usuarios a través de un funnel web o una página de destino, el píxel web de Adapty conecta cada visita con el anuncio que la originó. El píxel asigna a cada visitante un click ID. Cuando el visitante realiza un pago, pasas ese click ID junto con la transacción, y Adapty Attribution relaciona la compra con la campaña, el conjunto de anuncios y el anuncio exactos. El píxel está disponible para campañas de Meta Ads, TikTok for Business y Google Ads. En Google Ads, está disponible para campañas Other, no para campañas de aplicaciones. :::note Si tu funnel funciona con FunnelFox, no necesitas el píxel. Consulta [Integrar FunnelFox con Adapty Attribution](ua-funnelfox). ::: ## Cómo funciona \{#how-it-works\} Al cargar la página, el pixel: - Asigna al visitante un `click_id` y lo almacena en el navegador durante 7 días. - Captura las señales publicitarias de la URL de la página: los IDs de clic de Meta (`fbclid`), TikTok (`ttclid`) y Google (`gclid`, `gbraid`, `wbraid`), los identificadores y nombres de campaña, conjunto de anuncios y anuncio, y los parámetros `utm_*`. - Envía el `click_id` y las señales capturadas a Adapty en segundo plano. En el momento del pago, solo necesitas adjuntar el `click_id` a la transacción. Adapty lo relaciona con las señales que el pixel ya envió y determina el canal publicitario automáticamente. :::tip El píxel solo registra las visitas a los anuncios. Para llevar las compras a Atribución de Adapty, conecta [Stripe](ua-stripe) o [Paddle](ua-paddle). ::: ## Antes de empezar \{#before-you-start\} Conecta [Meta Ads](ua-facebook), [TikTok for Business](ua-tiktok) o [Google Ads](ua-google-ads) a la Atribución de Adapty y añade una configuración de campaña. Adapty genera el fragmento de píxel por cada configuración de campaña, así que necesitas una para copiar el fragmento desde ahí. El píxel lee los datos del anuncio desde la URL de tu página de destino, por lo que tus anuncios deben incluir los parámetros de seguimiento de Adapty. Sin ellos, Adapty atribuye la compra a la red, pero no a la campaña, el conjunto de anuncios ni el anuncio. Consulta [Etiqueta las URLs de tus anuncios para funnels web](ua-tag-ad-urls). ## Instala el píxel \{#install-the-pixel\} 1. En Adapty Attribution, abre la configuración de tu campaña de Meta Ads, TikTok o Google Ads. 2. Despliega la sección **Web funnel** y copia el valor del campo **Pixel snippet**. En las configuraciones de Meta Ads y TikTok, la sección está en la pestaña **General**. 3. Pega el snippet en el `<head>` de cada página de tu funnel: ```html <script async src="https://api-ua.adapty.io/pixel/loader.js" data-code="YOUR_CODE"></script> ``` El valor `data-code` identifica tu app y campaña. Es un identificador público, no un secreto. ## Gestionar el consentimiento \{#manage-consent\} El píxel escribe una cookie de origen y una entrada en localStorage, y lee los IDs de clic de anuncios al cargar la página. Tú eres el responsable del tratamiento de datos de tu funnel y tienes la base legal para ello. Por defecto, el píxel se ejecuta al cargar la página. Para condicionarlo al consentimiento, añade `data-consent="denied"` al snippet — el píxel no almacena ni envía nada hasta que concedas el consentimiento desde tu banner de cookies: ```js window.adaptyPixel.grantConsent(); // start capture window.adaptyPixel.revokeConsent(); // clear stored state and stop window.adaptyPixel.getConsent(); // 'granted' | 'denied' ``` :::warning Condiciona el píxel al consentimiento para el tráfico de la UE/EEE. La normativa ePrivacy exige consentimiento previo para el almacenamiento en el navegador, y un píxel de atribución de origen propio no está exento. ::: El fragmento se carga de forma asíncrona, por lo que `window.adaptyPixel` no existe en la línea inmediatamente posterior a la etiqueta `<script>`. Llama a la API de consentimiento desde el callback de tu banner de consentimiento, o protege una llamada temprana con `if (window.adaptyPixel) { ... }`. ## Notas de uso \{#usage-notes\} - **Funnels de una sola página**: La navegación del lado del cliente mantiene el mismo `click_id`, por lo que un funnel creado como una single-page app funciona sin configuración adicional. - **Estabilidad del Click ID**: Leer el `click_id` al cargar la página o en el momento del pago devuelve el mismo valor. Si el visitante vuelve a entrar al funnel desde un nuevo anuncio, el clic más reciente tiene prioridad. - **Renovaciones**: Asocia el `click_id` únicamente a la transacción inicial. Adapty atribuye las conversiones de prueba y las renovaciones de suscripción a través de la propia suscripción. --- # File: ua-tag-ad-urls --- --- title: "Etiqueta las URLs de tus anuncios para web funnels" description: "Añade los parámetros de seguimiento de Adapty a tus anuncios de Meta Ads, TikTok o Google Ads para que las compras de web funnels se atribuyan a la campaña, grupo de anuncios y anuncio exactos." --- Etiqueta las URLs de tus anuncios cuando el anuncio lleve a los usuarios a una página de destino o web funnel antes del store. Si tu anuncio va directamente al App Store o Google Play, usa un [enlace de seguimiento](ua-tracking-links) como destino — el enlace lleva los datos de campaña por sí solo, sin necesidad de etiquetar nada. Adapty Attribution lee la campaña, el conjunto de anuncios y el anuncio desde la URL de tu página de destino. Las redes publicitarias añaden automáticamente su propio ID de clic (`fbclid`, `ttclid` o `gclid`), que identifica la red pero no la campaña, el conjunto de anuncios ni el anuncio. Para atribuir las compras hasta el anuncio exacto, añade los parámetros de seguimiento a tus anuncios. Los pasos son los mismos independientemente de cómo esté configurado tu embudo: con el [píxel web de Adapty](ua-web-pixel) en tu propio embudo o en [FunnelFox](ua-funnelfox). ## Antes de empezar \{#before-you-start\} Conecta [Meta Ads](ua-facebook), [TikTok for Business](ua-tiktok) o [Google Ads](ua-google-ads) a Adapty Attribution y añade una configuración de campaña. Adapty genera los parámetros de URL del anuncio por configuración de campaña, por lo que necesitas una para copiarlos de ella. ## Etiqueta tus anuncios \{#tag-your-ads\} 1. En la sección **Web funnel** de la configuración de la campaña, copia el valor del campo **Ad URL parameters**. 2. En la red publicitaria, pega el valor: - **Meta**: En el campo **URL parameters** del anuncio. - **TikTok**: Bajo la URL de destino del anuncio, en **Build URL parameters**. - **Google Ads**: En la configuración de la campaña, en **Campaign URL options > Final URL suffix**. El etiquetado automático también debe estar activado en la configuración de tu cuenta de Google Ads. El valor contiene macros de red. La red las reemplaza con los valores reales de campaña, conjunto de anuncios y anuncio en el momento del clic. Mantén la URL de tu landing page o embudo en el campo de destino del anuncio (**Website URL** en Meta, **Destination URL** en TikTok, **Final URL** en Google Ads) sin los parámetros. La red los añade a esa URL en el momento del clic, así que puedes cambiar la URL de destino más adelante sin tener que etiquetar el anuncio de nuevo. Los parámetros no incluyen valores `utm_*`, por lo que puedes mantener tu propio esquema UTM para tus propias analíticas — Adapty no lo requiere. --- # File: ua-stripe --- --- title: "Integrar Stripe con Adapty Attribution" description: "Conecta tu cuenta de Stripe con Adapty Attribution para atribuir compras, renovaciones, reembolsos y contracargos del funnel web a los anuncios que los generaron." --- Conecta tu cuenta de Stripe a Adapty Attribution para atribuir las compras realizadas en tu embudo web a los anuncios que las generaron. Una vez conectada, las compras, renovaciones, reembolsos y contracargos de Stripe aparecen en los análisis de Adapty Attribution, y los eventos de conversión pueden enviarse de vuelta a las redes publicitarias para optimizar las campañas. ## Cómo funciona \{#how-it-works\} 1. El [píxel web de Adapty](ua-web-pixel) asigna un ID de clic a cada visitante del funnel y captura los datos del anuncio desde la URL de la página de destino. 2. En el momento del pago, tu funnel adjunta el ID de clic a la transacción de Stripe. 3. Adapty recibe la transacción a través de un webhook que crea en tu cuenta de Stripe y la asocia al clic del anuncio. :::note Las compras web no tienen evento de instalación de app. Adapty las agrupa en cohortes por la fecha del primer inicio de prueba o compra — consulta [Analytics](ua-analytics). ::: ## Antes de empezar \{#before-you-start\} [Configura el píxel web de Adapty](ua-web-pixel) en tu embudo. Sin el píxel, no hay ningún ID de clic que adjuntar y las transacciones no pueden atribuirse a los anuncios. ## Paso 1. Adjunta el click ID en el checkout \{#step-1-attach-the-click-id-at-checkout\} Cuando crees la transacción, lee el click ID y añádelo al `metadata` de Stripe bajo la clave `adpt_click_id`: ```js const adptClickId = window.adaptyPixel.getClickId(); ``` El valor debe quedar en el **PaymentIntent** para pagos únicos o en la **Subscription** para pagos recurrentes, ya que son los objetos que lee Adapty. Si los creas directamente: ```js metadata: { adpt_click_id: adptClickId } ``` Si usas Stripe Checkout, define los metadatos en los objetos anidados. Los metadatos de la propia sesión de Checkout no se copian al PaymentIntent ni a la Subscription, por lo que un click ID dejado ahí nunca llega a Adapty: ```js stripe.checkout.sessions.create({ // ... payment_intent_data: { metadata: { adpt_click_id: adptClickId } }, // one-off payments subscription_data: { metadata: { adpt_click_id: adptClickId } }, // subscriptions }) ``` :::warning Establece los metadatos al crear el PaymentIntent, no actualizándolos después. Stripe copia los metadatos en el cargo en el momento en que se crea, por lo que un click ID añadido posteriormente nunca llega a Adapty. ::: El `click_id` es un identificador aleatorio corto, no es un secreto ni un dato personal. Es visible en tu dashboard de Stripe, lo cual es lo esperado. ## Paso 2. Conecta tu cuenta de Stripe \{#step-2-connect-your-stripe-account\} Primero, crea la clave en Stripe. En tu dashboard de Stripe, ve a **Developers → API keys** y crea una clave restringida con estos permisos: | Recurso | Acceso | |---|---| | Charges | Read | | Refunds | Read | | Invoices | Read | | Balance transactions | Read | | Disputes | Read | | Webhook endpoints | Write | El acceso de escritura en los endpoints de webhook permite a Adapty crear el webhook que entrega tus pagos — no hay nada que configurar manualmente en Stripe. Todo lo demás es de solo lectura: Adapty nunca escribe en tus datos de pago. Una clave sin un permiso requerido se rechaza en el momento de la conexión con un error que indica el permiso que falta. :::warning Usa una clave en modo live. Adapty descarta los pagos en modo de prueba a medida que llegan, por lo que una clave de prueba (`rk_test_...`) se conecta correctamente y aparece como **Valid**, pero nunca aparecerá ningún dato. Para verificar la integración, conecta la clave live y realiza una compra real. ::: A continuación, añade la clave en Adapty: 1. En Adapty Attribution, ve a [**Integrations > Stripe**](https://app.adapty.io/ua/integrations/stripe). 2. Pega la clave en **API key**. Opcionalmente, introduce un **Name** para distinguir las cuentas; por defecto, Adapty usa los últimos 4 caracteres de la clave. 3. Haz clic en **Connect**. La cuenta conectada aparece en la tabla con su entorno, el estado de la clave y el estado del webhook. La conexión se aplica a la app que estás configurando. Para recibir los pagos de la misma cuenta de Stripe en otra app, añade la misma clave en esa app. Adapty importa las transacciones a partir del momento en que conectas la cuenta. Para importar transacciones anteriores, contáctanos en [support@adapty.io](mailto:support@adapty.io). ## Verificar la integración \{#verify-the-integration\} Realiza una compra real a través de tu funnel: Adapty descarta las compras en modo de prueba. La compra debería aparecer en [Adapty Attribution analytics](ua-analytics) en un par de horas. Si tu visita provino de un anuncio con [URLs etiquetadas](ua-web-pixel), la compra se atribuye a esa campaña, conjunto de anuncios y anuncio; de lo contrario, se contabiliza como orgánica. ## Enviar eventos de conversión a la red publicitaria \{#send-conversion-events-to-the-ad-network\} Las compras web atribuidas siguen el mismo mapeo de eventos que el resto de la campaña: los eventos que actives en la sección **Events names** de la configuración de la campaña se envían de vuelta al píxel que asociaste con la campaña. Consulta la configuración del mapeo para [Meta Ads](ua-facebook) y [TikTok for Business](ua-tiktok). --- # File: ua-paddle --- --- title: "Integrar Paddle con Adapty Attribution" description: "Conecta tu cuenta de Paddle con Adapty Attribution para atribuir compras, renovaciones, reembolsos y contracargos del funnel web a los anuncios que los generaron." --- Conecta tu cuenta de Paddle con Adapty Attribution para atribuir las compras realizadas en tu embudo web a los anuncios que las generaron. Una vez conectada, las compras, renovaciones, reembolsos y contracargos de Paddle aparecen en los análisis de Adapty Attribution, y los eventos de conversión pueden enviarse de vuelta a las redes publicitarias para optimizar las campañas. ## Cómo funciona \{#how-it-works\} 1. El [píxel web de Adapty](ua-web-pixel) asigna un ID de clic a cada visitante del embudo y captura los datos del anuncio desde la URL de la página de destino. 2. En el momento del pago, tu embudo adjunta el ID de clic a la transacción de Paddle. 3. Adapty recibe la transacción a través de un webhook que crea en tu cuenta de Paddle y la asocia con el clic del anuncio. :::note Las compras web no tienen evento de instalación de app. Adapty las agrupa en cohortes según la fecha del primer inicio de prueba o compra; consulta [Analytics](ua-analytics). ::: ## Antes de empezar \{#before-you-start\} [Configura el píxel web de Adapty](ua-web-pixel) en tu embudo. Sin el píxel, no hay ningún ID de clic que asociar y las transacciones no podrán atribuirse a los anuncios. ## Paso 1. Adjunta el click ID en el checkout \{#step-1-attach-the-click-id-at-checkout\} Cuando abras el checkout o crees la transacción, lee el click ID y añádelo al `customData` de Paddle bajo la clave `adpt_click_id`: ```js const adptClickId = window.adaptyPixel.getClickId(); customData: { adpt_click_id: adptClickId } ``` Adjunta el click ID únicamente a la transacción inicial. Paddle copia los datos personalizados de una suscripción en las transacciones de renovación, por lo que Adapty atribuye automáticamente las renovaciones, los reembolsos y los contracargos. El `click_id` es un identificador aleatorio corto, no es un secreto ni datos personales. Es visible en tu Paddle dashboard, lo cual es normal. ## Paso 2. Conecta tu cuenta de Paddle \{#step-2-connect-your-paddle-account\} Primero, crea la clave en Paddle. En tu dashboard de Paddle, ve a **Developer Tools → Authentication** y crea una clave de API con estos permisos: | Recurso | Acceso | |---|---| | Transactions | Read | | Adjustments | Read | | Notification settings | Write | El acceso de escritura en la configuración de notificaciones permite a Adapty crear el destino del webhook que entrega tus pagos; no hay nada que configurar manualmente en Paddle. Todo lo demás permanece en solo lectura: Adapty nunca escribe en tus datos de pago. Una clave sin un permiso requerido se rechaza al conectar con un error que indica cuál falta. :::warning Usa una clave de producción. Las claves de Paddle son específicas por entorno, y Adapty descarta los eventos de sandbox en cuanto llegan, por lo que una clave de sandbox se conecta correctamente y aparece como **Valid**, pero no se registra ningún dato. Para verificar la integración, conecta la clave de producción y realiza una compra real. ::: Luego, añade la clave en Adapty: 1. En Adapty Attribution, ve a [**Integrations > Paddle**](https://app.adapty.io/ua/integrations/paddle). 2. Pega la clave en **API key**. Opcionalmente, escribe un **Name** para distinguir las cuentas — por defecto, Adapty usa los últimos 4 caracteres de la clave. 3. Haz clic en **Connect**. La cuenta conectada aparece en la tabla con su entorno, estado de la clave y estado del webhook. La conexión se aplica a la app que estás configurando. Para recibir los pagos de la misma cuenta de Paddle en otra app, añade la misma clave allí. Adapty importa las transacciones a partir del momento en que conectas la cuenta. Para importar transacciones anteriores, contáctanos en [support@adapty.io](mailto:support@adapty.io). ## Verificar la integración \{#verify-the-integration\} Realiza una compra real a través de tu funnel — Adapty descarta las compras sandbox. La compra debería aparecer en [Adapty Attribution analytics](ua-analytics) en un par de horas. Si tu visita provino de un anuncio con [URLs etiquetadas](ua-web-pixel), la compra se atribuye a esa campaña, conjunto de anuncios y anuncio; de lo contrario, se contabiliza como orgánica. ## Enviar eventos de conversión a la red publicitaria \{#send-conversion-events-to-the-ad-network\} Las compras web atribuidas siguen el mismo mapeo de eventos que el resto de la campaña: los eventos que actives en la sección **Events names** de la configuración de la campaña se envían de vuelta al píxel que asociaste con la campaña. Consulta la configuración del mapeo para [Meta Ads](ua-facebook) y [TikTok for Business](ua-tiktok). --- # 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 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 de 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 Adapty Attribution dashboard. :::note Adapty Attribution exporta un conjunto de archivos por día natural. El programador de exportaciones se ejecuta tres veces al día: a las **02:00**, **04:00** y **06:00 UTC**. Cada ejecución crea el trabajo de exportación del día anterior si aún no existe, o lo reintenta si una ejecución anterior falló. Por lo general, tus datos estarán disponibles tras la ejecución de las 02:00; las ejecuciones posteriores están pensadas para recuperarse de fallos, no para agregar datos nuevos. Cada archivo contendrá datos de los eventos creados durante el día natural anterior completo en UTC. Por ejemplo, los datos exportados el 8 de marzo contendrán todos los eventos creados el 7 de marzo desde las 00:00:00 hasta las 23:59:59 en UTC. ::: :::warning La exportación programada solo se ejecuta para empresas con un plan de pago o en período de prueba. Si tu empresa no cumple ninguna de estas condiciones, el programador omite la ejecución y no escribe ningún archivo, aunque la integración siga habilitada en el dashboard. Si tu bucket está vacío y la configuración parece correcta, comprueba primero el estado de tu plan. ::: ## 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` | App store (`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 anunciantes de iOS | | `idfv` | Identificador de proveedores de iOS | | `gaid` | Google Advertising ID (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 la 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` | Facebook Click ID | | `ttclid` | TikTok Click ID | | `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 | | `click_time` | Marca de tiempo del clic atribuido, en segundos Unix. Vacío cuando no se atribuye ningún clic a la instalación. | --- # 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 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 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 exporta un conjunto de archivos por día natural. El programador de exportaciones se ejecuta tres veces al día: a las **02:00**, **04:00** y **06:00 UTC**. Cada ejecución crea el trabajo de exportación del día anterior si aún no existe, o lo reintenta si una ejecución anterior falló. Por lo tanto, tus datos suelen estar disponibles tras la ejecución de las 02:00, y las ejecuciones posteriores sirven para recuperarse de fallos, no para añadir datos. Cada archivo contendrá los datos de los eventos creados durante el día calendario anterior completo en UTC. Por ejemplo, los datos exportados el 8 de marzo contendrán todos los eventos creados el 7 de marzo entre las 00:00:00 y las 23:59:59 en UTC. ::: :::warning La exportación programada solo se ejecuta para empresas con un plan de pago o en período de prueba. Si tu empresa no cumple ninguna de estas condiciones, el programador omite la ejecución y no escribe ningún archivo, aunque la integración siga habilitada en el dashboard. Si tu bucket está vacío y la configuración parece correcta, comprueba primero el estado de tu plan. ::: ## 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 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 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 | | `bundle_id` | Identificador de 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 | | `sdk_version` | Versión del SDK de Adapty | | `channel` | Canal de atribución | | `campaign_id` | Identificador de campaña | | `campaign_name` | Nombre de campaña | | `adset_id` | Identificador de conjunto de anuncios | | `adset_name` | Nombre del conjunto de anuncios | | `ad_id` | Identificador de 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 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 | | `click_time` | Marca de tiempo del clic atribuido, en segundos Unix. Vacío cuando no hay ningún clic atribuido a la instalación. | --- # 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 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 Google Cloud Console y en el Adapty Attribution Dashboard. :::note Programación Adapty Attribution exporta un conjunto de archivos por día natural a Google Cloud Storage. El programador de exportaciones se ejecuta tres veces al día: a las **02:00**, **04:00** y **06:00 UTC**. Cada ejecución crea el trabajo de exportación del día anterior si aún no existe, o lo reintenta si una ejecución anterior falló. Por lo general, los datos estarán disponibles tras la ejecución de las 02:00; las ejecuciones posteriores sirven para recuperarse de posibles fallos, no para añadir datos nuevos. Cada archivo contendrá los datos de los eventos creados durante el día natural anterior completo en UTC. Por ejemplo, los datos exportados el 8 de marzo contendrán todos los eventos creados el 7 de marzo desde las 00:00:00 hasta las 23:59:59 en UTC. ::: :::warning La exportación programada solo se ejecuta para empresas con un plan de pago o en período de prueba. Si tu empresa no cumple ninguno de estos requisitos, el programador la omite y no escribe ningún archivo, aunque la integración siga habilitada en el dashboard. Si tu bucket está vacío y la configuración parece correcta, comprueba primero el estado de tu plan. ::: ## 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 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 apps (`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` | 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 | | `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 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 | | `click_time` | Marca de tiempo del clic atribuido, en segundos Unix. Vacío cuando no hay ningún clic atribuido a la instalación. | --- # File: adapty-mail --- --- title: "Adapty Mail" description: "Campañas de correo electrónico generadas por IA que convierten usuarios en 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-testing', '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. **Recopilar correos electrónicos**: Tu app envía los correos electrónicos y los valores de `customer_user_id` de los usuarios a Adapty a través del SDK. Adapty Mail usa estos datos para identificar a los destinatarios y atribuir los ingresos al correo electrónico concreto que generó cada compra. También puedes enviar estos datos desde tu servidor con la [API de Adapty Mail](mail-send-data-via-api). Cuando la misma persona llega a Adapty Mail desde más de una fuente, Adapty Mail fusiona los registros en un único perfil. La coincidencia se realiza primero por el ID propio del sistema de envío, luego por `customer_user_id` y finalmente por dirección de correo electrónico, de modo que esa persona recibe una sola secuencia en lugar de una por cada fuente. 2. **Crear un paywall web**: La página de pago a la que enlaza cada correo electrónico. 3. **Generar una secuencia**: La IA usa tu perfil de marca para crear entre 1 y 15 correos electrónicos: textos, diseño, imágenes destacadas y enlaces de pago personalizados adaptados a la categoría y al tono de marca de tu app. 4. **Lanzar un flow**: Elige un desencadenador (nunca ha comprado, renovación cancelada, problema de facturación, suscripción expirada o reembolsada) y un segmento, y luego vincula tu campaña. Los correos empiezan a enviarse automáticamente, y los ingresos de las compras generadas por correo electrónico se atribuyen al correo concreto que impulsó la conversión. ## Requisitos \{#requirements\} Cada proyecto de Adapty Mail necesita: - **Dominio de envío**: Un dominio que controles, con acceso a su configuración DNS. Adapty Mail envía desde tu propio dominio, y solo se aceptan dominios apex. Consulta [Configura tu dominio de envío](mail-sending-domain). - **Un checkout al que dirigir a los usuarios**: Cada correo enlaza a uno. Consulta [Configura el checkout](mail-checkout). - **Identidad del usuario**: Un ID de usuario estable y una dirección de correo electrónico para cada usuario. Estos son los datos que Adapty Mail usa para hacer los envíos y atribuir los ingresos; si falta alguno, ese usuario quedará fuera de los envíos y de los análisis de campaña. Luego, según cómo lo configures: - **Stripe**, si quieres que el Paywall Builder integrado genere tu checkout. Para usar otro proveedor de pagos, aloja el checkout tú mismo y apunta Adapty Mail a su URL. - **Un [punto de entrada](mail-entry-points)** que te traiga datos de usuario: el SDK de Adapty si tu app lo utiliza, la API de Adapty Mail si tu backend es la fuente de verdad, o FunnelFox si vendes a través de funnels web. ## 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. Una vez completada la configuración, [prueba tu flow](mail-testing) antes de enviarlo a usuarios reales. Para configurar y ejecutar Adapty Mail desde Claude, ChatGPT o un agente de código, conecta el [servidor MCP de Adapty Mail](mail-mcp). En un agente de código, el [skill de Adapty Mail](mail-skill) añade una confirmación antes de cualquier acción que envíe correos a personas reales. --- # File: mail-get-started --- --- title: "Primeros pasos con Adapty Mail" description: "Configura Adapty Mail y lanza tu primer flow de correo electrónico." --- En esta guía configurarás Adapty Mail y lanzarás tu primera campaña de marketing. Las campañas transaccionales (mensajes de servicio, como la confirmación de registro) quedan fuera del alcance de este artículo. La configuración consta de diez partes: - [Antes de empezar](#before-you-start) - [1. Elige tu tipo de cuenta](#1-choose-your-account-type) - [2. Conecta tus datos](#2-connect-your-data) - [3. Configura tu marca](#3-set-up-your-brand) - [4. Configura tu dominio de envío](#4-set-up-your-sending-domain) - [5. Añade la dirección de tu empresa](#5-add-your-company-address) - [6. Selecciona el tipo de destino del email](#6-select-the-email-destination-type) - [7. Configura un paywall web](#7-set-up-a-web-paywall) - [Opción A: Generar con IA](#option-a-generate-with-ai) - [Opción B: Usar tu propio paywall alojado](#option-b-use-your-own-hosted-paywall) - [8. Genera una campaña con IA](#8-generate-a-campaign-with-ai) - [9. Lanza un flow](#9-launch-a-flow) - [10. Empieza a enviar](#10-start-sending) - [Solución de problemas](#troubleshooting) - [Más información](#learn-more) Adapty Mail realiza un seguimiento de tu progreso en la lista de verificación de onboarding y la oculta una vez que todos los pasos están completados. :::tip Un asistente de IA puede encargarse de la mayor parte de esta configuración contigo: crear la marca, añadir el dominio de envío, generar la campaña y lanzar el flow. Para conectar uno, consulta [Gestionar Adapty Mail con asistentes de IA](mail-mcp). La dirección de la empresa, el checkout y la activación del envío de datos permanecen en el dashboard. ::: ## Antes de empezar \{#before-you-start\} Asegúrate de tener lo siguiente antes de comenzar: - **Acceso DNS**: Puedes añadir registros a tu dominio raíz. - **Pagos**: Tienes una cuenta de Stripe con tus productos de suscripción configurados. Si quieres usar otro proveedor, aloja el checkout tú mismo — consulta [Configurar checkout](mail-checkout). ## 1. Elige el tipo de cuenta \{#1-choose-your-account-type\} :::warning Elige con cuidado: un proyecto que crees tú mismo en Adapty Mail no podrá vincularse a Adapty después. ::: El tipo de cuenta determina lo que puede hacer tu proyecto de Adapty Mail: | | Vinculado a Adapty (recomendado) | Independiente | | --- | --- | --- | | **Registro** | Inicia sesión con tu [cuenta de Adapty](https://app.adapty.io/) y abre **Mail**. Adapty crea un proyecto vinculado a tu app. El intercambio de datos entre Adapty Mail y el SDK está desactivado hasta el último paso de la configuración. No tienes que migrar tus compras a Adapty: el [modo observador](observer-vs-full-mode), fácil de configurar y que solo reporta tus transacciones, es suficiente. | [Regístrate solo en Adapty Mail](https://mail.adapty.io/signup) con una dirección de correo y una contraseña. Sin cuenta de Adapty y sin nada que instalar en tu app — tú aportas los datos, a través de FunnelFox o la API. | | **Página de pago** (Paso 7) | **Genera con IA** — una página de pago alojada, construida en torno a tu marca, con pagos de Stripe integrados — o tu propio paywall alojado | Tu propio paywall alojado | | **Segmentos** | Importa desde Adapty o créalos en Mail | Créalos en Mail | | **Marca** — la base a partir de la que se generan tus correos: nombre, logotipo, colores y tono (Paso 3) | Construida a partir del listing de tu app en el store antes de que llegues, si existe | Construida a partir de la URL de un store que pegues | | **Datos de compra** (Paso 2) | Un clic en **Settings** > **Project**; FunnelFox y la API también funcionan | Clave secreta pegada en el dashboard de Adapty o FunnelFox, o la API | | **Equipo** | Los compañeros se unen al abrir Mail desde tu empresa en Adapty; también mediante invitaciones por correo | Invitaciones por correo | ## 2. Conecta tus datos \{#2-connect-your-data\} Adapty Mail selecciona los destinatarios, coloca a cada persona en un flow y atribuye los ingresos al correo electrónico que generó la compra. Todo esto se sustenta en tres tipos de datos: - **Su dirección de correo electrónico** (obligatoria): La dirección que recibe tus emails, sujeta a [verificación de dirección](mail-suppression#address-verification-failed). - **Su [customer user ID](how-profiles-work)** (opcional, pero recomendado): El identificador único del usuario. Adapty lo almacena en el perfil del usuario para que la misma persona sea reconocida en todos sus dispositivos. Sin él, Adapty crea un nuevo perfil cada vez que reinstala tu app o cierra sesión. - **Su feed de eventos de suscripción** (opcional, pero recomendado): Pruebas, renovaciones, cancelaciones y reembolsos, en tiempo real. Esto te permite dirigirte a los usuarios según su comportamiento y vincular los ingresos al correo que impulsó la compra. Sin el feed de eventos, la única secuencia que puede recibir un usuario es la destinada a quienes nunca han comprado. Sigue las instrucciones para crear un **[punto de entrada](mail-entry-points)** para estos datos: - **Adapty**: Úsalo si tu app ya ejecuta el SDK de Adapty. Adapty reenvía perfiles, correos electrónicos y eventos de suscripción por ti, por lo que no necesitas un servidor propio. En tu app, añade la identificación de usuario y la recogida de correos electrónicos. Consulta [Conectar Adapty a Adapty Mail](mail-collect-emails). - **Adapty Mail API**: Úsalo si ya tienes un backend que contiene estos datos, si tu app no tiene el SDK de Adapty, o si importas suscriptores desde otro lugar. Publicas perfiles y eventos de transacción tú mismo. La clave secreta en **Settings** > **Project** autentica las solicitudes. Consulta [Enviar correos electrónicos y transacciones a través de la Adapty Mail API](mail-send-data-via-api). - **FunnelFox**: Úsalo si captas y cobras a usuarios a través de los funnels web de FunnelFox. FunnelFox ya tiene sus correos electrónicos, compras y eventos de suscripción, y los reenvía a Adapty Mail por su cuenta — sin trabajo con el SDK ni solicitudes que escribir. Consulta [la guía de integración de FunnelFox](https://funnelfox.com/docs/integrations/email-providers/adapty-mail). - **Una combinación**: Puedes usar más de un punto de entrada a la vez. Adapty Mail empareja a las personas por dirección de correo electrónico. Dos fuentes que reporten la misma dirección confluyen en un solo perfil; dos direcciones distintas crean perfiles separados, incluso cuando `customer_user_id` es idéntico. Adapty Mail comienza a enviar correos electrónicos una vez que el resto de la configuración está en marcha. ## 3. Configura tu marca \{#3-set-up-your-brand\} Adapty Mail no puede generar emails ni paywalls web sin una **marca**. Tu marca almacena la información que Adapty Mail necesita para reflejar la apariencia y el tono de tu app: nombre, logo, colores, tipografías, tono de voz, funcionalidades y reseñas. Tú proporcionas el listado del store y Adapty Mail construye la marca a partir de él. 1. En Adapty Mail, abre **Brand**. 2. Si la página muestra una marca ya configurada, revísala y continúa. Si muestra la pantalla **Set up your brand**, sigue los pasos a continuación. 3. En **Store**, elige **App Store** o **Google Play** y pega la URL de tu ficha. :::note La pantalla de configuración solo acepta una ficha de App Store o Google Play. Si tu app no tiene ficha en ningún store —por ejemplo, un funnel web-to-app—, contacta con el soporte de Adapty antes de continuar. ::: 4. Haz clic en **Build my brand**. Adapty Mail descarga la página, analiza las reseñas e infiere el tono de tu marca. Normalmente termina en menos de un minuto. 5. Para revisar secciones, editarlas manualmente o añadir fuentes de landing page y redes sociales, consulta [Brand](mail-brand). ## 4. Configura tu dominio de envío \{#4-set-up-your-sending-domain\} Adapty Mail envía correos desde tu dominio privado. Añades los registros DNS una sola vez y todas las campañas usan el mismo dominio verificado. Para registrar tu primer dominio, en Adapty Mail ve a **Settings** > **DNS** > la sección **Email domains**. 1. En el campo **Domain**, escribe tu dominio raíz. Debe tener un sufijo de dominio de nivel superior (`yourapp.com`). Adapty Mail no acepta subdominios (`app.yourapp.com`) ni dominios con sufijos de segundo nivel (`yourapp.co.uk`, `yourapp.com.br`). 2. En el campo **Sending subdomain**, escribe el prefijo desde el que se enviarán tus correos, o déjalo vacío para usar `mail`. Los correos llegarán desde `hello@mail.yourapp.com`. 3. Haz clic en **Add domain**. 4. En tu registrador de dominio, añade todos los registros DNS que aparecen en la nueva tarjeta de dominio. Están agrupados en tres bloques: los registros DKIM y MAIL FROM de tu subdominio de envío, los registros del buzón que calienta el dominio, y los registros de `go.yourapp.com`, que sirve imágenes y enlaces rastreados. Para saber qué autentica cada grupo, consulta [Qué registros añadir](mail-sending-domain#2-add-the-dns-records). Para exportarlos todos de una vez, haz clic en **Export**. 5. Opcionalmente, añade el registro TXT DMARC sugerido en tu dominio raíz (recomendado). 6. Haz clic en **Check** para verificar de inmediato, o cierra la tarjeta y deja que Adapty Mail consulte el DNS automáticamente. Resumen del tiempo de verificación: - **Sondeo automático**: La primera comprobación se ejecuta 1 minuto después de añadir el dominio; luego los intervalos se duplican hasta alcanzar una vez por hora, hasta que se encuentren los registros. - **Comprobación manual**: Haz clic en **Check** en cualquier momento para lanzar una comprobación inmediata. - **Propagación de DNS**: Normalmente en minutos; en casos excepcionales, hasta 48 horas. - **Ventana de verificación**: 7 días. Si caduca, tus registros DNS permanecen en su lugar — añade tu dominio de nuevo en **Settings** > **DNS** para iniciar una nueva ventana. Un dominio verificado es suficiente para empezar a usar Adapty Mail. Un proyecto puede tener hasta cinco dominios, y puedes añadir el resto más adelante. Para más detalles sobre cada tipo de registro y el calentamiento del dominio, consulta [Configura tu dominio de envío](mail-sending-domain). ## 5. Añade la dirección de tu empresa \{#add-your-company-address\} Las leyes anti-spam exigen una dirección postal física en cada correo electrónico de marketing. Adapty Mail la incluye en el pie de página de cada correo de campaña y bloquea la generación de campañas hasta que la rellenes. 1. En Adapty Mail, ve a **Settings** > **Company**. 2. En la sección **Company address**, completa los campos **Legal company name**, **Address line 1**, **City**, **Postal code** y **Country**. Los campos **Address line 2** y **State / Region** son opcionales. 3. Haz clic en **Save**. ## 6. Selecciona el tipo de destino de correo \{#select-the-email-destination-type\} Elige dónde aterrizarán tus correos. Un **destino de correo** es una dirección de página que guardas y reutilizas — cualquier número de campañas puede apuntar al mismo. Para los distintos tipos de campaña, Adapty Mail ofrece diferentes tipos de destino, que se explican en detalle en [Destinos de correo](mail-destinations). Una campaña sin destino no se puede guardar. Esta guía crea una campaña de marketing, que necesita un **web paywall**. | Tipo de destino | Contenido del destino | Dónde utilizarlo | | --- | --- | --- | | **Web Paywall** | Una página de pago en el navegador, donde las compras son pagos web en lugar de transacciones de App Store o Google Play. | Campañas de marketing | | **Deep Link** | Una de las pantallas de tu app. Si el dispositivo no tiene la app instalada, el enlace lleva a la ficha de la store. | Emails de activación de la app, tras una compra en la web | | **Cancellation Page** | Página de gestión de cuenta y suscripción, alojada por ti o por tu proveedor de facturación. | Emails puntuales que informan a un nuevo suscriptor de dónde gestionar su suscripción | En Adapty Mail, ve a **Email Destinations**, mantén **Web Paywall** seleccionado y haz clic en **Create**. Asigna un **Name** al destino. ## 7. Configura un paywall web \{#7-set-up-a-web-paywall\} Completa el campo **URL** en el destino que acabas de crear. Adapty Mail puede crear el paywall por ti, o puedes alojar el tuyo propio: | Opción | Cómo se crea la página | Requisitos | | --- | --- | --- | | **Generate with AI** | Adapty Mail la construye en el editor integrado, impulsado por [FunnelFox](https://funnelfox.com). | Stripe y un proyecto vinculado a una app de Adapty | | **Use your own hosted paywall** | Tú la construyes y alojas. Adapty Mail redirige a ella con los datos del destinatario en la URL. | Cualquier proveedor de pagos, más tu propio código que lleve el `external_profile_id` de la URL a un [evento de transacción](mail-send-data-via-api#send-transaction-events). | ### Opción A: Generar con IA Bajo el campo **URL**, encuentra el enlace **generate one with AI**. Haz clic en él. Despliega el panel **Generation setup** y completa sus seis pasos en orden. Cada botón abre la página donde ocurre ese paso, así que haz clic en **Refresh** cuando vuelvas. <ZoomImage id="mail-checkout-prerequisites.webp" width="500px" alt="Lista de verificación de Generation setup en la página de creación de Web Paywall" /> | Elemento de la lista | Qué hacer | | --- | --- | | **1. Brand is ready** | Ya marcado si configuraste tu [marca](mail-brand). Si no, haz clic en **Set up brand**. | | **2. Log in to the paywall builder dashboard** | Haz clic en **Log in** para autenticarte en el builder. | | **3. Stripe account connected** | Haz clic en **Connect Stripe** y conecta tu cuenta. | | **4. At least one Stripe product with a price** | Haz clic en **Open Stripe dashboard** y añade un producto con precio. | | **5. Generate the paywall** | 5.1. Haz clic en **Generate**.<br />5.2. Elige hasta tres planes. Haz clic en **Add plan** para cada uno después del primero.<br />5.3. Asigna a cada plan un precio de producción, un precio de sandbox, o ambos, todos en la misma moneda. Rellena la misma columna para todos los planes: una columna parcialmente rellenada bloquea la generación. Sin precios de sandbox, los [emails de prueba](mail-testing) llevan al paywall de producción, donde las compras de prueba no funcionan.<br />5.4. Haz clic en **Generate paywall**. Tarda unos minutos, y el campo **URL** se rellena solo cuando termina. | | **6. Review and publish the paywall in the builder** | Haz clic en **Open editor**, luego revisa y publica. Desactivado hasta que hayas generado el paywall. | <ZoomImage id="mail-checkout-generation-dialog.webp" width="500px" alt="Diálogo Generate a paywall con columnas de precios de producción y sandbox" /> :::important El paywall debe estar publicado antes de que pueda gestionar el tráfico de checkout. Los paywalls sin publicar devuelven un error cuando los usuarios hacen clic en los enlaces de checkout del email. ::: Una vez publicado el paywall, haz clic en **Save**. ### Opción B: Utiliza tu propio paywall alojado 1. Pega la URL de tu paywall alojado en **URL**, con marcadores de posición como parámetros de consulta. Debe usar HTTPS. Adapty Mail sustituye los marcadores de posición por cada destinatario para que la página de pago pueda identificar al visitante. Ejemplo: ``` https://example.com/paywall?email={email}&adapty_profile_id={adapty.external_profile_id} ``` La página de creación muestra los marcadores de posición disponibles y bloquea el guardado si encuentra alguno que no reconoce. Consulta [Configurar el checkout](mail-checkout#use-your-own-hosted-paywall) para saber a qué se resuelve cada uno. 2. Haz clic en **Save**. [Configurar el checkout](mail-checkout) cubre el proceso completo de generación, el contenido de la página generada y lo que debe hacer una página alojada por ti. ## 8. Genera una campaña con IA \{#8-generate-a-campaign-with-ai\} Una **campaña** es una secuencia de hasta 15 correos electrónicos que un destinatario recibe a lo largo del tiempo. La IA escribe toda la secuencia a partir de la marca que configuraste en el [Paso 3](#3-set-up-your-brand): textos, diseño, imágenes principales y enlaces de pago personalizados. 1. En Adapty Mail, ve a **Campaigns** y haz clic en **Create**. 2. En el diálogo **New marketing campaign**, selecciona el motivo por el que el cliente recibirá estos correos: una compra que nunca realizó, una renovación que canceló o un pago fallido. No podrás cambiar esto después, y la campaña solo aparece en el flow para la situación que elijas. Haz clic en **Continue**. 3. Escribe el nombre de la campaña. 4. En el desplegable **Email Destination (Web Paywall)**, selecciona el web paywall que añadiste en el paso anterior. 5. Haz clic en **Generate emails**. 6. Rellena el diálogo de generación: tono, 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. 7. Haz clic en **Generate**. La generación suele tardar unos minutos. El sistema expira después de 5 minutos si no puede completarla — vuelve a intentarlo si ocurre. 8. Previsualiza cada correo. El encabezado de la vista previa tiene un **Theme toggle** (Auto, Light, Dark) que controla cómo se renderiza la previsualización — 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 preciso. 9. Haz clic en **Create** para guardar la campaña. Adapty Mail guarda la campaña como **borrador**. No envía nada hasta que la adjuntes a un flow en el siguiente paso. Para llegar a los destinatarios en su idioma, [localiza la campaña](mail-localization) antes de lanzar el flow. ## 9. Lanzar un flow \{#launch-a-flow\} Adapty Mail incluye sus propios **flows**. Un flow conecta **triggers** (eventos de suscripción) con **campañas** (secuencias de correos). Cada uno de los triggers — que una suscripción expire, que un pago falle, etc. — tiene su propio flow. Un flow puede enviar una campaña a todos los que activan el trigger, o usar **segmentos** para enviar campañas distintas a grupos diferentes. 1. En Adapty Mail, ve a **Flows** y abre el trigger que quieras configurar: - **Never purchased** — usuarios que se registraron pero todavía no han hecho 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 vencida, o período de gracia. - **Expired** — la suscripción ha caducado y se ha perdido el acceso. - **Refunded** — usuarios que solicitaron un reembolso tras la compra. Consulta [Flows](mail-flows) para ver el objetivo y el tono recomendado para cada trigger. 2. Haz clic en **Create** para abrir el diálogo. 3. En el diálogo: - Elige un **Segment**. **All Users** se dirige a todos los que activen este trigger, y es suficiente para empezar. Para acotar la audiencia más adelante, crea un [segmento diferente](mail-segments) — un subconjunto filtrado de tus usuarios, según su país, edad o estado de suscripción. - Establece el tipo de contenido en **Campaign** (la opción A/B Test se explica en [pruebas A/B](mail-ab-testing)). - Selecciona la **Campaign** que guardaste en el [Paso 8](#8-generate-a-campaign-with-ai). 4. Haz clic en **Save**. El flow está activo — no hay botón de lanzamiento. - Un usuario que activa el trigger y coincide con el segmento se incorpora inmediatamente a la campaña. El primer correo se envía tras su **Delay**. - Cada correo tiene su propio **Delay** — el tiempo que transcurre desde el anterior antes de enviarse. Puedes editarlos en el editor de campaña. - Adapty Mail solo envía entre las 08:00 y las 21:00 según la zona horaria del destinatario. Un envío programado fuera de ese horario se desplaza al límite más cercano de la ventana. - El primer correo está exento cuando debe enviarse en las dos horas siguientes al trigger. Se envía a tiempo, aunque sea de noche. :::note Puedes añadir más filas al mismo trigger, cada una con su propio segmento y campaña. Adapty Mail las revisa de arriba a abajo y usa el primer segmento que coincida. Deja **All Users** al final — Adapty Mail no guardará un flow con él en ninguna otra posición. ::: :::tip Si los nuevos destinatarios deben confirmar su dirección antes de recibir comunicaciones de marketing, activa el [doble opt-in](mail-double-opt-in) antes de este paso. Solo los perfiles creados después de activarlo deberán confirmarlo. ::: ## 10. Empezar a enviar \{#10-start-sending\} Tu campaña está configurada pero aún no está activa: ningún dato de usuario llega a Adapty Mail. Empieza a hacer fluir los datos a través del [punto de entrada que elegiste](#2-connect-your-data). Si tu proyecto está vinculado a Adapty (Paso 1) solo cambia *cómo* activas el flujo de Adapty, no lo que llega: | Tus datos provienen de | Tu proyecto de Mail está vinculado a una app de Adapty | Qué hacer | | --- | --- | --- | | [El SDK de Adapty](mail-collect-emails#5-enable-sending) | Sí | En Adapty Mail, ve a **Settings** > **Project** y, en la sección **Data integration**, haz clic en **Enable Adapty integration**. | | [El SDK de Adapty](mail-collect-emails#5-enable-sending) | No | Adapty puede alimentar un proyecto al que no esté vinculado — solo necesita la clave. Copia la clave secreta desde **Settings** > **Project** y pégala en el dashboard de Adapty en **Integrations** > **Adapty Mail**. | | Tu backend, a través de la [API de Adapty Mail](mail-send-data-via-api) | Cualquiera | Empieza a publicar perfiles y eventos de transacciones. Los perfiles que envíes antes de terminar esta configuración nunca recibirán nada, por eso este paso va al final. | | [FunnelFox](mail-funnelfox) | Cualquiera | Copia la clave secreta desde **Settings** > **Project** y pégala en FunnelFox en **Integrations** > **Adapty Mail**. Los datos empiezan a llegar en aproximadamente un minuto. | Desde aquí, los usuarios que cumplan el disparador y pertenezcan a tu segmento entrarán en la campaña. Antes de dejar entrar a usuarios reales, [prueba toda la cadena](mail-testing) con un usuario de prueba. ## Solución de problemas \{#troubleshooting\} | Problema | Solución | | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Generate emails** aparece en gris | Falta la dirección de tu marca o empresa. El tooltip del botón indica cuál. Consulta el [Paso 3](#3-set-up-your-brand) y el [Paso 5](#5-add-your-company-address) | | La generación del paywall está bloqueada | El panel indica la causa: no hay marca, no hay cuenta de Stripe conectada o no hay productos de Stripe con precios. Corrígelo y luego haz clic en **Refresh** en la lista de verificación | | La campaña no se guarda | Cada campaña necesita un destino. Crea uno en el [Paso 6](#6-select-the-email-destination-type) | | No se puede editar un destino | Un destino pasa a ser de solo lectura en cuanto lo vinculas a una campaña. Crea un nuevo destino y selecciónalo en su lugar. | | Los registros DNS no se verifican | Comprueba que coincidan exactamente: sin puntos finales, destinos CNAME correctos, un valor por registro. La propagación del DNS puede tardar hasta 48 horas | | La ventana de verificación expiró | Vuelve a añadir el dominio en **Settings** > **DNS** para iniciar una nueva ventana. Los registros en tu registrador siguen siendo válidos, no es necesario añadirlos de nuevo | | La generación de emails agotó el tiempo | La generación se detiene después de 5 minutos. Vuelve a intentarlo y contacta con el soporte de Adapty si el problema persiste | | Un destinatario de prueba no recibe el email | Es posible que la dirección del perfil no haya superado la verificación que Adapty Mail realiza en cada perfil nuevo. Prueba con una dirección diferente | Para errores fuera de la configuración inicial, consulta las secciones de solución de problemas en [Configura tu dominio de envío](mail-sending-domain#troubleshooting) y [Configura el checkout](mail-checkout#troubleshooting). ## Más información \{#learn-more\} - **[Prueba tu flow](mail-testing)**: Lleva a un usuario de prueba por toda la cadena antes de lanzar. - **[Conecta Adapty a Adapty Mail](mail-collect-emails)**: Configuración del SDK y estrategias para conseguir cobertura de emails si tu app aún no los recopila. - **[Configura tu dominio de envío](mail-sending-domain)**: Detalles de registros DNS, niveles de calentamiento y resolución de problemas. - **[Configura el checkout](mail-checkout)**: Qué contiene la página de checkout y cómo funciona la personalización. - **[Analíticas de campaña](mail-analytics)**: Rastrea entregas, interacciones e ingresos. - **[Pruebas A/B](mail-ab-testing)**: Prueba múltiples versiones de secuencias. --- # File: mail-entry-points --- --- title: "Puntos de entrada de Adapty Mail" description: "Elige cómo llegan a Adapty Mail los perfiles de usuario, correos electrónicos y compras." --- Un punto de entrada es la forma en que los datos de usuario llegan a Adapty Mail. Independientemente del que uses, siempre incluye las mismas tres cosas: - **Su dirección de correo electrónico** (obligatoria): el destino de tus emails. Sin ella, Adapty Mail no crea ningún perfil. - **Su [customer user ID](how-profiles-work)** (opcional, pero recomendado): tu propio identificador estable. Adapty mantiene un perfil por persona a lo largo de reinstalaciones e inicios de sesión, de modo que una compra realizada antes de que te dieran un email les llega igualmente. - **Su feed de eventos de suscripción** (opcional, pero recomendado): pruebas, renovaciones, cancelaciones y reembolsos, en tiempo real. Sin él, la única secuencia que alguien puede recibir es la destinada a quienes nunca han comprado. Pick the entry point that matches your app: - **Adapty**: Úsalo si tu app ya ejecuta el SDK de Adapty. Adapty reenvía perfiles, correos electrónicos y eventos de suscripción por ti, así que no necesitas un servidor propio. En tu app, añade identificación de usuario y recopilación de correos electrónicos. Consulta [Conectar Adapty a Adapty Mail](mail-collect-emails). - **Adapty Mail API**: Úsalo si ya tienes un backend con estos datos, si tu app no tiene el SDK de Adapty, o si importas suscriptores desde otro lugar. Tú publicas los perfiles y eventos de transacción por tu cuenta. La clave secreta en **Settings** > **Project** autentica las solicitudes. Consulta [Enviar correos electrónicos y transacciones mediante la Adapty Mail API](mail-send-data-via-api). - **FunnelFox**: Úsalo si adquieres y cobras usuarios a través de funnels web de FunnelFox. FunnelFox ya tiene sus correos electrónicos, compras y eventos de suscripción, y los reenvía a Adapty Mail por sí solo — sin trabajo de SDK ni solicitudes que escribir. Consulta [Conectar FunnelFox a Adapty Mail](mail-funnelfox). - **Una combinación**: Puedes usar más de un punto de entrada a la vez. Adapty Mail empareja personas por dirección de correo electrónico. Dos fuentes que informen la misma dirección terminan en un único perfil; dos direcciones diferentes crean perfiles separados, incluso cuando `customer_user_id` es idéntico. <CustomDocCardList ids={['mail-collect-emails', 'mail-send-data-via-api', 'mail-funnelfox']} /> --- # File: mail-collect-emails --- --- title: "Conectar Adapty con Adapty Mail" description: "Envía perfiles, correos electrónicos y compras a Adapty Mail desde una app que ya tiene el SDK de Adapty." --- Adapty es uno de los puntos de entrada de Adapty Mail — úsalo cuando tu app ya ejecute el SDK de Adapty. Adapty transfiere perfiles, direcciones de correo electrónico y eventos de suscripción a Adapty Mail, que los utiliza para seleccionar destinatarios, asignar a cada persona un flow y atribuir los ingresos al correo que impulsó la compra. Si tu app no ejecuta el SDK de Adapty, envía los mismos datos desde tu servidor — consulta [Enviar correos y transacciones mediante la API de Adapty Mail](mail-send-data-via-api). :::important Para obtener datos de ingresos precisos, la configuración mínima es instalar el SDK en modo observador y activar las notificaciones del servidor de App Store. ::: Adapty Mail necesita tres cosas de tu app: datos de compras (para atribuir los ingresos al email que generó cada conversión), un identificador de usuario estable y los emails de los usuarios. Configura las tres cosas, luego configura tu dominio, checkout, campaña y flow en Adapty Mail, y activa el envío al final. ## 1. Permite que Adapty registre tus ingresos \{#1-let-adapty-track-your-revenue\} 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 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 e informar 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 de servidor de App Store en Adapty](enable-app-store-server-notifications) para recibir actualizaciones relacionadas con los ingresos directamente desde App Store. ## 2. Identifica a tus usuarios \{#2-identify-your-users\} Pasa un ID estable — tu ID de usuario del backend, UID de Firebase o similar — ya sea pasándolo como `customerUserId` a `.activate()` al iniciar el SDK, o llamando a `Adapty.identify()` más tarde (por ejemplo, al iniciar sesión). En cualquier caso, establece el ID antes de mostrar cualquier paywall. Adapty lo utiliza para mantener un único perfil por persona entre reinstalaciones e inicios de sesión, y Adapty Mail lo almacena entre los IDs externos del perfil. Adapty Mail identifica a las personas por dirección de correo electrónico, no por este ID. 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). ## 3. Recopilar emails de usuarios \{#3-collect-user-emails\} Una vez que un usuario proporcione su email en tu app (por ejemplo, al registrarse o durante el proceso de pago), pásalo a Adapty llamando a `updateProfile` con el atributo `email`. 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). :::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 asociar las compras al usuario correcto. - Obtén el consentimiento explícito del usuario antes de recopilar y enviar emails a Adapty. Eres responsable del cumplimiento del RGPD, CAN-SPAM y normativas similares en tus mercados objetivo. Para que Adapty Mail confirme cada nueva dirección antes de enviar marketing, activa el [doble opt-in](mail-double-opt-in). - Si un usuario rechaza los emails de marketing, llama a `updateProfile` con el atributo personalizado `email_consent` establecido en `"false"`, para que Adapty Mail no le envíe ningún email de marketing. Establécelo en `"true"` si acepta más adelante. ::: <Details> <summary>Verifica la cobertura de tu email</summary> Después de implementar la recopilación, comprueba la cobertura en Adapty: 1. Ve a **Customers** > **Profiles**. 2. Filtra por 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 necesitas esperar al 100% — lanza en cuanto alcances el 30%. Los usuarios que proporcionen su email más adelante se inscriben automáticamente en las campañas activas cuando cumplen 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. | | **Captura de email antes del paywall** | Apps sin autenticación — salud, bienestar, astrología, editores de fotos | Añade una pantalla de captura de email entre el onboarding y el paywall. La conversión suele situarse entre el 70–90% porque los usuarios ya han invertido tiempo. | | **Checkout del web paywall builder** | Mínimo trabajo con el SDK; email capturado en web | La primera pantalla del web paywall builder recoge el email y lo pasa a Adapty — útil para usuarios que hacen clic en una campaña antes de que haya una pantalla de captura in-app activa. | | **Paso del onboarding** | Onboarding basado en cuestionarios (fitness, nutrición, educación) | Coloca una pantalla de captura de email 2–3 pasos dentro del onboarding. Preséntala como entrega de valor ("Te enviaremos tu plan personalizado por email") y evita que el paso sea omitible. | | **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). | ## 4. Configura el dominio, el checkout, la campaña y el flow \{#4-set-up-domain-checkout-campaign-and-flow\} Para enviar correos, Adapty Mail necesita: - Un **dominio de envío** verificado - Una **página de checkout** a la que enlacen tus correos - Una **campaña** para enviar - Un **flow** que decida quién la recibe Configura los cuatro en Adapty Mail: sigue los pasos del 3 al 8 de la guía [Primeros pasos con Adapty Mail](mail-get-started). ## 5. Activar el envío \{#enable-sending\} Activar la integración es el último paso: los eventos empiezan a fluir, los segmentos comienzan a coincidir y los correos se envían. Si tu proyecto de Mail está vinculado a una app de Adapty solo cambia *cómo* lo activas, no lo que llega: - **Vinculado a una app de Adapty**. Adapty creó este proyecto la primera vez que abriste Mail desde el Adapty Dashboard. En Adapty Mail, ve a **Settings** > **Project** y, en la sección **Data integration**, haz clic en **Enable Adapty integration** (o **Enable** si la integración ya existe de una configuración anterior). - **No vinculado**. Creaste este proyecto tú mismo en Adapty Mail, ya sea en una cuenta independiente o en tu cuenta de Adapty. La sección **Data integration** no tiene el botón **Enable Adapty integration**, pero Adapty puede alimentar el proyecto igualmente: solo necesita la clave. 1. En Adapty Mail, ve a **Settings** > **Project** y copia la **Secret key**. 2. En el Adapty Dashboard, ve a **Integrations** > **Adapty Mail**, activa la integración, pega la clave en **Secret Key** y guarda. Ambas opciones crean la misma integración en Adapty — el botón de Adapty Mail rellena automáticamente la clave secreta. Una vez activada, Adapty envía cada evento de suscripción — nuevas suscripciones, renovaciones, trials, 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. ## Limitaciones \{#limitations\} - **Usuarios anónimos**: Sin un `customer_user_id` estable, Adapty genera un nuevo ID de perfil cada vez que alguien reinstala la app o cierra sesión. Una compra realizada antes de que el usuario facilitara su email nunca llega a su perfil de Adapty Mail — queda vinculada al ID anterior. Sus ingresos, estado de compra y membresía en segmentos serán incorrectos a partir de ese momento. - **Usuarios sin email**: Adapty Mail no crea ningún perfil para quienes no hayan proporcionado una dirección de correo. No hay nada que excluir de los envíos ni de los análisis — simplemente no existen en el sistema. En cuanto se registre una dirección, el perfil se crea y el usuario pasa a ser elegible. --- # 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: - Agrega suscriptores cuando aún no tienes una base en Adapty Mail. - Reutiliza la base de suscriptores de tus otras apps. - Alimenta 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 recopila emails y compras automáticamente — consulta [Conectar Adapty con Adapty Mail](mail-collect-emails). Usa la API cuando tu app no tenga el SDK de Adapty, cuando los datos ya estén en tu servidor, o cuando importes suscriptores desde otra fuente. También puedes usar la API junto con otro punto de entrada. Adapty Mail identifica a las personas por dirección de email. Si dos fuentes reportan la misma dirección, se asocian a un único perfil; si las direcciones son distintas, se crean perfiles separados, aunque el `customer_user_id` sea idéntico. ::: ## 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 de API secreta**: En Adapty Mail, ve a **Settings** > **Project** y copia la **Secret key**. 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 aplicables 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. ::: Si la misma persona también llega a Adapty Mail a través de otro [punto de entrada](mail-entry-points), envía `customer_user_id` — el mismo ID de usuario que utiliza la fuente. En el SDK de Adapty es el valor que pasas a `Adapty.identify()`; FunnelFox envía el que tiene almacenado. Esto no determina en qué perfil aterriza el usuario; eso lo decide la dirección de email. Lo que aporta es una segunda forma de encontrar el perfil: un evento de transacción que lo incluya se resuelve sin necesidad de email, y una eliminación puede identificarlo por este valor en lugar de por la dirección. Si el usuario rechazó el correo de marketing, envía `"email_consent": false`. Adapty Mail no les enviará correos de marketing ni solicitudes de [doble opt-in](mail-double-opt-in); los correos transaccionales seguirán llegándoles. Omite el campo cuando no tengas una respuesta: un perfil sin este campo recibirá correos de marketing. ```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" } }' ``` :::note `country` y `store_country` usan un código de dos letras según la norma [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) — `US`, no `USA` ni `United States`. Cualquier otro valor se rechaza. ::: `device_info` describe el dispositivo del usuario. El objeto es opcional, pero `platform` debe estar presente siempre que lo envíes. | Campo | Opcional | Filtro de segmento | Notas | | --- | --- | --- | --- | | `platform` | No | Sí | | | `device` | Sí | Sí | El modelo del dispositivo. | | `os` | Sí | Sí | | | `locale` | Sí | Sí | Adapty Mail lo usa para decidir qué traducción de una [campaña localizada](mail-localization#select-languages) enviar. Un usuario sin configuración regional recibirá los correos en inglés. | | `app_version` | Sí | Sí | Se compara en orden de versión, por lo que `1.10.0` tiene mayor rango que `1.9.0`. | | `timezone` | Sí | No | Establece cuándo recibe el usuario el correo. Sin este dato, Adapty Mail lo trata como UTC, por lo que la ventana de envío de 08:00–21:00 sigue la hora UTC en lugar de su propia zona horaria. | 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" }' ``` See the [Save transaction event](api-mail/operations/saveTransactionEvent) reference for every available field. You can send a transaction before the profile exists. Adapty Mail keeps the event unattached, then links it to the profile on the next save that includes the same `external_profile_id`. ## Eliminar un perfil \{#delete-a-profile\} Para atender una solicitud de derecho al olvido, envía los identificadores del perfil a `/api/v1/profile/delete/`. Adapty Mail borra los datos personales del perfil y cancela sus correos programados. ```bash curl --request POST \ --url 'https://api-mail.adapty.io/api/v1/profile/delete/' \ --header 'Authorization: Bearer {your_secret_api_key}' \ --header 'Content-Type: application/json' \ --data '{ "external_profile_id": "user_12345" }' ``` Identifica el perfil mediante `external_profile_id`, `customer_user_id` o `email` — envía al menos uno. Adapty Mail borra los datos almacenados, pero no bloquea la dirección: si una fuente la vuelve a enviar, aparece un nuevo perfil sin historial. Para excluir a alguien de forma permanente, impide que la fuente envíe sus datos. Una solicitud de eliminación que agota el tiempo de espera te deja sin saber si llegó a procesarse, así que la reenvías. Esa segunda llamada es segura cuando identificas el perfil por `external_profile_id` o `customer_user_id` — tiene éxito y no cambia nada. Una segunda llamada que lleva solo `email` devuelve `404`, porque la primera eliminación borró la dirección con la que habría coincidido. La referencia de [Eliminar perfil](api-mail/operations/deleteProfile) lista los códigos de respuesta. ### 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-funnelfox --- --- title: "Conectar FunnelFox a Adapty Mail" description: "Reenvía emails, compras y eventos de suscripción desde tus funnels web de FunnelFox a Adapty Mail." --- FunnelFox es uno de los puntos de entrada de Adapty Mail: úsalo cuando adquieras y cobres a usuarios a través de funnels web de [FunnelFox](https://funnelfox.com). FunnelFox ya dispone de sus direcciones de email, compras y eventos de suscripción, por lo que la conexión no requiere ningún trabajo con el SDK ni solicitudes propias. La conexión es unidireccional. FunnelFox envía datos a Adapty Mail; nunca lee tus campañas de vuelta y nunca envía correos electrónicos por sí mismo. ## Qué envía FunnelFox \{#what-funnelfox-sends\} - **Perfiles y direcciones de email**, siempre que el funnel recoja un email. - **Identificadores**: el ID de perfil de FunnelFox del destinatario, y el ID de usuario personalizado que tu funnel tenga para él, si lo tiene. En la página de perfil aparecen bajo **External Profile IDs**, como fila **FunnelFox** y fila **Custom**. - **Historial de compras**: suscripciones, renovaciones y compras únicas. Los reembolsos llegan como importes negativos. - **Eventos de suscripción**: cancelaciones, reactivaciones, problemas de facturación y expiraciones. Estos son los que sitúan a cada usuario en un [flow](mail-flows). - **Atributos de perfil**: país, dispositivo, sistema operativo, plataforma y configuración regional — los cinco son filtros de [segmento](mail-segments). FunnelFox lee los cuatro últimos del user agent del navegador. También envía la zona horaria, que determina cuándo recibe el email el destinatario, pero no es un filtro. Nunca envía una versión de la app, por lo que ese filtro permanece vacío para los usuarios del funnel. - **Contexto del funnel**, almacenado como atributos personalizados en el perfil bajo estas claves: - `fnlfx_funnel_id`: el funnel que recogió el email. - `fnlfx_session_id`: la sesión del funnel. - `fnlfx_experiment_id`: el experimento del funnel, si el usuario participó en uno. - `email_consent`: si el usuario aceptó recibir emails en el funnel. El valor es el texto `"true"` o `"false"`, por lo que un filtro de segmento sobre él compara texto. Para pasar uno de estos valores a una página de checkout, añade su placeholder al enlace — por ejemplo, `{custom_attribute.fnlfx_session_id}`. Consulta los [placeholders](mail-destinations#placeholders). Un funnel de FunnelFox solo necesita `fpid`. No pases la sesión en un parámetro `fsid`: el funnel recogería una sesión que pertenece a otro funnel. Los datos llegan a Adapty Mail en aproximadamente un minuto tras el evento del funnel. ## Conectar FunnelFox \{#connect-funnelfox\} Primero completa el resto de la configuración de Adapty Mail — consulta [Primeros pasos con Adapty Mail](mail-get-started). Hasta que exista un flow, nada de lo que envíes generará correos electrónicos. 1. En Adapty Mail, ve a **Settings** > **Project** y copia la **Secret key**. 2. En FunnelFox, ve a **Integrations** > **Adapty Mail**. 3. Pega la clave y guarda. Para la configuración del lado de FunnelFox, consulta la [guía de integración de FunnelFox](https://funnelfox.com/docs/integrations/email-providers/adapty-mail). ## Después de conectar \{#after-you-connect\} En la página de un perfil, **External Profile IDs** muestra una fila de **FunnelFox** con el ID que FunnelFox usa para esa persona. Si un perfil es reportado por más de una fuente, aparece una fila por cada una, para que puedas ver todas las fuentes que lo reconocen. Para vincular un email a un funnel de FunnelFox, pasa `{funnelfox.external_profile_id}` en el parámetro `fpid`. Consulta [Vincular a un funnel de FunnelFox](mail-destinations#link-to-a-funnelfox-funnel). ## Úsalo junto a otro punto de entrada \{#use-it-alongside-another-entry-point\} Puedes ejecutar FunnelFox y otro [punto de entrada](mail-entry-points) al mismo tiempo. Adapty Mail identifica a las personas por dirección de email. Dos fuentes que reporten la misma dirección se consolidan en un único perfil; dos direcciones distintas crean perfiles separados, aunque el `customer_user_id` sea idéntico. --- # 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 la página se abre con un perfil ya rellenado**: Adapty Mail lo creó a partir de la URL de la store de tu proyecto de Adapty cuando te registraste. Revísalo y ajústalo. - **Si la página se abre con una pantalla de configuración**: Aún no existe ninguna marca; consulta [Configurar desde cero](#set-up-from-scratch). Los registros independientes siempre empiezan aquí, al igual que un proyecto de Adapty sin URL de store, como un funnel web-to-app. ## 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 corta, eslogan. - **Identidad visual**: Logo, siete colores (primario, fondo, secundario, acento, texto, fondo del CTA, texto del CTA), tipografías de títulos y cuerpo, y una nota de estilo en texto libre. - **Audiencia**: Demografía, 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. La valoración de la App Store es una media ponderada por número de valoraciones en las 20 principales tiendas — Apple no publica ningún dato global, y una única tienda subestima la cifra en varios órdenes de magnitud. - **Redes sociales**: Twitter, Instagram, TikTok, YouTube, Facebook, LinkedIn. - **Legal**: URL de términos, URL de privacidad, email de soporte. - **Reseñas**: Reseñas de usuarios extraídas de la fuente de la tienda — contenido, autor, valoración, fuente. El texto de las reseñas y los nombres de los autores se mantienen exactamente tal como están escritos, en su idioma original, de modo que un perfil de marca en inglés puede contener reseñas en español o en turco. - **Puntos de dolor**: Declaraciones de dolor fundamentadas en reseñas o señales sociales. - **FAQ**: Preguntas y respuestas usadas en el contenido de emails y en secciones de 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 y luego haz clic en **Apply** en la parte inferior del editor. La sección se cierra y sus nuevos valores se incorporan al borrador de la página, indicado por el banner *Unsaved draft* en la parte superior. 3. Haz clic en **Save** en el banner de borrador para confirmar todos los cambios aplicados. Haz clic en **Discard** para descartarlos todos. Solo puede haber una sección abierta a la vez. Si intentas abrir una segunda sección, la primera te pedirá que termines o canceles. :::important Las ediciones se pausan mientras una fuente está procesándose — una fuente que termina podría sobrescribir los cambios que tienes en curso. Cualquier editor abierto se cierra automáticamente cuando comienza el procesamiento, y el botón **Save** permanece desactivado en el banner de borrador hasta que el procesamiento finalice. ::: ### Logo, colores y tipografía \{#logo-colors-and-fonts\} El editor de **Visual identity** es la única sección que no consiste en campos de texto simples: - **Logo** es el primer control del editor, un área de colocación: arrastra una imagen o haz clic para seleccionar una. Se admiten PNG, JPEG y WebP de hasta 8 MB; los formatos PNG y WebP conservan la transparencia. Una vez cargado el logo, el área muestra una vista previa y ofrece la opción **Remove logo**; soltar otra imagen la reemplaza. - **No logo URLs**: el campo acepta únicamente una imagen subida a este proyecto o ninguna, por eso Adapty Mail copia el icono de tu store en su propio almacenamiento al crear la marca; consulta [Cómo llegan los recursos de marca a tus correos](#how-brand-assets-reach-your-emails). - **Colors** es una cuadrícula de siete muestras: **Primary**, **Background**, **Secondary**, **Accent**, **Text**, **CTA background** y **CTA text**. Cada una combina un selector de color con un campo hexadecimal, para que puedas usar el cuentagotas o pegar un valor exacto como `#aabbcc`. - **Heading font**, **Body font** y **Style** son campos de texto libre. Los de fuente aceptan el nombre de la familia tipográfica, y **Style** admite una descripción breve, como *Modern, minimalist*. Every one of these feeds both email generation and the web paywall, so a color you correct here changes the next campaign and the next generated paywall alike. ## 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. ## Configurar desde cero \{#set-up-from-scratch\} Si tu proyecto aún no tiene una marca — cualquier registro independiente y cualquier proyecto de Adapty sin una URL de store — la página **Brand** se abre con una pantalla de configuración. La fuente de la store — App Store o Google Play — es la base; otros tipos de fuentes se pueden añadir después. 1. Elige la store (**App Store** o **Google Play**) y pega la URL del listado. 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. ## Cómo llegan los activos de marca a tus emails \{#how-brand-assets-reach-your-emails\} Cuando Adapty Mail construye la marca, copia el icono de tu app en su propio almacenamiento en lugar de enlazarlo directamente desde el CDN del store, de modo que el cuerpo del email no cargue imágenes desde un host de terceros. En el momento del envío, reescribe esa URL una vez más, apuntando al host `go.` del [dominio de envío](mail-sending-domain#2-add-the-dns-records) que transporta el email — así, los destinatarios cargan tu logo e imágenes principales desde tu propio dominio. El texto publicitario se integra en una campaña al generarse, pero el logo, el nombre de marca y la dirección postal se resuelven en cada envío. Si cambias cualquiera de los tres, Adapty Mail aplica el nuevo valor a todos los correos que aún estén programados, sin necesidad de regenerar nada. ## 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 la marca se borra el perfil y todas las fuentes. Esta acción no tiene vuelta atrás; para recuperarlo, pega una URL de App Store o Google Play en la pantalla de inicio y vuelve a configurarlo desde cero. :::warning Las campañas existentes conservan el texto con el que fueron generadas, pero el logotipo y el nombre de la marca se resuelven en el momento del envío; si la marca ha sido eliminada, los correos ya programados se enviarán sin ninguno de los dos. Las nuevas campañas y generaciones de paywalls quedan bloqueadas hasta que vuelvas a registrar una marca. ::: ## Limitaciones \{#limitations\} - **Una marca por proyecto**: Cada proyecto de Adapty Mail tiene una sola marca. Para trabajar con una app diferente, crea un nuevo proyecto. - **Una store listing inicia cada marca**: La pantalla de configuración solo acepta una URL de App Store o Google Play. Las landing pages, páginas de términos y perfiles sociales refinan una marca que ya existe; ninguno de ellos puede crearla. - **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 social. - **Sin eliminación por fuente**: No es posible eliminar fuentes individuales desde la interfaz. Usa **Delete brand** si necesitas reemplazar una fuente. - **Edición pausada durante el procesamiento**: Las ediciones de secciones, los guardados del chat de refinamiento y la eliminación de marcas quedan bloqueados mientras una fuente está en estado `pending` o `processing`. - **Las fuentes fallidas permanecen visibles**: Una fuente en estado `failed` sigue apareciendo 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 tus dominios de envío y comprende el proceso de warm-up para que Adapty Mail pueda enviar 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 esté siempre bajo tu control. Con un dominio verificado es suficiente para empezar, y un proyecto puede tener hasta cinco. Para verificar un dominio con el mínimo de pasos, sigue la guía [Primeros pasos con Adapty Mail](mail-get-started#4-set-up-your-sending-domain). Este artículo es la referencia completa: configuración, verificación, calentamiento y envío desde más de un dominio. ## Requisitos y limitaciones \{#requirements-and-limitations\} - **Dominio raíz**: Introduce tu dominio raíz. Debe tener un sufijo de dominio de nivel superior (`yourapp.com`). Adapty Mail no acepta subdominios (`app.yourapp.com`) ni dominios con sufijos de segundo nivel (`yourapp.co.uk`, `yourapp.com.br`). - **Solo ASCII**: No se admiten Punycode ni dominios internacionalizados. - **Registros NS activos**: El dominio debe resolver. Adapty Mail realiza una consulta DNS durante la configuración y rechaza los dominios sin registros NS válidos. - **Un proyecto por dominio**: Un dominio no puede compartirse entre proyectos. Si ya lo tiene cualquier proyecto —tuyo o de otra persona—, la configuración falla. - **Un nombre de dominio que coincida con tu marca**: El nombre debe ser reconocible como el de tu app. `yourapp.com` es válido para una app llamada Yourapp; un `anotherapp.com` sin relación no lo es. Adapty usa el nombre de app, la descripción y el eslogan de tu [marca](mail-brand) como referencia. Un proyecto sin marca omite esta comprobación. - **Un dominio con buena reputación**: Adapty Mail rechaza los dominios que [Blacklist Master](https://www.blacklistmaster.com/) indique como incluidos en una lista negra de spam. - **Hasta cinco dominios por proyecto**: Cada uno se verifica y realiza el calentamiento de forma independiente. - **La eliminación no es reversible**: El envío se detiene de inmediato y se eliminan el progreso de verificación del dominio, los registros DNS y el buzón de calentamiento. Puedes volver a añadir el dominio, pero se verificará desde cero con nuevos registros. Los correos de campaña enviados en los últimos 30 días desde ese dominio siguen contando para la [puntuación de entregabilidad](mail-domain-health). Si mantienes el mismo subdominio de envío, el límite diario de envíos se conserva. Tus propios registros DNS permanecen donde los pusiste; elimínalos tú mismo. ## 1. Añade el dominio \{#1-add-the-domain\} En Adapty Mail, abre **Settings** > **DNS** y navega hasta la sección **Email domains**. 1. En el campo **Domain**, escribe tu dominio raíz, por ejemplo `yourapp.com`. 2. En el campo **Sending subdomain**, especifica el subdominio que se usará para enviar correos. Deja el campo vacío para usar `mail`. La indicación bajo el campo muestra una vista previa del nombre de host completo: *Emails will be sent from mail.yourapp.com*. 3. En el campo **Link subdomain**, especifica *un subdominio diferente* para los enlaces e imágenes dentro de los correos. Deja el campo vacío para usar `go`. La indicación también muestra este host: *Links and images in emails will be served from go.yourapp.com*. 4. Haz clic en **Add domain**. Adapty Mail valida los datos, añade el dominio a tu proyecto y muestra una tarjeta con todos los registros DNS que necesita el dominio. 5. Opcionalmente, [añade más dominios](#multiple-domains) para aumentar el número de correos electrónicos que puedes enviar al día. ### Usar subdominios personalizados \{#use-custom-subdomains\} El prefijo de envío aparece en la dirección De que ve cada destinatario, y el prefijo de enlace aparece en cada enlace que hacen clic. La mayoría de los proyectos mantienen los valores predeterminados; si escribes los tuyos, ambos campos siguen las mismas reglas: - Usa letras minúsculas, dígitos y guiones, sin guión al inicio ni al final, con un máximo de 63 caracteres. - `www`, `smtp`, `admin`, `api`, `mx` y `dkim` están reservados. - Las etiquetas ofensivas y las que imitan a una marca conocida —`paypal`, por ejemplo— son rechazadas. Las palabras de marketing habituales como `news`, `offers` o `hello` sí son válidas. - Los dos prefijos no pueden coincidir entre sí. - Ambos se fijan en el momento en que se añade el dominio. Para enviar desde un host diferente, elimina el dominio y vuelve a añadirlo, o añade un segundo dominio. ## 2. Añade los registros DNS \{#2-add-the-dns-records\} Añade todos los registros de la tarjeta a tu DNS, exactamente como se muestran. Para transferir el conjunto completo de una vez, haz clic en **Export** y elige **CSV**, **GoDaddy zone file**, **Cloudflare zone file** o **Route 53 zone file**. :::warning **En Cloudflare, cada CNAME debe ser solo DNS** — un registro con proxy resuelve a la dirección de Cloudflare y nunca se verifica. La exportación **Cloudflare zone file** se encarga de configurarlo automáticamente. ::: :::tip Un asistente de IA conectado al [servidor MCP de Adapty Mail](mail-mcp) puede leer estos registros, guiarte para publicarlos en tu proveedor de DNS y volver a ejecutar la verificación por ti. ::: Los tres primeros apartados son obligatorios: la configuración no estará completa hasta que los registros se resuelvan. El registro DMARC en tu dominio raíz es opcional. | Sección | Host | Qué hace | | --- | --- | --- | | **Sending infrastructure** | Subdominio de envío | Firma tus campañas y redirige los rebotes a Adapty Mail | | **Warmup mailbox** | Subdominio de envío | Gestiona el buzón que construye tu reputación de remitente durante el [calentamiento](#warm-up-and-daily-limits) | | **Media & redirects** | Subdominio de enlaces | Sirve las imágenes y los enlaces rastreados de tus correos desde tu propio dominio | | **DMARC policy** (opcional) | Dominio raíz | Indica a los proveedores de correo cómo tratar los mensajes que no superan la autenticación | Si tu dominio raíz ya tiene registros SPF y DMARC para tu correo corporativo, no los toques — los registros necesarios van todos en subdominios. <Details> <summary>Referencia de registros DNS</summary> **DKIM — 3 registros CNAME.** Firmas criptográficas que demuestran que el correo no fue alterado en tránsito. | Campo | Formato | | ----- | ----------------------------------- | | Type | CNAME | | Name | `{token}._domainkey.mail.yourapp.com` | | Value | `{token}.dkim.amazonses.com` | **MAIL FROM — 1 registro MX.** Gestiona los rebotes. | Campo | Formato | | -------- | -------------------------------------------------------- | | Tipo | MX | | Nombre | `hello.mail.yourapp.com` | | Prioridad | `10` | | Valor | `feedback-smtp.{region}.amazonses.com` | **MAIL FROM — 1 registro TXT.** Autoriza a Adapty a enviar en tu nombre. | Campo | Formato | | ----- | -------------------------------------- | | Type | TXT | | Name | `hello.mail.yourapp.com` | | Value | `"v=spf1 include:amazonses.com ~all"` | **DMARC — 1 registro TXT en tu dominio raíz.** Opcional pero recomendado. | Campo | Formato | | ----- | --------------------- | | Type | TXT | | Name | `_dmarc.yourapp.com` | | Value | `v=DMARC1; p=reject` | El buzón de calentamiento y los registros de `go.yourapp.com` siguen las mismas estructuras, con nombres y valores emitidos por los proveedores de correo y hosting que utiliza Adapty Mail. Los tokens, regiones, hostnames y valores de propiedad se generan en el momento de la configuración — cópialos desde la tarjeta de dominio o una exportación, nunca desde esta referencia. </Details> ## 3. Verifica los registros \{#verify-the-records\} La propagación DNS suele tardar unos minutos, aunque en casos excepcionales puede llegar a 48 horas. Para validar los registros, Adapty Mail consulta el DNS automáticamente a intervalos regulares hasta que los encuentra, o hasta que transcurren 7 días. Haz clic en **Check** en la tarjeta para ejecutar una comprobación manual (con un intervalo mínimo de 10 segundos entre intentos), o reinicia las comprobaciones automáticas una vez que se hayan detenido. Un registro se verifica cuando existe en DNS, su valor coincide con el que Adapty Mail te proporcionó y el proveedor lo confirma. Un registro sin verificar lleva un indicador que señala cuál de esos requisitos falta: | Chip | Qué significa | | --- | --- | | **Missing** | El registro no está en DNS. Añádelo exactamente como muestra la tarjeta. | | **Wrong value** | El registro existe, pero su valor es diferente. Actualízalo para que coincida. | | **Differs from suggested** | Tu dominio raíz ya tiene un registro DMARC con otro valor. La sugerencia de Adapty Mail no es obligatoria: sustitúyela solo si lo deseas. | | **Provider check failed** | El proveedor rechazó el registro. Comprueba el valor y haz clic en **Check**. | | **Awaiting provider** | El registro es correcto en DNS, pero el proveedor aún no lo ha confirmado. Haz clic en **Check** para actualizar. | Adapty Mail no realiza comprobaciones programadas para confirmar la validez de los registros ya verificados. Activa una comprobación manualmente haciendo clic en **Check**, o abriendo **Settings** > **DNS**. Mantén los registros en su lugar mientras planees enviar correos. Si los registros DKIM desaparecen, Amazon SES deja de firmar tu correo después de un período de gracia, y el dominio debe verificarse de nuevo desde cero. ## 4. Configura el nombre del remitente y la dirección de respuesta \{#set-the-sender-name-and-reply-to-address\} Una vez verificado un dominio, su tarjeta incluye el campo **Email sender name**. Adapty Mail lo rellena con el nombre de tu proyecto y envía desde `hello@` en tu subdominio de envío, por lo que la sugerencia del campo muestra la línea From completa — *Your Company <hello@mail.yourapp.com>*. El nombre es por dominio: un proyecto que envía desde dos dominios tiene dos nombres que configurar. El nombre no puede contener `+`, `{` ni `}`. Para recibir las respuestas que envíen tus destinatarios, en la misma tarjeta de dominio, introduce una dirección en el campo **Reply-to address** y haz clic en **Save**. Por ejemplo: `support@yourapp.com`. La dirección `hello@` es el [buzón de calentamiento](#warm-up-and-daily-limits) del dominio y nadie la lee, por lo que las respuestas a un correo enviado sin dirección de respuesta quedan sin contestar. La dirección de respuesta puede estar en cualquier dominio y no necesita registros DNS. Solo se aplica a los correos enviados después de guardarla. Al igual que el nombre del remitente, se configura por dominio. ## Preparación y límites diarios \{#warm-up-and-daily-limits\} Los dominios nuevos no tienen reputación ante proveedores de correo como Gmail o Yahoo, por lo que enviar grandes volúmenes desde un dominio recién creado puede acabar en la carpeta de spam. Adapty Mail prepara cada dominio de forma automática mediante dos mecanismos que funcionan en paralelo. Ninguno requiere configuración. - **Un buzón de calentamiento**: En cuanto el DNS se verifica, Adapty Mail crea un buzón en tu subdominio de envío. El buzón intercambia correo conversacional ordinario con buzones de prueba de los principales proveedores. Este intercambio bidireccional muestra a los proveedores que el dominio envía email normal, lo que construye su reputación. La tarjeta del dominio muestra en qué fase se encuentra el buzón. El calentamiento no comienza hasta que se verifiquen los registros del [**Warmup mailbox**](#2-add-the-dns-records). - **Un límite de envío diario**: Tus campañas se miden de forma independiente al buzón de calentamiento, a lo largo de 20 niveles. El límite mantiene tu volumen por debajo del umbral que haría que un dominio nuevo fuera marcado como spam, y va aumentando a medida que envías. ### Cómo funcionan los niveles \{#how-tiers-work\} Tu dominio comienza en el **Nivel 1** (100 envíos/día). El límite aumenta un nivel a la vez cuando se supera el límite diario existente (tanto los envíos como los intentos retenidos cuentan, cada correo una vez). El límite superior entra en vigor al día siguiente. La cuota se distribuye a lo largo del día: en cualquier hora, un dominio envía como máximo una dieciseisava parte de su límite diario, y Adapty Mail envía en una hora posterior los correos retenidos durante la hora anterior. Los correos transaccionales pueden usar hasta el doble del límite, y el primer correo de un flow y la solicitud de confirmación de doble opt-in hasta un 20% más, por lo que tienen prioridad sobre otros correos cuando el límite escasea. El avance está determinado únicamente por el volumen, y los niveles solo suben: no hay retroceso automático. Una vez superado el nivel máximo, el dominio envía sin límite diario. | Nivel | Límite diario | | ----- | ------------- | | 1 | 100 | | 2 | 200 | | 3 | 300 | | 4 | 500 | | 5 | 750 | | 6 | 1.000 | | 7 | 1.500 | | 8 | 2.500 | | 9 | 3.500 | | 10 | 5.000 | | 11 | 7.000 | | 12 | 9.000 | | 13 | 12.000 | | 14 | 15.000 | | 15 | 19.000 | | 16 | 24.000 | | 17 | 30.000 | | 18 | 37.000 | | 19 | 46.000 | | 20 | 57.000 | ### Impacto en el lanzamiento según el tamaño de la audiencia \{#impact-on-launch-by-audience-size\} | Tamaño de la audiencia | Efecto en el lanzamiento | | ---------------------- | --------------------------------------------- | | Menos de 100 usuarios | Toda la audiencia recibe el mensaje el día uno | | 100–2.000 usuarios | El envío se distribuye a lo largo de varios días | | Más de 2.000 usuarios | El envío se distribuye a lo largo de 1–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. ::: ## Capacidad de entrega \{#deliverability\} :::link Artículo principal: [Supervisar el estado del dominio](mail-domain-health) ::: Cada dominio en **Settings** > **DNS** muestra una **Deliverability score** sobre 100. Haz clic en **Details** para ver el panorama completo: la configuración te indica que el dominio está configurado, esto te indica si el correo está llegando. La puntuación combina tres señales: la tasa de rebote duro, la tasa de quejas y la tasa de bandeja de entrada del buzón de calentamiento. Una tasa de rebote duro del 2 % es una advertencia, y el 5 % es crítico. Una queja pesa mucho más que un rebote: el 0,1 % de los envíos es una advertencia, el 0,5 % es crítico. Cada queja [suprime al destinatario](mail-suppression#delivery-failed-or-the-recipient-marked-it-as-spam) de inmediato. ## Múltiples dominios \{#multiple-domains\} Hasta que tus dominios completen su período de calentamiento, Adapty [limita](#warm-up-and-daily-limits) el número de correos electrónicos diarios que cada uno puede enviar. Por ello, usar múltiples dominios aumenta tu capacidad de envío de correos. Adapty usa la siguiente lógica para decidir cuál de tus dominios envía el correo: - **Un dominio por destinatario por campaña.** Para mantener la coherencia de la campaña, Adapty Mail usa la misma dirección de correo para enviar sus emails al mismo destinatario. Si eliminas ese dominio o dejas de enviar desde él, el siguiente email se enviará desde el dominio más reciente que tenga capacidad disponible. - **Los dominios más nuevos tienen prioridad.** Un dominio añadido más tarde tiene preferencia sobre uno más antiguo. Cuando todos los dominios han superado su límite, Adapty Mail retrasa el email — [Throttle](mail-suppression#throttle-temporary-delays-and-cancellations) explica qué ocurre con el destinatario a partir de ese momento. ## Pausar y reanudar el envío \{#stop-and-resume-sending\} Para pausar un dominio sin tener que verificarlo de nuevo, haz clic en **Stop sending** en su tarjeta. La tarjeta mostrará **Sending off** y Adapty Mail omitirá el dominio al elegir desde dónde enviar. Haz clic en **Resume sending** para volver a activarlo. ## Solución de problemas \{#troubleshooting\} | Problema | Solución | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | | "Enter a valid domain (e.g. example.com)" | Comprueba la entrada: un dominio raíz con un sufijo de nivel superior (`yourapp.com`), solo ASCII, sin guiones al principio ni al final. Un sufijo de segundo nivel como `yourapp.co.uk` no se acepta. | | "Domain does not have valid DNS records" | El dominio raíz debe resolver. 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 el soporte si crees que el registro es un error. | | "This subdomain cannot be used" | La etiqueta está reservada o fue rechazada por el filtro. Elige otra: consulta [Usar subdominios personalizados](#use-custom-subdomains). | | "This link subdomain cannot be used" | El subdominio de enlace sigue las mismas reglas que el subdominio de envío y no puede ser idéntico a él; consulta [Usar subdominios personalizados](#use-custom-subdomains). | | "Please wait before checking again" | Espera 10 segundos entre comprobaciones manuales. El sondeo automático continúa en segundo plano. | | Los registros no se verifican | Comprueba que coinciden exactamente: sin puntos al final, destinos CNAME correctos, un valor por registro. El DNS puede tardar hasta 48 horas en propagarse. | | Los CNAME no se verifican en Cloudflare | Los registros están en modo **Proxied**. Cambia cada uno a **DNS only** y haz clic en **Check**. | | El calentamiento no ha empezado | Los registros MX, DKIM y SPF del buzón de calentamiento aún no están verificados. Añádelos o corrígelos y haz clic en **Check**. | | Los registros del subdominio de enlace no se verifican | La tarjeta muestra el motivo debajo del host. Confirma sus registros CNAME y TXT y haz clic en **Check**: el dominio no estará completamente configurado hasta que este host esté activo. | | "Cannot delete: some identities are verified" | El dominio se configuró antes del calentamiento automático y no se puede eliminar desde el dashboard. Contacta con el soporte. | | Los correos van a spam | Confirma que tu registro DMARC está publicado. Los dominios nuevos necesitan tiempo de calentamiento; consulta [calentamiento y límites diarios](#warm-up-and-daily-limits). Para ver a qué carpeta llegan los correos del buzón de calentamiento en los principales proveedores, revisa la [tarjeta **Inbox placement**](mail-domain-health#inbox-placement) en la página de entregabilidad. | | Las respuestas de los destinatarios no te llegan | Sin una dirección de respuesta, las respuestas van al buzón `hello@`, que nadie lee. [Configura una dirección de respuesta](#4-set-the-sender-name-and-reply-to-address) en la tarjeta del dominio. Los correos enviados antes de configurarla conservan la dirección anterior. | | Tasa de rebote alta | Verifica que tu lista de audiencia contiene direcciones válidas con consentimiento. [Razones por las que los correos se detienen](mail-suppression) explica qué le ocurre a una dirección que rebota. Para encontrar el motivo con más rebotes, revisa la [tarjeta **Bounces**](mail-domain-health#bounces) en la página de entregabilidad. | | Tasa de quejas alta | Mantén las denuncias de spam por debajo del 0,1 % de los envíos. Una queja tiene mucho más peso que un rebote y el destinatario se suprime de inmediato. Para ver la tasa de quejas de los últimos 30 días, abre la [página de entregabilidad](mail-domain-health#deliverability-score). | --- # File: mail-domain-health --- --- title: "Supervisar el estado del dominio en Adapty Mail" description: "Rastrea la puntuación de entregabilidad, rebotes, ubicación en la bandeja de entrada y niveles de envío de un dominio de envío en Adapty Mail." --- Revisa las métricas de tu dominio para detectar señales tempranas de que los proveedores de correo están clasificando tus campañas como spam. Los rebotes, las quejas y la ubicación en la bandeja de entrada pueden anticipar problemas futuros: monitorízalos para actuar antes de que escalen. Encuentra tu dominio en la sección **Email domains** de la página **DNS**. Haz clic en **Details** para ver sus métricas de estado: - **Puntuación de entregabilidad**: Una valoración del 0 al 100. - **Rebotes**: Rebotes del dominio durante los últimos 30 días, agrupados por motivo. - **Ubicación en bandeja de entrada**: En qué carpeta llegaron los correos del buzón de calentamiento en cada proveedor de correo principal. - **Niveles de envío**: Progreso a través de los [niveles de envío](mail-sending-domain#how-tiers-work) y el límite actual. ## Puntuación de entregabilidad \{#deliverability-score\} La tarjeta **Deliverability score** muestra uno de los siguientes estados: | Estado | Puntuación | | --- | --- | | **Healthy** | 90–100 | | **Needs attention** | 50–89 | | **At risk** | Por debajo de 50 | | **No data yet** | Ninguna: el dominio no envió correos de campaña en los últimos 30 días | La puntuación no afecta a los niveles de envío. Para mejorar una puntuación baja, sigue los [pasos de resolución de problemas](#troubleshooting). ### Cómo se calcula la puntuación \{#how-the-score-is-calculated\} La puntuación combina las siguientes señales, cada una valorada de 0 a 100: | Señal | Peso | Qué mide | Puntuación 100 | Puntuación 80 | Puntuación 0 | | --- | --- | --- | --- | --- | --- | | Tasa de quejas | 45% | Quejas de spam en correos de campaña, últimos 30 días | 0% | 0,1% | 0,5% o más | | Tasa de rebote duro | 35% | Rebotes duros en correos de campaña, últimos 30 días | 0% | 2% | 5% o más | | Tasa de bandeja de entrada | 20% | Correos de calentamiento que llegaron a la bandeja de entrada, últimos 7 días | 100% | 80% | 0% | La puntuación es la media ponderada de las señales, con dos ajustes: - **La señal más débil limita la puntuación**: Una vez que el dominio ha enviado suficientes correos, la puntuación no puede subir más de 10 puntos por encima de su señal más débil. Una sola señal débil basta para poner el dominio **En riesgo**. - **Las señales de pocos correos tienen menos influencia**: En un dominio nuevo, un solo rebote duro en 50 correos ya supone una tasa del 2%. Hasta que el dominio haya enviado unos pocos cientos de correos de campaña en los últimos 30 días, la puntuación se marca como **Datos iniciales**. Sin datos de calentamiento, la puntuación solo combina las tasas de quejas y rebotes duros. Los correos de prueba no cuentan para ninguna señal. ## Rebotes \{#bounces\} La tarjeta **Bounces** agrupa los rebotes de los últimos 30 días por motivo. En la columna **Top provider** se identifica el proveedor con más rebotes para cada motivo. Una etiqueta en el motivo indica su tipo de rebote: - **HARD**: La entrega falló por un motivo permanente, como una dirección que no existe. - **SOFT**: La entrega falló por un motivo temporal, como un buzón lleno o contenido rechazado. La tasa en el título de la tarjeta **Bounces** incluye tanto los rebotes hard como los soft. La puntuación de entregabilidad excluye los rebotes soft, igual que Amazon SES. Cada rebote, duro o suave, [suprime al destinatario](mail-suppression#delivery-failed-or-the-recipient-marked-it-as-spam): Adapty Mail deja de enviarles mensajes. Para reducir los rebotes, busca qué tienen en común la mayoría de ellos: | Lo que ves | Qué hacer | | --- | --- | | La mayoría de los rebotes tienen la etiqueta **HARD** | Revisa de dónde provienen las direcciones que rebotaron, como un formulario de registro. | | La mayoría de los rebotes son **Content rejected** | Revisa los enlaces y el texto de tus correos. | | Un proveedor es el **Top provider** de la mayoría de los rebotes | Contacta con soporte. | ## Ubicación en la bandeja de entrada \{#inbox-placement\} La tarjeta **Inbox placement** muestra en qué carpeta llegaron los emails de calentamiento para cada proveedor principal. El [buzón de calentamiento](mail-sending-domain#warm-up-and-daily-limits) del dominio intercambia estos emails con buzones de prueba en dichos proveedores. La tarjeta excluye los emails de campaña, porque ningún remitente puede ver en qué carpeta llegan. Además de **Inbox** y **Spam**, una fila de proveedor puede mostrar las siguientes etiquetas: - **Unknown**: Emails de calentamiento que rebotaron o no se entregaron. - **No signal**: El buzón de calentamiento no envió ningún email a ese proveedor. La tarjeta muestra los datos de un único día: como máximo, el de ayer, ya que Adapty Mail recopila los datos a diario. Para ver el historial de un proveedor, haz clic en su fila. Hasta que el buzón de calentamiento envíe sus primeros correos, la tarjeta muestra **No warm-up data collected yet**. Si el mensaje persiste, comprueba que los registros de [**Warmup mailbox**](mail-sending-domain#2-add-the-dns-records) estén verificados. Un dominio configurado antes del calentamiento automático no tiene buzón de calentamiento, por lo que la tarjeta seguirá mostrando este mensaje. ## Niveles de envío (límites diarios de correo) \{#send-tiers-daily-email-caps\} La tarjeta **Send tiers** muestra el nivel actual del dominio y el límite para hoy. Para un dominio nuevo, el límite comienza en 100 correos al día y sube a través de 20 niveles. [Cómo funcionan los niveles](mail-sending-domain#how-tiers-work) lista cada nivel y su límite. El dominio solo sube de nivel al día siguiente de alcanzar su límite diario. Los correos que Adapty Mail retrasa por el límite igualmente cuentan para él. El gráfico de la tarjeta compara los correos enviados con el límite de cada uno de los últimos 14 días. La tarjeta muestra uno de los siguientes estados: - **Not started**: El dominio aún no ha enviado ningún email de campaña. - **On plan**: El dominio ha subido un nivel cada día. - **Behind plan**: En al menos un día, el dominio no alcanzó su límite. Con una audiencia pequeña, este estado es normal. - **Warmed up**: El dominio superó el nivel máximo y no tiene límite diario. ## Limitaciones \{#limitations\} - **La ubicación en la bandeja de entrada es una estimación basada en los correos de calentamiento**: ningún remitente puede determinar con certeza en qué carpeta acaba su correo. - **Las quejas de Gmail están infrarreportadas**: Gmail [no notifica las quejas de spam individuales](https://aws.amazon.com/blogs/messaging-and-targeting/understanding-google-postmaster-tools-spam-complaints-for-amazon-ses-email-senders/) a los remitentes. Por eso, la tasa de quejas subestima cuántas personas marcan tus correos como spam. Para ver tu tasa de spam en Gmail, utiliza [Google Postmaster Tools](https://postmaster.google.com/). - **La puntuación abarca todos los subdominios de envío del dominio**: la puntuación y la tarjeta **Bounces** combinan los correos de campaña de todos ellos. La tarjeta **Send tiers** solo cubre el actual. - **La puntuación no se reinicia cuando vuelves a añadir un dominio**: si eliminas un dominio y lo añades de nuevo, los correos de campaña de los últimos 30 días siguen contando para la puntuación y la tarjeta **Bounces**. ## Solución de problemas \{#troubleshooting\} - **La tarjeta de puntuación de entregabilidad muestra Needs attention o At risk**: Localiza la señal con la puntuación más baja. - **Tasa de rebotes permanentes**: Revisa el origen de las direcciones con rebote, como un formulario de registro. - **Tasa de quejas**: Envía únicamente a destinatarios que hayan dado su consentimiento. Para pedir a los nuevos destinatarios que confirmen su dirección antes de recibir cualquier email de marketing, activa el [doble opt-in](mail-double-opt-in). - **Tasa de bandeja de entrada**: Sigue la [solución de problemas del dominio de envío](mail-sending-domain#troubleshooting) para los correos que van a spam. Si la puntuación no mejora, contacta con el soporte. - **La tarjeta de puntuación de Entregabilidad muestra Sin datos aún** (**Sin datos** en **Settings** > **DNS**): El dominio no envió ningún email de campaña en los últimos 30 días. Los emails de prueba no cuentan para la puntuación. - **La tarjeta de puntuación de Entregabilidad muestra Datos iniciales**: El dominio ha enviado menos de unos pocos cientos de emails de campaña en los últimos 30 días. - **El dominio no avanza al siguiente nivel**: El dominio permanece en su nivel hasta que alcanza su límite diario, independientemente de los días que pasen. - **Las campañas se envían lentamente**: Cuando todos tus dominios alcanzan sus límites, Adapty Mail [retrasa los emails restantes](mail-suppression#throttle-temporary-delays-and-cancellations): los emails de marketing una semana, los transaccionales un día. El primer día de un nivel, Adapty Mail divide el límite diario en límites horarios iguales. Si un email retrasado tampoco puede enviarse en su nueva fecha, Adapty Mail cancela la campaña para ese destinatario. Para aumentar la capacidad diaria antes de un lanzamiento importante desde un dominio nuevo, [añade más dominios](mail-sending-domain#multiple-domains). --- # File: mail-checkout --- --- title: "Configurar el checkout para Adapty Mail" description: "Crea un paywall web y conecta un proveedor de pagos para ofrecer a tus campañas de email un checkout web personalizado." --- Cada email enlaza a una página de checkout en el navegador, donde se realiza la compra. Para crear una, en Adapty Mail ve a **Email Destinations**, selecciona **Web Paywall** y haz clic en **Create**. Hay dos opciones: | | Generar con IA | Alojar por tu cuenta | | --- | --- | --- | | Quién construye la página | Adapty Mail, con el constructor de paywalls web integrado | Tú | | Proveedor de pago | Solo Stripe | Cualquiera | | Proyecto | Debe estar vinculado a una app de Adapty | Cualquiera | | Tu trabajo | Elegir planes, revisar y publicar | Construir la página, leer el destinatario desde la URL e informar las compras | :::note El producto principal de Adapty tiene sus propios paywalls web. Son una funcionalidad distinta: nada de lo que aquí se explica aplica a ellos. ::: ## Generar con IA \{#generate-with-ai\} :::warning La generación con IA necesita un proyecto **vinculado a una app de Adapty** — uno que Adapty creó automáticamente la primera vez que abriste Mail desde el Adapty Dashboard. Si creaste el proyecto tú mismo en Adapty Mail, en una cuenta independiente o en tu cuenta de Adapty, aloja tu propio paywall. ::: Bajo el campo **URL**, encuentra el enlace **generate one with AI**. Haz clic en él. Despliega el panel **Generation setup** y completa sus seis pasos en orden. Cada botón abre la página donde ocurre ese paso, así que haz clic en **Refresh** cuando vuelvas. <ZoomImage id="mail-checkout-prerequisites.webp" width="500px" alt="Lista de verificación de Generation setup en la página de creación de Web Paywall" /> | Elemento de la lista | Qué hacer | | --- | --- | | **1. Brand is ready** | Ya marcado si configuraste tu [marca](mail-brand). Si no, haz clic en **Set up brand**. | | **2. Log in to the paywall builder dashboard** | Haz clic en **Log in** para autenticarte en el builder. | | **3. Stripe account connected** | Haz clic en **Connect Stripe** y conecta tu cuenta. | | **4. At least one Stripe product with a price** | Haz clic en **Open Stripe dashboard** y añade un producto con precio. | | **5. Generate the paywall** | 5.1. Haz clic en **Generate**.<br />5.2. Elige hasta tres planes. Haz clic en **Add plan** para cada uno después del primero.<br />5.3. Asigna a cada plan un precio de producción, un precio de sandbox, o ambos, todos en la misma moneda. Rellena la misma columna para todos los planes: una columna parcialmente rellenada bloquea la generación. Sin precios de sandbox, los [emails de prueba](mail-testing) llevan al paywall de producción, donde las compras de prueba no funcionan.<br />5.4. Haz clic en **Generate paywall**. Tarda unos minutos, y el campo **URL** se rellena solo cuando termina. | | **6. Review and publish the paywall in the builder** | Haz clic en **Open editor**, luego revisa y publica. Desactivado hasta que hayas generado el paywall. | <ZoomImage id="mail-checkout-generation-dialog.webp" width="500px" alt="Diálogo Generate a paywall con columnas de precios de producción y sandbox" /> :::important El paywall debe estar publicado antes de que pueda gestionar el tráfico de checkout. Los paywalls sin publicar devuelven un error cuando los usuarios hacen clic en los enlaces de checkout del email. ::: Una vez publicado el paywall, haz clic en **Save**. Para más detalles sobre el proveedor de pagos dentro del builder (cuentas de Stripe, modo de prueba o producción, configuración de productos), consulta [Configuración de web paywall](web-paywall-configuration). ### Cómo es un paywall generado \{#what-a-generated-paywall-looks-like\} La generación construye cuatro pantallas, y quien hace clic desde un correo electrónico las ve en orden: 1. **Email** — un encabezado, un subencabezado y un campo de correo electrónico. Adapty Mail lo rellena previamente desde el enlace, así que el tráfico de campañas puede pasar directamente; quien llegue por otro medio lo escribe. 2. **Paywall** — la página de ventas, a continuación. 3. **Pago correcto** o **Pago fallido** — una u otra, nunca las dos. La IA escribe cada parte de la pantalla del paywall a partir de tu marca: | Sección | Qué contiene | | --- | --- | | Titular y subtitular | El argumento de apertura | | Insignia de oferta | Texto de urgencia, sin precios inventados | | Imagen hero | Generada a partir de tu marca | | Planes | Un título, un temporizador de oferta y los planes que elegiste | | Beneficios | 3–6 tarjetas | | Características | 3–8, cada una con un título y subtítulo | | Prueba social | Una línea de texto de respaldo y 3–5 reseñas | | FAQ | 3–6 preguntas y respuestas | | Garantía | Una línea de devolución de dinero o satisfacción | Un CTA y una cuenta atrás permanecen fijados en la parte superior en todo momento. **Payment success** muestra una imagen generada y los siguientes pasos; **Payment failed** invita a intentarlo de nuevo y mantiene el estado del proceso de pago. ## Usa tu propio paywall alojado \{#use-your-own-hosted-paywall\} Con este enfoque, Adapty Mail redirige a los destinatarios a una página que tú mismo alojas. La URL incluye [marcadores de posición](#personalize-your-url-with-placeholders-and-hooks) que transmiten la información del usuario. Todo lo que ocurre después del clic —la oferta, el pago, la personalización— corre de tu cuenta, con cualquier proveedor de pagos. 1. Dale al destino un **Name** — lo elegirás por ese nombre al adjuntarlo a una campaña. 2. Pega la URL de tu paywall alojado en **URL**. Debe usar HTTPS y puede incluir [marcadores de posición](#personalize-your-url-with-placeholders-and-hooks) para los valores que varían según el destinatario. 3. Haz clic en **Save**. El destino estará disponible para las campañas. ### Personaliza tu URL con placeholders y hooks \{#personalize-your-url-with-placeholders-and-hooks\} Usa placeholders y hooks para personalizar la URL de tu paywall con valores únicos para cada destinatario. [Placeholders](mail-destinations#placeholders) recoge el conjunto completo y las reglas. ### Lo que tu página debe hacer Adapty Mail no realiza ninguna acción después de la redirección. Las siguientes acciones son tu responsabilidad: - **Identifica al visitante antes de que empiece el pago**: Lee el ID de perfil o el email de la cadena de consulta y adjúntalo a la compra, para que el pago quede asociado al cliente al que se le envió el email. - **Notifica la compra**: Adapty Mail nunca ve un pago realizado en tu propio proveedor, y una compra que no conoce no cuenta ni como conversión ni como ingresos atribuidos en [Analytics](mail-analytics) — peor aún, el resto de la secuencia sigue enviándose a alguien que ya ha comprado. Envíala por el camino que ya lleva tus datos: la [integración con Adapty](mail-collect-emails), o un [evento de transacción](mail-send-data-via-api#send-transaction-events) en la API de Adapty Mail. ### Prueba el paywall antes de lanzarlo \{#test-the-paywall-before-launching\} **Send test email** comprueba que el correo se renderiza correctamente y que tu página acepta pagos, pero **Sandbox** no está disponible para un paywall añadido por URL — el modal indica *"This paywall has no sandbox products, so only production is available."* Así que el test va a tu página real con productos reales: usa una tarjeta real y luego haz un reembolso, o apunta la URL a tu propio entorno de pruebas mientras verificas la integración. Consulta [Prueba tu configuración](mail-testing#test-the-email-and-checkout-on-their-own). ## Solución de problemas \{#troubleshooting\} | Problema | Solución | |---|---| | El enlace de pago no abre | Verifica que el paywall esté publicado en el web paywall builder | | El usuario no está identificado en el pago | Comprueba que la URL incluya un marcador de posición para la fuente de la que proceden los datos. Un marcador de posición para una fuente incorrecta se resuelve como cadena vacía, por lo que el parámetro llega en blanco | | La compra no se atribuye al correo electrónico | La atribución sigue el clic en el CTA del correo, así que el destinatario debe llegar al pago a través de ese enlace y la compra debe alcanzar Adapty Mail en el mismo perfil. Una compra ya atribuida a un correo anterior conserva esa atribución | --- # File: mail-destinations --- --- title: "Destinos de email en Adapty Mail" description: "La página a la que envía cada campaña de Adapty Mail: los tres tipos de destino, los marcadores de posición que personalizan la URL y el hook que completa el resto." --- Toda campaña necesita un lugar al que enviar a sus destinatarios. Ese lugar es un **destino de email** — un nombre, URL y tipo guardados que configuras una vez y reutilizas en distintas campañas. Una campaña apunta exactamente a un destino, y cada parte en la que se puede hacer clic en sus correos lleva allí: el botón CTA, el logo y la imagen de cabecera. Se registran por separado en [análisis](mail-analytics), pero comparten una dirección. ## Tipos de destino \{#destination-types\} El propósito de tu campaña determina el tipo de destino. No puedes cambiar el tipo de destino una vez creado. | Tipo | Campañas compatibles | Adónde lleva al destinatario | De dónde viene la URL | | --- | --- | --- | --- | | **Web Paywall** | Cualquier campaña de marketing y [doble opt-in](mail-double-opt-in) | Una página de pago en el navegador, donde las compras son pagos web en lugar de transacciones de App Store o Google Play. | La genera Adapty Mail, o la pegas desde un paywall que alojas tú. [Configurar el checkout](mail-checkout) cubre ambos casos. | | **Deep Link** | Activación de la app | Tu app, en la pantalla que elijas. Si la app no está instalada, la página de tu store en su lugar. | Pegada, desde tu proveedor de enlaces — OneLink, Branch o Firebase. | | **Cancellation Page** | La campaña de página de cancelación | Tu propia página de cuenta, o el portal de tu proveedor de facturación, donde un suscriptor revisa, cambia o cancela. | Pegada, desde tu propio sitio o tu proveedor de facturación. | :::note Cualquier URL de destino es válida siempre que use HTTPS. Por lo tanto, una **Cancellation Page** puede ser un enlace universal que abra tu aplicación, no solo una página web. Esto es importante porque las suscripciones de App Store y Google Play se cancelan en la store, no en una página que tú alojes. ::: ### Destinos para campañas transaccionales \{#destinations-for-transactional-campaigns\} Adapty Mail tiene tres campañas transaccionales: - **Double opt-in** — se activa cuando un nuevo perfil llega desde tu [punto de entrada](mail-entry-points). Su botón abre la página de confirmación de Adapty Mail. Una vez que el destinatario confirma, accede a tu paywall web — por eso esta campaña necesita uno. - **App activation** — se activa tras una compra y lleva al comprador a tu app. - **Cancellation page** — se activa tras una compra y proporciona al comprador los controles de la suscripción. Cuando creas una campaña, si el desplegable **Email Destination** aparece vacío, significa que aún no tienes ningún destino de ese tipo. El enlace **Create** abre el formulario correspondiente. ## Crear un destino \{#create-a-destination\} 1. Ve a **Email Destinations** y selecciona el tipo que necesita tu campaña. 2. Haz clic en **Create**. 3. Dale un **Name** al destino — lo seleccionarás por ese nombre cuando lo vincules a una campaña. 4. Completa la **URL**. Debe usar HTTPS. 5. Añade [marcadores de posición](#placeholders) para los valores que varían por destinatario. 6. Si la URL usa el marcador de posición `{response.<key>}`, añade un [**Hook URL**](#hooks) en **Advanced**. 7. Haz clic en **Save**. La configuración de un destino pasa a ser de solo lectura en cuanto lo añades a una campaña, aunque sea en borrador. Para enviar una campaña a otro sitio, crea un segundo destino. ## Marcadores de posición \{#placeholders\} Los marcadores de posición personalizan la URL de destino para cada usuario. Sin ellos, tu página no puede saber quién llegó. Adapty Mail sustituye los marcadores de posición por valores únicos para cada destinatario. | Placeholder | A qué se resuelve | | --- | --- | | `{email}` | La dirección de correo del destinatario. | | `{adapty.external_profile_id}` | Su ID de perfil, para datos de la [integración de Adapty](mail-collect-emails). | | `{funnelfox.external_profile_id}` | Su ID de perfil, para datos de [FunnelFox](mail-funnelfox). | | `{custom.external_profile_id}` | El ID de usuario que enviaste, para datos de la [API de Adapty Mail](mail-send-data-via-api). | | `{scheduled_email_id}` | El ID del correo que llevaba el enlace. | | `{custom_attribute.<key>}` | Cualquier atributo personalizado del perfil del destinatario. Tú defines el nombre de la clave — `{custom_attribute.plan}`. | | `{response.<key>}` | Un valor que devuelve tu propio endpoint en el momento del clic. Se explica en [Hooks](#hooks). | - Un marcador de posición para el que el destinatario no tiene valor se resuelve como cadena vacía, por lo que nunca llega a tu página como texto literal. - Los valores de atributos personalizados y hooks están codificados en porcentaje, así que un valor que contenga `&` no puede partir la URL por la mitad. - Un marcador de posición que Adapty Mail no reconoce bloquea el guardado, y el error indica tanto lo que rechazó como lo que acepta. Una errata como `{emial}` se detecta aquí y no en el navegador del destinatario. ### Enlazar con un funnel de FunnelFox \{#link-to-a-funnelfox-funnel\} Cuando un destino lleva a un funnel de FunnelFox — un paywall, una oferta de recuperación o un checkout — pasa el ID de perfil del destinatario en el parámetro `fpid`: ``` https://your-funnel.example.com/offer?fpid={funnelfox.external_profile_id}&utm_source=adapty_mail&utm_medium=email&utm_content={scheduled_email_id} ``` Con `fpid`, el funnel continúa el perfil de FunnelFox existente del destinatario en lugar de crear uno nuevo. Una compra en el funnel llega entonces al mismo perfil de Adapty Mail y se contabiliza en el correo en el que el destinatario hizo clic. :::warning FunnelFox lee el ID de perfil únicamente desde `fpid`. Con cualquier otro nombre, como `ff_profile_id`, FunnelFox lo ignora, y un destinatario que abra el correo en otro dispositivo obtendrá un perfil nuevo y vacío. A menos que el funnel vuelva a pedirle su correo electrónico, Adapty Mail no podrá saber quién realizó la compra: la compra no aparecerá en los [análisis de correo](mail-analytics) y el [flow](mail-flows) seguirá enviándole correos. ::: #### Otorgar acceso en tu app \{#grant-access-in-your-app\} Si tu funnel usa la integración de FunnelFox con Adapty para conceder acceso en tu app, también debes pasar el customer user ID — el identificador que vincula al usuario en ambos sistemas. Para los destinatarios cuyo perfil no procede de FunnelFox, pásalo en el parámetro `adapty_profile_id`. FunnelFox concede entonces la compra en Adapty a ese customer user ID, por lo que se desbloquea en tu app: ``` https://your-funnel.example.com/offer?fpid={funnelfox.external_profile_id}&adapty_profile_id={custom.external_profile_id} ``` :::important A pesar de su nombre, `adapty_profile_id` recibe un ID de usuario del cliente, no un ID de perfil de Adapty. FunnelFox le da prioridad sobre el ID de usuario del perfil de FunnelFox y sobre el email introducido en el proceso de pago. ::: ## Hooks \{#hooks\} El marcador de posición `{response.<key>}` reserva espacio para un valor que proviene de tu propio endpoint. Los usos más habituales incluyen un token de inicio de sesión de un solo uso o una firma de sesión. Crea una nueva **Hook URL** en **Advanced** para configurar este marcador de posición; de lo contrario, la URL no se podrá guardar. Cómo funciona: 1. El destinatario hace clic en un enlace del correo. 2. Adapty Mail envía un GET a tu **Hook URL**, con sus marcadores rellenos para ese destinatario. Espera cinco segundos y realiza un único intento. 3. Tu endpoint verifica la firma y responde con un objeto JSON plano. 4. Adapty Mail rellena cada `{response.<key>}` con la clave del mismo nombre. Una clave que no devuelvas conservará su marcador. 5. El destinatario llega a tu página, independientemente de si el hook respondió o no. ### La solicitud La URL del hook reenvía el resto de tus marcadores de posición a tu endpoint. Usa estos marcadores de posición para identificar al usuario. Dos cabeceras autentican la llamada: | Cabecera | Valor | | --- | --- | | `X-Adapty-Mail-Timestamp` | Hora Unix en segundos, cuando se realizó la solicitud. | | `X-Adapty-Mail-Signature` | HMAC-SHA256 en hex de `<timestamp>.<url>`, firmado con la **Secret key** de **Settings** > **Project**. | Verifica ambas antes de responder. Un endpoint sin firma puede exponer información sensible a usuarios no autorizados. Rechaza cualquier timestamp demasiado antiguo para ser un clic real: ``` expected = hmac_sha256( key=project_secret_key, message=f"{timestamp}.{full_request_url}", ).hexdigest() ``` ### La respuesta Un objeto JSON plano. Cada clave rellena el `{response.<key>}` del mismo nombre: ```json { "checkout_token": "ct_9f2a…", "plan": "annual" } ``` Las cadenas, números y booleanos se convierten en cadenas. Los objetos anidados y los arrays se descartan, ya que una URL no tiene dónde incluirlos. ## Qué ocurre después del clic \{#what-happens-after-the-click\} Adapty Mail rellena los valores, llama al hook si existe, registra el clic y envía al destinatario a tu página. Tu trabajo es identificar al visitante, cobrar el pago y reportar la compra a Adapty. Consulta [Configurar el checkout](mail-checkout#what-your-page-has-to-do) para la configuración del paywall y [Analíticas de campaña](mail-analytics) para la atribución de compras. --- # 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 varios correos — textos, diseño, imágenes destacadas y configuración de tiempos — generada para tu app en un solo paso. Una campaña por sí sola no envía nada: se guarda como **borrador**, y un [flow](mail-create-flow) de Adapty Mail es lo que le da un disparador y una audiencia. <CustomDocCardList ids={['mail-create-campaign', 'mail-localization', 'mail-double-opt-in', '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 hero y líneas de asunto— a partir de los metadatos de tu app en la store. Sin necesidad de redactar ni diseñar nada. El calendario de envíos viene del propósito de la campaña, no de la IA, y cada retraso se puede editar. La campaña se guarda como borrador, y un [flow](mail-create-flow) de Adapty Mail es lo que hace que empiece a enviarse. :::tip Los pasos a continuación crean una campaña de marketing. La pestaña **Transactional** de la página **Campaigns** contiene mensajes de servicio: la campaña de confirmación ([doble opt-in](mail-double-opt-in)), así como las [campañas de activación de app y página de cancelación](mail-destinations#destinations-for-transactional-campaigns). ::: :::tip También puedes generar, revisar y guardar una campaña desde un chat. Conecta un asistente de IA al [servidor MCP de Adapty Mail](mail-mcp) y describe la campaña que quieres crear. ::: ## Antes de empezar \{#before-you-start\} Para generar y guardar una campaña, configura primero lo siguiente: - **Perfil de marca**: El copy, el tono y los elementos visuales se toman de él. Hasta que no exista uno, **Generate emails** permanece deshabilitado. [Brand](mail-brand) explica cómo crearlo. - **Dirección de la empresa**: Las leyes anti-spam exigen una dirección postal en cada email de marketing, por lo que Adapty Mail bloquea la generación hasta que añadas una en **Settings** > **Company**. - **Web paywall**: Cada campaña de marketing enlaza a uno, y Adapty Mail no guardará una campaña sin él. [Email destinations](mail-destinations) cubre los tres tipos de destino y cómo crearlos. ## 1. Genera la secuencia \{#1-generate-the-sequence\} 1. En Adapty Mail, ve a **Campaigns** y haz clic en **Create**. 2. En el diálogo **New marketing campaign**, selecciona el momento del ciclo de vida del cliente para el que está pensada la campaña y haz clic en **Continue**. Los cinco propósitos son los [activadores de flow de marketing](mail-flows#triggers): **Never purchased**, **Renewal cancelled**, **Expired**, **Billing issue** y **Refunded**. 3. Establece el nombre de la campaña. 4. En el desplegable **Email Destination (Web Paywall)**, selecciona el paywall web al que quieres que enlacen los correos. 5. Haz clic en **Generate emails**. 6. 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 verá opciones distintas. Tu elección define las líneas de asunto, los titulares, el cuerpo del texto y las CTAs en todos los emails; no afecta al diseño ni a las imágenes de cabecera. - **Custom prompt** (opcional): Instrucciones en texto libre de hasta 2.000 caracteres. Úsalo para destacar una promoción, una ocasión, matices de audiencia, puntos que deben incluirse o indicaciones de tono adicionales que los ajustes predefinidos 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 elige un valor (**1–15**, por defecto **4**). El tono no es fijo. Para probar otro, vuelve a abrir la campaña y genera de nuevo. Si la campaña tiene traducciones, Adapty Mail las elimina al guardar la nueva secuencia. El disparador que elegiste al principio es lo único que no puedes cambiar. 7. Haz clic en **Generate**. La generación suele tardar unos minutos. Se detiene después de cinco minutos si no puede terminar — vuelve a intentarlo si ocurre eso. :::note El propósito importa más que el copy. Adapty Mail establece los intervalos entre correos a partir de una secuencia diseñada para ese momento: una secuencia de **Never purchased** (nunca han comprado) se concentra en la primera semana, mientras que una secuencia de **Billing issue** (problema de facturación) se envía en los días en que el store reintenta el cobro. También determina dónde puede activarse la campaña: un flow solo ofrece las campañas diseñadas para su propio trigger. ::: ## 2. Revisar y ajustar \{#2-review-and-refine\} Tras la generación, la secuencia completa se muestra en una vista previa. Para cada correo electrónico puedes ver: - **Línea de asunto**: Una por correo. La secuencia se planifica como un conjunto, por lo que correos consecutivos emplean enfoques distintos con el mismo usuario. Para comparar el rendimiento de diferentes líneas de asunto entre sí, crea una segunda campaña y compáralas en una [prueba A/B](mail-ab-testing). - **Titular, cuerpo del mensaje y CTA**: El bloque de contenido principal. - **Imagen de cabecera**: Una imagen generada que se adapta al contenido del correo y a tu marca. - **Disposición y retraso**: Cómo se organiza 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 copy y una nueva imagen hero para un único email. La posición, el timing y el sistema de diseño general (colores, tipografía, modo oscuro) no cambian — solo se modifica el email en cuestión. Ten en cuenta que las traducciones [no se actualizan automáticamente](mail-localization#update-translations) junto con el email original. - **Editar el HTML directamente**: Se abre un editor de código HTML para tener control preciso sobre cualquier detalle que la IA no haya resuelto correctamente. - **Añadir idiomas**: La IA traduce toda la secuencia a los idiomas que elijas. Ver [Añadir idiomas](#add-languages). :::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. ::: ### Agregar idiomas \{#add-languages\} :::link Artículo principal: [Localizar correos electrónicos](mail-localization) ::: Los correos electrónicos se generan en inglés. Para enviarlos en otros idiomas, en la sección **Emails**, haz clic en **Localize** y selecciona los idiomas. La IA traduce todos los correos, incluido el texto de la imagen principal. Cada traducción tiene su propia pestaña. El destinatario recibe la traducción que coincide con el idioma de su dispositivo, o los correos en inglés si no hay ninguna coincidencia. Una sola campaña puede incluir todas las traducciones que añadas: no necesitas crear una nueva campaña ni un segmento por cada país. ## 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-localization --- --- title: "Localizar emails en Adapty Mail" description: "Envía una campaña de Adapty Mail en varios idiomas: cada destinatario recibe los emails en el idioma de su dispositivo, con inglés como respaldo." --- Para llegar a los destinatarios en su propio idioma, localiza tus campañas. Adapty Mail traduce los correos electrónicos de una campaña con IA y envía a cada destinatario la versión en el idioma de su dispositivo, o en inglés si la campaña no lo tiene disponible. Una campaña contiene todas las traducciones que añadas, por lo que no necesitas crear una campaña ni un segmento por cada país. Puedes localizar cualquier campaña, ya sea de marketing o transaccional. Cada campaña tiene sus propias traducciones: ni el proyecto ni el flow tienen idiomas predeterminados, y cada nueva campaña comienza únicamente en inglés. En una [prueba A/B](mail-ab-testing), cada variación es una campaña independiente, por lo que debes localizar cada variación para las mismas audiencias. Así, la prueba compara los correos en sí, no los diferentes mercados. :::tip Para traducir varias campañas a varios idiomas de una sola vez, pídele a un asistente de IA conectado al [servidor MCP de Adapty Mail](mail-mcp). Ejecuta una traducción por campaña e idioma, guarda cada una y reporta qué idiomas fallaron. ::: ## Qué se traduce y qué no \{#what-is-and-isnt-translated\} Adapty Mail traduce: - El asunto y el preencabezado - El cuerpo del mensaje y la versión en texto plano - El texto de los botones - El texto alternativo de las imágenes - El texto de la imagen principal (hero image) Adapty Mail mantiene sin cambios: - Los enlaces y el destino del correo - Los marcadores de posición, como `{{ company_address }}` - El diseño y los estilos - El logotipo y todas las imágenes excepto la imagen principal ## Traducir una campaña \{#translate-a-campaign\} Cada campaña tiene sus propias traducciones: Adapty Mail no aplica la misma selección de idioma a otras campañas. 1. En Adapty Mail, ve a **Campaigns** y abre una campaña existente — o crea una nueva. 2. Desplázate hasta la sección **Emails** del editor de campaña y haz clic en **Localize**. Esto abre el menú de selección de idioma. ### Seleccionar idiomas \{#select-languages\} Para elegir los mejores idiomas para tu audiencia, revisa tus analíticas para determinar los idiomas más comunes. Adapty Mail asocia a los destinatarios por el idioma del dispositivo, no por el país: un destinatario en Alemania con el dispositivo en inglés recibirá los correos en inglés. Estos datos provienen de [Adapty](mail-collect-emails), de [FunnelFox](mail-funnelfox), o como `device_info.locale` en las solicitudes de la [API de Adapty Mail](mail-send-data-via-api). FunnelFox envía el idioma del navegador como configuración regional. Adapty determina la configuración regional del usuario mediante las siguientes acciones: 1. Adapty Mail obtiene el idioma del dispositivo más reciente del perfil. Un perfil puede tener varios dispositivos, como un teléfono y un portátil. Si el más reciente no envió ningún idioma, Adapty Mail usa el siguiente dispositivo más reciente que sí lo hizo. Si ningún dispositivo envió uno, el destinatario recibirá los correos en inglés. 2. Busca una coincidencia exacta entre las traducciones de la campaña, como `pt-BR`. Si no la encuentra, busca el idioma sin la región, como `pt`. 3. Si no hay ninguna coincidencia, el destinatario recibirá los correos en inglés. Adapty Mail establece el idioma una sola vez, cuando el destinatario entra en el flow. Toda la secuencia se envía en ese idioma, aunque el idioma del dispositivo cambie más adelante. En la lista de idiomas, selecciona los idiomas en los que quieres localizar la campaña. #### Variedades regionales \{#regional-varieties\} El portugués y el chino aparecen dos veces en la lista de idiomas. Para llegar a todos los destinatarios que usan uno de estos idiomas, selecciona ambas opciones: - **Portugués de Brasil** y **Portugués**: Los destinatarios solo reciben portugués de Brasil cuando el idioma de su dispositivo es portugués de Brasil (`pt-BR`). El resto de los destinatarios con un idioma de dispositivo en portugués reciben **Portugués**, la variedad europea. Los destinatarios de Brasil también la reciben cuando la campaña no tiene versión en portugués de Brasil. - **Chino tradicional** y **Chino simplificado**: Los destinatarios solo reciben chino tradicional cuando el idioma de su dispositivo está configurado como chino de Taiwán (`zh-TW`). El resto de los destinatarios con un idioma de dispositivo en chino reciben chino simplificado, independientemente del sistema de escritura. Los destinatarios de Taiwán también lo reciben cuando la campaña no tiene versión en chino tradicional. Todos los demás idiomas tienen una sola variante para todas las regiones: - **Inglés**: Tanto `en-US` como `en-GB` reciben los mismos correos. También los destinatarios cuyo idioma de dispositivo no hayas localizado en la campaña, y los perfiles sin configuración regional. - **Español**: La lista de idiomas tiene una única entrada **Spanish**, sin variante para Latinoamérica. Cubre todos los idiomas de dispositivo en español, como `es-MX` y `es-ES`. ### Añadir traducciones 1. En la lista de idiomas, selecciona los idiomas que quieras y haz clic en **Localize**. Espera a que finalice la traducción. Mientras se ejecuta, no puedes editar el texto fuente en inglés ni guardar la campaña. Puedes salir de la página: Adapty Mail conserva las traducciones durante 2 horas. Si vuelves más tarde, las traducciones habrán desaparecido — haz clic en **Try again** para volver a localizar. Si es una campaña nueva, mantén la pestaña del navegador abierta. 2. Revisa cada versión localizada: - Para ver un correo tal como lo verán los destinatarios, haz clic en Preview en su tarjeta. - Para recibir el correo en tu bandeja de entrada, haz clic en **Send test email**. - Revisa el texto de cada imagen principal y asegúrate de que no haya cambiado nada más. Para volver a traducir el correo, incluido el texto de su imagen principal, haz clic en Re-localize this email **Re-localize this email** en su tarjeta. - Revisa los nombres de marca, nombres de producto, precios y fechas en cada traducción. Adapty Mail no los verifica después de la traducción. - Para corregir manualmente una traducción, edita el **Subject**, el **Template HTML** o el **Plain text** del correo. El **Delay** siempre coincide con el correo fuente. 3. Cuando termines, haz clic en **Save** o **Create**. Asegúrate de guardar antes de salir o recargar la página: estas traducciones solo están disponibles en tu navegador. Una traducción que añadas a una campaña activa solo llega a los destinatarios que entren en el flow después de guardar la campaña. Los destinatarios que ya están en el flow seguirán recibiendo los correos en el idioma con el que empezaron. ### Verificación de la traducción Adapty Mail compara cada email traducido con el email en inglés. Solo puede cambiar el texto: la traducción conserva los mismos párrafos, imágenes y botones, en el mismo orden, con los mismos enlaces y marcadores de posición. Si hay alguna diferencia, como un párrafo adicional, Adapty Mail vuelve a traducir el email. Si el email sigue fallando, toda la traducción falla y puedes [intentarlo de nuevo](#troubleshooting). Adapty Mail no verifica las imágenes hero en las traducciones, así que revísalas tú mismo. ## Comparar el rendimiento por idioma \{#compare-performance-by-language\} Para comparar el rendimiento de tus emails en cada idioma, abre [Analytics](mail-analytics#group-break-down-filter) y selecciona **Email language** en el menú desplegable **Group by**. Para limitar la comparación a una campaña, haz clic en **Add filter** > **Campaigns** y selecciona la campaña. **Email language** es el idioma en el que se envió cada email, no el idioma del dispositivo del destinatario. La fila **English** incluye los emails enviados a destinatarios sin una traducción correspondiente, y todos los emails enviados antes del 6 de octubre de 2026. ## Gestionar traducciones \{#manage-translations\} ### Insignias de traducción \{#translation-badges\} Las insignias en las pestañas de idioma y en las tarjetas de correo electrónico muestran el estado de cada traducción: | Insignia | Se muestra en | Significado | | --- | --- | --- | | **New** | Pestaña de idioma, cualquier tarjeta de correo | Adapty Mail añade la traducción o el correo al guardar. | | **Modified** | Pestaña de idioma, cualquier tarjeta de correo | Los cambios en esta traducción o correo se aplican al guardar. | | **Outdated** | Pestaña de idioma, correo traducido | El correo en inglés cambió después de que Adapty Mail lo tradujera. | | **Not translated** | Correo en inglés | Al menos una traducción aún no tiene este correo. | | **Translations outdated** | Correo en inglés | Este correo está desactualizado en al menos una traducción. | ### Actualizar traducciones \{#update-translations\} Cuando cambias el asunto, el HTML o el texto plano del email de origen, o lo regeneras, las traducciones conservan el texto anterior. Adapty Mail las marca como **Outdated**. Con una nueva imagen principal el comportamiento es distinto: todas las traducciones muestran la nueva imagen de inmediato, con el texto original de la imagen (si lo tiene). Para volver a traducir los emails, haz clic en uno de los siguientes controles: - Update translations **Update translations** en un correo fuente. Traduce ese correo donde esté desactualizado. - Re-localize this email **Re-localize this email** en un correo traducido. Traduce solo ese correo. - **Re-localize** en una pestaña de idioma: traduce todos los correos de esa traducción. Luego guarda la campaña. Si vuelves a generar toda la secuencia y la guardas, Adapty Mail elimina todas las traducciones existentes. ### Eliminar traducciones \{#remove-translations\} Para eliminar una traducción de un borrador, en su pestaña, haz clic en **Remove**. Adapty Mail la elimina cuando guardas. En una campaña activa o inactiva, no puedes eliminar una traducción, añadir un correo ni eliminar un correo. Para realizar estos cambios, actualiza las traducciones **Outdated** y guarda la campaña. Luego duplícala y trata la copia como una campaña diferente. Los destinatarios que ya están en la secuencia original seguirán recibiendo los correos originales. :::note Duplicar una campaña restablece su estado de traducción. Las copias desactualizadas aparecen como actuales. Comprueba las traducciones antes de activar la campaña duplicada. ::: ## Limitaciones \{#limitations\} - **Las imágenes no se localizan, excepto la imagen principal**: Si el logo u otra imagen contiene texto visible, permanece en el idioma original. - **No puedes editar la imagen principal en una traducción**: Para cambiarla, sube otra imagen al email fuente y, a continuación, [vuelve a localizar el email](#update-translations) en cada traducción. - **Las páginas no se localizan**: Adapty Mail solo traduce emails. La página de confirmación de doble opt-in y la página de cancelación de suscripción permanecen en inglés para todos los destinatarios. - **El destino no recibe el idioma**: Cada traducción enlaza al mismo destino, y Adapty Mail no transmite el idioma del email al destino. ## Solución de problemas \{#troubleshooting\} - **Un destinatario recibe la campaña en inglés en lugar de su idioma**: Cuando el destinatario entró al flow, su configuración regional no coincidió con ninguna de las traducciones de la campaña según las [reglas de coincidencia](#select-languages). Para ver qué idioma recibió, abre el [journey](mail-profiles#the-activity-journey) del perfil: muestra el asunto de cada correo enviado. Causas comunes: - **El perfil no tiene configuración regional**: Para los perfiles que envías a través de la API de Adapty Mail, incluye `device_info.locale`. - **El dispositivo usa otro idioma**: El idioma del dispositivo es el que decide, no el país. - **Falta la versión regional**: Por ejemplo, la campaña tiene portugués de Brasil y el dispositivo usa `pt-PT`. Añade portugués. - **El destinatario empezó antes de que añadieras su traducción**: Recibirá los correos en inglés hasta que termine su secuencia. - **Los destinatarios reciben una traducción desactualizada después de editar un correo en inglés**: Al editar el asunto, el HTML o el texto plano solo cambias el correo fuente. Sus traducciones permanecen como estaban, y Adapty Mail las marca con la insignia **Outdated** [badge](#translation-badges). Para aplicar tus cambios a todas las traducciones, [actualiza las traducciones](#update-translations) y guarda la campaña. Los correos enviados a partir de ese momento usarán las traducciones actualizadas. Los correos que ya se enviaron no cambian. - **Una traducción muestra la imagen principal con texto en inglés**: Adapty Mail tradujo el correo antes de que cambiaras la imagen principal del correo fuente, o antes del 6 de octubre de 2026. En la tarjeta del correo, la fila **Hero image** indica **Same as English**. Para traducir el texto de la imagen, [vuelve a localizar el correo](#update-translations) y guarda la campaña. Los correos enviados a partir de ese momento usarán la nueva versión. - **El botón Save o Create está desactivado**: Adapty Mail está traduciendo, o falta uno de los correos en inglés en una traducción. A una traducción le falta un correo después de añadir un correo a un borrador localizado. En el banner de esa traducción, haz clic en **Localize it**, o en **Localize them** si son varios correos. - **Una traducción ha fallado**: Si falla un correo o su imagen principal, Adapty Mail descarta toda la traducción a ese idioma: la campaña conserva la traducción anterior o se queda sin ninguna. Las demás traducciones de la misma ejecución aparecen en sus pestañas. Para conservarlas, guarda la campaña. Si una ejecución no termina en 10 minutos, Adapty Mail descarta su resultado. Para volver a intentarlo, haz clic en **Try again** en el aviso **Couldn't localize**. - **Una campaña generada en otro idioma tiene una pestaña base en inglés (English)**: Antes de que se lanzara la localización el 1 de octubre de 2026, Adapty Mail podía generar una campaña en otro idioma. Cada una de estas campañas tiene ahora una versión en inglés, y el idioma original es una de sus traducciones. Los destinatarios con ese idioma en el dispositivo siguen recibiendo los correos originales, y todos los demás los reciben en inglés. --- # File: mail-suppression --- --- title: "Razones por las que se detienen los correos: exclusiones, condiciones de parada y fallos de entrega" description: "Todas las razones por las que Adapty Mail deja de enviar correos a alguien — verificación fallida, bajas, rebotes y condiciones de parada — y qué puedes hacer en cada caso." --- Adapty Mail deja de enviar correos a un usuario por cuatro razones diferentes: - **Stop condition**: La secuencia actual del usuario termina porque ha convertido. No se suprime y sigue siendo elegible para otras campañas. - **No engagement**: La secuencia de marketing actual del usuario termina porque no abrió ni hizo clic en ninguno de sus emails. No se suprime y sigue siendo elegible para otras campañas. - **Suppression**: El usuario queda excluido de todos los envíos de marketing futuros en este proyecto. Los [emails transaccionales](#what-still-reaches-a-suppressed-profile) aún pueden llegarle. - **Throttle**: Un dominio nuevo tiene un límite diario que aumenta conforme envías y que, con el tiempo, desaparece. Un email que supera el límite espera hasta que haya cupo disponible. Si no puede enviarse en tres días, se marca como **Cancelled** y la campaña se detiene para ese destinatario. All four are project-specific. Suppression in one Adapty project doesn't affect another. ## Condiciones de parada (conclusión de campaña por éxito) \{#stop-conditions-campaign-conclusion-on-success\} Una condición de parada finaliza una campaña en cuanto esta tiene éxito, de modo que los correos restantes nunca se envían. Cada evento que recibe Adapty Mail — una compra, un reembolso, una confirmación — actualiza el perfil. Tras cada actualización, Adapty Mail comprueba en el perfil si se cumple la condición de parada correspondiente: - **Campañas de marketing**: el estado de suscripción del perfil llega a **Subscribed**, o el estado de su compra única llega a **Purchased**. - **App activation** y **Cancellation page**: sin condición de parada — siempre se ejecutan hasta el final. - **Double opt-in**: el destinatario confirma su dirección. Cuando se detecta una condición de parada, la campaña finaliza para ese destinatario. Siguen siendo elegibles para las demás campañas. Abre los análisis de tu campaña para ver un desglose de quién llega a las condiciones de parada y cómo. ## Sin interacción (una campaña de marketing ignorada termina antes) \{#no-engagement-an-ignored-marketing-campaign-ends-early\} Una campaña de marketing termina antes para un destinatario que no abre ni hace clic en ninguno de sus correos. Los tres primeros correos se envían independientemente de si se abren o no. Antes de cada correo posterior, Adapty Mail compara el número de correos entregados con el tiempo transcurrido desde que el destinatario entró en la campaña: | Correos entregados | La campaña termina si el destinatario entró hace al menos | | --- | --- | | 3 | 2 días | | 4 | 36 horas | | 5 o más | 1 día | Una apertura o clic en cualquier correo de la campaña mantiene al destinatario en ella hasta el final. Las campañas transaccionales nunca terminan de esta forma. El [journey](mail-profiles#the-activity-journey) del perfil muestra el correo que no se envió como **Cancelled**, con el motivo **No engagement**. El perfil no queda suprimido y puede entrar en otras campañas. ## Supresión (eliminación de la lista de correo) \{#suppression-removal-from-mailing-list\} ### Qué sigue llegando a un perfil suprimido \{#what-still-reaches-a-suppressed-profile\} Un perfil suprimido no recibe más emails de marketing, sea cual sea el motivo. Los emails transaccionales, como el acceso a la app tras una compra o la solicitud de confirmación de [doble opt-in](mail-double-opt-in), sí se envían, a menos que el perfil haya sido suprimido por un rebote, una denuncia de spam o una verificación de dirección fallida. En esos casos, ningún email puede llegar al buzón, por lo que Adapty Mail no envía nada. ### La verificación de dirección falló \{#address-verification-failed\} Adapty Mail comprueba cada dirección de correo electrónico que recibe. Las siguientes direcciones se rechazan directamente. No crean nuevos perfiles: - Cualquier dirección en `privaterelay.appleid.com`, la dirección que Apple emite cuando alguien elige **Hide My Email**. Para enviar correos a esos usuarios, recopila una dirección real por separado. Apple está migrando las nuevas direcciones de retransmisión a `private.icloud.com`, que Adapty Mail no reconoce, por lo que esas llegan como perfiles ordinarios. - Cualquier dirección con caracteres no ASCII o espacios en blanco, que Amazon SES no acepta. Para todas las demás direcciones, Adapty Mail contacta el servidor del usuario para comprobar si el buzón existe. Durante esta comprobación no se envía ningún correo. La dirección se suprime si: - no corresponde a un buzón activo - está alojada en un dominio desechable - su buzón está lleno - es una cuenta de rol, como `info@` o `support@` Si la comprobación no finaliza —porque el servidor de correo la bloquea o no responde— Adapty Mail no suprime la dirección. El perfil se crea y recibe campañas como cualquier otro. Para ver qué ocurrió con una dirección, búscala en [Perfiles](mail-profiles). Un perfil marcado como **Unsubscribed** no superó la verificación, aunque nunca se le llegó a enviar nada. Una dirección sin perfil fue rechazada. La [API de Adapty Mail](mail-send-data-via-api) no puede informarte de esto: su respuesta llega antes de que se ejecute la verificación. Una dirección que deja de funcionar más adelante conserva su perfil hasta que el primer rebote la suprime. Adapty Mail nunca vuelve a verificar una dirección, y enviar una corregida no repara el perfil, sino que crea uno nuevo. :::important Adapty Mail verifica una dirección una sola vez, cuando llega. Después de eso, una dirección inválida solo se detecta cuando [falla el envío de un correo electrónico](#delivery-failed-or-the-recipient-marked-it-as-spam). Valida las direcciones por tu parte antes de pasarlas a Adapty Mail. ::: ### El destinatario canceló la suscripción \{#the-recipient-unsubscribed\} Un correo de marketing ofrece al destinatario dos formas de cancelar la suscripción. Los correos transaccionales no tienen ninguna: los mensajes de servicio no se envían con consentimiento, así que no hay nada que retirar. - **Enlace en el pie de página**: Todos los emails de marketing lo incluyen. Abre una página con la dirección enmascarada del destinatario y un botón **Unsubscribe**; al hacer clic, se suprime el perfil. El paso de confirmación es deliberado: los escáneres y los prefetchers abren por su cuenta los enlaces del cuerpo del mensaje y no deben activar cancelaciones accidentalmente. - **Cancelación con un clic**: Los clientes de correo que admiten esta función muestran un botón de "cancelar suscripción" junto al nombre del remitente. Un solo clic suprime el perfil, sin página de confirmación intermedia. Adapty Mail envía las cabeceras `List-Unsubscribe` que Gmail y Yahoo exigen a los remitentes masivos (RFC 8058) con cada email de marketing. Ambas URLs contienen un token cifrado que identifica al destinatario y el correo del que proviene, por lo que darse de baja no requiere iniciar sesión ni ninguna búsqueda. Ambas apuntan a tu propio dominio, pero la página de cancelación está alojada en Adapty Mail. :::note Darse de baja desde un [correo de prueba](mail-testing) no cambia nada: los envíos de prueba quedan excluidos de la supresión. ::: ### Entrega fallida o el destinatario lo marcó como spam \{#delivery-failed-or-the-recipient-marked-it-as-spam\} Amazon SES informa sobre cada correo electrónico que gestiona. Tres de esos informes suprimen el perfil de forma inmediata: | Evento | Qué significa | | --- | --- | | Bounce | El servidor de correo receptor rechazó el email y lo devolvió. Tanto los hard bounces (direcciones sin buzón o dominio) como los soft bounces (un buzón lleno, por ejemplo) suprimen el perfil. Ayuda a detectar direcciones de email que superaron [la verificación inicial](#address-verification-failed) pero dejaron de funcionar más tarde. | | Complaint | El destinatario marcó el email como spam. | | Reject | Amazon SES rechazó el mensaje antes de enviarlo. | El [recorrido](mail-profiles#the-activity-journey) del perfil muestra un rebote como **Bounced** y una queja como **Spam report**. ### Volver a activar un destinatario \{#bring-a-recipient-back\} Nada en el dashboard revierte una supresión. Contacta con el soporte de Adapty, por ejemplo cuando alguien marca un email de prueba como spam. La página del perfil sí muestra si un perfil está suprimido: el encabezado indica el motivo, como **Bounced** o **Reported spam**. El menú ⋯ de esa misma página también permite suprimir un perfil manualmente. ## Limitación de envíos (retrasos y cancelaciones temporales) \{#throttle-temporary-delays-and-cancellations\} Un dominio de envío nuevo tiene un límite de 100 correos por día. El límite aumenta a medida que envías y desaparece una vez que el dominio ha completado el calentamiento; consulta [Calentamiento de dominio](mail-sending-domain#warm-up-and-daily-limits). Adapty Mail comprueba cada uno de tus dominios con su propio límite y envía desde el primero que aún no lo haya alcanzado. Por eso, tener un segundo dominio verificado aumenta el volumen diario que puede enviar el proyecto. El límite se distribuye a lo largo del día, por lo que cada dominio también tiene un límite por hora — consulta [Cómo funcionan los niveles](mail-sending-domain#how-tiers-work). Cuando todos tus dominios superan un límite, el correo nunca llega a Amazon SES. El [recorrido](mail-profiles#the-activity-journey) del perfil queda en **Throttled**. Adapty Mail lo reintenta una vez que el límite se libera: en la hora siguiente si fue el límite por hora el que retuvo el correo, o después de la medianoche UTC si fue el límite diario. Los correos transaccionales se reintentan primero, luego los primeros correos de los flows y, después, el resto. Un correo que sigue sin poder enviarse tras tres días UTC, contando el día en que debería haberse enviado por primera vez, se marca como **Cancelled**, y la campaña del destinatario se detiene. El perfil no queda suprimido y puede entrar en otra campaña. Nada registra la cancelación por sí solo, y ese chip es el único registro. Antes de un lanzamiento masivo desde un dominio nuevo, añade un segundo dominio verificado o deja que el límite suba primero. Los envíos de prueba ignoran el límite por completo, así que un [correo de prueba](mail-testing) exitoso no te dice nada sobre si un envío real llegaría a salir. ## Cumplimiento normativo \{#compliance\} Cumplir con la CAN-SPAM, el RGPD y las normativas equivalentes en tus mercados es tu responsabilidad como remitente. Adapty Mail gestiona una parte de forma automática y deja el resto en tus manos. ### Lo que se gestiona automáticamente - **Enlace para cancelar suscripción**: En el pie de cada email de marketing, procesado en cuanto el destinatario lo confirma. - **Cabeceras List-Unsubscribe**: Enviadas con cada email de marketing para cancelar la suscripción con un clic desde el buzón (RFC 8058). - **Supresión automática**: Se activa por un rebote o un informe de spam. - **Marcador de dirección**: Reservado en el pie de cada email de campaña y completado con la dirección que proporciones. ### Tu responsabilidad - **La dirección de tu empresa**: Guárdala en **Settings** > **Company** — ese es el valor con el que se rellena el marcador de posición. La ley CAN-SPAM exige una dirección física en el pie de página, y Adapty Mail no generará una campaña sin ella. - **Consentimiento explícito de opt-in**: Recógelo antes de pasar el correo electrónico de un usuario a Adapty. Consulta [Conectar Adapty a Adapty Mail](mail-collect-emails). Para que cada nuevo destinatario confirme también su dirección antes de recibir cualquier correo de marketing, activa el [doble opt-in](mail-double-opt-in). - **Solicitudes de eliminación según el RGPD**: Envía los identificadores del perfil al endpoint [Delete profile](api-mail/operations/deleteProfile) de la API de Adapty Mail. Adapty Mail borra los datos personales del perfil y cancela sus correos programados. La eliminación es definitiva: una solicitud posterior con los mismos identificadores no vuelve a crear el perfil. --- # File: mail-double-opt-in --- --- title: "Doble confirmación en Adapty Mail" description: "Pide a los nuevos destinatarios que confirmen su dirección de correo electrónico antes de que Adapty Mail les envíe cualquier correo de marketing." --- Con el doble opt-in activado, cada nuevo perfil recibe primero un correo de confirmación. Hasta que la persona haga clic en él, no le llega ningún mensaje de marketing. Úsalo para dirigirte a mercados que exigen consentimiento explícito, como la UE o Canadá. La [verificación de dirección](mail-suppression#address-verification-failed) ya comprueba que el buzón existe; el doble opt-in comprueba que quien escribió la dirección es quien la lee. Este ajuste no requiere cambios en tu código. El doble opt-in no reemplaza el consentimiento que recopilas cuando solicitas la dirección en primer lugar — [cumplimiento normativo](mail-suppression#compliance) cubre lo que necesitas gestionar. La configuración solo se aplica a los perfiles creados después de activarla, desde cada [punto de entrada](mail-entry-points). Los perfiles que ya existían siguen recibiendo campañas como antes. Para cubrir toda tu audiencia, activa el doble opt-in antes de [empezar a enviar](mail-get-started#10-start-sending). Los perfiles de FunnelFox también están incluidos, incluso cuando el funnel ya recopiló el consentimiento. Un perfil cuyo `email_consent` es `false` no recibe la solicitud de confirmación: la persona ya rechazó el email de marketing y Adapty Mail no le envía ninguno. ## Cómo funciona \{#how-it-works\} Adapty Mail crea un perfil en el momento en que recibe una dirección de correo electrónico, desde cualquier [punto de entrada](mail-entry-points). Con el doble opt-in activado, ese momento da inicio a una secuencia de dos correos: 1. El primero se envía un minuto después de crear el perfil y pide a la persona que confirme la dirección. Ignora la [ventana de envío](mail-flows#email-delivery-timing) habitual, así que un registro a las 3am recibe su solicitud a las 3am. 2. Si no confirma, se envía un recordatorio aproximadamente 72 horas después, ajustado a la ventana de envío si caería fuera de ella. Adapty Mail no vuelve a solicitarlo. El botón de confirmación abre una página con los colores de tu marca que muestra la dirección enmascarada. Cargar esa página no confirma nada: la persona también tiene que hacer clic allí, de modo que los escáneres de buzón que siguen enlaces no puedan confirmar en su nombre. El enlace nunca caduca, así que cualquiera de los dos emails seguirá funcionando meses después. Si el usuario aún no ha comprado nada, el botón muestra **Confirm and open my offer**, y la página lo redirige a tu paywall web justo después del clic. Confirmar tiene tres efectos: - **El recordatorio se cancela**: cualquier correo de confirmación pendiente se cancela. - **El bloqueo de marketing se levanta**: Adapty Mail evalúa tus flows de marketing para esa persona de inmediato, y entra en cualquier flow que coincida. Si no coincide con ninguno, no se envía nada hasta que lo haga. - **Adapty Mail no vuelve a pedir confirmación**: la confirmación se almacena una sola vez, y desactivar y volver a activar la configuración no genera una nueva solicitud. En el [journey](mail-profiles#the-activity-journey) del perfil, los correos de confirmación aparecen como eventos de correo, y la confirmación en sí se muestra como **Confirmed**. ## Activar el doble opt-in \{#turn-on-double-opt-in\} Antes de activarlo, asegúrate de que el proyecto tiene al menos un [web paywall](mail-checkout). Adapty Mail genera la campaña de confirmación con IA y necesita un paywall al que redirigir a los usuarios confirmados. Sin él, el interruptor falla con el mensaje *"Failed to update double opt-in"*. Para activar el doble opt-in, ve a **Settings** > **Project**. En la sección **Double opt-in**, activa el interruptor. Puede tardar un minuto en responder, ya que Adapty Mail genera los correos de confirmación antes de guardar la configuración. <ZoomImage id="mail-double-opt-in-setting.webp" width="700px" alt="La sección Double opt-in en la pestaña Settings > Project, con el interruptor en Enabled" /> Al activar el interruptor se crean ambas partes de la secuencia: una campaña transaccional con los correos de confirmación y un flow que los envía a cada nuevo perfil. - **Campaña**: llamada **Double opt-in**, en **Campaigns** > **Transactional**. - **Flow**: en **Flows** > **Transactional** > **Double opt-in**, donde **All Users** está vinculado a esa campaña. El flow se comporta como cualquier otro flow. No lo vacíes mientras la configuración esté activada: si no se le pide confirmación a nadie, ningún perfil nuevo recibirá comunicaciones de marketing. ## Prueba de doble opt-in \{#test-double-opt-in\} Para ver la secuencia completa antes de que los usuarios reales la recorran, ejecuta un perfil de prueba: 1. En tu app, regístrate con una dirección de correo que no tenga todavía un perfil en el proyecto, o publica un perfil con una nueva dirección a través de la [Adapty Mail API](mail-send-data-via-api). 2. En Adapty Mail, ve a **Profiles**, abre el nuevo perfil y espera a que aparezca el correo **Double opt-in** en su journey. La entrega tarda unos minutos tras el retraso de un minuto. 3. En el correo, haz clic en el botón de confirmación y confirma en la página que se abre. 4. De vuelta en el journey, el correo debería mostrar **Confirmed**. Si algún flow de marketing coincide con el perfil, su primer correo debería programarse justo después. Para la parte de marketing de la cadena, sigue [Prueba tu flow de Adapty Mail](mail-testing). ## Editar el correo electrónico de confirmación \{#edit-the-confirmation-email\} Un correo electrónico de confirmación sigue reglas más estrictas que un correo de marketing: - **El botón de confirmación es el enlace de destino**: El botón debe usar `{{ destination_url }}`. En una campaña de doble opt-in, Adapty Mail lo reemplaza con un enlace de confirmación único por destinatario en lugar de la URL de tu paywall. - **Nada de venta**: El texto no puede mencionar precios, descuentos, pruebas ni plazos. El generador rechaza cualquier texto que lo haga. - **Ningún otro enlace**: El email no tiene ningún enlace salvo el botón de confirmación, ni enlace de baja en el pie de página. Adapty Mail también omite la cabecera `List-Unsubscribe`, por lo que los clientes de correo no muestran un botón de baja para una solicitud de confirmación. Para regenerar los correos, abre la campaña desde **Campaigns** > **Transactional** y haz clic en **Generate emails**. El diálogo ofrece un menú desplegable **Purpose**, que está configurado en **Double opt-in**, y un campo **Custom prompt**. La longitud de la secuencia, los retrasos y el diseño están fijados por el propósito, por lo que las opciones de tono, imagen y número de correos de una campaña de marketing no aparecen. Para llegar a los destinatarios en su idioma, [localiza la campaña **Double opt-in**](mail-localization). ## Desactivar el doble opt-in \{#turn-off-double-opt-in\} Para desactivar el doble opt-in, ve a **Settings** > **Project**. En la sección **Double opt-in**, desactiva el interruptor. Adapty Mail finaliza el flow de **Double opt-in**, incluidos los segmentos que hayas añadido tú mismo. La campaña se conserva, por lo que si vuelves a activar la configuración se reutilizarán sus correos en lugar de generarlos de nuevo. Después de desactivarlo: - Los nuevos perfiles entran en los flows de marketing sin necesidad de confirmar. - Los perfiles que estaban esperando confirmación ya no quedan bloqueados, pero no se reevalúan en ese momento. Entran en un flow de marketing la próxima vez que llegue un evento para ellos. - Si vuelves a activar la configuración, los perfiles creados mientras estaba desactivada se consideran existentes y no se les pide confirmación. ## Limitaciones \{#limitations\} - **Los correos de confirmación cuentan en los análisis de Campaign**: La campaña **Double opt-in** aparece como una campaña propia en la página de [Analytics](mail-analytics), y sus envíos, aperturas y clics se suman a los totales. Para consultar solo tus cifras de marketing, agrupa por **Campaigns** y desmarca su casilla de visibilidad. - **No hay estado de confirmación en Profiles**: La [lista de Profiles](mail-profiles) ni la página del perfil muestran si una dirección está confirmada o pendiente. El journey es el único lugar donde aparece la confirmación. - **No hay reglas por país**: La configuración se aplica a todos los perfiles nuevos del proyecto, independientemente de su país. Enviar `email_consent` como `true` no omite la confirmación. - **Los perfiles existentes están exentos**: A los perfiles creados antes de activar la configuración nunca se les pedirá que confirmen. Para recopilar confirmaciones de ellos, tendrás que hacerlo fuera de Adapty Mail. - **La página de confirmación está en inglés**: Los destinatarios la ven en inglés aunque localices los correos de confirmación. --- # 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 envíos programados. Decide qué campaña recibe un usuario cuando se activa un **trigger**, y un **segmento** decide qué usuarios califican. Adapty Mail evalúa cada flow cada vez que se activa un evento coincidente — sin polling, sin cron, sin lanzamientos manuales. :::note El producto principal de Adapty tiene sus propios flows. Son una funcionalidad diferente — nada de lo que se explica aquí aplica a ellos. ::: ## Disparadores \{#triggers\} Un disparador es el evento que inicia un flow. Los disparadores son fijos: no puedes crear los tuyos propios ni ampliar la lista. ### Disparadores de marketing \{#marketing-triggers\} Adapty Mail tiene cinco disparadores de marketing, cada uno con su propia pestaña en **Flows** > **Marketing**: - **Nunca ha comprado**: Usuarios que se registraron pero aún no han realizado ninguna compra. Objetivo: activación y primera conversión. Los usuarios en prueba no están aquí — iniciar una prueba cuenta como suscripción activa. - **Renovación cancelada**: 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 convertirse. La mejor ventana para retenerlos — todavía tienen acceso. Separa las audiencias de pago y de prueba mediante filtros de segmento si el mensaje necesita ser distinto. - **Problema de facturación**: El pago falló — tarjeta rechazada o vencida, o período de gracia tras una renovación fallida. Objetivo: recuperación urgente y útil, no un empuje de ventas. Recupéralos rápido — ya querían pagar. - **Expirado**: La suscripción ha caducado y se ha perdido el acceso. Incluye tanto expiraciones de pago como pruebas que terminaron sin convertirse. Objetivo: recuperarlos. Los filtros de segmento pueden adaptar el mensaje para audiencias con prueba expirada y con pago expirado. - **Reembolsado**: Usuarios que solicitaron un reembolso tras la compra. Objetivo: entender qué salió mal y ofrecer una opción más adecuada. El tono debe ser humilde y curioso, no un intento de venta agresivo. Un flow de marketing se ejecuta de uno en uno por destinatario: mientras hay una secuencia en curso, no se inicia ninguna segunda. Finaliza cuando el destinatario compra, o antes si [abre y no hace clic en ninguno de sus correos](mail-suppression#no-engagement-an-ignored-marketing-campaign-ends-early). ### Disparadores transaccionales \{#transactional-triggers\} Los flows transaccionales llevan mensajes de servicio. Dos disparadores los inician, distribuidos en tres pestañas en **Flows** > **Transactional**: - **Double opt-in**: Se activa cuando llega un nuevo perfil desde tu [punto de entrada](mail-entry-points), mientras el [doble opt-in](mail-double-opt-in) está activado. Se detiene en cuanto se confirma la dirección. - **App activation**: Se activa tras una compra y lleva al comprador a tu app. - **Cancellation page**: Se activa tras una compra y proporciona al comprador los controles de suscripción. Ambos flows de compra se activan en la compra inicial de un producto, por lo que un comprador configurado para ambos los recibirá los dos. Cada flow se ejecuta una vez por perfil, junto con cualquier flow de marketing aplicable. :::note Las renovaciones no activan estos flows. ::: ## Audiencia y contenido \{#audience-and-content\} Bajo cada trigger, vinculas un **segmento de usuario** con lo que recibirá. Un flow puede contener varios de estos pares, de modo que un mismo trigger puede servir a varias audiencias. ### Quién cumple los requisitos \{#who-qualifies\} Un segmento es un conjunto de filtros sobre tu base de usuarios; consulta [Segmentos](mail-segments). Solo los usuarios que coincidan con el segmento entrarán en el flow. No necesitas un segmento por cada país: en una [campaña localizada](mail-localization), Adapty Mail selecciona el idioma para cada destinatario. **All Users** es un segmento integrado sin filtros, por lo que todos los usuarios cumplen los requisitos. No puedes editarlo ni eliminarlo. ### Qué reciben \{#what-they-receive\} Cada segmento recibe una de dos cosas: - **Campaign**: Todos los que coincidan reciben la misma campaign. - **A/B Test**: Dos o más campaigns con pesos configurables. Adapty Mail distribuye a los usuarios entre ellas de forma aleatoria y hace seguimiento de las métricas por variante; consulta [pruebas A/B](mail-ab-testing). Cuando varios segmentos podrían coincidir con la misma persona, la **priority** decide qué campaign reciben. Cuando un usuario activa el trigger, Adapty Mail: 1. Comprueba los segmentos de arriba a abajo, en el orden en que aparecen. 2. Envía la campaña asociada al primer segmento que coincida. 3. Se detiene. Los segmentos inferiores no se evalúan para ese usuario. Un segmento amplio situado por encima de uno más específico absorbe a todos los usuarios que de otro modo habrían coincidido con el más específico. Para reordenar, arrastra el controlador a la izquierda de cualquier fila. :::important **All Users**, si está presente, debe tener la prioridad más baja, es decir, ser la última entrada del trigger. De lo contrario, el flow no se guardará. ::: ## Ciclo de vida de un flow \{#flow-lifecycle\} Los flows no tienen estado de borrador. Se publican en el momento en que los guardas. Un usuario que activa el desencadenador y coincide con el segmento se une a la campaña de inmediato, y el primer correo se envía tras su **Delay**. El efecto de una edición depende de lo que cambies: - **Ediciones de flow**: Una edición de flow solo afecta a los usuarios que activen el disparador a partir de ese momento. Eliminar un segmento no cancela los correos ya programados. - **Ediciones de campaña**: Las ediciones de campaña afectan a los usuarios que ya están en el flow. Adapty Mail lee el contenido de cada correo en el momento del envío, así como su **Delay** cuando se entrega el correo anterior. - **Campañas localizadas**: El texto de una traducción solo cambia cuando [actualizas la traducción](mail-localization#update-translations) y guardas la campaña. Una prueba A/B tiene su propio ciclo de vida — **draft**, **live** y **finished** — que se controla de forma independiente al segmento con el que está asociada. Consulta [Pruebas A/B](mail-ab-testing). ### Tiempo de envío de los emails \{#email-delivery-timing\} - Cada email tiene su propio **Delay** (retraso) — el tiempo que pasa desde el email anterior hasta que se envía. Puedes editarlo en el editor de campaña. - Adapty Mail solo envía entre las 08:00 y las 21:00 en la zona horaria del destinatario. Si un envío cae fuera de ese horario, se pospone al límite más cercano de la ventana. - El primer email es una excepción cuando debe enviarse dentro de las dos horas siguientes al desencadenante. Se envía a tiempo, aunque sea de noche. --- # File: mail-create-flow --- --- title: "Gestionar segmentos en un flow en Adapty Mail" description: "Añade, reordena, edita y elimina los segmentos en un flow para dirigir campañas a tus usuarios." --- En Adapty Mail, un **flow** conecta un **trigger** con una **campaña**, y un **segmento de usuarios** decide quién la recibe. Un trigger es el evento que inicia un flow. Cada trigger tiene su propia pestaña, donde emparejas segmentos con las campañas que recibirán, en orden de prioridad. Esta guía explica cómo añadir, reordenar, editar y eliminar esos segmentos. Consulta [Flows](mail-flows) para obtener una visión general de los conceptos relacionados. :::tip Un asistente de IA conectado al [servidor MCP de Adapty Mail](mail-mcp) puede añadir y reordenar segmentos por ti. Al guardar un flow se empieza a enviar, así que revisa el plan completo del asistente antes de aprobarlo. El [skill de Adapty Mail](mail-skill) muestra cada fila que va a guardar, incluido lo que elimina. ::: ## Añadir un segmento a un flow \{#add-a-segment-to-a-flow\} 1. En Adapty Mail, ve a **Flows** y abre el trigger 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 para todos. - **Content type**: **Campaign** para una única campaña, o **A/B Test** para comparar varias — consulta [Pruebas A/B](mail-ab-testing). - **Campaign**: Selecciona la campaña a enviar. La lista solo muestra las campañas escritas para este trigger — consulta [purpose](mail-create-campaign). 4. Haz clic en **Save**. El flow se activa en el momento en que lo guardas. Los usuarios que activen el trigger y coincidan con el segmento empezarán a recibir la campaña desde ese momento, con el primer correo enviado tras su [**Delay**](mail-flows#email-delivery-timing). ## Cambiar la prioridad \{#change-priority\} Para reordenar, arrastra el control de la izquierda de cualquier fila. **All Users**, si está presente, debe tener la prioridad más baja, es decir, ser la última entrada del trigger. De lo contrario, el flow no se guardará. ## Cambiar lo que recibe un segmento \{#change-what-a-segment-receives\} 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. Un segmento vinculado a 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 un segmento de un flow \{#remove-a-segment-from-a-flow\} Abre el menú de acciones de la fila y haz clic en **Delete**. No hay diálogo de confirmación: la fila se elimina inmediatamente. - **Segmentos vinculados a una campaña**: Se pueden eliminar en cualquier momento. - **Segmentos vinculados a una prueba A/B activa**: No se pueden eliminar. Finaliza primero la prueba usando **Finish A/B test**. :::note Eliminar un segmento impide que nuevos usuarios entren en la secuencia. Los usuarios que ya estén en mitad de la secuencia podrían 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\} | Group | Field | Type | | --- | --- | --- | | Profile | Email | String | | Profile | Gender | String | | Profile | Age | Number | | Profile | Country | String | | Profile | Store country | String | | Profile Source Identity | Source | String | | Profile Source Identity | Created at | Date | | Device info | Device | String | | Device info | Locale | String | | Device info | OS | String | | Device info | Platform | String | | Device info | App version | Version | | Purchase state | Total revenue (USD) | Number | | Purchase state | Subscription state | String | | Purchase state | Subscription purchased at | Date | | Purchase state | Subscription expires at | Date | | Purchase state | One-time purchase state | String | | Purchase state | One-time purchased at | Date | Los campos de información del dispositivo solo se rellenan cuando tu punto de entrada los envía. [Enviar datos a través de la API](mail-send-data-via-api) explica qué contiene ese payload. **Valores del estado de suscripción**: Never purchased, Subscribed, Auto-renew off, Billing issue, Grace period, Expired, Refunded. **Valores del estado de compra única**: Never purchased, Purchased, Refunded. Operadores disponibles por 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. - **Version**: las mismas comparaciones que Number, aplicadas en orden de versión — `1.10.0` tiene mayor rango que `1.9.0`. ## 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 según cómo se está usando: - **Draft**: Creado, sin estar vinculado a ningún flow ni prueba A/B. - **Live**: Vinculado a un flow activo o una prueba A/B. - **Inactive**: Estuvo vinculado, pero la prueba A/B finalizó o eliminaste el segmento del flow. - **Archived**: Eliminado de forma lógica y oculto de la lista principal. La página de segmentos tiene un filtro de estado en la barra de herramientas para que puedas acotar la lista a cualquiera de estos estados. ## Editar y eliminar un segmento \{#edit-and-delete-a-segment\} - **Nombre y descripción**: Siempre editables. - **Filtros**: Solo editables mientras el segmento está en estado **Draft**. Una vez que un flow o una prueba A/B lo ha utilizado, los filtros quedan bloqueados de forma permanente — **Live**, **Inactive** y **Archived** los bloquean todos. El nombre y la descripción siguen siendo editables. Para cambiar el targeting, crea un nuevo segmento y sustitúyelo en el flow. - **Eliminar**: Realiza una eliminación lógica del segmento. Los segmentos en estado Live no se pueden eliminar — primero retíralos del flow (o finaliza la prueba A/B). ## Limitaciones \{#limitations\} - **Sin lógica OR ni anidamiento**: Los filtros se combinan solo 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 tras el primer uso**: Solo se pueden modificar los filtros de un segmento en estado **Draft**. Cualquier segmento que ya se haya utilizado es de solo lectura, excepto su nombre y 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 el email 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, con el resultado de sus compras, su interacción con el email y el historial completo de actividad. Los perfiles se crean automáticamente a través del [punto de entrada](mail-entry-points) que hayas configurado. El requisito es disponer de una dirección de correo electrónico: Adapty Mail no crea ningún perfil para un cliente que no la haya proporcionado. :::tip Para agrupar perfiles en audiencias reutilizables para flows y pruebas A/B, consulta [Segmentos](mail-segments). ::: :::tip Para saber por qué una persona no recibió un correo electrónico, pregúntale a un asistente de IA conectado al [servidor MCP de Adapty Mail](mail-mcp). Este lee el perfil y su historial de actividad. El [skill de Adapty Mail](mail-skill) revisa en orden las posibles causas. ::: ## Cómo llegan los perfiles a Adapty Mail \{#how-profiles-get-into-adapty-mail\} Adapty Mail crea perfiles automáticamente desde tres fuentes: - **Adapty**: El SDK recopila correos electrónicos y compras desde tu app. Consulta [Conectar Adapty con Adapty Mail](mail-collect-emails). - **Adapty Mail API**: 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). - **FunnelFox**: Tus funnels web reenvían correos electrónicos, compras y eventos de suscripción. Consulta [Conectar FunnelFox con Adapty Mail](mail-funnelfox). Adapty Mail identifica a las personas por dirección de correo electrónico. Dos fuentes que informen la misma dirección se fusionan en un único perfil; dos direcciones diferentes crean perfiles separados, aunque el `customer_user_id` sea idéntico. Cada fuente mantiene su propio identificador para la persona, y la página del perfil los lista todos bajo **External Profile IDs**. Si también usas Adapty, un perfil aquí no es tu perfil de Adapty. Adapty Mail lleva sus propios registros y almacena el ID de perfil de Adapty como uno de esos IDs externos. Es normal tener menos perfiles aquí que en Adapty, ya que los perfiles de Adapty sin dirección de correo electrónico nunca llegan. No puedes crear ni editar perfiles aquí —la fuente que los envió es la propietaria de esos datos—, pero sí puedes cancelar su suscripción y eliminarlos. ## La lista de perfiles \{#the-profiles-list\} La lista muestra un perfil por fila, los más recientes primero. 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 email del cliente y la campaña detrás de su evento de email más reciente. | | Status | El estado de compra del perfil. Ver [Estado del perfil](#profile-status). | | Country | El país del cliente. | | Open rate | Emails abiertos divididos entre emails enviados. Un guion significa que aún no se ha enviado ningún email. | | LTV | Valor de por vida: ingresos totales de este perfil en todas las fuentes. | | Joined | Cuándo el perfil entró por primera vez en Adapty Mail. | | Last activity | Cuándo ocurrió el evento de email más reciente del perfil. Un envío cuenta, no solo una apertura o un 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: qué adquirió el cliente y si sigue activo. No indica si recibe tus correos electrónicos. | Estado | Significado | | --- | --- | | Never purchased | El cliente no ha comprado nada. | | Purchased | El cliente realizó una compra única. | | Active subscriber | El cliente tiene una suscripción de pago configurada para renovarse. | | Renewal cancelled | 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 terminó. | | Refunded | Se reembolsó una compra. | :::note Un perfil puede ser un **Suscriptor activo** y estar **Sin suscripción** a tus emails, o haber dado de baja (**Churned**) y seguir **Suscrito**. El estado del email aparece en la página del perfil y determina 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 del perfil \{#the-profile-header\} El encabezado identifica al cliente: correo electrónico, país, género, edad, plataforma y la fecha "cliente desde". También muestra tres insignias: - **Purchase status**: El estado de compra del perfil, usando los valores que aparecen en [Estado del perfil](#profile-status). - **Open rate**: Las aperturas de este perfil en relación con los emails enviados. [Analytics](mail-analytics) cuenta los destinatarios que abrieron al menos una vez, no los emails, por eso ambas cifras difieren. - **Email status**: **Subscribed**, o el motivo por el que se dejó de enviar, como **Unsubscribed**, **Bounced** o **Reported spam**. **Consent denied** significa que la fuente registró que la persona rechazó los emails de marketing; consulta **Email consent** en [la tarjeta de perfil](#the-profile-card). Esta insignia no está relacionada con la suscripción de pago en **Status**. **Subscribed** significa que nada bloquea el envío, no que haya algo programado. El perfil aún tiene que coincidir con un segmento en un flow, y el [doble opt-in](mail-double-opt-in) retiene los correos de marketing hasta que el usuario confirme su dirección. [Por qué se detienen los correos](mail-suppression) cubre todos los motivos de supresión que reemplazan **Subscribed**. ### Métricas de interacción \{#engagement-metrics\} Cinco métricas aparecen en la parte superior de la página: - **Sent**: Correos enviados al perfil. - **Delivered**: Correos aceptados por el proveedor de buzón. - **Opened**: Veces que el destinatario abrió un correo, contando aperturas repetidas. - **Clicked**: Veces que el destinatario hizo clic en un enlace. - **Revenue**: Dos cifras: ingresos atribuidos y valor de por vida. :::note **Los ingresos atribuidos** son los ingresos generados por tus emails: las compras que realizó el cliente después de hacer clic en un enlace de uno de ellos. **El valor de vida (LTV)** es el total de ingresos del perfil de todas las fuentes, independientemente de si el email intervino o no. La cabecera muestra primero los ingresos atribuidos y luego el LTV. ::: ### La tarjeta de Perfil \{#the-profile-card\} La tarjeta **Profile** muestra los atributos del cliente: - **Platform**: La plataforma del dispositivo más reciente del cliente, como iOS o Android. - **Email provider**: Quién gestiona el buzón — **Google**, **iCloud**, **Proton**, etc. — identificado a partir de los registros MX del dominio. **Disposable** indica un servicio de direcciones desechables, que [no supera la verificación](mail-suppression#address-verification-failed) y llega ya suprimido. - **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, seguida del cumpleaños que envió la fuente. - **Email consent**: La respuesta del cliente sobre el correo electrónico de marketing, tal como la envió la fuente: **Granted**, **Denied** o **Not provided**. Adapty Mail no envía correos de marketing a perfiles con **Denied**. Los correos transaccionales sí les llegan, excepto la solicitud de confirmación de [doble opt-in](mail-double-opt-in). - **Profile ID**: El identificador interno de Adapty Mail para el perfil. **External Profile IDs** lista una entrada por fuente — **Adapty**, **FunnelFox** o **Custom** — con el identificador que esa fuente usa para esta persona y cuándo llegó por primera vez. **Custom attributes** lista los pares clave-valor enviados por la fuente. ### Estado de la compra \{#purchase-state\} La tarjeta **Purchase state** muestra el historial de ingresos y compras del perfil. El valor de por vida aparece en la parte superior, seguido de los ingresos recurrentes y, a continuación, hasta dos secciones: - **Subscription**: Precio, store, fecha de inicio, ID del producto y una fecha etiquetada como **Renews** mientras el acceso esté activo o **Expired** cuando haya finalizado. - **One-time purchase**: Precio, store, fecha de compra e ID del producto de la compra única más reciente. Cuando el cliente tiene ambas, cada sección muestra su propia insignia de estado. Si no ha realizado ninguna compra, la tarjeta muestra **No purchase yet**. ### Segmentos \{#segments\} La tarjeta **Segments** muestra cuántos segmentos coinciden con el perfil en ese momento y los enumera; haz clic en uno para abrirlo. Si ninguno coincide, la tarjeta muestra **Not in any segment**. Adapty Mail vuelve a evaluar los filtros en cada solicitud, por lo que la lista siempre refleja los datos más recientes del perfil. [Segmentos](mail-segments) explica cómo crearlos. ## El recorrido de actividad \{#the-activity-journey\} La sección **Journey** es una línea de tiempo de todo lo que ha ocurrido en el perfil, comenzando con **Profile created**. Los eventos de correo electrónico y los eventos de transacción se intercalan en una sola lista. ### Eventos de email \{#email-events\} Cada entrada es un email enviado por Adapty Mail. Expándela para ver los enlaces en los que hizo clic el destinatario y cualquier compra que generó el email. Un chip muestra hasta qué punto llegó el email: | Chip | Qué significa | | --- | --- | | **Scheduled** | El correo tiene una hora de envío y Adapty Mail aún no lo ha enviado. | | **Sent** | Adapty Mail envió el correo y aún no tiene confirmación de entrega. | | **Delivered** | El servidor de correo receptor aceptó el correo. Este chip reemplaza a **Sent**. | | **Opened** | El píxel de seguimiento se cargó. | | **Clicked** | El destinatario hizo clic en un enlace. Expande el correo para ver qué enlaces y la compra que generó. | | **Confirmed** | El destinatario confirmó su dirección desde un correo de [doble opt-in](mail-double-opt-in). | | **Bounced** | El servidor de correo receptor rechazó el correo y lo devolvió. | | **Spam report** | El destinatario marcó el correo como spam. | | **Unsubscribed** | El destinatario se dio de baja. | | **Delayed** | El servidor de correo receptor pidió a Adapty Mail que lo intentara más tarde. | | **Throttled** | Todos los dominios de envío superaron su [límite horario o diario](mail-suppression#throttle-temporary-delays-and-cancellations), por lo que el correo está en espera. | | **Cancelled** | El correo no se enviará. Una compra, una baja, un rebote, un reporte de spam, una eliminación, un consentimiento de correo rechazado, un throttle que dura más de tres días, o una campaña de marketing que el destinatario nunca abre ni en la que hace clic cancelan lo que queda de una secuencia; consulta [Razones por las que se detienen los correos](mail-suppression). | ### Eventos de transacción \{#transaction-events\} Cada entrada es un hito de suscripción o compra única. El recorrido muestra una etiqueta en lenguaje sencillo en lugar del `event_type` sin procesar: | `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 de Adapty, o puedes enviarlos tú mismo mediante [la API](mail-send-data-via-api#send-transaction-events). ## Cancelar la suscripción de 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 de marketing y los flows lo omiten. Los [correos transaccionales](mail-suppression#what-still-reaches-a-suppressed-profile) siguen llegándole. Una vez que un perfil está suprimido —por cualquier motivo— **Unsubscribe** permanece desactivado. [Razones por las que se detienen los correos](mail-suppression) explica las dos formas en que los destinatarios pueden darse de baja por su cuenta. ## Eliminar un perfil \{#delete-a-profile\} Elimina un perfil para borrar todo lo que Adapty Mail almacena sobre una persona — por ejemplo, para responder a una solicitud de eliminación de datos. Para eliminar un perfil, abre su página, haz clic en **...**, selecciona **Delete** y confirma. A continuación, Adapty Mail: - Borra la dirección de correo electrónico y todos los atributos personales: nombre, género, fecha de nacimiento, país, país de la store, datos del dispositivo y atributos personalizados. - Cancela los correos electrónicos pendientes de envío para ese perfil. - Elimina el perfil de la página Profiles. Esta acción es irreversible. Los registros de compra se mantienen sin ningún dato que identifique a la persona, por lo que los totales de ingresos no cambian. La eliminación no bloquea la dirección. Si una fuente la vuelve a enviar, Adapty Mail crea un nuevo perfil, desconectado del anterior. Para impedir definitivamente el acceso a alguien, deja de enviar sus datos desde la fuente. Tu backend también puede eliminar perfiles a través del endpoint [Delete profile](api-mail/operations/deleteProfile). --- # 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 variación es una campaña independiente y completa. Cuando un usuario coincide con el segmento de la prueba en un [flow](mail-flows), Adapty Mail lo dirige a una de las variaciones según los pesos configurados y hace un seguimiento de la entrega, el engagement y los ingresos por variación. :::tip Un asistente de IA conectado al [servidor MCP de Adapty Mail](mail-mcp) puede crear una prueba, lanzarla en un flow, leer el embudo por variación y finalizarla con la variante ganadora. Te pedirá confirmación antes de lanzar o finalizar. ::: ## Qué es una variante \{#what-a-variation-is\} Cada variante es una campaña completa. Las variantes pueden diferir en el texto, las imágenes principales, el tono, la longitud de la secuencia o los tiempos de espera entre correos. Cada variante tiene sus propias [traducciones](mail-localization). Para una comparación justa, localiza todas las variantes en los mismos idiomas. Las variaciones no pueden diferir en quién las recibe ni en cuándo. Un segmento abarca toda la prueba. Cada campaña incluida comparte un mismo [propósito](mail-create-campaign): el momento del ciclo de vida que aborda, con el mismo nombre que usa un flow para su [desencadenador](mail-flows#triggers). Eso es lo que determina en qué flow puede ejecutarse la prueba. Escribes cada campaña por separado y luego las añades como variaciones. ## Crear una prueba A/B \{#create-an-ab-test\} 1. Primero crea las campañas en **Campaigns**. Cada variación necesita su propia campaña, y todas deben tener el mismo propósito. 2. En Adapty Mail, ve a **A/B Tests** y haz clic en **Create**. 3. Añade cada campaña como variación 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 no envía nada todavía. Para activarla, debe asociarse a un flow. ## Lanzar desde un flow \{#launch-from-a-flow\} El lanzamiento y la finalización de una prueba A/B se gestionan desde el flow, no desde la página **A/B Tests**. 1. En Adapty Mail, ve a **Flows** y abre el trigger que debe iniciar la prueba. 2. Haz clic en **Create** en una fila nueva. En el cuadro de 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 **draft** a **live** y los usuarios entrantes que coincidan con el segmento empiezan a ser dirigidos a las variantes. [Gestionar segmentos en un flow](mail-create-flow) cubre el resto de opciones disponibles. ## 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. ## Leer resultados \{#read-results\} En la página de pruebas A/B, cada variante muestra sus contadores brutos y las tasas derivadas: - **Delivery**: Sends, Deliveries, Bounces. - **Engagement**: Opens, Clicks, Unsubs. - **Revenue**: Purchases, Revenue. [Analíticas de campaña](mail-analytics) explica qué cuenta cada métrica y cómo se atribuyen los ingresos. ## Finalizar la prueba \{#finish-the-test\} 1. Abre el trigger que inicia 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 flow por completo. 4. Confirma. :::note Los usuarios que ya están a mitad de una secuencia en cualquier variante —ganadora o perdedora— siguen recibiendo sus emails programados. No se les cambia a la variante ganadora. ::: ## Ciclo de vida \{#lifecycle\} Una prueba A/B pasa por cuatro estados: - **Borrador**: Creada, pero aún no añadida a un flow. - **Activa**: Vinculada y en marcha; enrutando a los usuarios entrantes. - **Finalizada**: Detenida mediante **Finish A/B test**. - **Archivada**: Eliminada de forma provisional de la lista. --- # File: mail-analytics --- --- title: "Analíticas en Adapty Mail" description: "Analiza el rendimiento de tus campañas por campaña, segmento, variante A/B, mensaje o trigger, y consulta entrega, engagement e ingresos en un mismo lugar." --- La página **Analytics** muestra qué hicieron tus correos: cuántos se entregaron, abrieron y recibieron clics, y cuántos ingresos generaron. Cada número se puede desglosar — por campaña, por segmento, por variante A/B y más. La página muestra un gráfico en la parte superior y una tabla de desglose debajo. Haz clic en cualquier fila para profundizar en una entidad concreta. :::tip Para consultar tus datos en lenguaje natural — "¿qué campaña tuvo mejor conversión el mes pasado?" — conecta un asistente de IA al [servidor MCP de Adapty Mail](mail-mcp). Lee las mismas métricas y desgloses que esta página. ::: ## Elige 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. - **Fecha del evento** / **Fecha de envío**: Elige el día en que se contabiliza cada evento. **Fecha del evento**, la opción predeterminada, cuenta cada evento en el día en que ocurrió. **Fecha de envío** cuenta cada apertura, clic y compra en el día en que se envió su email, de modo que cada día muestra los resultados de los emails enviados ese día. - **Granularidad**: Bloques **Diario**, **Semanal** o **Mensual**. La granularidad se ajusta automáticamente cuando el rango crece: **Diario** cambia a **Semanal** pasados los 92 días, y ambos cambian a **Mensual** pasados los 366 días. - **Estilo del gráfico**: **Línea**, **Área** o **Barra**. Si la página indica que el rango es demasiado amplio, reduce el rango de fechas, aumenta la granularidad, agrupa por menos criterios o aplica filtros. ## Agrupar, desglosar, filtrar \{#group-break-down-filter\} Los tres controles que hay debajo de la barra de herramientas definen qué muestra el gráfico y la tabla: - **Group by**: Divide la tabla en una fila por campaña, por segmento o por lo que elijas. Con **No grouping**, todo se colapsa en una única fila **All**. - **Breakdown**: Subdivide cada una de esas filas según una segunda selección. Expande una fila para verlas. Permanece desactivado hasta que establezcas un **Group by**, y no puedes elegir la misma opción dos veces. - **Add filter**: Reduce el gráfico y la tabla a las campañas, segmentos o dominios que te interesen. Los tres ofrecen las mismas doce opciones: | Opción | Una fila por | | --- | --- | | **Campaigns** | campaña | | **Campaign type** | marketing o transaccional | | **Campaign purpose** | momento del ciclo de vida para el que se redactó la campaña | | **Email language** | idioma en el que se envió el correo | | **Segments** | segmento al que pertenecía el destinatario | | **A/B variants** | variante a la que se dirigió al destinatario | | **Messages** | correo individual dentro de una campaña | | **Triggers** | disparador que inició el flow | | **Sender email domain** | dominio de envío que llevó el correo | | **Mailbox email provider** | proveedor de correo del destinatario: Google, iCloud, etc. | | **Mailbox email domain** | dominio de correo del destinatario | | **Event outcome** | motivo del rebote, tipo de queja o enlace en el que se hizo clic | Dos de esas opciones se comportan de forma diferente: - **Messages** aparece solo en el desplegable **Breakdown**. El asunto de un mensaje no tiene sentido fuera de su campaña, así que agrupa primero por **Campaigns** y luego divide cada fila de campaña en los emails individuales que envió. - **Event outcome** asigna a cada fila un único tipo de evento, por lo que la mayoría de sus columnas estarán vacías: un motivo de rebote no tiene aperturas. Las filas que no tienen nada que mostrar para las métricas seleccionadas se ocultan. Si la categoría de métricas **Rate** está activada, **Event outcome** no está disponible, porque sus filas no tienen nada entre lo que dividir. ## Lee el gráfico \{#read-the-chart\} El gráfico muestra las métricas que selecciones en el intervalo de tiempo elegido. - **Categoría de métrica**: Alterna entre **Email actions** (Sent, Delivered, Opened, Clicked, Bounced, Complained, Unsubscribed, Unique purchases), **Revenue** y **[Rate](#plot-a-rate)**. **Unique purchases** es el nombre del gráfico para **Converted**. - **Pastillas de métricas**: Elige qué métricas representar. En modo agregado (sin agrupación), puedes trazar varias métricas en el mismo gráfico. Con agrupación activada, 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á activada, la leyenda a la derecha lista todos los grupos y te permite activar o desactivar cada serie individualmente. Las casillas de visibilidad de la tabla de métricas también controlan qué filas aparecen en el gráfico. ## Representar una tasa \{#plot-a-rate\} Para ver una métrica como proporción de otra, selecciona la categoría de métricas **Rate**. El gráfico representará cada tasa como un porcentaje por período. Cada tasa aparece como un chip encima del gráfico. Para representar una tasa, haz clic en su chip. Sin agrupación, puedes representar varias tasas a la vez. Con la agrupación activada, el gráfico representa una sola tasa con una línea por grupo. Cada proyecto comienza con tres tasas: | Tasa | Fórmula | | --- | --- | | **Open rate** | Unique openers / Delivered | | **Click rate** | Unique clickers / Delivered | | **Conversion rate** | Unique purchases / Delivered | Estas tasas se dividen por **Delivered**, por lo que pueden diferir de las tasas de apertura y clic de la tabla de métricas, que se describen en [Tasas derivadas](#derived-rates). Las tasas se leen mejor con **Send date** seleccionado en la [barra de herramientas](#pick-a-time-range). Cada día divide entonces las aperturas, los clics y las compras de los correos de ese día entre esos mismos correos. Con **Event date**, un día mezcla correos enviados antes, por lo que una tasa puede desviarse o superar el 100%. Un período en el que el denominador es cero no tiene tasa. La línea se interrumpe ahí en lugar de caer al 0%. ### Cambiar las tasas Las tasas pertenecen al proyecto, por lo que cualquier cambio que guardes se aplica a todos los miembros del proyecto. 1. Edita la lista: - Para cambiar una tasa, haz clic en el icono de lápiz de su chip, luego edita su **Name**, **Numerator** y **Denominator**. - Para añadir una tasa, haz clic en **Add rate**. - Para eliminar una tasa, haz clic en el icono de lápiz de su chip y luego en **Remove rate**. 2. Revisa el gráfico. Muestra los cambios al instante, antes de guardarlos. 3. Para conservar los cambios, haz clic en **Save for everyone**. Para descartarlos, haz clic en **Discard**. El **numerador** y el **denominador** pueden ser cada uno de los siguientes: Sent, Delivered, Unique recipients, Opened, Unique openers, Clicked, Unique clickers, Bounced, Complained, Unsubscribed y Unique purchases. Los términos que comienzan con **Unique** cuentan personas. Los demás términos cuentan emails. Revenue no está disponible en una tasa. **Save for everyone** permanece desactivado hasta que la lista siga estas reglas: - **Al menos una tasa**: No puedes eliminar la última tasa. - **Un nombre para cada tasa**: Hasta 64 caracteres. - **Dos términos diferentes**: Una tasa no puede dividir una métrica por sí misma. - **Una fórmula única**: Dos tasas no pueden usar el mismo numerador y denominador. ## 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 utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo 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**: Emails enviados a SES. - **Delivered**: Entregas en bandeja de entrada confirmadas por SES. - **Bounced**: Rebotes reportados por SES. Los rebotes duros y blandos cuentan como un **Bounced**. Cada dominio de envío tiene una [tarjeta **Bounces**](mail-domain-health#bounces) que los muestra por separado para los últimos 30 días. - **Opened**: Cargas del píxel. Apple Mail Privacy Protection precarga imágenes en iOS 15+ e infla este contador — utiliza clics e ingresos como señales más fiables. - **Clicked**: Clics en el botón CTA, la imagen principal o el logotipo. Los enlaces del pie de página —cancelar suscripción, privacidad, términos— no se contabilizan y no aparecen en ningún lugar de las analíticas. Un clic se registra en tu propio [subdominio de enlaces](mail-sending-domain#1-add-the-domain), por el que pasan todos los enlaces antes de redirigir al destino. - **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 período de tiempo. Con **Event date**, las conversiones se agrupan por fecha de compra — un clic en marzo seguido de una compra en abril cuenta en abril. Con **Send date**, se agrupan por el día en que se envió el email. Para cambiar, usa la [barra de herramientas](#pick-a-time-range). Un perfil que compra más de una vez sigue contando una sola vez. - **Revenue**: Suma de los ingresos atribuidos (USD) de inicios de suscripción, renovaciones y compras únicas. ## Tasas derivadas \{#derived-rates\} | Tasa | Qué divide | | --- | --- | | Tasa de entrega | Entregados / Enviados | | Tasa de apertura | Personas que abrieron al menos un correo / todas a las que Adapty Mail intentó enviar | | Tasa de clics | Personas que hicieron clic en un CTA, imagen principal o logo / todas a las que Adapty Mail intentó enviar | La tasa de entrega cuenta correos; las otras dos cuentan personas. Alguien que abre el mismo correo cinco veces suma cinco a **Opened** pero uno a la tasa de apertura. El denominador incluye las direcciones que rebotaron, así que un lote con muchos rebotes arrastra la tasa de apertura hacia abajo. El desglose muestra las mismas tres tasas junto a sus tarjetas de resumen. ## Atribución de ingresos \{#revenue-attribution\} Los ingresos se atribuyen por **último clic**: 1. Cuando un destinatario hace clic en el CTA, la imagen principal o el logotipo, Adapty Mail recuerda de qué correo vino ese clic durante una hora. 2. Una compra realizada en esa hora se acredita a ese correo, siempre que no tenga ya una atribución y haya ocurrido después del clic. 3. Una compra realizada más de una hora después del clic, o sin ningún clic previo, permanece sin atribuir. 4. La renovación de un producto que el perfil ya compró no se acredita nunca, sin importar cuándo fue el último clic. La atribución no requiere nada adicional de tu página de pago: el clic se registra en la redirección y la compra se asocia al perfil en cuanto llega a Adapty Mail. Los marcadores `{email}` e ID de perfil que puede incluir una página alojada por ti sirven para personalizar lo que ve el visitante; no intervienen en la atribución. [Configurar el checkout](mail-checkout) cubre ambos casos. ## 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 Flows**: Bajo cada desencadenador, cada segmento muestra sus propios datos de entrega, interacción e ingresos. - **Página A/B Tests**: Las variaciones se muestran una al lado de la otra con el mismo conjunto de métricas, lo que facilita comparar variantes directamente. Usa la página Analytics cuando compares entre campañas o quieras profundizar en una entidad concreta, y las vistas integradas cuando ya estés trabajando en un flow o en una prueba A/B. Las definiciones de métricas, tasas derivadas y reglas de atribución descritas anteriormente se aplican de forma idéntica en las tres vistas. ## Limitaciones \{#limitations\} - **Todos los rebotes se cuentan juntos**: La métrica **Bounced** incluye tanto los rebotes permanentes como los temporales. Cada dominio de envío tiene una [tarjeta **Bounces**](mail-domain-health#bounces) que los muestra por separado para los últimos 30 días. - **Consistencia eventual, no en tiempo real**: Los recuentos se agregan desde tablas de eventos. Los eventos recientes suelen aparecer en cuestión de minutos, pero no hay garantía de streaming. - **El tamaño del rango está limitado**: Un rango de fechas amplio con una granularidad fina, o una agrupación con muchas filas, puede superar lo que el gráfico es capaz de renderizar. En ese caso, la página muestra *"Selected range is too wide. Try a coarser granularity or shorter range."* - **Sin datos de idioma para eventos anteriores**: **Email language** muestra todos los eventos anteriores al 6 de octubre de 2026 como **English**, incluidos los procedentes de correos electrónicos traducidos. --- # File: mail-testing --- --- title: "Prueba tu flow de Adapty Mail" description: "Haz pasar a un usuario de prueba por Adapty Mail para confirmar que los correos se envían, el checkout procesa el pago y el acceso llega a tu app." --- Prueba una campaña antes de lanzarla a usuarios reales. - **Prueba la cadena en vivo**: ejercita la automatización y te cuesta un pago real. Ejecútala al menos una vez antes de lanzar. - **Envía el correo de prueba**: omite la automatización, y en el checkout funciona una tarjeta de prueba de Stripe. ## Antes de empezar \{#before-you-start\} La cadena funciona de extremo a extremo solo cuando todo lo siguiente es verdad: - **Tu dominio de envío está verificado**: Abre **Settings** > **DNS** para confirmarlo. Consulta [Configurar tu dominio de envío](mail-sending-domain). - **Checkout**: Tu paywall web está creado y publicado. Un paywall sin publicar devuelve un error cuando el CTA intenta abrirlo. Consulta [Configurar el checkout](mail-checkout). - **Campaña vinculada a un flow**: Una campaña por sí sola permanece como borrador y no envía nada. Un flow de Adapty Mail es lo que la activa — [Gestionar segmentos en un flow](mail-create-flow) explica cómo hacerlo. - **Un punto de entrada activo**: Los datos de usuario llegan realmente a Adapty Mail. Sea cual sea el [punto de entrada](mail-entry-points) que uses, la comprobación es la misma: los nuevos registros aparecen en la página [Profiles](mail-profiles). ## Probar la cadena en vivo \{#test-the-live-chain\} Para probar una campaña en condiciones reales, regístrate como tu propio usuario. La campaña te enviará correos igual que a cualquier otro usuario, y pagas con una tarjeta real en tu checkout de producción. Los pasos a continuación reducen la espera para que todo tarde minutos en lugar de días. 1. **Acorta el primer retraso.** En el editor de campaña, establece el **Delay** del primer correo en uno o dos minutos para no tener que esperar tu programación real. Restaura los retrasos después de la prueba. 2. **Crea un usuario de prueba.** En tu app, regístrate con una dirección de correo que no tenga ningún perfil en el proyecto todavía. Cada ejecución necesita una dirección nueva, ya que un perfil que ya haya pasado por la campaña no volverá a entrar en ella. Para recibir una [traducción](mail-localization), configura el idioma de tu dispositivo de prueba antes de registrarte. 3. **Espera el primer correo.** Adapty Mail programa el envío para el momento del trigger más el retraso que hayas configurado, y procesa los correos pendientes cada minuto. La entrega añade unos minutos más. 4. **Abre el correo y haz clic en el CTA.** Llegarás a tu checkout con la dirección de correo electrónico ya rellena. 5. **Completa el pago.** Paga con una tarjeta real y luego reembolsala en Stripe. ## Probar el correo y el checkout de forma independiente \{#test-the-email-and-checkout-on-their-own\} Para revisar el correo y el checkout sin esperar a la automatización, usa **Send test email** en el editor de campañas. El botón permanece desactivado hasta que tu dominio de envío esté verificado y la campaña tenga un [destino de correo](mail-destinations) seleccionado. El modal tiene tres configuraciones: - **Environment**: **Production** abre tu checkout en vivo con productos reales. **Sandbox** abre la versión de prueba, donde la tarjeta de prueba de Stripe `4242 4242 4242 4242` funciona con cualquier fecha de expiración futura y cualquier CVC. Sandbox solo se puede seleccionar para un paywall generado con productos sandbox — un paywall añadido por URL muestra *"This paywall has no sandbox products, so only production is available."* - **Profile**: Opcional. Selecciona un perfil para enviar el email a la dirección de ese perfil y rellenar el CTA con sus IDs. - **Custom address**: Opcional. Escribe una dirección para enviar allí en su lugar, con un ID de perfil de marcador de posición. Seleccionar un perfil la borra, y el perfil siempre tiene prioridad si ambos están configurados. Sin ninguno de los dos configurados, el correo va a la dirección de tu propia cuenta con un ID de perfil de marcador de posición. Un envío de prueba es un correo independiente, separado de la cadena en vivo. Confirma que el correo se renderiza y que el checkout procesa el pago, pero no te dice nada sobre si la automatización se dispara. Para confirmar la automatización, ejecuta [la cadena en vivo](#test-the-live-chain). ## Confirmar el acceso en tu app \{#confirm-access-in-your-app\} Una vez procesado el pago, la suscripción llega al perfil de Adapty del usuario y tu app detecta el nivel de acceso. Adapty registra un pago sandbox como una transacción sandbox, así que asegúrate de que tu nivel de acceso acepta transacciones sandbox antes de tratar un nivel de acceso ausente como un error. ## Comprueba el resultado \{#check-the-result\} Abre tu perfil de prueba en [Profiles](mail-profiles) y consulta su [journey](mail-profiles#the-activity-journey). Una prueba exitosa muestra el primer email llegando a **Clicked**, la compra atribuida a él y el resto de la secuencia en **Cancelled**. :::note Adapty Mail registra una apertura cuando se carga el píxel de seguimiento, y Apple Mail Privacy Protection lo carga independientemente de si alguien leyó el email. Es normal ver un chip **Opened** que no obtuviste. [Campaign analytics](mail-analytics) explica qué cuenta cada métrica. ::: ## Más información \{#learn-more\} - **[Crear una campaña](mail-create-campaign)**: Contenido del email, retrasos y duración de la secuencia. - **[Configurar el checkout](mail-checkout)**: Qué contiene la página de checkout y cómo la personalización rellena el CTA. - **[Perfiles](mail-profiles)**: La vista completa del perfil detrás de la línea de tiempo del journey. - **[Motivos por los que se detienen los emails](mail-suppression)**: Por qué un perfil deja de recibir emails. --- # File: mail-mcp --- --- title: "Gestiona Adapty Mail con asistentes de IA (MCP)" description: "Conecta Claude, ChatGPT, Claude Code, Codex, Cursor u otro cliente MCP a tu proyecto de Adapty Mail y gestiona campañas, flows, dominios de envío y analíticas desde un chat." --- Adapty Mail ejecuta un servidor MCP, de modo que un asistente de IA puede trabajar en tu proyecto igual que tú lo harías en el dashboard: generar y localizar campañas, lanzar flows, configurar un dominio de envío, consultar perfiles y leer analíticas. El asistente inicia sesión con tu propia cuenta de Adapty Mail y obtiene tus permisos en cuanto apruebas la conexión en el dashboard. No se necesita ninguna clave de API. | | | |---|---| | URL del servidor | `https://api-mail.adapty.io/mcp` | | Protocolo | MCP sobre Streamable HTTP, inicio de sesión OAuth 2.1 | | Compatible con | Claude (web y escritorio), ChatGPT, Claude Code, Codex CLI, GitHub Copilot CLI, Gemini CLI, Cursor, Windsurf, y cualquier cliente que admita servidores MCP remotos con OAuth | :::tip Para obtener los mejores resultados en un agente de código, añade también el [skill de Adapty Mail](mail-skill). Se conecta al servidor por ti, pregunta antes de hacer cualquier cosa que envíe correos a personas reales, y cubre el trabajo posterior al lanzamiento: resultados, pruebas A/B y por qué alguien no recibió un correo. ::: ## Qué puede hacer el asistente \{#what-the-assistant-can-do\} Las herramientas del servidor reflejan el dashboard: | Área | Ejemplos | |---|---| | Orientación | Localiza tus empresas y proyectos, ve qué pasos de configuración faltan | | Marca | Crea la marca a partir de un listado en la store, revísala y edítala | | Dominios de envío | Añade un dominio, obtén sus registros DNS, comprueba la verificación, consulta el estado del dominio | | Destinos | Crea y actualiza las páginas a las que enlazan tus correos | | Campañas | Genera una campaña a partir de un brief, edita correos, tradúcelos, envía un correo de prueba | | Flows y segmentos | Crea segmentos y lanza o modifica flows | | Pruebas A/B | Crea, lanza y finaliza pruebas | | Perfiles | Busca un perfil y su historial, da de baja a un perfil | | Analítica | Métricas de campaña a lo largo del tiempo, desgloses, embudos de pruebas A/B | | Ajustes | Nombre del remitente, dirección de respuesta, doble opt-in, subida de imágenes | Hay tres flujos de trabajo guiados disponibles como prompts en los clientes que los admiten. En Claude Code aparecen como comandos slash, por ejemplo `/mcp__adaptymail__campaign_to_flow`: - **campaign_to_flow**: Genera una campaña a partir de un brief, elige un destino y un segmento, y lanza el flow. - **campaign_localization**: Traduce una campaña guardada a más idiomas. - **domain_onboarding**: Añade un dominio de envío, publica sus registros DNS y verifícalo. Algunos pasos de configuración solo se realizan en el dashboard: tu [dirección de empresa](mail-get-started#5-add-your-company-address), el [web checkout](mail-checkout) y la [activación del envío de datos](mail-entry-points). ## Antes de empezar - **Membresía del proyecto**: Tu cuenta de Adapty Mail es miembro del proyecto en el que quieres trabajar. El asistente solo ve las empresas y proyectos que tu cuenta puede abrir. - **Compatibilidad del cliente**: Tu cliente admite servidores MCP remotos con OAuth. :::warning El asistente tiene los mismos permisos que tú, y todo lo que hace es real: guardar un flow empieza a enviar correos a personas reales. El servidor marca sus herramientas destructivas, y los asistentes que hemos probado piden confirmación antes de ejecutarlas. Lee el plan del asistente antes de aprobarlo. ::: ## Conecta tu asistente \{#connect-your-assistant\} ### Claude (claude.ai y Claude desktop) 1. Para añadir el servidor, abre **Settings → Connectors** y haz clic en **Add custom connector**. 2. Introduce un nombre, por ejemplo `Adapty Mail`, y la URL `https://api-mail.adapty.io/mcp`. 3. Deja **Authentication** en **Sign in now** y el cliente OAuth en el valor predeterminado detectado, luego haz clic en **Add**. 4. En la pestaña Adapty Mail que se abre, inicia sesión si se te pide y haz clic en **Approve**. Los administradores del espacio de trabajo pueden añadir el conector una sola vez para toda la organización. Cada miembro inicia sesión con su propia cuenta de Adapty Mail la primera vez que lo usa. ### ChatGPT ChatGPT añade servidores MCP personalizados desde la página de conectores en **Settings**. El modo desarrollador debe estar habilitado en tu workspace. 1. Para añadir el servidor, haz clic en **Create**. 2. Introduce un nombre, por ejemplo `Adapty Mail`, y la URL del servidor `https://api-mail.adapty.io/mcp`. Mantén **Authentication** en **OAuth**. 3. Acepta la advertencia del servidor MCP personalizado y haz clic en **Create**. 4. Cuando ChatGPT lo solicite, conecta el servidor. En la pestaña de Adapty Mail que se abre, inicia sesión si es necesario y haz clic en **Approve**. ### Claude Code ```bash claude mcp add --transport http --scope user adaptymail https://api-mail.adapty.io/mcp ``` Para iniciar sesión, ejecuta `/mcp` en Claude Code, selecciona `adaptymail` y elige **Authenticate**. Luego inicia sesión en el Adapty Mail dashboard en tu navegador y haz clic en **Approve**. ### Codex CLI ```bash codex mcp add adaptymail --url https://api-mail.adapty.io/mcp codex mcp login adaptymail ``` El comando de login abre tu navegador, donde apruebas la conexión. Para configurar el servidor manualmente, agrégalo a `~/.codex/config.toml` y luego ejecuta el comando de login: ```toml [mcp_servers.adaptymail] url = "https://api-mail.adapty.io/mcp" ``` ### GitHub Copilot CLI ```bash copilot mcp add --transport http adaptymail https://api-mail.adapty.io/mcp ``` La primera vez que el servidor se conecta, el navegador se abre para iniciar sesión. ### Gemini CLI ```bash gemini mcp add --transport http --scope user adaptymail https://api-mail.adapty.io/mcp ``` Inicia una nueva sesión y ejecuta `/mcp auth adaptymail` para aprobar la conexión en tu navegador. ### Cursor, Windsurf y otros clientes Añade el servidor al archivo de configuración MCP del cliente, por ejemplo `~/.cursor/mcp.json`: ```json { "mcpServers": { "adaptymail": { "url": "https://api-mail.adapty.io/mcp" } } } ``` La primera vez que se conecta, el cliente abre el navegador para iniciar sesión. Los clientes que no admiten OAuth para servidores remotos no pueden conectarse a Adapty Mail. ## Aprobar la conexión \{#approve-the-connection\} Todos los clientes terminan en la misma pantalla del dashboard de Adapty Mail. En ella aparece el nombre de la aplicación que solicita acceso y la dirección donde la aplicación recibe ese acceso. En el caso de herramientas de escritorio, esa dirección está en tu propio equipo (`localhost`), así que aprueba únicamente una conexión que hayas iniciado tú. Para finalizar, haz clic en **Approve**. La solicitud de conexión caduca pasados 10 minutos. Si la pantalla muestra **This request has expired**, vuelve a empezar desde el cliente. ## Empieza a trabajar \{#start-working\} Pide al asistente que liste tus proyectos y luego nombra el que quieres trabajar. Algunas solicitudes que puedes probar: - "Muéstrame el estado de onboarding de mi proyecto e indícame qué falta." - "Genera una campaña de recuperación para usuarios cuyo trial haya expirado, en inglés, y muéstrame el primer email." - "Traduce la campaña Trial expired al alemán y al francés." - "Añade `mail.example.com` como dominio de envío y dame los registros DNS." - "Compara las tasas de apertura y clics de mis campañas activas en los últimos 30 días." ## Desconexión \{#disconnect\} Para desconectarte, elimina el conector en los ajustes del cliente. En Claude, está en **Settings → Connectors**; en ChatGPT, en la página del conector; en Claude Code, ejecuta `claude mcp remove adaptymail`. El token de acceso del cliente expira en una hora. El token de actualización se revoca cuando el cliente se desconecta; uno sin usar expira tras 90 días. Para volver a conectarte, añade el servidor y aprueba la conexión de nuevo. ## Solución de problemas \{#troubleshooting\} | Problema | Solución | |---|---| | El dashboard indica que la solicitud de conexión ha expirado | Inicia la conexión de nuevo desde el cliente y apruébala en un plazo de 10 minutos. | | El asistente indica que no se encontró el proyecto | Tu cuenta no es miembro de ese proyecto. Solicita acceso al propietario de la empresa. | | El asistente cerró sesión | El acceso expira tras un largo período de inactividad o al desconectarte. Vuelve a conectarte desde el cliente. | | El cliente no muestra ninguna herramienta | El cliente se conectó sin iniciar sesión. Ejecuta el paso de inicio de sesión correspondiente, por ejemplo `/mcp` en Claude Code. | --- # File: mail-skill --- --- title: "Skill de agente para Adapty Mail" description: "Instala el skill adapty-mail para que tu herramienta de codificación con IA conecte el servidor MCP de Adapty Mail, lance flows de correo y averigüe por qué no se envió un email." --- El skill adapty-mail ejecuta tu proyecto de Adapty Mail desde tu herramienta de codificación con IA. Funciona a través del [servidor MCP de Adapty Mail](mail-mcp), con tu sesión iniciada. Lanza flows, traduce campañas, guía un dominio de envío a través de DNS y verificación, lee los resultados de campañas y pruebas A/B, y averigua por qué una persona no recibió un email. The MCP server alone gives your agent the tools. The skill adds what an agent gets wrong without it: - **Conexión con el servidor**: Verifica si el servidor está conectado y tiene sesión iniciada. Si no es así, te indica el paso de configuración para tu cliente. - **Mail o el resto de Adapty**: Los flows, segmentos y perfiles tienen significados distintos en Adapty Mail y en los paywalls in-app de Adapty. El skill aclara cuál de los dos quieres decir antes de hacer cualquier cambio. - **Lo que solo hace el dashboard**: Te indica cuándo una solicitud requiere activar tu dirección de empresa, el checkout web o el envío de datos, y enlaza la página que lo explica. - **El trabajo posterior al lanzamiento**: Resultados, pruebas A/B, entregabilidad y rastreo de un email que no ha llegado. :::note Nada llega a personas reales sin tu confirmación. Guardar un flow inicia el envío, y la lista de segmentos que guarda reemplaza todo el flow, por lo que el skill primero muestra el flow tal como quedará: cada segmento, cada campaña y cualquier elemento eliminado. Las eliminaciones, cancelaciones de suscripción y los lanzamientos y finalizaciones de pruebas A/B también esperan tu confirmación. ::: ## Instalar el skill \{#install-the-skill\} El skill adapty-mail forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener nuevos skills a medida que se publican, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). El skill necesita el servidor MCP de Adapty Mail. Si tu agente aún no lo tiene, pídele que conecte Adapty Mail. El skill te proporciona el comando para tu cliente y tú apruebas la conexión en el dashboard de Adapty Mail. Los pasos para cada cliente están en [Gestionar Adapty Mail con asistentes de IA](mail-mcp#connect-your-assistant). ## Qué puedes preguntar \{#what-you-can-ask\} - "Conecta Adapty Mail y dime qué le falta a mi proyecto." - "Configura un flow de recuperación para usuarios cuya suscripción haya expirado." - "Traduce mi campaña de problemas de facturación al alemán y al español." - "Añade nuestro dominio como dominio de envío y guíame por los registros DNS." - "¿Cuál de mis campañas tuvo mejor conversión el mes pasado?" - "Mi prueba A/B en el flow de usuarios que nunca compraron lleva tres semanas activa. ¿Qué variante debería ganar?" - "¿Por qué esta persona no recibió nuestro correo de trial expirado?" *Ejemplo de conversación con un proyecto de muestra. Tus segmentos y campañas serán diferentes.* **User:** Envía a los usuarios de Alemania la campaña en alemán en mi flow de nunca comprado. **Agent ran:** Se llamaron 6 herramientas de Adapty Mail - `whoami` - `project_list` - `container_list` - `campaign_list` - `segment_meta` - `segment_create` **Agent:** **Creé un segmento de Alemania y lo coloqué por encima de Todos los usuarios en tu flow de Nunca comprado, para que el resto de tus usuarios sigan recibiendo lo que reciben ahora.** Tu flow tiene hoy dos filas: los usuarios de prueba de pago reciben Trial nudge, y todos los demás reciben Welcome series. Alemania va entre ellas. Alguien de Alemania que además sea usuario de prueba de pago seguirá recibiendo Trial nudge, porque las filas se comprueban de arriba a abajo. **Agent asks:** Guardar el flow Never purchased ``` Flow: Never purchased, marketing 1. Paid trial users → Trial nudge (unchanged) 2. Germany → Welcome series DE (new) 3. All users → Welcome series (moved from 2) Removed: nothing ``` Esto se enviará a personas reales en cuanto lo guarde. ¿Guardarlo? 1. Sí, guardar el flow 2. No, cambiar algo primero ## Cómo funciona \{#how-it-works\} 1. **Conectar.** El skill verifica que el servidor MCP responde y que has iniciado sesión. 2. **Orientar.** Encuentra tu proyecto y lee lo que ya está configurado: marca, dirección de la empresa, dominio de envío, campañas, flows y cuándo llegaron los últimos datos. Reutiliza lo que ya existe. 3. **Planificar.** Nombra cualquier cosa que solo tú puedas hacer en el dashboard antes de comenzar. 4. **Actuar.** Trabaja con las herramientas del servidor y te muestra cada resultado. Cualquier acción que envíe correos a personas reales o que no pueda deshacerse espera tu confirmación. ## Por qué no se envió un email \{#why-an-email-didnt-send\} Cuando preguntas por una persona, el skill lee el perfil y el journey de ese perfil, y luego recorre estas razones en orden, deteniéndose en la primera que lo explique: 1. El perfil está suprimido: se ha dado de baja, ha rebotado, ha marcado el correo como spam o no ha superado la verificación de dirección. Consulta [Cancelación de suscripción y supresión](mail-suppression). 2. La persona ya está en otro flow de marketing. 3. La persona compró, por lo que la secuencia finalizó según lo previsto. 4. Los límites de envío retuvieron el correo durante el calentamiento del dominio. 5. El disparador nunca se activó. O bien ningún evento de compra llega a Adapty Mail, o el perfil llegó antes de que se completara la configuración. 6. Ningún segmento del flow coincidió con la persona. 7. El doble opt-in está activado y la persona no ha confirmado la dirección. 8. El correo sigue programado para la ventana de envío en la zona horaria de la persona. *Ejemplo de conversación con un proyecto de muestra. La dirección y las fechas son ficticias.* **User:** ¿Por qué sam@example.com no recibió nuestro correo de prueba expirada? **Agent ran:** Se llamaron 3 herramientas de solo lectura de Adapty Mail - `profile_list` - `profile_get` - `profile_journey` **Agent:** **Esta dirección se dio de baja el 12 de octubre, por lo que Adapty Mail ya no le envía ningún correo de marketing.** La cancelación de la suscripción provino del enlace del segundo correo electrónico de tu campaña Trial expired. El trial expiró el 15 de octubre, por lo que el flow nunca se inició para esta persona. Los correos de confirmación de compra y otros transaccionales siguen llegando a la dirección. Solo el soporte de Adapty puede levantar una supresión, y solo la persona que canceló la suscripción puede solicitarlo. --- # File: configuration --- --- title: "Configurar integración de terceros" description: "Aprende a configurar los ajustes de Adapty para optimizar la gestión de suscripciones." --- Con las integraciones de Adapty, puedes transmitir de forma sencilla eventos de suscripción y datos de compras a tu plataforma o flujo de trabajo preferido. Tanto si buscas información sobre el comportamiento de los usuarios, estrategias de captación de clientes o análisis de producto avanzado para tu equipo de marketing, Adapty puede reenviar sin esfuerzo los eventos de compras in-app a la integración que elijas. Adapty rastrea sin esfuerzo las compras in-app y los eventos de suscripción como pruebas, conversiones, renovaciones y cancelaciones. Estos [eventos](events) se comunican automáticamente a las integraciones que hayas configurado. Esto te permite interactuar con los clientes según su etapa actual y analizar las actividades relacionadas con los ingresos dentro de tu app. ## Configuración de la integración \{#integration-settings\} Las integraciones ofrecen las siguientes opciones de configuración que afectan a todos los eventos enviados a través de esta integración: | 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). Aunque recomendamos usar los nombres de evento predeterminados de Adapty, tienes la libertad de adaptarlos según tus necesidades específicas. ### Configura el temporizador para los eventos sintéticos \{#set-the-timer-for-synthetic-events\} :::warning Mantén el retraso de **Trial still active** más corto que el período de prueba. Con un período de prueba de 7 días y un retraso de 7 días, el período de prueba ya habrá convertido o expirado cuando Adapty lo compruebe, por lo que el evento nunca llegará. ::: Los [eventos sintéticos](events#synthetic-events) llegan según un temporizador, no en respuesta a un cambio. Para configurar el temporizador, junto al interruptor del evento, introduce el retraso en días y horas. Adapty espera ese tiempo y luego envía el evento si el período de prueba o la suscripción sigue activa. El retraso puede ser de 1 hora a 90 días. **Trial still active** empieza en 1 día y **Subscription still active** en 7. Cada integración mantiene su propio retraso, por lo que dos destinos pueden recibir el mismo evento con días de diferencia. --- # File: events --- --- title: "Eventos para enviar a integraciones de terceros" description: "Rastrea eventos clave de suscripción con las herramientas de análisis de Adapty." --- Apple y Google reportan los eventos de suscripción directamente a los servidores de Adapty — a través de [App Store Server Notifications](enable-app-store-server-notifications) y [Real-time Developer Notifications](enable-real-time-developer-notifications-rtdn). Así es como sabes que el usuario convirtió, renovó o canceló su suscripción. Para reenviar estos eventos, configura el destino en Adapty. Las plataformas de terceros — MMPs, analítica, mensajería — tienen cada una su propia página de integración. Tu propio backend recibe los eventos a través del [webhook](webhook). Todos los eventos comparten la misma estructura, aunque los campos dependen del tipo de evento, el store y tu configuración. [Tipos de eventos y campos del webhook](webhook-event-types-and-fields) documenta el payload del webhook, y cada artículo de integración documenta el formato que recibe ese destino. [Estados de los eventos](event-statuses) explica cómo interpretar un estado de entrega. ## Ejemplos \{#examples\} La mayoría de los eventos siguen las compras del usuario desde el primer cargo hasta la expiración. ### El usuario cancela durante el período de prueba _El usuario activó una suscripción mensual el 1 de abril con un período de prueba de 7 días. El día 4, canceló la suscripción._ 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 ### El usuario cancela después de convertir \{#the-user-cancels-after-converting\} _El usuario activó una suscripción mensual el 1 de abril con un período de prueba de 7 días. El décimo día, canceló la suscripción._ 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 [Flows de eventos](event-flows). ## Lista de eventos \{#event-list\} Puedes controlar qué eventos recibe cada integración. La mayoría están disponibles para todas las integraciones, excepto tres: - **Access level updated** solo se activa si la [integración con webhook](webhook) está configurada y el evento está habilitado. Aparece en el [Event Feed](https://app.adapty.io/event-feed) y llega al webhook, pero ninguna otra integración lo recibe. Si la integración con webhook no está configurada o el tipo de evento no está habilitado, Adapty no crea el evento en absoluto y nunca aparece en el Event Feed. - **Trial still active** y **Subscription still active** son [eventos sintéticos](#synthetic-events), y solo algunas integraciones los reciben. La siguiente tabla muestra todos los eventos que Adapty puede enviar a integraciones de terceros: | 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_active | Confirma que la suscripción sigue activa después del evento `subscription_started`. Debes establecer el número de días entre el inicio de la suscripción y esta comprobación. El evento llega si la suscripción sigue activa, incluso cuando el usuario está en un período de gracia. Si la suscripción ha finalizado o el usuario desactivó la renovación automática, no llega nada y Adapty no vuelve a comprobarlo. Este evento solo sigue a las suscripciones compradas sin período de prueba. Una prueba que se convierte envía `trial_converted`, que no inicia la comprobación. | | subscription_renewed | Ocurre cuando se renueva una suscripción y se cobra al usuario. Este evento comienza a partir de la segunda facturación, ya sea en una suscripción con o sin período de prueba. | | subscription_renewal_cancelled | Un 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 sigue 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 mientras mantienen el acceso a las funciones premium. Esta función está disponible a través de la API de Google Play Developer y puede usarse para períodos de prueba gratuitos 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_active | Confirma que la prueba sigue activa después del evento `trial_started`. Debes establecer el número de días entre el inicio de la prueba y esta comprobación. El evento llega si la prueba sigue en curso. Si la prueba ha finalizado o se ha convertido, o el usuario desactivó la renovación automática, no llega nada y Adapty no vuelve a comprobarlo. | | 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 | Un 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 iniciará 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 una prueba finaliza sin convertirse en una suscripción. | | entered_grace_period | Ocurre cuando un intento de pago falla 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. | ## Eventos sintéticos \{#synthetic-events\} La mayoría de los eventos se activan por un cambio: una compra, una renovación, una cancelación. Los eventos **Trial still active** y **Subscription still active** son lo contrario. Tú estableces un temporizador. Cuando expira, Adapty comprueba el estado del trial o la suscripción. Si el trial o la suscripción **no ha terminado**, Adapty lanza el evento correspondiente. Estos eventos sintéticos están disponibles para Adjust, Amplitude, AppMetrica, AppsFlyer, Branch, Facebook Analytics, Firebase, Mixpanel, OneSignal, PostHog, Singular, SplitMetrics y el webhook. Debes activarlos manualmente. Si el usuario desactiva la renovación automática antes de que expire el temporizador, Adapty omite el envío. Cuenta al usuario como perdido, aunque su acceso se mantenga hasta el final del período pagado. Desactivar el evento impide que Adapty inicie nuevos temporizadores. Los temporizadores que ya están en marcha siguen enviando cuando expiran, por lo que un destino puede seguir recibiendo el evento durante el tiempo que dure el retraso que hayas configurado. :::note Adapty Attribution ofrece una función similar para campañas de Meta y TikTok: [Qualified trials](ua-facebook#qualified-trial) retiene un evento de prueba durante 1 a 24 horas. `trial_active` espera de 1 hora a 90 días. Ambos omiten el envío si el usuario ha desactivado la renovación automática. Se configuran en lugares diferentes y no se afectan entre sí. ::: ### La prueba sigue activa \{#the-trial-is-still-running\} - **Integración**: AppsFlyer - **Evento**: Trial still active - **Demora**: 3 días - **Inicio de la prueba**: 1 de abril - **Fin de la prueba**: 7 de abril AppsFlyer recibe: 1. `trial_started` el 1 de abril 2. `trial_active` el 4 de abril 3. `trial_converted` el 7 de abril Ten en cuenta lo siguiente: - Al tratarse de una prueba convertida, nunca recibirás el evento `subscription_active`. - Si la demora se hubiera configurado a 7 días, `trial_active` nunca llegaría. Para ese momento, el usuario ya habría convertido. ### La suscripción sigue activa \{#the-subscription-is-still-running\} - **Integración**: Webhook - **Evento**: Suscripción activa - **Retraso**: 7 días - **Inicio de la suscripción**: 1 de abril, sin período de prueba - **Renovación**: 1 de mayo El webhook recibe: 1. `subscription_started` el 1 de abril 2. `subscription_active` el 8 de abril 3. `subscription_renewed` el 1 de mayo :::note - La suscripción debe comenzar sin prueba. Una prueba convertida envía `trial_converted`, que no inicia el temporizador. - Si el usuario desactivó la renovación automática el 3 de abril, `subscription_active` nunca llega, aunque su acceso siga vigente hasta el 1 de mayo. - La renovación de mayo no inicia un segundo temporizador. Recibes `subscription_active` una sola vez por suscripción. ::: --- # File: event-flows --- --- title: "Flujos de eventos" description: "Descubre esquemas detallados de los flujos de eventos de suscripción en Adapty. Aprende cómo se generan y envían los eventos de suscripción a las integraciones, para hacer seguimiento de los momentos clave en el recorrido de tus clientes." --- Una sola acción puede generar varios eventos. Por ejemplo, una primera compra envía tanto **Subscription started** como **Access level updated**. Los diagramas a continuación muestran qué envía Adapty cuando un cliente se suscribe, cancela o reactiva, y en qué orden. ## Cómo leer los diagramas \{#how-to-read-the-diagrams\} - **Tiempo.** Apple cobra una suscripción varias horas antes de que empiece o se renueve. Mostrar ese margen añadiría un paso a cada diagrama sin cambiar los eventos que recibes, así que los diagramas muestran el cobro y el inicio juntos. - **Orden.** Los eventos de una misma acción llegan en el mismo momento. Un diagrama tiene que representarlos en algún orden, pero tu [Event Feed](event-feed) puede listarlos en un orden diferente, y ninguno es más correcto que el otro. - **Cobertura.** Una notificación del store desencadena todos los eventos mostrados. Los [eventos sintéticos](events#synthetic-events) no aparecen: Adapty los envía mediante un temporizador y el retraso varía según la integración, por lo que no tienen un lugar fijo en una secuencia. ## 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 que el pago no es exitoso 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. ### 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 permanece 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 la suscripción finaliza, se activa el evento **Subscription expired (churned)** para marcar el fin de la suscripción. Si se aprueba un reembolso, el siguiente evento sustituye a **Subscription expired (churned)**: - **Subscription refunded** para finalizar la suscripción y proporcionar los detalles del reembolso En Stripe, una suscripción puede cancelarse de forma inmediata, omitiendo el período restante. En este caso, todos los eventos se crean simultáneamente: - **Subscription renewal cancelled** - **Subscription expired (churned)** - **Access Level updated** para retirar el acceso del usuario Si se aprueba un reembolso, también se activa un evento **Subscription refunded** en el momento de la aprobación. ### 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 ### 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**. ## Flujos de prueba \{#trial-flows\} Si usas períodos de prueba en tu app, recibirás eventos adicionales relacionados con ellos. ### Flow de prueba con conversión exitosa \{#trial-with-successful-conversion-flow\} El flow más común ocurre cuando un usuario inicia una prueba, proporciona una tarjeta de crédito y se convierte exitosamente en una suscripción estándar al final del período de prueba. En esta situación, los siguientes eventos se crean en el momento del inicio de 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. ### Flow 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, los siguientes eventos se crean 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 tendrá acceso hasta el final de la prueba, momento en el que se crea el evento **Trial expired** para marcar su fin. ### 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**. ## 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). Si un usuario degrada la suscripción, la primera suscripción durará hasta el final del período pagado y, cuando finalice, será reemplazada por una 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 la suscripción sea reemplazada de forma efectiva: - 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. ### 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 otorgar 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. ## 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 Sin período de gracia, el Período de Reintento de Facturación (el período en que el store intenta cobrarle al usuario de nuevo) comienza de inmediato. Si el pago nunca se realiza correctamente antes de que finalice el período de gracia, el flujo es el mismo: los mismos eventos se crean cuando el store cancela la suscripción automáticamente: - Evento **Trial expired** o **Subscription expired (churned)** con `cancellation_reason` de `billing_error` - **Nivel de acceso actualizado** para revocar el acceso del usuario ## 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). ### Flujo de transferencia de acceso a un nuevo usuario \{#transfer-access-to-new-user-flow\} La opción recomendada es transferir el nivel de acceso al nuevo usuario. Esto preserva 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 A continuación se detallan 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 realiza una compra de 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 (se envía 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 tanto, solo se creará 1 evento: **Access level updated** para conceder acceso al segundo usuario. A continuación se detallan 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 (se envía 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>. --- # File: event-statuses --- --- title: "Estados de eventos de integración" description: "" --- Adapty determina la entregabilidad basándose en el 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á desactivado 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. Si el evento falta por completo, [Un evento no aparece en el feed](event-feed#an-event-is-missing-from-the-feed) enumera los motivos. --- # File: attribution-integration --- --- title: "Integración de atribución" description: "Integra Adapty con herramientas de atribución para rastrear la adquisición de usuarios y el LTV." --- Adapty puede intercambiar información con servicios de terceros para atribuir eventos de suscripción a campañas de marketing específicas. Este intercambio te permite: * Descubrir qué estrategias de marketing generan más ingresos * Filtrar los [gráficos de suscripción](charts) de Adapty por atribución * Aprovechar las capacidades de servicios de terceros para analizar los datos de suscripción de Adapty Puedes configurarlo de dos maneras: * [La atribución integrada](#integrated-attribution) requiere una configuración mínima y permite que Adapty intercambie datos con 9 plataformas populares. * [La atribución manual](#manual-attribution) requiere que obtengas los datos de atribución de las APIs de servicios de terceros antes de poder enviarlos a Adapty. :::tip Activa [Adapty Attribution](user-acquisition) para obtener una visión completa de la economía de tu aplicación. Adapty Attribution es un dashboard web fácil de configurar que consolida datos de diferentes fuentes para detectar estrategias eficientes de adquisición de usuarios. ::: :::warning Mantén tus datos limpios: evita la duplicación de eventos y los conflictos de atribución. Sigue los consejos de la sección **[Prevenir problemas con los datos](#prevent-data-issues)** para asegurarte de que una nueva fuente de datos no contamine tu analítica. ::: ## Atribución integrada \{#integrated-attribution\} Adapty ofrece integración de atribución lista para usar con 9 servicios populares. Estas plataformas pueden recibir automáticamente [datos de suscripción](events) desde Adapty, procesar cada compra y responder con la atribución correspondiente. Cada plataforma tiene un flujo diferente, pero los pasos son igual de sencillos: 1. **Configura el intercambio automático de datos.** Autoriza a Adapty para que se comunique con la plataforma que elijas. 2. **Integra el SDK de Adapty.** Algunas plataformas requieren código adicional para enviar datos de atribución. 3. **Desactiva otros servicios de compartición de eventos y fuentes de atribución** para evitar [duplicación de eventos](#avoid-event-duplication) y [conflictos de datos](#select-a-single-attribution-source). Consulta la guía específica de cada plataforma para obtener una descripción detallada de la integración: - [Adjust](adjust) - [Airbridge](airbridge) - [Apple Search Ads](apple-search-ads) - [AppsFlyer](appsflyer) - [Asapty](asapty) - [Branch](branch) - [Facebook Ads](facebook-ads) - [Singular](singular) - [Tenjin](tenjin) Adjust, AppsFlyer, Branch y Singular también pueden recibir [eventos sintéticos](events#synthetic-events) — una señal de que una prueba o suscripción ha superado sus primeros días. Para campañas de Meta y TikTok, Adapty Attribution ofrece [Pruebas cualificadas](ua-facebook#qualified-trial) en su lugar. :::note Si quieres que Adapty amplíe la lista, [crea una solicitud de función](https://adapty.featurebase.app/en?b=6979f233ebd3cffd4f425ba0) e indica tu interés en un servicio en particular. ::: ## Atribución manual \{#manual-attribution\} Si Adapty no ofrece [atribución integrada](#integrated-attribution) con el servicio que necesitas, tendrás que escribir tu propio código para intercambiar datos con la fuente de atribución. 1. **Obtén los datos del servicio de atribución**. Usa la API del servicio para solicitar los datos de atribución. 2. **Crea un diccionario con los datos de atribución recibidos.** El diccionario puede contener las siguientes claves: - `status` (`organic`, `non-organic`, o `unknown`) - `channel` - `campaign` - `ad_group` - `ad_set` - `creative` :::important * Todas las claves son opcionales. * Adapty ignora las claves que no están en la lista. * El valor de cada clave puede tener hasta 50 caracteres. ::: **Ejemplo**: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers let attribution = [ "status": "non_organic", "channel": "Google Ads", "campaign": "Christmas Sale", "ad_group": "ad group 1", "ad_set": "ad set 1", "creative": "creative id 1" ] ``` ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)"> ```kotlin showLineNumbers val attribution = mapOf( "status" to "non_organic", "channel" to "Google Ads", "campaign" to "Christmas Sale", "ad_group" to "ad group 1", "ad_set" to "ad set 1", "creative" to "creative id 1" ) ``` </TabItem> <TabItem value="rn" label="React Native (TS)"> ```typescript showLineNumbers const attribution = { status: 'non_organic', channel: 'Google Ads', campaign: 'Christmas Sale', ad_group: 'ad group 1', ad_set: 'ad set 1', creative: 'creative id 1', }; ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)"> ```dart showLineNumbers final attribution = <String, String>{ 'status': 'non_organic', 'channel': 'Google Ads', 'campaign': 'Christmas Sale', 'ad_group': 'ad group 1', 'ad_set': 'ad set 1', 'creative': 'creative id 1', }; ``` </TabItem> <TabItem value="unity" label="Unity (C#)"> Unity recibe los datos de atribución como una cadena JSON serializada, así que construye el diccionario y serializalo: ```csharp showLineNumbers using System.Collections.Generic; using Newtonsoft.Json; var attribution = new Dictionary<string, object> { ["status"] = "non_organic", ["channel"] = "Google Ads", ["campaign"] = "Christmas Sale", ["ad_group"] = "ad group 1", ["ad_set"] = "ad set 1", ["creative"] = "creative id 1" }; var attributionJson = JsonConvert.SerializeObject(attribution); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform"> ```kotlin showLineNumbers val attribution = mapOf( "status" to "non_organic", "channel" to "Google Ads", "campaign" to "Christmas Sale", "ad_group" to "ad group 1", "ad_set" to "ad set 1", "creative" to "creative id 1" ) ``` </TabItem> <TabItem value="capacitor" label="Capacitor"> ```typescript showLineNumbers const attribution = { status: 'non_organic', channel: 'Google Ads', campaign: 'Christmas Sale', ad_group: 'ad group 1', ad_set: 'ad set 1', creative: 'creative id 1', }; ``` </TabItem> </Tabs> 3. **Establece los datos de atribución**: Pasa el diccionario de atribución al método `updateExternalAttribution` (llamado `updateAttribution` antes de la versión 4.1 del SDK). Una vez que Adapty ha guardado el valor de atribución, no es posible sobreescribirlo: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers Adapty.updateExternalAttribution(attribution, provider: .custom) { error in if error == nil { // attribution data accepted for processing } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)"> ```kotlin showLineNumbers Adapty.updateExternalAttribution(attribution, AdaptyExternalAttributionProvider.CUSTOM) { error -> if (error == null) { // attribution data accepted for processing } } ``` </TabItem> <TabItem value="rn" label="React Native (TS)"> ```typescript showLineNumbers await adapty.updateExternalAttribution(attribution, 'custom'); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)"> ```dart showLineNumbers await Adapty().updateExternalAttribution(attribution, provider: AdaptyExternalAttributionProvider.custom); ``` </TabItem> <TabItem value="unity" label="Unity (C#)"> ```csharp showLineNumbers Adapty.UpdateExternalAttribution( attributionJson, AdaptyExternalAttributionProvider.Custom, (error) => { // handle the error }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform"> ```kotlin showLineNumbers Adapty.updateExternalAttribution( attribution, AdaptyExternalAttributionProvider.CUSTOM ).onSuccess { // attribution data accepted for processing }.onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor"> ```typescript showLineNumbers await adapty.updateExternalAttribution({ attribution, provider: 'custom' }); ``` </TabItem> </Tabs> :::info `appliedExternalAttributionProviders` solo reporta atribución de Apple Ads. La atribución de otros proveedores es visible en los perfiles de usuario y está disponible en los filtros de segmento, pero aún no aquí. ::: **Parámetros:** - `attribution` (required): diccionario con datos de atribución. - `provider` (required; named `source` before SDK version 4.1): proveedor de atribución. Usa el valor personalizado si tu proveedor de atribución no admite [atribución integrada](#integrated-attribution) — `.custom` en iOS, `AdaptyExternalAttributionProvider.CUSTOM` en Android y Kotlin Multiplatform, `AdaptyExternalAttributionProvider.Custom` en Unity, `AdaptyExternalAttributionProvider.custom` en Flutter, y la cadena `'custom'` en Capacitor y React Native. Antes de la versión 4.1 del SDK, pasa la cadena `"custom"`. 4. **Deshabilita otros servicios de compartición de eventos y fuentes de atribución** para evitar [la duplicación de eventos](#avoid-event-duplication) y [los conflictos de datos](#select-a-single-attribution-source). ## Prevenir problemas con los datos \{#prevent-data-issues\} ### Selecciona una única fuente de atribución \{#select-a-single-attribution-source\} No habilites la integración de atribución con varias plataformas a la vez. Adapty solo puede aceptar una fuente de atribución a la vez, y únicamente puede modificar la atribución guardada de un perfil durante los primeros 15 minutos tras su creación. Si habilitas varias fuentes de atribución, Adapty seleccionará la fuente con más datos, no necesariamente la mejor. Por ejemplo, la [atribución de Apple Search Ads](apple-search-ads) no orgánica siempre tendrá prioridad en iOS. 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**. Una vez que un perfil tiene atribución, los datos de atribución que llegan a Adapty más de 15 minutos después de la creación del perfil no modifican esa atribución, incluso si tu proveedor de atribución reatribuye la instalación posteriormente. Para mantener la atribución del perfil completa, envía todos los datos de atribución a Adapty en cuanto tu SDK de atribución los devuelva. ### Evita la duplicación de eventos \{#avoid-event-duplication\} Si usas Adapty para compartir datos de suscripción en tiempo real con tus servicios de atribución, **debes desactivar** otros servicios que cumplan la misma función. Si conectaste tu cuenta de Facebook a AppsFlyer, Adjust o Branch, estos reenviarán automáticamente tus eventos a dichos servicios, a menos que lo desactives. Los eventos duplicados pueden distorsionar tus análisis y dificultar la interpretación de los datos. Una vez que hayas configurado el intercambio de eventos en Adapty, desactiva las funcionalidades de reenvío de eventos de terceros. --- # File: adjust --- --- title: "Adjust" description: "Conecta Adjust con Adapty para un mejor seguimiento y análisis de suscripciones." --- [Adjust](https://www.adjust.com/) es una de las principales plataformas de 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 */ }} /> :::warning Adapty envía las [compras de Stripe y Paddle](#web-purchases-stripe-and-paddle) a Adjust con el app token de la plataforma en la que el comprador instaló tu app. Si tienes una app de Android y vendes en la web, rellena el **App Token** de Android. Sin él, la compra web de un comprador de Android fallará con [Missing integration credentials](handle-integration-errors#missing-integration-credentials). ::: ### 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 \{#connect-your-app-to-adjust\} Una vez completados los pasos anteriores, añade los dos métodos siguientes a tu app. Estos 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 `setIntegrationIdentifier()` del SDK 2. **Para recibir datos de atribución desde Adjust**: Actualiza los datos de atribución con el método `updateExternalAttribution()` del SDK (llamado `updateAttribution()` en versiones anteriores a la 4.1 del SDK) 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.setIntegrationIdentifier(.adjustDeviceId(adid)) } } func updateAdjustAttribution() { Adjust.attribution { attribution in guard let attribution = attribution?.dictionary() else { return } Adapty.updateExternalAttribution(attribution, provider: .adjust) } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adjust.getAdid { adid -> if (adid == null) return@getAdid Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.adjustDeviceId(adid)) { error -> if (error != null) { // handle the error } } } Adjust.getAttribution { attribution -> val attributionJson = attribution?.jsonResponse ?: return@getAttribution Adapty.updateExternalAttribution(attributionJson, AdaptyExternalAttributionProvider.ADJUST) { error -> // handle the error } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adjust.getAdid(adid -> { if (adid == null) return; Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.adjustDeviceId(adid), error -> { if (error != null) { // handle the error } }); }); Adjust.getAttribution(attribution -> { if (attribution == null || attribution.jsonResponse == null) return; Adapty.updateExternalAttribution(attribution.jsonResponse, AdaptyExternalAttributionProvider.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.updateExternalAttribution(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().updateExternalAttribution(attribution, provider: AdaptyExternalAttributionProvider.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.UpdateExternalAttribution(attributionJsonString, AdaptyExternalAttributionProvider.Adjust, (error) => { if (error != null) { // handle the error } }); } ``` </TabItem> </Tabs> ## Compras web (Stripe y Paddle) \{#web-purchases-stripe-and-paddle\} Adjust recibe compras de [Stripe](stripe) y [Paddle](paddle) junto con las compras de App Store y Google Play. Una compra web ocurre fuera de la app, por lo que no tiene una plataforma móvil propia. Adapty utiliza en su lugar la plataforma del comprador. Cuando el perfil de Adapty del usuario se creó en App Store o Google Play, la compra web se envía a Adjust como si procediera de esa store: | Store del perfil del comprador | Qué envía Adapty con la compra | |:-------------------------------|:-------------------------------| | App Store | Tu **App Token** y **OAuth Token** de iOS, más el `idfa` y el `idfv` del comprador. | | Google Play | Tu **App Token** y **OAuth Token** de Android, más el `android_id` y el `gps_adid` del comprador. | La compra llega a Adjust asociada al mismo dispositivo que la instalación, de modo que Adjust puede atribuirla a la campaña que captó al usuario. La página de integración de Adjust no tiene campos separados para Stripe ni Paddle. Las compras web reutilizan las credenciales de iOS y Android de [Conectar Adapty con Adjust](#connect-adapty-to-adjust). Un comprador que nunca abrió tu app móvil no tiene un ID de dispositivo de Adjust, y su perfil se creó en Stripe o Paddle. Adapty envía su compra a Adjust con tu **App Token** de iOS y sin ID de publicidad. Adjust no tiene entonces ningún identificador que vincule la compra con una instalación. ## 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. ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # File: airbridge --- --- title: "Airbridge" description: "Conecta Adapty con Airbridge para rastrear insights 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 Identity Resolution Engine de Airbridge, puedes combinar datos de identidad de clientes dispersos procedentes de interacciones web y de aplicaciones en una identidad unificada basada en personas, lo que se traduce en una atribución más precisa. 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 cómo se comportan 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 Airbridge funciona de dos maneras principales. 1. **Recibir datos de atribución de Airbridge** Una vez que hayas configurado la integración con Airbridge, Adapty comenzará a recibir datos de atribución de Airbridge. Puedes acceder y consultar fácilmente estos datos en la página del usuario. 2. **Enviar eventos de suscripción a Airbridge** Adapty puede enviar a Airbridge todos los eventos de suscripción configurados en tu integración. De este modo, podrás hacer seguimiento de estos eventos desde el dashboard de Airbridge. 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 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(AdaptyIntegrationIdentifier.airbridgeDeviceId(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 de dispositivo de 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 for Advertisers. | | `device.ifv` | String | **Solo iOS**. ID for Vendors. | | `device.gaid` | String | **Solo Android**. Google Advertising ID. | | `app` | Object | Información de la app. | | `app.packageName` | String | El nombre de 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 | Timestamp del evento en milisegundos. | | `eventData` | Object | Detalles del evento. | | `eventData.goal.category` | String | La categoría de evento de 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 moneda (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 es 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). | ## Solución de problemas \{#troubleshooting\} ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # 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 */ }} /> ### Conecta tu app a la red AdServices \{#connect-your-app-to-the-adservices-network\} Una vez que completes [la configuración del framework AdServices](#connect-adapty-to-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 no quieres este comportamiento, *desactiva* la atribución de ASA siguiendo las instrucciones que se muestran 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. ::: El interruptor controla si Adapty utiliza la atribución de Apple Ads, no si el SDK envía el token de AdServices. El SDK envía el token una vez por perfil, en su primera sincronización, y no dispone de ninguna opción para desactivar esto. Según Apple, [AdServices no realiza seguimiento](https://ads.apple.com/app-store/help/attribution/0028-measuring-ad-performance) según la definición de App Tracking Transparency. ## 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: appsflyer --- --- title: "AppsFlyer" description: "Envía eventos de suscripción de Adapty a AppsFlyer y recibe datos de atribución de AppsFlyer en Adapty." --- Adapty intercambia datos con [AppsFlyer](https://www.appsflyer.com/) en ambas direcciones. AppsFlyer le indica a Adapty qué campaña trajo a cada usuario. Adapty le indica a AppsFlyer cuánto pagó ese usuario — compras, renovaciones, pruebas y reembolsos, con los ingresos y los detalles del producto incluidos. - **Ver todo el ciclo de vida de la suscripción, no solo la primera compra.** Las renovaciones y conversiones de prueba son eventos del store, sin sesión de app para que el SDK del cliente de AppsFlyer los registre. Adapty recibe los eventos de suscripción del lado del servidor y los reenvía a AppsFlyer, de modo que las cifras de campaña siguen actualizándose mucho después de la instalación inicial. Los reembolsos siguen el mismo camino, y AppsFlyer los deduce de los ingresos de campaña. - **Optimiza tus campañas publicitarias con datos de eventos de suscripción.** AppsFlyer retransmite los eventos in-app de Adapty a tus redes publicitarias como *postbacks*. Los servicios que gestionan tu presupuesto publicitario pueden optimizar en función de lo que los usuarios realmente pagaron. - **Filtra los análisis de Adapty por campaña.** Adapty guarda la atribución de AppsFlyer en cada perfil, hasta el conjunto de anuncios y el creativo, y los [gráficos de suscripción](charts) pueden filtrarse por ella. - **Muestra un paywall diferente por campaña.** Los [segmentos](segments) de Adapty filtran por los mismos campos de atribución: campaña, conjunto de anuncios y creativo. Usa un segmento como [audiencia](audience) para asociar el paywall al anuncio que trajo al usuario. :::tip Tu cuenta de Adapty ya incluye dos herramientas para campañas de pago. [Adapty Ads Manager](adapty-ads-manager) gestiona tus campañas de Apple Ads; [Adapty Attribution](adapty-user-acquisition) cubre Meta Ads y TikTok. Ambas reportan ROAS y LTV directamente desde tus datos de compras de Adapty, y las dos son gratuitas para empezar — consulta los [precios](https://adapty.io/pricing). ::: ## Cómo funciona la integración \{#how-the-integration-works\} Adapty recibe datos de atribución de AppsFlyer y le envía eventos de suscripción. Ambos dependen de un único valor: el **AppsFlyer ID**, una cadena que AppsFlyer genera cuando tu app se lanza por primera vez. 1. Cuando el usuario instala tu app, el SDK de AppsFlyer le asigna un ID único. 2. Tu app pasa ese ID a Adapty, que lo almacena en el perfil del usuario como `appsflyer_id`. 3. Tu app también pasa los datos de atribución de AppsFlyer a Adapty, que los guarda en el mismo perfil. 4. Más tarde, cuando el usuario desencadena un evento de suscripción —por ejemplo, inicia una prueba o compra un producto—, los servidores de Adapty envían el evento a la API S2S de AppsFlyer con el mismo `appsflyer_id`. 5. AppsFlyer relaciona el ID con la instalación que ya había atribuido, de modo que la compra hereda la campaña y la fuente de medios de esa instalación. ## Instrucciones de configuración \{#setup-instructions\} Antes de comenzar: - **Confirma que tu plan de AppsFlyer admite eventos S2S in-app.** El paquete de nivel básico **Zero** de AppsFlyer no lo permite: su API rechaza todos los eventos que Adapty envía con `403 Forbidden`. - **Incluye el SDK de AppsFlyer en tu app.** El ID de AppsFlyer del que depende la integración solo existe una vez que ese SDK se inicializa. Una integración solo del lado del servidor no generará el ID. - **Desactiva todas las demás integraciones de atribución.** Adapty acepta una sola fuente de atribución por perfil y no puede sobrescribir un valor existente. En iOS, la [atribución de Apple Ads](apple-search-ads) no orgánica siempre tiene prioridad; consulta [Seleccionar una única fuente de atribución](attribution-integration#select-a-single-attribution-source). ### Crear un token S2S en AppsFlyer \{#create-an-s2s-token-in-appsflyer\} Adapty se autentica con la API S2S de AppsFlyer mediante un token que tú creas. Solo los administradores de AppsFlyer pueden abrir la página **Tokens**, así que pide a uno que cree el token si tu cuenta no es de administrador. Salta a [Configurar Adapty](#configure-adapty) si ya tienes uno. 1. Inicia sesión en [AppsFlyer](https://www.appsflyer.com/home). 2. Haz clic en tu nombre de cuenta en la esquina superior derecha y abre el **Security center**. 3. En la página **Manage your account security**, busca la tarjeta **AppsFlyer API and S2S tokens** y haz clic en **Manage your AppsFlyer tokens**. Se abrirá la página **Tokens**. 4. Haz clic en **New token**. 5. Introduce un **Name** para el token. El nombre es solo para tu referencia y puedes cambiarlo más adelante. 6. Selecciona el tipo de token **S2S**. Cualquier otro tipo romperá la integración. 7. Haz clic en **Create new token**. :::note AppsFlyer permite dos tokens por tipo. Si tu cuenta ya tiene dos tokens S2S, reutiliza uno de ellos. ::: 8. Busca tu nuevo token en la lista. AppsFlyer enmascara el valor, así que haz clic en el icono de copia de la columna **Token** para obtenerlo. ### Configurar Adapty 1. Abre [**Integrations** > **AppsFlyer**](https://app.adapty.io/integrations/appsflyer) en el Adapty Dashboard. 2. Activa el interruptor de **AppsFlyer**. 3. Si ya conectaste tu app a la App Store, el campo **iOS App ID** se rellena automáticamente con el Apple ID numérico de tu app. Si está vacío, [conecta primero tu cuenta de App Store](app-store-connection-configuration#step-1-provide-bundle-id-and-apple-app-id). Android no requiere un campo equivalente. 4. Pega el token S2S en el campo **Production** de **S2S key for iOS**, **S2S key for Android**, o en ambos. 5. Rellena los campos de **Sandbox** para que las compras de prueba no aparezcan en tus datos de producción; consulta [Mantener los datos de sandbox fuera de producción](#keep-sandbox-data-out-of-production). 6. En **How the revenue data should be send**, elige qué cifra de ingresos envía Adapty como `af_revenue`. Las tres opciones se corresponden con las [vistas de ingresos de Adapty Analytics](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue), por lo que tu elección determina también con qué vista deberían coincidir los datos de AppsFlyer. | Opción | Qué envía Adapty | | --- | --- | | **Gross revenue** | El importe total que pagó el comprador, antes de comisiones e impuestos. Es el valor predeterminado. | | **Proceeds after store commission** | El importe menos la comisión del store, pero con impuestos incluidos. | | **Proceeds after store commission and taxes** | El importe menos ambos conceptos. | 7. Configura las opciones restantes: | Toggle | When on | Default | | --- | --- | --- | | **Report user's currency** | Adapty reporta cada venta en la divisa en la que pagó el comprador, en lugar de USD. | Off | | **Send trial price** | Los inicios de prueba no llevan ingresos por defecto. Activa esto para asignarles un precio de referencia; aparecerá el campo **Trial price percentage** — indícale qué porcentaje del precio de la suscripción debe reportar la prueba. Con `60%`, una suscripción de $10 envía $6. | Off | | **Exclude historical events** | Adapty omite los eventos que ocurrieron antes de que el usuario instalara una compilación que contenía el SDK de Adapty. | **On** | | **Delay events with a future datetime** | Apple reporta las renovaciones y conversiones de prueba con antelación, por lo que estos eventos llevan una fecha futura. AppsFlyer normalmente reemplaza esa fecha por el día en que llega el evento. Activa esto para retener cada evento hasta su fecha — consulta [Las renovaciones caen en el día incorrecto](#renewals-land-on-the-wrong-day). | Off | 8. Renombra o deshabilita eventos individuales en la sección **Events names** — consulta [Nombres de eventos](#event-names). 9. Haz clic en **Save**. ### Mantén los datos de sandbox fuera de producción \{#keep-sandbox-data-out-of-production\} Para que las compras de prueba no contaminen tus datos reales, envíalas a una app de AppsFlyer independiente. Registra una segunda app para tus builds de desarrollo y pega su token en el campo **Sandbox** de cada sección de plataforma. Adapty enruta cada transacción según el entorno: las compras reales van a la app cuyo token está en **Production**, y las compras de prueba, a la app en **Sandbox**. Si prefieres registrarlo todo en una sola app, pega el mismo token en ambos campos. Las compras realizadas durante la revisión de App Store y en TestFlight son transacciones de sandbox, aunque se ejecuten en una compilación de producción. Adapty envía estas compras con la clave **Sandbox**. :::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. ::: ### Configura el código de tu app \{#configure-your-app-code\} 1. Registra un callback de conversión con el SDK de AppsFlyer. En iOS, implementa el protocolo `AppsFlyerLibDelegate`; en Android, la interfaz `AppsFlyerConversionListener`; en Unity, la interfaz `IAppsFlyerConversionData`. En React Native y Flutter, pasa un handler al método `onInstallConversionData` en su lugar. 2. Espera a que AppsFlyer invoque ese callback. AppsFlyer atribuye cada instalación en sus propios servidores, por lo que el resultado llega a tu app de forma asíncrona y no en el momento del inicio. El SDK vuelve a invocar el callback en cada sesión posterior. 3. Dentro del callback, lee el ID de AppsFlyer del usuario con `getAppsFlyerUID` y pásalo a Adapty con `setIntegrationIdentifier()`. Los eventos de Adapty solo llegan al usuario correcto en AppsFlyer si se incluye este valor. 4. Dentro del mismo callback, pasa los datos de atribución de AppsFlyer a Adapty con `updateExternalAttribution()`. Esto le indica a Adapty qué campaña generó la instalación. El método se llamaba `updateAttribution()` antes de la versión 4.1 del SDK. 5. Usa `await` con `Adapty.identify()` en lugar de ejecutarlo en paralelo con los pasos 3 y 4. Adapty crea un perfil anónimo al activarse y luego cambia al perfil identificado cuando `identify()` se resuelve. Un `appsflyer_id` establecido durante ese cambio no siempre se conserva. Para la secuencia completa, consulta el orden de llamadas en los SDKs de [iOS](ios-sdk-call-order), [Android](android-sdk-call-order), [React Native](react-native-sdk-call-order), [Flutter](flutter-sdk-call-order), [Unity](unity-sdk-call-order), [Capacitor](capacitor-sdk-call-order) y [Kotlin Multiplatform](kmp-sdk-call-order). :::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 class YourAppsFlyerLibDelegateImplementation { // Find your implementation of AppsFlyerLibDelegate // and update onConversionDataSuccess method: func onConversionDataSuccess(_ conversionInfo: [AnyHashable : Any]) { let uid = AppsFlyerLib.shared().getAppsFlyerUID() Adapty.setIntegrationIdentifier(.appsflyerId(uid)) Adapty.updateExternalAttribution(conversionInfo, provider: .appsflyer) } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)"> ```kotlin showLineNumbers val conversionListener: AppsFlyerConversionListener = object : AppsFlyerConversionListener { override fun onConversionDataSuccess(conversionData: Map<String, Any>) { val uid = AppsFlyerLib.getInstance().getAppsFlyerUID(context) Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.appsflyerId(uid)) { error -> if (error != null) { // handle the error } } Adapty.updateExternalAttribution(conversionData, AdaptyExternalAttributionProvider.APPSFLYER) { error -> if (error != null) { //handle the error } } } } ``` </TabItem> <TabItem value="rn" label="React Native (TS)"> ```typescript showLineNumbers appsFlyer.onInstallConversionData(installData => { appsFlyer.getAppsFlyerUID(async (error, appsFlyerUID) => { if (error) { // handle the error return; } try { await adapty.setIntegrationIdentifier('appsflyer_id', appsFlyerUID); await adapty.updateExternalAttribution(installData, 'appsflyer'); } catch (error) { // handle `AdaptyError` } }); }); // ... appsFlyer.initSdk(/*...*/); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)"> ```dart showLineNumbers AppsflyerSdk appsflyerSdk = AppsflyerSdk(<YOUR_OPTIONS>); appsflyerSdk.onInstallConversionData((data) async { try { final appsFlyerUID = await appsFlyerSdk.getAppsFlyerUID(); await Adapty().setIntegrationIdentifier( key: "appsflyer_id", value: appsFlyerUID, ); await Adapty().updateExternalAttribution(data, provider: AdaptyExternalAttributionProvider.appsflyer); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } }); appsflyerSdk.initSdk( registerConversionDataCallback: true, registerOnAppOpenAttributionCallback: true, registerOnDeepLinkingCallback: true, ); ``` </TabItem> <TabItem value="unity" label="Unity (C#)"> ```csharp showLineNumbers using AdaptySDK; using AppsFlyerSDK; // before SDK initialization AppsFlyer.getConversionData(this.name); // in your IAppsFlyerConversionData void onConversionDataSuccess(string conversionData) { string appsFlyerId = AppsFlyer.getAppsFlyerId(); Adapty.SetIntegrationIdentifier( "appsflyer_id", appsFlyerId, (error) => { // handle the error }); Adapty.UpdateExternalAttribution( conversionData, AdaptyExternalAttributionProvider.Appsflyer, (error) => { // handle the error }); } ``` </TabItem> </Tabs> ### Verificar la integración \{#verify-the-integration\} 1. Activa una compra en sandbox y abre el [**Event Feed**](event-feed) de tu app. Ahí aparece cada intento de envío. Para leer la respuesta de AppsFlyer a un intento fallido, pasa el cursor sobre esa fila. 2. En AppsFlyer, ve a **Settings** > **SDK Integration Tests** > **Live Events** y selecciona tu dispositivo de prueba. Live Events muestra los eventos S2S a medida que llegan, mucho antes de que aparezcan en el dashboard de **Activity** de AppsFlyer. 3. Abre el dashboard de **Activity** de tu app y confirma el evento, sus ingresos y su fuente de medios. Espera aproximadamente una hora: los eventos S2S no llegan a este dashboard de inmediato. :::note Los eventos de Adapty nunca aparecen en el registro de depuración del SDK de AppsFlyer de tu app. Adapty los envía desde sus propios servidores, por lo que nunca pasan por tu app. Un registro local vacío no dice nada sobre la integración. ::: ## Estructura de eventos de AppsFlyer \{#appsflyer-event-structure\} Adapty envía una solicitud POST por evento a `https://api3.appsflyer.com/inappevent/{app_id}`, con el token S2S en el encabezado `authentication`. La API 2 usa `https://api2.appsflyer.com/inappevent/{app_id}`. ```json showLineNumbers { "appsflyer_id": "1699887556000-6192770", "eventName": "af_subscribe", "eventTime": "2026-03-01 12:00:00", "eventValue": "{\"af_content_id\":\"yearly.premium.6999\",\"af_order_id\":\"GPA.3383-4699-1373-07113\",\"store_country\":\"US\",\"profile_country\":\"US\",\"af_content_type\":\"in_app\",\"af_revenue\":\"9.9900\",\"af_currency\":\"USD\",\"af_quantity\":\"1\"}", "os": "17.0.1", "bundleIdentifier": "com.example.app", "customer_user_id": "user_12345", "eventCurrency": "USD", "ip": "192.168.100.1", "advertising_id": "00000000-0000-0000-0000-000000000000", "idfa": "00000000-0000-0000-0000-000000000000", "idfv": "00000000-0000-0000-0000-000000000000", "att": "3" } ``` | Parámetro | Tipo | Descripción | |:---|:---|:---| | `appsflyer_id` | String | El ID de AppsFlyer que tu app pasó a `setIntegrationIdentifier`. AppsFlyer relaciona el evento con una instalación mediante este valor. | | `eventName` | String | El nombre de la sección **Events names** — consulta [Nombres de eventos](#event-names). | | `eventTime` | String | Cuándo ocurrió el evento (UTC, `YYYY-MM-DD HH:MM:SS`). Adapty lo reemplaza con la hora actual para eventos de más de 26 horas — consulta [Eventos antiguos llegan con la fecha de hoy](#old-events-arrive-with-todays-date). | | `eventValue` | String | Una cadena codificada en JSON con los campos de la tabla siguiente. | | `os` | String | La versión del sistema operativo del dispositivo del usuario. | | `bundleIdentifier` | String | El bundle ID de la app en iOS, o el nombre del paquete en Android. | | `customer_user_id` | String | El Customer User ID del usuario. | | `eventCurrency` | String | El código de moneda ISO 4217, por ejemplo `USD`. | | `ip` | String | La dirección IP del usuario. | | `advertising_id` | String | **Solo Android.** El Google Advertising ID. | | `idfa` | String | **Solo iOS.** El ID para Anunciantes. | | `idfv` | String | **Solo iOS.** El ID para Proveedores. | | `att` | String | **Solo iOS.** El estado de App Tracking Transparency, de `0` a `3`. Adapty envía `0` cuando no tiene ningún valor. | `eventValue` lleva la compra en sí. Los últimos cuatro parámetros aparecen solo en los eventos que generan ingresos: | Parámetro | Tipo | Descripción | |:---|:---|:---| | `af_content_id` | String | El ID del producto en el store. | | `af_order_id` | String | El ID de transacción original. | | `store_country` | String | El país de la cuenta del store del usuario. | | `profile_country` | String | El país que Adapty derivó de la dirección IP del usuario. | | `af_content_type` | String | Siempre `in_app`. | | `af_revenue` | String | El importe de ingresos, con 4 decimales. Negativo en reembolsos. | | `af_currency` | String | La divisa de `af_revenue`. | | `af_quantity` | String | Siempre `1`. | ### Nombres de eventos \{#event-names\} Por defecto, Adapty asigna sus eventos de ingresos a los [nombres de eventos estándar de AppsFlyer](https://support.appsflyer.com/hc/en-us/articles/115005544169-In-app-events-Overview), en lugar de enviarlos como eventos personalizados con sus propios nombres. Esto es importante si reenvías eventos a redes publicitarias: una red actúa sobre los nombres estándar que ya reconoce, por lo que te ahorras crear un mapeo para cada uno. | Evento de Adapty | Nombre predeterminado en AppsFlyer | | --- | --- | | Subscription started | `af_subscribe` | | Subscription renewed | `af_subscribe` | | Trial converted | `af_subscribe` | | Trial started | `af_start_trial` | | Non-subscription purchase | `af_purchase` | El resto de eventos de Adapty conservan su propio nombre, por ejemplo `subscription_refunded`. En la sección **Events names** de la [página de integración de AppsFlyer](https://app.adapty.io/integrations/appsflyer), puedes renombrar cualquier evento o desactivar los que no necesites. Para ver la lista completa de lo que Adapty puede enviar, consulta [Eventos](events). ## Limitaciones \{#limitations\} - **Un perfil sin `appsflyer_id` no genera ningún evento.** AppsFlyer asocia un evento con la instalación que generó el ID y lee la campaña a partir de esa instalación. Sin él, Adapty no envía nada, y el Event Feed marca estos perfiles — consulta [Los eventos no llegan a AppsFlyer](#events-dont-reach-appsflyer). - **Sin relleno histórico.** Adapty reenvía eventos desde el momento en que activas la integración. Las compras pasadas nunca llegarán a AppsFlyer. - **Los detalles del dispositivo quedan vacíos en los datos sin procesar de AppsFlyer.** Un evento S2S solo contiene lo que incluye la solicitud. El modelo del dispositivo, la categoría del dispositivo, el idioma, el operador, el WIFI, la versión de la app y el nombre de la app no tienen parámetro S2S, por lo que nada enviado de esta forma puede completarlos. ## Solución de problemas \{#troubleshooting\} - [Los eventos no llegan a AppsFlyer](#events-dont-reach-appsflyer) - [Las compras aparecen como orgánicas](#purchases-show-up-as-organic) - [Los eventos antiguos llegan con la fecha de hoy](#old-events-arrive-with-todays-date) - [Las renovaciones caen en el día equivocado](#renewals-land-on-the-wrong-day) - [Los ingresos en AppsFlyer no coinciden con Adapty Analytics](#revenue-in-appsflyer-doesnt-match-adapty-analytics) - [`Failed to authenticate` en el Event Feed](#failed-to-authenticate-in-the-event-feed) - [`access_level_updated` aparece como fallido en el Event Feed](#access_level_updated-shows-as-failed-in-the-event-feed) #### Los eventos no llegan a AppsFlyer \{#events-dont-reach-appsflyer\} Abre primero el [**Event Feed**](event-feed). Una entrega fallida muestra el error que devolvió AppsFlyer. Estas causas explican la mayoría de los casos: - **El perfil no tiene `appsflyer_id`.** Confirma que tu app llama a `getAppsFlyerUID` y pasa el resultado a `setIntegrationIdentifier` en cada plataforma en la que publicas — consulta [Configura el código de tu app](#configure-your-app-code). - **Falta el ID de la app para esa plataforma.** AppsFlyer identifica cada app por su ID, por lo que Adapty no envía nada si no hay uno. Los eventos de iOS necesitan el **iOS App ID** en la página de integración; los eventos de Android necesitan el nombre del paquete en **App settings** > **Android SDK** — consulta [Configura Adapty](#configure-adapty). - **La compra es una transacción sandbox y la clave Sandbox está vacía.** Consulta [Mantén los datos de sandbox fuera de producción](#keep-sandbox-data-out-of-production). - **El evento está desactivado** en la sección **Events names**. Una entrega correcta no garantiza que AppsFlyer conserve el evento. AppsFlyer devuelve `200 OK` para cualquier solicitud bien formada y luego descarta los eventos cuyo `appsflyer_id` no coincide con ninguna instalación real. #### Las compras aparecen como orgánicas \{#purchases-show-up-as-organic\} Cada `appsflyer_id` identifica una instalación, y todos los eventos que llevan ese ID heredan la atribución de esa instalación: la campaña que la generó, o ninguna si la instalación fue orgánica. AppsFlyer necesita entre 20 y 30 segundos o más para atribuir una nueva instalación. Un evento que llega antes de ese tiempo no tiene atribución que heredar, por lo que AppsFlyer lo marca como orgánico no atribuido. Espera que las primeras compras tras el lanzamiento aparezcan como orgánicas — pruebas que comienzan segundos después de abrir la app. Para evitarlo, retrasa el paywall el tiempo suficiente para que AppsFlyer termine de procesar la instalación. Si todos los eventos aparecen como orgánicos, no solo las primeras compras tras el lanzamiento, el problema es de atribución y no de tiempo: otra fuente reclamó el perfil primero. Consulta [Seleccionar una única fuente de atribución](attribution-integration#select-a-single-attribution-source). #### Los eventos antiguos llegan con la fecha de hoy \{#old-events-arrive-with-todays-date\} Los eventos pueden llegar a Adapty con retraso por dos motivos: - Cuando las App Store Server Notifications muestran **Delayed** en App Store Connect, Apple pone sus notificaciones en cola. Una renovación puede llegar a Adapty mucho después de que se haya producido — consulta [App Store Server Notifications muestran "Delayed"](troubleshoot-app-store-integration#app-store-server-notifications-show-delayed). - Los eventos con fecha retroactiva se procesan siempre que **Exclude historical events** esté desactivado. AppsFlyer no acepta marcas de tiempo antiguas como estas. Para garantizar un manejo coherente de los eventos, Adapty reemplaza `eventTime` con la hora actual para cualquier evento de más de 26 horas de antigüedad. No es posible desactivar esta reescritura. #### Las renovaciones aparecen en el día incorrecto \{#renewals-land-on-the-wrong-day\} Apple notifica a Adapty las renovaciones y conversiones de prueba *antes* de que ocurran, y Adapty las reenvía de inmediato con el `eventTime` futuro sin modificar. AppsFlyer conserva una marca de tiempo futura solo cuando corresponde al día de llegada: si una renovación tiene fecha de mañana, se registra con la hora de llegada de hoy. Activa **Delay events with a future datetime** en los [ajustes de integración](#configure-adapty). Adapty retiene cada evento hasta que llegue su momento, de modo que AppsFlyer registre la fecha que Apple anunció. Consulta [Marcas de tiempo de eventos con fechas futuras](how-profiles-work#event-timestamps-with-future-dates-appleios). #### Los ingresos en AppsFlyer no coinciden con Adapty Analytics \{#revenue-in-appsflyer-doesnt-match-adapty-analytics\} Adapty y AppsFlyer contabilizan las mismas compras de forma distinta. Estas diferencias explican casi cualquier discrepancia. - **El dashboard Overview de AppsFlyer agrupa los ingresos por fecha de instalación; los gráficos de Adapty los agrupan por fecha de evento.** En AppsFlyer, una renovación de julio proveniente de una instalación de enero cuenta para enero. Compara ese dashboard con el [análisis de cohortes de Adapty](analytics-cohorts), que también agrupa los ingresos por mes de instalación. Los informes de datos en bruto de AppsFlyer agrupan por fecha de evento, así que compáralos con los gráficos de Adapty. - **Las compras de perfiles sin `appsflyer_id` nunca llegan a AppsFlyer.** Se quedan en Adapty Analytics. Consulta [Los eventos no llegan a AppsFlyer](#events-dont-reach-appsflyer). - **AppsFlyer solo muestra la cifra de ingresos que seleccionaste en Adapty.** El ajuste **How the revenue data should be send** decide si Adapty envía ingresos brutos, recaudación neta o ingresos netos. Comparar eso con otra vista de Adapty Analytics genera una diferencia igual a la comisión, el impuesto o ambos. - **Adapty usa la zona horaria de informes de tu app; AppsFlyer recibe UTC.** Las integraciones siempre reciben marcas de tiempo en UTC, independientemente de lo que configures en [App Settings](general#3-reporting-timezone). Una compra a las 23:30 UTC del 1 de julio aparece el 2 de julio en Adapty si tu zona horaria de informes es +02:00. - **A AppsFlyer le faltan eventos históricos.** Hay dos causas probables. Con **Exclude historical events** activado, el historial de AppsFlyer de un usuario comienza en su primer lanzamiento de una versión con Adapty. Además, Adapty nunca rellena de forma retroactiva los eventos que procesó antes de que habilitaras la integración. - **Las compras en sandbox llegan a la app de AppsFlyer identificada por la clave Sandbox.** Si esa es la app de producción, las compras de prueba inflan sus ingresos. Consulta [Mantén los datos de sandbox fuera de producción](#keep-sandbox-data-out-of-production). - **Los eventos desactivados en la integración nunca llegan a AppsFlyer.** Adapty Analytics sigue contándolos. Revisa la sección **Events names**: desactivar `subscription_renewed` elimina la mayor parte de los ingresos de cualquier app consolidada. #### `Failed to authenticate` en el Event Feed AppsFlyer rechaza las credenciales cuando no coinciden con la versión de la API. La API 2 espera una Dev key; la API 3 espera un token S2S. Cambiar la versión sin reemplazar la clave genera este error en cada evento. Crea un nuevo token en el [AppsFlyer Security Center](https://hq1.appsflyer.com/security-center/api-tokens) y pégalo en los campos **S2S key**, o sigue [Switch from AppsFlyer S2S API 2 to 3](switch-from-appsflyer-s2s-api-2-to-3). #### `access_level_updated` aparece como fallido en el Event Feed `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # 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 específica no añade datos de atribución a Adapty, ya que obtenemos todo lo que necesitamos 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\} ### Los eventos no llegan a Adapty \{#events-dont-reach-asapty\} - 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 ASA detallada y no orgánica enviarán sus eventos a Asapty. Verás "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 una configuración correcta, asegúrate de que el toggle **Receive Apple Search Ads attribution in Adapty** esté habilitado en la pestaña [**App Settings** -> **Apple Search Ads**](https://app.adapty.io/settings/apple-search-ads). ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # 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 aplicación a 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 { try await Adapty.setIntegrationIdentifier(.branchId(<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.updateExternalAttribution(data.toString(), AdaptyExternalAttributionProvider.BRANCH) { error -> if (error != null) { //handle the error } } } } // logout Branch.getAutoInstance(context).logout() ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; FlutterBranchSdk.setIdentity('YOUR_USER_ID'); 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 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 `.updateExternalAttribution()` (llamado `.updateAttribution()` antes de la versión 4.1 del SDK) 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.updateExternalAttribution(data, provider: .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> ```dart showLineNumbers import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; FlutterBranchSdk.listSession().listen((data) async { try { await Adapty().updateExternalAttribution( data, provider: AdaptyExternalAttributionProvider.branch, ); } 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.UpdateExternalAttribution( attributionString, AdaptyExternalAttributionProvider.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.updateExternalAttribution(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 Advertisers. | | `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 los ingresos. | | `event_data.currency` | String | Código de moneda (p. ej., "USD"). | | `custom_data` | Object | Atributos detallados del evento (contiene todos los [campos del evento](webhook-event-types-and-fields#for-most-event-types) disponibles). | ## Solución de problemas \{#troubleshooting\} ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # File: facebook-ads --- --- title: "Facebook Ads" description: "Integra Facebook Ads con Adapty para una marketing de suscripciones eficaz." --- Con la integración de Facebook Ads, puedes consultar fácilmente las estadísticas de tu app en Meta Analytics. Adapty envía eventos a Meta Ads Manager, lo que te permite crear audiencias similares basadas en suscripciones para obtener mejores resultados. Así puedes ver con precisión cuánto dinero generan tus anuncios a partir de las suscripciones. La integración entre Adapty y Facebook Ads funciona de la siguiente manera: Adapty envía a Facebook Ads todos los eventos de suscripción que hayas configurado en tu integración. Esta integración es ú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\} Adapty mapea sus eventos de compra a los eventos estándar de Meta, que Meta puede usar para optimizar campañas: `StartTrial`, `Subscribe` y `fb_mobile_purchase`, el evento de compra de Meta. **Subscription started**, **Subscription renewed**, **Trial started** y **Trial converted** envían un evento de Meta por cada nombre seleccionado en el campo del evento, y todos los nombres están seleccionados por defecto. Un inicio de prueba, por ejemplo, llega a Meta como tres eventos. Para enviar menos, elimina nombres del campo. | Evento de Adapty | Eventos Meta predeterminados | | :------------------------ | :-------------------------------------------- | | Subscription started | `Subscribe`, `fb_mobile_purchase` | | Subscription renewed | `Subscribe`, `fb_mobile_purchase` | | Trial started | `StartTrial`, `Subscribe`, `fb_mobile_purchase` | | Trial converted | `Subscribe`, `fb_mobile_purchase` | | Non-subscription purchase | `fb_mobile_purchase` | Meta no tiene un evento estándar para el resto de eventos, por lo que Adapty los envía como eventos personalizados con sus nombres de Adapty. Para ajustar el nombre a tu convención, cámbialo en el campo del evento. | Evento de Adapty | Evento Meta predeterminado | | :--------------------------------- | :----------------------------------- | | Subscription expired (Churned) | `subscription_expired` | | Subscription renewal cancelled | `subscription_renewal_cancelled` | | Subscription renewal reactivated | `subscription_renewal_reactivated` | | Subscription paused (Android only) | `subscription_paused` | | Subscription deferred (Android only) | `subscription_deferred` | | Subscription refunded | `subscription_refunded` | | Trial expired | `trial_expired` | | Trial renewal cancelled | `trial_renewal_cancelled` | | Trial renewal reactivated | `trial_renewal_reactivated` | | Entered grace period | `entered_grace_period` | | Billing issue detected | `billing_issue_detected` | | Non-subscription purchase refunded | `non_subscription_purchase_refunded` | | Trial still active | `trial_active` | | Subscription still active | `subscription_active` | **Trial still active** y **Subscription still active** son [eventos sintéticos](events#synthetic-events): Adapty los envía a Meta mediante un temporizador, y solo si el período de prueba o la suscripción siguen activos cuando el temporizador expira. No llevan ingresos asociados, por lo que Meta no los contabiliza en el valor de compra. Ambos están desactivados por defecto. Para enviarlos, actívalos y [configura el retraso](configuration#set-the-timer-for-synthetic-events). <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 */ }} /> ### 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 | En iOS, el IDFA, o el IDFV cuando el IDFA no está disponible. En Android, el Advertising ID. Cuando el perfil no tiene ID de dispositivo, `00000000-0000-0000-0000-000000000000`. | | `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 objetos de evento (véase 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 | ID anónimo de Facebook (si está disponible). | El parámetro `custom_events` es un array codificado en JSON con un objeto por cada evento de Meta al que se asigna el evento de Adapty. Solo `StartTrial`, `Subscribe` y `fb_mobile_purchase` incluyen `fb_content_id`, `fb_content_type`, `fb_num_items` y `fb_transaction_id`. Cada objeto 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. | ## Solución de problemas \{#troubleshooting\} ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # File: singular --- --- title: "Singular" description: "Integra Singular con Adapty para analizar datos de marketing y suscripción." --- [Singular](https://www.singular.net/) es una plataforma de Mobile Measurement Partner (MMP) que recopila y presenta datos de campañas de marketing. Adapty envía los [eventos de suscripción](events) que selecciones a Singular. En el dashboard de Singular, podrás ver cuántos ingresos genera cada campaña y evaluar la efectividad de tu publicidad. ## Configurar la integración \{#set-up-integration\} ### Conectar Adapty con Singular Para configurar la integración con Singular, ve a [Integrations > Singular](https://app.adapty.io/integrations/singular) en tu Adapty Dashboard, activa el toggle y rellena los campos. Las siguientes credenciales están disponibles: - **Singular SDK Key**: Obligatorio. La clave SDK de producción para tu app de 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 */ }} /> ### Configurar eventos y etiquetas \{#configure-events-and-tags\} A continuación de las credenciales, hay tres grupos de eventos que puedes enviar a Singular desde Adapty. Para ver la lista completa de eventos de Adapty, consulta [Eventos](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 eventos predeterminados que ofrece Adapty, aunque puedes cambiarlos según tus necesidades. Adapty envía eventos de suscripción al servidor de Singular server-to-server, para que puedas ver todos los eventos de suscripción en tu dashboard de Singular y vincularlos a tus campañas de adquisición. :::warning Los perfiles creados antes de configurar la integración no pueden enviar sus eventos a Singular. ::: ### Conecta tu app con Singular Singular habilita el Singular Device ID (SDID) por defecto en las cuentas creadas a partir del 15 de julio de 2026. Estas cuentas solo aceptan eventos a través de la API de eventos v2, y Adapty envía un evento a v2 únicamente cuando el perfil del usuario tiene un SDID. Para enviar eventos a este tipo de cuenta, pasa el SDID a Adapty desde tu app. Si tu cuenta de Singular se creó antes del 15 de julio de 2026 y el SDID no está habilitado en ella, omite este paso. Adapty envía tus eventos a la API de eventos v1 y no es necesario añadir código en la app. 1. Activa Adapty y luego inicia el SDK de Singular con un callback de SDID: `sdidReceivedHandler` en iOS, `SDIDAccessorHandler` en Android, `sdidReceivedCallback` en React Native y Flutter, y `SingularSdidAccessorHandler` en Unity. 2. Espera a que Singular llame al callback. El SDID llega a tu app de forma asíncrona, después de que el SDK de Singular se inicie. Singular llama al callback en cada lanzamiento, incluso cuando el SDID ya está almacenado en el dispositivo. 3. Dentro del callback, pasa el SDID a Adapty con `setIntegrationIdentifier()` y la clave `singular_sdid`. Ningún SDK de Adapty tiene una constante predefinida para esta clave, así que escríbela como un literal de cadena. 4. Llama a `Adapty.identify()` solo después de que `setIntegrationIdentifier()` se complete. Adapty crea un perfil anónimo al activarse y luego cambia al perfil identificado una vez que `identify()` se resuelve. Un SDID establecido durante ese cambio puede perderse. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers let config = SingularConfig(apiKey: "<SINGULAR_SDK_KEY>", andSecret: "<SINGULAR_SDK_SECRET>") config.sdidReceivedHandler = { sdid in Task { do { try await Adapty.setIntegrationIdentifier( AdaptyIntegrationIdentifier(key: "singular_sdid", value: sdid) ) // Call Adapty.identify() here, if your app identifies users } catch { // handle the error } } } Singular.start(config) ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)"> ```kotlin showLineNumbers val config = SingularConfig("<SINGULAR_SDK_KEY>", "<SINGULAR_SDK_SECRET>") .withCustomSdid(null, object : SDIDAccessorHandler { override fun didSetSdid(result: String) {} override fun sdidReceived(result: String) { Adapty.setIntegrationIdentifier( AdaptyIntegrationIdentifier(AdaptyIntegrationIdentifier.Key("singular_sdid"), result) ) { error -> if (error != null) { // handle the error return@setIntegrationIdentifier } // Call Adapty.identify() here, if your app identifies users } } }) Singular.init(context, config) ``` </TabItem> <TabItem value="java" label="Android (Java)"> ```java showLineNumbers SingularConfig config = new SingularConfig("<SINGULAR_SDK_KEY>", "<SINGULAR_SDK_SECRET>") .withCustomSdid(null, new SDIDAccessorHandler() { @Override public void didSetSdid(String result) {} @Override public void sdidReceived(String result) { Adapty.setIntegrationIdentifier( new AdaptyIntegrationIdentifier(new AdaptyIntegrationIdentifier.Key("singular_sdid"), result), error -> { if (error != null) { // handle the error return; } // Call Adapty.identify() here, if your app identifies users } ); } }); Singular.init(context, config); ``` </TabItem> <TabItem value="rn" label="React Native (TS)"> ```typescript showLineNumbers const config = new SingularConfig('<SINGULAR_SDK_KEY>', '<SINGULAR_SDK_SECRET>') // null keeps the SDID that Singular generates. Singular's typings require a string. .withCustomSdid(null as unknown as string, () => {}, async (sdid) => { try { await adapty.setIntegrationIdentifier('singular_sdid', sdid); // Call adapty.identify() here, if your app identifies users } catch (error) { // handle `AdaptyError` } }); Singular.init(config); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)"> ```dart showLineNumbers final config = SingularConfig('<SINGULAR_SDK_KEY>', '<SINGULAR_SDK_SECRET>'); config.sdidReceivedCallback = (String sdid) async { try { await Adapty().setIntegrationIdentifier( key: 'singular_sdid', value: sdid, ); // Call Adapty().identify() here, if your app identifies users } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } }; Singular.start(config); ``` </TabItem> <TabItem value="unity" label="Unity (C#)"> ```csharp showLineNumbers using AdaptySDK; using Singular; public class SingularSdidHandler : SingularSdidAccessorHandler { public void DidSetSdid(string result) {} public void SdidReceived(string result) { Adapty.SetIntegrationIdentifier( "singular_sdid", result, (error) => { if (error != null) { // handle the error return; } // Call Adapty.Identify() here, if your app identifies users }); } } // Register the handler before the Singular SDK initializes SingularSDK.SetSingularSdidAccessorHandler(new SingularSdidHandler()); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform"> ```kotlin showLineNumbers // Call this from the SDID callback of the native Singular SDK // on each platform: sdidReceivedHandler on iOS, SDIDAccessorHandler on Android suspend fun onSingularSdidReceived(sdid: String) { Adapty.setIntegrationIdentifier("singular_sdid", sdid) .onSuccess { // Call Adapty.identify() here, if your app identifies users } .onError { error -> // handle the error } } ``` </TabItem> <TabItem value="capacitor" label="Capacitor"> ```typescript showLineNumbers // Call this from the SDID callback of the Singular SDK async function onSingularSdidReceived(sdid: string) { try { await adapty.setIntegrationIdentifier({ key: 'singular_sdid', value: sdid }); // Call adapty.identify() here, if your app identifies users } catch (error) { // handle the error } } ``` </TabItem> </Tabs> ## Estructura del evento \{#event-structure\} Adapty selecciona la versión de la API de eventos de Singular según si el perfil del usuario tiene un SDID: - **Con SDID**: Adapty envía una solicitud `POST` a `https://s2s.singular.net/api/v2/evt` con un cuerpo codificado en form-urlencoded. El parámetro `sdid` reemplaza los identificadores de dispositivo `idfa`, `idfv`, `aifa`, `andi` y `asid`. Los booleanos se envían como `true` o `false`. - **Sin SDID**: Adapty envía una solicitud `GET` a `https://s2s.singular.net/api/v1/evt` con parámetros de consulta. Ambas versiones comparten los mismos parámetros en todo lo demás. Un evento v1 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, "partner": "adapty", "use_ip": false, "is_revenue_event": true, "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 clave SDK de Singular. | | `p` | String | Plataforma ("iOS" o "Android"). | | `i` | String | ID de la app en el store (Bundle ID). | | `ip` | String | Dirección IP del usuario. | | `sdid` | String | **Solo v2**. ID de dispositivo de Singular, obtenido del identificador de integración `singular_sdid`. | | `idfa` | String | **iOS, solo v1**. ID para anunciantes (en mayúsculas). | | `idfv` | String | **iOS, solo v1**. ID para proveedores (en mayúsculas). | | `aifa` | String | **Android, solo v1**. Google Advertising ID (en minúsculas). | | `andi` | String | **Android, solo v1**. Android ID (en minúsculas). | | `asid` | String | **Android, solo v1**. App Set ID (en minúsculas). | | `ve` | String | Versión del sistema operativo. | | `att_authorization_status` | Integer | **Solo iOS**. Estado de 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. | | `partner` | String | Siempre `adapty`. Identifica el evento como enviado por Adapty. | | `use_ip` | Boolean | `true` cuando Adapty no tiene dirección IP para el usuario. | | `is_revenue_event` | Boolean | `true` si el evento contiene ingresos. Se envía solo con eventos de ingresos. | | `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 contiene 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. | ## Solución de problemas \{#troubleshooting\} ### Los eventos fallan con `invalid api version: v1` Tu cuenta de Singular fue creada el 15 de julio de 2026 o después, y solo acepta eventos a través de la API de eventos v2. Adapty envió el evento a v1 porque el perfil del usuario no tiene SDID. Para solucionarlo, pasa el SDID a Adapty desde tu app, tal como se describe en [Conecta tu app a Singular](#connect-your-app-to-singular). Los eventos de usuarios que tengan una versión de la app sin la llamada `setIntegrationIdentifier()` seguirán fallando hasta que actualicen la app. ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # 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 */ }} /> ### Conectar tu app a Tenjin \{#connect-your-app-to-tenjin\} Usa el método del SDK `Adapty.updateExternalAttribution()` (llamado `updateAttribution()` antes de la versión 4.1 del SDK) para obtener los datos de atribución de Tenjin y pasárselos 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 { try await Adapty.setIntegrationIdentifier(.tenjinAnalyticsInstallationId(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 { try await Adapty.updateExternalAttribution(info, provider: .tenjin) } catch { // handle the error } } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.tenjinAnalyticsInstallationId(tenjinSdk.analyticsInstallationId)) { error -> if (error != null) { // handle the error } } tenjinSdk.getAttributionInfo { attribution -> if (attribution == null) return@getAttributionInfo Adapty.updateExternalAttribution(attribution, AdaptyExternalAttributionProvider.TENJIN) { error -> if (error != null) { // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.tenjinAnalyticsInstallationId(tenjinSdk.getAnalyticsInstallationId()), error -> { if (error != null) { // handle the error } }); tenjinSdk.getAttributionInfo(attribution -> { if (attribution == null) return; Adapty.updateExternalAttribution(attribution, AdaptyExternalAttributionProvider.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().updateExternalAttribution(attribution, provider: AdaptyExternalAttributionProvider.tenjin); } } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; BaseTenjin instance = Tenjin.getInstance("<SDK_KEY>"); var tenjinId = instance.GetAnalyticsInstallationId(); Adapty.SetIntegrationIdentifier( "tenjin_analytics_installation_id", tenjinId, (error) => { // handle the error }); instance.GetAttributionInfo((attribution) => { // you will probably need to install Newtonsoft.Json package, if not yet var attributionJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(attribution); Adapty.UpdateExternalAttribution( attributionJsonString, AdaptyExternalAttributionProvider.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 moneda (p. ej., USD se reporta en dólares). | | **locale** | String | El idioma y región del dispositivo. Para Android: `Locale.getDefault().toString()`. Para iOS: `[[NSLocale currentLocale] localeIdentifier]`. | | **country** | String | El código de país estándar ISO (p. ej., US para Estados Unidos). | | **postcut** | String (Boolean) | Indica si la compra se envió tras el recorte de la plataforma. 1 para verdadero, 0 para falso. | | **currency** | String | El código de moneda 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 del paquete de la app (p. ej., `com.example.app`). | | **ip_address** | String (IPv4) | La dirección IP del usuario. Se usa para determinar el país. | | **os_version** | String | La versión del SO 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 publicitario del dispositivo. Obligatorio para Android. En iOS puede estar vacío o con todos ceros. | | **os_version_release** | String | La versión de lanzamiento del SO. 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`. | ## Solución de problemas \{#troubleshooting\} ### Algunos eventos aparecen como fallidos en el Event Feed \{#some-events-show-as-failed-in-the-event-feed\} Tenjin solo acepta los eventos enumerados en [Configurar eventos y etiquetas](#configure-events-and-tags). El resto de eventos sigue generando una fila en el Event Feed, marcada como entrega fallida con el tooltip "The event type is not supported for integration." Esto incluye `access_level_updated`, que no encontrarás en la sección **Events names**, ya que es exclusivo de la [integración con webhook](webhook). --- # File: analytics-integration --- --- title: "Integraciones de análisis" description: "Integra herramientas de análisis con Adapty para rastrear y optimizar las suscripciones de usuarios." --- Adapty envía todos los [eventos de suscripción](events) a servicios de análisis como [Amplitude](amplitude), [Mixpanel](mixpanel) y [AppMetrica](appmetrica). También podemos enviar eventos a tu servidor mediante la integración con [webhook](webhook). Lo mejor de todo es que no tienes que encargarte de enviar ningún evento: nosotros lo hacemos por ti. Solo asegúrate de configurar la integración en el Adapty Dashboard. Adapty admite la integración con los siguientes servicios de análisis de terceros: - [Amplitude](amplitude) - [AppMetrica](appmetrica) - [Firebase and Google Analytics](firebase-and-google-analytics) - [Mixpanel](mixpanel) - [PostHog](posthog) - [SplitMetrics Acquire](splitmetrics) :::note ¿No ves tu proveedor de análisis? ¡Cuéntanos! [Crea una solicitud de función](https://adapty.featurebase.app/en?b=6979f233ebd3cffd4f425ba0) y lo consideraremos. ::: ## Propiedades del evento \{#event-properties\} Los eventos de webhook se envían en formato JSON. Todos los eventos siguen la misma estructura, pero sus campos varían según el tipo de evento, el store y tu configuración específica. :::note Adapty convierte otras divisas a USD utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: | Propiedad | Tipo | Descripción | | ----------------------------- | ------------- | ------------------------------------------------------------ | | **profile_id** | uuid | ID de usuario de Adapty. | | **currency** | str | Moneda local (por defecto USD). | | **price_usd** | float | Precio del producto antes de la comisión de Apple/Google. Ingresos. | | **proceeds_usd** | float | Precio del producto después de la comisión de Apple/Google. Ingresos netos. | | **net_revenue_usd** | float | Ingresos netos (ingresos tras la comisión de Apple/Google e impuestos) en USD. Puede estar vacío. | | **price_local** | float | Precio del producto antes de la comisión de Apple/Google en moneda local. Ingresos. | | **proceeds_local** | float | Precio del producto después de la comisión de Apple/Google en moneda local. Ingresos netos. | | **transaction_id** | str | Identificador único de una transacción, como una compra o renovación. | | **original_transaction_id** | str | Identificador de transacción de la compra original. | | **purchase_date** | ISO 8601 date | Fecha y hora de compra del producto. | | **original_purchase_date** | ISO 8601 date | Fecha y hora de la compra original. | | **environment** | str | Puede ser _Sandbox_ o _Production_. | | **vendor_product_id** | str | ID del producto en Apple App Store, Google Play Store o Stripe. Para un [plan de compromiso de 12 meses](app-store-12-month-commitment), el valor es `<product_id>:monthly`. | | **base_plan_id** | str | [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. | | **event_datetime** | ISO 8601 date | Fecha y hora del evento. | | **store** | str | Puede ser _app_store_ o _play_store_. | | **trial_duration** | str | Duración del período de prueba en días. Se envía en el formato "{} days", por ejemplo, "7 days". | | **cancellation_reason** | str | <p>Motivo por el que el usuario canceló una suscripción.</p><p></p><p>Puede ser</p><p>iOS & Android</p><p>_voluntarily_cancelled_, _billing_error_, _refund_</p><p>iOS</p><p>_price_increase_, _product_was_not_available_, _unknown_</p><p>Android</p><p>_new_subscription_replace_, _cancelled_by_developer_</p> | | **subscription_expires_at** | ISO 8601 date | Fecha de vencimiento de la suscripción. Normalmente en el futuro. | | **consecutive_payments** | int | Número de períodos que un usuario lleva suscrito sin interrupciones. Incluye el período actual. | | **rate_after_first_year** | bool | Booleano que indica si la suscripción cumple los requisitos para una tasa de comisión reducida (normalmente el 15%) tras un año de renovación continua. Las tasas de comisión varían según la elegibilidad del programa y el país. Consulta [Comisión e impuestos del store](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue) para más detalles. | | **promotional_offer_id** | str | ID de la oferta promocional tal como figura en la sección de productos del Adapty Dashboard. | | **store_offer_category** | str | Puede ser _introductory_ o _promotional_. | | **store_offer_discount_type** | str | Puede ser _free_trial_, _pay_as_you_go_ o _pay_up_front_. | | **paywall_name** | str | Nombre del paywall donde se originó la transacción. Para un flow, el ID de la pantalla del paywall (`scr_...`). | | **paywall_revision** | int | Revisión del paywall donde se originó la transacción. El valor se establece en 1. | | **developer_id** | str | ID de desarrollador (SDK) del placement donde se originó la transacción. | | **ab_test_name** | str | Nombre de la prueba A/B donde se originó la transacción. Para una prueba A/B entre placements, el nombre de la prueba en un placement (`<test name> child-N`). Para agrupar una prueba entre placements, usa `ab_test_id`. | | **ab_test_revision** | int | Revisión de la prueba A/B donde se originó la transacción. El valor se establece en 1. | | **ab_test_id** | uuid | ID de la prueba A/B donde se originó la transacción. Para una prueba A/B entre placements, es el mismo en todos los placements. | | **ab_test_parent_name** | str | Nombre de la prueba A/B entre placements. Solo está presente en pruebas A/B entre placements. | | **ab_test_variant_id** | uuid | ID de la variante de la prueba A/B donde se originó la transacción. Úsalo para dividir las transacciones por variante de prueba. | | **ab_test_variant_name** | str | Nombre de la variante de la prueba A/B. | | **flow_id** | uuid | ID del flow donde se originó la transacción. | | **flow_name** | str | Nombre del flow donde se originó la transacción. | | **flow_variation_id** | uuid | ID de la variante del flow en este placement, igual que `flow.variationId` en el SDK. | | **cohort_name** | str | Nombre de la audiencia a la que pertenece el perfil. | | **profile_event_id** | uuid | ID de evento único que puede usarse para deduplicación. | | **store_country** | str | País enviado por el store. | | **profile_ip_address** | str | IP del perfil (puede ser IPv4 o IPv6, con preferencia por IPv4 cuando esté disponible). Se actualiza cada vez que cambia la IP del dispositivo. | | **profile_country** | str | Determinado por Adapty a partir de la IP del perfil. | | **profile_total_revenue_usd** | float | Ingresos totales del perfil, reembolsos incluidos. | | **variation_id** | uuid | ID único del paywall donde se realizó la compra. Para un flow, el ID de la pantalla del paywall dentro del flow, no la variante de la prueba A/B. | | **access_level_id** | str | ID del nivel de acceso de pago. | | **is_active** | bool | Booleano que indica si el nivel de acceso de pago está activo para el perfil. | | **will_renew** | bool | Booleano que indica si el nivel de acceso de pago se renovará. | | **is_refund** | bool | Booleano que indica si la transacción ha sido reembolsada. | | **is_lifetime** | bool | Booleano que indica si el nivel de acceso de pago es de por vida. | | **is_in_grace_period** | bool | Booleano que indica si el perfil está en período de gracia. | | **starts_at** | ISO 8601 date | Fecha y hora en que el nivel de acceso de pago comienza para el usuario. | | **renewed_at** | ISO 8601 date | Fecha y hora en que comenzó más recientemente el período actual del acceso de pago. Hasta la primera renovación, coincide con `activated_at`. | | **expires_at** | ISO 8601 date | Fecha y hora en que expirará el acceso de pago. | | **activated_at** | ISO 8601 date | Fecha y hora en que se activó el acceso de pago. | | **billing_issue_detected_at** | ISO 8601 date | Fecha y hora del problema de facturación. | | **profile_has_access_level** | Bool | Booleano que indica si el perfil tiene un nivel de acceso activo (solo Webhook). | Cada evento incluye las siguientes propiedades: `transaction_id, original_transaction_id, purchase_date, original_purchase_date, environment, vendor_product_id, event_datetime, store`. Además, algunos eventos tienen propiedades adicionales. Para los eventos `subscription_refunded` y `non_subscription_purchase_refunded`, es obligatorio proporcionar los valores de `price_usd` y `proceeds_usd` como propiedades adicionales. | Nombre del evento | Propiedades | | :---------------------------------- | :----------------------------------------------------------- | | **subscription\_initial\_purchase** | price\_usd, proceeds\_usd, subscription\_expires\_at, consecutive\_payments, rate\_after\_first\_year, trial\_duration | | **subscription\_renewed** | price\_usd, proceeds\_usd, subscription\_expires\_at, consecutive\_payments, rate\_after\_first\_year, trial\_duration | | **subscription\_cancelled** | cancellation\_reason, trial\_duration | | **trial\_started** | subscription\_expires\_at, trial\_duration | | **trial\_converted** | price\_usd, proceeds\_usd, subscription\_expires\_at, consecutive\_payments, rate\_after\_first\_year, trial\_duration | | **trial\_cancelled** | cancellation\_reason, trial\_duration | | **non\_subscription\_purchase** | price\_usd, proceeds\_usd | | **billing\_issue\_detected** | subscription\_expires\_at, trial\_duration | | **entered\_grace\_period** | subscription\_expires\_at, trial\_duration | Ejemplo de evento ```json title="Json" { "price_usd": 9.99, "proceeds_usd": 6.99, "transaction_id": "1000000628581600", "original_transaction_id": "1000000628581600", "purchase_date": "2020-02-18T18:40:22.000000+0000", "original_purchase_date": "2020-02-18T18:40:22.000000+0000", "environment": "Sandbox", "vendor_product_id": "premium", "event_datetime": "2020-02-18T18:40:22.000000+0000", "store": "app_store" } ``` Adapty envía eventos a tu servidor y a sistemas de analítica de terceros. La propiedad **profile_ip_address** se sincroniza con la IP actual del dispositivo. Cada vez que los servidores de Adapty reciben información del SDK, la IP se actualizará si difiere de la que tenemos registrada. ### Configurar el identificador del perfil \{#setting-the-profiles-identifier\} - Configura el identificador del perfil para la analítica seleccionada usando las instrucciones para <InlineTooltip tooltip="instrucciones para configurar atributos de usuario en tu app">[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>. :::warning Evitar duplicados No olvides desactivar el envío de eventos de suscripción desde los dispositivos y tu servidor para evitar duplicados. ::: ### Deshabilitar la analítica externa para un cliente específico \{#disabling-external-analytics-for-a-specific-customer\} Es posible que quieras dejar de enviar eventos de analítica para un cliente concreto. Esto es útil si tu app ofrece la opción de no participar en servicios de analítica. Para deshabilitar la analítica externa de un cliente, usa el método `updateProfile()`. Crea un objeto `AdaptyProfileParameters.Builder` y establece el valor correspondiente. Cuando la analítica externa está bloqueada, Adapty no enviará ningún evento a ninguna integración para ese usuario específico. Si quieres deshabilitar una integración para todos los usuarios de tu app, simplemente desactívala en el Adapty Dashboard. :::warning Bloquear el análisis externo detiene la entrega de eventos a **todas** las integraciones para este usuario, incluidos los [webhooks](webhook) personalizados. No lo uses si tu backend depende de eventos de webhook para gestionar el acceso de este usuario. La configuración no es retroactiva: los eventos creados antes de activarla se siguen entregando. Tampoco afecta al análisis propio de Adapty: las transacciones del usuario siguen contando en los gráficos del dashboard. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(analyticsDisabled: true) Adapty.updateProfile(parameters: builder.build()) ``` </TabItem> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers val parameters = AdaptyProfileParameters( analyticsDisabled = true ) Adapty.updateProfile(parameters) { error -> if (error == null) { // successful update } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers AdaptyProfileParameters.Builder builder = new AdaptyProfileParameters.Builder() .withExternalAnalyticsDisabled(true); Adapty.updateProfile(builder.build(), error -> { if (error == null) { // successful update } }); ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```javascript showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setAnalyticsDisabled(true); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers var builder = new AdaptyProfileParameters.Builder() .SetAnalyticsDisabled(true); Adapty.UpdateProfile(builder.Build(), (error) => { if(error != null) { // handle the error } }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers adapty.updateProfile({ analyticsDisabled: true }); ``` </TabItem> </Tabs> ### Deshabilitar la recopilación de identificadores publicitarios \{#disable-collection-of-advertising-identifiers\} <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> Puedes deshabilitar la recopilación de IDFA usando la propiedad `idfaCollectionDisabled`. Asegúrate de llamarla antes del método `.activate()`. ```swift showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") // highlight-start .with(idfaCollectionDisabled: true) // set to `true` // highlight-end Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> Puedes deshabilitar la recopilación de AAID/GAID usando la propiedad `withAdIdCollectionDisabled` al activar el SDK de Adapty: ```swift showLineNumbers override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") // highlight-start .withAdIdCollectionDisabled(true) // set to `true` // highlight-end .build() ) } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> Puedes deshabilitar la recopilación de AAID/GAID usando la propiedad `withAdIdCollectionDisabled` al activar el SDK de Adapty: ```swift showLineNumbers  @Override public void onCreate() { super.onCreate(); Adapty.activate( applicationContext, new AdaptyConfig.Builder("PUBLIC_SDK_KEY") // highlight-start .withAdIdCollectionDisabled(true) // set to `true` // highlight-end .build() ); } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> Puedes deshabilitar la recopilación de IDFA usando la propiedad `withAppleIdfaCollectionDisabled` y la del ID publicitario de Google/Android usando `withGoogleAdvertisingIdCollectionDisabled`. Establécelas en `true` al activar el SDK de Adapty: ```dart showLineNumbers try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') // highlight-start ..withGoogleAdvertisingIdCollectionDisabled(true), // set to `true` ..withAppleIdfaCollectionDisabled(true), // set to `true` // highlight-end ); } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity" default> Puedes deshabilitar la recopilación de IDFA usando la propiedad `SetIDFACollectionDisabled` al activar el SDK de Adapty. La recopilación de AAID/GAID no puede deshabilitarse por el momento. ```dart showLineNumbers var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") // highlight-start .SetIDFACollectionDisabled(true); // set to `true` // highlight-end Adapty.Activate(builder.Build(), (error) => { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native" default> También puedes deshabilitar la recopilación de IDFA usando la propiedad `idfaCollectionDisabled` al activar el SDK de Adapty, o deshabilitar la recopilación de AAID/GAID usando la propiedad `adIdCollectionDisabled`. ```typescript showLineNumbers adapty.activate('PUBLIC_SDK_KEY', { // highlight-start ios: { idfaCollectionDisabled: true, // set to `true` }, android: { adIdCollectionDisabled: true, }, // highlight-end }); ``` </TabItem> </Tabs> --- # 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 análisis móvil. Con Adapty, puedes enviar eventos a Amplitude fácilmente, ver cómo se comportan tus usuarios y tomar decisiones más inteligentes. Adapty proporciona un conjunto completo de datos que te permite hacer seguimiento de [eventos de suscripción](events) desde los stores en un solo lugar y los envía a tu cuenta de Amplitude. Esto te permite relacionar el comportamiento de tus usuarios con su historial de pagos en Amplitude, y así tomar mejores decisiones de producto. ### Cómo configurar la integración con Amplitude En Adapty, puedes configurar flows separados para **eventos de producción** y **eventos de prueba** del entorno sandbox de Apple, Stripe o Paddle, o de la cuenta de prueba de Google. - Para eventos de producción, introduce las claves API de **Production** del dashboard de Amplitude, con una clave API única para cada plataforma: iOS, Android, Stripe y Paddle. - Para eventos de prueba, usa los campos **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/ Paddle API key** | Introduce la **API Key** de Amplitude para iOS/ Android/ Stripe/ Paddle en Adapty. La encontrarás en **Project settings** de Amplitude. Si necesitas ayuda, consulta la [documentación de Amplitude](https://amplitude.com/docs/apis/authentication). Empieza con las claves de **Sandbox** para hacer pruebas y luego cambia a las claves de **Production** una vez que las pruebas sean correctas. Rellena solo las plataformas en las que vendes: si una plataforma no tiene clave, no enviará eventos. | <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(AdaptyIntegrationIdentifier.amplitudeDeviceId(amplitudeDeviceId)) { error -> if (error != null) { // handle the error } } ``` **Configuración de 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(AdaptyIntegrationIdentifier.amplitudeUserId(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. | ## Solución de problemas \{#troubleshooting\} ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # 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 analítica 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(AdaptyIntegrationIdentifier.appmetricaDeviceId(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(AdaptyIntegrationIdentifier.appmetricaDeviceId(deviceIdHash)) { error -> if (error != null) { // handle the error } } Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.appmetricaProfileId("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 (p. ej., "subscription_renewed", "refund", "intro_started"). Consulta [mapeo de eventos de AppMetrica](#events-and-tags). | | `price` | Float | Importe de ingresos (según tu configuración de cálculo de ingresos). | | `currency` | String | Código de moneda (p. ej., "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 con los detalles del evento. Se recorta automáticamente si supera los 30 KB eliminando campos opcionales en orden de importancia para conservar los datos más críticos. | | `os_name` | String | "ios" o "android". | | `profile_id` | String | AppMetrica Profile ID (si está definido); en caso contrario, 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. | ## Solución de problemas \{#troubleshooting\} ### `access_level_updated` se muestra como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # 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, la 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 los datos a ambas plataformas. :::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 web (Stripe y Paddle) \{#web-purchases-stripe-and-paddle\} Una compra de Stripe o Paddle llega a Firebase solo si el comprador abrió primero tu aplicación móvil. El Firebase App Instance ID debe configurarse **antes** de que se realice la compra web. En las compras de App Store y Play Store, esto ocurre automáticamente. 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 realiza la compra. Stripe y Paddle procesan las compras fuera de la app, en la web. Tu app móvil debe llamar a `setIntegrationIdentifier` al iniciar —antes de que se realice cualquier compra web—. De lo contrario, Adapty no tiene ningún ID al que asociarla y la compra 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 con fines analíticos. 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. - **Los eventos históricos llegan a Firebase en el primer lanzamiento del usuario.** Adapty no reenvía los eventos procesados antes de activar la integración. Sin embargo, cuando un suscriptor existente lanza por primera vez una versión con el SDK de Adapty, Adapty importa su historial completo del store y lo reenvía como cualquier otro evento — y Firebase mapea las compras iniciales, renovaciones, conversiones de prueba y compras únicas al evento `purchase` de GA4, por lo que Adapty envía cada renovación retroalimentada con los ingresos asociados. (Los datos anteriores también 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 han instalado la app no tienen ID: sus compras no llegan a Firebase. Consulta [Compras web (Stripe y Paddle)](#web-purchases-stripe-and-paddle) más arriba para más detalles, y considera la [integración de FunnelFox con Firebase](https://funnelfox.com/docs/integrations/subscription-management/adapty) o un flujo de datos web de Google Analytics para el seguimiento web. - **Las compras web heredan las limitaciones específicas del store.** Consulta las [limitaciones de la integración con Stripe](stripe#current-limitations) y las [limitaciones de la integración con Paddle](paddle#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 por cada plataforma en la que publiques (iOS / Android / Web). Para Stripe y Paddle, añade una entrada de aplicación Web, ya que ninguno de los dos tiene un tipo de app nativa. Cada entrada genera un **Firebase App ID** único y el correspondiente flujo de datos en Google Analytics. Pegarás ese ID en la configuración de la integración con 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 botón **Firebase integration**. 3. Introduce las credenciales para cada plataforma en la que publiques. Adapty necesita tanto un **Firebase App ID** como un **Google Analytics secret** para cada plataforma — cada valor es distinto en iOS, Android, Stripe y Paddle. Una plataforma que tenga solo uno de los dos valores no enviará eventos. | 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: - **Revenue definition** desplegable: Ingresos brutos, Ingresos tras la comisión de la store, o [Ingresos tras la comisión de la store e impuestos](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). - **Send user properties** toggle: Cuando está activado, los eventos incluyen `subscription_state` y `subscription_product_id`. Para usarlos en informes o audiencias, consulta [Usar datos de suscripción en informes](#use-subscription-data-in-reports-and-audiences). - **Report user's currency** toggle: Cuando está activado, Adapty reporta cada venta en la moneda con la que pagó el comprador. Cuando está desactivado, reporta en USD. - **Send trial price** toggle: Los inicios de prueba son valiosos — la mayoría de los usuarios de pago suelen empezar con una prueba. Pero la optimización de pujas de Google Ads solo trata como relevantes los eventos con ingresos. Activa esto para asignar un precio de marcador a cada prueba y que Google las vea como conversiones y optimice el gasto en publicidad hacia la captación de usuarios que inician pruebas. Cuando está activado, aparece el campo **Trial price percentage**. Establécelo en el porcentaje del precio completo de la suscripción que Google debe considerar que vale cada prueba; por ejemplo, `50%` reporta la mitad del precio de la suscripción durante la 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**, por lo que puedes usar nombres distintos por plataforma. **Las compras de Stripe y Paddle utilizan el mapa de eventos de iOS**: no existe un mapa independiente para ninguno de ellos. Google Analytics aplica límites estrictos del Protocolo de Medición: nombres de eventos de 40 caracteres, nombres de propiedades de usuario de 24 caracteres, valores de propiedades de usuario de 36 caracteres y valores de parámetros de eventos de 100 caracteres. Google Analytics descarta silenciosamente los eventos personalizados que superan estos límites. Adapty recorta los valores de parámetros de eventos de texto a 100 caracteres para cumplir con el límite. :::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 exactamente de estas cadenas. Modifica los valores predeterminados solo si no necesitas esas funcionalidades. ::: 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(AdaptyIntegrationIdentifier.firebaseAppInstanceId(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(AdaptyIntegrationIdentifier.firebaseAppInstanceId(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. ## Dividir eventos por prueba A/B y flow \{#split-events-by-ab-test-and-flow\} Para comparar variantes de [prueba A/B](ab-tests) o flows en Google Analytics, usa los parámetros de eventos de flow y prueba A/B. Adapty los añade solo cuando tienen un valor: | Parámetro | Descripción | |---|---| | `ab_test_id` | ID de la prueba A/B. Para una prueba A/B entre placements, es el mismo en todos los placements. | | `ab_test_variant_id` | ID de la variante de la prueba A/B. | | `ab_test_variant_name` | Nombre de la variante de la prueba A/B. | | `ab_test_parent_name` | Nombre de la prueba A/B entre placements. Solo está presente en pruebas A/B entre placements. | | `flow_id` | ID del flow. | | `flow_name` | Nombre del flow. | | `developer_id` | ID del placement. | | `paywall_name` | Nombre del paywall. Para un flow, el ID de la pantalla del paywall (`scr_...`). | Para obtener detalles sobre cada campo, consulta [Campos de flow y prueba A/B](webhook-event-types-and-fields#flow-and-ab-test-fields). Adapty no envía `flow_variation_id` a Firebase. Adapty añade estos parámetros a todos los eventos `purchase` y `refund`. A otros eventos, Adapty los añade solo si encajan: Google Analytics acepta hasta 25 parámetros por evento, y Adapty los añade después de los demás parámetros del evento. Los eventos que ya alcanzan el límite, como `trial_started` o los eventos de compra que hayas renombrado en el mapa de eventos, no los reciben. Para usar los parámetros en los informes de Google Analytics, registra cada uno como una dimensión personalizada con el alcance **Event**. En BigQuery están disponibles sin necesidad de registro. :::note Adapty recorta los valores de texto a 100 caracteres. Los IDs siempre son más cortos, pero los nombres pueden ser más largos: si dos pruebas A/B o flows comparten los primeros 100 caracteres de su nombre, Google Analytics los mostrará como uno solo. Para distinguirlos, usa `ab_test_id` o `flow_id`. ::: ## 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-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. ### 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 más común. Consulta [Firebase infracontabiliza los ingresos de apps con StoreKit 2](#firebase-undercounts-revenue-for-storekit-2-apps). - **Adopción del SDK**: Firebase solo cuenta los eventos de usuarios cuya app envía un Firebase App Instance ID. Las versiones antiguas de la app no hacen esa llamada. Adapty sí cuenta esos usuarios; Firebase, no. - **Eventos sandbox**: Adapty también reenvía las transacciones sandbox a Firebase. Usa un proyecto de Firebase independiente para las builds de desarrollo y así mantenerlos separados. - **Importación del historial del primer lanzamiento**: Cuando un suscriptor existente abre por primera vez una build con el SDK de Adapty, Adapty reenvía todo su historial del store a Firebase, enviando cada renovación pasada como un evento `purchase` de GA4 con sus ingresos. Firebase puede reportar ingresos muy superiores a los de Adapty para el período afectado. Consulta [Limitaciones](#limitations). - **Muestreo**: Las Exploraciones de Google Analytics aplican muestreo a conjuntos de datos grandes. Para conteos sin muestreo, revisa 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 estrategias óptimas de adquisición, conversión y retención de usuarios en diferentes 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 toggle e introduce tu **Mixpanel Token**. Adapty gestiona tokens **Production** y **Sandbox** por separado para cada plataforma: iOS, Android, Stripe y Paddle. Rellena solo las plataformas de las que quieras recibir datos — una plataforma sin token no envía ningún evento. 3. Configura el **Mixpanel Data Residency** para que coincida con tu proyecto de Mixpanel. Este campo es obligatorio y tiene el valor predeterminado **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(AdaptyIntegrationIdentifier.mixpanelUserId(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 en Mixpanel \{#mixpanel-event-structure\} Adapty envía eventos a Mixpanel usando el método `track`. En `properties`, Adapty envía las propiedades de Mixpanel `ip`, `time` y `$insert_id`, y todas las [propiedades de evento](analytics-integration#event-properties) de Adapty del evento, incluyendo los [campos de flow y prueba A/B](webhook-event-types-and-fields#flow-and-ab-test-fields). Por ejemplo: ```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", "ab_test_id": "00000000-0000-0000-0000-000000000000", "ab_test_variant_id": "00000000-0000-0000-0000-000000000000", "ab_test_variant_name": "Variant B" } } ``` Las propiedades más utilizadas: | Parámetro | Tipo | Descripción | |:-------------------------------------|:--------|:--------------------------------------------| | `event` | String | El 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 | Marca de tiempo UNIX del evento en segundos. | | `properties.$insert_id` | String | ID único del evento (UUID) para deduplicación. | | `properties.vendor_product_id` | String | El ID del producto en el store. | | `properties.original_transaction_id` | String | ID de la transacción original. | | `properties.currency` | String | Código de moneda. | | `properties.store` | String | Nombre del store (p. ej., "app_store"). | | `properties.environment` | String | Entorno ("Sandbox" o "Production"). | | `properties.ab_test_variant_id` | String | ID de la variante de la prueba A/B. Úsalo para dividir un funnel por variante de prueba. | ### 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. | ## Solución de problemas \{#troubleshooting\} ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # File: posthog --- --- title: "PostHog" description: "Envía eventos de suscripción de Adapty a PostHog para conectar lo que compraron los usuarios con lo que hicieron en tu app." --- Adapty puede enviar eventos de suscripción (compras, renovaciones, reembolsos, inicios de prueba) al flujo de eventos de PostHog, para que se unan al resto de tus datos. Cada evento incluye datos de ingresos, moneda, store y producto, además del paywall y la variante de prueba A/B que generó la compra. Cualquier herramienta de PostHog puede usar estos datos en cuanto lleguen. Los eventos de suscripción de Adapty añaden una dimensión de ingresos a todo lo que ya rastreas en PostHog: - **¿Qué acciones in-app predicen una suscripción?** Correlaciona tus propios eventos de PostHog con `trial_started` y `subscription_started` para detectar el comportamiento que lleva a las compras. - **¿Qué variante del experimento generó más ingresos?** Usa los eventos de ingresos de Adapty como métrica para un experimento de PostHog sobre cualquier cambio en el producto. Las [pruebas A/B](ab-tests) de Adapty cubren paywalls y onboardings. - **¿Qué ocurre antes de un reembolso?** Crea un insight sobre `subscription_refunded`, luego abre los perfiles detrás de él y revisa sus grabaciones de sesión. - **¿Afectan los errores a las renovaciones?** Cruza el seguimiento de errores con `subscription_renewal_cancelled`, que se dispara en cuanto un usuario desactiva la renovación automática, mucho antes de `subscription_expired`. - **¿Cómo retienen los suscriptores en comparación con los usuarios gratuitos?** Crea cohortes y curvas de retención basadas en el estado de acceso derivado; consulta [Cómo distinguir un suscriptor de un usuario gratuito](#how-to-tell-a-subscriber-from-a-free-user). - **¿Qué paywall y variante generaron los ingresos?** Los eventos de compra incluyen el paywall y la variante que los originaron, así que puedes desglosar los ingresos por paywall. Registra las vistas de paywall con el SDK de PostHog para medir la tasa de vista-a-compra junto a ellos. - **Haz preguntas cruzando ambos conjuntos de datos** con insights o SQL. ## Cómo funciona la integración \{#how-the-integration-works\} PostHog identifica a cada usuario mediante una cadena llamada `distinct_id`. Todo lo relacionado con esta integración depende de que Adapty y PostHog utilicen la misma cadena. 1. El SDK de PostHog asigna al usuario un `distinct_id`. En el primer lanzamiento, esta cadena es anónima y tiene alcance de dispositivo. Sin embargo, puedes asignarle un valor personalizado al iniciar sesión, o restablecerlo al cerrarla. 2. En cada lanzamiento de la app, después de `Adapty.activate()` y antes de que pueda producirse cualquier compra, pasa el `distinct_id` a Adapty con `setIntegrationIdentifier()`. Adapty adjunta el valor al perfil de Adapty del usuario como `posthog_distinct_user_id`. 3. Cuando el usuario inicia una prueba o realiza una compra, los servidores de Adapty envían el evento a la API de captura de PostHog con ese `distinct_id` adjunto. El intercambio ocurre de servidor a servidor, fuera de tu app. 4. PostHog asocia el evento a la persona que tiene ese `distinct_id`, de modo que los ingresos por suscripción aparecen junto al resto de la actividad del usuario en tu app. ### Hacer coincidir el ID de Adapty con el de PostHog \{#match-adaptys-id-to-posthogs\} Ambos sistemas deben usar el mismo `distinct_id` para un usuario; de lo contrario, una misma persona se divide en dos entidades sin conexión. Tienes dos formas de evitar esta fragmentación, y cuál usar depende de cuándo puede producirse una compra. **Si una compra requiere inicio de sesión**, llama a `identify()` de PostHog con tu **Customer User ID** en el momento en que el usuario inicia sesión. Adapty ya usa ese valor cuando no tiene otro, así que ambos sistemas coinciden. **Si los usuarios anónimos pueden realizar compras**, lee el `distinct_id` de PostHog y pásalo a Adapty con `setIntegrationIdentifier`. Llámalo en cada inicio después de `Adapty.activate()`, y de nuevo después de cada `reset()` de PostHog. Los perfiles anónimos no tienen Customer User ID, por lo que el propio ID de PostHog es el único valor que ambos sistemas pueden compartir — consulta [Configura el código de tu app](#configure-your-app-code). Sea cual sea la ruta que elijas, el valor debe sobrevivir a una reinstalación. Adapty crea un nuevo perfil en cada reinstalación, y PostHog un nuevo `distinct_id` anónimo por dispositivo, por lo que ninguno de los dos sistemas mantiene la identidad a través de esa brecha por sí solo. Adapty sí transfiere el acceso de pago entre los perfiles anónimos de un usuario, pero eso vincula niveles de acceso, no identidad analítica: el perfil que hereda sigue enviando eventos con su propio ID. Sin un ID estable que sea propiedad de tu propio backend, un suscriptor que reinstala la app aparece en PostHog como una persona nueva con una renovación y sin ninguna compra previa. :::warning PostHog bloquea ciertos valores durante las fusiones de personas: `null`, `undefined`, `None`, `0`, `anonymous`, `guest`, `distinct_id`, `id`, `email`, `true`, `false`, `[object Object]`, `NaN`, cadenas vacías y variantes entrecomilladas de estos. Asegúrate de que el valor que envíes nunca pueda tomar uno de ellos. PostHog recomienda UUIDs o validar contra esa lista antes de enviarlo. Consulta la [guía de resolución de identidad de PostHog](https://posthog.com/docs/product-analytics/identity-resolution). ::: Los IDs que no coinciden provocan fragmentación de datos — consulta [Un usuario aparece como varias personas](#one-user-appears-as-several-persons-in-posthog). :::important Los eventos de Adapty incluyen propiedades de persona, lo que los convierte en *eventos identificados* según la terminología de PostHog — y PostHog cobra hasta 4 veces más por procesarlos que por los anónimos. Adapty envía un evento por cada cambio en el ciclo de vida de una suscripción, por lo que el volumen es pequeño comparado con el de los análisis del lado del cliente. Aun así, hacer coincidir los IDs merece la pena: PostHog recomienda identificar a los usuarios en casi todos los casos. ::: ## Instrucciones de configuración \{#setup-instructions\} ### Copia el token de tu proyecto de PostHog \{#copy-your-posthog-project-token\} 1. Inicia sesión en el despliegue que aloja tus datos — [US Cloud](https://us.posthog.com/) o [EU Cloud](https://eu.posthog.com/) — y selecciona el proyecto que quieres conectar. 2. Ve a **Settings** > **Project** > **General** y busca la sección **Project token & ID**. 3. Copia el **Project token**. Empieza por `phc_`. PostHog lo describe como de solo escritura y seguro para publicar, por lo que no necesitas rotarlo. ### Configurar Adapty 1. Abre [**Integrations** > **PostHog**](https://app.adapty.io/integrations/posthog) en el Adapty Dashboard. 2. Activa el interruptor de **PostHog**. 3. Pega el token en **Project API key**. Adapty lo valida contra PostHog al guardar — si el token no es válido, falla de inmediato en lugar de descartar los eventos en silencio. 4. La sección "server location" depende de tu configuración. - Si utilizas **PostHog Cloud**, establece la **Region** según el entorno en el que iniciaste sesión: `US Cloud` o `EU Cloud`. Deja el campo PostHog Instance URL vacío. - Si utilizas **tu propia instancia**, selecciona **Self-hosted** e introduce la URL en el campo **PostHog Instance URL**. No selecciones ninguna región. Adapty debe poder conectarse a la instancia sin proxy ni VPN. 5. En **How the revenue data should be send**, elige qué dato de ingresos envía Adapty. Las tres opciones coinciden con las [vistas de ingresos de Adapty Analytics](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue), de modo que tu elección también determina la vista con la que deberían coincidir tus cifras en PostHog. | Opción | Qué envía Adapty | | --- | --- | | **Gross revenue** | El importe total que pagó el comprador, antes de comisiones e impuestos. Es el valor predeterminado. | | **Proceeds after store commission** | El importe menos la comisión del store, pero con impuestos incluidos. | | **Proceeds after store commission and taxes** | El importe menos ambos conceptos. | 6. Configura las opciones restantes: | Opción | Al activarla | Valor por defecto | | --- | --- | --- | | **Report user's currency** | Adapty reporta cada venta en la moneda en que pagó el comprador, en lugar de USD. | Off | | **Send trial price** | Los inicios de prueba no llevan ingresos por defecto. Activa esto para asignarles un precio de marcador y aparecerá el campo **Trial price percentage** — establécelo como el porcentaje del precio de la suscripción que debe reportar la prueba. Con `60%`, una suscripción de $10 envía $6. | Off | | **Exclude historical events** | Adapty omite los eventos ocurridos antes de que el usuario instalara una versión que incluya el SDK de Adapty. | **On** | 7. Cambia el nombre o desactiva eventos individuales en la sección **Events names**. PostHog acepta cualquier nombre de evento que no esté vacío, así que usa el que encaje con tu taxonomía existente. 8. Haz clic en **Save**. ### Mantén los datos sandbox fuera de producción \{#keep-sandbox-data-out-of-production\} Adapty envía transacciones sandbox y de producción a través de la misma integración, por lo que ambas llegan al mismo proyecto de PostHog. La integración utiliza una sola **Project API key**, sin ninguna clave sandbox separada que apuntar a otro lugar. Darle a tu build de desarrollo su propio proyecto de PostHog tampoco evitará que los eventos sandbox lleguen al de producción. El sandbox es una propiedad de la **transacción del store**, no de tu build. Las compras realizadas durante la revisión de App Store y mediante TestFlight son transacciones sandbox procedentes de un build de producción. En su lugar, sepáralos con consultas. Cada evento incluye una propiedad `environment` con el valor `Sandbox` o `Production`. Filtra por ella para aislar los ingresos reales: ```sql WHERE properties.environment = 'Production' ``` ### Configura el código de tu app \{#configure-your-app-code\} 1. Solicita al SDK de PostHog el `distinct_id` actual: - **En cada inicio de la app**, después de `Adapty.activate()` y antes de que pueda producirse cualquier compra. PostHog atribuye cualquier evento anterior a [una persona diferente](#one-user-appears-as-several-persons-in-posthog). - **Después del `reset()` de PostHog**, que la mayoría de las apps ejecutan al cerrar sesión. `reset()` genera un nuevo ID anónimo y no lo vincula a la persona anterior, por lo que un valor desactualizado sigue apuntando al usuario que acaba de cerrar sesión. 2. Pásalo a Adapty con `setIntegrationIdentifier()`. :::note No es necesario hacer ninguna llamada adicional después de llamar a `identify()` de PostHog. PostHog fusiona la persona anónima con la identificada, por lo que los eventos de Adapty se resuelven de la misma manera. Consulta [Asociar el ID de Adapty con el de PostHog](#match-adaptys-id-to-posthogs). ::: :::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 let distinctId = PostHogSDK.shared.getDistinctId() do { try await Adapty.setIntegrationIdentifier(.posthogDistinctUserId(distinctId)) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)"> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier( AdaptyIntegrationIdentifier.posthogDistinctUserId(PostHog.distinctId()) ) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="java" label="Android (Java)"> ```java showLineNumbers Adapty.setIntegrationIdentifier( AdaptyIntegrationIdentifier.posthogDistinctUserId(PostHog.distinctId()), error -> { if (error != null) { // handle the error } } ); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)"> ```dart showLineNumbers try { final distinctId = await Posthog().getDistinctId(); await Adapty().setIntegrationIdentifier( key: "posthog_distinct_user_id", value: distinctId, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native (TS)"> ```typescript showLineNumbers const posthog = usePostHog(); try { await adapty.setIntegrationIdentifier( "posthog_distinct_user_id", posthog.getDistinctId() ); } catch (error) { // handle `AdaptyError` } ``` </TabItem> <TabItem value="capacitor" label="Capacitor (TS)"> El plugin de Capacitor de PostHog está mantenido por la comunidad por [Capawesome](https://capawesome.io/plugins/posthog/), no por PostHog. Lee el distinct ID con el método `getDistinctId` del plugin y pásalo a Adapty con `setIntegrationIdentifier`. </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform"> Lee el distinct ID con el método [`getDistinctId()`](https://posthog.com/docs/libraries/kmp) de PostHog y pásalo a Adapty con `setIntegrationIdentifier`. </TabItem> <TabItem value="unity" label="Unity (C#)"> PostHog distribuye un [SDK oficial de Unity](https://posthog.com/docs/libraries/unity) para Windows, Mac, Linux, iOS, Android y WebGL. Lee `PostHog.DistinctId` y pásalo a Adapty con `Adapty.SetIntegrationIdentifier(key, value, completionHandler)`. </TabItem> </Tabs> ### Verificar la integración \{#verify-the-integration\} 1. Realiza una compra en sandbox y abre el **Event Feed** en el Adapty Dashboard. Cada intento de entrega aparece con su resultado. Adapty verifica tu **Project API key** y la URL de la instancia al guardar, por lo que los fallos en esta etapa son poco frecuentes. Las causas más habituales: - **Una clave que dejó de funcionar.** PostHog devuelve `401` si eliminas la clave de integración. - **Una instancia que dejó de responder.** Adapty deja de esperar después de 10 segundos. Puede afectar a despliegues autohospedados. Pasa el cursor sobre un evento fallido para leer la respuesta de PostHog. 2. En PostHog, abre la vista [**Activity**](https://us.posthog.com/activity/explore) y busca el evento. La propiedad `environment` de tu compra debería ser `Sandbox`. 3. Abre el perfil de esa persona y comprueba la pestaña **Distinct IDs**. Los eventos propios de tu app y los eventos de servidor de Adapty deberían pertenecer a una misma persona. Si aparecen dos personas para el mismo usuario, significa que los IDs no coinciden — consulta [Un usuario aparece como varias personas](#one-user-appears-as-several-persons-in-posthog). :::note Los eventos de Adapty nunca llegan al output de depuración de PostHog de tu propia app. Adapty los envía desde sus servidores, por lo que nunca pasan por el SDK de tu app. Un log local vacío no dice nada sobre el estado de la integración. ::: ## Informar ingresos en PostHog \{#report-revenue-in-posthog\} Adapty Analytics sigue siendo la fuente de verdad para las cifras de ingresos, ya que calcula a partir de los datos completos del store, mientras que PostHog solo recibe lo que esta integración le reenvía. Si también quieres ver los ingresos en un dashboard de PostHog junto a tus métricas de producto, la función [Revenue Analytics](https://posthog.com/docs/revenue-analytics/managed-views) de PostHog los lee a partir de las propiedades de evento que tú indiques. Abre **Data management** > **Revenue** en PostHog y configura: | Campo de PostHog | Propiedad de Adapty | | --- | --- | | Revenue | `price_usd`, `proceeds_usd` o `net_revenue_usd` — elige la opción que seleccionaste en [Cómo se deben enviar los datos de ingresos](#configure-adapty) | | Currency | `currency`, o establece una moneda estática si reportas en USD | | Product | `vendor_product_id` | | Subscription | `original_transaction_id` | Deja la opción "values are in cents" de PostHog **desactivada**. Adapty envía importes en decimal, no en unidades menores. ## Estructura de eventos de PostHog \{#posthog-event-structure\} Adapty envía los eventos que hayas habilitado en la sección **Events names** de la [página de integración de PostHog](https://app.adapty.io/integrations/posthog), con una solicitud de captura por evento: ```json showLineNumbers { "api_key": "phc_YOUR_PROJECT_TOKEN", "distinct_id": "john.doe@example.com", "timestamp": "2026-01-08T11:06:12+00:00", "event": "subscription_started", "properties": { "$ip": "10.168.1.1", "$geoip_time_zone": "America/New_York", "$geoip_disable": true, "$set": { "email": "user@example.com", "first_name": "John", "last_name": "Doe", "birthday": "1990-01-01", "gender": "male", "os": "iOS" }, "*": "{{other_event_properties}}" } } ``` | Parámetro | Tipo | Descripción | | --- | --- | --- | | `api_key` | String | Tu **Project API key** de PostHog. | | `distinct_id` | String | Identifica a la persona en PostHog. Adapty usa el primer valor que encuentra — consulta [prioridad de distinct ID](#distinct-id-priority). | | `timestamp` | Fecha y hora ISO 8601 | Cuándo ocurrió el evento. Las renovaciones y conversiones de prueba pueden tener fecha en el futuro — consulta [Los eventos aparecen en PostHog antes de que ocurran](#events-appear-in-posthog-before-they-happen). | | `event` | String | El nombre que estableciste en la sección **Events names**. | | `properties` | Object | Las [propiedades de evento](messaging#event-properties) de Adapty, las [propiedades de IP y ubicación](#ip-and-location-properties) y [`$set`](#person-properties). Adapty omite cualquier propiedad sin valor.| Hay cinco propiedades exclusivas de webhooks que nunca aparecen aquí; consulta [Limitaciones](#limitations). ### Prioridad del Distinct ID \{#distinct-id-priority\} Adapty utiliza el primer valor que detecta: | Prioridad | Valor | Establecido por | | --- | --- | --- | | 1 | `posthog_distinct_user_id` | Tu llamada a `setIntegrationIdentifier` | | 2 | **Customer User ID** | `Adapty.activate()` o `Adapty.identify()` | | 3 | ID de perfil interno de Adapty | Adapty, siempre presente | Adapty resuelve este orden por evento, no una vez por usuario. Un evento que se dispara antes de que llegue tu llamada a `setIntegrationIdentifier` queda registrado bajo un ID de menor prioridad, y PostHog crea una segunda persona para el mismo usuario. Elige una de las dos configuraciones en [Vincular el ID de Adapty con el de PostHog](#match-adaptys-id-to-posthogs) y coloca el valor antes de que pueda dispararse cualquier evento. Para los perfiles que ya se han dividido, consulta [Un usuario aparece como varias personas](#one-user-appears-as-several-persons-in-posthog). ### Propiedades de IP y ubicación \{#ip-and-location-properties\} Para segmentar los eventos de Adapty por ubicación, usa las propiedades `store_country` y `profile_country`. Adapty desactiva la búsqueda de ubicación de PostHog, por lo que PostHog no añade valores `$geoip_*` propios. Las tres propiedades que se indican a continuación se aplican por evento, por lo que la configuración de tu proyecto y tus propios eventos no se ven afectados. | Propiedad | Valor | Efecto | | --- | --- | --- | | `$ip` | La dirección IP del usuario | PostHog la almacena en el evento. Adapty envía el mismo valor como encabezado `x-forwarded-for`. | | `$geoip_time_zone` | La zona horaria del usuario | Adapty establece este valor directamente. | | `$geoip_disable` | Siempre `true` | Desactiva la búsqueda de ubicación de PostHog para este evento. | ### Propiedades de persona \{#person-properties\} Todo lo que hay dentro de `$set` se convierte en una propiedad de **persona** en PostHog, en lugar de una propiedad de evento. PostHog asocia las propiedades de persona al usuario en sí, no a un evento concreto, por lo que describen el estado actual del usuario y no un momento puntual en el tiempo. Adapty omite cualquier campo para el que no tenga valor, y elimina `$set` por completo cuando no hay ninguno. | Parámetro | Tipo | Descripción | | --- | --- | --- | | `email` | String | La dirección de correo electrónico del usuario. | | `first_name` | String | El nombre del usuario. | | `last_name` | String | El apellido del usuario. | | `birthday` | String (date) | La fecha de nacimiento del usuario. | | `gender` | String | El género del usuario. | | `os` | String | El sistema operativo del dispositivo del usuario. | ## Limitaciones \{#limitations\} - **Sin nivel de acceso ni estado de suscripción.** El evento `access_level_updated` es exclusivo de las [integraciones de webhook](webhook), por lo que Adapty no envía a PostHog ningún campo que describa a qué tiene acceso el usuario actualmente — consulta [Cómo distinguir a un suscriptor de un usuario gratuito](#how-to-tell-a-subscriber-from-a-free-user). - **Sin relleno histórico.** Adapty reenvía eventos desde el momento en que habilitas la integración. Las compras anteriores nunca llegan a PostHog. - **PostHog no geolocaliza los eventos de Adapty.** PostHog infiere la ubicación a partir de la dirección IP del origen del evento. El origen de los eventos de Adapty es siempre un servidor de Adapty, no el dispositivo del usuario. Para evitar la contaminación de datos, Adapty indica a PostHog que omita la búsqueda. Adapty rellena `$geoip_time_zone`, `store_country` y `profile_country`, pero no datos de ubicación más precisos. - **No puedes filtrar eventos de Adapty por origen.** PostHog registra el SDK que envía en `$lib` — `posthog-ios`, `posthog-android`, `web` — pero Adapty publica directamente en la API de PostHog sin intermediación del SDK, por lo que esa propiedad permanece vacía. Filtra por nombre de evento en su lugar. ## Solución de problemas \{#troubleshooting\} - [Los eventos no aparecen en PostHog](#events-dont-appear-in-posthog) - [`access_level_updated` aparece como fallido en el Event Feed](#access_level_updated-shows-as-failed-in-the-event-feed) - [Un usuario aparece como varias personas en PostHog](#one-user-appears-as-several-persons-in-posthog) - [Los ingresos en PostHog no coinciden con Adapty Analytics](#revenue-in-posthog-doesnt-match-adapty-analytics) - [Los eventos aparecen en PostHog antes de que ocurran](#events-appear-in-posthog-before-they-happen) - [Sin datos de país o ciudad en los eventos de Adapty](#no-country-or-city-data-on-adapty-events) - [Cómo distinguir a un suscriptor de un usuario gratuito](#how-to-tell-a-subscriber-from-a-free-user) - [Las vistas de paywall no aparecen en PostHog](#paywall-views-dont-appear-in-posthog) #### Los eventos no aparecen en PostHog \{#events-dont-appear-in-posthog\} - Primero comprueba el **Event Feed** de Adapty. Una entrega fallida muestra el error que devolvió PostHog. - Una entrega exitosa no garantiza que PostHog haya conservado el evento. PostHog responde `200 OK` en cuanto el payload y la clave son válidos, pero descarta silenciosamente los eventos que no tienen nombre o tienen un `distinct_id` vacío. - Confirma que el evento que buscas está habilitado en la configuración de la integración. - Si alojas PostHog tú mismo, asegúrate de que el servidor acepta las solicitudes POST de Adapty en `/capture`. Una configuración correcta no garantiza que este acceso exista: Adapty usa un endpoint diferente para verificar la validez de tu clave. #### `access_level_updated` aparece como fallido en el Event Feed `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. #### Un usuario aparece como varias personas en PostHog PostHog no puede deshacer la mayoría de las divisiones a posteriori — consulta [Hacer coincidir el ID de Adapty con el de PostHog](#match-adaptys-id-to-posthogs). ##### Por qué divergen los IDs Cada evento que Adapty envía incluye un `distinct_id` del perfil de Adapty del usuario — consulta [Prioridad de Distinct ID](#distinct-id-priority). Adapty lee ese valor en el momento de enviar el evento, no cuando tu app llama a `setIntegrationIdentifier`. Si el `distinct_id` de un evento de Adapty difiere del `distinct_id` interno del instalado de la app, PostHog atribuye ambos tipos de eventos a dos personas distintas. Tres situaciones provocan este desajuste: 1. **Tu app nunca llama a `setIntegrationIdentifier` en una de las plataformas.** Adapty recurre al Customer User ID o al ID de perfil anónimo. Compruébalo en todas las plataformas en las que publiques. 2. **La llamada a `setIntegrationIdentifier` ocurre demasiado tarde.** Los eventos de suscripción que ocurren antes de la llamada llevan el ID de respaldo. 3. **El ID que envías a PostHog con `identify()` difiere del que estableces como identificador de integración.** Adapty conserva el valor que pasaste por última vez y nunca lo actualiza por su cuenta. El método `reset()` de PostHog asigna un nuevo ID anónimo, lo que deja a Adapty con el anterior — así que llama a `setIntegrationIdentifier` de nuevo después de cada `reset()`. Corrige los tres problemas y PostHog registrará a una sola persona por usuario a partir de entonces. ##### Fusiona los IDs de usuario divergentes en la primera llamada a identify() \{#merge-diverging-user-ids-at-the-first-identify-call\} Tu app tiene una sola oportunidad para reconciliar los IDs divergentes: su **primera llamada** al método `identify()` de PostHog. PostHog fusiona los eventos de la instalación de la app con la persona que indiques en esa llamada, así que usa el ID que envía Adapty — consulta [Vincula el ID de Adapty con el de PostHog](#match-adaptys-id-to-posthogs). Tras esa llamada, PostHog considera la instalación de la app como identificada y rechaza fusionar dos personas ya identificadas. ##### Comprueba si hay una fusión rechazada \{#check-for-a-refused-merge\} Para confirmar que PostHog registró un usuario como dos personas, abre [**Data management** > **Ingestion warnings**](https://us.posthog.com/data-management/ingestion-warnings) en PostHog y busca el error `Refused to merge an already identified user`. PostHog también bloquea las fusiones cuando el ID es uno de sus valores reservados; consulta [Hacer coincidir el ID de Adapty con el de PostHog](#match-adaptys-id-to-posthogs) para ver la lista. ##### Reparar una división existente \{#repair-an-existing-split\} Ni `identify()` ni `alias()` pueden recuperar una división una vez que la [ventana de fusión](#merge-diverging-user-ids-at-the-first-identify-call) se ha cerrado. Solo `$merge_dangerously` de PostHog puede forzar la fusión. PostHog lo documenta como irreversible, sin salvaguardas, y pensado como una recuperación puntual de problemas de implementación. Se envía como un evento, no como una configuración, y nombra a dos personas. La dirección decide cuál sobrevive: | Campo | Valor | | --- | --- | | `distinct_id` | La persona que sobrevive al merge | | `properties.alias` | La persona que se fusiona en ella — sus eventos y `distinct_id` se transfieren a la superviviente | Decide qué lado sobrevive antes de enviar nada. La persona de Adapty contiene el historial de suscripciones, y la persona de tu app contiene el comportamiento in-app. Prueba con un único usuario y verifica el resultado antes de hacer cualquier reparación masiva. El artículo [How to merge users](https://posthog.com/docs/product-analytics/identify#how-to-merge-users) de PostHog incluye el payload para cada uno de sus SDKs. #### Los ingresos en PostHog no coinciden con Adapty Analytics \{#revenue-in-posthog-doesnt-match-adapty-analytics\} Adapty y PostHog procesan los mismos eventos de forma diferente. Estas diferencias explican prácticamente cualquier discrepancia. - **Cada evento de Adapty incluye tres importes de ingresos, uno por cada opción de [Cómo se deben enviar los datos de ingresos](#configure-adapty).** Así se corresponden con las propiedades de PostHog: | Adapty Analytics | Propiedad del evento (en USD) | Propiedad del evento (en la moneda del comprador) | | --- | --- | --- | | Ingresos brutos | `price_usd` | `price_local` | | Ingresos tras comisión de la store | `proceeds_usd` | `proceeds_local` | | Ingresos tras comisión de la store e impuestos | `net_revenue_usd` | `net_revenue_local` | Comparar entre filas produce una diferencia igual a la comisión, los impuestos o ambos. Adapty envía las seis propiedades en cada evento, independientemente de cómo esté configurado **Report user's currency**. - **Adapty cuenta cada evento de ingresos en un período; un insight de PostHog solo cuenta los eventos que añades.** Si omites `subscription_renewed`, perderás la mayor parte de los ingresos de cualquier app establecida. - **El rango de fechas de Adapty cubre todo el último día; un filtro `timestamp` se detiene en el instante que le indiques.** El rango 1 jul – 15 jul de Adapty incluye todos los eventos hasta el 15 jul a las 23:59:59. En PostHog, `timestamp < 2026-07-15` elimina ese día completo — usa `timestamp < 2026-07-16`. - **Adapty Analytics separa el sandbox de la producción; PostHog los mezcla.** Filtra por `properties.environment = 'Production'` — consulta [Mantener los datos de sandbox fuera de producción](#keep-sandbox-data-out-of-production). - **Adapty usa la zona horaria de informes de tu app; PostHog recibe UTC.** Las integraciones siempre reciben marcas de tiempo en UTC, independientemente de lo que configures en [App Settings](general#3-reporting-timezone). Una compra a las 23:30 UTC del 1 de julio aparece el 2 de julio en Adapty si tu zona horaria de informes es +02:00, mientras que en PostHog permanece el 1 de julio. - **Faltan eventos históricos en PostHog.** Hay dos límites distintos que impiden que los eventos antiguos lleguen, por lo que las suscripciones y renovaciones anteriores solo llegan a Adapty Analytics. Los eventos capturados por tu propio SDK de PostHog no se ven afectados. - **Exclude historical events** (Excluir eventos históricos), un interruptor en la [página de integración de PostHog](https://app.adapty.io/integrations/posthog), está activado por defecto. Adapty no envía eventos con fecha anterior a la existencia del perfil, y el Event Feed marca cada uno como expirado. Los eventos de Adapty de un usuario comienzan, por tanto, en su primer lanzamiento de una versión con Adapty. Desactívalo para permitir el paso de eventos con fecha retroactiva a partir de ese momento. - **No historical backfill** (Sin relleno histórico) es una [limitación permanente](#limitations). Adapty envía los eventos a medida que los procesa y nunca recupera los eventos que procesó antes de que habilitaras la integración. - **Los eventos que desactivaste en la configuración de la integración nunca llegan a PostHog.** Adapty Analytics sigue contándolos. Consulta la sección **Events names** en [Configurar Adapty](#configure-adapty). #### Los eventos aparecen en PostHog antes de que ocurran \{#events-appear-in-posthog-before-they-happen\} Para las renovaciones y conversiones de prueba, Apple notifica a Adapty antes de que ocurra el evento. Adapty reenvía estos eventos de inmediato, con el `timestamp` futuro sin cambios. Adapty Analytics los retiene hasta que ese momento llegue, por lo que PostHog muestra eventos que Adapty aún no ha reportado. Ambos son correctos. Filtra por `timestamp` para excluirlos; consulta [Timestamps de eventos con fechas futuras](how-profiles-work#event-timestamps-with-future-dates-appleios). #### Sin datos de país o ciudad en los eventos de Adapty \{#no-country-or-city-data-on-adapty-events\} Usa en su lugar las propiedades `store_country` y `profile_country`. Los valores `$geoip_*` propios de PostHog se quedan vacíos en los eventos de Adapty — consulta [Propiedades de IP y ubicación](#ip-and-location-properties). #### Cómo distinguir a un suscriptor de un usuario gratuito \{#how-to-tell-a-subscriber-from-a-free-user\} Los informes de eventos de Adapty no muestran el acceso actual del usuario. `$set` incluye únicamente `email`, `first_name`, `last_name`, `birthday`, `gender` y `os`. Las propiedades de evento que describen los niveles de acceso solo se rellenan en `access_level_updated`, y Adapty comparte ese evento exclusivamente con la integración de webhook. Hay dos opciones: - Deriva el estado en PostHog a partir del historial de eventos. Un usuario cuyo evento más reciente de Adapty sea `subscription_started`, `subscription_renewed` o `trial_converted` tiene acceso actualmente; uno cuyo evento más reciente sea `subscription_expired`, `trial_expired` o `subscription_refunded` no lo tiene. - Usa la [integración de webhooks](webhook) para recibir `access_level_updated` y reenvíalo a PostHog por tu cuenta. La API de captura de PostHog espera su propio formato de payload, por lo que necesitarás un paso de transformación de tu parte: el payload del webhook de Adapty no se puede enviar directamente a PostHog sin modificaciones. #### Las vistas de paywall no aparecen en PostHog \{#paywall-views-dont-appear-in-posthog\} El SDK de Adapty captura las interacciones con paywall, flow y onboarding únicamente para Adapty Analytics. El servidor de Adapty reenvía los eventos de suscripción a las integraciones, pero estas interacciones no son eventos de suscripción. Los webhooks tampoco las incluyen. Captúralas con el SDK de PostHog en el lugar donde muestres el paywall. --- # File: splitmetrics --- --- title: "SplitMetrics Acquire" description: "Usa SplitMetrics con Adapty para pruebas A/B y optimización de suscripciones." --- 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. Y puedes hacer seguimiento de tus usuarios durante meses para saber cuánto dinero generan 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, basados en la atribución de Apple Search Ads. No añade ningún dato de atribución a Adapty, ya que obtenemos todo lo que necesitamos 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\} ### Los eventos no llegan a SplitMetrics Acquire \{#events-dont-reach-splitmetrics-acquire\} Si la integración con SplitMetrics Acquire no funciona a pesar de tener la configuración correcta: - Asegúrate de haber habilitado el toggle **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 tienen atribución ASA no orgánica. Solo los perfiles con atribución ASA detallada y no orgánica enviarán sus eventos a Adapty. ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. ## 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 toque en el anuncio. | | `open_time` | String | Fecha y hora de 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 del 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: messaging --- --- title: "Integraciones de servicios de mensajería" description: "Usa las herramientas de mensajería de Adapty para mejorar el engagement y la retención de suscripciones." --- Captar usuarios en el mercado móvil no es fácil ni barato. Por eso, gestionar bien a los usuarios que ya tienes mejora tu economía unitaria, especialmente en nichos muy competitivos. Adapty proporciona información en tiempo real sobre las acciones de pago principales de tus usuarios. Sabemos cuándo un cliente inició una prueba, si tuvo problemas con su pago, o si compró una suscripción y decidió cancelarla más adelante. Todos estos eventos y otros muestran el cambio en el estado del cliente. Y este es el mejor momento para reaccionar: enviar una oferta, un regalo personalizado o cualquier acción de retención. Las plataformas de notificaciones push permiten describir a un usuario con etiquetas estándar y personalizadas para construir un sistema automático de retención eficaz. Para que este sistema funcione, solo necesitas eventos de activación que indiquen al sistema cuándo es el momento de enviar un mensaje. Estos eventos llegarán a la plataforma de push desde Adapty a través de la integración configurada. Elige a continuación el servicio que necesitas integrar y sigue las instrucciones: - [Braze](braze) - [OneSignal](onesignal) - [Pushwoosh](pushwoosh) - [Slack](slack) :::note ¿No encuentras tu proveedor de atribución? ¡Cuéntanos! [Crea una solicitud de función](https://adapty.featurebase.app/en?b=6979f233ebd3cffd4f425ba0) y lo tendremos en cuenta. ::: ## Propiedades del evento \{#event-properties\} Los eventos de webhook se envían en formato JSON. Todos los eventos siguen la misma estructura, pero sus campos varían según el tipo de evento, el store y tu configuración específica. :::note Adapty convierte otras divisas a USD utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo se **fija en el momento de la transacción** — los cambios futuros no afectan al resultado de la conversión. ::: | Propiedad | Tipo | Descripción | | ----------------------------- | ------------- | ------------------------------------------------------------ | | **profile_id** | uuid | ID de usuario de Adapty. | | **currency** | str | Moneda local (por defecto USD). | | **price_usd** | float | Precio del producto antes de la comisión de Apple/Google. Ingresos. | | **proceeds_usd** | float | Precio del producto después de la comisión de Apple/Google. Ingresos netos. | | **net_revenue_usd** | float | Ingresos netos (ingresos tras la comisión de Apple/Google e impuestos) en USD. Puede estar vacío. | | **price_local** | float | Precio del producto antes de la comisión de Apple/Google en moneda local. Ingresos. | | **proceeds_local** | float | Precio del producto después de la comisión de Apple/Google en moneda local. Ingresos netos. | | **transaction_id** | str | Identificador único de una transacción, como una compra o una renovación. | | **original_transaction_id** | str | Identificador de la transacción de la compra original. | | **purchase_date** | ISO 8601 date | Fecha y hora de la compra del producto. | | **original_purchase_date** | ISO 8601 date | Fecha y hora de la compra original. | | **environment** | str | Puede ser _Sandbox_ o _Production_. | | **vendor_product_id** | str | ID del producto en Apple App Store, Google Play Store o Stripe. Para un [plan de compromiso de 12 meses](app-store-12-month-commitment), el valor es `<product_id>:monthly`. | | **base_plan_id** | str | [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. | | **event_datetime** | ISO 8601 date | Fecha y hora del evento. | | **store** | str | Puede ser _app_store_ o _play_store_. | | **trial_duration** | str | Duración del período de prueba en días. Se envía en el formato "{} days", por ejemplo, "7 days". | | **cancellation_reason** | str | <p>Motivo por el que el usuario canceló una suscripción.</p><p></p><p>Puede ser</p><p>iOS & Android</p><p>_voluntarily_cancelled_, _billing_error_, _refund_</p><p>iOS</p><p>_price_increase_, _product_was_not_available_, _unknown_</p><p>Android</p><p>_new_subscription_replace_, _cancelled_by_developer_</p> | | **subscription_expires_at** | ISO 8601 date | Fecha de expiración de la suscripción. Normalmente en el futuro. | | **consecutive_payments** | int | Número de períodos en los que el usuario lleva suscrito sin interrupciones. Incluye el período actual. | | **rate_after_first_year** | bool | Booleano que indica que la suscripción cumple los requisitos para una tasa de comisión reducida (normalmente el 15%) tras un año de renovación continua. Las tasas de comisión varían según la elegibilidad del 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. | | **promotional_offer_id** | str | ID de la oferta promocional tal como se indica en la sección de productos del Adapty Dashboard. | | **store_offer_category** | str | Puede ser _introductory_ o _promotional_. | | **store_offer_discount_type** | str | Puede ser _free_trial_, _pay_as_you_go_ o _pay_up_front_. | | **paywall_name** | str | Nombre del paywall donde se originó la transacción. Para un flow, el ID de la pantalla del paywall (`scr_...`). | | **paywall_revision** | int | Revisión del paywall donde se originó la transacción. El valor está establecido en 1. | | **developer_id** | str | ID (SDK) del desarrollador del placement donde se originó la transacción. | | **ab_test_name** | str | Nombre de la prueba A/B donde se originó la transacción. Para una prueba A/B entre placements, el nombre de la prueba en un placement (`<test name> child-N`). Para agrupar una prueba entre placements, usa `ab_test_id`. | | **ab_test_revision** | int | Revisión de la prueba A/B donde se originó la transacción. El valor está establecido en 1. | | **ab_test_id** | uuid | ID de la prueba A/B donde se originó la transacción. Para una prueba A/B entre placements, el mismo en todos los placements. | | **ab_test_parent_name** | str | Nombre de la prueba A/B entre placements. Solo está presente en pruebas A/B entre placements. | | **ab_test_variant_id** | uuid | ID de la variante de la prueba A/B donde se originó la transacción. Úsalo para dividir las transacciones por variante de prueba. | | **ab_test_variant_name** | str | Nombre de la variante de la prueba A/B. | | **flow_id** | uuid | ID del flow donde se originó la transacción. | | **flow_name** | str | Nombre del flow donde se originó la transacción. | | **flow_variation_id** | uuid | ID de la variante del flow en este placement, el mismo que `flow.variationId` en el SDK. | | **cohort_name** | str | Nombre de la audiencia a la que pertenece el perfil. | | **profile_event_id** | uuid | ID de evento único que puede usarse para deduplicación. | | **store_country** | str | El país que nos envía la store. | | **profile_ip_address** | str | IP del perfil (puede ser IPv4 o IPv6, con preferencia por IPv4 cuando esté disponible). Se actualiza cada vez que cambia la IP del dispositivo. | | **profile_country** | str | Determinado por Adapty según la IP del perfil. | | **profile_total_revenue_usd** | float | Ingresos totales del perfil, incluidos los reembolsos. | | **variation_id** | uuid | ID único del paywall donde se realizó la compra. Para un flow, el ID de la pantalla del paywall dentro del flow, no la variante de la prueba A/B. | | **access_level_id** | str | ID del nivel de acceso de pago. | | **is_active** | bool | Booleano que indica si el nivel de acceso de pago está activo para el perfil. | | **will_renew** | bool | Booleano que indica si el nivel de acceso de pago se renovará. | | **is_refund** | bool | Booleano que indica si la transacción ha sido reembolsada. | | **is_lifetime** | bool | Booleano que indica si el nivel de acceso de pago es de por vida. | | **is_in_grace_period** | bool | Booleano que indica si el perfil está en período de gracia. | | **starts_at** | ISO 8601 date | Fecha y hora en que el nivel de acceso de pago comienza para el usuario. | | **renewed_at** | ISO 8601 date | Fecha y hora en que comenzó más recientemente el período actual del acceso de pago. Hasta la primera renovación, coincide con `activated_at`. | | **expires_at** | ISO 8601 date | Fecha y hora en que expirará el acceso de pago. | | **activated_at** | ISO 8601 date | Fecha y hora en que se activó el acceso de pago. | | **billing_issue_detected_at** | ISO 8601 date | Fecha y hora del problema de facturación. | | **profile_has_access_level** | Bool | Booleano que indica si el perfil tiene un nivel de acceso activo (solo Webhook). | Cada evento incluye las siguientes propiedades: `transaction_id, original_transaction_id, purchase_date, original_purchase_date, environment, vendor_product_id, event_datetime, store`. Además, algunos eventos tienen propiedades adicionales. Para los eventos `subscription_refunded` y `non_subscription_purchase_refunded`, es obligatorio proporcionar los valores de `price_usd` y `proceeds_usd` como propiedades adicionales. | Nombre del evento | Propiedades | | :---------------------------------- | :----------------------------------------------------------- | | **subscription\_initial\_purchase** | price\_usd, proceeds\_usd, subscription\_expires\_at, consecutive\_payments, rate\_after\_first\_year, trial\_duration | | **subscription\_renewed** | price\_usd, proceeds\_usd, subscription\_expires\_at, consecutive\_payments, rate\_after\_first\_year, trial\_duration | | **subscription\_cancelled** | cancellation\_reason, trial\_duration | | **trial\_started** | subscription\_expires\_at, trial\_duration | | **trial\_converted** | price\_usd, proceeds\_usd, subscription\_expires\_at, consecutive\_payments, rate\_after\_first\_year, trial\_duration | | **trial\_cancelled** | cancellation\_reason, trial\_duration | | **non\_subscription\_purchase** | price\_usd, proceeds\_usd | | **billing\_issue\_detected** | subscription\_expires\_at, trial\_duration | | **entered\_grace\_period** | subscription\_expires\_at, trial\_duration | Ejemplo de evento ```json title="Json" { "price_usd": 9.99, "proceeds_usd": 6.99, "transaction_id": "1000000628581600", "original_transaction_id": "1000000628581600", "purchase_date": "2020-02-18T18:40:22.000000+0000", "original_purchase_date": "2020-02-18T18:40:22.000000+0000", "environment": "Sandbox", "vendor_product_id": "premium", "event_datetime": "2020-02-18T18:40:22.000000+0000", "store": "app_store" } ``` Adapty envía eventos a tu servidor y a sistemas analíticos de terceros. La propiedad **profile_ip_address** se sincroniza con la IP actual del dispositivo. Cada vez que los servidores de Adapty reciben información del SDK, la IP se actualiza si difiere de la que tenemos registrada. --- # File: braze --- --- title: "Braze" description: "Integra Braze con Adapty para una gestión de clientes y notificaciones push sin fricciones." --- 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, correo electrónico, 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 cómo se comportan tus suscriptores, conocer sus preferencias y usar esa información para comunicarte con ellos de forma dirigida y efectiva. Por lo tanto, esta integración te permite rastrear eventos de suscripción en tu dashboard de Braze y vincularlos 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 los clientes mediante notificaciones push de Braze tras una integración breve y sencilla como se describe a continuación. ## Cómo configurar la integración con Braze \{#how-to-set-up-braze-integration\} Para conectar Adapty con Braze, abre [**Integrations** → **Braze**](https://app.adapty.io/integrations/braze) en el Adapty Dashboard, activa el interruptor y completa las credenciales: - **REST API key** y **Braze Instance ID** son obligatorios. - **iOS App ID** y **Android App ID** son opcionales. <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. Para crear la **REST API key**, en el dashboard de Braze, ve a **Settings** → **APIs and Identifiers** y haz clic en **Create API Key**. En los permisos de datos de usuario, selecciona `users.track`. Braze no permite modificar los permisos de una clave una vez creada, por lo que si una clave no tiene `users.track`, deberás reemplazarla por una nueva. Para más información, consulta [Creating REST API keys](https://www.braze.com/docs/api/basics#creating-rest-api-keys) en la documentación de Braze. 2. Para encontrar el **Braze Instance ID**, compara la URL de tu dashboard de Braze con la [tabla de instancias](https://www.braze.com/docs/api/basics/#endpoints) en la documentación de Braze. El ID de instancia tiene un formato regional, como `US-03` o `EU-01`. Por ejemplo, `dashboard-03.braze.com` corresponde a `US-03`. 3. Para encontrar el **iOS App ID** y el **Android App ID**, en el dashboard de Braze, ve a **Settings** → **APIs and Identifiers** → **App Identifiers**. Copia el valor de la columna **Identifier** para cada app. Para más información, consulta [App identifier](https://www.braze.com/docs/api/identifier_types#app-identifier) en la documentación de Braze. ## 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 tienen ingresos, como las conversiones de prueba y las renovaciones, Adapty enviará esta información a Braze como compras. :::warning Adapty envía ingresos a Braze únicamente como eventos de compra legacy. Braze ha puesto los eventos de compra legacy en modo de mantenimiento y no los habilita para nuevos clientes. Si tu workspace de Braze no los acepta, Braze rechaza cada evento que tenga ingresos con HTTP 400 y el mensaje `Legacy purchase ingestion is disabled`. Esos eventos, como primeras compras, renovaciones, conversiones de prueba y reembolsos, no llegan a Braze en absoluto, no solo sus ingresos. Antes de activar la integración, asegúrate de que tu workspace de Braze acepta eventos de compra heredados. Para más información, consulta [Purchase events](https://www.braze.com/docs/user_guide/data/activation/events/purchase_events) en la documentación de Braze. ::: [Aquí](messaging#event-properties) encontrarás las especificaciones completas de las propiedades de los eventos enviados a Braze. :::note Atributos de usuario útiles Adapty envía por defecto algunos atributos de usuario para la integración con Braze. Consulta la lista que se incluye 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> ## Solución de problemas \{#troubleshooting\} ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # File: onesignal --- --- title: "OneSignal" description: "Integra OneSignal con Adapty para mejorar la interacción mediante 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(AdaptyIntegrationIdentifier.oneSignalSubscriptionId(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(AdaptyIntegrationIdentifier.oneSignalSubscriptionId(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 ) } } } ``` ```kotlin showLineNumbers // PlayerID val osSubscriptionObserver = OSSubscriptionObserver { stateChanges -> stateChanges?.to?.userId?.let { playerId -> Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.oneSignalPlayerId(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(AdaptyIntegrationIdentifier.oneSignalPlayerId(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 del identificador de usuario en todos los dispositivos y actualizar OneSignal cada vez que cambie el ID de un usuario. Esto simplifica el seguimiento de la actividad y las suscripciones, 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 de OneSignal](https://documentation.onesignal.com/docs/en/users). ## Solución de problemas \{#troubleshooting\} ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # File: pushwoosh --- --- title: "Pushwoosh" description: "Integra Pushwoosh con Adapty para un seguimiento de notificaciones push sin interrupciones." --- Adapty utiliza eventos de suscripción para actualizar las etiquetas del 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 sencilla y rápida, tal como 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(AdaptyIntegrationIdentifier.pushwooshHWID(Pushwoosh.getInstance().hwid)) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.pushwooshHWID(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> ## Solución de problemas \{#troubleshooting\} ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # 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 probablemente no necesita presentación. Con esta integración, podrás recibir notificaciones en Slack cada vez que Adapty registre un evento de ingresos. Puede ser muy útil si quieres celebrar cada momento en que tu MRR sube, o si quieres estar al tanto de cancelaciones de prueba, 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 */ }} /> ## Solución de problemas \{#troubleshooting\} ### `access_level_updated` aparece como fallido en el Event Feed \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` es un **evento exclusivo de webhooks**. Adapty nunca lo envía a esta integración. Sin embargo, Adapty registra un resultado para cada integración habilitada, y un evento no compatible se muestra como un error. --- # File: webhook-and-etl --- --- title: "Integraciones de Webhook y ETL" description: "Configura la integración de webhook y ETL para un seguimiento avanzado de eventos de suscripción." --- Consulta las guías paso a paso de Adapty sobre cómo integrar el SDK de Adapty con opciones de webhook y ETL como Amazon S3 y Google Cloud Storage. Con la integración de webhook, puedes recibir notificaciones en tiempo real sobre acciones y eventos de los usuarios. Estas notificaciones se pueden personalizar y enviar a un endpoint de tu elección, lo que te permite monitorizar y analizar fácilmente los datos de tu app. Usa la siguiente documentación para saber más sobre la integración de webhook de Adapty y cómo implementarla en tu app: - [Webhook](webhook) Adapty ofrece una práctica función que permite realizar entregas automáticas de datos con toda la información de transacciones relacionada con tu app. Con esta función, puedes exportar sin esfuerzo los datos de transacciones a diversos proveedores de almacenamiento en la nube de forma diaria. Los datos se suben como un archivo .csv comprimido en gzip, lo que permite almacenar y analizar la información de manera eficiente. Aprende a gestionar tus datos de forma efectiva y a optimizar su administración siguiendo nuestras guías fáciles de usar: - [Amazon S3](s3-exports) - [Google Cloud Storage](google-cloud-storage) --- # 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, solo tienes que 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á datos de los eventos creados durante todo el día calendario 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. 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 utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo 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 una renovación. | | **original_transaction_id** | Identificador de la transacción de compra original. | | **subscription_expires_at** | Fecha de expiración 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** | Ganancias en USD. Puede estar vacío. | | **net_revenue_usd** | Ingresos netos (ingresos después de 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** | Ganancias en moneda local. Puede estar vacío. | | **net_revenue_local** | Ingresos netos (ingresos después de 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 el UUID, el correo electrónico u otro identificador del usuario. Nulo si no se ha establecido. | | **store** | Puede ser _app_store_ o _play_store_. | | **product_id** | ID del producto en Apple App Store, Google Play Store o Stripe. Para un [plan de compromiso de 12 meses](app-store-12-month-commitment), el valor es `<product_id>:monthly`. | | **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. En una prueba A/B multiplacement, el nombre de la prueba en un placement (`<nombre de la prueba> child-N`). Para agrupar una prueba multiplacement entre placements, usa **ab_test_id**. | | **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. En un flow, el ID de la pantalla del paywall (`scr_...`). El nombre del flow se encuentra en **flow_name**. | | **paywall_revision** | Revisión del paywall donde se originó la transacción. | | **profile_county** | País del perfil determinado por Adapty según 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, que el usuario puede restablecer, para casos de uso publicitario no monetizado. | | **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 o 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 con los [atributos personalizados del usuario](setting-user-attributes#custom-user-attributes). Incluye cualquier atributo personalizado que hayas configurado para enviar desde tu app. 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'} | | **variation_id** | ID único del paywall donde se originó la transacción. En un flow, el ID de la pantalla del paywall dentro del flow, no el de la variante de la prueba A/B. | | **flow_id** | ID del flow donde se originó la transacción. | | **flow_name** | Nombre del flow donde se originó la transacción. | | **flow_variation_id** | ID de la variante del flow en este placement, igual que `flow.variationId` en el SDK. | | **ab_test_id** | ID de la prueba A/B donde se originó la transacción. En una prueba A/B multiplacement, es el mismo en todos los placements. | | **ab_test_variant_id** | ID de la variante de la prueba A/B donde se originó la transacción. Úsalo para dividir las transacciones por variante de prueba. | | **ab_test_variant_name** | Nombre de la variante de la prueba A/B. | | **ab_test_parent_name** | Nombre de la prueba A/B multiplacement. Vacío en otras pruebas A/B. | Las columnas de flow y prueba A/B son las últimas columnas del archivo. Adapty las calcula en el momento de la exportación, por lo que un nombre en la exportación puede diferir del nombre en un evento de webhook anterior si renombraste la prueba A/B, la variante o el flow en ese intervalo. Para saber qué contiene cada columna en cada tipo de prueba A/B, consulta [Campos de flow y prueba A/B](webhook-event-types-and-fields#flow-and-ab-test-fields). A continuación se muestra la estructura de la tabla para las visitas a paywalls: | 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 un 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 utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo 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 la compra original. | | **subscription_expires_at** | Fecha de vencimiento 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 (ingresos después de 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** | Ganancias en moneda local. Puede estar vacío. | | **net_revenue_local** | Ingresos netos (ingresos después de 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 el UUID, el correo electrónico u otro identificador del usuario. Es nulo si no lo has definido. | | **store** | Puede ser *app_store* o *play_store*. | | **product_id** | ID del producto en el Apple App Store, Google Play Store o Stripe. Para un [plan de compromiso de 12 meses](app-store-12-month-commitment), el valor es `<product_id>:monthly`. | | **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. Para una prueba A/B entre placements, el nombre de la prueba en un placement (`<nombre de la prueba> child-N`). Para agrupar una prueba entre placements, usa **ab_test_id**. | | **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. Para un flow, el ID de la pantalla del paywall (`scr_...`). El nombre del flow aparece en **flow_name**. | | **paywall_revision** | Revisión del paywall donde se originó la transacción. | | **profile_country** | País del perfil determinado por Adapty según 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 exclusiva el dispositivo de un usuario. | | **ip_address** | IP del dispositivo (puede ser IPv4 o IPv6; se prefiere 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>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 por cuenta de desarrollador, que el usuario puede restablecer, 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 del creative de atribución. | | **attributes** | JSON con los [atributos personalizados del usuario](setting-user-attributes#custom-user-attributes). Incluirá los atributos personalizados que hayas configurado para enviar desde tu app. Para activarlo, habilita 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'} | | **variation_id** | ID único del paywall donde se originó la transacción. Para un flow, el ID de la pantalla del paywall dentro del flow, no la variante de la prueba A/B. | | **flow_id** | ID del flow donde se originó la transacción. | | **flow_name** | Nombre del flow donde se originó la transacción. | | **flow_variation_id** | ID de la variante del flow en este placement; es el mismo que `flow.variationId` en el SDK. | | **ab_test_id** | ID de la prueba A/B donde se originó la transacción. Para una prueba A/B entre placements, es el mismo en todos los placements. | | **ab_test_variant_id** | ID de la variante de la prueba A/B donde se originó la transacción. Úsalo para dividir las transacciones por variante de prueba. | | **ab_test_variant_name** | Nombre de la variante de la prueba A/B. | | **ab_test_parent_name** | Nombre de la prueba A/B entre placements. Vacío para otras pruebas A/B. | Las columnas de flow y prueba A/B son las últimas columnas del archivo. Adapty las calcula en el momento de la exportación, por lo que un nombre en la exportación puede diferir del nombre en un evento de webhook anterior si has renombrado la prueba A/B, la variante o el flow entre medias. Para saber qué contiene cada columna en cada tipo de prueba A/B, consulta [Campos de flow y prueba A/B](webhook-event-types-and-fields#flow-and-ab-test-fields). ### 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 --- --- title: "Integración de webhooks" description: "Integra webhooks en Adapty para automatizar el seguimiento de eventos de suscripción." --- Un webhook es una forma eficiente de recibir notificaciones en tiempo real sobre [eventos](webhook-event-types-and-fields#webhook-event-types), especialmente para rastrear cambios en suscripciones y compras. Esto te permite monitorear el estado de los suscriptores y reaccionar en consecuencia. A diferencia de las solicitudes a la API que requieren polling constante, un webhook se configura una sola vez y envía datos automáticamente vía HTTP cuando ocurre un evento. :::tip ¿Estás configurando esto con un agente de codificación con IA? Consulta [Gestiona eventos de suscripción de Adapty con webhooks](handle-webhooks-with-ai) para una guía completa en una sola página. ::: Con los webhooks integrados, puedes: - Hacer seguimiento de suscripciones y compras en tu sistema backend. - Automatizar procesos y flujos de trabajo basados en el ciclo de vida de las suscripciones. - Interactuar con los suscriptores recordándoles los beneficios de la app, gestionando decisiones de cancelación y resolviendo problemas de facturación. - Realizar un análisis detallado del comportamiento de los usuarios. **Características de la integración** | Característica de integración | Descripción | | :---------------------------- | :----------------------------------------------------------------------- | | Programación | Actualizaciones en tiempo real | | Dirección de datos | Transmisión de datos unidireccional: de Adapty a tu servidor | | Flow de integración de Adapty | Los eventos se envían desde el servidor de Adapty en cuanto se reciben | ## Eventos enviados al webhook \{#events-sent-to-webhook\} Puedes consultar todos los tipos de eventos que se pueden enviar a un webhook en la página [Tipos de eventos y campos del webhook](webhook-event-types-and-fields). Puedes enviarlos todos o elegir solo algunos. Consulta nuestra página [Flujos de eventos](event-flows) para decidir qué eventos son necesarios y cuáles no. Puedes desactivar los tipos de eventos que no necesites al [configurar tu integración con Webhook](set-up-webhook-integration#configure-webhook-integration-in-the-adapty-dashboard). Allí también puedes reemplazar los ID de eventos predeterminados de Adapty por los tuyos propios si es necesario. **A continuación:** - [Tipos de eventos y campos de webhook](webhook-event-types-and-fields): Explora descripciones detalladas de cada evento y sus campos de datos. - [Flujos de eventos](event-flows): Aprende sobre la secuencia de eventos y sus dependencias. - [Configurar la integración de webhook](set-up-webhook-integration): Guía paso a paso para configurar tu webhook en el Adapty Dashboard. - [Probar la integración de webhook](test-webhook): Asegúrate de que tu webhook está configurado correctamente con nuestras herramientas de prueba. --- # 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 los 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_active | Confirma que la suscripción sigue activa después del evento `subscription_started`. Debes establecer el número de días entre el inicio de la suscripción y esta comprobación. El evento llega si la suscripción sigue activa, incluso cuando el usuario está en un período de gracia. Si la suscripción ha finalizado o el usuario desactivó la renovación automática, no llega nada y Adapty no vuelve a comprobarlo. Este evento solo sigue a las suscripciones compradas sin período de prueba. Una prueba que se convierte envía `trial_converted`, que no inicia la comprobación. | | subscription_renewed | Ocurre cuando se renueva una suscripción y se cobra al usuario. Este evento comienza a partir de la segunda facturación, ya sea en una suscripción con o sin período de prueba. | | subscription_renewal_cancelled | Un 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 sigue 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 mientras mantienen el acceso a las funciones premium. Esta función está disponible a través de la API de Google Play Developer y puede usarse para períodos de prueba gratuitos 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_active | Confirma que la prueba sigue activa después del evento `trial_started`. Debes establecer el número de días entre el inicio de la prueba y esta comprobación. El evento llega si la prueba sigue en curso. Si la prueba ha finalizado o se ha convertido, o el usuario desactivó la renovación automática, no llega nada y Adapty no vuelve a comprobarlo. | | 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 | Un 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 iniciará 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 una prueba finaliza sin convertirse en una suscripción. | | entered_grace_period | Ocurre cuando un intento de pago falla 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. ::: :::note En Google Play, Adapty puede enviar un evento `subscription_started` con todos los campos monetarios establecidos en cero — por ejemplo, cuando Google reemplaza una suscripción por otra o cuando la suscripción comienza con una fase sin cargo. No contabilices estos eventos como ingresos. Cuando Google reemplaza una suscripción, emite un nuevo token de compra, por lo que Adapty reporta la transacción pagada bajo un `store_transaction_id` diferente. ::: ## 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_id": "00000000-0000-0000-0000-000000000000", "ab_test_name": "onboarding_ab_test", "ab_test_revision": 1, "ab_test_variant_id": "00000000-0000-0000-0000-000000000000", "ab_test_variant_name": "Variant B", "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` - `singular_sdid` ### 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 utilizando el tipo de cambio de [currencylayer.com](https://currencylayer.com/) (actualizado cada 8 horas). El tipo 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_id** | UUID | ID de la [prueba A/B de Adapty](ab-tests) en la que se originó la transacción. En una prueba A/B multiplacement, el ID de la prueba multiplacement es el mismo en todos los placements. Consulta [Campos de flow y prueba A/B](#flow-and-ab-test-fields). | | **ab_test_name** | String | Nombre de la [prueba A/B de Adapty](ab-tests) en la que se originó la transacción. En una prueba A/B multiplacement, es el nombre de la prueba en un placement concreto, con el formato `<nombre del test> child-N`, donde N es el número de placement, no el de la variante. Para agrupar una prueba multiplacement entre placements, usa `ab_test_id` y toma su nombre de `ab_test_parent_name`. | | **ab_test_parent_name** | String | Nombre de la prueba A/B multiplacement en la que se originó la transacción. Solo está presente en pruebas A/B multiplacement. | | **ab_test_revision** | Integer | Revisión de la prueba A/B en la que se originó la transacción. | | **ab_test_variant_id** | UUID | ID de la variante de la prueba A/B en la que se originó la transacción. En una prueba A/B de paywall normal, coincide con `variation_id`. En una prueba A/B de flow, coincide con `flow_variation_id`. En una prueba A/B multiplacement, es el mismo para la variante en todos los placements. Úsalo para dividir transacciones por variante de prueba. | | **ab_test_variant_name** | String | Nombre de la variante de la prueba A/B. En una prueba A/B multiplacement, el nombre de la variante según la configuración de la prueba. En otras pruebas, el nombre de la variante, o el nombre del paywall o del flow cuando la variante no tiene nombre. Aparece junto con `ab_test_variant_id`. | | **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>Está 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. `null` en `trial_started` a menos que esté habilitado **Send trial price**. Consulta [Propiedades adicionales de eventos de impuestos e ingresos](webhook-event-types-and-fields#additional-tax-and-revenue-event-properties). | | **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. Para 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 se desconoce el precio estándar (consulta `original_price_local`). Siempre `null` para las ofertas pay-up-front de App Store: el único cargo anticipado 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. | | **flow_id** | UUID | ID del [flow](adapty-flow-builder) en el que se originó la transacción. Presente en toda transacción realizada en un flow, esté o no en una prueba A/B. | | **flow_name** | String | Nombre del flow en el que se originó la transacción. Aparece junto con `flow_id`. | | **flow_variation_id** | UUID | ID de la variante del flow en este placement. El mismo valor que el SDK devuelve a la app como `flow.variationId`. Aparece junto con `flow_id`. | | **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, es el precio de renovación. Equivale a `price_local` para una compra a precio completo y siempre es igual a `price_local` para compras únicas, ya que los stores no reportan un precio estándar separado para ellas. `null` para una compra con descuento cuando el store no reporta un precio estándar fiable (por ejemplo, si 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 | Para 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 ella. La fecha de compra original es la fecha y hora de esa primera transacción. | | **original_transaction_id** | String | <p>Para 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 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. En un flow, este es el ID de la pantalla del paywall dentro del flow (`scr_...`), no un nombre. El nombre del flow está en `flow_name`. | | **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` en `trial_started`, ya que no se cobra nada, a menos que esté habilitado **Send trial price**. | | **price_usd** | Float | El importe cobrado por la transacción antes de la comisión de Apple/Google, en USD. `null` en `trial_started`, ya que no se cobra nada, a menos que esté habilitado **Send trial price**. | | **profile_country** | String | Determinado por Adapty según la IP del perfil. | | **profile_event_id** | UUID | ID de evento único que se puede usar 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, con preferencia por IPv4 cuando esté disponible). `null` si **Collect users' IP addresses** está deshabilitado en la [configuración 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 lo estableces tú 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 (normalmente el 15 %) tras un año de renovación continua. Las tasas de comisión varían según la elegibilidad del programa y el país. Consulta [Comisión del store e impuestos](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue) para más detalles. | | **store** | String | Store donde se compró el producto. Valores estándar: **app_store**, **play_store**, **stripe**, **paddle**. <br/>Si estableces [transacciones de store personalizadas](api-adapty/operations/setTransaction) mediante la API del servidor, se usa el valor del parámetro **store**. | | **store_country** | String | El país que nos envía el 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 descuenta una oferta (1 o más). Solo está presente cuando se aplica una oferta. `null` para las ofertas pay-up-front de App Store y cuando el store no reporta la duración de la oferta. | | **subscription_expires_at** | ISO 8601 date | <p>La fecha de vencimiento de la suscripción.</p><p>En eventos de reembolso, problema de facturación, período de gracia y nivel de acceso, contiene la fecha de cancelación o la fecha de fin del período de gracia cuando corresponda. En eventos de compra y renovación, contiene la fecha de vencimiento regular de la suscripción.</p> | | **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 en el formato "{} days", por ejemplo, "7 days". Solo está presente en los tipos de evento relacionados con la prueba: `trial_started`, `trial_converted`, `trial_cancelled`. | | **variation_id** | UUID | ID único del paywall donde se realizó la compra. En un flow, es el ID de la pantalla del paywall dentro del flow, no el de la variante de la prueba A/B. Cada versión publicada del flow recibe nuevos IDs de pantalla, por lo que Adapty envía una variante con diferentes valores de `variation_id` a lo largo del tiempo. Para dividir transacciones por variante de prueba, usa `ab_test_variant_id`. | | **vendor_product_id** | String | <p>ID del producto en Apple App Store, Google Play Store o Stripe.</p><p>Para un [plan de compromiso de 12 meses](app-store-12-month-commitment), el valor es `<product_id>:monthly`.</p><p>Si se concedió el acceso sin una transacción real en el 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> | #### Campos de flow y prueba A/B Los campos de flow y prueba A/B indican de qué flow, prueba A/B y variante proviene una transacción. Funcionan con todos los [tipos de prueba A/B](ab-test-types): pruebas A/B de paywall y flow regulares, y pruebas A/B de placement cruzado en paywalls y en flows. Adapty envía estos campos en los eventos de una transacción realizada en un placement, los mismos eventos que incluyen `developer_id` y `variation_id`. Las renovaciones, reembolsos, cancelaciones y otros eventos de suscripción llevan el mismo flow, prueba A/B y variante que la compra original, incluso después de que la prueba A/B haya finalizado. Cuando un campo no tiene valor, el evento no lo incluye: estos campos nunca están vacíos ni son `null`. | Fuente de transacción | `flow_id`, `flow_name`, `flow_variation_id` | `ab_test_id` | `ab_test_variant_id`, `ab_test_variant_name` | `ab_test_parent_name` | |---|---|---|---|---| | Prueba A/B de paywall regular | — | ID de prueba A/B | Coincide con `variation_id` | — | | Paywall fuera de una prueba A/B | — | — | — | — | | Prueba A/B de flow | ✅ | ID de prueba A/B | Coincide con `flow_variation_id` | — | | Flow fuera de una prueba A/B | ✅ | — | — | — | | Prueba A/B multiplacement en paywalls | — | ID de prueba multiplacement | Compartido por todos los placements | ✅ | | Prueba A/B multiplacement en flows | ✅, con la variante de flow en este placement | ID de prueba multiplacement | Compartido por todos los placements | ✅ | | No es un placement | — | — | — | — | Por ejemplo, para una compra en la variante A en el segundo placement de una prueba A/B entre placements con flows, Adapty envía: ```json title="Json" showLineNumbers { "variation_id": "<paywall screen ID on this placement>", "ab_test_name": "Onboarding test child-1", "paywall_name": "scr_a1b2c3d4", "flow_id": "<flow ID>", "flow_name": "Onboarding X", "flow_variation_id": "<variant A on this placement>", "ab_test_id": "<crossplacement test ID>", "ab_test_variant_id": "<variant A, shared by all placements>", "ab_test_variant_name": "A", "ab_test_parent_name": "Onboarding test" } ``` Para crear informes sobre estos campos, agrupa las transacciones por los IDs (`ab_test_id`, `ab_test_variant_id`, `flow_id`) y usa los nombres como etiquetas. Adapty toma los nombres en el momento en que envía el evento, por lo que si renombras una prueba, variante o flow, los eventos posteriores llevarán el nuevo nombre. Los eventos ya enviados no se reenvían ni se actualizan. :::note El ID de variante compartido de una prueba A/B entre placements coincide con el ID de variante de un solo placement, normalmente el primero. Para relacionar un evento con la variante que el SDK devolvió en un placement concreto, usa `flow_variation_id` o `variation_id`. ::: :::warning En una prueba A/B entre placements donde el mismo flow aparece en más de una variante, la compra por sí sola no indica en qué variante estaba el usuario. Adapty obtiene la variante del registro de asignación del usuario. Si ese registro no está disponible, Adapty envía `flow_id`, `flow_name` y `ab_test_id`, pero no `flow_variation_id`, `ab_test_variant_id` ni `ab_test_variant_name`. Esto ocurre cuando el usuario compra justo después de ver el flow, antes de que el registro se guarde, y también para las renovaciones que llegan después de que el registro ha expirado. El Adapty Dashboard cuenta esas transacciones en la primera variante, por lo que los datos de tu integración pueden diferir del dashboard. ::: #### Propiedades adicionales de impuestos e ingresos en 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 aplican a ciertos tipos de eventos. Esto significa que los tipos de eventos indicados incluyen las [Propiedades de evento para la mayoría de los tipos de eventos](webhook-event-types-and-fields#for-most-event-types), junto con los campos extra que se listan a continuación. Tipos de eventos que tienen las propiedades de impuestos e ingresos: - `subscription_renewed` - `subscription_initial_purchase` (también denominado `subscription_started` — mismo evento) - `subscription_refunded` - `non_subscription_purchase` - `non_subscription_purchase_refunded` - `trial_started` - `trial_converted` | Campo | Tipo | Descripción | | :-------------------- | :---- | :----------------------------------------------------------- | | **net_revenue_local** | Float | Ingresos netos (ingresos tras el recorte de Apple/Google e impuestos) en moneda local. | | **net_revenue_usd** | Float | Ingresos netos (ingresos tras el recorte de Apple/Google e impuestos) en USD. | | **proceeds_local** | Float | `price_local` tras el recorte de Apple/Google. | | **proceeds_usd** | Float | `price_usd` tras el recorte de Apple/Google. | | **tax_amount_local** | Float | [Impuesto estimado](how-adapty-analytics-works#taxes) incluido en `price_local`. `0` para Stripe y Paddle, incluso cuando el usuario pagó impuestos. | | **tax_amount_usd** | Float | [Impuesto estimado](how-adapty-analytics-works#taxes) incluido en `price_usd`. `0` para Stripe y Paddle, incluso cuando el usuario pagó impuestos. | Para los ingresos netos, usa `net_revenue_*` directamente. A menos que `tax_amount_*` sea `0`, `proceeds_*` menos `tax_amount_*` da una cantidad menor. Por ejemplo, con un precio de 12, una comisión del 30% y una tasa impositiva del 20%, Adapty calcula: - `proceeds_*` = `price_*` × (1 − comisión) = 12 × 0.7 = 8.40 - `tax_amount_*` = `price_*` × tasa impositiva ÷ (1 + tasa impositiva) = 12 × 0.2 ÷ 1.2 = 2.00 - `net_revenue_*` = `proceeds_*` ÷ (1 + tasa impositiva) = 8.40 ÷ 1.2 = 7.00 Una prueba gratuita no cobra nada, por lo que en `trial_started` los campos de precio (`price_*`, `proceeds_*`, `net_revenue_*`, `tax_amount_*`) y `currency` son `null` por defecto. Para recibirlos, activa **Send trial price** en los [ajustes de webhook](set-up-webhook-integration). Adapty los calcula a partir del precio de la suscripción multiplicado por **Trial price percentage** y envía `currency`. Estos valores describen la suscripción, no un cobro, así que no incluyas `trial_started` en los totales de ingresos. #### 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_*`) están incluidos. <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_id** | UUID | ID de la prueba A/B donde se originó la transacción. Consulta [Campos de flow y prueba A/B](#flow-and-ab-test-fields). | | **ab_test_name** | String | Nombre de la prueba A/B donde se originó la transacción. Para una prueba A/B entre placements, el nombre de la prueba en un placement (`<test name> child-N`). | | **ab_test_parent_name** | String | Nombre de la prueba A/B entre placements. Solo está presente en pruebas A/B entre placements. | | **ab_test_variant_id** | UUID | ID de la variante de la prueba A/B donde se originó la transacción. | | **ab_test_variant_name** | String | Nombre de la variante de la prueba A/B. | | **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 indicada 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 | Motivos posibles 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 | <p>Fecha y hora en que expira el acceso. Adapty calcula `is_active` a partir de este campo.</p><p>Durante un período de gracia, contiene el final del período de gracia; tras un reembolso o cancelación, contiene la fecha de cancelación. No existe un campo separado para el final del período de gracia.</p> | | **flow_id** | UUID | ID del flow donde se originó la transacción. | | **flow_name** | String | Nombre del flow donde se originó la transacción. | | **flow_variation_id** | UUID | ID de la variante del flow en este placement, igual que `flow.variationId` en el SDK. | | **is_active** | Boolean | Booleano que indica si el nivel de acceso está activo. Se calcula a partir de `expires_at` (junto con `starts_at` e `is_lifetime`) cuando se genera el evento. | | **is_in_grace_period** | Boolean | Booleano que indica si el perfil está en el período de gracia. Mientras sea `true`, `expires_at` contiene el final del 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 suscripciones recurrentes, la compra original es la primera transacción de la cadena; su ID, llamado ID de transacción original, enlaza la cadena de renovaciones. Las transacciones posteriores son extensiones de ella. La fecha de compra original es la fecha y hora de esa primera transacción. | | **original_transaction_id** | String | <p>En suscripciones recurrentes, este es el ID de transacción original que enlaza la cadena de renovaciones. La transacción original es la primera de la cadena; las transacciones posteriores son extensiones de ella.</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. En un flow, el ID de la pantalla del paywall (`scr_...`). | | **paywall_revision** | String | Revisión del paywall donde se originó la transacción. El valor por defecto es 1. | | **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 de usuario interno de Adapty. | | **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, incluidos los reembolsos. | | **purchase_date** | ISO 8601 date | La fecha y hora de la compra del producto. | | **renewed_at** | ISO 8601 date | Fecha y hora en que comenzó más recientemente el período actual de acceso de pago. Hasta la primera renovación, coincide con `activated_at`. | | **starts_at** | ISO 8601 date | Fecha y hora en que comienza el nivel de acceso. | | **store** | String | Store donde se compró 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 utiliza el valor del parámetro **store**. | | **store_country** | String | País enviado a Adapty por el store. | | **subscription_expires_at** | ISO 8601 date | La fecha de expiración de la suscripción. En este evento contiene el mismo valor que `expires_at`: durante un período de gracia, el final del período de gracia; tras un reembolso o cancelación, la fecha de cancelación. | | **transaction_id** | String | Identificador único de una transacción. | | **trial_duration** | String | Duración de un 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. En un flow, el ID de la pantalla del paywall dentro del flow, no la variante de la prueba A/B. | | **vendor_product_id** | String | <p>ID del producto en el store (Apple/Google/Stripe).</p><p>Para un [plan de compromiso de 12 meses](app-store-12-month-commitment), el valor es `<product_id>:monthly`.</p><p>Si el acceso fue concedido sin una transacción real en el 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> | | **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 \{#verification-request\} 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: | Configuración | Descripción | | :--------------------------------- | :----------------------------------------------------------- | | **Send trial price** | Cuando está habilitado, Adapty rellena los campos de precio, ingresos e impuestos y el `currency` del evento **Trial Started** a partir del precio de la suscripción. Usa **Trial price percentage** para enviar un porcentaje de ese precio. Cuando está deshabilitado, estos campos son `null`. Consulta [Propiedades adicionales de eventos de impuestos e ingresos](webhook-event-types-and-fields#additional-tax-and-revenue-event-properties). | | **Exclude Historical Events** | Permite 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. | | **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 (por ejemplo, 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 es necesario. Al habilitarla, 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). ## Gestiona los eventos de webhook \{#handle-webhook-events\} Los webhooks suelen entregarse 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. La entrega de webhooks de Adapty es de tipo "al menos una vez": cada evento recibe al menos un intento de entrega, y Adapty reintenta en caso de fallo en lugar de descartarlo. 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 posterior — hasta 9 reintentos distribuidos a lo largo de 24 horas. Te recomendamos configurar tu webhook para realizar solo una validación básica del cuerpo del evento recibido 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 las tareas que consumen tiempo de forma asíncrona y responde a Adapty rápidamente. Si Adapty no recibe una respuesta en 10 segundos, considerará el intento como fallido y volverá a intentarlo. Los eventos no tienen garantía de llegar en orden — consulta [Campos de evento](webhook-event-types-and-fields#event-fields) para saber cómo ordenarlos y deduplicarlos por tu cuenta. :::note Se aplican dos límites adicionales a los reintentos, además del calendario anterior. Si tu endpoint no ha recibido ninguna entrega correcta durante 24 horas, Adapty deja de reintentar los eventos fallidos hasta que se complete una entrega con éxito. Además, un evento que no se haya entregado en las 24 horas siguientes a su creación deja de reintentarse. Los eventos que fallaron durante una interrupción prolongada no se reenvían automáticamente; contacta con el [soporte de Adapty](mailto:support@adapty.io) para reenviarlos. ::: ### Entrega pausada tras fallos repetidos \{#paused-delivery-after-repeated-failures\} Cuando la mayoría de los envíos recientes a tu endpoint fallan, Adapty pausa la entrega de webhooks para tu app. Un fallo aquí es el mismo que para los reintentos: un código de respuesta fuera del rango 200-404, un error de conexión o ninguna respuesta en 10 segundos. Mientras la entrega está pausada, Adapty no envía nuevos eventos a tu endpoint ni los reintenta más adelante. Estos eventos muestran el estado **Sending failed**, aunque tu servidor nunca haya recibido la solicitud. Tras un período de espera, Adapty envía el siguiente evento para probar el endpoint. Si ese envío se completa correctamente, se reanuda la entrega normal. Si falla, la entrega se vuelve a pausar con un período de espera más largo. La solicitud de verificación que Adapty envía cuando haces clic en **Save** no pasa por este canal de entrega, por lo que puede completarse correctamente aunque la entrega esté pausada. Para mantener la entrega activa, responde con un estado `2xx` a cada evento, incluidos los eventos que tu servidor aún no pueda asociar a un usuario, y procésalos en tu lado. --- # 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 es compatible con esta integración, o lo desactivaste al configurarla. **Solución**: 1. Comprueba si la integración admite este tipo de evento. Los siguientes eventos no llegan a todas las integraciones: - `access_level_updated`: solo el webhook. - [Eventos sintéticos](events#synthetic-events) (`trial_active`, `subscription_active`): Adjust, Amplitude, AppMetrica, AppsFlyer, Branch, Facebook Analytics, Firebase, Mixpanel, OneSignal, PostHog, Singular, SplitMetrics y el webhook. 2. Si la integración es compatible, asegúrate de que el evento está activado en la configuración de la integración. ## Un evento sintético nunca llegó \{#a-synthetic-event-never-arrived\} **Razón**: Los [eventos sintéticos](events#synthetic-events) se lanzan una vez, con un temporizador, y hay cuatro situaciones que los detienen sin mostrar un error: - La prueba o suscripción terminó antes de que transcurriera el retraso. Una compra convertida, expirada o reembolsada no genera nada, y Adapty no vuelve a comprobarlo. Busca `trial_converted`, `trial_expired`, `subscription_expired` o `subscription_refunded` en su lugar. - El usuario desactivó la renovación automática antes de que transcurriera el retraso. Su acceso se mantiene hasta el final del período pagado, pero Adapty omite el envío. Busca `trial_renewal_cancelled` o `subscription_renewal_cancelled` en su lugar. - Activaste el evento después de que comenzara la compra. Adapty inicia el temporizador cuando llega `trial_started` o `subscription_started`, así que solo las compras realizadas después de que actives el evento generarán uno. - Cambiaste el retraso después de que comenzara la compra. Los envíos ya programados conservan el retraso con el que fueron creados. **Solución**: Ninguno de estos es un fallo de entrega, por lo que no hay nada que reintentar y no aparece nada en el Event Feed. Para confirmar que la configuración funciona, activa el evento y observa una compra realizada a partir de ese momento. --- # File: app-store-test --- --- title: "Probar compras in-app en App Store" description: "Prueba compras en el entorno sandbox para asegurar transacciones sin problemas." --- Una vez que hayas configurado todo en el Adapty Dashboard y en tu app móvil, es momento de realizar las pruebas de compras in-app. Hay dos formas de probar compras en tu app de iOS: - [**Pruebas en sandbox**](test-purchases-in-sandbox): Usa una cuenta sandbox y ejecuta tu app desde Xcode o descárgala desde TestFlight. Esta opción te permite probar todo el flujo de compra y ver las actualizaciones del perfil en el dashboard de Adapty. - [**Pruebas con StoreKit en Xcode**](local-sk-files): Ejecuta tu app desde Xcode sin necesidad de crear ni restablecer una cuenta sandbox. Esta opción es ideal para desarrolladores que quieren probar distintos escenarios en el entorno de Xcode. Sin embargo, ten en cuenta que todos los escenarios se ejecutan de forma local y no verás ningún cambio en el dashboard de Adapty. --- # 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\} :::info Recomendamos probar las compras in-app en un dispositivo real. Aunque las compras en sandbox pueden ejecutarse en simuladores, se necesitan dispositivos reales para probar todos los flows en su totalidad, incluidos los diálogos de pago y las solicitudes biométricas. ::: Hay 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 de 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-7-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 ha sido actualizada: 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 de 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-7-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 compra en sandbox se rechaza como no autorizada \{#the-sandbox-purchase-is-refused-as-unauthorized\} La store muestra un aviso del tipo `You are not authorized to make purchases of this in-app in sandbox at this time`. Apple rechaza la compra, no Adapty. Comprueba lo siguiente en el dispositivo de prueba y en App Store Connect: - **Las compras in-app están permitidas en el dispositivo**: En **Settings** > **Screen Time** > **Apps & Websites** > **App Store**, **In-app Purchases** debe estar en **Allowed**. Apple describe esta configuración en [Turn off Apple In-App Purchases from Screen Time](https://support.apple.com/en-us/102470). - **La cuenta sandbox está iniciada sesión en el lugar correcto**: Inicia sesión en [**Settings** > **Developer** > **Sandbox Apple Account**](test-purchases-in-sandbox#step-4-switch-to-sandbox-test-account), no desde la configuración principal de tu cuenta de Apple. La instrucción de Apple es "usar la opción de inicio de sesión Sandbox en Configuración, no la configuración principal de tu cuenta de Apple." - **La dirección de correo electrónico del tester no es una cuenta de Apple existente**: Apple indica que reutilizar una "causará errores durante las pruebas." No puedes editar la dirección de correo de un tester una vez creado, así que [crea un nuevo tester](test-purchases-in-sandbox#step-1-create-sandbox-test-account-in-app-store-connect) con una dirección diferente. ## 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." --- Repasa esta lista de verificación antes de publicar para confirmar que el flujo de monetización funciona de extremo a extremo en una build real. ## 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 en 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). **Habilitado (por defecto)** 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á registrado con el mismo Apple/Google ID. Esto es útil cuando un usuario reinstala la app e inicia sesión con un email 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 análisis coherentes y conservar un historial completo de transacciones, incluyendo períodos de prueba, compras de suscripciones, renovaciones y más, vinculados 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é registrado con el 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 análisis coherentes y conservar un historial completo de transacciones, incluyendo períodos de prueba, compras de suscripciones, renovaciones y más, vinculados al mismo perfil. Tras cambiar a **Transferir acceso al nuevo usuario**, los niveles de acceso no se transferirán entre perfiles de inmediato. El proceso de transferencia para cada nivel de acceso específico solo se activa cuando Adapty recibe un evento del store, como la renovación de una suscripción, una restauración o la validación de una transacción. **Deshabilitado** El primer perfil de usuario identificado que obtenga un nivel de acceso lo conservará de forma permanente. Esta es la mejor opción si la lógica de tu 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. Deshabilitar el uso compartido solo afecta a los nuevos usuarios. Las suscripciones que ya se compartan entre usuarios seguirán compartiéndose incluso después de deshabilitar esta opción. :::warning Apple y Google requieren 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. Deshabilitar el uso compartido puede impedir que los usuarios recuperen el acceso al iniciar sesión. Recomendamos deshabilitar el uso compartido solo si tus usuarios **deben iniciar sesión** antes de realizar una compra. De lo contrario, un usuario identificado podría comprar una suscripción, iniciar sesión con 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 usa 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 vincular las compras 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 aun así 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 **Deshabilitado** 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: android-settings --- --- title: "Credenciales de Google Play Store" description: "Configura los ajustes de Android en Adapty para una gestión fluida de suscripciones." --- Para que el SDK de Adapty para Android funcione, necesitas configurar varios parámetros. <img src="/assets/shared/img/f6d76ec-app-settings_android.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 | | :--------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Package name** | El nombre de paquete es el identificador único de tu app en Google Play Store. Es obligatorio para el funcionamiento básico de Adapty, como el procesamiento de suscripciones. | | **Service account key file** | [Claves](create-service-account) para habilitar la autenticación segura y la validación de compras. | | **Google Play RTDN topic name** | URL que se usa para habilitar las [notificaciones server2server](enable-real-time-developer-notifications-rtdn) desde Play Store y así monitorizar y responder a los cambios de estado de suscripción de los usuarios. | --- # File: google-play-store-connection-configuration --- --- title: "Configurar la integración con Google Play Store" description: "Introduce el nombre del paquete y sube el archivo de clave de cuenta de servicio para que Adapty pueda validar las compras de Android y crear tu tema Pub/Sub." --- Este es el único paso de la integración con Google Play que se realiza en Adapty en lugar de en una consola de Google. Le proporcionas a Adapty el nombre de tu paquete y el archivo de clave de cuenta de servicio, y Adapty los usa para validar las compras y leer tus productos. :::danger No cambies el nombre del paquete ni el archivo de clave una vez que tu app esté en producción con el SDK de Adapty. Esto romperá la validación de compras para todos los usuarios que ya tengan tu app. Una clave de un proyecto Cloud diferente también cambia el nombre del topic, lo que detiene las notificaciones en tiempo real. Una clave que ha dejado de funcionar es el caso contrario. Si la clave de cuenta de servicio se elimina o rota en Google Cloud, sube el reemplazo en cuanto lo detectes: la lectura de productos y la validación de compras seguirán rotas hasta que lo hagas. Consulta [Si las actualizaciones dejan de llegar](store-sync#if-updates-stop-arriving). ::: ## Antes de empezar \{#before-you-start\} [Genera el archivo de clave de cuenta de servicio](create-service-account-key-file). Ese archivo es el que subirás aquí. ## Conecta la app \{#connect-the-app\} 1. Abre [Google Play Console](https://play.google.com/console/), selecciona tu app y copia el nombre de su paquete. La página **Dashboard** lo muestra bajo el nombre de la app. 2. Abre [**App settings** > **Android SDK**](https://app.adapty.io/settings/android-sdk) en el Adapty Dashboard. 3. Pega el nombre del paquete en **Package name**. 4. Sube el archivo de clave JSON en **Service account key file**. 5. Haz clic en **Save**. Al guardar el archivo de clave también se crea el tema Pub/Sub en tu proyecto de Cloud, que el siguiente paso necesita. ## Pasos siguientes \{#next-steps\} [Activa las notificaciones para desarrolladores en tiempo real (RTDN)](enable-real-time-developer-notifications-rtdn) para que Google Play informe a Adapty sobre renovaciones, cancelaciones y reembolsos. --- # File: enable-real-time-developer-notifications-rtdn --- --- title: "Habilitar notificaciones en tiempo real para desarrolladores (RTDN) en Google Play Console" description: "Pega el nombre del tema Pub/Sub de Adapty en Google Play Console para que las renovaciones, cancelaciones y reembolsos lleguen a Adapty en tiempo real." --- Google Play informa a Adapty sobre renovaciones, cancelaciones, reembolsos y problemas de facturación a través de notificaciones en tiempo real para desarrolladores. Estos eventos ocurren mientras tu app está cerrada, por lo que el SDK no puede reportarlos. ## Antes de empezar \{#before-you-start\} [Configura la integración con Google Play Store](google-play-store-connection-configuration). Adapty genera el nombre del topic a partir de tu archivo de clave, por lo que no hay nada que copiar hasta que lo hayas subido. ## Activar notificaciones \{#enable-notifications\} 1. Abre [**App settings** > **Android SDK**](https://app.adapty.io/settings/android-sdk) en el Adapty Dashboard y copia el nombre del tema en **Google Play RTDN topic name**. Empieza por `projects/`. 2. Abre [Google Play Console](https://play.google.com/console/), selecciona tu app y ve a **Monetize with Play** > **Monetization setup**. 3. En la sección **Google Play Billing**, marca la casilla **Enable real-time notifications**. 4. Pega el nombre del tema en **Topic name**. 5. Haz clic en **Save changes**. ## Verificar la conexión \{#verify-the-connection\} 1. En la página **Monetization setup**, haz clic en el botón **Send test notification** bajo el campo **Topic name**. 2. Vuelve a [**App settings** > **Android SDK**](https://app.adapty.io/settings/android-sdk) en Adapty. Una vez que Adapty reciba la notificación, la etiqueta de estado en la sección **Google Play RTDN topic name** aparecerá como **Active**. :::note La notificación de prueba solo verifica la conexión. No crea eventos de compra, por lo que el [Event Feed](event-feed) permanece vacío después de una prueba; los eventos aparecen cuando se producen transacciones reales. ::: :::warning Si el RTDN deja de llegar a Adapty, las nuevas transacciones, renovaciones y reembolsos dejan de aparecer. Adapty no lo notifica — consulta [Si las actualizaciones dejan de llegar](store-sync#if-updates-stop-arriving). ::: ## Enviar notificaciones a dos apps de Adapty \{#send-notifications-to-two-adapty-apps\} Google Play envía las notificaciones de una app a un único topic. Si una app de Google Play reporta a dos apps de Adapty —por ejemplo, una app de producción y una de desarrollo—, añade una segunda suscripción a ese topic para que ambas apps reciban las notificaciones. 1. En la segunda app de Adapty, [configura la integración con Google Play Store](google-play-store-connection-configuration) para la misma app de Google Play. Adapty crea un topic y una push subscription para la segunda app, y les asigna el mismo nombre: la parte del **Google Play RTDN topic name** de la segunda app que aparece tras `topics/`, visible en [**App settings** > **Android SDK**](https://app.adapty.io/settings/android-sdk). 2. En la [Google Cloud Console](https://console.cloud.google.com/), selecciona el proyecto del archivo de clave de la segunda app y ve a **Pub/Sub** > **Subscriptions**. Abre la suscripción con ese nombre y copia su URL de endpoint. 3. Selecciona el proyecto del topic que utiliza Google Play Console y ve a **Pub/Sub** > **Topics**. Abre ese topic y crea una suscripción con el tipo de entrega **Push** y la URL de endpoint que copiaste. La segunda app de Adapty recibirá entonces una notificación por cada compra realizada en la app de Google Play, incluidas las compras realizadas a través de la primera app de Adapty. ## Reenviar eventos brutos de Google \{#forward-raw-google-events\} Adapty decodifica cada notificación de Google y la convierte en su propio evento, vinculado a un perfil y un producto, con los ingresos asociados. Si también quieres recibir el mensaje original de Google, añade tu endpoint en **URL for forwarding raw Google events to your webhook** en **App settings** > **Android SDK**. Adapty publica el mensaje de Pub/Sub exactamente como Google lo envió, antes de analizar nada: codificado en base64, usando los nombres de campo de Google y los tipos de notificación numéricos, e identificando la compra por token en lugar de por perfil de Adapty. Google documenta el formato en su [referencia de RTDN](https://developer.android.com/google/play/billing/rtdn-reference). ## Solución de problemas de RTDN \{#rtdn-troubleshooting\} ### Falta el nombre del tema \{#the-topic-name-is-missing\} La configuración de RTDN comienza copiando un nombre de tema desde Adapty. Si no hay nada, no puedes continuar: **Topic name** en Play Console es como Google Play sabe dónde enviar los eventos, y Adapty es el único lugar del que proviene ese nombre. Adapty crea el topic en tu proyecto de Cloud al guardar el archivo de clave. Un nombre válido empieza por `projects/`, como `projects/your-project-id/topics/adapty-prod-3f7a1c2e-9b4d-4e8f-a1b2-c3d4e5f60718`. Si Adapty no pudo crearlo, la sección muestra un mensaje breve y un botón **Recreate Pub/Sub topic** en lugar del nombre, con una explicación del problema: | Lo que ves | Lo que significa | Qué hacer | | --- | --- | --- | | **To set the Google Play RTDN topic name first upload a valid service account key file above.** | Adapty no tiene ningún archivo de clave para esta aplicación | [Sube el archivo de clave](google-play-store-connection-configuration) | | **Enable Pub/Sub API** | La API de Cloud Pub/Sub está desactivada en ese proyecto de Cloud | Sigue el enlace que muestra Adapty y haz clic en **Recreate Pub/Sub topic** | | **Grant permissions** | La cuenta de servicio no tiene el rol **Pub/Sub Admin** en ese proyecto | En [**IAM & Admin** > **IAM**](https://console.cloud.google.com/iam-admin/iam), edita la cuenta de servicio y añade **Pub/Sub** > **Pub/Sub Admin**, luego haz clic en **Recreate Pub/Sub topic** | Hasta que el topic exista, no hay nada que pegar en Play Console, Google Play no envía eventos y Adapty no recibe tus renovaciones, cancelaciones ni reembolsos. **Recreate Pub/Sub topic** vuelve a ejecutar la configuración con el archivo de clave que ya subiste, sin necesidad de volver a cargarlo. ### Recreación del tema falla \{#recreating-the-topic-fails\} Google Play necesita permiso para publicar en tu tema, por lo que Adapty otorga ese permiso a `google-play-developer-notifications@system.gserviceaccount.com`. Esa cuenta pertenece a Google, no a tu empresa. Las empresas suelen configurar Google Cloud para permitir el acceso solo a cuentas de su propio dominio. Estas reglas a nivel de organización se denominan políticas de organización, y dos de ellas rechazan la concesión de permisos a una cuenta externa, por lo que el tema no se crea. Relaja ambas políticas para este proyecto, vuelve a crear el tema y, después, restáuralas. Modificar las políticas de organización suele requerir permisos de administrador, así que puede que necesites a quien gestione el Google Cloud de tu empresa. 1. Abre la [Google Cloud Console](https://console.cloud.google.com/) y selecciona el proyecto del que proviene el archivo de clave. 2. Ve a **IAM & Admin** > **Organization Policies**. 3. Busca la política **Domain restricted contacts**, abre su menú **Actions** y haz clic en **Edit policy**. 4. Selecciona **Override parent's policy**, establece **Policy enforcement** en **Replace** y haz clic en **Add a rule**. 5. En **Policy values**, elige **Allow All** y haz clic en **Set policy**. 6. Repite el proceso para la política **Domain restricted sharing**. 7. Vuelve a crear el topic en Adapty. :::warning Restablece ambas políticas a **Inherit parent's policy** en cuanto exista el topic. Mientras aparezca **Allow All**, este proyecto acepta concesiones de acceso y uso compartido para cualquier cuenta de Google fuera de tu dominio, no solo para el servicio de notificaciones de Google. ::: ## Próximos pasos \{#next-steps\} [Instala el SDK](installation-of-adapty-sdks) para cada framework que use tu app. Hasta que lo hagas, tu app no podrá mostrar paywalls ni registrar compras en Adapty. - [Android](sdk-installation-android) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) - [Capacitor](sdk-installation-capacitor) --- # 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 */ }} /> ### Conecta tu app a la red AdServices \{#connect-your-app-to-the-adservices-network\} Una vez que completes [la configuración del framework AdServices](#connect-adapty-to-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 no quieres este comportamiento, *desactiva* la atribución de ASA siguiendo las instrucciones que se muestran 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. ::: El interruptor controla si Adapty utiliza la atribución de Apple Ads, no si el SDK envía el token de AdServices. El SDK envía el token una vez por perfil, en su primera sincronización, y no dispone de ninguna opción para desactivar esto. Según Apple, [AdServices no realiza seguimiento](https://ads.apple.com/app-store/help/attribution/0028-measuring-ad-performance) según la definición de App Tracking Transparency. ## 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 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, miembros del equipo y facturación. La página tiene tres pestañas: - [General](#general-settings) - [Subscription & Billing](#subscription--billing) - [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: apple-platform-resources --- --- title: "Recursos de la plataforma Apple" description: "Explora los recursos de la plataforma Apple para optimizar la monetización y la gestión de suscripciones de tu app." --- Adapty ofrece SDKs e integraciones diseñadas para las plataformas Apple, que simplifican el desarrollo de compras in-app, suscripciones, paywalls y pruebas A/B. Usa los siguientes recursos para sacar el máximo partido a lo que Adapty ofrece para Apple. ### Configuración inicial en App Store Connect \{#initial-configuration-in-app-store-connect\} 1. [Genera la clave de compra in-app en App Store Connect](generate-in-app-purchase-key) ### Configuración de productos y ofertas en App Store Connect \{#products-and-offers-configuration-in-app-store-connect\} 1. [Producto en App Store](app-store-products) 2. [Ofertas en App Store](app-store-offers) ### Información adicional \{#additional-information\} 1. [Configurar App Store Connect](set-up-app-store-connect) 2. [Privacidad de apps de Apple](apple-app-privacy) 3. [Compartir en familia de Apple](apple-family-sharing) 4. [App Store Small Business Program](app-store-small-business-program) --- # 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é sigue? \{#whats-next\} - [Integración inicial con App Store](initial_ios): Conecta tu app de App Store con 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 a App Store](submit-app-to-app-store): Sube tu build y envíalo para revisión de Apple - [Programa para pequeñas empresas de App Store](app-store-small-business-program): Reduce tu comisión en App Store --- # 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 ofrece orientación para crear un producto en App Store Connect. Aunque esta información puede no estar directamente relacionada con la funcionalidad de Adapty, es un recurso útil si encuentras dificultades 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ú de la izquierda. <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 suscripción, 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 categorizan y gestionan tus productos, permitiendo a los usuarios cambiar entre diferentes ofertas sin interrupciones. 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 suscripción en el campo **Reference Name**. El nombre de referencia es una etiqueta o identificador definido por el usuario que te ayuda a distinguir y gestionar los distintos grupos de suscripción dentro de tu app. El nombre de referencia no es visible para los usuarios; es principalmente para tu uso interno y organización. Te permite identificar y hacer referencia fácilmente a grupos de suscripción específicos al gestionarlos en la interfaz de App Store Connect. Esto puede ser 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 suscripción. 5. El grupo de suscripciones se ha creado y está abierto. Ahora puedes crear suscripciones en el 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, escribe su nombre en el campo **Reference Name** y el código único de la suscripción en el campo **Product ID**. El Reference Name es un identificador exclusivo dentro de App Store Connect para tu suscripción in-app. No es visible para tus usuarios en el App Store. Te 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 esencial para acceder a tu producto durante la fase de desarrollo y sincronizarlo con Adapty, un servicio diseñado para gestionar las suscripciones in-app. 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 aparezca indicada en el nombre de la suscripción, recuerda completar el campo **Subscription Duration**. Para añadir un [plan de compromiso de 12 meses](set-up-12-month-commitment#1-app-store-connect-configuration), selecciona **1 Year** y configura las cuotas. <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 es momento de configurar el precio de la suscripción. Para hacerlo, haz clic en el botón **Add Subscription Price** que aparece 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**. Después, Apple calculará automáticamente los precios para los 175 países o regiones en función de 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** situado en la parte inferior de la ventana. 13. En la ventana **Confirm Subscription Price?** que se abre, revisa los precios finales con atención. Si necesitas corregirlos, puedes hacer clic en **Back** para volver a la ventana **Price by Country or Region** y actualizarlos. Cuando estés conforme con los precios, haz clic en **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. Tras cerrar la ventana **Confirm Subscription Price?**, recuerda hacer clic en el botón **Save** de la ventana de 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 deseas configurar otros tipos de compras in-app, puedes hacer clic en la pestaña **In-App Purchases** de 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 un 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 mayor comodidad. Asegúrate de actualizar el SDK para obtener el manifiesto. El manifiesto solo alimenta el informe de privacidad que genera Xcode. App Review verifica tus respuestas en [App Store Connect](#apple-app-privacy-disclosure-in-app-store-connect), así que completa esa sección en su totalidad. Si identificas a los usuarios con el `customerUserId` de Adapty, declara el ID de usuario en tu archivo de manifiesto: 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 a 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 la clave `NSPrivacyCollectedDataTypeLinked`. Añade `false` para la clave `NSPrivacyCollectedDataTypeTracking`, a menos que uses el ID de usuario para [rastreo según lo define Apple](https://developer.apple.com/app-store/user-privacy-and-data-use/). 5. Usa las cadenas `NSPrivacyCollectedDataTypePurposeAppFunctionality` y `NSPrivacyCollectedDataTypePurposeAnalytics` como valores 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 datos | Obligatorio | Nota | |----------------------------------------------------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------| | Identificadores | ✅ | <p>Adapty genera un ID de dispositivo para cada instalación y recopila el IDFV, por lo que debes seleccionar 'Device ID'.</p><p></p><p>Si identificas a los usuarios con un customerUserId, selecciona 'User ID'.</p> | | Compras | ✅ | Adapty recopila el historial de compras de los usuarios. | | Datos de uso | ✅ | Adapty registra las aperturas de la app y los eventos de paywall y flow en el perfil del usuario, por lo que debes seleccionar 'Product Interaction'. | | Información de contacto, incluido nombre, número de teléfono o correo electrónico | 👀 | Obligatorio si pasas datos personales como nombre, número de teléfono o correo electrónico mediante el método **`updateProfile`**. | | Ubicación | ❌ | Adapty no recopila datos de ubicación precisa. Selecciona esta opción si tu app los recopila. | | Salud y fitness | ❌ | Adapty no recopila datos de salud ni de fitness. Selecciona esta opción si tu app los recopila. | | Información confidencial | ❌ | Adapty no recopila información confidencial. Selecciona esta opción si tu app la recopila. | | Contenido del usuario | ❌ | Adapty no recopila contenido del usuario. Selecciona esta opción si tu app lo recopila. | | Diagnóstico | ❌ | Adapty no recopila datos de diagnóstico. Selecciona esta opción si tu app los recopila. | | Historial de navegación | ❌ | Adapty no recopila el historial de navegación. Selecciona esta opción si tu app lo recopila. | | Historial de búsqueda | ❌ | Adapty no recopila el historial de búsqueda. Selecciona esta opción si tu app lo recopila. | | Contactos | ❌ | Adapty no recopila listas de contactos. Selecciona esta opción si tu app los recopila. | | Información financiera | ❌ | Adapty no recopila información financiera. Selecciona esta opción si tu app la recopila. | Adapty también recopila el modelo del dispositivo, la versión del SO, el idioma, la zona horaria, el storefront de App Store y el user agent. Estos no coinciden con ningún tipo de dato de Apple, por lo que no es necesario declararlos. ### 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 genera un Device ID para cada instalación y recopila el IDFV. También recopila el IDFA, a menos que [desactives la recopilación de identificadores publicitarios](analytics-integration#disable-collection-of-advertising-identifiers). No es posible desactivar el Device ID por instalación, por lo que debes declarar **Device ID** en todos los casos. - **User ID** — obligatorio si identificas a los 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 */ }} /> #### Datos de uso \{#usage-data\} Al utilizar Adapty, debes declarar que tu app recopila **Product Interaction**. Adapty registra las aperturas de la app y los eventos de paywall y flow en el perfil del usuario. ### 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 utilizan los datos del historial de compras, selecciona lo siguiente para Adapty: - **Analytics** — Adapty usa el historial de compras para análisis de ingresos, cohortes y métricas. - **Product Personalization** — Adapty usa los datos de compras para segmentación de audiencias y targeting de paywalls. Esto solo aplica si segmentas los placements por audiencia, pero seleccionarlo es la opción más segura por defecto. - **App Functionality** — Adapty valida las compras, gestiona los niveles de acceso y hace seguimiento del estado de las suscripciones. Selecciona propósitos adicionales si tu app utiliza los datos de compra de otras formas (por ejemplo, si envías eventos de compra a plataformas de anuncios a través de las 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 del identificador, 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. - **Analytics** — Adapty usa los identificadores para contar usuarios y atribuirles eventos en los análisis. 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 identificadores de otras maneras. <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**. 6. En **Product Interaction**, cuando Apple pregunte cómo se usan los datos, selecciona **Analytics** para Adapty. ### Vinculación y seguimiento \{#linking-and-tracking\} Para cada tipo de dato, Apple también pregunta si está vinculado a la identidad del usuario y si se usa para seguimiento. En el caso de Adapty, responde de la siguiente manera: - **Vinculado al usuario**: **No**, si no estableces `customerUserId`. El ID de dispositivo es un identificador aleatorio generado en cada instalación y no identifica a ninguna persona. Si estableces `customerUserId`, responde **Sí** para todos los tipos de datos de Adapty, ya que el ID de usuario los vincula todos al usuario. - **Usado para rastreo**: **No**. Adapty no utiliza estos datos para [rastreo según la definición de Apple](https://developer.apple.com/app-store/user-privacy-and-data-use/). Responde **Sí** solo si tu app usa los datos para rastreo. --- # 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. :::important Este comportamiento predeterminado se invierte para los productos que Adapty crea por ti. Si usaste [Crear producto y publicar en el store](create-product#create-product-and-push-to-store) en lugar de crear la suscripción tú mismo en App Store Connect, Family Sharing estará **activado**. Al igual que con cualquier configuración de Family Sharing, no podrás desactivarla después — para evitar que el producto se comparta, desactiva Family Sharing en App Store Connect antes de la primera revisión de la suscripción, o crea la suscripción directamente allí en lugar de publicarla desde Adapty. ::: 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 anuales en ingresos de App Store pueden participar en el [programa para pequeñas empresas](https://developer.apple.com/app-store/small-business-program/) de Apple. Si te registras, la comisión estándar del store se reduce al **15%** (12% en la tienda de China continental). La tarifa estándar es del 30% en la mayoría de los países. Para ver las tarifas de cada tienda, consulta [Tarifas de comisión de App Store](how-adapty-analytics-works#app-store-commission-rates). Los miembros del programa deben **cambiar su configuración de Adapty** para garantizar el cálculo correcto de los ingresos y el manejo 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 inscribirse en el programa](#apply-for-the-program) si quieres reducir la 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 en curso superan 1 millón de USD, pierdes la membresía del programa y Apple comienza a aplicar la tasa de comisión estándar. :::important Si tu negocio sale del Programa para Pequeñas Empresas, **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: google-platform-resources --- --- title: "Recursos de la plataforma Google" description: "Explora los recursos de la plataforma Google para optimizar la gestión de suscripciones en tu app." --- Adapty ofrece SDKs e integraciones diseñados para las plataformas Google, lo que simplifica el desarrollo de compras in-app, suscripciones, paywalls y pruebas A/B. Usa los siguientes recursos para sacar el máximo partido a lo que Adapty ofrece para las plataformas Google. ### Configuración inicial en Google Cloud Console y Google Play Console \{#initial-configuration-in-google-cloud-console-and-google-play-console\} 1. [Habilitar las API de desarrollador en Google Cloud Console](enabling-of-devepoler-api) 2. [Crear una cuenta de servicio en Google Cloud Console](create-service-account) 3. [Conceder permisos a una cuenta de servicio en Google Play Console](grant-permissions-to-service-account) 4. [Generar el archivo de clave de cuenta de servicio en Google Cloud Console](create-service-account-key-file) ### Configuración de productos y ofertas en Google Play Console \{#products-and-offers-configuration-in-google-play-console\} 1. [Crear productos para tu app móvil](android-products) 2. [Crear ofertas para productos](google-play-offers) ### Información adicional \{#additional-information\} 1. [Política de seguridad de datos de Google Play](google-play-data-safety) 2. [Privacidad de apps de Apple](apple-app-privacy) 3. [Tarifa de servicio reducida de Google](google-reduced-service-fee) --- # 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 ofrece orientación sobre cómo crear un producto en Play Store. Aunque esta información puede no estar directamente relacionada con la funcionalidad de Adapty, es un recurso útil si encuentras dificultades al crear productos en la consola de Google Play. El producto hace referencia a un artículo digital o servicio que ofreces dentro de tu app en Play Store, generalmente disponible para su compra. Puede tratarse de 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 varios planes base, cada uno con distintos descuentos u ofertas. Esta estructura se compone de tres elementos 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 los usuarios elegibles, modificando el precio del plan base. Por ejemplo, "prueba gratuita de 14 días para nuevos usuarios." :::warning El SDK de Android de Adapty está construido sobre Google Play Billing Library 8, pero no utiliza todas las capacidades que expone la librería. Antes de adoptar un nuevo modelo de producto de Google Play o una capacidad de facturación, envía un correo a [support@adapty.io](mailto:support@adapty.io) para confirmar que Adapty es compatible con él — algunos de estos cambios no se pueden revertir una vez guardados en Play Console. ::: ## ¿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 tu paywall: - **Sé transparente desde el principio.** Muestra el precio exacto de los productos, la frecuencia de facturación, los beneficios y las condiciones de cancelación antes de solicitar al usuario que realice una compra. Diferencia claramente entre las compras únicas y los productos que requieren pagos recurrentes. Si un producto incluye una prueba gratuita, indica claramente su duración y condiciones. Si ofreces un [plan de compromiso de 12 meses](set-up-12-month-commitment#4-add-to-a-flow-or-custom-paywall), muestra tanto el precio de la cuota mensual como el total anual. No utilices un lenguaje intencionadamente confuso para engañar al usuario. - **Sé coherente.** Los precios del producto deben coincidir en la ficha de la App Store, las pantallas in-app, las pantallas de gestión de suscripciones y el contenido de marketing. Cualquier discrepancia de 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 generes una falsa sensación de urgencia o escasez. No obligues a los usuarios a realizar compras haciendo intencionalmente que las funciones gratuitas sean 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 & Paywall Builder](adapty-flow-builder), el botón de restauración activa automáticamente el proceso de restauración. Si [implementaste un paywall personalizado](ios-implement-paywalls-manually), añade código que ejecute el 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: "Responde automáticamente a las solicitudes de consumo de App Store y las revisiones de contracargos de Google Play con Adapty Refund Saver, para disputar reembolsos injustos." --- Cuando un usuario solicita un reembolso, el store lo investiga antes de decidir. Para determinar **si el reembolso está justificado**, le pide al desarrollador información sobre la actividad de ese usuario. Sin esta evidencia, incluso una suscripción con uso intensivo probablemente será reembolsada. El **Refund Saver** responde a estas solicitudes automáticamente, protegiendo tus ingresos y **aumentando las posibilidades de rechazo** de las solicitudes injustificadas. Funciona tanto en App Store como en Google Play y cubre todos los tipos de compras in-app: suscripciones con renovación automática, suscripciones únicas, consumibles y no consumibles (incluidos los productos de acceso de por vida). :::tip **Retén suscriptores antes de que cancelen.** [Retention Messaging](retention-messaging) muestra un mensaje personalizado dentro de la pantalla de cancelación de suscripción de Apple — un motivo para quedarse, justo cuando el suscriptor toca Cancelar. ::: ## Cómo funciona Refund Saver \{#how-refund-saver-works\} 1. Cuando un usuario inicia una solicitud de reembolso, el store envía una notificación solicitando los detalles de la transacción y el uso. Si **ignoras** o **retrasa** la respuesta, el store probablemente **aprobará el reembolso**. 2. El Adapty Refund Saver responde a estas notificaciones automáticamente y envía al store los datos necesarios. Esta automatización reduce la probabilidad 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. ## Diferencias entre los stores \{#how-the-stores-differ\} Refund Saver funciona en ambos stores, pero cada uno gestiona los reembolsos a su manera. | | App Store | Google Play | | --- | --- | --- | | Qué responde Refund Saver | Una solicitud de consumo para una transacción | Una revisión de reembolso para un pedido. El banco del usuario la inicia como contracargo | | Consentimiento del usuario | Requerido | No necesario | | Cómo Adapty conoce el resultado | El App Store envía una notificación | Adapty consulta el estado del pedido hasta que Google resuelve la revisión | En Google Play, un usuario puede obtener un reembolso de dos formas. Google resuelve una **solicitud de reembolso** sin tu participación, por lo que Refund Saver nunca la ve. Un **chargeback** es diferente: el usuario le pide a su banco que revierta el pago, Google solicita tu opinión antes de que se resuelva la disputa, y Refund Saver responde por ti. Un chargeback que el usuario gana te cuesta tanto tu parte del precio de compra como la comisión de chargeback del banco. ## Requisitos previos según el store \{#store-specific-prerequisites\} ### App Store Cuando un usuario solicita un reembolso a Apple, Apple lo investiga. Antes de tomar una decisión, te envía una solicitud de consumo (consumption request), en la que te pide detalles sobre cómo el usuario utilizó la compra. Refund Saver responde a la solicitud con el resultado que prefieras y los datos de consumo del usuario. Para usar Refund Saver en el App Store, necesitas: 1. **Activa las [Notificaciones del servidor de App Store V2](enable-app-store-server-notifications)**: Refund Saver no funciona con las notificaciones V1, que Apple ha dejado obsoletas. 2. **Añade la clave de compra In-App**: Refund Saver firma su respuesta a Apple con la clave de compra In-App. Adapty necesita el Issuer ID, el Key ID y el archivo `.p8` en **App settings → iOS SDK**, además de tu Bundle ID. Sin los cuatro, no puede responder a una solicitud de consumo. Consulta [Generar una clave de compra In-App](generate-in-app-purchase-key). 3. **Indica el uso de datos de consumo en tu Política de privacidad**: Apple exige esta divulgación, y es responsabilidad tuya, no de Adapty. Consulta los [Detalles de privacidad de apps de Apple](https://developer.apple.com/app-store/app-privacy-details/) para saber qué debes indicar. 4. **Obtén el consentimiento del usuario para compartir datos**: También es responsabilidad tuya: Apple requiere un consentimiento válido antes de que Adapty comparta los datos de un usuario. Consulta [Obtener el consentimiento del usuario](#obtain-user-consent). ### Google Play Para usar Refund Saver en Google Play, conecta la store y activa RTDN — ambos pasos forman parte de la integración estándar de Google Play: [Configurar la integración de Google Play Store](google-play-store-connection-configuration) y [Activar las notificaciones en tiempo real para desarrolladores (RTDN)](enable-real-time-developer-notifications-rtdn). Google anuncia cada revisión de reembolso a través de RTDN, por lo que sin él las revisiones quedan sin respuesta. No se necesita el consentimiento del usuario — una disputa de contracargo no implica compartir datos personales. ## Activar Refund Saver \{#turn-on-refund-saver\} Solo tienes que activar Refund Saver una vez. Una vez activada la función, Adapty responde a las solicitudes de reembolso tanto de App Store como de Google Play, siempre que ambos estén correctamente conectados a Adapty y cumplas los [requisitos específicos de cada store](#store-specific-prerequisites). 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**. ## Configurar Refund Saver \{#configure-refund-saver\} Después de activar la función, puedes configurar el Refund Saver. Los ajustes se aplican en dos niveles: - **Valores predeterminados de la aplicación**: Una [preferencia de reembolso para cada store](#set-a-default-refund-preference), más la [política de consentimiento predeterminada](#set-the-default-consent-policy) — el consentimiento que Refund Saver asume para los usuarios sin ningún valor de consentimiento registrado. Ambas se configuran en la ventana **Edit Refund saver preferences**. - **Anulaciones por usuario**: Una preferencia de reembolso y un valor de consentimiento para un usuario específico. Cuando se establecen, tienen prioridad sobre los valores predeterminados de la aplicación. Puedes configurar la preferencia desde el [Dashboard](#set-refund-behavior-for-a-specific-user-in-the-dashboard), el [SDK](#set-a-refund-preference-for-a-user) o la [API del servidor](api-adapty/operations/setRefundSaverSettings) — los tres escriben el mismo valor. Las actualizaciones de consentimiento provienen del [SDK](#update-a-users-consent) o de la API. ### Establecer una preferencia de reembolso para cada store \{#set-a-default-refund-preference\} La preferencia de reembolso es lo que Adapty recomienda cuando responde a solicitudes de reembolso. Adapty la aplica a todas las solicitudes de ese store, en lugar de decidir caso por caso. Cada store mantiene su propia preferencia. La preferencia es solo una recomendación: la decisión final la toma el store. 1. Para cambiar tu preferencia, en la sección [Refund Saver](https://app.adapty.io/refund-saver), haz clic en **Refund Saver Settings**. 2. En la ventana **Edit Refund saver preferences**, selecciona la pestaña **Apple** o **Google**. 3. Elige tu opción de **Default refund request preference**: | Opción | Descripción | | -------------------------------------------- | ------------------------------------------------------------ | | Decline first refund request, grant all next | (predeterminado) Para cada transacción que encuentra Refund Saver, inicialmente solicita al store que rechace el reembolso. Si la misma transacción vuelve a aparecer, Refund Saver recomienda aprobar el reembolso. Este enfoque reduce la frustración del usuario ante rechazos injustos de reembolso: el usuario puede volver a solicitarlo y lo más probable es que lo reciba. | | Always decline | Solicita al store que rechace todas las solicitudes de reembolso, incluidas las solicitudes repetidas de la misma transacción. | | Always refund | Sugiere al store que apruebe todas las solicitudes de reembolso. | | Grant prorated refund | Sugiere a Apple que reembolse únicamente la parte no consumida de la compra, de modo que conserves los ingresos correspondientes a la parte que el usuario ya ha utilizado. Solo disponible en App Store; la pestaña de Google no ofrece esta opción. | | No preference | No proporciona ninguna recomendación al store. En este caso, el store determinará el resultado del reembolso según sus políticas internas y el historial del usuario, sin que tus ajustes influyan en la decisión. Es la opción más neutral. | #### Elige la preferencia adecuada \{#choose-the-right-preference\} Rechazar todas las solicitudes no garantiza menos reembolsos. El store trata tu respuesta únicamente como una sugerencia. Los desarrolladores informan que Apple puede aprobar una mayor proporción de solicitudes, incluso cuando le pides que las rechace todas. Además, rechazos repetidos para la misma transacción dan al usuario un motivo para volver a presentar la solicitud. Para la mayoría de las apps, **Decline first refund request, grant all next** ofrece la ventaja de rechazar la primera solicitud sin generar un patrón de rechazos repetidos. Un reembolso prorrateado es la opción intermedia entre rechazar y aprobar. Solo funciona en App Store. Devuelves la parte no utilizada de la compra y te quedas con el resto, en lugar de arriesgarte a perder el pago completo con un rechazo. Los reembolsos prorrateados son ideales para compras que el usuario ha utilizado parcialmente: un paquete de monedas del que ya gastó la mitad, o un plan anual cancelado en el segundo mes. Google solo acepta rechazar, aprobar o sin preferencia en una revisión de reembolso, por lo que Refund Saver no puede solicitar un reembolso parcial allí. Puedes emitir uno manualmente desde la gestión de pedidos de Play Console. #### Cómo calcula Adapty la parte consumida \{#how-adapty-calculates-the-consumed-share\} Un reembolso prorrateado cubre la parte no consumida de una compra, por lo que Apple necesita saber cuánto de la compra ha consumido ya el usuario. - **Suscripciones de renovación automática**: Apple calcula la parte consumida por su cuenta, y Adapty envía la preferencia a Apple sin un porcentaje de consumo. - **Cualquier otro tipo de producto**: Adapty calcula la parte consumida y la envía a Apple junto con la preferencia. Adapty obtiene la parte de una de estas dos fuentes: - **Moneda virtual**: La parte gastada del saldo de [moneda virtual](virtual-currencies) que otorgó la compra, promediada entre todas las monedas que otorgó. La precisión de la parte gastada depende del gasto que tu app reporte a través de la API del lado del servidor. - **Período de acceso**: La parte del período transcurrida, que se utiliza cuando la compra no otorgó ninguna moneda virtual. Apple acepta un reembolso prorrateado solo para una compra parcialmente consumida. Adapty envía una preferencia diferente en los demás casos: - Para una compra totalmente consumida, Adapty solicita a Apple que rechace el reembolso. - Para una compra sin usar, Adapty solicita a Apple que conceda el reembolso completo. - Para una compra sin moneda virtual ni período de acceso, Adapty no dispone de datos de consumo que enviar y responde a Apple sin indicar ninguna preferencia. Un consumible que no otorga moneda virtual entra en este caso. ### Establecer la política de consentimiento predeterminada \{#set-the-default-consent-policy\} La **Default consent policy** se aplica a los usuarios sin ningún valor de consentimiento registrado. Tu app no ha informado su consentimiento a través del [SDK](#update-a-users-consent) ni de la API del lado del servidor. La política define cómo trata Refund Saver a estos usuarios. Haz que coincida con el enfoque de consentimiento que elegiste en [Obtener el consentimiento del usuario](#obtain-user-consent): en la ventana **Edit Refund saver preferences**, en la pestaña **Apple**, configura la opción **Default consent policy**. <p> </p> | Opción | Descripción | | ------- | ------------------------------------------------------------ | | Opt-out | (predeterminado) Si Adapty desconoce 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 | (recomendado por Apple) Si Adapty desconoce el estado de consentimiento del usuario, asume que el consentimiento **no fue otorgado** y Refund Saver **no compartirá** ningún dato con Apple. | ### Establecer el comportamiento de reembolsos para un usuario específico en el dashboard \{#set-refund-behavior-for-a-specific-user-in-the-dashboard\} Puede que quieras configurar preferencias individuales para usuarios concretos. Abre el [perfil](profiles-crm) del usuario y busca la tarjeta **Refund saver settings** en la parte inferior de la columna izquierda. La preferencia por usuario es un único valor para ambas stores. Establece **Refund request preference** en **Grant refund**, **Grant prorated refund**, **Decline refund** o **No preference**. Adapty aplica el valor a las solicitudes de reembolso del usuario tanto en App Store como en Google Play. **Grant prorated refund** no tiene equivalente en Google, por lo que Adapty envía **No preference** en Google Play. :::note Las preferencias por usuario anulan el valor predeterminado a nivel de app, incluido el comportamiento "Decline first refund request, grant all next". ::: ## Obtener el consentimiento del usuario \{#obtain-user-consent\} :::info El consentimiento solo aplica a App Store; las revisiones de reembolso en Google Play no implican datos personales y no requieren consentimiento. ::: Cómo 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 usará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 ha 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 completamente, si incluyó un período de prueba, el importe total gastado y el importe total reembolsado."* Después de elegir un enfoque, establece la [política de consentimiento predeterminada](#set-the-default-consent-policy) correspondiente en el Dashboard. ### Verificar el consentimiento del usuario \{#check-user-consent\} Revisa el estado de consentimiento de un usuario para confirmar que su elección ha llegado a Adapty. En el Adapty Dashboard, abre el perfil del usuario y busca el ajuste **Allow data sharing** en la tarjeta **Refund saver settings**. **Yes** y **No** muestran la elección registrada del usuario; **Not set** significa que Refund Saver seguirá tu [política de consentimiento predeterminada](#set-the-default-consent-policy). :::note También puedes usar la API del lado del servidor para [obtener las preferencias individuales de reembolso y uso compartido](api-adapty/operations/getRefundSaverSettings). ::: ## Seguimiento de ahorros de solicitudes de reembolso \{#track-savings-from-refund-requests\} La sección [Refund Saver](https://app.adapty.io/refund-saver) muestra cuántas solicitudes de reembolso respondió Adapty y cómo se resolvió cada una. Para ver los datos de un solo store, selecciona la pestaña **Apple** o **Google** en la parte superior de la página. **All Platforms** combina ambos stores. Google no notifica a Adapty cuando resuelve una revisión de reembolso, por lo que Adapty consulta el estado del pedido hasta que la revisión se resuelve. Como resultado, los resultados de Google Play aparecen más tarde que los de App Store. La sección Refund Saver cubre únicamente las solicitudes que llegaron a Refund Saver. Para ver todos los reembolsos de tu app, incluidos los que Google decide sin tu intervención, consulta los gráficos [Refund money](refund-money) y [Refund events](refund-events) en Analytics. ## Gestionar Refund Saver desde el código de tu app \{#manage-refund-saver-from-your-app-code\} Tu app puede escribir dos valores por usuario a través del SDK de iOS: - **Preferencia de solicitud de reembolso**: Qué recomienda Adapty cuando responde a las solicitudes de reembolso de este usuario. El valor pertenece al perfil y no a ninguna plataforma en concreto, por lo que una preferencia de solicitud de reembolso se aplica tanto en App Store como en Google Play. - **Consentimiento para compartir datos**: Si Adapty puede enviar los datos de compra de este usuario a Apple. El consentimiento se aplica únicamente en App Store. Usa el SDK para las elecciones que el usuario realiza en tu propia UI, como un aviso de consentimiento o un interruptor de preferencias. El Dashboard y la [API del lado del servidor](api-adapty/operations/setRefundSaverSettings) escriben los mismos dos valores. Android no tiene métodos equivalentes: los frameworks multiplataforma escriben estos valores únicamente en iOS. ### Establecer una preferencia de reembolso para un usuario \{#set-a-refund-preference-for-a-user\} Puedes configurar la preferencia de reembolso en el código de tu app de forma individual para cada instalación según las acciones del usuario. La preferencia se aplica a las solicitudes de reembolso del usuario en ambas stores. 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> El SDK solo acepta los valores grant, decline y no-preference. Para establecer **Grant prorated refund** para un usuario, usa el Dashboard o la [API del servidor](api-adapty/operations/setRefundSaverSettings). ### Actualizar el consentimiento de un usuario \{#update-a-users-consent\} Para indicarle a Adapty si un usuario específico ha dado su consentimiento, utiliza el método `updateCollectingRefundDataConsent`. El consentimiento llega únicamente a Apple: una revisión de reembolso de Google Play no incluye ningún campo de consentimiento. El valor se persiste en el servidor asociado al perfil, por lo que una sola llamada por cambio es suficiente. <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> --- # 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 de tu conjunto de anuncios en el campo **Ad set name**. 2. En el desplegable **Conversion location**, selecciona **Website**. 3. En el campo **Performance goal**, selecciona el objetivo que se ajuste a cómo generas tráfico: - **Maximize number of conversions**: Optimiza según las compras y pruebas que Adapty reporta a tu dataset de Meta. Requiere el dataset y el evento de conversión en los dos pasos siguientes. - **Maximize number of landing page views**: Úsalo si tienes una landing page. - **Maximize number of link clicks**: Úsalo con un smart link que lleve a los usuarios directamente al store. 4. Si seleccionaste **Maximize number of conversions**, en el campo **Dataset**, selecciona el dataset que creaste en el [Paso 2](#step-2-add-meta-pixel). Para los otros dos objetivos, pasa directamente al paso 6. 5. Selecciona un **Conversion event**. En nuestro caso, probablemente será **Purchase** o **Start trial**. No te preocupes si ves un aviso de que tu dataset no tiene eventos todavía — eso simplemente significa que el dataset es nuevo. Para que Adapty reporte las compras y los ensayos en ese dataset, conecta el píxel a tu campaña de Adapty y mapea los eventos; consulta [Añadir campañas](ua-facebook#step-2-add-campaigns) y [Mapear eventos](ua-facebook#step-3-map-events). 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 aún estás aprendiendo, por lo que puedes empezar con importes más pequeños e ir ajustándolos sobre la marcha. Establece las fechas de inicio y, si procede, de fin del conjunto de anuncios. Por ejemplo, si quieres publicitar 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 a tu 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 separada para tu app y la conectaste en Meta Business Suite en el [Paso 1](#step-1-create-meta-ads-manager-account), selecciónala en el desplegable **Instagram account**. De lo contrario, selecciona **Use Facebook page** para que los anuncios de Instagram se publiquen usando la página de Facebook. 3. En **Ad setup**, selecciona cómo quieres publicar tu anuncio. Al promocionar apps, recomendamos seleccionar **Create ad** para que tu 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 cuántos creativos tengas y cómo quieras mostrarlos. 4. En la sección **Destination**, mantén **Website** seleccionado como **Main destination**. Luego, rellena el campo **Website URL** y el campo **URL parameters** en la sección **Tracking**. Ambos valores provienen de tu [campaña web en Adapty Attribution](ua-facebook) y dependen de adónde envía a los usuarios el anuncio: - **App Store o Google Play**: Para facilitar la aprobación de tu anuncio, divide tu **Click link** en el signo `?`. En **Website URL**, pega la parte anterior al `?`, por ejemplo `https://api-ua.adapty.io/api/v1/attribution/c/YOUR_CODE`. Cada campaña web tiene su propio código, así que copia este valor desde la campaña que estás configurando, no de otro anuncio. En **URL parameters**, pega todo lo que va después del `?`. - **Landing page o embudo web**, como FunnelFox: En **Website URL**, pega la URL de tu página. En **URL parameters**, pega el valor de **Ad URL parameters**. Consulta [Etiqueta las URLs de tus anuncios para embudos web](ua-tag-ad-urls). 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 de tu anuncio, en la sección **Languages** haz clic en **Add languages**. A continuación, añade un idioma principal, que tomará los textos de tu creativo 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é sigue? \{#whats-next\} Para activar tu anuncio, deberás añadir un método de pago si aún 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 Attribution](adapty-user-acquisition). ¿Aún no usas Adapty Attribution? [Reserva una llamada con nosotros](https://calendly.com/adapty-murat/meeting-1) para descubrir cómo puede ayudarte a rastrear 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 del 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**. Si tu anuncio dirige a los usuarios a una página de destino o un embudo web, como FunnelFox, pega la URL de esa página en **Destination URL** y añade los parámetros de seguimiento de Adapty tal como se describe en [Etiqueta las URLs de tus anuncios para embudos web](ua-tag-ad-urls). 3. En la sección **Creatives**, haz clic en **+ Videos and images**. 4. Si quieres usar tus publicaciones de TikTok como creatividades, seleciónalas en la pestaña **TikTok post tab**. Si no, cambia a la pestaña **Creative library** y haz clic en **Upload**. Los archivos que subas allí estarán disponibles desde esa pestaña más adelante, así que podrás reutilizarlos en otras campañas. 5. Recorta las creatividades para que se ajusten 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**. Puedes subir tus propios archivos mp3 desde ahí. Añadir música es obligatorio. 7. En el campo **Add text**, introduce 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 a tu anuncio. 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é sigue? \{#whats-next\} Ahora puedes [explorar cómo la campaña afecta los ingresos de tu app en el dashboard de Atribución de Adapty](adapty-user-acquisition). ¿Aún no usas Adapty Attribution? [Reserva una llamada con nosotros](https://calendly.com/adapty-murat/meeting-1) para descubrir cómo puede ayudarte a rastrear 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" description: "Documentación del legacy Onboarding Builder, que pronto dejará de estar disponible." --- :::warning A partir del 1 de octubre de 2026, no podrás crear nuevos paywalls ni onboardings en el Paywall Builder y el Onboarding Builder heredados, y se dejará de ofrecer soporte para ellos. Los paywalls y onboardings publicados seguirán funcionando en tus apps y podrás editarlos. Crea los nuevos en [Flow & Paywall Builder](adapty-flow-builder) — consulta [Deprecación del Paywall Builder y el Onboarding Builder](paywall-onboarding-builder-deprecation) para conocer los cambios. ::: Un onboarding es una serie de pantallas que presenta tu app a los usuarios, creada en el legacy Onboarding Builder. Puedes personalizar las pantallas con preguntas interactivas y variables, y luego realizar pruebas A/B con ellas. Los onboardings están disponibles para aplicaciones 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" --- :::warning A partir del 1 de octubre de 2026, no podrás crear nuevos paywalls ni onboardings en el Paywall Builder y el Onboarding Builder heredados, y se dejará de ofrecer soporte para ellos. Los paywalls y onboardings publicados seguirán funcionando en tus apps y podrás editarlos. Crea los nuevos en [Flow & Paywall Builder](adapty-flow-builder) — consulta [Deprecación del Paywall Builder y el Onboarding Builder](paywall-onboarding-builder-deprecation) para conocer los cambios. ::: Los [onboardings](onboardings) presentan a los nuevos usuarios el valor, las funcionalidades 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ña onboardings" description: "Crea onboardings con sentido." --- :::warning A partir del 1 de octubre de 2026, no podrás crear nuevos paywalls ni onboardings en el Paywall Builder y el Onboarding Builder heredados, y se dejará de ofrecer soporte para ellos. Los paywalls y onboardings publicados seguirán funcionando en tus apps y podrás editarlos. Crea los nuevos en [Flow & Paywall Builder](adapty-flow-builder) — consulta [Deprecación del Paywall Builder y el Onboarding Builder](paywall-onboarding-builder-deprecation) para conocer los cambios. ::: El onboarding builder para apps móviles sin código 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: "Documentación del antiguo Paywall Builder, próximamente discontinuado." --- :::warning A partir del 1 de octubre de 2026, no podrás crear nuevos paywalls ni onboardings en el Paywall Builder y el Onboarding Builder heredados, y se dejará de ofrecer soporte para ellos. Los paywalls y onboardings publicados seguirán funcionando en tus apps y podrás editarlos. Crea los nuevos en [Flow & Paywall Builder](adapty-flow-builder) — consulta [Deprecación del Paywall Builder y el Onboarding Builder](paywall-onboarding-builder-deprecation) para conocer los cambios. ::: El **Paywall Builder** de Adapty es la herramienta anterior sin código para diseñar paywalls, que ya no está en desarrollo. Requiere Adapty SDK v3.0 o posterior. No crees nuevos paywalls aquí, especialmente con SDK v4. Los que ya hayas creado seguirán funcionando: el SDK v4 los renderiza a través de los mismos métodos que los flows. Para recrear un paywall existente como flow en un solo clic, consulta [Convertir un paywall en un flow](convert-paywall-to-flow). Para mostrar un paywall que ya has creado, obtenlo junto con su configuración de vista: [iOS](get-pb-paywalls) | [Android](android-get-pb-paywalls) | [React Native](react-native-get-pb-paywalls) | [Flutter](flutter-get-pb-paywalls) | [Unity](unity-get-pb-paywalls) | [Capacitor](capacitor-get-pb-paywalls) | [Kotlin Multiplatform](kmp-get-pb-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: "Aprende cómo funcionan los placements, las audiencias y las pruebas A/B antes de integrar Adapty en tu app de FlutterFlow." --- Con Adapty, puedes crear y ejecutar paywalls y pruebas A/B en diferentes puntos del recorrido del usuario en tu app móvil, como Onboarding, Configuración, etc. Estos puntos se llaman [Placements](placements). Un placement en tu app puede gestionar múltiples paywalls o [pruebas A/B](ab-tests) a la vez, cada uno diseñado para un grupo concreto de usuarios, lo que llamamos [Audiencias](audience). Además, puedes experimentar con paywalls, reemplazando uno por otro a lo largo del tiempo sin necesidad de publicar una nueva versión de la app. Lo único que escribes directamente en el código de la app móvil 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), [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 acceder al 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 [iniciarla](ff-getting-started#initiate-adapty-plugin) como se muestra a continuación. :::warning Antes de comenzar, ten en cuenta las siguientes limitaciones: - La librería de Adapty para FlutterFlow no es compatible con aplicaciones web. Evita compilar aplicaciones web con ella. - La librería de Adapty para FlutterFlow no es compatible con paywalls creados con el Paywall Builder de Adapty. Debes 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 un flow para mostrar datos del paywall" description: "Crea un action flow en FlutterFlow que recupere los datos del paywall y los productos de Adapty para tu propia página de paywall." --- :::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. ::: Tras añadir la librería de Adapty como dependencia en tu proyecto de FlutterFlow, es momento de construir el flow que **obtiene 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 todo va bien, mostraremos el título del producto y el precio en la página del paywall. En caso contrario, mostraremos un mensaje de error. Antes de continuar, asegúrate de haber completado lo siguiente: 1. [Creado al menos un paywall y añadido al menos un producto a él](create-paywall) en el Adapty Dashboard. 2. [Creado al menos un placement](create-placement) y [añadido tu paywall a él](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 de paywall" description: "Mapea los datos de productos de Adapty, como el título y el precio, en la página de paywall que diseñaste en FlutterFlow." --- Una vez que hayas [recibido todos los datos de producto necesarios](ff-action-flow), es momento de mapearlos en el 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: "Permite que los usuarios compren un producto desde tu paywall de FlutterFlow a través de Adapty." --- ¡Enhorabuena! Has [configurado tu paywall para mostrar los datos de producto de Adapty](ff-add-variables-to-paywalls) con éxito, incluyendo el título del producto y el precio. Ahora pasemos al último paso: 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. Comprobar el acceso al contenido de pago" description: "Comprueba el nivel de acceso de un usuario en FlutterFlow para decidir si desbloquear el contenido de pago." --- Para determinar si un usuario tiene acceso a un contenido de pago concreto, necesitas 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 comprobando 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**. 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 contiene los datos sobre el 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 de la siguiente manera: - **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 a continuación. - **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 adecuada 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 Adapty para FlutterFlow" description: "Referencia de las acciones personalizadas y tipos de datos de Adapty disponibles en FlutterFlow." --- ## 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-10-30T13:00:39.970Z_ _Successfully processed: 363/363 files_ # UNITY - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-10-30T13:00:39.982Z Total files: 56 --- # File: unity-sdk-overview --- --- title: "Descripción general del SDK de Unity" description: "Aprende sobre el SDK de Unity de Adapty y sus principales características." --- [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Unity.svg?style=flat&logo=unity)](https://github.com/adaptyteam/AdaptySDK-Unity/releases) ¡Bienvenido! Estamos aquí para que las compras in-app sean pan comido 🚀 Hemos desarrollado el SDK de Adapty para Unity con el objetivo de quitarte el dolor de cabeza de las compras in-app para que puedas centrarte en lo que mejor sabes hacer: crear juegos increíbles. Esto es lo que gestionamos por ti: - Gestiona compras, validación de recibos y administración de suscripciones sin configuración adicional - Crea y prueba paywalls sin publicar actualizaciones de la app - Obtén análisis detallados de compras con cero configuración: cohortes, LTV, churn y análisis de embudo incluidos - Mantén el estado de la suscripción del usuario siempre actualizado en todas las sesiones y dispositivos - Integra tu app con servicios de atribución y análisis de marketing con una sola línea de código :::note Antes de entrar 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 dejarlo todo listo primero. ::: ## Primeros pasos \{#get-started\} For a fully automated integration, use the [adapty-integration skill](https://github.com/adaptyteam/adapty-skills): 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-unity): Añade el SDK como dependencia a tu proyecto y actívalo en el código. 2. [Activar compras con flows y paywalls](unity-quickstart-paywalls): Configura el flujo de compra para que los usuarios puedan adquirir productos. Si prefieres crear tu propia interfaz, consulta [Implementar paywalls personalizados](unity-quickstart-manual). 3. [Comprobar el estado de la suscripción](unity-check-subscription-status): Verifica automáticamente el estado de suscripción del usuario y controla su acceso al contenido de pago. 4. [Identificar usuarios (opcional)](unity-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? Te tenemos cubierto: - **App de ejemplo**: Consulta nuestro [ejemplo completo](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets) que muestra la configuración completa ## Conceptos principales \{#main-concepts\} Antes de entrar en el código, familiarícémonos 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 necesidad de actualizar la app: 1. [**Producto**](product) - Todo lo que está disponible para comprar en tu app: una suscripción, un producto consumible o un acceso de por vida. 2. **Flows & paywalls** - Productos agrupados con configuración, vinculados a un placement. Dos formas de crearlos, comparadas en [Flows & paywalls](flows-and-paywalls): - **[Flow & Paywall Builder](adapty-flow-builder)** - Interfaz visual sin código. Adapty renderiza la UI y gestiona la compra por ti. - **[Paywalls personalizados](paywalls)** - Sin configuración visual; creas la UI en tu propio código y llamas a `MakePurchase` tú mismo. Consulta [Implementar paywalls personalizados](unity-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 "cuándo" de tu estrategia de monetización. Los placements más habituales son: - `main` - Tu ubicación principal de 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` para tu primera integración, y luego [piensa en qué otros puntos de tu app podrían estar los usuarios listos para comprar](choose-meaningful-placements). 4. [**Perfil**](profiles-crm) - Cuando los usuarios compran un producto, su perfil recibe un **nivel de acceso** que usas para definir el acceso a las funciones de pago. --- # 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 renderiza flows, además de los paywalls del constructor antiguo. :::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 funcionalidades básicas. ::: ## Requisitos \{#requirements\} El SDK de Adapty es compatible con iOS 13.0+, pero requiere iOS 15.0+ para trabajar con paywalls creados en el Paywall Builder. Adapty SDK 4.1 — que añade soporte para [flows](adapty-flow-builder) — requiere **iOS 15.0+** para toda la app: un validador de compilación en el Unity Editor detiene el build de iOS si el deployment target es inferior. Compilar para iOS con el SDK 4.x también requiere **Xcode 26** o posterior — el SDK nativo de iOS que añade como Swift package está compilado con Swift tools 6.2. :::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. ::: ## Dependencias \{#dependencies\} El SDK de Adapty funciona con las siguientes versiones de la Google Play Billing Library en Android: | Versión del SDK de Adapty | Versión de la Billing Library | |---|---| | 3.17.0 y posteriores | v8 | | 3.15.0–3.15.2 | v7 por defecto, v8 si otra dependencia la eleva | :::note La compatibilidad con una versión de la Billing Library no significa que Adapty sea compatible con todas las funciones que Google introdujo en ella. Antes de adoptar una nueva capacidad de facturación de Google Play, consulta [Producto en Play Store](android-products). ::: ## Instala 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) Siempre recomendamos instalar la versión más reciente disponible del SDK — incluye las últimas correcciones de estabilidad y mejoras. Elige tu método de instalación preferido: <Tabs groupId="unity-install-method"> <TabItem value="git-url" label="Git URL"> Instala el SDK de Adapty a través de 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 ``` 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> Tras 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 y las dependencias de gradle de Android. 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. ### Adapty SDK 4.1 SDK 4.1 — que añade soporte para [flows](adapty-flow-builder) — es la primera versión estable de la línea 4.x. Para instalarlo a través del Unity Package Manager, añade la etiqueta de versión a la URL de Git: ``` https://github.com/adaptyteam/AdaptySDK-Unity.git?path=/Packages/com.adapty.unity-sdk#4.1.0 ``` Si instalas mediante el paquete de Unity, descarga `adapty-unity-plugin-4.1.0.unitypackage` desde la [versión 4.1.0](https://github.com/adaptyteam/AdaptySDK-Unity/releases/tag/4.1.0). Dos cambios en la configuración de compilación vienen con la versión 4.x: el SDK nativo de Adapty para iOS se declara como un paquete Swift remoto y ya no se instala a través de CocoaPods: - Actualiza el External Dependency Manager a **1.2.188 o posterior** — las versiones anteriores no admiten dependencias de Swift Package Manager. Esta es la versión que declara el SDK 4.1 como dependencia entre pares, por lo que Unity te avisará si tu proyecto tiene una versión más antigua. - Los pasos de CocoaPods mencionados anteriormente (`iOS Resolver -> Install Cocoapods`, abrir `Unity-iPhone.xcworkspace`) son exclusivos del SDK 3.x. Con el SDK 4.1, EDM añade el paquete Swift al proyecto Xcode generado de forma automática. - Establece el deployment target de iOS en **15.0 o posterior**. Un validador de compilación en el Unity Editor detiene el build de iOS si no se cumple este requisito. Consulta la [guía de migración](migration-to-unity-sdk-v4) para ver la lista completa de cambios en la línea 4.x. ## 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) { } } ``` :::note En el SDK 4.1, las interfaces de listener siguen la convención de prefijo I de C# — implementa `IAdaptyEventListener` en lugar de `AdaptyEventListener` — y la interfaz requiere un método adicional, `OnReceivePromotedPurchase`. Consulta la [guía de migración](migration-to-unity-sdk-v4). ::: :::important Espera al 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 [Flow & Paywall Builder](adapty-flow-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](unity-quickstart-paywalls). - Si creas 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 tienes previsto usar el [Flow & Paywall Builder](adapty-flow-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); ``` ### Habilitar la atribución de Adapty \{#enable-adapty-attribution\} :::info Este parámetro está disponible a partir de la versión 4.1 del SDK. ::: Si usas [Adapty Attribution](user-acquisition), establece `SetAdaptyAttributionEnabled` en `true` al activar el SDK. El valor predeterminado es `false`: sin este parámetro, el SDK no registra instalaciones ni entrega los detalles de instalación a tu app. En versiones del SDK anteriores a la 4.1, la atribución de Adapty se habilita automáticamente. ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAdaptyAttributionEnabled(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 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 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 (que normalmente se encuentra 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 espacio de nombres `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 finales e indique al fusionador de manifiestos que reemplace los valores de las bibliotecas: ```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 usa diferentes 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 ejemplo. Reemplaza o añade reglas para cualquier otro SDK que estés usando 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 heredado 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: "Activar compras con Flow y Paywall Builder en el SDK de Unity" description: "Guía de inicio rápido para activar compras in-app con el Flow y Paywall Builder de Adapty." --- Esta guía utiliza las APIs del SDK de Unity v4 (beta) de Adapty. Si usas la v3, consulta la [guía de migración](migration-to-unity-sdk-v4) para los nombres de métodos correspondientes. 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 y paywalls**](adapty-flow-builder) – pantallas que presentan productos a los usuarios, creadas en el Flow & Paywall Builder sin código. El SDK los recupera mediante `GetFlow`. Si prefieres construir la UI en tu propio código, consulta [Implementar paywalls personalizados](unity-quickstart-manual). - [**Placements**](placements) – dónde y cuándo muestras flows y paywalls en tu app (como `main`, `onboarding`, `settings`). Los vinculas 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 contenidos a diferentes 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 Flow & Paywall Builder | ✅ Fácil | [Creas un flow o paywall completo y listo para compras en el editor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona toda la lógica de compra, validación de recibos y gestión de suscripciones. | | Paywalls personalizados | 🟡 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 la oferta 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 flow o paywall creado en el Adapty Flow & Paywall Builder.** Si prefieres crear la interfaz del paywall tú mismo, consulta [Implementar paywalls personalizados](unity-quickstart-manual). ::: Para mostrar un flow o paywall creado en el Adapty Flow & Paywall Builder, en el código de tu app solo necesitas: 1. **Obtén el flow o el paywall**: Obténlo desde Adapty. 2. **Muéstralo y Adapty gestionará las compras por ti**: Muestra la vista en tu app. 3. **Gestiona las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abre enlaces o cierra el paywall cuando el usuario pulse los 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 o paywall](paywall-builder-templates) y [añade productos a él](paywall-product-block). 4. [Crea un placement y añade tu flow o paywall a él](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 flows y placements usando el [CLI para desarrolladores](developer-cli-quickstart). ::: ## 1. Obtén el flow o el paywall \{#1-get-the-flow-or-paywall\} Tus flows y paywalls están asociados con placements configurados en el dashboard. Los placements te permiten mostrar diferentes flows o paywalls a distintas audiencias o ejecutar [pruebas A/B](ab-tests). Para obtener un flow o paywall creado en el Adapty Flow & Paywall Builder, necesitas: 1. Obtener el objeto `flow` a través del ID del [placement](placements) usando el método `GetFlow`. 2. Crea la vista del flow con el método `CreateFlowView`. La vista contiene los elementos de UI y el estilo necesarios para mostrarla. Si el flow no tiene ninguna vista configurada, `CreateFlowView` devuelve un error — trátalo en el callback. :::important Para obtener la vista, [publica el flow o paywall](builder-save-publish). Un flow o paywall con cambios sin publicar tiene el estado **Dirty**, y su placement seguirá sirviendo la última versión publicada. ::: ```csharp showLineNumbers Adapty.GetFlow("YOUR_PLACEMENT_ID", (flow, error) => { if (error != null) { // handle the error return; } // Create the flow view AdaptyUI.CreateFlowView(flow, (view, error) => { if (error != null) { // the flow has no view configured, or view creation failed return; } // view - the flow view ready to be presented }); }); ``` :::info Este quickstart proporciona la configuración mínima necesaria para mostrar un flow o paywall. Para más detalles sobre la configuración avanzada, consulta nuestra [guía para obtener flows y paywalls](unity-get-pb-paywalls). ::: ## 2. Mostrar el flow o el paywall \{#2-display-the-flow-or-paywall\} Ahora que ya 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()` sobre el `view` creado por el método `CreateFlowView`. Cada `view` solo puede usarse una vez: cuando lo descartes, llama a `CreateFlowView` de nuevo para mostrarlo otra vez. ```csharp showLineNumbers title="Unity" view.Present((error) => { // handle the error }); ``` :::info Para más detalles sobre cómo mostrar un flow o un paywall, consulta nuestra [guía](unity-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#handle-button-actions\} Cuando los usuarios hacen clic en los botones del flow o del 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 que gestiones las acciones en tu código. Por ejemplo, tu paywall probablemente tiene 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 necesita implementar la interfaz `IAdaptyFlowsEventsListener` y registrarse como listener. Ten en cuenta que el flow permanece abierto después de una compra exitosa. Si quieres cerrarlo una vez que la compra finalice, cierra la vista en el callback `FlowViewDidFinishPurchase`. :::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, IAdaptyFlowsEventsListener { void Start() { // Register this class as the flows events listener Adapty.SetFlowsEventsListener(this); } // IAdaptyFlowsEventsListener method - handles button actions public void FlowViewDidPerformAction( AdaptyUIFlowView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: view.Dismiss(null); break; case AdaptyUIUserActionType.OpenUrl: AdaptyUI.OpenUrl(action.Value, AdaptyWebPresentation.ExternalBrowser, null); break; default: break; } } // IAdaptyFlowsEventsListener method - dismiss the flow after a purchase public void FlowViewDidFinishPurchase( AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult ) { if (purchasedResult.Type != AdaptyPurchaseResultType.UserCancelled) { view.Dismiss(null); } } } ``` ## 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 flow o 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 flow. Ahora necesitas [verificar el nivel de acceso de los usuarios](unity-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\} Así es como todos esos pasos pueden integrarse juntos en tu app. ```csharp showLineNumbers using System; using System.Collections.Generic; using UnityEngine; using AdaptySDK; public class FlowManager : MonoBehaviour, IAdaptyFlowsEventsListener { [SerializeField] private string placementId = "YOUR_PLACEMENT_ID"; void Start() { // Register for flow events Adapty.SetFlowsEventsListener(this); GetAndDisplayFlow(); } private void GetAndDisplayFlow() { Adapty.GetFlow(placementId, (flow, error) => { if (error != null) { Debug.LogError("Error getting flow: " + error.Message); return; } CreateAndPresentFlowView(flow); }); } private void CreateAndPresentFlowView(AdaptyFlow flow) { AdaptyUI.CreateFlowView(flow, (view, error) => { if (error != null) { // the flow has no view configured — use custom logic Debug.LogError("Error creating flow view: " + error.Message); return; } view.Present((presentError) => { if (presentError != null) { Debug.LogError("Error presenting flow: " + presentError.Message); return; } Debug.Log("Flow presented successfully"); }); }); } // IAdaptyFlowsEventsListener implementation public void FlowViewDidPerformAction( AdaptyUIFlowView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: Debug.Log("Close button pressed"); view.Dismiss(null); break; case AdaptyUIUserActionType.OpenUrl: AdaptyUI.OpenUrl(action.Value, AdaptyWebPresentation.ExternalBrowser, null); break; default: break; } } public void FlowViewDidFinishPurchase( AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult ) { if (purchasedResult.Type != AdaptyPurchaseResultType.UserCancelled) { view.Dismiss(null); } } // Required interface methods (implement as needed) public void FlowViewDidAppear(AdaptyUIFlowView view) { } public void FlowViewDidDisappear(AdaptyUIFlowView view) { } public void FlowViewDidSelectProduct(AdaptyUIFlowView view, string productId) { } public void FlowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) { } public void FlowViewDidFailPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyError error) { } public void FlowViewDidStartRestore(AdaptyUIFlowView view) { } public void FlowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { } public void FlowViewDidFailRestore(AdaptyUIFlowView view, AdaptyError error) { } public void FlowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { } public void FlowViewDidFailLoadingProducts(AdaptyUIFlowView view, AdaptyError error) { } public void FlowViewDidFinishWebPaymentNavigation(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyError error) { } public void FlowViewDidReceiveAnalyticEvent(AdaptyUIFlowView view, string name, IDictionary<string, object> @params) { } public void ShowFlow() { GetAndDisplayFlow(); } } ``` --- # 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. :::note En el SDK 4.1, las interfaces de listener siguen la convención de prefijo I de C# — implementa `IAdaptyEventListener` en lugar de `AdaptyEventListener` — y la interfaz requiere un método adicional, `OnReceivePromotedPurchase`. Consulta la [guía de migración](migration-to-unity-sdk-v4). ::: ```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; } if (!profile.AccessLevels.TryGetValue("YOUR_ACCESS_LEVEL", out var accessLevel) || !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 skill de integración del SDK" description: "Usa la skill adapty-integration para integrar el SDK de Adapty en tu app de Unity de principio a fin con tu herramienta de codificación IA." --- :::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-unity) — te lleva a través de cada etapa con la documentación adecuada. ::: El skill `adapty-integration` automatiza la integración de Adapty de principio a fin: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. Detecta tu plataforma automáticamente y obtiene la documentación relevante de Adapty en cada etapa. Funciona en cualquier agente de programación que soporte skills: Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI y otros. El skill forma parte del [plugin `adapty-skills`](https://github.com/adaptyteam/adapty-skills). Para instalar el plugin, ejecuta en tu terminal: ```bash npx adapty skills install ``` Reinicia tu agente para cargar los skills. Para otros métodos de instalación, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Claude Code no actualiza el plugin por sí solo. Para obtener los nuevos skills a medida que se publiquen, en Claude Code ejecuta `/plugin`, abre **Marketplaces**, selecciona `adapty` y elige **Enable auto-update**. Para el comando de actualización en otros agentes, consulta el [README del plugin](https://github.com/adaptyteam/adapty-skills/blob/main/plugin/README.md#install). Para iniciar la integración, pide a tu agente: ``` Integrate Adapty into my app ``` El skill hace algunas preguntas de configuración y luego te guía a través de 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-integration skill](https://github.com/adaptyteam/adapty-skills): 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 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. El skills CLI no actualiza el skill por sí solo. Para obtener la última versión, ejecuta `npx skills update`. ### 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 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 la cadena que pasas a `Adapty.GetFlow("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\} 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 requiere ningún cambio de código. [Pruebas A/B](ab-tests) - **Flows y placements adicionales**: Añade más llamadas `GetFlow` 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 Flow & Paywall Builder**](adapty-flow-builder): Creas flows en el editor no-code de Adapty y el SDK los renderiza automáticamente. - [**Paywalls personalizados**](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? Lee la [tabla comparativa en el quickstart](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 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 obtener instrucciones de configuración. <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Flow & Paywall Builder" default> **Guías:** - [Activar compras con Flow y Paywall Builder (inicio rápido)](unity-quickstart-paywalls) - [Obtener flows y su configuración](unity-get-pb-paywalls) - [Mostrar flows](unity-present-paywalls) - [Gestionar eventos de flow](unity-handling-events) - [Responder a acciones de 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 que configuraste. Al pulsar un producto, se activa el diálogo de compra en sandbox. - **Problema común:** Flow vacío o error `GetFlow` → 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 personalizados"> **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](unity-quickstart-manual) - [Obtener paywalls y productos personalizados](fetch-paywalls-and-products-unity) - [Presentar paywalls personalizados](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-paywalls --- --- title: "Flows y paywalls - Unity" description: "Muestra y gestiona flows y paywalls creados en los builders sin código de Adapty en tu app de Unity." --- ## Mostrar paywalls \{#display-paywalls\} ### Adapty Flow & Paywall Builder <CustomDocCardList ids={['unity-get-pb-paywalls', 'unity-present-paywalls', 'unity-handling-events', 'unity-handle-paywall-actions']} /> :::tip Para empezar rápidamente con los flows y paywalls de Adapty, consulta nuestra [guía de inicio rápido](unity-quickstart-paywalls). ::: ### Implementar paywalls personalizados \{#implement-custom-paywalls\} <CustomDocCardList ids={['unity-quickstart-manual', 'fetch-paywalls-and-products-unity', 'present-remote-config-paywalls-unity', 'unity-making-purchases']} /> Para más guías sobre cómo implementar paywalls personalizados y gestionar compras, consulta [Implementar paywalls personalizados](unity-implement-paywalls-manually). ## Funciones útiles \{#useful-features\} <CustomDocCardList ids={['unity-use-fallback-paywalls', 'unity-web-paywalls']} /> --- # File: unity-get-pb-paywalls --- --- title: "Obtener flows y paywalls - Unity" description: "Obtén flows y paywalls de Adapty en tu app de Unity." --- <SDKv4> <MethodPromo method="GetFlow" /> Después de [diseñar tu flow](adapty-flow-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. :::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 \{#before-you-start\} Necesitas: - **Productos en el Adapty Dashboard**: [Crea tus productos](create-product). - **Un flow con productos**: Constrúyelo en el [Flow & Paywall Builder](adapty-flow-builder) y [asigna productos a él](paywall-product-block). - **Un placement**: [Crea un placement](create-placement) y asígnale tu flow. - **SDK de Adapty instalado**: Consulta la [guía de instalación del SDK de Unity](sdk-installation-unity). ## Obtener un flow/paywall \{#fetch-flowpaywall\} Si has diseñado un flow o paywall en el builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese flow o paywall contiene tanto lo que debe mostrarse en él 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](unity-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`: ```csharp showLineNumbers Adapty.GetFlow( "YOUR_PLACEMENT_ID", AdaptyPlacementFetchPolicy.Default, TimeSpan.FromSeconds(5), (flow, error) => { if (error != null) { // handle the error return; } // flow - the requested flow/paywall } ); ``` 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: `AdaptyPlacementFetchPolicy.Default` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee la caché primero y va al servidor solo cuando no hay nada en caché. 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 regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee la caché primero mientras la copia en caché sea más reciente que el `TimeSpan` que indiques, y va al servidor una vez que sea más antigua.</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 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 garantizar que siempre obtengas la versión más reciente, 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 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> | | 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 con fines de precarga, interfaz personalizada o comprobaciones programáticas, llama a `GetPaywallProducts(flow)`. | ## Obtener la configuración de la vista \{#fetch-the-view-configuration\} Tras obtener 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 se diseñó en el **Flow & Paywall Builder** (un flow) o en el **old Paywall Builder** (un paywall), `CreateFlowView` devuelve la vista lista para mostrarse. Si el placement es un paywall personalizado sin interfaz de Builder, `CreateFlowView` devuelve un error — [renderízalo tú mismo](present-remote-config-paywalls-unity). :::important Asegúrate de [publicar el flow](builder-save-publish). Un flow con ediciones sin publicar tiene el estado **Dirty** y su placement sigue sirviendo la última versión publicada. ::: ```csharp showLineNumbers var parameters = new AdaptyUICreateFlowViewParameters() .SetPreloadProducts(true) .SetLoadTimeout(TimeSpan.FromSeconds(5)); AdaptyUI.CreateFlowView(flow, parameters, (view, error) => { if (error != null) { // the flow has no view configured, or view creation failed return; } // use view }); ``` | Parámetro | Presencia | Descripción | | :--------------------------- | :------------- | :----------------------------------------------------------- | | **flow** | obligatorio | Un objeto `AdaptyFlow` obtenido mediante `Adapty.GetFlow`. | | **Locale** | opcional | El identificador de la [localización del Builder](add-paywall-locale-in-adapty-paywall-builder) con la que se renderizará el flow o paywall, por ejemplo `en` o `pt-br`. El flow se localiza cuando se crea su vista, por lo que este es el único lugar donde elegir su localización. El SDK no lee el idioma del dispositivo: si omites este parámetro, el flow se renderiza en `en`, o en su idioma predeterminado si el flow no tiene localización `en`. El código debe coincidir exactamente con un código de localización del flow. Consulta [Usar localizaciones y códigos de idioma](unity-localizations-and-locale-codes). | | **LoadTimeout** | opcional | Este valor limita el tiempo de espera de este método. Si se alcanza el límite, se devolverán datos en caché o el respaldo 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 consistir en diferentes solicitudes internamente. | | **PreloadProducts** | opcional | Establece en `true` para precargar productos y mejorar el rendimiento. Cuando está activado, los productos se cargan con antelación, reduciendo el tiempo necesario para mostrar el flow o paywall. | | **CustomTags** | opcional | Los valores de tu app para las [etiquetas personalizadas](flow-custom-tags) del flow, en un diccionario con el nombre completo de cada etiqueta como clave. Pasa el diccionario al método `SetCustomTags`. Para más detalles, consulta [Pasar datos a flows](unity-flow-custom-tags). | | **ProductPurchaseParameters** | opcional | Solo Android (se ignora en iOS). Un diccionario de `AdaptyProductIdentifier` a `AdaptyPurchaseParameters`. Úsalo para configurar parámetros específicos de compra, como ofertas personalizadas o parámetros de actualización de suscripción para productos individuales en el flow o paywall. | | **EnableSafeAreaPaddings** | opcional | Solo Android (se ignora en iOS). Cuando es `true`, la vista del flow aplica rellenos de área segura. Valor predeterminado: `true`. El valor predeterminado es adecuado para la mayoría de los casos. | :::note Si usas varios idiomas, aprende cómo añadir una [localización en el Builder](add-paywall-locale-in-adapty-paywall-builder). ::: Una vez cargado, [presenta el flow o el paywall](unity-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 necesitas preocuparte 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 quisieras. En esas situaciones, puede que prefieras mostrar un flow o paywall 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 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 flow/paywall](#fetch-flowpaywall) anterior. :::warning Por qué recomendamos usar `GetFlow` El método `GetFlowForDefaultAudience` tiene algunas desventajas importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar diferentes flows para distintas 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 (heredada) o asumir que los usuarios con esa versión podrían tener problemas con flows que no se rendericen 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ís, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estas desventajas para beneficiarte de una recuperación más rápida del flow o del paywall, usa el método `GetFlowForDefaultAudience` de la siguiente manera. De lo contrario, utiliza `GetFlow` descrito [anteriormente](#fetch-flowpaywall). ::: ```csharp showLineNumbers Adapty.GetFlowForDefaultAudience( "YOUR_PLACEMENT_ID", AdaptyPlacementFetchPolicy.Default, (flow, error) => { if (error != null) { // handle the error return; } // flow - the requested flow } ); ``` | 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: `AdaptyPlacementFetchPolicy.Default` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero al servidor y devuelve los datos en caché si esa petición falla. Recomendamos esta opción porque garantiza que tus usuarios reciban siempre los datos más actualizados.</p><p></p><p>Sin embargo, si crees que tus usuarios tienen una conexión inestable, considera `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y consulta el servidor solo cuando no hay nada en caché. 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>Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre ambas: lee primero la caché mientras la copia en caché sea más reciente que el `TimeSpan` que indiques, y consulta el servidor cuando sea más antigua.</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 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 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. - Mostrar contenido multimedia incluido en la app, para que la primera pantalla se renderice sin necesidad de descarga. Consulta [Mostrar contenido multimedia de la primera pantalla desde el bundle de la app](unity-optimize-paywall-fetching#show-first-screen-media-from-the-app-bundle). A continuación se muestra un ejemplo de cómo puedes proporcionar assets 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 AdaptyUICreateFlowViewParameters() .SetCustomAssets(customAssets) .SetLoadTimeout(TimeSpan.FromSeconds(3)); AdaptyUI.CreateFlowView(flow, parameters, (view, error) => { // handle the result }); ``` :::note Si no se encuentra un asset o falla al cargarse, el flow o paywall volverá a su apariencia predeterminada configurada en el Builder. ::: ## Configurar temporizadores definidos por el desarrollador \{#set-up-developer-defined-timers\} Para usar temporizadores personalizados en tu app de Unity, pasa un diccionario de IDs de temporizador y sus fechas de finalización 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(2026, 1, 1) } }; var parameters = new AdaptyUICreateFlowViewParameters() .SetCustomTimers(customTimers) .SetLoadTimeout(TimeSpan.FromSeconds(3)); AdaptyUI.CreateFlowView(flow, 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 resolver de temporizadores 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 Año Nuevo. - `CUSTOM_TIMER_6H`: El tiempo restante en un período de 6 horas que comenzó cuando el usuario abrió el flow o paywall. </SDKv4> <SDKv3> Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el antiguo Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu aplicación móvil. El primer paso de este proceso es obtener el paywall asociado al placement y su configuración de vista, como se describe a continuación. :::warning Los paywalls creados con el Paywall Builder del SDK 3.x requieren la versión 3.3.0 o superior del SDK de Unity. ::: Por favor, ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Si implementas paywalls personalizados, consulta el tema [Obtener paywalls y productos para paywalls personalizados 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 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-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. 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> | | **fetchPolicy** | por defecto: `AdaptyPlacementFetchPolicy.Default` | <p>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y consulta el servidor solo cuando no hay nada en caché. 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 solicitudes de red.</p><p></p><p>Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el `TimeSpan` que indiques, y consulta el servidor cuando sea más antigua.</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 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 escasa.</p> | | **loadTimeout** | por defecto: 5 seg | <p>Este valor limita el tiempo de espera de este método. Si se alcanza el timeout, 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 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. | ## Obtén 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 toggle **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 `ViewConfiguration`, lo que indica que fue creado con Paywall Builder. Esto te guiará sobre cómo mostrar el paywall. Si la `ViewConfiguration` está presente, trátalo como un paywall de Paywall Builder; si no, [trátalo como un paywall personalizado](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 admites varios idiomas, añade una localización a tu paywall. Para consultar los códigos que debes usar, consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes). ::: Una vez que tengas la vista, [muestra 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 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`, como se detalla en la sección [Obtener paywall](#fetch-paywall-designed-with-paywall-builder) anterior. :::warning Considera usar `GetPaywall` en lugar de `GetPaywallForDefaultAudience`, ya que este último tiene limitaciones importantes: - **Problemas de compatibilidad**: Puede generar problemas al mantener varias versiones de la app, lo que obliga a diseños retrocompatibles o a aceptar que las versiones 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 para tu caso de uso la obtención más rápida compensa estos inconvenientes, usa `GetPaywallForDefaultAudience` como se muestra a continuación. De lo contrario, usa `GetPaywall` como se describe [arriba](#fetch-paywall-designed-with-paywall-builder). ::: ```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 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: `AdaptyPlacementFetchPolicy.Default` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada almacenado. 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>Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el `TimeSpan` que indiques, y consulta el servidor cuando sea más antigua.</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 paywalls localmente en dos capas: la caché de actualización regular 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 por si el CDN no está disponible. Este sistema está diseñado para garantizar que siempre recibas la versión más reciente de tus paywalls, asegurando la fiabilidad incluso cuando la conexión a internet es limitada.</p> | </SDKv3> --- # File: unity-present-paywalls --- --- title: "Mostrar flows y paywalls - Unity" description: "Presenta flows y paywalls a los usuarios en tu aplicación Unity." --- <SDKv4> <MethodPromo method="GetFlow" label="Display flows and paywalls" /> Si has creado un flow o un paywall en el antiguo Paywall Builder, no necesitas 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 se debe mostrar. :::warning Esta guía cubre flows y paywalls renderizados por Adapty. El proceso es diferente para paywalls personalizados y el [modo Observer](observer-vs-full-mode). - Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](present-remote-config-paywalls-unity). - Para presentar flows en **Observer mode**, consulta [Presentar flows en Observer mode](unity-present-flows-in-observer-mode). ::: Para obtener el objeto `view` que se usa a continuación, consulta [Obtener flows y paywalls](unity-get-pb-paywalls). Para mostrar un flow o paywall, utiliza el método `view.Present()` en la `view` creada por el método [`CreateFlowView`](unity-get-pb-paywalls#fetch-the-view-configuration). Cada `view` solo puede usarse una vez: una vez descartada, la vista se destruye. Si necesitas mostrar el flow de nuevo, llama a `CreateFlowView` otra vez para crear una nueva instancia de `view`. :::warning Reutilizar la misma `view` sin recrearla puede provocar un error. ::: ```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 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 presente correctamente por encima del flow en todas las plataformas. ```csharp showLineNumbers title="Unity" var dialog = new AdaptyUIDialogConfiguration() .SetTitle("Close this screen?") .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 flow view.Dismiss((error) => { // handle the error }); } // If primary - do nothing, user stays } }); ``` ## 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`. ```csharp showLineNumbers title="Unity" view.Present(AdaptyUIIOSPresentationStyle.PageSheet, (error) => { // handle the error }); ``` </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 ya incluye tanto lo que se debe mostrar como la forma en que debe mostrarse. :::warning Esta guía cubre el **antiguo Paywall Builder**, que requiere el SDK de Adapty 3.3.0 o posterior. Para presentar paywalls personalizados, consulta [Presentar paywalls personalizados](present-remote-config-paywalls-unity). ::: 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 un 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 }); ``` </SDKv3> --- # File: unity-handle-paywall-actions --- --- title: "Responder a acciones de flow - Unity" description: "Gestiona las acciones de botones de flows y paywalls en tu app de Unity." --- <SDKv4> Si estás creando flows en el Adapty Flow & Paywall Builder, es fundamental configurar los botones correctamente: 1. Añade un [botón en el 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 muestra cómo gestionar acciones personalizadas y preexistentes en tu código. :::warning **Las compras y restauraciones se gestionan automáticamente.** El resto de acciones de los botones, como cerrar flows o abrir enlaces, requieren una implementación correcta en el código de la app. ::: ## Configura el listener de eventos de flows \{#set-up-the-flows-events-listener\} Para gestionar las acciones de los flows, implementa la interfaz `IAdaptyFlowsEventsListener` y regístrala con `Adapty.SetFlowsEventsListener()`. Esto debe hacerse al inicio del ciclo de vida de tu app, normalmente en la escena principal o en la inicialización de la app. ```csharp showLineNumbers title="Unity" using AdaptySDK; public class FlowsListener : MonoBehaviour, IAdaptyFlowsEventsListener { void Start() { Adapty.SetFlowsEventsListener(this); } // implement all IAdaptyFlowsEventsListener methods here } ``` `IAdaptyFlowsEventsListener` es una interfaz de C#, así que implementa todos sus métodos — consulta [Gestionar eventos de flow y paywall](unity-handling-events) para ver la lista completa. Los ejemplos a continuación muestran únicamente el método `FlowViewDidPerformAction`. Todas las acciones de botones llegan al callback `FlowViewDidPerformAction(view, action)` como un objeto `AdaptyUIUserAction` con un `Type` de `Close`, `SystemBack`, `OpenUrl` o `Custom`, más un `Value` opcional (la URL o el ID de la acción personalizada). ## 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. :::info Una vista descartada se destruye y no puede volver a mostrarse. Para mostrar el flow de nuevo, llama otra vez a `CreateFlowView`. ::: ```csharp showLineNumbers title="Unity" public void FlowViewDidPerformAction( AdaptyUIFlowView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: view.Dismiss(null); break; default: // handle other events break; } } ``` ## Manejar el botón de retroceso del sistema Android \{#handle-the-android-system-back-button\} Pulsar el botón de retroceso del sistema Android (o usar el gesto de retroceso) emite una acción del tipo `SystemBack`. Por sí sola no cierra el flow — 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 de retroceso del sistema cierre el flow, gestiona la acción tú mismo: ```csharp showLineNumbers title="Unity" public void FlowViewDidPerformAction( AdaptyUIFlowView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: case AdaptyUIUserActionType.SystemBack: view.Dismiss(null); break; default: // handle other events break; } } ``` ## 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 manejador para la acción `OpenUrl` que abra la URL recibida. Use el método `AdaptyUI.OpenUrl` para abrir la URL de forma nativa, respetando la opción de navegador externo o interno (`action.OpenIn`) configurada en el builder: ```csharp showLineNumbers title="Unity" public void FlowViewDidPerformAction( AdaptyUIFlowView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.OpenUrl: var urlString = action.Value; if (!string.IsNullOrWhiteSpace(urlString)) { AdaptyUI.OpenUrl( urlString, action.OpenIn ?? AdaptyWebPresentation.ExternalBrowser, (error) => { // handle the error } ); } 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 builder, añade un botón y asígnale la acción **Custom** con ID `login`. 2. En el código de tu app, implementa un manejador para la acción personalizada `login` que identifique a tu usuario. ```csharp showLineNumbers title="Unity" public void FlowViewDidPerformAction( AdaptyUIFlowView 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 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 flow o paywall: ```csharp showLineNumbers title="Unity" public void FlowViewDidPerformAction( AdaptyUIFlowView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Custom: if (action.Value == "openNewFlow") { // Display another flow or paywall ShowAlternativeFlow(); } break; default: // handle other events break; } } private void ShowAlternativeFlow() { // Implement your logic to show an alternative flow } ``` </SDKv4> <SDKv3> Si estás creando paywalls con el Paywall Builder de Adapty, es fundamental configurar los botones correctamente: 1. En el antiguo Paywall Builder, añade un botón y asígnale una acción existente o 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 existentes en tu código. :::warning **Solo las compras y restauraciones se gestionan automáticamente.** El resto de acciones de los botones, 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 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 } ``` </SDKv3> --- # File: unity-handling-events --- --- title: "Gestionar eventos de flow y paywall - Unity" description: "Gestiona eventos de flow y paywall en tu aplicación Unity." --- <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 cómo gestionar las acciones del flow](unity-handle-paywall-actions) para más detalles. ::: Los flows y paywalls configurados en el [Flow & Paywall Builder](adapty-flow-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 producto, 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. ::: ## Gestión de eventos \{#handling-events\} Para controlar o monitorear los procesos que ocurren en la pantalla del flow dentro de tu aplicación móvil, implementa la interfaz `IAdaptyFlowsEventsListener` y regístrala con `Adapty.SetFlowsEventsListener()`: ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; public class FlowEventsHandler : MonoBehaviour, IAdaptyFlowsEventsListener { void Start() { Adapty.SetFlowsEventsListener(this); } // Implement all interface methods below } ``` :::note Estos métodos son donde añades tu lógica personalizada para responder a los eventos del flow. El SDK no aplica ningún comportamiento predeterminado: una compra exitosa o un error no cierra la vista automáticamente — llama a `view.Dismiss(...)` tú mismo cuando corresponda. ::: ### Eventos generados por el usuario #### Flow appeared Se invoca cuando la vista del flow 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 flow y se abre un web paywall en el navegador integrado. ::: ```csharp showLineNumbers title="Unity" public void FlowViewDidAppear(AdaptyUIFlowView view) { } ``` #### Flow disappeared Se invoca cuando la vista del flow se cierra 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 flow en un navegador in-app desaparece de la pantalla. ::: ```csharp showLineNumbers title="Unity" public void FlowViewDidDisappear(AdaptyUIFlowView view) { } ``` #### Selección de producto \{#product-selection\} Se invoca cuando se selecciona un producto para su compra (por el usuario o por el sistema). ```csharp showLineNumbers title="Unity" public void FlowViewDidSelectProduct( AdaptyUIFlowView 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 un usuario inicia el proceso de compra. ```csharp showLineNumbers title="Unity" public void FlowViewDidStartPurchase( AdaptyUIFlowView view, AdaptyPaywallProduct product ) { } ``` :::note En el [modo Observer](unity-present-flows-in-observer-mode), las compras iniciadas desde un flow se entregan a tu `IAdaptyUIObserverModeResolver` en su lugar. ::: <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 si la compra se realiza con éxito, el usuario la cancela o queda en estado pendiente. Las cancelaciones del usuario y los pagos pendientes (por ejemplo, cuando se requiere aprobación parental) activan este método, no `FlowViewDidFailPurchase`. El flow permanece abierto tras la compra hasta que lo cierres manualmente, así que llama a `view.Dismiss(...)` cuando el usuario obtenga acceso: ```csharp showLineNumbers title="Unity" public void FlowViewDidFinishPurchase( AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult ) { switch (purchasedResult.Type) { case AdaptyPurchaseResultType.Success: // Check if user has access to premium features if (purchasedResult.Profile != null && purchasedResult.Profile.AccessLevels.TryGetValue("premium", out var premium) && premium.IsActive) { view.Dismiss(null); } break; case AdaptyPurchaseResultType.Pending: // Handle pending purchase (e.g., user will pay offline with cash) break; case AdaptyPurchaseResultType.UserCancelled: // Handle user cancellation break; default: break; } } ``` <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 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, y los pagos pendientes no activan este método. ```csharp showLineNumbers title="Unity" public void FlowViewDidFailPurchase( AdaptyUIFlowView 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 FlowViewDidStartRestore(AdaptyUIFlowView view) { } ``` #### Restauración exitosa \{#successful-restore\} Se invoca cuando la restauración de compras se completa con éxito. El flow permanece abierto tras la restauración hasta que lo cierres: ```csharp showLineNumbers title="Unity" public void FlowViewDidFinishRestore( AdaptyUIFlowView view, AdaptyProfile profile ) { // Check if user has access to premium features if (profile.AccessLevels.TryGetValue("premium", out var premium) && premium.IsActive) { view.Dismiss(null); } } ``` <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 saber cómo comprobarlo. #### Fallo en la restauración \{#failed-restore\} Se invoca cuando la restauración de una compra falla: ```csharp showLineNumbers title="Unity" public void FlowViewDidFailRestore( AdaptyUIFlowView 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 [paywall web](web-paywall) para realizar una compra (tanto si tuvo éxito como si falló), se invocará este método: ```csharp showLineNumbers title="Unity" public void FlowViewDidFinishWebPaymentNavigation( AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyError error ) { } ``` **Parámetros:** - `product`: El producto para el que se abrió (o intentó abrir) el paywall web, o `null` - `error`: `null` si el paywall web 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": null, "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 FlowViewDidFailLoadingProducts( AdaptyUIFlowView 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 y en tiempo de ejecución \{#rendering-and-runtime-errors\} Si ocurre un error durante el renderizado de la interfaz, o cualquier otro error en tiempo de ejecución no relacionado con una compra, este método lo reportará. La vista no se cierra automáticamente — llama a `view.Dismiss(...)` tú mismo si lo deseas: ```csharp showLineNumbers title="Unity" public void FlowViewDidReceiveError( AdaptyUIFlowView view, AdaptyError error ) { } ``` <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 una situación normal, estos errores no deberían producirse, así que si te encuentras con alguno, háznos lo saber. #### Eventos de análisis \{#analytics-events\} ```csharp showLineNumbers title="Unity" public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IDictionary<string, object> @params ) { } ``` Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. Consulta [Rastrear vistas de pantallas de flow](unity-flow-screen-views) para saber qué hacer con ellos. Un flow también reporta los valores que los usuarios escriben y seleccionan. Consulta [Procesar datos de flows](unity-flow-input) para leerlos. ### Gestión de solicitudes del sistema \{#handle-system-requests\} `IAdaptyUISystemRequestsHandler` (registrado mediante `Adapty.SetSystemRequestsHandler(...)`) está reservado para solicitudes del sistema que provienen de un flow: solicitudes de permisos del SO (como notificaciones push o acceso a la cámara) y solicitudes de valoración de la app. Los flows todavía no generan estas solicitudes, por lo que no es necesario registrar un handler. ### Navegación \{#navigation\} #### Botón de retroceso del sistema Android \{#android-system-back-button\} El botón de retroceso del sistema Android (o el gesto de retroceso) se entrega a `FlowViewDidPerformAction` como una acción `SystemBack` y no cierra el flow por sí solo — el usuario abandona el flow a través de una ruta 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, gestiona la acción tú mismo: ```csharp showLineNumbers title="Unity" public void FlowViewDidPerformAction( AdaptyUIFlowView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: case AdaptyUIUserActionType.SystemBack: view.Dismiss(null); break; default: // handle other events break; } } ``` Consulta la [guía sobre cómo gestionar las acciones de flow](unity-handle-paywall-actions) para ver la lista completa de acciones. </SDKv4> <SDKv3> :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderización 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](unity-handle-paywall-actions) para más detalles. ::: Los paywalls configurados con [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 cubre únicamente **paywalls creados en el builder antiguo**, que requieren Adapty SDK 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 (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 un 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 y renderizado de datos \{#data-fetching-and-rendering\} #### Errores al cargar productos \{#product-loading-errors\} Se invoca cuando falla la carga de productos y proporciona `AdaptyError`. Si no pasaste el array de productos durante la inicialización, AdaptyUI recuperará los objetos necesarios del servidor por sí mismo. Esta operación puede fallar, y AdaptyUI notificará el error invocando este método: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailLoadingProducts( AdaptyUIPaywallView view, AdaptyError error ) { } ``` <Details> <summary>Ejemplo de evento (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 una situación normal, estos errores no deberían producirse, así que si te encuentras con uno, por favor, comunícanoslo. </SDKv3> --- # File: unity-flow-screen-views --- --- title: "Rastrear vistas de pantalla de flow en Unity SDK" description: "Recibe un evento cada vez que un usuario abre una pantalla de flow en tu app de Unity, y reenvíalo a tu analítica de producto." --- Cada vez que un usuario abre una pantalla en un [flow](adapty-flow-builder), el SDK notifica un evento `flow_screen_showed` a tu app. Reenvíalo a Amplitude, Mixpanel o cualquier analítica de producto que uses, para poder analizar las pantallas del flow junto con el resto de los eventos de tu app. :::tip Para ver cuántos usuarios llegan a cada pantalla y en cuál abandonan, no necesitas escribir ningún código: el [embudo del flow](flow-funnel) ya muestra eso en el Adapty Dashboard. Usa esta guía para construir el mismo embudo en tu propia herramienta de analítica, o para combinar las pantallas del flow con eventos que Adapty no registra. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty Unity SDK v4 o posterior**: El callback de análisis de flow no existe en versiones anteriores. - **Una versión de flow publicada después de que se lanzara esta función**: Adapty genera los eventos de pantalla al publicar un flow, por lo que un flow publicado antes de esa fecha no reportará nada. Si no llegan eventos a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Qué recibes \{#what-you-receive\} Un flow reporta `flow_screen_showed` cada vez que un usuario abre una de sus pantallas. Adapty contabiliza estos eventos en sus propios análisis de flows y también los envía a tu aplicación, para que puedas construir el mismo embudo en tus propias herramientas de análisis. | Parámetro | Descripción | |:-----------------|:---------------------------------------------------------------------------------------------------------------------------------| | `instanceId` | El ID de la pantalla que el usuario abrió. | | `screen_order` | La posición de la pantalla en el flow. | | `is_last_screen` | `true` cuando la pantalla no tiene a dónde seguir. Un flow con ramificaciones puede terminar en varias pantallas distintas, y cada una reporta `true`. | Tanto `isBackendEvent` como `isCustomerEvent` son `true` en este evento: Adapty sigue contabilizándolo y tu aplicación también lo recibe. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: Las vistas de pantalla llegan al mismo callback de análisis que cualquier otro evento de flow. Consulta [Gestionar eventos de flow y paywall](unity-handling-events) para saber dónde registrarlo. ## Reenvía cada vista de pantalla a tu analítica \{#forward-every-screen-view-to-your-analytics\} Comprueba el nombre del evento antes de leer nada más, porque un flow reporta otros eventos a través del mismo callback. ```csharp showLineNumbers title="Unity" public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IDictionary<string, object> @params ) { if (name != "flow_screen_showed") return; var screenId = @params["instanceId"]; var screenOrder = @params["screen_order"]; // Send screenId and screenOrder to your analytics provider here. } ``` ## Ejecutar código cuando el usuario llega al final \{#run-code-when-a-user-reaches-the-end\} `is_last_screen` es `true` en una pantalla a partir de la cual el flow no puede avanzar. Úsalo para desbloquear contenido, marcar el onboarding como completado o enviar tu propio evento de finalización, sin necesidad de añadir una acción de botón en el builder. ```csharp showLineNumbers title="Unity" public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IDictionary<string, object> @params ) { if (name != "flow_screen_showed") return; if (!(@params["is_last_screen"] is bool isLast) || !isLast) return; // The user reached the end of the flow. } ``` Llegar a la última pantalla no es lo mismo que cerrar el flow. Un usuario puede ver la pantalla final y descartar el flow sin realizar ninguna acción, y un flow con ramificaciones puede terminar en varias pantallas distintas. Para saber que el flow en sí ha desaparecido, usa `FlowViewDidDisappear` en su lugar. --- # File: unity-flow-input --- --- title: "Procesar datos de flows en Unity SDK" description: "Guarda y usa los datos que los usuarios introducen en flows en tu app de Unity con Adapty SDK." --- Cuando un usuario escribe en un campo de texto, responde un cuestionario o activa un interruptor en un [flow](adapty-flow-builder), el SDK pasa el valor a tu app a través de su callback de analytics. Las apps suelen usar esos datos para: - **Registrar usuarios en tu propio backend**: Toma el correo electrónico y el nombre que el usuario introdujo en tu onboarding flow, y crea su cuenta cuando el flow se cierre. - **Guardar respuestas y preferencias**: Registra lo que eligió el usuario para que tu app pueda utilizarlo más adelante — por ejemplo, [escríbelo en su perfil de Adapty](unity-setting-user-attributes) como atributos personalizados. - **Personalizar flows futuros**: Guarda las respuestas del cuestionario como atributos personalizados y luego apunta a un placement posterior para que cada segmento vea un flow diferente, o un paywall distinto dentro de él. - **Mostrar valores calculados a partir de las respuestas**: Por ejemplo, calcula el IMC a partir del peso y la altura del usuario, y [pásalo a una etiqueta personalizada en el siguiente flow](unity-flow-custom-tags#example-show-a-bmi-from-onboarding-answers). - **Enviar datos a plataformas de analíticas de terceros**: Reenvía las respuestas a Amplitude, Mixpanel o cualquier otra herramienta de analíticas de producto que uses. Los inputs y los grupos de selección reportan sus valores automáticamente. Para distinguirlos en tu código, asigna a cada input un **Element ID** significativo y a cada grupo de selección un **Group ID** en el builder. :::note Por defecto, estos IDs se generan automáticamente, por lo que tu app recibe `scr_oAPBHPa7` en lugar de `welcome`. Si tu analítica depende de leerlos, defínelos tú mismo: asigna nombres a los inputs y grupos seleccionables en el Flow Builder, y renombra las pantallas con la skill del generador de flows. Consulta [Nombra los IDs que ve tu analítica](flow-analytics-ids). ::: ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty SDK v4 o posterior**: Los callbacks de flow no existen en versiones anteriores. - **Un flow creado en el [Flow & Paywall Builder](adapty-flow-builder)**: Solo los flows reportan valores de entrada a través de este callback. - **Una versión de flow publicada recientemente**: Un flow solo reporta valores de entrada si lo publicaste después de que esta función estuviera disponible. Si no llega nada a tu app, publica una nueva versión del flow e inténtalo de nuevo. ## Recibir valores de entrada \{#receive-input-values\} Los valores de entrada llegan al mismo callback que cualquier otro evento de analíticas de un flow, bajo el nombre de evento `flow_user_input`. Implementa `FlowViewDidReceiveAnalyticEvent` en el listener que registras con `Adapty.SetFlowsEventsListener`: ```csharp showLineNumbers title="Unity" public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IReadOnlyDictionary<string, object> parameters ) { HandleFlowInput(name, parameters); } ``` El callback `FlowViewDidReceiveAnalyticEvent` entrega todos los eventos de análisis de un flow, incluidas las [vistas de pantalla](unity-flow-screen-views). Para filtrar los eventos de entrada del usuario, compara `name` con `flow_user_input`. El parámetro que almacena el valor depende de `element_type`: | `element_type` | Parámetro que almacena el valor | Tipo C# | |:---------------|:--------------------------------|:--------| | `text_input`, `email_input`, `number_input`, `phone_input` | `value` | `string` | | `date_picker`, `time_picker`, `date_time_picker` | `value` | `long`, tiempo Unix en milisegundos. | | `single_choice`, `multi_choice` | `item_ids`, `item_titles` | `JArray` | | `toggle` | `value` | `bool` | La sección [Qué recibes](#what-you-receive) enumera todos los parámetros y lo que reporta cada elemento del builder. Para confirmar que el callback se ejecuta, interactúa con el input en una versión de prueba de tu propia app. Si tu callback handler no recibe ningún evento, revisa los [prerequisitos](#before-you-start). Asegúrate de que el flow fue publicado después de que esta función estuviera disponible. ## Cuando tu app recibe el input \{#when-your-app-receives-the-input\} :::important El valor predeterminado de entrada o selección nunca llega a tu aplicación a través de este callback. Si un usuario acepta la opción marcada como **Set as default** y continúa, no se dispara ningún evento. No interpretes la ausencia del evento como "sin respuesta": el usuario simplemente dejó el valor predeterminado tal como estaba. ::: **Los siguientes elementos disparan este evento:** - Campos de texto, email, número y teléfono - Selectores de fecha, hora y fecha-hora - Grupos seleccionables de opción única y múltiple, y toggles **Los siguientes no lo hacen:** - Campos de contraseña, selecciones de productos y cambios de pestaña - Inputs dentro de un elemento Header, que se comparte entre pantallas - Grupos seleccionables con IDs de opción duplicados o faltantes, o con un ID de grupo reutilizado en otra pantalla. Ese tipo de grupo no envía nada en absoluto, en lugar de enviar parte de la respuesta. **El evento se dispara cuando:** - Un campo pierde el foco. Un campo vaciado reporta una cadena vacía; un campo que el usuario nunca editó no reporta nada. Un marcador de posición no es un valor. Si el usuario vuelve al campo, lo edita y lo abandona de nuevo, se genera un segundo evento. - El usuario cierra un selector tras elegir un nuevo valor. Cerrarlo sin cambios no reporta nada. - El usuario pulsa una opción o un toggle. Un evento de opción múltiple lista todas las opciones seleccionadas, por lo que deseleccionar la última envía dos arrays vacíos. **El evento no se dispara cuando:** - El usuario escribe. No hay flujo de pulsaciones de teclas, solo el valor que tiene el campo cuando pierde el foco. - El usuario envía o cierra el flow. Un valor que aún se está editando en ese momento puede perderse; la sección de [limitaciones de entrega](#delivery-and-limitations) explica cómo diseñar la última pantalla teniendo esto en cuenta. - Se establece un valor sin interacción del usuario. Una opción marcada como **Set as default** se preselecciona al abrirse la pantalla, y una acción **Set Variable** puede seleccionar una opción o rellenar un input a partir de otra interacción. Ninguna de las dos envía un evento; un input prerrellenado solo se reporta una vez que el usuario lo edita. ## Qué recibes \{#what-you-receive\} El callback entrega dos eventos. Filtra por `name` para mostrar únicamente los eventos `flow_user_input`. El payload JSON de respuesta tiene este aspecto: ```json { "name": "flow_user_input", "instanceId": "scr_registration", "isBackendEvent": false, "isCustomerEvent": true, "element_id": "email", "element_type": "email_input", "value": "jane@example.com" } ``` | Parámetro | Descripción | |:------------------|:------------| | `name` | `flow_user_input` para eventos de entrada, `flow_screen_showed` para vistas de pantalla. | | `instanceId` | El ID de la pantalla del input. Los IDs de elemento son únicos dentro de una pantalla, no en todo el flow. Si tu flow tiene inputs en más de una pantalla, combina `instanceId` con `element_id` al filtrar eventos. | | `element_id` | El **Element ID** del [input](builder-inputs-and-forms), o el **Group ID** del [grupo seleccionable](flow-selectable-elements). | | `element_type` | El tipo de elemento que envió el evento. Determina cuál de los parámetros siguientes contiene el valor del input. | | `value` | **Solo campos de texto, selectores y toggles.** El valor del input: una cadena para campos de texto, un entero para selectores, un booleano para toggles. | | `item_ids` | **Solo grupos seleccionables de elección única y múltiple.** Los **Element IDs** de las opciones seleccionadas, en el orden en que aparecen en el builder. Una entrada para un grupo de elección única; cualquier número para un grupo de elección múltiple. | | `item_titles` | **Solo grupos seleccionables de elección única y múltiple.** Los títulos de las opciones listadas en `item_ids`, en el mismo orden. Nunca está vacío: una opción sin título informa su ID en su lugar. | | `isCustomerEvent` | Indicador de utilidad, siempre `true` para este evento. Marca los eventos que el SDK entrega a tu callback. Resulta útil si un mismo handler reenvía todos los eventos del flow a tu sistema de análisis y filtras por este indicador en lugar de por `name`. | | `isBackendEvent` | Indicador de utilidad, siempre `false` para este evento. Marca los eventos que Adapty también registra para su propio análisis. `false` confirma que lo que los usuarios introducen llega a tu app y solo a ella — Adapty no lo recibe ni lo almacena. | Lo que reporta cada elemento: | En el builder | `element_type` | Parámetro que almacena el valor | Qué contiene | |:--------------|:---------------|:--------------------------------|:-------------| | Input de **Texto**, **Número**, **Número de teléfono** | `text_input`, `number_input`, `phone_input` | `value` | La cadena literal que escribió el usuario. Los números llegan como cadenas, no como tipos numéricos. | | Input de **E-mail** | `email_input` | `value` | La cadena literal que escribió el usuario, aunque no haya superado la validación de formato del builder. Valídala en tu lado antes de usarla. | | Input de **Contraseña** | ninguno | ninguno | No envía ningún evento. | | Input de **Fecha** | `date_picker` | `value` | Tiempo Unix en milisegundos, como entero, a medianoche local de la fecha seleccionada. | | Input de **Hora** | `time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Input de **Fecha y hora** | `date_picker` y `time_picker` | `value` | Dos elementos, un selector de fecha y un selector de hora. Cada uno envía su propio evento. | | Input cambiado a **Fecha y hora** en el desplegable **Type** | `date_time_picker` | `value` | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. | | Grupo de **elección única** | `single_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: un array con el Element ID de la opción seleccionada. `item_titles`: un array con el título de esa opción. | | Grupo de **elección múltiple** | `multi_choice` | `item_ids`, `item_titles` | Dos arrays. `item_ids`: los Element IDs de todas las opciones seleccionadas, en el orden en que aparecen en el builder. `item_titles`: sus títulos, en el mismo orden. Ambos arrays están vacíos cuando no hay nada seleccionado. | | Grupo de **Toggle** | `toggle` | `value` | Un booleano. | Para bifurcar según una respuesta, compara `item_ids`, no `item_titles`. El título es derivado: el **Element Title** de la opción si lo definiste, si no su texto en tu idioma predeterminado, y si no su Element ID. Un usuario que vio el flow en otro idioma vio un texto diferente. ## Ejemplos de eventos \{#event-examples\} Estos ejemplos muestran las propiedades disponibles en cada evento, con valores ilustrativos en los comentarios. <Details> <summary>Entrada de texto, email, número y teléfono (haz clic para expandir)</summary> ```csharp public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IReadOnlyDictionary<string, object> parameters ) { var eventName = name; // "flow_user_input" var paramName = parameters["name"]; // "flow_user_input" var screenId = parameters["instanceId"]; // "scr_J260KU5q" var isCustomerEvent = parameters["isCustomerEvent"]; // true var isBackendEvent = parameters["isBackendEvent"]; // false var elementId = parameters["element_id"]; // "email" var elementType = parameters["element_type"]; // "email_input" var value = parameters["value"]; // "jane@example.com" (string) } ``` </Details> <Details> <summary>Selectores de fecha, hora y fecha-hora (Click to expand)</summary> ```csharp public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IReadOnlyDictionary<string, object> parameters ) { var elementId = parameters["element_id"]; // "birthday" var elementType = parameters["element_type"]; // "date_picker" var value = parameters["value"]; // 645408000000 (long — Unix milliseconds, 1990-06-15, local midnight) } ``` </Details> <Details> <summary>Selección única (Click to expand)</summary> ```csharp public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IReadOnlyDictionary<string, object> parameters ) { var elementId = parameters["element_id"]; // "experience" var elementType = parameters["element_type"]; // "single_choice" var itemIds = parameters["item_ids"]; // ["pro"] (JArray) var itemTitles = parameters["item_titles"]; // ["I train professionally"] } ``` </Details> <Details> <summary>Selección múltiple (Haz clic para expandir)</summary> ```csharp public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IReadOnlyDictionary<string, object> parameters ) { var elementId = parameters["element_id"]; // "interests" var elementType = parameters["element_type"]; // "multi_choice" var itemIds = parameters["item_ids"]; // ["sports", "music"] (JArray) var itemTitles = parameters["item_titles"]; // ["Sports", "Music"] } ``` </Details> <Details> <summary>Toggle (Click to expand)</summary> ```csharp public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IReadOnlyDictionary<string, object> parameters ) { var elementId = parameters["element_id"]; // "reminders" var elementType = parameters["element_type"]; // "toggle" var value = parameters["value"]; // true (bool) } ``` </Details> ## Entrega y limitaciones \{#delivery-and-limitations\} :::warning Los flows envían valores sin procesar: direcciones de correo electrónico, números de teléfono y cualquier otra cosa que el usuario escriba. Trata todo lo que entregue el callback de analíticas como datos personales y no los almacenes en ningún lugar donde no guardarías el correo electrónico de un usuario. ::: - **El último valor prevalece**: recibes un evento por campo con el valor con el que el usuario se quedó, no una secuencia tecla a tecla. Si edita un campo, solo llega la última versión. - **Sin garantía de envío**: los valores te llegan a medida que el usuario avanza por el flow, y puede salir en cualquier momento. Espera a que el flow se cierre antes de considerar un conjunto de respuestas como completo. - **Entrega de mejor esfuerzo**: si un usuario cierra el flow o minimiza la app mientras un campo tiene el foco o un selector está abierto, ese valor puede perderse. Para que el valor del último campo sea fiable, termina el flow con una pantalla que no tenga entradas ni selectores, y cierra el flow mediante una acción explícita del usuario en lugar de hacerlo automáticamente al aparecer esa pantalla. Pasar a la pantalla final quita el foco del campo anterior, que es lo que envía su valor. Guarda cada valor de entrada a medida que tu listener lo recibe y envía el conjunto completo cuando el flow se cierra. Para capturar ese momento, implementa `FlowViewDidDisappear` en el mismo listener. Se ejecuta cuando la vista del flow se cierra, tanto si el usuario completó el flow como si lo cerró a medias. ## Casos de uso \{#use-cases\} ### Registra usuarios en tu backend \{#register-users-on-your-backend\} Recoge los valores a medida que llegan y envíalos una vez que el flow se cierre, para que una sola petición lleve un conjunto completo de respuestas. La vista desaparece tanto si el usuario completó el flow como si lo abandonó a medias. Comprueba que tienes los campos necesarios antes de llamar a tu backend. El flow no puede mostrar errores de tu backend. El callback no devuelve ningún valor, y el SDK no dispone de ningún método para enviar datos a un flow en ejecución. Si el registro falla, por ejemplo porque el email ya está en uso, muestra el error en tu propia UI una vez que el flow se haya cerrado. ```csharp showLineNumbers title="Unity" private readonly Dictionary<string, string> flowAnswers = new Dictionary<string, string>(); public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IReadOnlyDictionary<string, object> parameters ) { if (name != "flow_user_input") return; if (!parameters.TryGetValue("value", out var raw) || !(raw is string value)) return; var elementId = parameters["element_id"] as string; flowAnswers[elementId] = value; } public void FlowViewDidDisappear(AdaptyUIFlowView view) { if (!flowAnswers.ContainsKey("email")) return; // Send flowAnswers to your backend here to create the account. flowAnswers.Clear(); } ``` ### Enriquece los perfiles de usuario con datos \{#enrich-user-profiles-with-data\} Para vincular lo que el usuario ha introducido a su perfil y evitar pedirle los mismos datos dos veces, [actualiza el perfil de usuario](unity-setting-user-attributes) a medida que vayan llegando los valores. Por ejemplo, si tu flow tiene un campo de texto con el ID de elemento `name` y un campo de email con el ID de elemento `email`: ```csharp showLineNumbers title="Unity" private void HandleFlowInput(string name, IReadOnlyDictionary<string, object> parameters) { if (name != "flow_user_input") return; if (!parameters.TryGetValue("value", out var raw) || !(raw is string value)) return; var builder = new AdaptyProfileParameters.Builder(); switch (parameters["element_id"] as string) { case "name": builder = builder.SetFirstName(value); break; case "email": builder = builder.SetEmail(value); break; default: return; } Adapty.UpdateProfile(builder.Build(), (error) => { if (error != null) { // handle the error } }); } ``` ### Personalizar flows mostrados más adelante \{#customize-flows-shown-later\} Las respuestas del cuestionario también pueden determinar lo que un usuario ve en un [placement](placements) posterior: un flow diferente o un paywall distinto dentro de él. Por ejemplo, pregunta a los usuarios sobre su experiencia con el deporte en tu onboarding flow y luego muestra a cada grupo su propio flow con diferentes productos y textos. 1. Añade un [cuestionario](onboarding-quizzes) a tu flow. Asigna al [grupo seleccionable](flow-selectable-elements) el Group ID `experience`, y a cada opción un Element ID significativo. 2. Gestiona las respuestas y [establece atributos personalizados](unity-setting-user-attributes) para el usuario. ```csharp showLineNumbers title="Unity" private void HandleFlowInput(string name, IReadOnlyDictionary<string, object> parameters) { if (name != "flow_user_input") return; if ((parameters["element_id"] as string) != "experience") return; var optionId = (parameters["item_ids"] as JArray)?.First?.ToString(); if (optionId == null) return; var builder = new AdaptyProfileParameters.Builder(); // Set the custom attribute 'experience' to the option the user selected // (beginner, amateur, or pro). builder = builder.SetCustomStringAttribute("experience", optionId); Adapty.UpdateProfile(builder.Build(), (error) => { if (error != null) { // handle the error } }); } ``` 3. [Crea un segmento](segments) para cada valor de atributo personalizado. 4. Crea un [placement](placements) y añade una [audiencia](audience) para cada segmento. 5. [Muestra el flow](unity-present-paywalls) de ese placement en tu app. --- # File: unity-flow-custom-tags --- --- title: "Pasar datos a flows en Unity SDK" description: "Pasa un valor desde tu app Unity a un flow y muéstralo en un elemento de texto." --- Un [custom tag](flow-custom-tags) es una variable personalizada con un valor proveniente de tu app. Después de seleccionar **Value comes from outside the app** en la configuración de una variable, tu app puede establecer su valor a través del SDK de Adapty. Por ejemplo, un elemento de texto contiene "Coins:" y un custom tag. Si tu app pasa "120" para el tag, los usuarios ven "Coins: 120". ## Antes de comenzar \{#before-you-start\} Necesitas el SDK de Adapty v4 o posterior. Las versiones anteriores a v4 no muestran flows. ## Añadir etiquetas personalizadas al flow \{#add-custom-tags-to-the-flow\} Para mostrar los valores de tu app en un flow, configura etiquetas personalizadas en el builder: 1. [Crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) para cada valor que tu app envíe. <ZoomImage id="flow-custom-tag-create.webp" width="500px" alt="Formulario para crear una variable con nombre, descripción, tipo de valor String y valor inicial" /> 2. [Inserta cada etiqueta en un elemento de texto](flow-custom-tags#add-a-custom-tag-to-a-text-element). 3. Anota el nombre completo de cada etiqueta, incluido su grupo. El código de tu app pasa cada valor con ese nombre. Por ejemplo, si el grupo `user` contiene una etiqueta `streak`, el nombre completo de la etiqueta es `user.streak`. <ZoomImage id="flow-custom-tag-full-name.webp" width="500px" alt="Pestaña personalizada del desplegable Añadir variable con la etiqueta streak en el grupo user" /> ## Pasar valores de etiquetas personalizadas \{#pass-custom-tag-values\} Pasa los valores de tu app al método `SetCustomTags` de un objeto `AdaptyUICreateFlowViewParameters`, como un diccionario de cadenas, por ejemplo `Dictionary<string, string>`, y llama a `AdaptyUI.CreateFlowView` con ese objeto: - Cada clave es el nombre completo de una etiqueta personalizada, como `user.streak`. - Cada valor es la cadena que los usuarios ven en lugar de la etiqueta. - Cada valor debe ser legible por personas, como "1.250", no "1250.0". Adapty no lo formatea ni lo traduce. El SDK lee los valores una sola vez, cuando crea la vista, por lo que no cambian mientras el flow está abierto. ```csharp showLineNumbers title="Unity" var parameters = new AdaptyUICreateFlowViewParameters() .SetCustomTags(new Dictionary<string, string> { { "username", "DragonSlayer42" }, { "coins", "1,250" } }); AdaptyUI.CreateFlowView(flow, parameters, (view, error) => { if (error != null) { // handle the error return; } // present the view }); ``` Para probar tus etiquetas personalizadas, abre el flow en tu app después de añadir el código que establece sus valores. El canvas del builder y la [vista previa en dispositivos](paywall-device-compatibility-preview#preview-on-devices) solo muestran los valores iniciales: la app móvil de Adapty no establece los valores de las etiquetas personalizadas. Si tu app ya establece valores de etiquetas personalizadas para paywalls del antiguo Paywall Builder, consulta [cómo difieren las etiquetas personalizadas en los flows](migrate-to-flows#custom-tags-from-the-old-paywall-builder). ## Lo que ven los usuarios sin un valor \{#what-users-see-without-a-value\} Los usuarios ven el **Valor inicial** de una etiqueta personalizada como valor de respaldo en los siguientes casos: - Tu app no pasa ningún valor para la etiqueta. - El usuario no ha actualizado tu app a una versión que envíe el valor. - El nombre en el código de tu app no coincide con el nombre completo de la etiqueta, incluidas las mayúsculas y minúsculas. - Otra etiqueta personalizada en el mismo elemento de texto no tiene valor. En ese caso, todas las etiquetas personalizadas de ese elemento de texto muestran su valor inicial. Para no mostrar nada en lugar de una etiqueta, pasa una cadena vacía. Adapty trata una cadena vacía como un valor, por lo que el valor de respaldo no aparece. ## Ejemplo: mostrar el IMC a partir de las respuestas del onboarding \{#example-show-a-bmi-from-onboarding-answers\} Un flow puede recoger respuestas de los usuarios, y una etiqueta personalizada puede mostrar un valor de tu app. Combina ambas cosas para mostrar a los usuarios un resultado que tu app calcula a partir de sus respuestas, como una puntuación, un plan recomendado o una métrica de salud: 1. Un flow recoge las respuestas y las envía a tu app. 2. Tu app calcula el resultado. 3. Tu app pasa el resultado al siguiente flow como valor de etiqueta personalizada. En este ejemplo, un flow de onboarding recoge el peso y la altura del usuario. Cuando el usuario cierra el flow de onboarding tras su última pantalla, tu app calcula su índice de masa corporal (IMC). Luego abre un flow de paywall que muestra el IMC. ### Configura los flows \{#set-up-the-flows\} 1. [Crea dos flows](paywall-builder-templates): un flow de onboarding que recoja el peso y la altura del usuario, y un flow de paywall que muestre su IMC. 2. Añade dos campos de tipo [**Number**](builder-inputs-and-forms) al flow de onboarding: - Uno para el peso en kilogramos, con el **Element ID** `weight`. - Uno para la altura en centímetros, con el **Element ID** `height`. En ambos campos, establece el **Format** como **Integer**. Un campo envía su valor como texto, exactamente tal como lo escribe el usuario, y el [código de ejemplo](#pass-the-bmi-to-the-paywall) no interpreta "70.5" y "70,5" de la misma manera. Los números enteros no tienen separador decimal, por lo que el código los lee igual en cualquier idioma. 3. Para asegurarte de que tu app recibe ambos valores, termina el flow de onboarding con una pantalla que no tenga campos. En esa pantalla, añade un botón con la acción [**Close flow**](onboarding-actions#close-flow). Los campos envían sus valores cuando pierden el foco o cuando el usuario pasa a otra pantalla. La acción Close flow no los envía. El código de ejemplo solo abre el paywall si el usuario llegó a la última pantalla. 4. En el flow de paywall, [crea una etiqueta personalizada](flow-custom-tags#create-a-custom-tag) llamada `bmi`. En el campo **Initial value**, introduce un guion (`—`). Los usuarios verán el guion si tu app no pasa ningún IMC. 5. Añade un elemento de texto con las palabras "Your BMI:" al flow de paywall. Después de esas palabras, [inserta la etiqueta personalizada `bmi`](flow-custom-tags#add-a-custom-tag-to-a-text-element). 6. Añade el flow de onboarding a un [placement](placements), por ejemplo `onboarding`. Añade el flow de paywall a un placement con el ID `bmi_paywall`. El código de [Pass the BMI to the paywall](#pass-the-bmi-to-the-paywall) obtiene el paywall usando este ID. ### Pasar el IMC al paywall Recoge el peso y la altura de los eventos `flow_user_input` del flow de onboarding, como se explica en [Procesar datos de flows](unity-flow-input). Para saber que el usuario llegó a la última pantalla, observa el evento `flow_screen_showed` con `is_last_screen` a `true`, como en [Rastrear vistas de pantallas del flow](unity-flow-screen-views). Cuando el usuario cierre el flow de onboarding tras esa pantalla, calcula el IMC. Luego crea la vista del paywall y pasa el IMC para la etiqueta personalizada `bmi`. Implementa los siguientes métodos en el listener que registras con `Adapty.SetFlowsEventsListener`. `FlowViewDidDisappear` solo se ejecuta después de que tu app cierra el flow de onboarding, por lo que el listener también debe gestionar la acción **Close flow**, como en [Cerrar flows y paywalls](unity-handle-paywall-actions#close-flows-and-paywalls): ```csharp showLineNumbers title="Unity" private readonly Dictionary<string, string> bodyAnswers = new Dictionary<string, string>(); private bool reachedLastScreen; public void FlowViewDidReceiveAnalyticEvent( AdaptyUIFlowView view, string name, IReadOnlyDictionary<string, object> parameters ) { if (name == "flow_screen_showed") { if (parameters.TryGetValue("is_last_screen", out var lastScreen) && lastScreen is bool isLast && isLast) { reachedLastScreen = true; } return; } if (name != "flow_user_input") return; if (!parameters.TryGetValue("value", out var raw) || !(raw is string value)) return; if (!parameters.TryGetValue("element_id", out var idRaw) || !(idRaw is string elementId)) return; bodyAnswers[elementId] = value; } public void FlowViewDidDisappear(AdaptyUIFlowView view) { // The listener receives events from every flow. Reset `bodyAnswers` and // `reachedLastScreen` whenever a flow disappears, so that the paywall // doesn't open again when the user closes it. var answers = new Dictionary<string, string>(bodyAnswers); var finished = reachedLastScreen; bodyAnswers.Clear(); reachedLastScreen = false; // Number inputs send their values as strings. if (!finished) return; if (!answers.TryGetValue("weight", out var weightText) || !double.TryParse(weightText, out var weight)) return; if (!answers.TryGetValue("height", out var heightText) || !double.TryParse(heightText, out var height) || height <= 0) return; var heightInMeters = height / 100; var bmi = weight / (heightInMeters * heightInMeters); ShowBmiPaywall(bmi.ToString("0.0")); } private void ShowBmiPaywall(string bmi) { Adapty.GetFlow("bmi_paywall", (flow, flowError) => { if (flowError != null) { // handle the error return; } var parameters = new AdaptyUICreateFlowViewParameters() .SetCustomTags(new Dictionary<string, string> { { "bmi", bmi } }); AdaptyUI.CreateFlowView(flow, parameters, (view, viewError) => { if (viewError != null) { // handle the error return; } view.Present((presentError) => { if (presentError != null) { // handle the error } }); }); }); } ``` `ToString("0.0")` usa el separador decimal de la cultura actual: "22.7" en inglés, "22,7" en alemán. ### Guardar el IMC en el perfil del usuario \{#save-the-bmi-to-the-users-profile\} Para mostrar distintos flows a los usuarios según su IMC, guárdalo como un [atributo personalizado](unity-setting-user-attributes#custom-user-attributes) de su perfil. En el Adapty Dashboard, [crea un atributo personalizado de tipo **Number**](segments#custom-attributes) con la clave `bmi`. Luego muestra cada flow a una [audiencia](audience) creada a partir de ese atributo. Llama al método `Adapty.UpdateProfile` en `FlowViewDidDisappear`, justo después de la línea que calcula `bmi`. Pasa ese `double`, no la cadena formateada, para que un segmento pueda comparar el IMC con un umbral: ```csharp showLineNumbers title="Unity" var parameters = new AdaptyProfileParameters.Builder() .SetCustomDoubleAttribute("bmi", bmi) .Build(); Adapty.UpdateProfile(parameters, (error) => { if (error != null) { // handle the error } }); ``` --- # 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, AdaptyWebPresentation.ExternalBrowser, (error) => { if (error != null) { Debug.LogError($"Failed to open web paywall: {error.Message}"); } else { Debug.Log("Web paywall opened successfully"); } } ); ``` :::note Existen 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. Úsalo cuando los productos de tu paywall en Adapty difieran de los del paywall web. En SDK v4, el argumento `paywall` recibe un `AdaptyFlowPaywall`, que es una de las variantes del paywall en `flow.Paywalls`. Consulta la [guía de migración](migration-to-unity-sdk-v4). ::: #### 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 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 flow, paywall u 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 }); ``` :::important `SetFallback` debe ejecutarse antes de que el SDK obtenga el flow, paywall u onboarding de destino. ::: 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 el SDK de Unity" description: "Aprende cómo localizar paywalls en tu app de Unity 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 o un onboarding, 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 en 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\} En SDK v4, los flows y los onboardings coinciden con los códigos de idioma de forma diferente: los flows se localizan mediante el SDK en el dispositivo, los onboardings mediante el servidor de Adapty. ### Flows y paywalls Un paywall que Adapty renderiza se entrega como un flow en el SDK v4, por lo que la regla que se describe a continuación aplica a ambos. La coincidencia es exacta. El SDK compara el código que pasas con los códigos de localización del flow carácter por carácter: no cambia las mayúsculas, no reemplaza los guiones bajos (`_`) por guiones (`-`) y no recurre a la etiqueta de idioma principal. Para un flow con una localización `pt-br`, solo `pt-br` coincide: `pt-BR`, `pt_BR` y `pt-PT` no lo hacen. Cuando el código no coincide con ninguna localización, el flow se renderiza silenciosamente en su [configuración regional predeterminada](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) — el SDK no devuelve un error ni registra una advertencia. Cuando el código coincide, Adapty fusiona la localización con la predeterminada: las cadenas y los recursos que no estén definidos en la localización coincidente se obtienen de la localización predeterminada. Omitir el código de idioma no es lo mismo que pedir la localización predeterminada del flow: el SDK sustituye un `en` fijo. Un flow cuyo idioma predeterminado sea `de` seguirá mostrándose en `en` si tiene una localización `en`, y solo recurrirá a `de` cuando no la tenga. :::warning Pasa el código de idioma exactamente como está configurado en el dashboard — subetiquetas en minúsculas separadas por guiones. No pases un identificador de idioma del sistema tal cual: `CultureInfo.CurrentCulture.Name` devuelve `pt-BR`, y recurrirá a la localización predeterminada. Convierte el valor en tu aplicación antes de pasarlo. ::: ### Onboardings Los onboardings se localizan en el servidor, y las reglas del servidor toleran otros formatos. Cuando pasas un `Locale` a [`GetOnboarding`](unity-get-onboardings): 1. La cadena de configuración regional 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 configuración regional que coincida exactamente 3. Si no se encuentra ninguna coincidencia, Adapty toma la subcadena anterior al primer guión (`pt` para `pt-br`) y busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, Adapty devuelve el contenido para la configuración regional predeterminada del onboarding Esta forma, `pt_BR`, `pt-BR` y `pt-br` resuelven a la misma localización de onboarding. ## Implementación de localizaciones \{#implementing-localizations\} En SDK v4, no se pasa un código de idioma al obtener un flow; el flow se localiza cuando se crea su vista. - **Flows y paywalls renderizados por Adapty**: el SDK no lee el idioma del dispositivo, así que resuélvelo en tu app y [pásalo al crear la vista](#choose-the-localization-of-a-flow). El código de idioma es opcional — si lo omites, el flow se renderiza en `en`, o en su idioma predeterminado cuando el flow no tiene localización `en`. - **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 `Dictionary` de valores. Selecciona la entrada que corresponda al usuario, con tu propio mecanismo de respaldo: ```csharp showLineNumbers using System.Linq; using AdaptySDK; Adapty.GetFlow("YOUR_PLACEMENT_ID", (flow, error) => { if (error != null) { // handle the error return; } var config = flow.RemoteConfigs.FirstOrDefault(c => c.Locale == "en") ?? flow.RemoteConfigs.FirstOrDefault(); // read your values from config?.Dictionary }); ``` Adapty almacena los códigos `Locale` en el formato descrito en [Estándar de código de idioma en Adapty](#locale-code-standard-at-adapty). El SDK no compara los Remote Configs con un idioma automáticamente, así que es tu app quien decide qué entrada aplicar. ### Elige la localización de un flow \{#choose-the-localization-of-a-flow\} Para renderizar un flow o paywall con una localización específica, pasa el código de idioma a `SetLocale` al crear la vista: ```csharp showLineNumbers var parameters = new AdaptyUICreateFlowViewParameters() .SetLocale("pt-br"); AdaptyUI.CreateFlowView(flow, parameters, (view, error) => { if (error != null) { // handle the error return; } // view.Locale — the localization the view was built with }); ``` La vista informa de la localización con la que se ha construido en `view.Locale`: la que solicitaste si esa localización existe, o la localización predeterminada del flow en caso contrario. </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 porque estos códigos son complejos, 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 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ó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 recibida se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. A continuación, 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 anterior al primer guión (`pt` para `pt-br`) y se busca la localización correspondiente 4. Si tampoco se encuentra ninguna coincidencia, se devuelve el contenido para el idioma predeterminado del paywall 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. Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: unity-present-flows-in-observer-mode --- --- title: "Presentar flows en modo Observer - Unity" description: "Presenta flows en modo Observer en tu app de Unity mientras gestionas las compras con tu propio código." --- Si utilizas el [modo Observer](observer-vs-full-mode), eres responsable de tu propia lógica de compras. Pero puedes delegar la interfaz del paywall al [Flow & Paywall Builder](adapty-flow-builder). Diseña la pantalla en el builder, y el SDK de Adapty la renderiza de forma nativa. Actualiza el texto, el diseño y los precios cuando quieras, sin necesidad de publicar una nueva versión de la app. Esta guía muestra cómo presentar un flow y gestionar los callbacks de compra. Si Adapty gestiona las compras por ti, consulta [Mostrar flows y paywalls](unity-present-paywalls) en su lugar. ## Antes de empezar \{#before-you-start\} Necesitas: - **Adapty Unity SDK 4.1 o posterior**: Las versiones anteriores no pueden renderizar flows. Para migrar una app existente, consulta la [guía de migración](migration-to-unity-sdk-v4). - **`SetObserverMode(true)` llamado en el constructor de configuración**: Consulta la [guía de instalación del SDK de Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). - **Integración inicial con el store**: Configura Adapty [con el App Store](initial_ios) y [con Google Play](initial-android). - **Un flow publicado con productos**: Créalo en el [Flow & Paywall Builder](adapty-flow-builder) y asígnale [productos](create-product). - **Un placement para el flow**: [Crea un placement](create-placement) y asigna tu flow a él. - **El flow obtenido en tu app**: [Obtén el flow y su configuración](unity-get-pb-paywalls) antes de mostrarlo. ## Implementación \{#implementation\} 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 renderizado por Adapty, el SDK llama a tu `IAdaptyUIObserverModeResolver` en su lugar — realiza la compra o restauración con tu propio código allí. 1. Implementa la interfaz `IAdaptyUIObserverModeResolver`: ```csharp showLineNumbers using System; using AdaptySDK; public class MyObserverModeResolver : IAdaptyUIObserverModeResolver { public void FlowViewDidInitiatePurchase( AdaptyUIFlowView view, AdaptyPaywallProduct product, Action onStartPurchase, Action onFinishPurchase ) { 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 } public void FlowViewDidInitiateRestore( AdaptyUIFlowView view, Action onStartRestore, Action onFinishRestore ) { onStartRestore(); // restore purchases with your own code, then: onFinishRestore(); } } ``` El método `FlowViewDidInitiatePurchase` te informa de que el usuario ha iniciado una compra, y `FlowViewDidInitiateRestore` — de que el usuario ha iniciado una restauración. Activa tu flujo personalizado de compra o restauración 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 correcto comportamiento del flow, como mostrar el loader, entre otras cosas: | Callback | Descripción | | :----------------- | :------------------------------------------------------------------------------------------------------- | | onStartPurchase() | Este callback debe invocarse para notificar a AdaptyUI que la compra ha comenzado. | | onFinishPurchase() | Este callback debe invocarse para notificar a AdaptyUI que la compra ha finalizado. | | onStartRestore() | Este callback debe invocarse para notificar a AdaptyUI que la restauración ha comenzado. | | onFinishRestore() | Este 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: ```csharp showLineNumbers Adapty.SetObserverModeResolver(new 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 de la forma habitual: [obtén el flow y crea su vista](unity-get-pb-paywalls) y luego [preséntala](unity-present-paywalls). No se necesitan parámetros adicionales — una vez que el resolver está registrado, cada flow 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-unity). De lo contrario, Adapty no reconocerá la transacción y no podrá determinar el paywall de origen de la compra. ::: --- # File: unity-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del Paywall Builder antiguo en el SDK de Unity" description: "Corrige errores al mostrar paywalls creados en el Paywall Builder antiguo en el SDK de Unity de Adapty." --- Esta guía te ayuda a resolver los problemas más comunes al usar paywalls diseñados en el Paywall Builder de Adapty en el SDK de Unity. Si tienes problemas con flows, consulta [Problemas frecuentes](flow-common-issues). ## 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 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. **Causa**: Es posible que estés llamando a `LogShowFlow` (SDK v4+) / `LogShowPaywall` en tu código, lo que duplica el contador de visualizaciones de un paywall creado en el antiguo Paywall Builder. Adapty registra esas visualizaciones automáticamente, por lo que no necesitas este método. **Solución**: Asegúrate de no estar llamando a `LogShowFlow` (SDK v4+) / `LogShowPaywall` en tu código para un paywall creado en el antiguo 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-implement-paywalls-manually --- --- title: "Implementar paywalls personalizados en Unity SDK" description: "Aprende cómo implementar paywalls personalizados en tu app de Unity 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 del usuario y 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={['unity-quickstart-manual', 'fetch-paywalls-and-products-unity', 'present-remote-config-paywalls-unity', 'unity-making-purchases', 'unity-restore-purchase', 'unity-troubleshoot-purchases']} /> ## Modo observador \{#observer-mode\} Si quieres implementar tu propia lógica de manejo de compras desde cero, pero aun así quieres aprovechar el análisis avanzado de Adapty, puedes usar el modo observador. :::important Consulta las limitaciones del modo observador [aquí](observer-vs-full-mode). ::: <CustomDocCardList ids={['implement-observer-mode-unity', 'report-transactions-observer-mode-unity', 'unity-troubleshoot-purchases']} /> --- # File: unity-quickstart-manual --- --- title: "Habilitar compras en tu paywall personalizado en 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 productos, gestiona nuevas compras y restaura las anteriores. Esta guía utiliza las APIs de Adapty Unity SDK v4 (beta) — si usas v3, consulta la [guía de migración](migration-to-unity-sdk-v4) para conocer 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 & Paywall Builder](unity-quickstart-paywalls). Con el Flow & Paywall Builder, creas flows 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 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 personalizados**](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 un paywall personalizado. Básicamente, son simplemente tu forma de gestionar los productos que vendes en tu app. Para implementar tu paywall personalizado, tendrás que crear un **paywall personalizado** y añadirlo a un **placement**. Esta configuración te permite recuperar tus productos. Para entender qué tienes que 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. 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`. ```csharp showLineNumbers using AdaptySDK; void LoadPaywall() { Adapty.GetFlow("YOUR_PLACEMENT_ID", (flow, error) => { if (error != null) { // Handle the error return; } Adapty.GetPaywallProducts(flow, (products, productsError) => { if (productsError != null) { // Handle the error return; } // Use products to build your custom paywall UI }); }); } ``` ## 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. ```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. 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. ```csharp showLineNumbers using AdaptySDK; void RestorePurchases() { Adapty.RestorePurchases((profile, error) => { if (error != null) { // Handle the error return; } // Restore successful, profile updated }); } ``` ## Paso 4. Comprobar 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 lugar de la app, usa el método `GetProfile`: ```csharp showLineNumbers using AdaptySDK; void CheckPremiumAccess() { Adapty.GetProfile((profile, error) => { if (error != null) { // Handle the error return; } var hasPremiumAccess = profile.AccessLevels.TryGetValue("premium", out var premium) && premium.IsActive; // Grant access to paid features if hasPremiumAccess is true }); } ``` 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](unity-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 ya 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. --- # File: fetch-paywalls-and-products-unity --- --- title: "Obtener paywalls personalizados y productos en Unity SDK" description: "Obtén paywalls y productos en Adapty Unity SDK para mejorar la monetización de usuarios." --- <SDKv4> Antes de mostrar paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls personalizados. Para obtener orientación sobre cómo recuperar flows o paywalls personalizados en el **Flow & Paywall Builder** o en el **old Paywall Builder**, consulta [Obtener flows y paywalls](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 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-unity) en tu app. </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 la aplicación 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 fijos en el código.** El único ID que debes incluir de forma fija 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 modificar el código. ::: ```csharp showLineNumbers Adapty.GetFlow( "YOUR_PLACEMENT_ID", AdaptyPlacementFetchPolicy.Default, TimeSpan.FromSeconds(5), (flow, error) => { if (error != null) { // handle the error return; } // flow - the requested flow } ); ``` | 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: `AdaptyPlacementFetchPolicy.Default` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo consulta el servidor cuando no hay nada en caché. 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>Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el `TimeSpan` que indiques, y consulta el servidor una vez que sea más antigua.</p><p></p><p>Ten en cuenta que la caché se conserva al reiniciar la app y solo se borra cuando se desinstala 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 periódica descrita anteriormente y los [paywalls de respaldo](unity-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 que siempre recibas la última versión de tus flows y paywalls, garantizando la fiabilidad incluso cuando la conexión a internet es limitada.</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></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 implicar distintas solicitudes internamente.</p> | ¡No uses IDs de producto en el código! Como los flows se configuran de forma remota, los productos disponibles, su cantidad 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 en un primer momento 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` con: el identificador del flow, las variaciones de paywall (`Paywalls` — cada una con sus propios identificadores de producto), una lista `RemoteConfigs` (una entrada por cada configuración regional configurada) y varias otras propiedades. Para obtener los productos del flow, llama a `GetPaywallProducts(flow)`. | :::note En la versión 4, `GetFlow` no tiene parámetro `locale`. Al renderizar un flow con `CreateFlowView`, la localización se resuelve automáticamente. Para paywalls personalizados, todos los locales disponibles se devuelven juntos en `flow.RemoteConfigs` — elige el que coincida con el idioma del dispositivo del usuario o la configuración de tu app. Consulta [Localizaciones y códigos de locale](unity-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: ```csharp showLineNumbers Adapty.GetPaywallProducts(flow, (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` 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`. A continuación se muestran las propiedades más utilizadas. | 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 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 (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, `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 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 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 son del tipo `AdaptyPaymentMode.FreeTrial`.<br/>• `Price`: un objeto `AdaptyPrice` con el precio con descuento; usa `Price.Amount` para el valor numérico y `Price.LocalizedString` para mostrarlo. En las pruebas gratuitas, busca `0` en `Price.Amount`.<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 mostraría `"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 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\} Por lo general, los flows se obtienen casi al instante, 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 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 mediante el método `GetFlow`, tal como se detalla en la sección [Obtener información del flow](fetch-paywalls-and-products-unity#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 (actual y 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 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 segmentación personalizada (incluyendo por países, 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, usa el método `GetFlowForDefaultAudience` como se muestra a continuación. De lo contrario, sigue usando `GetFlow` descrito [anteriormente](fetch-paywalls-and-products-unity#fetch-flow-information). ::: ```csharp showLineNumbers Adapty.GetFlowForDefaultAudience( "YOUR_PLACEMENT_ID", AdaptyPlacementFetchPolicy.Default, (flow, error) => { if (error != null) { // handle the error return; } // flow - the requested flow } ); ``` | 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: `AdaptyPlacementFetchPolicy.Default` | <p>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK va primero al servidor y devuelve los datos en caché si esa petición falla. 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo va al servidor cuando no hay nada en caché. 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 regularmente, por lo que es seguro usarla durante la sesión para evitar peticiones de red.</p><p></p><p>Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el `TimeSpan` que indiques, y va al servidor una vez que sea más antigua.</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> | </SDKv4> <SDKv3> Antes de mostrar paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls personalizados. Para obtener orientación sobre cómo obtener 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 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-unity) 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. 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 obtener más información sobre los códigos de idioma y cómo recomendamos usarlos.</p> | | **fetchPolicy** | por defecto: `AdaptyPlacementFetchPolicy.Default` | <p>`fetchPolicy` determina qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud falla. 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 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo consulta el servidor cuando no hay nada en caché. 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>Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el `TimeSpan` que indiques, y consulta el servidor cuando sea más antigua.</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](unity-use-fallback-paywalls). También utilizamos 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 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 de espera especificado en `loadTimeout`, ya que la operación puede consistir en distintas 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 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 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?.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 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ás `AdaptySubscriptionPeriodUnit.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, revisa 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`: un objeto `AdaptyPrice` con el precio con descuento; usa `Price.Amount` para el valor numérico y `Price.LocalizedString` para mostrarlo. En las pruebas gratuitas, busca `0` en `Price.Amount`.<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.<br/>• `LocalizedSubscriptionPeriod`: un período de suscripción formateado del descuento para el idioma 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 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`, tal como se detalla en la sección [Obtener paywall](#fetch-paywall-information) más arriba. :::warning Considera usar `GetPaywall` en lugar de `GetPaywallForDefaultAudience`, 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 retrocompatibles 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 para tu caso de uso la velocidad de carga compensa estos inconvenientes, usa `GetPaywallForDefaultAudience` como se muestra a continuación. De lo contrario, usa `GetPaywall` como se describe [arriba](#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: `AdaptyPlacementFetchPolicy.Default` | <p>`fetchPolicy` define qué capa lee primero el SDK, no si puede usar la caché. Por defecto, el SDK consulta primero el servidor y devuelve los datos en caché si esa solicitud falla. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.</p><p></p><p>Sin embargo, si consideras que tus usuarios tienen una conexión a internet inestable, plantéate usar `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`, que invierte ese orden: lee primero la caché y solo acude al servidor cuando no hay nada almacenado. 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 utilizarla durante la sesión para evitar solicitudes de red.</p><p></p><p>Una tercera política, `AdaptyPlacementFetchPolicy.ReturnCacheDataIfNotExpiredElseLoad(maxAge)`, se sitúa entre las dos: lee primero la caché mientras la copia almacenada sea más reciente que el `TimeSpan` que indiques, y acude al servidor una vez que supere ese tiempo.</p><p></p><p>Ten en cuenta que la caché se mantiene intacta al reiniciar la aplicación 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é con actualizaciones regulares descrita anteriormente y los paywalls de respaldo. También utilizamos CDN para obtener los paywalls más rápidamente 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 y asegurar la fiabilidad incluso cuando la conexión a internet es escasa.</p> | </SDKv3> --- # File: present-remote-config-paywalls-unity --- --- title: "Presentar paywalls personalizados en Unity SDK" description: "Descubre cómo presentar paywalls personalizados en Adapty Unity 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 móvil 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. Adapty proporciona un método para obtener la configuración remota, dándote autonomía para mostrar tu paywall personalizado. ## Obtener el Remote Config de un flow y presentarlo \{#get-flow-remote-config-and-present-it\} En la v4, un flow contiene una entrada `AdaptyRemoteConfig` por cada idioma configurado en la lista `RemoteConfigs`. Elige el idioma que coincida con las preferencias del usuario y lee los valores que necesitas. ```csharp showLineNumbers using System.Linq; using AdaptySDK; Adapty.GetFlow("YOUR_PLACEMENT_ID", (flow, error) => { if (error != null) { // handle the error return; } var config = flow.RemoteConfigs.FirstOrDefault(c => c.Locale == "en") ?? flow.RemoteConfigs.FirstOrDefault(); var headerText = config?.Dictionary?["header_text"] as string; // Or access raw JSON data var jsonData = config?.Data; }); ``` 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 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](present-remote-config-paywalls-unity#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. ::: Después 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](unity-making-purchases). Te recomendamos [crear un paywall de respaldo llamado 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 vista 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 vistas requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un flow. Para registrar un evento de vista, simplemente llama a `.LogShowFlow(flow)`, y quedará reflejado en tus métricas en 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. ::: ```csharp showLineNumbers Adapty.LogShowFlow(flow, (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 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ú 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 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 diferentes pantallas de móviles y orientaciones, 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-unity#track-paywall-view-events-1) 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 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** | requerido | Un objeto [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). | </SDKv3> --- # File: unity-making-purchases --- --- title: "Realizar compras in-app en Unity 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, presentar un paywall solo permite compras por sí solo cuando Adapty renderiza la pantalla — es decir, un [flow](adapty-flow-builder), o un paywall del [antiguo Paywall Builder](adapty-paywall-builder). Si renderizas la pantalla en tu propio código, 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 se aplicará automáticamente solo si Adapty renderiza la pantalla. 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 compra \{#make-purchase\} :::note **¿Adapty renderiza tu pantalla?** Para un [flow](adapty-flow-builder) o un paywall de [Paywall Builder](adapty-paywall-builder), las compras se procesan automáticamente: puedes omitir este paso. **¿Buscas orientación paso a paso?** Consulta la [guía de inicio rápido](unity-implement-paywalls-manually) para 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>Acerca de los códigos de oferta</summary> Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios específicos. A diferencia de las ofertas regulares que se aplican automáticamente, los códigos de oferta se distribuyen fuera de la app — a través de campañas de correo electrónico, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, siguiendo 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 realiza al precio completo. - **Pay as you go** — el usuario paga un precio con descuento en cada ciclo de facturación durante un período determinado, luego la suscripción se renueva al precio completo. - **Pay up front** — el usuario paga un precio único con descuento por toda la duración de la oferta, luego 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`. Una vez que el período de oferta termina y la suscripción se renueva al precio completo, la etiqueta ya no está presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Solución de problemas de discrepancias en 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 con descuento de la oferta, verifica lo siguiente en App Store Connect: - El código de oferta tiene el precio correcto configurado para todas las regiones donde los usuarios pueden canjearlo. - El precio de la oferta está establecido para el país o región específico 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 en su lugar. Puedes filtrar y verificar las transacciones con códigos 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 reemplazan con más funcionalidades: elegibilidad configurable, fechas de vencimiento y hasta 1 millón de códigos por trimestre. Si anteriormente usabas códigos promocionales para compras in-app, haz la transición a los códigos de oferta en App Store Connect. ::: Los códigos promocionales heredados (limitados a 100 por app por versión) otorgaban 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ódigos promocionales — enviaba el precio completo del producto en el recibo. Como resultado, Adapty registraba estas transacciones al precio completo, lo que causaba discrepancias de ingresos 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. Dado que estos códigos están ahora obsoletos, haz la transición 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}` ::: ## Compras in-app promocionadas desde la App Store \{#in-app-purchases-from-the-app-store\} :::info Tu app recibe compras in-app promocionadas a partir de la versión 4.1 del SDK, en **iOS 16.4** o posterior. Por debajo de iOS 16.4, el callback nunca se ejecuta y las compras promocionadas se completan por su cuenta. Esta es una función exclusiva de iOS: el callback nunca se ejecuta en Android. ::: Cuando un usuario inicia una compra desde la página de tu producto en el App Store, el SDK te entrega el producto a través de `OnReceivePromotedPurchase` y espera. Completar la compra depende de tu app: pasa el producto a `Adapty.MakePromotedPurchase`. Como tú controlas cuándo ocurre eso, puedes mostrar tu propia pantalla antes de que comience la compra. Si el producto promocionado incluye una oferta de suscripción, el SDK la aplica automáticamente al realizar la compra. La oferta se obtiene del intent de compra del App Store, que la expone en iOS 18.0 y versiones posteriores. En iOS 16.4–17.x, la compra se realiza al precio base. `IAdaptyEventListener` incluye el método obligatorio `OnReceivePromotedPurchase(AdaptyPromotedProduct)`, junto con `Adapty.MakePromotedPurchase` para completar una compra recibida: ```csharp showLineNumbers public void OnReceivePromotedPurchase(AdaptyPromotedProduct product) { Adapty.MakePromotedPurchase(product, (result, error) => { // process the purchase result }); } ``` :::warning `OnReceivePromotedPurchase` es la compra. Una implementación vacía descarta una compra que el usuario ya inició: el App Store entrega el producto a tu app y nada más lo completa. ::: ## 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 para Unity SDK" description: "Aprende cómo restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- Restaurar compras en iOS y Android 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 hayan desinstalado y reinstalado la app o que hayan cambiado de dispositivo y quieran acceder a su contenido comprado anteriormente sin pagar de nuevo. :::note Si creaste la pantalla en un builder, no necesitas restaurar las compras manualmente. Conecta su control de restauración — la acción **Restore purchases** en el Flow & Paywall Builder, o un enlace o botón de restauración en el antiguo — y el SDK completa la restauración. ::: Si creaste la pantalla tú mismo, 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 Unity SDK" description: "Implementa el modo observer en Adapty para rastrear eventos de suscripción de usuarios en Unity 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 se adapta 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 [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 2. [Notificar las transacciones](report-transactions-observer-mode-unity) desde tu infraestructura de compras existente a Adapty. :::tip En la versión 4 del SDK, también puedes presentar flows y paywalls renderizados por Adapty en el modo Observer: implementa `IAdaptyUIObserverModeResolver` y regístralo con `SetObserverModeResolver` 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](unity-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 las suscripciones tú mismo y usas Adapty para enviar eventos de suscripción y análisis. :::important Cuando se ejecuta en modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlo tú mismo. ::: :::note En el SDK 4.1, las interfaces de listener siguen la convención de prefijo C# con I — implementa `IAdaptyEventListener` en lugar de `AdaptyEventListener` — y la interfaz requiere un método adicional, `OnReceivePromotedPurchase`. Consulta la [guía de migración](migration-to-unity-sdk-v4). ::: ```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`. | ## Uso de flows y paywalls de Adapty en modo Observer \{#using-adapty-paywalls-in-observer-mode\} Si también quieres usar los flows, paywalls y funciones de pruebas A/B de Adapty, puedes hacerlo, pero en el modo Observer se necesita algo de configuración adicional. Esto es lo que tendrás que hacer además de los pasos anteriores: 1. Muestra los [paywalls personalizados](present-remote-config-paywalls-unity) como siempre. Para flows y paywalls creados con el Flow & Paywall Builder, consulta [Presentar flows en modo Observer](unity-present-flows-in-observer-mode). 2. [Asocia flows y paywalls](report-transactions-observer-mode-unity) con las transacciones de compra. --- # File: report-transactions-observer-mode-unity --- --- title: "Reportar transacciones en Observer Mode en Unity SDK" description: "Reporta transacciones de compra en el Observer Mode de Adapty para obtener información sobre usuarios y seguimiento de ingresos en Unity SDK." --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+ (actual)" 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. 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 flows y paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra al flow o paywall que la desencadenó, garantizando análisis precisos. ```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 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 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 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 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 flows y paywalls de Adapty, incluye el `PAYWALL_VARIATION_ID` al reportar una transacción. Esto vincula la compra con el flow o paywall que la originó, garantizando unos análisis precisos. ```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 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 | El identificador de tipo String 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> **Informar transacciones** - Las versiones hasta 3.1.x escuchan automáticamente las transacciones en el App Store, por lo que no es necesario informarlas manualmente. - La versión 3.2 no admite el modo Observer. </TabItem> <TabItem value="kotlin" label="Android and Android-based cross-platforms" default> **Informar transacciones** Usa `restorePurchases` para notificar a Adapty de una transacción en el Modo Observador, tal como se explica en la página [Restaurar compras en el 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> **Asociación de flows y paywalls a transacciones** El SDK de Adapty no puede determinar el origen de las compras, ya que eres tú quien las procesa. Por eso, si tienes pensado usar flows, paywalls y/o pruebas A/B en modo Observer, necesitas asociar la transacción que llega desde tu store con el flow o paywall correspondiente en el código de tu app. Es importante hacerlo correctamente antes de publicar tu app; de lo contrario, provocará 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 | 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 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.</p> | | variationId | obligatorio | El identificador de tipo String 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-user --- --- title: "Usuarios y acceso en Unity SDK" description: "Aprende a trabajar con usuarios y niveles de acceso en tu app de Unity con el SDK de Adapty." --- <CustomDocCardList /> --- # File: unity-identifying-users --- --- title: "Identificar usuarios en Unity SDK" description: "Aprende a identificar usuarios en tu aplicación 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 Unity SDK" description: "Aprende cómo actualizar los atributos de usuario y los datos de perfil en tu app de Unity 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. Después 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()`: ```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 el SDK de Unity" 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 Unity." --- Con Adapty, hacer un seguimiento del estado de la suscripción es muy sencillo. No es necesario 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. <details> <summary>Antes de comenzar a verificar 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> ## 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 } }); ``` ### Cómo 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, es necesario realizar una configuración adicional: :::note En el SDK 4.1, las interfaces de listener siguen la convención de prefijo I de C# — implementa `IAdaptyEventListener` en lugar de `AdaptyEventListener` — y la interfaz requiere un método adicional, `OnReceivePromotedPurchase`. Consulta la [guía de migración](migration-to-unity-sdk-v4). ::: ```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 este caso, se pasará el estado de 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 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: "Modo Infantil en el SDK de Unity" description: "Activa fácilmente el Modo Infantil para cumplir con las políticas de Apple y Google. Sin IDFA, GAID ni datos publicitarios en el SDK de Unity." --- <SDKv4> Si tu aplicación de 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 la tienda. ## ¿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**. ### Actualizaciones en el código de tu app \{#updates-in-your-app-code\} Desactiva la recopilación del Android Advertising ID y la dirección IP al activar el SDK: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") // highlight-start .SetGoogleAdvertisingIdCollectionDisabled(true) // set to `true` .SetIPAddressCollectionDisabled(true); // set to `true` // highlight-end Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` En iOS, no es necesario llamar a `SetAppleIDFACollectionDisabled` — al activar el modo Kids para la compilación de iOS (ver más abajo), la recopilación de IDFA se deshabilita automáticamente en la configuración del SDK. ### Habilitar el modo Kids para la compilación de iOS \{#enable-kids-mode-for-the-ios-build\} :::important En iOS, el modo Kids es un trait de Swift Package llamado `KidsMode`. Al habilitarlo, IDFA, AdSupport y AppTrackingTransparency se excluyen del binario del SDK en tiempo de compilación. Requiere SDK 4.0 o posterior —que instala el SDK nativo de iOS mediante Swift Package Manager— y **Xcode 26** o posterior, ya que las versiones anteriores de Xcode no admiten traits de Swift Package. ::: 1. En el Unity Editor, ve a **Player Settings** > **Other Settings** y añade `ADAPTY_KIDS_MODE` en **Scripting Define Symbols** para la plataforma iOS. 2. Compila tu proyecto para iOS como de costumbre. Adapty activa el trait `KidsMode` en la referencia del paquete AdaptySDK-iOS en el proyecto Xcode generado y fuerza `apple_idfa_collection_disabled` en la configuración de ejecución. :::warning Añade el define en **Player Settings**, no en un perfil de compilación. Los scripting defines de un perfil de compilación solo llegan a los ensamblados del Editor después de que Unity los recompile, por lo que una compilación iniciada en la misma sesión puede producir un binario que reporte Kids Mode en tiempo de ejecución mientras sigue enlazando IDFA. Adapty falla la compilación de iOS cuando detecta ese estado. `BuildPlayerOptions.extraScriptingDefines` no es compatible en absoluto, ya que nunca llega a los ensamblados del Editor. ::: ### Actualizaciones en tu manifiesto de Android \{#updates-in-your-android-manifest\} Si tu app está dirigida **únicamente** a niños y compila contra Android 13 (API 33) o superior, Google Play exige que no solicites el permiso `AD_ID`. Deshabilitar la recopilación en la configuración del SDK evita que Adapty recopile el ID, pero no elimina un permiso que otro plugin declare mediante la fusión de manifiestos. Para eliminar el permiso, activa **Custom Main Manifest** en **Player Settings** > **Publishing Settings**, y luego añade lo siguiente dentro del elemento `<manifest>` de `Assets/Plugins/Android/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" /> ``` </SDKv4> <SDKv3> Si tu aplicación Unity está destinada a niños, debes cumplir con las políticas de [Apple](https://developer.apple.com/kids/) y [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Si utilizas el SDK de Adapty, unos sencillos pasos 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 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, recomendamos usar el customer user ID con cuidado. Un ID de usuario con formato `<FirstName.LastName>` se considerará sin duda una recopilación de datos personales, al igual que el uso del correo electrónico. Para el modo Kids, la mejor práctica es utilizar identificadores aleatorios o anonimizados (por ejemplo, IDs hasheados o UUIDs generados por el dispositivo) para garantizar el cumplimiento normativo. ## Activar el 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** dentro de **Collect users' IP address**. ### Actualizaciones en el código de tu app \{#updates-in-your-app-code\} Desactiva la recopilación del Android Advertising ID y la dirección IP al activar el SDK: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") // highlight-start .SetGoogleAdvertisingIdCollectionDisabled(true) // set to `true` .SetIPAddressCollectionDisabled(true); // set to `true` // highlight-end Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` ### Habilitar el modo Kids para la compilación de iOS \{#enable-kids-mode-for-the-ios-build\} Compilar IDFA y AdSupport fuera del binario de iOS requiere Adapty Unity SDK 4.0 o posterior. Actualiza con la [guía de migración](migration-to-unity-sdk-v4) y sigue las instrucciones del SDK 4.0 en esta página. En SDK 3.x, puedes deshabilitar la recopilación de IDFA en la configuración del SDK con `SetAppleIDFACollectionDisabled(true)`, pero el código de IDFA y AdSupport permanece en el binario. Para las opciones de configuración nativas, consulta [Modo Kids en iOS SDK](kids-mode) y [Modo Kids en Android SDK](kids-mode-android). </SDKv3> --- # File: unity-onboardings --- --- title: "Onboardings en Unity SDK" description: "Aprende a trabajar con onboardings en tu app de Unity con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: <CustomDocCardList /> --- # File: unity-get-onboardings --- --- title: "Obtener onboardings en Unity SDK" description: "Aprende cómo recuperar onboardings en Adapty para Unity." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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 de 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 Unity](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 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](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 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 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><p></p><p>El SDK de Adapty almacena los onboardings localmente en dos capas: la caché actualizada regularmente 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> | | **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 estar compuesta de diferentes peticiones internamente.</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 abordar 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-and-create-view) 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ños compatibles con versiones anteriores o a aceptar que las versiones más antiguas se muestren 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 el tiempo de carga más rápido supera estos inconvenientes para tu caso de uso, utiliza `GetOnboardingForDefaultAudience` como se muestra a continuación. De lo contrario, usa `GetOnboarding` tal como se describe [más arriba](#fetch-onboarding-and-create-view). ::: ```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 a presentar onboardings de forma efectiva para conseguir más conversiones." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: Si has personalizado un onboarding con el builder, no necesitas preocuparte por renderizarlo en el código de tu app Unity para mostrárselo al usuario. Ese onboarding ya incluye tanto el contenido que debe aparecer como la forma en que debe presentarse. Antes de empezar, asegúrate de que: 1. Tienes 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). Para mostrar un onboarding, utiliza el método `view.Present()` sobre 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 provocar 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 el SDK de Unity" description: "Maneja eventos relacionados con el onboarding en Unity usando Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: 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`. :::note En el SDK 4.1, las interfaces de listener siguen la convención de prefijo I de C#: implementa `IAdaptyOnboardingsEventsListener` en lugar de `AdaptyOnboardingsEventsListener`. Los métodos no han cambiado. Consulta la [guía de migración](migration-to-unity-sdk-v4). ::: ## 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 utiliza datos de onboardings en tu app de Unity con el SDK de Adapty." --- :::warning **Los onboardings están obsoletos a partir de Adapty SDK v4.** Usa [flows](adapty-flow-builder) 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 — animaciones más fluidas, una apariencia nativa consistente, tiempos de carga más rápidos y sin dependencia del runtime de WebView. ::: Cuando tus usuarios responden a una pregunta de un cuestionario 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. Implementa el método `OnboardingViewOnStateUpdatedAction` en tu clase: :::note En el SDK 4.1, las interfaces de listener siguen la convención de prefijo I de C#: implementa `IAdaptyOnboardingsEventsListener` en lugar de `AdaptyOnboardingsEventsListener`. Los métodos no cambian. Consulta la [guía de migración](migration-to-unity-sdk-v4). ::: ```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-best-practices --- --- title: "Mejores prácticas en el SDK de Unity" description: "Patrones de referencia para integrar el SDK de Adapty en Unity: orden de llamadas, manejo de errores y otras reglas para entornos de producción." --- <CustomDocCardList /> --- # 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 MMP o SDK de análisis (AppsFlyer, Adjust, PostHog, Branch) | Al iniciar la app, primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerId`. | | 2a | `Adapty.Activate(builder.Build(), ...)` con `SetCustomerUserId` configurado en el builder | 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(builder.Build(), ...)` sin `SetCustomerUserId` | 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, 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 se asocien al 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 el flujo 2b con autenticación | Espera el callback de finalización. Las llamadas concurrentes durante `Identify` generan `#3006 profileWasChanged`. | | 5 | `GetPaywall` (`GetFlow` en SDK v4), `GetPaywallProducts`, `RestorePurchases`, `MakePurchase`, `UpdateAttribution` (`UpdateExternalAttribution` en SDK 4.1), `UpdateProfile` | Después del paso 4 si llamas a `Identify`; si no, 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 obtención de flows y paywalls en Unity SDK" description: "Obtén flows y paywalls de Adapty de forma fiable: temporización, caché y patrones de respaldo para Unity." --- Una obtención fiable de un flow o paywall en Unity hace tres cosas: renderiza rápido, devuelve la variación orientada a la audiencia y recurre al respaldo sin problemas cuando la red es lenta. Las reglas a continuación cubren la temporización, 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 Unity](unity-sdk-call-order). ::: ## 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. | No hagas prefetch de todos los placements a la vez al arrancar. | El prefetch masivo bloquea el hilo principal y provoca una pantalla en negro durante el pico de carga. | | Obtén `GetFlow` después de que la atribución haya tenido tiempo de resolverse — por ejemplo, 1–2 segundos después de `Activate` o tras el evento `OnLoadLatestProfile`. | No llames a `GetFlow` en `Awake()`. | La atribución todavía no ha llegado. El flow se resuelve contra la audiencia por defecto y omite silenciosamente los segmentos y la personalización de ASA. | | Define un `loadTimeout` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | No esperes indefinidamente a `GetFlow`. | Sin un timeout, los usuarios con mala conectividad ven una pantalla en blanco hasta que la red responde — o cierran la app. | Consulta [Obtener paywalls y productos personalizados](fetch-paywalls-and-products-unity) como referencia para los parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado. ## Mostrar el contenido multimedia de la primera pantalla desde el bundle de la app \{#show-first-screen-media-from-the-app-bundle\} Un flow descarga sus imágenes y vídeos desde Adapty. Para mostrar el contenido multimedia de la primera pantalla de forma instantánea, sírvelo desde el bundle de la app. Es una buena forma de reutilizar los archivos multimedia que ya incluyes, como los de un onboarding nativo existente. 1. En el Flow & Paywall Builder, asigna un [ID de contenido multimedia personalizado](custom-media#custom-media-id) a la imagen o el vídeo. El archivo que subas ahí se usará como respaldo. 2. Añade el archivo a tu proyecto de Unity. 3. Al crear la vista del flow, pasa el archivo del bundle para ese ID con `SetCustomAssets`: ```csharp showLineNumbers // "welcome_video" is the custom media ID set in the Flow & Paywall Builder var bundledAssets = new Dictionary<string, AdaptyCustomAsset> { { "welcome_video", AdaptyCustomAsset.LocalVideoFile("custom_assets/videos/welcome.mp4") } }; var parameters = new AdaptyUICreateFlowViewParameters() .SetCustomAssets(bundledAssets); AdaptyUI.CreateFlowView(flow, parameters, (view, error) => { // handle the result }); ``` Los archivos incluidos aumentan el tamaño de descarga de tu app, así que incluye solo los recursos multimedia que los usuarios ven primero. Los medios que no incluyes en el bundle aparecen igualmente de inmediato: el layout del flow incluye una copia pequeña de baja resolución de cada imagen —incluido el fotograma estático de un vídeo— y la muestra hasta que se carga el archivo completo. Para la referencia completa de `customAssets`, consulta [Personalizar assets](unity-get-pb-paywalls#customize-assets). ## Ajustes para conectividad deficiente \{#tune-for-poor-connectivity\} Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte, regiones con problemas de enrutamiento): - Establece `fetchPolicy` en `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad` en cada solicitud 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 flow a `GetProfile`. Llama a `GetFlow` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: unity-show-aa-targeted-paywall --- --- title: "Mostrar un paywall segmentado por Apple Ads en el primer lanzamiento con Unity SDK" description: "Espera la atribución de Apple Ads antes de solicitar el paywall en Unity usando AdaptyProfile.AppliedExternalAttributionProviders." --- Este artículo se aplica a la versión iOS de tu app. La atribución de Apple Ads solo existe en iOS. Apple Ads (AA) la atribución llega de forma asíncrona después de `Adapty.Activate()`. Si llamas a `GetFlow` demasiado pronto, la atribución a menudo aún no ha llegado y Adapty resuelve el placement contra la audiencia predeterminada, saltándose tus paywalls segmentados por AA. `AdaptyProfile.AppliedExternalAttributionProviders` permite a la app detectar cuándo se ha aplicado la atribución de AA al perfil, para que la solicitud de paywall pueda esperar hasta que la segmentación de AA se resuelva correctamente. :::important `AppliedExternalAttributionProviders` solo reporta Apple Ads. La atribución de otros proveedores es visible en los perfiles de usuario y está disponible en los filtros de segmento, pero aún no aquí. ::: ## Antes de empezar \{#before-you-start\} Necesitas: - Adapty Unity SDK **4.1** o posterior. - Apple Ads configurado para la aplicación en Adapty. Consulta [Apple Ads](apple-search-ads). :::note Los ejemplos utilizan los nombres de la API del SDK 4.1. En el SDK 4.0, la propiedad del perfil se llama `AppliedAttributionSources`. En el SDK 3.x, esa propiedad tiene el mismo nombre anterior, y los paywalls se obtienen con `GetPaywall`/`GetPaywallForDefaultAudience`, que devuelven un `AdaptyPaywall`. Consulta [Migrate Adapty Unity SDK to v4](migration-to-unity-sdk-v4). ::: ## 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 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 manejador `OnLoadLatestProfile`, con `AdaptyExternalAttributionProvider.AppleAds` en su lista `AppliedExternalAttributionProviders`. Una lista vacía puede significar cualquiera de estas cosas: - La atribución de Apple Ads aún no se ha procesado para este perfil. - No ha llegado ninguna atribución. - La atribución llegó de otro proveedor que esta lista no reporta. Aunque la lista esté vacía, `GetFlow` es seguro de llamar: Adapty resuelve la solicitud según la audiencia que coincida con el estado actual del perfil, normalmente la audiencia predeterminada. :::important La espera solo aplica al **primer lanzamiento**. Una vez que se ha registrado la atribución de Apple Ads, queda almacenada permanentemente en el perfil. En cada lanzamiento posterior, el perfil en caché ya incluye `AppleAds` en `AppliedExternalAttributionProviders`, `OnLoadLatestProfile` se dispara inmediatamente con ese valor, y `GetFlow` devuelve el paywall segmentado por Apple Ads sin ningún retraso. ::: ## Implementación \{#implementation\} En el primer lanzamiento, comprueba si recibes `AdaptyExternalAttributionProvider.AppleAds` en el perfil y aplica un tiempo de espera máximo — 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 Unity](sdk-installation-unity). 2. **Suscríbete a las actualizaciones del perfil** implementando `IAdaptyEventListener` y registrándolo con `Adapty.SetEventListener`. Si aún no has configurado el listener, consulta [Escuchar actualizaciones de suscripción](unity-check-subscription-status#listen-to-subscription-updates). 3. **Comprueba si aparece `AppleAds` en `AppliedExternalAttributionProviders`.** Cuando aparezca, solicita el paywall; Adapty devolverá la variante segmentada por AA: ```csharp using System.Linq; public void OnLoadLatestProfile(AdaptyProfile profile) { if (profile.AppliedExternalAttributionProviders.Contains(AdaptyExternalAttributionProvider.AppleAds)) { // load the paywall via Adapty.GetFlow(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 audiencia predeterminada con `GetFlowForDefaultAudience`. Este devuelve el paywall sin esperar la segmentación. Whichever path fires first should load the paywall; the other path should be skipped. Use a single state flag (for example, `hasLoadedPaywall`) to deduplicate so the paywall isn't fetched twice. Configure a [paywall de respaldo](unity-use-fallback-paywalls) for the placement so the user is never stuck if the network request fails. `SetEventListener` acepta un único listener. Si tu app ya usa un `IAdaptyEventListener` para otros fines (por ejemplo, [escuchar actualizaciones de suscripción](unity-check-subscription-status#listen-to-subscription-updates)), añade la comprobación en su `OnLoadLatestProfile` existente en lugar de registrar un segundo listener. --- # 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: unity-reference --- --- title: "Referencia del SDK de Unity" description: "Documentación de referencia para el SDK de Adapty para Unity." --- Esta página contiene la documentación de referencia del SDK de Adapty para Unity. Elige el tema que necesitas: - **[Modelos del SDK](https://unity.adapty.io/)** - Modelos de datos y estructuras utilizados por el SDK - **[Gestionar errores](unity-handle-errors)** - Gestión de errores y solución de problemas --- # File: unity-handle-errors --- --- title: "Gestión de errores en Unity SDK" description: "Gestión de errores en Unity SDK." --- Todos los errores que devuelve el SDK son de tipo `AdaptyErrorCode`. Aquí tienes un ejemplo: :::tip **Activa los logs detallados antes de depurar.** La mayoría de los `AdaptyError`s envuelven un error subyacente de StoreKit, Play Billing, de red o de backend. Con los logs detallados activados (`Adapty.SetLogLevel(AdaptyLogLevel.Verbose, ...)` — ver [Logging](sdk-installation-unity#logging)), ese error subyacente 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 soporte y ayudarnos a asistirte de forma más eficiente. ::: ```csharp showLineNumbers using AdaptySDK; Adapty.MakePurchase(product, (result, error) => { if (error != null && error.Code == AdaptyErrorCode.CantMakePayments) { // In-app purchases are not allowed on this device } }); ``` :::note Una compra cancelada por el usuario **no** es un error. `MakePurchase` lo indica a través del resultado: `result.Type` es `AdaptyPurchaseResultType.UserCancelled` — consulta [Realizar compras](unity-making-purchases). ::: ## Códigos de sistema StoreKit \{#system-storekit-codes\} | Error | Código | Solución | |-----|----|-----------| | [unknown](https://developer.apple.com/documentation/storekit/skerror/code/unknown) | 0 | Código de error que indica que ocurrió un error desconocido o inesperado. <br/> Vuelve a intentarlo o consulta la sección [Otros problemas](#other-issues). | | [clientInvalid](https://developer.apple.com/documentation/storekit/skerror/code/clientinvalid) | 1 | Este código de error indica que el cliente no tiene permiso para realizar la acción solicitada. | | [paymentCancelled](https://developer.apple.com/documentation/storekit/skerror/code/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, aunque a nivel de lógica de negocio puedes ofrecer un descuento al usuario o recordárselo más adelante.</p> | | [paymentInvalid](https://developer.apple.com/documentation/storekit/skerror/code/paymentinvalid) | 3 | Este error indica que el App Store no reconoció uno de los parámetros del pago. | | [paymentNotAllowed](https://developer.apple.com/documentation/storekit/skerror/code/paymentnotallowed) | 4 | Este código de error indica que el usuario no tiene permiso para autorizar pagos. | | [storeProductNotAvailable](https://developer.apple.com/documentation/storekit/skerror/code/storeproductnotavailable) | 5 | Este código de error indica que el producto solicitado no está disponible en el store. <br/> Prueba a reinstalar la app. | | [cloudServicePermissionDenied](https://developer.apple.com/documentation/storekit/skerror/code/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](https://developer.apple.com/documentation/storekit/skerror/code/cloudservicenetworkconnectionfailed) | 7 | Este código de error indica que el dispositivo no pudo conectarse a la red. | | [cloudServiceRevoked](https://developer.apple.com/documentation/storekit/skerror/code/cloudservicerevoked/) | 8 | Este código de error indica que el usuario ha revocado el permiso para usar este servicio en la nube. | | [privacyAcknowledgementRequired](https://developer.apple.com/documentation/storekit/skerror/code/privacyacknowledgementrequired) | 9 | Este código de error indica que el usuario aún no ha aceptado la política de privacidad de Apple. | | [unauthorizedRequestData](https://developer.apple.com/documentation/storekit/skerror/code/unauthorizedrequestdata) | 10 | Este código de error indica que la app intenta usar una propiedad para la que no tiene el entitlement requerido. | | [invalidOfferIdentifier](https://developer.apple.com/documentation/storekit/skerror/code/invalidofferidentifier) | 11 | <p>El [`identifier`](https://developer.apple.com/documentation/storekit/skpaymentdiscount/identifier) de la oferta no es válido. Por ejemplo, no has configurado una oferta con ese identificador en el App Store o la has revocado.</p><p>Asegúrate de configurar las ofertas deseadas en App Store Connect y de pasar un identificador de oferta válido.</p> | | [invalidSignature](https://developer.apple.com/documentation/storekit/skerror/code/invalidsignature) | 12 | Este código de error indica que la firma en un descuento de pago no es válida. | | [missingOfferParams](https://developer.apple.com/documentation/storekit/skerror/code/missingofferparams) | 13 | Este código de error indica que faltan parámetros en un descuento de pago. | | [invalidOfferPrice](https://developer.apple.com/documentation/storekit/skerror/code/invalidofferprice/) | 14 | Este código de error indica que el precio especificado en App Store Connect ya no es válido. Las ofertas deben representar siempre un precio con descuento. | ## Códigos personalizados de Android \{#custom-android-codes\} | Error | Code | Solution | |-----|----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | adaptyNotInitialized | 20 | Debes configurar correctamente el SDK de Adapty mediante el método `Adapty.activate`. Aprende cómo hacerlo [para Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). | | productNotFound | 22 | Este error indica que el producto solicitado para la compra no está disponible en el store. | | invalidJson | 23 | El JSON del paywall no es válido. Corrígelo en el Adapty Dashboard. Consulta el tema [Personalizar el paywall con Remote Config](customize-paywall-with-remote-config) para más detalles sobre cómo solucionarlo. | | currentSubscriptionToUpdateNotFoundInHistory | 24 | No se encuentra 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 de Android Developer 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 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` ha sido interrumpida. | | billingServiceUnavailable | 102 | Este error transitorio indica que el servicio de facturación de Google Play no está disponible actualmente. En la mayoría de los casos, significa que hay un problema de conexión de red en algún punto entre el dispositivo cliente y los servicios de Google Play Billing. | | billingUnavailable | 103 | <p>Este error indica que se produjo 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 se encuentra en un país no compatible.</p><p>3. El usuario es un empleado de empresa y su administrador ha deshabilitado las compras para los usuarios.</p><p>4. Google Play no puede cargar el método de pago del usuario. Por ejemplo, es posible que la tarjeta de crédito del usuario haya vencido.</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 con el propio 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ó sin | ## Códigos personalizados de StoreKit \{#custom-storekit-codes\} | Error | Código | Solución | |-----|----|-----------| | noProductIDsFound | 1000 | <p>Este error indica que ninguno de los productos que solicitaste en el paywall está disponible para su compra en el App Store, aunque estén listados allí. A veces puede ir acompañado de una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin un error, puedes ignorarla sin problema.</p><p>Si encuentras este error, sigue los pasos de la sección [Solución para el error Code-1000 `noProductIDsFound`](InvalidProductIdentifiers-unity).</p> | | productRequestFailed | 1002 | <p>No se pueden obtener los productos disponibles en este momento. Posible motivo:</p><p></p><p>- Todavía no se ha creado caché y tampoco hay conexión a internet.</p> | | cantMakePayments | 1003 | Las compras in-app no están permitidas en este dispositivo. Consulta la [guía](cantMakePayments-unity) de solución de problemas. | | cantReadReceipt | 1005 | <p>No hay ningún recibo válido disponible en el dispositivo. Esto puede ocurrir durante las pruebas en sandbox.</p><p>No es necesario hacer nada, 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ó. Este error envuelve un error subyacente de StoreKit — lee el error anidado (o activa los logs detallados para verlo en la consola) para conocer el motivo real. El error anidado suele ser uno de los códigos StoreKit del 0 al 14 de la tabla anterior — los más comunes son `paymentCancelled`, `paymentInvalid`, `paymentNotAllowed` o `invalidOfferPrice`. Si no puedes identificar un motivo concreto, 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. | | fetchSubscriptionStatusFailed | 1020 | El SDK no pudo leer el estado de la suscripción desde el store. <br/> Reintenta la llamada y consulta la sección [Otros problemas](#other-issues). | ## Códigos de red personalizados \{#custom-network-codes\} | Error | Código | Solución | |:---------------------|:-------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | notActivated | 2002 | El SDK de Adapty no está activado. <br/> Suele aparecer cuando una pantalla de splash o un hook de script temprano llama a métodos de Adapty antes de que `Adapty.Activate` termine. El síntoma es intermitente y puede no reproducirse en el editor porque el timing en dispositivos reales es diferente. Espera al callback de finalización de `Activate` antes de programar cualquier otra llamada al SDK. Consulta [Orden de llamadas en el SDK de Unity](unity-sdk-call-order) para ver la secuencia completa. | | badRequest | 2003 | Solicitud incorrecta. <br/> Asegúrate de haber completado todos los pasos necesarios para [integrarte con el App Store](app-store-connection-configuration). | | serverError | 2004 | Error del servidor. <br/> Inténtalo de nuevo después de un tiempo. Si el problema persiste, contacta con el equipo de soporte de Adapty. | | networkFailed | 2005 | Este error indica problemas con la conexión de red en el dispositivo del usuario. <br/> Prueba a desactivar la VPN o a cambiar de red móvil a WiFi, o viceversa. | | decodingFailed | 2006 | Este error indica que la decodificación de la respuesta falló. <br/> 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 de API no válida. | | encodingFailed | 2009 | Este error indica que la codificación de la solicitud falló. | | analyticsDisabled | 3000 | No podemos gestionar los eventos de analíticas porque [los desactivaste](analytics-integration#disabling-external-analytics-for-a-specific-customer). | | wrongParam | 3001 | Este error indica que alguno de tus parámetros no es correcto. <br/> Si usas el Paywall Builder antiguo y no puedes mostrar un paywall por este error, activa **Show on device** en ese builder.<br/> Otro motivo posible es que la versión del archivo de [respaldo](fallback-paywalls) local no coincida con la versión del SDK. Descarga un archivo nuevo desde el dashboard. | | 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. <br/> Esto ocurre cuando se llama a un método mientras `Adapty.Identify` aún está en curso: la llamada en vuelo aterriza en un perfil que está a punto de ser reemplazado y el SDK la rechaza. Espera al callback de finalización de `Identify` antes de cualquier llamada de acción del usuario. Consulta [Orden de llamadas en el SDK de Unity](unity-sdk-call-order). | | unsupportedData | 3007 | Este error indica que el formato de datos no es compatible con el SDK. | | fetchTimeoutError | 3101 | Este error indica que la operación de fetch superó el tiempo de espera. | | operationInterrupted | 9000 | El sistema interrumpió esta operación. <br/> Vuelve a intentar la llamada. | ## 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. - **Contacta con el equipo de soporte u obtén ayuda de otros desarrolladores** en el [foro de soporte](https://adapty.featurebase.app/). - **Contacta con el equipo de soporte en [support@adapty.io](mailto:support@adapty.io) o por el chat**: Si no puedes actualizar el SDK o la actualización no ha resuelto el problema, contacta con nuestro equipo de soporte. Ten en cuenta que tu problema se resolverá más rápido si [activas el registro detallado](sdk-installation-unity#logging) y compartes los registros con el equipo. También puedes adjuntar fragmentos de código relevantes. --- # 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**. Copia solo el identificador — en formato DNS inverso sin espacios, por ejemplo, `com.company.app`. App Store Connect muestra el nombre de la app junto a él en otras vistas, como la lista de apps; pegar eso en lugar del identificador hará que la conexión no pueda validarse. <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: unity-sdk-migration-guides --- --- title: "Guías de migración del SDK de Unity" description: "Guías de migración para las versiones del SDK de Unity de Adapty." --- Esta página contiene todas las guías de migración para el SDK de Unity de Adapty. Elige la versión a la que quieres migrar para obtener instrucciones detalladas: - **[Migrar a v4.1](migration-to-unity-sdk-v4)** - **[Migrar a v3.14](migration-to-unity-sdk-314)** - **[Migrar a v3.4](migration-to-unity-sdk-34)** - **[Migrar a v3.3](migration-to-unity330)** - **[Migrar a v3.0](migration-to-unity-sdk-v3)** --- # File: migration-to-unity-sdk-v4 --- --- title: "Migrar el SDK de Unity de Adapty a v4.1" description: "Migra al SDK de Unity de Adapty v4.1: reemplaza las APIs de paywall con APIs de flow, adopta las APIs de atribución renombradas, habilita Adapty Attribution explícitamente y vuelve a descargar tu archivo de respaldo." --- Adapty Unity SDK 4.1 es la primera versión estable de la línea 4.x — la 4.0 solo se publicó en beta, así que si estás en 3.x, migra directamente a la 4.1. Esta guía cubre toda la migración: los flows introducidos en la 4.0 y los cambios de la 4.1 sobre ellos. La línea 4.x introduce flows y renombra las APIs de paywall en consecuencia. Las nuevas APIs funcionan con flows, y también siguen funcionando con paywalls del builder anterior — no se requieren cambios de configuración en el Adapty Dashboard. Además, la versión 4.1 renombra las APIs de atribución externa, convierte la atribución de Adapty en opt-in, añade un método de listener obligatorio y cambia el formato del archivo de respaldo. :::note ¿Vienes de la beta 4.0? Sustituye la etiqueta beta fijada por la [instalación 4.1.0](#installation), y solo aplican cuatro secciones: [el nuevo método del listener](#new-required-method-onreceivepromotedpurchase), [las APIs de atribución externa renombradas](#renamed-external-attribution-apis), [la atribución de Adapty está desactivada por defecto](#adapty-attribution-is-disabled-by-default), y [los archivos de respaldo](#fallback-files). ::: ## Referencia rápida \{#quick-reference\} | v3 | v4.1 | |---|---| | `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, ...)` | | `AdaptyUICreatePaywallViewParameters` | `AdaptyUICreateFlowViewParameters` | | `AdaptyUIPaywallView` | `AdaptyUIFlowView` | | `AdaptyUI.PresentPaywallView(view, ...)` / `DismissPaywallView(view, ...)` | `AdaptyUI.PresentFlowView(view, ...)` / `DismissFlowView(view, ...)` | | `Adapty.SetPaywallsEventsListener(listener)` | `Adapty.SetFlowsEventsListener(listener)` | | `AdaptyPaywallsEventsListener` | `IAdaptyFlowsEventsListener` | | `AdaptyEventListener` | `IAdaptyEventListener`, con un nuevo método requerido `OnReceivePromotedPurchase` | | `AdaptyOnboardingsEventsListener` | `IAdaptyOnboardingsEventsListener` | | `PaywallViewDidPerformAction`, `PaywallViewDidAppear` y otros callbacks `PaywallView...` | `FlowViewDidPerformAction`, `FlowViewDidAppear` y otros callbacks `FlowView...` | | `PaywallViewDidFailRendering` | `FlowViewDidReceiveError` | | `Adapty.UpdateAttribution(data, source, ...)` con un `string` source | `Adapty.UpdateExternalAttribution(jsonString, provider, ...)` con un `AdaptyExternalAttributionProvider` | | `AdaptyProfile.AppliedAttributionSources` como `IReadOnlyList<string>` | `AdaptyProfile.AppliedExternalAttributionProviders` como `IReadOnlyList<AdaptyExternalAttributionProvider>` | | Atribución de Adapty habilitada automáticamente | deshabilitada por defecto — actívala con `Builder.SetAdaptyAttributionEnabled(true)` | | Archivo de respaldo descargado para 3.x | nuevo formato de archivo de respaldo — descarga el archivo de nuevo | | `Adapty.SetFallbackPaywalls(...)` (obsoleto en v3) | eliminado — usa `Adapty.SetFallback(fileName, ...)` | | `Builder.SetIDFACollectionDisabled(...)` (obsoleto en v3) | eliminado — usa `Builder.SetAppleIDFACollectionDisabled(...)` | | `paywall.Products` (una lista de `AdaptyProductReference`) | eliminado — usa `ProductIdentifiers` o `VendorProductIds`, o llama a `GetPaywallProducts(flow)` para obtener productos completos | | `AdaptyProductReference` | eliminado como tipo público — consulta [Modelo de datos](#data-model) | | `paywall.RemoteConfigString` | eliminado — usa `flow.RemoteConfig?.Data` | `AdaptyPaywallProduct` mantiene su nombre — los productos siguen perteneciendo a un flow, y `GetPaywallProducts` también mantiene su nombre, ahora tomando un `AdaptyFlow`. Los métodos `GetFlow` y `GetFlowForDefaultAudience` ya no aceptan el parámetro `locale`. Las APIs de compra y perfil (`MakePurchase`, `RestorePurchases`, `GetProfile`, `Identify`, `UpdateProfile`) no cambian. `SetFallback` mantiene su firma, pero el archivo que lee debe descargarse de nuevo — consulta [Archivos de respaldo](#fallback-files). 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). ## Instalación \{#installation\} Para instalar el SDK 4.1 mediante el Unity Package Manager, añade la etiqueta de versión a la URL de Git: ``` https://github.com/adaptyteam/AdaptySDK-Unity.git?path=/Packages/com.adapty.unity-sdk#4.1.0 ``` Si instalas mediante el paquete de Unity, descarga `adapty-unity-plugin-4.1.0.unitypackage` desde la [versión 4.1.0](https://github.com/adaptyteam/AdaptySDK-Unity/releases/tag/4.1.0). Consulta [Instalar el SDK de Adapty](sdk-installation-unity#install-adapty-sdk) para ver la configuración completa. Con la versión 4.x llegan dos cambios en la configuración del build: - **Las dependencias de iOS migran a Swift Package Manager.** El SDK nativo de Adapty para iOS se declara ahora como paquete Swift remoto en lugar de un pod de CocoaPods. Actualiza el [External Dependency Manager](https://github.com/googlesamples/unity-jar-resolver#getting-started) a **1.2.188 o posterior**, ya que las versiones anteriores no admiten dependencias de Swift Package Manager. Los pasos de CocoaPods (`iOS Resolver -> Install Cocoapods`, abrir `Unity-iPhone.xcworkspace`) ya no aplican. Para compilar para iOS ahora se requiere **Xcode 26** o posterior, ya que el paquete Swift se compila con Swift tools 6.2. - **El deployment target de iOS debe ser 15.0 o posterior.** Un nuevo validador de compilación en el Unity Editor detiene la compilación para iOS si el target es inferior. Las versiones nativas del SDK de Adapty se actualizan a la 4.x en ambas plataformas y se resuelven automáticamente; no se necesitan cambios adicionales en la compilación. ## Recuperar 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 idioma se resuelve automáticamente; para paywalls personalizados, todos los idiomas se devuelven en `flow.RemoteConfigs`: ```diff showLineNumbers - Adapty.GetPaywall("YOUR_PLACEMENT_ID", "en", (paywall, error) => { + Adapty.GetFlow("YOUR_PLACEMENT_ID", (flow, error) => { if (error != null) { // handle the error return; } - // use the paywall + // use the flow }); ``` `GetPaywallForDefaultAudience` se renombra de la misma manera: ```diff showLineNumbers - Adapty.GetPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", (paywall, error) => { /* ... */ }); + Adapty.GetFlowForDefaultAudience("YOUR_PLACEMENT_ID", (flow, error) => { /* ... */ }); ``` ### GetPaywallProducts(paywall) → GetPaywallProducts(flow) `GetPaywallProducts` mantiene su nombre pero ahora acepta un `AdaptyFlow`: ```diff showLineNumbers - Adapty.GetPaywallProducts(paywall, (products, error) => { + Adapty.GetPaywallProducts(flow, (products, error) => { if (error != null) { // handle the error return; } // use the products }); ``` ## Modelo de datos \{#data-model\} `GetFlow` devuelve un `AdaptyFlow` en lugar de un `AdaptyPaywall`, y la forma del objeto ha cambiado: | Propiedad de `AdaptyPaywall` en v3 | Propiedad de `AdaptyFlow` en v4 | Acción | |---|---|---| | `RemoteConfig` (única, nullable) | `RemoteConfigs` (lista) | Un flow lleva un Remote Config por idioma configurado. Lee el que corresponda al usuario desde `flow.RemoteConfigs`. El atajo `flow.RemoteConfig` devuelve la primera entrada. | | _(nuevo)_ | `Paywalls` (lista de `AdaptyFlowPaywall`) | Cada entrada es una variación de paywall en el flow, con su propio `Name`, `VariationId` y `ProductIdentifiers`. Los métodos de web paywall reciben un `AdaptyFlowPaywall`; consulta [Métodos de web paywall](#web-paywall-methods). | | `ProductIdentifiers`, `VendorProductIds` | se conservan | En `AdaptyFlow`, estos agregan los productos de todas las variaciones de paywall. Cada variación también expone sus propios `ProductIdentifiers` y `VendorProductIds`. Para obtener productos, sigue llamando a `GetPaywallProducts(flow)`. | | `HasViewConfiguration` | eliminado | Elimina cualquier comprobación de `HasViewConfiguration` de tu código — `CreateFlowView` devuelve un error en su lugar (consulta [Mostrar flows](#displaying-flows)). | | `Products` (lista de `AdaptyProductReference`) | eliminado | `AdaptyProductReference` ya no es público, y con él desaparecen los valores `PromotionalOfferId`, `WinBackOfferId` y `AndroidOfferId` que contenía. Usa `ProductIdentifiers` — una lista de `AdaptyProductIdentifier` con `VendorProductId` y el campo exclusivo de Android `BasePlanId` (el `AndroidBasePlanId` de v3) — o llama a `GetPaywallProducts(flow)` cuando necesites objetos `AdaptyPaywallProduct` completos con precios y ofertas. | | `RemoteConfigString` | eliminado | Lee el string directamente desde el Remote Config: `flow.RemoteConfig?.Data`, o la entrada correspondiente en `flow.RemoteConfigs`. | | _(nuevo)_ | `FlowVersionId` (nullable) | El identificador de versión del flow, o `null` cuando no está disponible. | `AdaptyPaywallProduct` gana un campo nuevo: `FlowProductId`, el identificador del producto dentro del flow, que es `null` para los productos que no pertenecen a un flow. ## Métodos de paywall web \{#web-paywall-methods\} `OpenWebPaywall` y `CreateWebPaywallUrl` mantienen sus nombres, pero el argumento `paywall` ahora acepta un `AdaptyFlowPaywall` — una de las variaciones en `flow.Paywalls`. Aún puedes pasar un `AdaptyPaywallProduct` en su lugar: ```diff showLineNumbers - Adapty.OpenWebPaywall(paywall, AdaptyWebPresentation.ExternalBrowser, (error) => { /* ... */ }); + var flowPaywall = flow.Paywalls.FirstOrDefault(); + if (flowPaywall != null) { + Adapty.OpenWebPaywall(flowPaywall, AdaptyWebPresentation.ExternalBrowser, (error) => { /* ... */ }); + } ``` ## Seguimiento de visualizaciones de flows \{#tracking-flow-views\} ### LogShowPaywall → LogShowFlow `LogShowPaywall` pasa 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 pruebas A/B existentes siguen funcionando sin cambios en el dashboard. ```diff showLineNumbers - Adapty.LogShowPaywall(paywall, (error) => { /* ... */ }); + Adapty.LogShowFlow(flow, (error) => { /* ... */ }); ``` Al igual que en v3, no es necesario llamar a este método al mostrar flows o paywalls renderizados por Adapty — Adapty registra esas vistas automáticamente. ## Mostrando flows \{#displaying-flows\} ### CreatePaywallView → CreateFlowView Renombra el método de fábrica y pasa el `AdaptyFlow`. El tipo de vista devuelto cambia de `AdaptyUIPaywallView` a `AdaptyUIFlowView`, pero sus métodos (`Present`, `Dismiss`) no cambian, y el objeto de parámetros opcionales mantiene los mismos campos (`LoadTimeout`, `PreloadProducts`, `CustomTags`, `CustomTimers`, `CustomAssets`, `ProductPurchaseParameters`) bajo el nuevo nombre `AdaptyUICreateFlowViewParameters`, además de dos nuevos — `Locale` y `EnableSafeAreaPaddings`: :::note `CustomTimers` sigue existiendo, pero solo afecta a los paywalls del Paywall Builder heredado. El [temporizador de cuenta atrás](flow-timer) de un flow se rige por la configuración del Flow & Paywall Builder, por lo que un flow ignora lo que pases aquí. ::: ```diff showLineNumbers - AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { + AdaptyUI.CreateFlowView(flow, parameters, (view, error) => { if (error != null) { // handle the error return; } view.Present((error) => { /* handle the error */ }); }); ``` `CreateFlowView` devuelve un error si el flow no tiene ninguna vista configurada — esto reemplaza la comprobación `HasViewConfiguration` de v3: ```diff showLineNumbers - if (paywall.HasViewConfiguration) { - AdaptyUI.CreatePaywallView(paywall, null, (view, error) => { /* ... */ }); - } + AdaptyUI.CreateFlowView(flow, (view, error) => { + if (error != null) { + // the flow has no view configured, or view creation failed + return; + } + view.Present((error) => { /* handle the error */ }); + }); ``` :::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 presentar el flow otra vez. ::: ### Rellenos de área segura en Android \{#android-safe-area-paddings\} `AdaptyUICreateFlowViewParameters` añade `EnableSafeAreaPaddings`, que controla los rellenos de área segura en Android en tiempo de ejecución. Se ignora en iOS y su valor predeterminado es `true`: ```csharp showLineNumbers var parameters = new AdaptyUICreateFlowViewParameters() .SetEnableSafeAreaPaddings(false); ``` ## Manejo de eventos \{#handling-events\} Las interfaces de listener siguen ahora la convención de prefijo `I` de C#, y no se mantienen alias heredados; renombra `AdaptyEventListener` a `IAdaptyEventListener` y `AdaptyOnboardingsEventsListener` a `IAdaptyOnboardingsEventsListener` en todos los lugares donde las implementes. El listener de eventos de flow se renombra de `AdaptyPaywallsEventsListener` a `IAdaptyFlowsEventsListener`, su método de registro de `SetPaywallsEventsListener` a `SetFlowsEventsListener`, y sus callbacks cambian el prefijo `PaywallView` por `FlowView`. Los cuerpos de los handlers existentes no necesitan cambios de código: solo hay que renombrar la interfaz y los métodos: ```diff showLineNumbers - public class MyListener : MonoBehaviour, AdaptyPaywallsEventsListener { - public void PaywallViewDidFinishPurchase( - AdaptyUIPaywallView view, + public class MyListener : MonoBehaviour, IAdaptyFlowsEventsListener { + public void FlowViewDidFinishPurchase( + AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult ) { // custom logic after purchase } // ... } - Adapty.SetPaywallsEventsListener(myListener); + Adapty.SetFlowsEventsListener(myListener); ``` Un callback ha sido renombrado: `PaywallViewDidFailRendering` pasa a llamarse `FlowViewDidReceiveError`. Se activa para los mismos errores de renderizado que antes, además de otros errores de ejecución no relacionados con compras: ```diff showLineNumbers - public void PaywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { } + public void FlowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { } ``` Consulta [Gestionar eventos de flow y paywall](unity-handling-events) para ver la lista completa de callbacks. ### Nuevo método obligatorio: OnReceivePromotedPurchase \{#new-required-method-onreceivepromotedpurchase\} A partir de la versión 4.1 del SDK, `IAdaptyEventListener` tiene un método adicional, por lo que cualquier clase que lo implemente dejará de compilar hasta que añadas: ```csharp showLineNumbers public void OnReceivePromotedPurchase(AdaptyPromotedProduct product) { // The user tapped one of your in-app purchases on your App Store product page. // Complete the purchase through Adapty: Adapty.MakePromotedPurchase(product, (result, error) => { /* ... */ }); } ``` Este método es para compras in-app promocionadas en el App Store y nunca se llama en Android. No dejes el cuerpo vacío: en iOS 16.4 y versiones posteriores, el SDK entrega la compra aquí y espera a que la completes, por lo que un cuerpo vacío descarta una compra que el usuario ya había iniciado. Las versiones anteriores completaban este tipo de compras automáticamente. Consulta [Compras in-app promocionadas desde el App Store](unity-making-purchases#in-app-purchases-from-the-app-store). ### Nuevas APIs \{#new-apis\} - `Adapty.SetObserverModeResolver(...)` con un `IAdaptyUIObserverModeResolver` — gestiona compras y restauraciones iniciadas desde flows cuando el SDK funciona en [modo Observer](implement-observer-mode-unity). Antes esto solo estaba disponible en los SDKs nativos de iOS y Android. Consulta [Presentar flows en modo Observer](unity-present-flows-in-observer-mode). - `Adapty.SetSystemRequestsHandler(...)` con un `IAdaptyUISystemRequestsHandler` — reservado para solicitudes del sistema desde un flow: prompts de permisos del SO (`FlowViewDidAskPermission`) y solicitudes de valoración de la app (`FlowViewDidRequestAppReview`). Los flows aún no activan estas solicitudes, por lo que no necesitas registrar un handler. - `AdaptyUICreateFlowViewParameters.Locale` (configúralo con `SetLocale`) — renderiza un flow o paywall con una [localización del Builder](add-paywall-locale-in-adapty-paywall-builder) específica en lugar de la predeterminada del flow. Un flow se localiza cuando se crea su vista, así que este es el único lugar donde elegir su localización; la vista creada reporta la localización con la que fue construida en `view.Locale`. Consulta [Usar localizaciones y códigos de idioma](unity-localizations-and-locale-codes). - El nuevo callback `FlowViewDidReceiveAnalyticEvent` en `IAdaptyFlowsEventsListener` reporta eventos de análisis desde un flow, empezando por una vista de pantalla para cada pantalla que abre el usuario. Consulta [Rastrear vistas de pantalla de flows](unity-flow-screen-views). - `AdaptyUI.OpenUrl(url, openIn, ...)` y `AdaptyUI.RequestAppReview(...)` — el comportamiento nativo detrás de las acciones `open_url` y las solicitudes de valoración de la app. Llama a `OpenUrl` desde `FlowViewDidPerformAction` para mantener el comportamiento predeterminado de URLs; `RequestAppReview` respalda el prompt de valoración predeterminado, que los flows aún no activan. ## APIs de atribución externa renombradas \{#renamed-external-attribution-apis\} A partir de la versión 4.1 del SDK, las APIs para pasar datos de atribución desde un proveedor externo (Adjust, AppsFlyer, Branch, Tenjin o uno personalizado) se han renombrado para coincidir con los SDKs nativos, y el proveedor pasa de ser una cadena de texto a un tipo. No existen alias obsoletos, por lo que los puntos de llamada existentes dejan de compilar hasta que los actualices: | Antes de 4.1 | 4.1 | |---|---| | `Adapty.UpdateAttribution(data, source, ...)` | `Adapty.UpdateExternalAttribution(jsonString, provider, ...)` | | `source` como `string` | `provider` como `AdaptyExternalAttributionProvider` | | `AdaptyProfile.AppliedAttributionSources` como `IReadOnlyList<string>` | `AdaptyProfile.AppliedExternalAttributionProviders` como `IReadOnlyList<AdaptyExternalAttributionProvider>` | Renombrar el método no es suficiente — cambia también el argumento del proveedor en la misma edición: ```diff showLineNumbers - Adapty.UpdateAttribution(attributionJsonString, "adjust", (error) => { /* ... */ }); + Adapty.UpdateExternalAttribution(attributionJsonString, AdaptyExternalAttributionProvider.Adjust, (error) => { /* ... */ }); ``` `AdaptyExternalAttributionProvider` lleva el identificador con el que el backend reconoce al proveedor, con seis instancias compartidas: `AppleAds` (`apple_search_ads`), `Adjust`, `Appsflyer`, `Branch`, `Tenjin` y `Custom`. Para un proveedor que Adapty añada después de esta versión del SDK, constrúyelo a partir de su identificador — `new AdaptyExternalAttributionProvider("your_provider")` — y llegará al backend sin cambios. Los espacios en blanco alrededor se eliminan automáticamente. Los datos de atribución se envían como una **cadena JSON serializada**. Si los tienes en forma de diccionario, serialízalos primero: ```csharp showLineNumbers var attributionJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(attribution); ``` En el lado del perfil, lee los proveedores aplicados a través del nuevo tipo: ```diff showLineNumbers - if (profile.AppliedAttributionSources.Contains("apple_search_ads")) { + if (profile.AppliedExternalAttributionProviders.Contains(AdaptyExternalAttributionProvider.AppleAds)) { // Apple Ads attribution has been applied } ``` ## La atribución de Adapty está desactivada por defecto \{#adapty-attribution-is-disabled-by-default\} :::warning Si usas [Adapty Attribution](user-acquisition) y actualizas al SDK 4.1 sin activar esta opción, fallará de forma silenciosa: las instalaciones dejan de registrarse y no recibirás ningún aviso. ::: En versiones anteriores, el SDK registraba las instalaciones para [Adapty Attribution](user-acquisition) de forma automática. A partir de la versión 4.1 del SDK, esto está desactivado por defecto: el SDK no registra instalaciones, los callbacks `OnInstallationDetailsSuccess` y `OnInstallationDetailsFail` nunca se ejecutan, y `GetCurrentInstallationStatus` devuelve el estado `NotAvailable`. Si usas Adapty Attribution, actívalo al inicializar el SDK: ```csharp showLineNumbers var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAdaptyAttributionEnabled(true); ``` Si no usas Adapty Attribution, no necesitas hacer ningún cambio. ## Archivos de respaldo \{#fallback-files\} El formato del [archivo de respaldo](unity-use-fallback-paywalls) cambió en el SDK 4.1. Descarga de nuevo los archivos de respaldo de iOS y Android desde **[Placements](https://app.adapty.io/placements)** > **Fallbacks** y reemplaza los que tengas en `Assets/StreamingAssets`, aunque ya los hayas descargado para una versión anterior. :::warning Este paso no genera ningún error de compilación. Si lo omites, `SetFallback` reportará `DecodingFailed` (`adapty_code: 2006`) y cada placement perderá su paywall de respaldo. ::: ## Cambios en el comportamiento predeterminado \{#default-behavior-changes\} Estos cambios no causan errores de compilación, así que compruébalos en tiempo de ejecución: - **Finalización de la compra**: En v3, la vista se cerraba automáticamente tras una compra exitosa. En v4, **un flow permanece abierto tras una compra o un error hasta que lo cierres tú** — el SDK no aplica ningún comportamiento por defecto. Llama a `view.Dismiss(...)` en `FlowViewDidFinishPurchase` una vez que el usuario obtenga acceso. - **Botón Atrás de Android**: El botón Atrás del sistema (o el gesto de retroceso) se entrega a `FlowViewDidPerformAction` como una acción `SystemBack` y 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 al gestionar la acción. - **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. - **Transacciones en modo Observer**: `ReportTransaction` ya no devuelve un error de decodificación cuando tiene éxito — en v3 la respuesta de éxito se parseaba incorrectamente, por lo que un reporte exitoso siempre terminaba con un error. ## Obsolescencia de la API de onboarding \{#onboarding-api-deprecation\} La API de onboarding heredada está en desuso en v4 en favor del [Flow & Paywall 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 & Paywall Builder. Símbolos en desuso: `GetOnboarding`, `GetOnboardingForDefaultAudience`, `AdaptyUI.CreateOnboardingView`, `AdaptyUI.PresentOnboardingView`, `AdaptyUI.DismissOnboardingView` y `Adapty.SetOnboardingsEventsListener`. --- # 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#configure-your-app-code). ```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-10-30T13:00:40.023Z_ _Successfully processed: 56/56 files_