← Volver al listado de tecnologías
Capítulo 2: Inicialización y Configuración Básica
Capítulo 2: Inicialización y Configuración Básica
En este capítulo profundizaremos en la inicialización correcta del SDK de AppsFlyer, incluyendo gestión de permisos, configuración de privacidad y mejores prácticas.
🎯 Objetivos del Capítulo
Al finalizar este capítulo serás capaz de:
- ✅ Inicializar AppsFlyer con configuración avanzada
- ✅ Gestionar permisos y ATT en iOS 14.5+
- ✅ Configurar User ID y sesiones
- ✅ Implementar opt-out y cumplimiento GDPR
- ✅ Manejar diferentes entornos (dev/staging/prod)
📱 Gestión de Permisos iOS 14.5+ (ATT)
App Tracking Transparency (ATT)
Desde iOS 14.5, necesitas solicitar permiso explícito para rastrear usuarios.
Paso 1: Configurar Info.plist
<!-- ios/YourApp/Info.plist -->
<key>NSUserTrackingUsageDescription</key>
<string>Esta app recopila datos para mejorar tu experiencia y mostrarte contenido relevante</string>
Paso 2: Instalar react-native-tracking-transparency
npm install react-native-tracking-transparency
cd ios && pod install
Paso 3: Implementar ATT
// ATTManager.js
import {
requestTrackingPermission,
getTrackingStatus
} from 'react-native-tracking-transparency';
import { Platform } from 'react-native';
class ATTManager {
async requestPermission() {
if (Platform.OS !== 'ios') {
return 'not-ios';
}
try {
const status = await getTrackingStatus();
if (status === 'not-determined') {
// Solicitar permiso
const newStatus = await requestTrackingPermission();
return newStatus;
}
return status;
} catch (error) {
console.error('Error solicitando ATT:', error);
return 'error';
}
}
async checkStatus() {
if (Platform.OS !== 'ios') {
return 'not-ios';
}
return await getTrackingStatus();
}
isTrackingAuthorized(status) {
return status === 'authorized';
}
}
export default new ATTManager();
🚀 Inicialización Avanzada
Configuración Completa del SDK
// AppsFlyerConfig.js
import appsFlyer from 'react-native-appsflyer';
import { Platform } from 'react-native';
import DeviceInfo from 'react-native-device-info';
import ATTManager from './ATTManager';
class AppsFlyerConfig {
constructor() {
this.initialized = false;
this.conversionData = null;
this.deepLinkData = null;
}
async initialize(config = {}) {
try {
// Solicitar ATT en iOS
let attStatus = 'not-applicable';
if (Platform.OS === 'ios') {
attStatus = await ATTManager.requestPermission();
console.log('ATT Status:', attStatus);
}
// Configuración base
const defaultConfig = {
devKey: process.env.APPSFLYER_DEV_KEY,
appId: Platform.OS === 'ios' ? process.env.APPSFLYER_IOS_APP_ID : undefined,
isDebug: __DEV__,
onInstallConversionDataListener: true,
onDeepLinkListener: true,
timeToWaitForATTUserAuthorization: 10,
};
// Merge con configuración personalizada
const initOptions = { ...defaultConfig, ...config };
// Configuraciones adicionales pre-inicialización
await this.configurePreInit();
// Inicializar SDK
return new Promise((resolve, reject) => {
appsFlyer.initSdk(
initOptions,
async (result) => {
console.log('✅ AppsFlyer inicializado:', result);
this.initialized = true;
// Configuraciones post-inicialización
await this.configurePostInit();
resolve({ success: true, result, attStatus });
},
(error) => {
console.error('❌ Error iniciando AppsFlyer:', error);
reject(error);
}
);
});
} catch (error) {
console.error('Error en inicialización:', error);
throw error;
}
}
async configurePreInit() {
// Configurar User ID si existe
const userId = await this.getUserId();
if (userId) {
appsFlyer.setCustomerUserId(userId);
console.log('User ID configurado:', userId);
}
// Configurar datos adicionales del usuario
await this.setAdditionalData();
// Configurar sharing filters si es necesario
if (Platform.OS === 'android') {
// Deshabilitar partners específicos si es necesario
// appsFlyer.setSharingFilterForPartners(['facebook_int']);
}
}
async configurePostInit() {
// Configurar listeners
this.setupConversionListener();
this.setupDeepLinkListener();
// Configurar sesión mínima (Android)
if (Platform.OS === 'android') {
appsFlyer.setMinTimeBetweenSessions(5); // 5 segundos
}
// Log de primera apertura
await this.logFirstOpen();
}
async getUserId() {
// Obtener ID de usuario de tu sistema de autenticación
// Por ejemplo, desde AsyncStorage, Redux, etc.
try {
// const userId = await AsyncStorage.getItem('userId');
// return userId;
return null; // Placeholder
} catch (error) {
console.error('Error obteniendo User ID:', error);
return null;
}
}
async setAdditionalData() {
const additionalData = {
app_version: DeviceInfo.getVersion(),
build_number: DeviceInfo.getBuildNumber(),
device_id: DeviceInfo.getUniqueId(),
device_model: DeviceInfo.getModel(),
os_version: DeviceInfo.getSystemVersion(),
};
appsFlyer.setAdditionalData(additionalData);
console.log('Datos adicionales configurados:', additionalData);
}
setupConversionListener() {
appsFlyer.onInstallConversionData((data) => {
console.log('📊 Datos de conversión recibidos:', data);
this.conversionData = data;
this.handleConversionData(data);
});
appsFlyer.onInstallConversionFailure((data) => {
console.error('❌ Error en conversión:', data);
});
}
setupDeepLinkListener() {
appsFlyer.onDeepLink((deepLinkData) => {
console.log('🔗 Deep Link recibido:', deepLinkData);
this.deepLinkData = deepLinkData;
this.handleDeepLink(deepLinkData);
});
}
handleConversionData(data) {
const {
media_source,
campaign,
is_first_launch,
af_status
} = data;
if (is_first_launch === true) {
if (af_status === 'Non-organic') {
console.log('Usuario de campaña:', media_source, campaign);
// Lógica para usuarios de campañas
this.trackCampaignUser(media_source, campaign);
} else {
console.log('Usuario orgánico');
// Lógica para usuarios orgánicos
this.trackOrganicUser();
}
} else {
console.log('No es primera instalación');
}
}
handleDeepLink(linkData) {
const {
deep_link_value,
deep_link_sub1,
campaign,
media_source
} = linkData;
console.log('Procesando deep link:', deep_link_value);
// Navegar según el deep link
// NavigationService.navigate(deep_link_value, { ...linkData });
}
async logFirstOpen() {
try {
const isFirstOpen = await this.checkFirstOpen();
if (isFirstOpen) {
appsFlyer.logEvent('first_open', {
timestamp: Date.now(),
source: 'organic'
});
await this.markFirstOpenComplete();
}
} catch (error) {
console.error('Error logging first open:', error);
}
}
async checkFirstOpen() {
// Implementar lógica para verificar si es primera apertura
// Por ejemplo, usando AsyncStorage
return false; // Placeholder
}
async markFirstOpenComplete() {
// Marcar que ya se registró la primera apertura
// await AsyncStorage.setItem('first_open_logged', 'true');
}
trackCampaignUser(mediaSource, campaign) {
appsFlyer.logEvent('campaign_user_identified', {
media_source: mediaSource,
campaign: campaign,
timestamp: Date.now()
});
}
trackOrganicUser() {
appsFlyer.logEvent('organic_user_identified', {
timestamp: Date.now()
});
}
}
export default new AppsFlyerConfig();
👤 Configuración de User ID
Customer User ID vs AppsFlyer ID
// UserIDManager.js
import appsFlyer from 'react-native-appsflyer';
import AsyncStorage from '@react-native-async-storage/async-storage';
class UserIDManager {
// Configurar Customer User ID (tu ID interno)
async setCustomerUserId(userId) {
try {
appsFlyer.setCustomerUserId(userId);
await AsyncStorage.setItem('customer_user_id', userId);
console.log('Customer User ID configurado:', userId);
// Log evento de login
appsFlyer.logEvent('af_login', {
af_customer_user_id: userId,
login_method: 'email'
});
} catch (error) {
console.error('Error configurando User ID:', error);
}
}
// Obtener AppsFlyer ID (único de AppsFlyer)
async getAppsFlyerId() {
return new Promise((resolve) => {
appsFlyer.getAppsFlyerUID((error, uid) => {
if (error) {
console.error('Error obteniendo AppsFlyer ID:', error);
resolve(null);
} else {
console.log('AppsFlyer ID:', uid);
resolve(uid);
}
});
});
}
// Limpiar User ID (logout)
async clearCustomerUserId() {
appsFlyer.setCustomerUserId('');
await AsyncStorage.removeItem('customer_user_id');
console.log('Customer User ID limpiado');
}
// Vincular IDs para cross-platform
async linkUserIds(email, phoneNumber) {
const emails = [appsFlyer.EmailCryptTypeNone + ':' + email];
const phones = [appsFlyer.PhoneCryptTypeNone + ':' + phoneNumber];
appsFlyer.setUserEmails(emails);
appsFlyer.setPhoneNumber(phones);
console.log('IDs vinculados para cross-platform');
}
}
export default new UserIDManager();
🔒 Privacidad y Cumplimiento GDPR
Implementar Opt-Out
// PrivacyManager.js
import appsFlyer from 'react-native-appsflyer';
import AsyncStorage from '@react-native-async-storage/async-storage';
class PrivacyManager {
constructor() {
this.PRIVACY_KEY = 'appsflyer_privacy_settings';
}
// Opt-out completo (detiene todo el tracking)
async optOut(shouldOptOut = true) {
try {
appsFlyer.stop(shouldOptOut);
await AsyncStorage.setItem(this.PRIVACY_KEY, JSON.stringify({
optOut: shouldOptOut,
timestamp: Date.now()
}));
console.log(`Usuario ${shouldOptOut ? 'opted out' : 'opted in'}`);
if (!shouldOptOut) {
// Si opta in de nuevo, reiniciar tracking
appsFlyer.start();
}
} catch (error) {
console.error('Error en opt-out:', error);
}
}
// Anonimizar datos del usuario
async anonymizeUser() {
try {
appsFlyer.anonymizeUser(true);
console.log('Usuario anonimizado');
// Log evento de anonimización
appsFlyer.logEvent('user_anonymized', {
timestamp: Date.now()
});
} catch (error) {
console.error('Error anonimizando usuario:', error);
}
}
// Deshabilitar recolección de ciertos datos
async configureDataCollection(options = {}) {
const {
collectIMEI = false,
collectAndroidID = false,
collectOAID = false
} = options;
if (Platform.OS === 'android') {
appsFlyer.setCollectIMEI(collectIMEI);
appsFlyer.setCollectAndroidID(collectAndroidID);
appsFlyer.setCollectOAID(collectOAID);
}
console.log('Configuración de recolección actualizada:', options);
}
// Verificar estado de privacidad
async getPrivacyStatus() {
try {
const settings = await AsyncStorage.getItem(this.PRIVACY_KEY);
return settings ? JSON.parse(settings) : { optOut: false };
} catch (error) {
console.error('Error obteniendo estado de privacidad:', error);
return { optOut: false };
}
}
// Solicitar consentimiento GDPR
async requestGDPRConsent() {
return new Promise((resolve) => {
// Aquí implementarías tu UI de consentimiento
// Por ejemplo, mostrar un modal
// Simulación de consentimiento
setTimeout(() => {
const consent = {
analytics: true,
marketing: true,
personalization: true,
timestamp: Date.now()
};
this.saveGDPRConsent(consent);
resolve(consent);
}, 100);
});
}
async saveGDPRConsent(consent) {
await AsyncStorage.setItem('gdpr_consent', JSON.stringify(consent));
// Configurar AppsFlyer según el consentimiento
if (!consent.analytics || !consent.marketing) {
await this.optOut(true);
}
console.log('Consentimiento GDPR guardado:', consent);
}
}
export default new PrivacyManager();
🌍 Configuración Multi-Entorno
Gestión de Diferentes Entornos
// EnvironmentConfig.js
const ENV = {
development: {
devKey: 'DEV_KEY_DESARROLLO',
appId: 'id123456789',
isDebug: true,
host: 'https://dev-api.tuapp.com',
oneLink: 'https://tuapp-dev.onelink.me'
},
staging: {
devKey: 'DEV_KEY_STAGING',
appId: 'id123456789',
isDebug: true,
host: 'https://staging-api.tuapp.com',
oneLink: 'https://tuapp-staging.onelink.me'
},
production: {
devKey: 'DEV_KEY_PRODUCCION',
appId: 'id123456789',
isDebug: false,
host: 'https://api.tuapp.com',
oneLink: 'https://tuapp.onelink.me'
}
};
const getEnvironment = () => {
if (__DEV__) {
return 'development';
}
// Puedes usar react-native-config para mejor gestión
// return Config.ENVIRONMENT || 'production';
return 'production';
};
export default ENV[getEnvironment()];
Inicialización con Multi-Entorno
// App.js
import React, { useEffect, useState } from 'react';
import { View, Text, Alert } from 'react-native';
import AppsFlyerConfig from './AppsFlyerConfig';
import EnvironmentConfig from './EnvironmentConfig';
import PrivacyManager from './PrivacyManager';
import UserIDManager from './UserIDManager';
const App = () => {
const [initStatus, setInitStatus] = useState('pending');
useEffect(() => {
initializeAppsFlyer();
}, []);
const initializeAppsFlyer = async () => {
try {
// Verificar consentimiento GDPR
const gdprConsent = await PrivacyManager.requestGDPRConsent();
if (!gdprConsent.analytics) {
console.log('Usuario no dio consentimiento para analytics');
setInitStatus('no-consent');
return;
}
// Configurar con entorno correcto
const config = {
devKey: EnvironmentConfig.devKey,
appId: EnvironmentConfig.appId,
isDebug: EnvironmentConfig.isDebug,
};
// Inicializar
const result = await AppsFlyerConfig.initialize(config);
// Configurar User ID si el usuario está logueado
const userId = await getUserIdFromAuth();
if (userId) {
await UserIDManager.setCustomerUserId(userId);
}
// Obtener AppsFlyer ID para referencia
const afId = await UserIDManager.getAppsFlyerId();
console.log('AppsFlyer ID:', afId);
setInitStatus('success');
// Log evento de app abierta
logAppOpen();
} catch (error) {
console.error('Error inicializando:', error);
setInitStatus('error');
Alert.alert('Error', 'No se pudo inicializar el tracking');
}
};
const getUserIdFromAuth = async () => {
// Obtener de tu sistema de autenticación
return 'user_123'; // Placeholder
};
const logAppOpen = () => {
appsFlyer.logEvent('app_open', {
environment: getEnvironment(),
timestamp: Date.now(),
session_id: generateSessionId()
});
};
const generateSessionId = () => {
return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
};
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<Text style={{ fontSize: 24, marginBottom: 20 }}>
AppsFlyer Multi-Entorno
</Text>
<Text>Entorno: {getEnvironment()}</Text>
<Text>Estado: {initStatus}</Text>
{/* Tu contenido de app */}
</View>
);
};
export default App;
🔧 Configuraciones Especiales
Sesiones y Background Tracking
// SessionManager.js
import appsFlyer from 'react-native-appsflyer';
import { AppState } from 'react-native';
class SessionManager {
constructor() {
this.appState = AppState.currentState;
this.sessionStartTime = null;
}
init() {
AppState.addEventListener('change', this.handleAppStateChange);
this.startSession();
}
handleAppStateChange = (nextAppState) => {
if (this.appState.match(/inactive|background/) && nextAppState === 'active') {
console.log('App volvió al foreground');
this.onAppResume();
} else if (this.appState === 'active' && nextAppState.match(/inactive|background/)) {
console.log('App fue al background');
this.onAppPause();
}
this.appState = nextAppState;
};
startSession() {
this.sessionStartTime = Date.now();
// Configurar tiempo mínimo entre sesiones (Android)
if (Platform.OS === 'android') {
appsFlyer.setMinTimeBetweenSessions(8); // 8 segundos
}
appsFlyer.logEvent('session_start', {
timestamp: this.sessionStartTime
});
}
onAppResume() {
// Reanudar tracking si estaba pausado
appsFlyer.start();
this.startSession();
}
onAppPause() {
if (this.sessionStartTime) {
const sessionDuration = Date.now() - this.sessionStartTime;
appsFlyer.logEvent('session_end', {
duration: Math.floor(sessionDuration / 1000), // en segundos
timestamp: Date.now()
});
}
}
destroy() {
AppState.removeEventListener('change', this.handleAppStateChange);
}
}
export default new SessionManager();
Currency y Location
// ConfigurationExtras.js
import appsFlyer from 'react-native-appsflyer';
import Geolocation from '@react-native-community/geolocation';
class ConfigurationExtras {
// Configurar moneda global
setCurrency(currencyCode = 'USD') {
appsFlyer.setCurrencyCode(currencyCode);
console.log('Moneda configurada:', currencyCode);
}
// Enviar ubicación (si tienes permisos)
async sendLocation() {
return new Promise((resolve, reject) => {
Geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
appsFlyer.logEvent('location_shared', {
lat: latitude,
lng: longitude,
timestamp: Date.now()
});
console.log('Ubicación enviada:', latitude, longitude);
resolve({ latitude, longitude });
},
(error) => {
console.error('Error obteniendo ubicación:', error);
reject(error);
},
{
enableHighAccuracy: false,
timeout: 20000,
maximumAge: 1000
}
);
});
}
// Configurar host personalizado (para proxies o regiones específicas)
setCustomHost(host, prefixHost = '') {
appsFlyer.setHost(prefixHost, host);
console.log('Host personalizado configurado:', host);
}
// Habilitar/deshabilitar logs verbose
setVerboseLogging(enabled = true) {
if (__DEV__) {
appsFlyer.setIsDebug(enabled);
console.log(`Verbose logging ${enabled ? 'habilitado' : 'deshabilitado'}`);
}
}
}
export default new ConfigurationExtras();
🧪 Testing de Inicialización
Test Suite
// __tests__/AppsFlyerInit.test.js
import AppsFlyerConfig from '../AppsFlyerConfig';
import appsFlyer from 'react-native-appsflyer';
jest.mock('react-native-appsflyer');
describe('AppsFlyer Initialization', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('debe inicializar con configuración correcta', async () => {
const mockConfig = {
devKey: 'TEST_KEY',
appId: 'id123',
isDebug: true
};
appsFlyer.initSdk.mockImplementation((config, success) => {
success({ status: 'success' });
});
await AppsFlyerConfig.initialize(mockConfig);
expect(appsFlyer.initSdk).toHaveBeenCalledWith(
expect.objectContaining(mockConfig),
expect.any(Function),
expect.any(Function)
);
});
test('debe manejar errores de inicialización', async () => {
appsFlyer.initSdk.mockImplementation((config, success, error) => {
error({ error: 'Init failed' });
});
await expect(AppsFlyerConfig.initialize()).rejects.toThrow();
});
});
📋 Checklist de Inicialización
- ATT configurado y solicitado (iOS 14.5+)
- Customer User ID configurado
- Consentimiento GDPR implementado
- Multi-entorno configurado
- Sesiones configuradas correctamente
- Listeners de conversión activos
- Deep link listeners configurados
- Privacy settings implementados
- Modo debug activo en desarrollo
- Tests de inicialización pasando
🎯 Ejercicios Prácticos
Ejercicio 1: ATT Flow
Implementa un flujo completo de ATT que:
- Muestre una pantalla explicativa pre-prompt
- Solicite el permiso de ATT
- Maneje todos los estados posibles
- Inicialice AppsFlyer según el resultado
Ejercicio 2: Multi-Usuario
Implementa el cambio de usuario:
- Logout del usuario actual
- Limpiar Customer User ID
- Login con nuevo usuario
- Configurar nuevo Customer User ID
- Verificar que los eventos se atribuyan correctamente
Ejercicio 3: GDPR Compliance
Crea un modal de consentimiento que:
- Explique qué datos se recopilan
- Permita opt-in/opt-out granular
- Guarde las preferencias
- Configure AppsFlyer según las preferencias
📚 Resumen
En este capítulo aprendiste:
- ✅ Gestión de ATT en iOS 14.5+
- ✅ Configuración avanzada del SDK
- ✅ Manejo de Customer User ID
- ✅ Implementación de privacidad y GDPR
- ✅ Configuración multi-entorno
- ✅ Gestión de sesiones y tracking
🚀 Próximo Capítulo
En el siguiente capítulo exploraremos el Tracking de Instalaciones, incluyendo:
- Atribución de primera instalación
- Diferencia entre orgánico y no-orgánico
- Datos de conversión
- Validación y testing
← Capítulo 1: Instalación | Siguiente: Tracking de Instalaciones →