← Volver al listado de tecnologías

Capítulo 2: Inicialización y Configuración Básica

Por: Artiko
appsflyerreact-nativeinicializaciónpermisosATTprivacy

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:

📱 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

🎯 Ejercicios Prácticos

Ejercicio 1: ATT Flow

Implementa un flujo completo de ATT que:

  1. Muestre una pantalla explicativa pre-prompt
  2. Solicite el permiso de ATT
  3. Maneje todos los estados posibles
  4. Inicialice AppsFlyer según el resultado

Ejercicio 2: Multi-Usuario

Implementa el cambio de usuario:

  1. Logout del usuario actual
  2. Limpiar Customer User ID
  3. Login con nuevo usuario
  4. Configurar nuevo Customer User ID
  5. Verificar que los eventos se atribuyan correctamente

Ejercicio 3: GDPR Compliance

Crea un modal de consentimiento que:

  1. Explique qué datos se recopilan
  2. Permita opt-in/opt-out granular
  3. Guarde las preferencias
  4. Configure AppsFlyer según las preferencias

📚 Resumen

En este capítulo aprendiste:

🚀 Próximo Capítulo

En el siguiente capítulo exploraremos el Tracking de Instalaciones, incluyendo:


← Capítulo 1: Instalación | Siguiente: Tracking de Instalaciones →