SouthGames Flutter SDK

pub package License: MIT

Flutter SDK para integrar gamificación y loyalty de SouthGames en tu app.

Funcionalidades

  • Registro de clientes — upsert por externalId con custom params y device tokens.
  • Idioma automático — detecta el locale del dispositivo (ej: es-CL) y lo envía al registrar.
  • Identificación automatica — envía y refresca el device token en cada inicio de app.
  • Campañas activas — listado de campañas con configuración de juego.
  • 4 tipos de juego — spin wheel, scratch card, trivia y slot machine.
  • Juegos del marketplace — carga juegos HTML5 en WebView con SouthGamesGameView.
  • Códigos promocionales — generación automatica al ganar + canje.
  • Sistema de puntos — consultar saldo, historial, gastar y ganar puntos canjeables.
  • Notificaciónes in-app — filtradas por segment rules del cliente.
  • Push notifications — registro de device tokens con cualquier proveedor (FCM, APNs, etc.).
  • Eventos personalizados — trackeo de eventos con propiedades y notificaciones triggered.

Instalación

Agrega southgames_flutter a tu pubspec.yaml:

dependencies:
  southgames_flutter: ^0.6.3

Luego ejecuta:

flutter pub get

Inicio rápido

import 'package:southgames_flutter/southgames_flutter.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';

@pragma('vm:entry-point')
Future<void> _firebaseBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  await SouthGamesSDK.handleBackgroundMessage(message);
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  FirebaseMessaging.onBackgroundMessage(_firebaseBackgroundHandler);

  // 1. Inicializar el SDK (una sola vez)
  await SouthGamesSDK.init(
    apiKey: 'sg_live_xxxxxxxxxxxxxxxx',
    orgId: 'tu-org-slug',
  );

  runApp(const MyApp());
}
// 2. Desde cualquier parte de la app, usar SouthGamesSDK.instance
final sdk = SouthGamesSDK.instance;

// Identificar al usuario
final res = await sdk.identify(
  externalId: 'user_123',
  email: 'usuario@app.com',
  customParams: {'plan': 'premium'},
);
print('Client ID: ${res.clientId}');

// Obtener campañas activas
final campaigns = await sdk.getCampaigns();

// Jugar
final play = await sdk.play(
  campaignId: campaigns.first.id,
  externalUserId: 'user_123',
);

if (play.won && play.prizeCode != null) {
  print('Ganaste! Código: ${play.prizeCode}');
}

// Canjear un código
final redeem = await sdk.redeem(code: play.prizeCode!);
print('Descuento: ${redeem.discountValue}%');

Uso

Inicialización

Llama a SouthGamesSDK.init() una vez antes de usar cualquier otro método. Generalmente en main().

await SouthGamesSDK.init(
  apiKey: 'sg_live_xxxxxxxxxxxxxxxx',
  orgId: 'tu-org-slug',
  pushConfig: PushConfig(
    requestPermissionOnInit: true,
    showForegroundNotifications: true,
    onNotificationTap: (message) {
      debugPrint('Notification tapped: ${message.data}');
    },
  ),
  onCtaTap: (action) {
    debugPrint('CTA tapped: $action');
  },
);

Después de init(), accede al SDK desde cualquier parte de la app:

final sdk = SouthGamesSDK.instance;

O verifica si fue inicializado:

if (SouthGamesSDK.isInitialized) {
  // usar sdk
}

Nota: Si usas el SDK sin llamar a init(), se lanza SouthGamesNotInitializedException.

Identificación de usuario

identify() registra o actualiza al usuario y envía el device token automáticamente. El idioma del dispositivo (ej: es-CL) se detecta y envía automáticamente. Llámalo en cada inicio de app una vez que conozcas la identidad del usuario.

final sdk = SouthGamesSDK.instance;

final res = await sdk.identify(
  externalId: 'user_123',       // ID del usuario en tu plataforma
  email: 'user@example.com',    // opciónal
  firstName: 'Juan',            // opciónal
  lastName: 'Perez',            // opciónal
  phone: '+56912345678',        // opciónal
  latitude: -33.45,             // opciónal
  longitude: -70.66,            // opciónal
  customParams: {'tier': 'gold'}, // opciónal
);

print(res.clientId);        // ID único del cliente en SouthGames
print(res.created);         // true si es nuevo, false si se actualizó
print(res.deviceTokenSent); // true si el device token fue envíado

Debug: El SDK imprime logs con el prefijo [SouthGames] en la consola de debug.

Registro manual de cliente

Si necesitas más control, puedes usar registerClient() directamente:

final res = await SouthGamesSDK.instance.registerClient(
  externalId: 'user_123',
  email: 'user@example.com',
  deviceToken: 'fcm_or_apns_token', // opciónal
);

Heartbeat

Actualiza lastSeenAt del cliente. Útil para trackear actividad.

await SouthGamesSDK.instance.heartbeat(clientId: 'client_id');

Campañas activas

Retorna las campañas con status == "active" dentro de su rango de fechas.

final campaigns = await SouthGamesSDK.instance.getCampaigns();

for (final c in campaigns) {
  print('${c.name} (${c.gameType})');
}

Jugar un juego

final sdk = SouthGamesSDK.instance;

// Spin wheel / scratch card / slot machine
final play = await sdk.play(
  campaignId: 'campaign_id',
  externalUserId: 'user_123',
);

// Trivia (requiere respuestas)
final triviaPlay = await sdk.play(
  campaignId: 'trivia_campaign_id',
  externalUserId: 'user_123',
  answers: [0, 2, 1, 3], // índice de la opción elegida por pregunta
);

print(play.won);        // true si ganó
print(play.prizeCode);  // código promo si ganó, null si no
print(play.result);     // descripción del resultado
print(play.metadata);   // metadata especifica del juego

Juegos del marketplace (WebView)

Para campañas con juegos del marketplace, usa SouthGamesGameView:

if (campaign.isMarketplaceGame) {
  Navigator.push(context, MaterialPageRoute(
    builder: (_) => SouthGamesGameView(
      campaign: campaign,
      externalUserId: 'user_123',
      onResult: (result) {
        print(result.won ? 'Ganaste!' : 'No ganaste');
        Navigator.pop(context);
      },
      onClose: () => Navigator.pop(context),
      onError: (error) => print('Error: $error'),
    ),
  ));
}

Eventos personalizados

Trackea eventos y recibe notificaciones triggered automáticamente.

final result = await SouthGamesSDK.instance.trackEvent(
  eventName: 'purchase_completed',
  properties: {'amount': 9990, 'product': 'premium'},
);

print('Evento: ${result.eventName}');
if (result.triggeredNotifications.isNotEmpty) {
  print('${result.triggeredNotifications.length} notificación(es) triggered');
}

Canjear código promocional

final redeem = await SouthGamesSDK.instance.redeem(
  code: 'SG-AB2C-XY9Z',
  externalUserId: 'user_123', // opciónal
);

print(redeem.success);        // true
print(redeem.discountType);   // 'percentage' | 'fixed'
print(redeem.discountValue);  // ej: 20
print(redeem.remainingUses);  // usos restantes

Notificaciónes in-app

Las notificaciones in-app se muestran automáticamente después de init(). No necesitas agregar ningún widget.

Para obtener las notificaciones manualmente:

final notifications = await SouthGamesSDK.instance.getInAppNotifications();

for (final n in notifications) {
  print('${n.title} — ${n.body}');
  if (n.cta != null) {
    print('CTA: ${n.cta!.label} -> ${n.cta!.action}');
  }
}

Si prefieres controlar el rendering con un widget:

MaterialApp(
  builder: (context, child) => SouthGamesNotificationOverlay(
    child: child!,
    onCtaTap: (action) => print('CTA: $action'),
  ),
)

Puntos

Consulta el saldo, historial, gasta y gana puntos canjeables.

final sdk = SouthGamesSDK.instance;

// Consultar saldo
final balance = await sdk.getPointsBalance(
  externalUserId: 'user_123',
);
print('${balance.pointsName}: ${balance.pointsBalance}');
print('Ganados: ${balance.pointsEarned}');
print('Gastados: ${balance.pointsSpent}');

// Historial de puntos
final history = await sdk.getPointsHistory(
  externalUserId: 'user_123',
  limit: 10,
);
for (final entry in history) {
  print('${entry.type}: ${entry.amount} — ${entry.reason}');
}

// Gastar puntos
try {
  final spend = await sdk.spendPoints(
    externalUserId: 'user_123',
    amount: 100,
    reason: 'Canje por descuento',
  );
  print('Gastaste ${spend.pointsSpent} puntos. Saldo: ${spend.newBalance}');
} on SouthGamesException catch (e) {
  if (e.code == 'INSUFFICIENT_BALANCE') {
    print('Saldo insuficiente');
  }
}

// Ganar puntos manualmente
final earn = await sdk.earnPoints(
  externalUserId: 'user_123',
  action: 'custom',
  eventName: 'purchase_completed',
);
print('Ganaste ${earn.pointsAwarded} puntos. Saldo: ${earn.newBalance}');

Nota: Los puntos también se ganan automáticamente al jugar, ganar juegos y canjear códigos, si hay reglas de ganancia configuradas en el dashboard.

Atribución

Captura datos de atribución para medir de dónde vienen tus usuarios.

// Opción 1: Pasar directamente en identify()
final attr = Attribution(
  utmSource: 'meta',
  utmMedium: 'cpc',
  utmCampaign: 'black-friday-2026',
);

await SouthGamesSDK.instance.identify(
  externalId: 'user_123',
  attribution: attr,
);

// Opción 2: Parsear desde un deep link
final attr = Attribution.fromUri(deepLinkUri);
await SouthGamesSDK.instance.identify(
  externalId: 'user_123',
  attribution: attr,
);

// Opción 3: Guardar antes del identify (ej: desde Install Referrer)
SouthGamesSDK.instance.setAttribution(
  Attribution.fromMap(installReferrerParams),
);
// Se envía automáticamente en el próximo identify()

Nota: La atribución se limpia automáticamente después de enviarse.

Push notifications (manual)

Si necesitas registrar tokens manualmente:

await SouthGamesSDK.instance.push.registerDeviceToken(
  externalId: 'user_123',
  token: 'fcm_or_apns_token',
);

Logout

Desregistra el dispositivo y limpia todo el estado local.

await SouthGamesSDK.instance.logout(externalId: 'user_123');
// Después de logout, debes llamar init() e identify() de nuevo.

Limpieza

Libera los recursos del HTTP client cuando ya no necesites el SDK.

SouthGamesSDK.dispose();

Manejo de errores

Todas las llamadas pueden lanzar SouthGamesException:

try {
  await SouthGamesSDK.instance.redeem(code: 'INVALID');
} on SouthGamesException catch (e) {
  print(e.message);    // "Código no encontrado"
  print(e.code);       // "CODE_NOT_FOUND"
  print(e.statusCode); // 404
}

Códigos de error comunes

Código HTTP Descripción
MISSING_API_KEY 401 Falta API key
MISSING_ORG_ID 401 Falta org ID
INVALID_API_KEY 401 API key inválida o inactiva
API_KEY_EXPIRED 401 API key expirada
VALIDATION_ERROR 400 Datos de entrada inválidos
NOT_FOUND 404 Recurso no encontrado
CAMPAIGN_INACTIVE 400 Campaña no activa
CODE_NOT_FOUND 404 Código promo no existe
CODE_INACTIVE 400 Código desactivado
CODE_EXPIRED 400 Código expirado
CODE_MAX_USES_REACHED 400 Código sin usos restantes
INSUFFICIENT_BALANCE 400 Saldo de puntos insuficiente
POINTS_NOT_ENABLED 400 Sistema de puntos no habilitado

Modelos

Clase Descripción
RegisterResponse Resultado del registro (clientId, created, deviceTokenSent)
Campaign Campaña activa (id, name, gameType, config, fechas)
PlayResponse Resultado de jugar (sessionId, won, prizeCode, metadata)
RedeemResult Resultado de canje (discountType, discountValue, remainingUses)
InAppNotification Notificación in-app (title, body, type, position, cta, colores)
CtaButton Botón CTA de notificación (label, action)
TrackEventResult Resultado de trackear evento (eventName, triggeredNotifications)
PointsBalance Saldo de puntos (pointsBalance, pointsEarned, pointsSpent, pointsName)
PointsLedgerEntry Entrada del historial de puntos (type, amount, balance, reason)
SpendPointsResponse Resultado de gastar puntos (pointsSpent, newBalance)
EarnPointsResponse Resultado de ganar puntos (pointsAwarded, newBalance, totalEarned)
Attribution Datos de atribución (utmSource, utmMedium, utmCampaign, utmContent, utmTerm, linkId)

Requisitos

  • Dart SDK >=3.0.0 <4.0.0
  • Flutter >=3.10.0
  • Una cuenta en SouthGames con API key activa

Licencia

MIT — ver LICENSE.

Libraries

southgames_flutter
Flutter SDK for SouthGames gamification and loyalty platform.