Skip to content

Sistema de Gamificação - Sport Tech Club

1. Fundamentos de Neurociência

1.1 Motivação Intrínseca

Baseado na Teoria da Autodeterminação (Deci & Ryan):

Autonomia

  • Jogador escolhe quando jogar
  • Customização de perfil e avatar
  • Decisão sobre participar de desafios
  • Controle sobre privacidade de dados

Maestria

  • Sistema de progressão visível
  • Feedback de evolução técnica
  • Metas graduais e alcançáveis
  • Reconhecimento de melhoria

Pertencimento

  • Comunidade de jogadores
  • Times e grupos
  • Reconhecimento social
  • Conexões significativas

1.2 Sistema de Recompensa (Dopamina)

Ciclo de Dopamina Saudável

Antecipação → Ação → Recompensa → Consolidação
     ↑                                    ↓
     ←────────────────────────────────────

Triggers de Dopamina

  • XP ganho (recompensa imediata)
  • Level up (marco significativo)
  • Badge desbloqueado (conquista)
  • Vitória em partida (resultado)
  • Streak mantido (consistência)

1.3 Recompensas Variáveis

Schedule de Recompensas

  • Fixed Ratio: XP por partida (previsível)
  • Variable Ratio: Badges raros (imprevisível)
  • Fixed Interval: Desafios semanais
  • Variable Interval: Eventos surpresa

Loot Box Ético

  • Transparência de probabilidades
  • Sem pay-to-win
  • Apenas itens cosméticos
  • Sempre obtível por jogo

1.4 Redução de Fricção Cognitiva

Princípios de UX

  • Onboarding em 3 passos
  • Tutorial interativo
  • Defaults inteligentes
  • Ações com 1 clique
  • Informação progressiva

Flow State

  • Desafio balanceado com habilidade
  • Feedback imediato
  • Metas claras
  • Sem distrações

1.5 Feedback Rápido e Visual

Feedback Imediato

  • Animações de XP (+50 XP)
  • Progress bars animadas
  • Confetti em conquistas
  • Notificações push
  • Sound effects

Feedback Visual

typescript
interface FeedbackEvent {
  type: 'xp' | 'level' | 'badge' | 'streak';
  animation: 'pulse' | 'confetti' | 'glow';
  duration: number;
  sound?: string;
}

1.6 Social Proof

Elementos Sociais

  • "X jogadores online agora"
  • "Seus amigos jogaram Y partidas"
  • "Top 10% dos jogadores"
  • Testemunhos de conquistas
  • Feed de atividades

FOMO Positivo

  • "Desafio termina em 2h"
  • "Evento especial hoje"
  • Sem punição por perder

1.7 Microvitórias

Wins Frequentes

  • +10 XP por check-in
  • +25 XP por partida completa
  • +50 XP por vitória
  • Primeiro badge em 5min
  • Nível 2 em primeira sessão

Celebração Apropriada

typescript
const celebrationLevel = {
  micro: 'subtle_animation',      // +10 XP
  small: 'notification_toast',    // Badge comum
  medium: 'modal_celebration',    // Level up
  large: 'full_screen_confetti'   // Badge épico
};

1.8 Estímulo à Recorrência

Habit Building

  • Streak de dias consecutivos
  • Desafios diários (3 tarefas)
  • Recompensa de login diário
  • Notificações inteligentes

Optimal Timing

typescript
const notificationSchedule = {
  daily: '19:00',           // Horário típico de jogo
  weekly: 'Sexta 18:00',    // Fim de semana
  abandoned: '3 dias',       // Re-engagement
  achievement: 'instant'     // Celebração
};

1.9 Design para Dopamina Saudável

Evitar Dark Patterns

NÃO fazer:

  • Infinite scroll sem break
  • Recompensas manipulativas
  • Pay-to-win
  • FOMO punitivo
  • Fake scarcity
  • Shame por não jogar

FAZER:

  • Limites saudáveis sugeridos
  • Transparência total
  • Recompensas justas
  • FOMO positivo
  • Pausa sugerida após 2h
  • Encorajamento positivo

Health Metrics

typescript
interface HealthyEngagement {
  maxSessionTime: 120; // minutos
  suggestBreakAfter: 90;
  cooldownPeriod: 30;
  maxDailyNotifications: 3;
  respectDoNotDisturb: true;
}

2. Mecânicas de Gamificação

2.1 Sistema de XP (Experience Points)

Ganho de XP

AçãoXP BaseMultiplicadores
Check-in na arena10-
Partida completa25+50% se vitória
Vitória50+25% se streak
MVP da partida100-
Primeiro jogo do dia20Bonus diário
Desafio completado100-500Por dificuldade
Evento especial200+Variável
Streak de 7 dias500Semanal
Convidar amigo150Por aceite

Fórmula de XP

typescript
function calculateXP(action: Action, context: Context): number {
  let xp = BASE_XP[action.type];

  // Multiplicadores
  if (context.isVictory) xp *= 1.5;
  if (context.hasStreak) xp *= 1.25;
  if (context.isFirstToday) xp += 20;
  if (context.isMVP) xp *= 2;

  // Bônus de nível (reduz grind)
  if (context.playerLevel < 10) xp *= 1.5;

  return Math.floor(xp);
}

2.2 Sistema de Níveis

Progressão de Níveis

NívelXP TotalXP NecessárioTítuloBenefício
100Novato-
2100100InicianteAvatar frame bronze
3250150Aprendiz+1 desafio diário
5750500JogadorBadge customizado
1030002250ExperienteHighlight button
1575004500VeteranoPartida "valendo"
20150007500ExpertAvatar frame prata
303500020000MestreCriar torneios
406500030000CampeãoAvatar frame ouro
5010000035000LendaAvatar frame diamante

Curva de Progressão

typescript
function xpForLevel(level: number): number {
  // Curva logarítmica suavizada
  if (level <= 1) return 0;

  const base = 100;
  const exponent = 1.5;
  const smoothing = 0.85;

  return Math.floor(
    base * Math.pow(level, exponent) * smoothing
  );
}

function levelFromXP(totalXP: number): number {
  let level = 1;
  let accumulatedXP = 0;

  while (accumulatedXP <= totalXP) {
    level++;
    accumulatedXP += xpForLevel(level);
  }

  return level - 1;
}

Visualização de Progresso

typescript
interface LevelProgress {
  currentLevel: number;
  currentXP: number;
  xpForCurrentLevel: number;
  xpForNextLevel: number;
  progressPercent: number;
  title: string;
  nextTitle: string;
  estimatedMatchesToNext: number;
}

2.3 Sistema de Badges/Conquistas

Categorias de Badges

A) Participação

  • First Steps: Primeira partida
  • Regular: 10 partidas
  • Dedicated: 50 partidas
  • Hardcore: 100 partidas
  • Legend: 500 partidas
  • Marathon: 10 partidas em 1 dia
  • Weekend Warrior: 20 partidas em fim de semana

B) Performance

  • Hat Trick: 3 gols em 1 partida (futebol)
  • Triple Double: 10+ pontos, 10+ rebotes, 10+ assistências (basquete)
  • Perfect Game: Vitória sem sofrer pontos
  • Comeback King: Vitória após perder por 10+ pontos
  • Domination: Vitória por 20+ pontos
  • Sharpshooter: 80%+ de aproveitamento

C) Social

  • Team Player: 10 partidas com mesmo time
  • Ambassador: Convidar 5 amigos
  • Community Hero: 50 likes recebidos
  • Supporter: 100 check-ins como torcedor
  • Organizer: Criar 10 partidas

D) Streak

  • On Fire: 3 vitórias seguidas
  • Unstoppable: 5 vitórias seguidas
  • Legendary Streak: 10 vitórias seguidas
  • Dedicated: 7 dias consecutivos
  • Month Warrior: 30 dias consecutivos

E) Evolução

  • Rising Star: Level 10
  • Veteran: Level 25
  • Master: Level 50
  • First Win: Primeira vitória
  • Improvement: +50% win rate em 30 dias

F) Raros/Épicos

  • Unicorn: Vitória com score exato (111 pontos)
  • Perfect Week: 7 vitórias em 7 dias
  • Grand Slam: Ganhar torneio de 16+ jogadores
  • Hall of Fame: Top 10 ranking global
  • GOAT: Top 1 ranking global por 30 dias

Schema de Badge

typescript
interface Badge {
  id: string;
  name: string;
  description: string;
  category: 'participation' | 'performance' | 'social' | 'streak' | 'evolution' | 'rare';
  rarity: 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary';
  icon: string;
  xpReward: number;
  arenaCoinsReward?: number;

  // Condições
  requirements: Requirement[];

  // Metadata
  earnedBy: number; // Quantidade de jogadores
  earnedByPercent: number;
  firstEarnedBy?: string;
  firstEarnedAt?: Date;
}

interface Requirement {
  type: 'matches_played' | 'wins' | 'streak' | 'score' | 'level' | 'social';
  value: number;
  timeframe?: 'daily' | 'weekly' | 'monthly' | 'alltime';
}

2.4 Sistema de Rankings

Tipos de Rankings

typescript
enum RankingType {
  GLOBAL = 'global',
  SPORT = 'sport',           // Por modalidade
  ARENA = 'arena',           // Por local
  AGE_GROUP = 'age_group',   // Por faixa etária
  GENDER = 'gender',         // Por gênero
  LEVEL = 'level',           // Por nível similar
  FRIENDS = 'friends',       // Entre amigos
  TEAM = 'team'              // Times
}

enum RankingPeriod {
  DAILY = 'daily',
  WEEKLY = 'weekly',
  MONTHLY = 'monthly',
  SEASON = 'season',
  ALLTIME = 'alltime'
}

Cálculo de Rating

Baseado em Elo Rating adaptado:

typescript
interface PlayerRating {
  overall: number;        // Rating geral
  bySport: Map<string, number>;  // Por esporte
  wins: number;
  losses: number;
  winRate: number;
  averageScore: number;
  matchesPlayed: number;

  // Tendência
  trend: 'rising' | 'stable' | 'falling';
  trendPercent: number;

  // Ranking
  globalRank: number;
  sportRank: number;
  percentile: number;
}

function calculateNewRating(
  playerRating: number,
  opponentRating: number,
  result: 'win' | 'loss' | 'draw',
  kFactor: number = 32
): number {
  const expectedScore = 1 / (1 + Math.pow(10, (opponentRating - playerRating) / 400));

  const actualScore = result === 'win' ? 1 : result === 'loss' ? 0 : 0.5;

  const newRating = playerRating + kFactor * (actualScore - expectedScore);

  return Math.round(newRating);
}

Leaderboard UI

typescript
interface LeaderboardEntry {
  rank: number;
  previousRank: number;
  player: {
    id: string;
    name: string;
    avatar: string;
    level: number;
  };
  rating: number;
  ratingChange: number;
  wins: number;
  losses: number;
  winRate: number;
  badges: Badge[];
  isCurrentUser?: boolean;
}

interface Leaderboard {
  type: RankingType;
  period: RankingPeriod;
  sport?: string;

  topPlayers: LeaderboardEntry[];      // Top 10
  nearbyPlayers: LeaderboardEntry[];   // 5 acima + user + 5 abaixo
  userEntry: LeaderboardEntry;

  totalPlayers: number;
  lastUpdated: Date;
}

2.5 Sistema de Desafios

Tipos de Desafios

typescript
enum ChallengeType {
  DAILY = 'daily',           // Renovado diariamente
  WEEKLY = 'weekly',         // Renovado semanalmente
  SEASONAL = 'seasonal',     // Por temporada
  EVENT = 'event',           // Eventos especiais
  PERSONAL = 'personal',     // Baseado em histórico
  COMMUNITY = 'community'    // Desafio global
}

interface Challenge {
  id: string;
  type: ChallengeType;
  name: string;
  description: string;

  // Objetivos
  objectives: Objective[];

  // Recompensas
  xpReward: number;
  arenaCoinsReward: number;
  badgeReward?: string;

  // Timing
  startsAt: Date;
  endsAt: Date;

  // Progresso
  progress: number;
  progressMax: number;
  isCompleted: boolean;

  // Dificuldade
  difficulty: 'easy' | 'medium' | 'hard' | 'expert';

  // Social
  participantCount: number;
  completionRate: number;
}

interface Objective {
  id: string;
  description: string;
  type: 'play_matches' | 'win_matches' | 'score_points' | 'invite_friends' | 'check_in';
  target: number;
  current: number;
  isCompleted: boolean;
}

Exemplos de Desafios

typescript
const dailyChallenges = [
  {
    name: "Aquecimento",
    objectives: [
      { type: 'play_matches', target: 3 }
    ],
    xpReward: 100,
    difficulty: 'easy'
  },
  {
    name: "Tríplice Coroa",
    objectives: [
      { type: 'play_matches', target: 3 },
      { type: 'win_matches', target: 2 },
      { type: 'score_points', target: 50 }
    ],
    xpReward: 300,
    arenaCoinsReward: 50,
    difficulty: 'medium'
  },
  {
    name: "Campeão do Dia",
    objectives: [
      { type: 'win_matches', target: 5 },
      { type: 'score_points', target: 100 }
    ],
    xpReward: 500,
    arenaCoinsReward: 100,
    difficulty: 'hard'
  }
];

const weeklyChallenges = [
  {
    name: "Maratonista",
    objectives: [
      { type: 'play_matches', target: 20 }
    ],
    xpReward: 1000,
    arenaCoinsReward: 200,
    badgeReward: 'weekly_warrior',
    difficulty: 'medium'
  },
  {
    name: "Invencível",
    objectives: [
      { type: 'win_matches', target: 10 },
      { type: 'play_matches', target: 15 }
    ],
    xpReward: 2000,
    arenaCoinsReward: 500,
    badgeReward: 'weekly_champion',
    difficulty: 'hard'
  }
];

2.6 Sistema de Streaks

Tipos de Streak

typescript
interface Streak {
  type: 'login' | 'play' | 'win' | 'challenge';
  current: number;
  best: number;
  lastDate: Date;

  // Rewards
  milestones: StreakMilestone[];
  nextMilestone: StreakMilestone;

  // Status
  isActive: boolean;
  expiresAt: Date;
  daysUntilExpire: number;
}

interface StreakMilestone {
  days: number;
  xpReward: number;
  arenaCoinsReward: number;
  badgeReward?: string;
  title: string;
}

const streakMilestones: StreakMilestone[] = [
  { days: 3, xpReward: 100, arenaCoinsReward: 50, title: "Iniciando" },
  { days: 7, xpReward: 300, arenaCoinsReward: 150, badgeReward: 'week_streak', title: "Dedicado" },
  { days: 14, xpReward: 700, arenaCoinsReward: 350, title: "Consistente" },
  { days: 30, xpReward: 2000, arenaCoinsReward: 1000, badgeReward: 'month_streak', title: "Comprometido" },
  { days: 60, xpReward: 5000, arenaCoinsReward: 2500, title: "Incansável" },
  { days: 100, xpReward: 10000, arenaCoinsReward: 5000, badgeReward: 'century_streak', title: "Lendário" }
];

Lógica de Streak

typescript
class StreakManager {
  checkStreak(userId: string, type: StreakType): Streak {
    const streak = this.getStreak(userId, type);
    const now = new Date();
    const lastAction = streak.lastDate;

    const hoursSinceLastAction =
      (now.getTime() - lastAction.getTime()) / (1000 * 60 * 60);

    // Streak quebrado se passou mais de 48h
    if (hoursSinceLastAction > 48) {
      return this.resetStreak(streak);
    }

    // Mesma data, não incrementa
    if (this.isSameDay(now, lastAction)) {
      return streak;
    }

    // Incrementa streak
    return this.incrementStreak(streak);
  }

  incrementStreak(streak: Streak): Streak {
    streak.current++;
    streak.lastDate = new Date();

    if (streak.current > streak.best) {
      streak.best = streak.current;
    }

    // Verifica milestones
    const milestone = this.checkMilestone(streak);
    if (milestone) {
      this.rewardMilestone(streak, milestone);
    }

    return streak;
  }

  // Freeze de streak (comprado com ArenaCoins)
  freezeStreak(streak: Streak, days: number): void {
    streak.expiresAt = addDays(new Date(), days + 2); // +2 para margem
  }
}

3. Sistema de Progressão

3.1 Cálculo de XP por Ação

Engine de XP

typescript
class XPEngine {
  private baseXPTable = {
    CHECK_IN: 10,
    MATCH_COMPLETE: 25,
    MATCH_WIN: 50,
    MATCH_MVP: 100,
    CHALLENGE_COMPLETE: 100,
    FRIEND_INVITE: 150,
    EVENT_PARTICIPATION: 200
  };

  calculateXP(event: GameEvent, player: Player): XPGain {
    let xp = this.baseXPTable[event.type] || 0;
    const multipliers: Multiplier[] = [];

    // 1. Multiplicador de vitória
    if (event.isVictory) {
      xp *= 1.5;
      multipliers.push({ type: 'victory', value: 1.5 });
    }

    // 2. Multiplicador de streak
    if (player.hasActiveStreak) {
      xp *= 1.25;
      multipliers.push({ type: 'streak', value: 1.25 });
    }

    // 3. Bônus primeiro jogo do dia
    if (event.isFirstToday) {
      xp += 20;
      multipliers.push({ type: 'daily_bonus', value: 20 });
    }

    // 4. Boost para novatos (anti-grind inicial)
    if (player.level < 10) {
      xp *= 1.5;
      multipliers.push({ type: 'newbie_boost', value: 1.5 });
    }

    // 5. Evento especial
    if (event.isSpecialEvent) {
      xp *= 2;
      multipliers.push({ type: 'special_event', value: 2 });
    }

    // 6. Performance excepcional
    if (event.performance === 'exceptional') {
      xp *= 1.5;
      multipliers.push({ type: 'exceptional', value: 1.5 });
    }

    return {
      baseXP: this.baseXPTable[event.type],
      finalXP: Math.floor(xp),
      multipliers,
      breakdown: this.generateBreakdown(xp, multipliers)
    };
  }
}

interface XPGain {
  baseXP: number;
  finalXP: number;
  multipliers: Multiplier[];
  breakdown: string;
}

3.2 Tabela de Níveis Completa

typescript
const LEVEL_TABLE = [
  { level: 1, xpRequired: 0, totalXP: 0, title: "Novato" },
  { level: 2, xpRequired: 100, totalXP: 100, title: "Iniciante" },
  { level: 3, xpRequired: 150, totalXP: 250, title: "Aprendiz" },
  { level: 4, xpRequired: 200, totalXP: 450, title: "Jogador" },
  { level: 5, xpRequired: 300, totalXP: 750, title: "Jogador" },
  { level: 6, xpRequired: 400, totalXP: 1150, title: "Competidor" },
  { level: 7, xpRequired: 500, totalXP: 1650, title: "Competidor" },
  { level: 8, xpRequired: 600, totalXP: 2250, title: "Atleta" },
  { level: 9, xpRequired: 700, totalXP: 2950, title: "Atleta" },
  { level: 10, xpRequired: 750, totalXP: 3700, title: "Experiente" },
  { level: 15, xpRequired: 3800, totalXP: 7500, title: "Veterano" },
  { level: 20, xpRequired: 7500, totalXP: 15000, title: "Expert" },
  { level: 25, xpRequired: 10000, totalXP: 25000, title: "Elite" },
  { level: 30, xpRequired: 10000, totalXP: 35000, title: "Mestre" },
  { level: 40, xpRequired: 30000, totalXP: 65000, title: "Campeão" },
  { level: 50, xpRequired: 35000, totalXP: 100000, title: "Lenda" },
  { level: 60, xpRequired: 50000, totalXP: 150000, title: "Imortal" },
  { level: 70, xpRequired: 75000, totalXP: 225000, title: "Divino" },
  { level: 80, xpRequired: 100000, totalXP: 325000, title: "Mítico" },
  { level: 90, xpRequired: 125000, totalXP: 450000, title: "GOAT" },
  { level: 100, xpRequired: 150000, totalXP: 600000, title: "Hall of Fame" }
];

3.3 Habilidades Evoluíveis

typescript
interface Skill {
  id: string;
  name: string;
  sport: string;
  category: 'offense' | 'defense' | 'strategy' | 'physical' | 'mental';

  currentLevel: number;
  maxLevel: number;

  experience: number;
  experienceToNext: number;

  description: string;
  benefits: string[];

  unlockedAt: number; // Player level requirement
}

// Exemplo: Futebol
const footballSkills: Skill[] = [
  {
    id: 'shooting',
    name: 'Finalização',
    sport: 'football',
    category: 'offense',
    maxLevel: 10,
    description: 'Melhora precisão e potência dos chutes',
    benefits: [
      'Nível 3: +5% precisão',
      'Nível 5: Chutes curvados',
      'Nível 10: Finalização de elite'
    ],
    unlockedAt: 1
  },
  {
    id: 'dribbling',
    name: 'Drible',
    sport: 'football',
    category: 'offense',
    maxLevel: 10,
    description: 'Controle de bola e capacidade de driblar',
    benefits: [
      'Nível 3: +10% controle',
      'Nível 7: Dribles especiais',
      'Nível 10: Mestre do drible'
    ],
    unlockedAt: 5
  }
];

class SkillProgression {
  addExperience(skill: Skill, amount: number): SkillLevelUp | null {
    skill.experience += amount;

    if (skill.experience >= skill.experienceToNext) {
      return this.levelUpSkill(skill);
    }

    return null;
  }

  levelUpSkill(skill: Skill): SkillLevelUp {
    skill.currentLevel++;
    skill.experience -= skill.experienceToNext;
    skill.experienceToNext = this.calculateNextLevelXP(skill.currentLevel);

    return {
      skillId: skill.id,
      newLevel: skill.currentLevel,
      unlockedBenefit: this.getUnlockedBenefit(skill)
    };
  }
}

3.4 Progressão Visual

typescript
interface ProgressionVisual {
  // Level Progress
  levelProgress: {
    currentLevel: number;
    nextLevel: number;
    currentXP: number;
    requiredXP: number;
    percentComplete: number;
    animation: 'pulse' | 'glow' | 'fill';
  };

  // Visual Assets
  avatarFrame: {
    tier: 'bronze' | 'silver' | 'gold' | 'platinum' | 'diamond';
    animation: boolean;
    glow: string; // Hex color
  };

  // Title Display
  title: {
    current: string;
    color: string;
    icon?: string;
  };

  // Stats Cards
  stats: {
    totalMatches: number;
    winRate: number;
    favoriteSport: string;
    hoursPlayed: number;
    rank: number;
    percentile: number;
  };

  // Achievements Showcase
  showcaseBadges: Badge[]; // Top 3 selected by player

  // Recent Activity
  recentActivity: ActivityItem[];
}

4. Rankings e Competição Saudável

4.1 Ranking Geral

typescript
interface GlobalRanking {
  // Identificação
  playerId: string;
  rank: number;
  previousRank: number;
  rankChange: number;

  // Métricas
  rating: number;
  ratingChange: number;
  totalXP: number;
  level: number;

  // Estatísticas
  matchesPlayed: number;
  wins: number;
  losses: number;
  winRate: number;

  // Contexto
  percentile: number;
  tier: 'bronze' | 'silver' | 'gold' | 'platinum' | 'diamond' | 'master';

  // Temporal
  updatedAt: Date;
}

class RankingService {
  async getGlobalRanking(
    limit: number = 100,
    offset: number = 0
  ): Promise<Ranking[]> {
    return await db.players
      .orderBy('rating', 'desc')
      .limit(limit)
      .offset(offset)
      .toArray();
  }

  async getPlayerRanking(playerId: string): Promise<PlayerRankingContext> {
    const player = await this.getPlayer(playerId);
    const rank = await this.calculateRank(player.rating);

    // Jogadores próximos (contexto)
    const above = await this.getPlayersAbove(player.rating, 5);
    const below = await this.getPlayersBelow(player.rating, 5);

    return {
      player,
      rank,
      playersAbove: above,
      playersBelow: below,
      percentile: await this.calculatePercentile(rank)
    };
  }
}

4.2 Rankings por Categoria

typescript
interface CategoryRanking {
  type: RankingType;
  filters: RankingFilters;
  rankings: Ranking[];
}

interface RankingFilters {
  sport?: string;
  arena?: string;
  ageGroup?: '18-25' | '26-35' | '36-45' | '46+';
  gender?: 'male' | 'female' | 'other' | 'mixed';
  levelRange?: { min: number; max: number };
  location?: { city: string; state: string };
}

class CategoryRankingService {
  // Ranking por esporte
  async getSportRanking(sport: string): Promise<Ranking[]> {
    return await db.rankings
      .where('sport').equals(sport)
      .sortBy('rating');
  }

  // Ranking por faixa de nível (fair play)
  async getLevelRanking(level: number): Promise<Ranking[]> {
    const range = this.getLevelRange(level);

    return await db.rankings
      .where('level')
      .between(range.min, range.max)
      .sortBy('rating');
  }

  private getLevelRange(level: number): { min: number; max: number } {
    // Agrupa em faixas de 10 níveis
    const tier = Math.floor(level / 10) * 10;
    return {
      min: tier,
      max: tier + 9
    };
  }

  // Ranking por idade (opcional, respeitando privacidade)
  async getAgeGroupRanking(ageGroup: string): Promise<Ranking[]> {
    return await db.rankings
      .where('ageGroup').equals(ageGroup)
      .sortBy('rating');
  }
}

4.3 Rankings Temporais

typescript
enum RankingPeriod {
  DAILY = 'daily',
  WEEKLY = 'weekly',
  MONTHLY = 'monthly',
  SEASONAL = 'seasonal',
  ALLTIME = 'alltime'
}

interface TemporalRanking {
  period: RankingPeriod;
  startDate: Date;
  endDate: Date;
  rankings: Ranking[];
  isActive: boolean;
}

class TemporalRankingService {
  async getWeeklyRanking(): Promise<TemporalRanking> {
    const now = new Date();
    const startOfWeek = startOfWeek(now);
    const endOfWeek = endOfWeek(now);

    const rankings = await db.matches
      .where('date')
      .between(startOfWeek, endOfWeek)
      .toArray();

    const aggregated = this.aggregateByPlayer(rankings);

    return {
      period: RankingPeriod.WEEKLY,
      startDate: startOfWeek,
      endDate: endOfWeek,
      rankings: this.sortByRating(aggregated),
      isActive: true
    };
  }

  async getMonthlyChampions(): Promise<MonthlyChampion[]> {
    // Retorna campeões mensais históricos
    return await db.monthlyRankings
      .where('rank').equals(1)
      .toArray();
  }

  // Reset semanal com recompensas
  async resetWeeklyRanking(): Promise<void> {
    const champions = await this.getTopPlayers(10);

    // Distribuir recompensas
    for (const [index, player] of champions.entries()) {
      const reward = this.getWeeklyReward(index + 1);
      await this.rewardPlayer(player.id, reward);
    }

    // Arquivar ranking atual
    await this.archiveRanking(RankingPeriod.WEEKLY);

    // Criar novo ranking
    await this.createNewRanking(RankingPeriod.WEEKLY);
  }

  private getWeeklyReward(rank: number): Reward {
    const rewards = {
      1: { xp: 5000, arenaCoins: 1000, badge: 'weekly_champion' },
      2: { xp: 3000, arenaCoins: 600 },
      3: { xp: 2000, arenaCoins: 400 },
      // 4-10
      default: { xp: 1000, arenaCoins: 200 }
    };

    return rewards[rank] || rewards.default;
  }
}

4.4 Design Não-Punitivo

Princípios de Competição Saudável

typescript
interface HealthyCompetitionPrinciples {
  // 1. Progressão sempre positiva
  noNegativeXP: true;
  noLevelDowngrade: true;
  noRatingDecrease?: false; // Rating pode cair, mas não Level/XP

  // 2. Comparação contextual
  compareWithSimilarLevel: true;
  showOnlyNearbyRanks: true;
  hideGlobalIfTooFar: true;

  // 3. Múltiplas formas de "vencer"
  multipleTiers: true;
  personalBests: true;
  improvementHighlight: true;

  // 4. Participação recompensada
  participationXP: true;
  effortRecognition: true;
  consistencyBonus: true;

  // 5. Sem shaming
  noPublicFailures: true;
  privateStats: true;
  optInLeaderboards: true;
}

class HealthyCompetition {
  // Mostrar ranking contextual
  getContextualRanking(player: Player): ContextualRanking {
    return {
      // Mostrar apenas jogadores próximos
      nearbyPlayers: this.getNearbyPlayers(player, 10),

      // Comparar com média do nível
      averageAtLevel: this.getAverageStats(player.level),

      // Mostrar melhoria pessoal
      personalProgress: {
        lastWeek: player.stats.lastWeek,
        thisWeek: player.stats.thisWeek,
        improvement: this.calculateImprovement(player)
      },

      // Destacar vitórias pessoais
      personalBests: [
        { metric: 'Win Streak', value: 12, isNew: false },
        { metric: 'Best Score', value: 45, isNew: true },
        { metric: 'Matches This Month', value: 28, isNew: true }
      ]
    };
  }

  // Celebrar pequenas vitórias
  generateEncouragement(player: Player, match: Match): string[] {
    const messages = [];

    if (!match.won) {
      // Mesmo na derrota, encontrar pontos positivos
      if (match.personalBest) {
        messages.push("🎯 Seu melhor desempenho pessoal!");
      }
      if (match.improvement) {
        messages.push("📈 Você está melhorando!");
      }
      messages.push("💪 +25 XP por esforço");
    }

    return messages;
  }
}

UI de Ranking Não-Punitiva

typescript
interface RankingUI {
  // Mostrar contexto positivo
  displayMode: 'nearby' | 'tier' | 'friends';

  // Mensagens encorajadoras
  encouragementMessages: {
    rising: "Você subiu 5 posições esta semana! 📈",
    stable: "Você mantém seu desempenho consistente 🎯",
    falling: "Continue jogando! Você já jogou 3 partidas esta semana 💪"
  };

  // Highlight de conquistas
  highlights: {
    personalBest: boolean;
    improvement: number;
    consistency: boolean;
  };

  // Opt-in para ranking público
  privacy: {
    showInGlobalRanking: boolean;
    showStats: boolean;
    showToFriendsOnly: boolean;
  };
}

5. Conquistas e Badges

5.1 Badges de Participação

typescript
const participationBadges: BadgeDefinition[] = [
  {
    id: 'first_match',
    name: 'Primeiro Passo',
    description: 'Completou sua primeira partida',
    icon: '🎯',
    rarity: 'common',
    xpReward: 50,
    requirement: { matches: 1 }
  },
  {
    id: 'regular_player',
    name: 'Jogador Regular',
    description: 'Completou 10 partidas',
    icon: '⚽',
    rarity: 'common',
    xpReward: 200,
    arenaCoinsReward: 50,
    requirement: { matches: 10 }
  },
  {
    id: 'dedicated_player',
    name: 'Dedicado',
    description: 'Completou 50 partidas',
    icon: '🏆',
    rarity: 'uncommon',
    xpReward: 500,
    arenaCoinsReward: 150,
    requirement: { matches: 50 }
  },
  {
    id: 'hardcore_player',
    name: 'Hardcore',
    description: 'Completou 100 partidas',
    icon: '🔥',
    rarity: 'rare',
    xpReward: 1000,
    arenaCoinsReward: 300,
    requirement: { matches: 100 }
  },
  {
    id: 'legend_player',
    name: 'Lenda',
    description: 'Completou 500 partidas',
    icon: '👑',
    rarity: 'epic',
    xpReward: 5000,
    arenaCoinsReward: 1000,
    requirement: { matches: 500 }
  },
  {
    id: 'weekend_warrior',
    name: 'Guerreiro de Fim de Semana',
    description: 'Jogou 20 partidas em um fim de semana',
    icon: '🎮',
    rarity: 'uncommon',
    xpReward: 300,
    arenaCoinsReward: 100,
    requirement: { matches: 20, timeframe: 'weekend' }
  },
  {
    id: 'night_owl',
    name: 'Coruja Noturna',
    description: 'Jogou 10 partidas após 22h',
    icon: '🦉',
    rarity: 'uncommon',
    xpReward: 250,
    requirement: { matches: 10, time: 'night' }
  },
  {
    id: 'early_bird',
    name: 'Madrugador',
    description: 'Jogou 10 partidas antes das 8h',
    icon: '🌅',
    rarity: 'uncommon',
    xpReward: 250,
    requirement: { matches: 10, time: 'morning' }
  }
];

5.2 Badges de Performance

typescript
const performanceBadges: BadgeDefinition[] = [
  // Futebol
  {
    id: 'hat_trick',
    name: 'Hat Trick',
    description: 'Marcou 3 gols em uma partida',
    sport: 'football',
    icon: '⚽⚽⚽',
    rarity: 'rare',
    xpReward: 500,
    arenaCoinsReward: 150,
    requirement: { goals: 3, inOneMatch: true }
  },
  {
    id: 'clean_sheet',
    name: 'Jogo Limpo',
    description: 'Venceu sem sofrer gols (goleiro)',
    sport: 'football',
    icon: '🧤',
    rarity: 'uncommon',
    xpReward: 300,
    requirement: { position: 'goalkeeper', goalsConceded: 0, won: true }
  },

  // Basquete
  {
    id: 'triple_double',
    name: 'Triple Double',
    description: '10+ pontos, rebotes e assistências',
    sport: 'basketball',
    icon: '🏀',
    rarity: 'epic',
    xpReward: 1000,
    arenaCoinsReward: 300,
    requirement: {
      points: { min: 10 },
      rebounds: { min: 10 },
      assists: { min: 10 }
    }
  },
  {
    id: 'sharpshooter',
    name: 'Atirador de Elite',
    description: '80%+ de aproveitamento (mín. 10 arremessos)',
    sport: 'basketball',
    icon: '🎯',
    rarity: 'rare',
    xpReward: 500,
    requirement: {
      shootingPercentage: { min: 80 },
      minimumShots: 10
    }
  },

  // Universal
  {
    id: 'perfect_game',
    name: 'Jogo Perfeito',
    description: 'Venceu sem sofrer pontos',
    icon: '💯',
    rarity: 'epic',
    xpReward: 1000,
    arenaCoinsReward: 500,
    requirement: { pointsConceded: 0, won: true }
  },
  {
    id: 'comeback_king',
    name: 'Rei da Virada',
    description: 'Venceu após estar perdendo por 10+ pontos',
    icon: '🔄',
    rarity: 'rare',
    xpReward: 750,
    arenaCoinsReward: 200,
    requirement: {
      comeback: true,
      deficitSize: { min: 10 }
    }
  },
  {
    id: 'domination',
    name: 'Dominação',
    description: 'Venceu por 20+ pontos de diferença',
    icon: '💪',
    rarity: 'uncommon',
    xpReward: 400,
    requirement: {
      won: true,
      marginOfVictory: { min: 20 }
    }
  },
  {
    id: 'clutch_player',
    name: 'Clutch',
    description: 'Venceu 5 partidas por diferença mínima (1-3 pontos)',
    icon: '⏱️',
    rarity: 'rare',
    xpReward: 600,
    requirement: {
      wins: 5,
      marginOfVictory: { max: 3 }
    }
  }
];

5.3 Badges Sociais

typescript
const socialBadges: BadgeDefinition[] = [
  {
    id: 'team_player',
    name: 'Espírito de Equipe',
    description: 'Jogou 10 partidas com o mesmo time',
    icon: '🤝',
    rarity: 'uncommon',
    xpReward: 300,
    arenaCoinsReward: 100,
    requirement: {
      sameTeamMatches: 10
    }
  },
  {
    id: 'ambassador',
    name: 'Embaixador',
    description: 'Convidou 5 amigos que jogaram pelo menos 1 partida',
    icon: '📢',
    rarity: 'rare',
    xpReward: 750,
    arenaCoinsReward: 300,
    requirement: {
      invitedFriends: 5,
      friendsPlayed: true
    }
  },
  {
    id: 'community_hero',
    name: 'Herói da Comunidade',
    description: 'Recebeu 50 curtidas em highlights',
    icon: '❤️',
    rarity: 'uncommon',
    xpReward: 400,
    requirement: {
      likesReceived: 50
    }
  },
  {
    id: 'supporter',
    name: 'Torcedor Fiel',
    description: 'Fez check-in como torcedor em 50 partidas',
    icon: '📣',
    rarity: 'uncommon',
    xpReward: 300,
    requirement: {
      spectatorCheckIns: 50
    }
  },
  {
    id: 'organizer',
    name: 'Organizador',
    description: 'Criou 10 partidas que aconteceram',
    icon: '📅',
    rarity: 'rare',
    xpReward: 500,
    arenaCoinsReward: 200,
    requirement: {
      matchesCreated: 10,
      matchesCompleted: true
    }
  },
  {
    id: 'mentor',
    name: 'Mentor',
    description: 'Jogou 20 partidas ajudando jogadores nível 1-5',
    icon: '🎓',
    rarity: 'rare',
    xpReward: 600,
    requirement: {
      matchesWithNewbies: 20,
      teammateLevel: { max: 5 }
    }
  },
  {
    id: 'social_butterfly',
    name: 'Borboleta Social',
    description: 'Jogou com 50 jogadores diferentes',
    icon: '🦋',
    rarity: 'uncommon',
    xpReward: 400,
    requirement: {
      uniqueTeammates: 50
    }
  }
];

5.4 Badges de Evolução

typescript
const evolutionBadges: BadgeDefinition[] = [
  {
    id: 'rising_star',
    name: 'Estrela em Ascensão',
    description: 'Alcançou nível 10',
    icon: '⭐',
    rarity: 'common',
    xpReward: 500,
    arenaCoinsReward: 100,
    requirement: { level: 10 }
  },
  {
    id: 'veteran',
    name: 'Veterano',
    description: 'Alcançou nível 25',
    icon: '🎖️',
    rarity: 'uncommon',
    xpReward: 1000,
    arenaCoinsReward: 300,
    requirement: { level: 25 }
  },
  {
    id: 'master',
    name: 'Mestre',
    description: 'Alcançou nível 50',
    icon: '🥇',
    rarity: 'rare',
    xpReward: 2500,
    arenaCoinsReward: 750,
    requirement: { level: 50 }
  },
  {
    id: 'first_win',
    name: 'Primeira Vitória',
    description: 'Conquistou sua primeira vitória',
    icon: '🏆',
    rarity: 'common',
    xpReward: 100,
    arenaCoinsReward: 25,
    requirement: { wins: 1 }
  },
  {
    id: 'improvement',
    name: 'Em Evolução',
    description: 'Melhorou win rate em 50% nos últimos 30 dias',
    icon: '📈',
    rarity: 'rare',
    xpReward: 750,
    arenaCoinsReward: 200,
    requirement: {
      winRateImprovement: { min: 50 },
      timeframe: '30days'
    }
  },
  {
    id: 'skill_master',
    name: 'Mestre de Habilidade',
    description: 'Maximizou uma habilidade (nível 10)',
    icon: '🎯',
    rarity: 'rare',
    xpReward: 1000,
    requirement: {
      skillMaxed: true
    }
  },
  {
    id: 'all_rounder',
    name: 'Completo',
    description: 'Alcançou nível 5+ em todas as habilidades',
    icon: '🌟',
    rarity: 'epic',
    xpReward: 2000,
    arenaCoinsReward: 500,
    requirement: {
      allSkillsMinLevel: 5
    }
  },
  {
    id: 'multi_sport',
    name: 'Multi-Esportivo',
    description: 'Jogou 20+ partidas em 3 esportes diferentes',
    icon: '🎾⚽🏀',
    rarity: 'uncommon',
    xpReward: 600,
    requirement: {
      sports: 3,
      matchesPerSport: { min: 20 }
    }
  }
];

5.5 Badges Raros/Épicos

typescript
const rareBadges: BadgeDefinition[] = [
  {
    id: 'unicorn',
    name: 'Unicórnio',
    description: 'Venceu com score exato de 111 pontos',
    icon: '🦄',
    rarity: 'legendary',
    xpReward: 5000,
    arenaCoinsReward: 2000,
    requirement: {
      finalScore: 111,
      won: true
    },
    firstEarnedBy: null,
    earnedByCount: 0
  },
  {
    id: 'perfect_week',
    name: 'Semana Perfeita',
    description: 'Venceu 7 partidas em 7 dias consecutivos',
    icon: '💎',
    rarity: 'legendary',
    xpReward: 3000,
    arenaCoinsReward: 1500,
    requirement: {
      wins: 7,
      consecutive: true,
      timeframe: '7days'
    }
  },
  {
    id: 'grand_slam',
    name: 'Grand Slam',
    description: 'Venceu torneio com 16+ jogadores',
    icon: '🏆',
    rarity: 'epic',
    xpReward: 2500,
    arenaCoinsReward: 1000,
    requirement: {
      tournamentWin: true,
      tournamentSize: { min: 16 }
    }
  },
  {
    id: 'hall_of_fame',
    name: 'Hall da Fama',
    description: 'Alcançou Top 10 do ranking global',
    icon: '🌟',
    rarity: 'epic',
    xpReward: 5000,
    arenaCoinsReward: 2000,
    requirement: {
      globalRank: { max: 10 }
    }
  },
  {
    id: 'goat',
    name: 'GOAT',
    description: 'Manteve #1 no ranking global por 30 dias',
    icon: '🐐',
    rarity: 'legendary',
    xpReward: 10000,
    arenaCoinsReward: 5000,
    requirement: {
      globalRank: 1,
      duration: '30days'
    },
    firstEarnedBy: null,
    earnedByCount: 0
  },
  {
    id: 'iron_man',
    name: 'Homem de Ferro',
    description: 'Jogou 50 partidas em 7 dias',
    icon: '⚙️',
    rarity: 'epic',
    xpReward: 2000,
    arenaCoinsReward: 750,
    requirement: {
      matches: 50,
      timeframe: '7days'
    }
  },
  {
    id: 'centurion',
    name: 'Centurião',
    description: 'Alcançou streak de 100 dias',
    icon: '💯',
    rarity: 'legendary',
    xpReward: 10000,
    arenaCoinsReward: 5000,
    requirement: {
      loginStreak: 100
    }
  },
  {
    id: 'underdog',
    name: 'Azarão Vencedor',
    description: 'Venceu 10 partidas sendo underdog (rating 200+ menor)',
    icon: '🐕',
    rarity: 'epic',
    xpReward: 1500,
    arenaCoinsReward: 600,
    requirement: {
      underdogWins: 10,
      ratingDifference: { min: 200 }
    }
  }
];

6. Partidas "Valendo"

6.1 Conceito

Partidas "valendo" permitem que jogadores apostem ArenaCoins em partidas, aumentando emoção e stakes sem envolver dinheiro real (cumprindo regulamentações).

6.2 Tipos de Partidas

typescript
enum MatchType {
  CASUAL = 'casual',        // Sem stakes
  FRIENDLY = 'friendly',    // Stakes mínimos (10-50 coins)
  COMPETITIVE = 'competitive', // Stakes médios (50-200 coins)
  HIGH_STAKES = 'high_stakes', // Stakes altos (200-1000 coins)
  TOURNAMENT = 'tournament'  // Definido pelo TO
}

interface StakedMatch {
  id: string;
  type: MatchType;
  sport: string;

  // Stakes
  entryFee: number;        // ArenaCoins por jogador
  prizePool: number;       // Total apostado
  distribution: PrizeDistribution;

  // Participantes
  minPlayers: number;
  maxPlayers: number;
  currentPlayers: Player[];

  // Requisitos
  minLevel: number;
  minRating?: number;

  // Status
  status: 'open' | 'full' | 'in_progress' | 'completed';
  startsAt: Date;

  // Regras
  rules: MatchRules;
}

interface PrizeDistribution {
  winner: number;          // % para vencedor
  runnerUp?: number;       // % para 2º lugar (se aplicável)
  participation: number;   // % devolvido para todos
  platformFee: number;     // Taxa da plataforma (5%)
}

const distributionTemplates = {
  winner_takes_all: {
    winner: 95,
    participation: 0,
    platformFee: 5
  },
  balanced: {
    winner: 70,
    runnerUp: 20,
    participation: 5,
    platformFee: 5
  },
  participation_reward: {
    winner: 60,
    runnerUp: 15,
    participation: 20,
    platformFee: 5
  }
};

6.3 Fluxo de Partida Valendo

typescript
class StakedMatchService {
  // 1. Criação de partida
  async createStakedMatch(
    creator: Player,
    config: StakedMatchConfig
  ): Promise<StakedMatch> {
    // Validações
    if (creator.level < 15) {
      throw new Error('Level mínimo 15 para criar partidas valendo');
    }

    if (creator.arenaCoins < config.entryFee) {
      throw new Error('ArenaCoins insuficientes');
    }

    // Congelar entry fee do criador
    await this.walletService.freeze(creator.id, config.entryFee);

    return await this.matchRepository.create({
      ...config,
      creatorId: creator.id,
      status: 'open',
      currentPlayers: [creator]
    });
  }

  // 2. Join em partida
  async joinStakedMatch(
    player: Player,
    matchId: string
  ): Promise<void> {
    const match = await this.getMatch(matchId);

    // Validações
    this.validateJoin(player, match);

    // Congelar entry fee
    await this.walletService.freeze(player.id, match.entryFee);

    // Adicionar jogador
    match.currentPlayers.push(player);

    // Se lotou, iniciar
    if (match.currentPlayers.length === match.maxPlayers) {
      await this.startMatch(match);
    }

    await this.matchRepository.update(match);
  }

  // 3. Finalização e distribuição
  async finalizeStakedMatch(
    matchId: string,
    results: MatchResults
  ): Promise<void> {
    const match = await this.getMatch(matchId);

    // Calcular prêmios
    const prizes = this.calculatePrizes(match, results);

    // Distribuir
    for (const prize of prizes) {
      await this.walletService.unfreeze(prize.playerId, match.entryFee);
      await this.walletService.add(prize.playerId, prize.amount, {
        type: 'match_prize',
        matchId: match.id,
        description: `Prêmio - ${match.type}`
      });
    }

    // Taxa da plataforma
    const platformFee = match.prizePool * (match.distribution.platformFee / 100);
    await this.walletService.collectFee(platformFee);

    // Atualizar match
    match.status = 'completed';
    match.results = results;
    await this.matchRepository.update(match);

    // Notificar jogadores
    await this.notifyPrizes(prizes);
  }

  private calculatePrizes(
    match: StakedMatch,
    results: MatchResults
  ): Prize[] {
    const prizes: Prize[] = [];
    const pool = match.prizePool;
    const dist = match.distribution;

    // Vencedor
    const winner = results.winner;
    prizes.push({
      playerId: winner.id,
      amount: pool * (dist.winner / 100),
      position: 1
    });

    // 2º lugar (se aplicável)
    if (dist.runnerUp && results.runnerUp) {
      prizes.push({
        playerId: results.runnerUp.id,
        amount: pool * (dist.runnerUp / 100),
        position: 2
      });
    }

    // Prêmio de participação
    if (dist.participation > 0) {
      const participationAmount = (pool * (dist.participation / 100)) / match.currentPlayers.length;

      for (const player of match.currentPlayers) {
        prizes.push({
          playerId: player.id,
          amount: participationAmount,
          position: null,
          type: 'participation'
        });
      }
    }

    return prizes;
  }
}

6.4 Proteções e Fairness

typescript
interface StakedMatchProtections {
  // Anti-fraude
  minimumLevel: 15;
  minimumMatchHistory: 20;
  accountAge: '30days';

  // Matchmaking justo
  maxRatingDifference: 300;
  similarLevelOnly: true;

  // Proteções financeiras
  dailyLossLimit: 1000;      // Máximo de perda por dia
  maxSingleStake: 1000;      // Máximo por partida
  cooldownAfterLoss: '1hour'; // Cooldown após perder

  // Transparência
  showPlayerStats: true;
  showWinRate: true;
  showHistory: true;

  // Reversão
  disputeWindow: '24hours';
  refundOnDispute: 'automatic';
}

class FairPlayService {
  validateStakedMatch(player: Player, match: StakedMatch): void {
    // Level mínimo
    if (player.level < match.minLevel) {
      throw new Error(`Level mínimo: ${match.minLevel}`);
    }

    // Rating similar
    if (match.minRating) {
      const avgRating = this.getAverageRating(match.currentPlayers);
      if (Math.abs(player.rating - avgRating) > 300) {
        throw new Error('Rating muito diferente dos outros jogadores');
      }
    }

    // Limite diário
    const todayLosses = await this.getTodayLosses(player.id);
    if (todayLosses >= 1000) {
      throw new Error('Limite diário de perdas atingido. Tente amanhã.');
    }

    // Cooldown
    const lastLoss = await this.getLastLoss(player.id);
    if (lastLoss && this.isInCooldown(lastLoss)) {
      throw new Error('Aguarde 1 hora após uma derrota antes de apostar novamente');
    }

    // Histórico mínimo
    if (player.matchesPlayed < 20) {
      throw new Error('Mínimo de 20 partidas para participar de partidas valendo');
    }
  }

  // Sistema de disputa
  async handleDispute(matchId: string, playerId: string, reason: string): Promise<void> {
    const match = await this.getMatch(matchId);

    // Congelar distribuição de prêmios
    await this.freezePrizes(match);

    // Criar ticket de disputa
    const dispute = await this.disputeRepository.create({
      matchId,
      playerId,
      reason,
      status: 'under_review',
      createdAt: new Date()
    });

    // Notificar moderadores
    await this.notifyModerators(dispute);

    // Auto-resolve casos óbvios
    if (await this.canAutoResolve(dispute)) {
      await this.resolveDispute(dispute.id, 'approved');
    }
  }
}

6.5 UI/UX de Partidas Valendo

typescript
interface StakedMatchUI {
  // Destacar stakes
  visual: {
    badge: '💰 VALENDO',
    color: 'gold',
    animation: 'shimmer'
  };

  // Info clara
  display: {
    entryFee: '50 ArenaCoins',
    prizePool: '450 ArenaCoins',
    yourPotentialWin: '427 ArenaCoins',
    platformFee: '23 ArenaCoins (5%)'
  };

  // Confirmação
  confirmation: {
    title: 'Confirmar Aposta',
    message: 'Você está apostando 50 ArenaCoins nesta partida.',
    warnings: [
      'Você pode perder seus ArenaCoins se não vencer',
      'Certifique-se de que está pronto para jogar'
    ],
    requireExplicitConfirm: true
  };

  // Transparência
  playerInfo: {
    showLevel: true,
    showRating: true,
    showWinRate: true,
    showRecentMatches: true
  };
}

7. Integração com Wallet

7.1 ArenaCoins - A Moeda da Plataforma

typescript
interface ArenaCoin {
  // Definição
  name: 'ArenaCoin';
  symbol: 'AC';
  type: 'virtual_currency';  // Não é criptomoeda
  decimal: 2;

  // Propriedades
  isRefundable: false;       // Não pode ser sacado como dinheiro
  isTransferable: true;      // Pode ser transferido entre usuários
  expiresAt: null;           // Não expira

  // Compliance
  notACryptocurrency: true;  // Importante legalmente
  notRedeemableForCash: true;
  virtualGoodOnly: true;
}

interface Wallet {
  userId: string;

  // Balanços
  balance: number;           // ArenaCoins disponíveis
  frozen: number;            // Congelados (em partidas, etc)
  lifetime: number;          // Total ganho na vida

  // Histórico
  transactions: Transaction[];

  // Limites
  dailySpendLimit: number;
  dailyEarnLimit: number;

  // Metadata
  createdAt: Date;
  updatedAt: Date;
}

interface Transaction {
  id: string;
  walletId: string;
  type: TransactionType;
  amount: number;
  balance: number;           // Saldo após transação
  description: string;
  metadata: object;
  createdAt: Date;
}

enum TransactionType {
  // Ganhos
  MATCH_WIN = 'match_win',
  CHALLENGE_COMPLETE = 'challenge_complete',
  BADGE_UNLOCK = 'badge_unlock',
  LEVEL_UP = 'level_up',
  DAILY_REWARD = 'daily_reward',
  REFERRAL_BONUS = 'referral_bonus',
  PURCHASE = 'purchase',

  // Gastos
  MATCH_ENTRY = 'match_entry',
  ITEM_PURCHASE = 'item_purchase',
  TRANSFER_SENT = 'transfer_sent',

  // Recebimentos
  MATCH_PRIZE = 'match_prize',
  TRANSFER_RECEIVED = 'transfer_received',
  REFUND = 'refund',

  // Sistema
  ADMIN_ADJUSTMENT = 'admin_adjustment',
  PROMOTION = 'promotion'
}

7.2 Fontes de ArenaCoins

typescript
const arenaCoinsRewards = {
  // Progressão
  levelUp: {
    1: 10,
    5: 50,
    10: 100,
    15: 150,
    20: 250,
    25: 350,
    30: 500,
    // ...
    50: 1000
  },

  // Atividades
  checkIn: 5,
  matchComplete: 10,
  matchWin: 25,
  matchMVP: 50,

  // Desafios
  dailyChallenge: 50,
  weeklyChallenge: 200,
  specialEvent: 500,

  // Social
  friendInvite: 150,
  friendFirstMatch: 100,

  // Badges
  badgeCommon: 50,
  badgeUncommon: 100,
  badgeRare: 250,
  badgeEpic: 500,
  badgeLegendary: 1000,

  // Streaks
  streak3Days: 50,
  streak7Days: 150,
  streak30Days: 1000,

  // Rankings
  weeklyTop10: [1000, 600, 400, 300, 250, 200, 150, 125, 100, 100],
  monthlyTop10: [5000, 3000, 2000, 1500, 1000, 750, 500, 400, 300, 200],

  // Daily Login
  day1: 10,
  day2: 15,
  day3: 20,
  day4: 25,
  day5: 30,
  day6: 40,
  day7: 100,  // Bonus no 7º dia

  // Compras (opcional, com dinheiro real)
  iap: {
    starter: { price: 4.99, coins: 100 },
    basic: { price: 9.99, coins: 220 },
    pro: { price: 19.99, coins: 500 },
    elite: { price: 49.99, coins: 1500 }
  }
};

7.3 Usos de ArenaCoins

typescript
const arenaCoinsUses = {
  // Partidas
  stakedMatchEntry: {
    friendly: 10-50,
    competitive: 50-200,
    highStakes: 200-1000
  },

  // Itens Cosméticos
  avatars: 100-500,
  frames: 200-1000,
  badges: 300-1500,
  themes: 500-2000,
  emotes: 50-300,
  celebrations: 200-800,

  // Boost temporários (não pay-to-win)
  xpBoost24h: 500,      // +50% XP por 24h
  xpBoost7d: 2000,      // +50% XP por 7 dias
  highlightSlots: 300,  // +5 slots de highlights

  // Social
  giftToFriend: 50-1000,
  highlightPromotion: 100,  // Destacar highlight no feed

  // Torneios
  tournamentEntry: 100-500,
  privateTournament: 1000,  // Criar torneio privado

  // Funcionalidades Premium
  customBadge: 5000,        // Badge customizado
  nameChange: 500,
  streakFreeze: 200,        // Congelar streak por 1 dia
  statsReset: 1000          // Reset de estatísticas (1x)
};

7.4 Wallet Service

typescript
class WalletService {
  // Adicionar ArenaCoins
  async add(
    userId: string,
    amount: number,
    transaction: Partial<Transaction>
  ): Promise<Wallet> {
    const wallet = await this.getWallet(userId);

    // Validar
    if (amount <= 0) {
      throw new Error('Amount must be positive');
    }

    // Atualizar saldo
    wallet.balance += amount;
    wallet.lifetime += amount;

    // Registrar transação
    await this.transactionRepository.create({
      walletId: wallet.id,
      amount,
      balance: wallet.balance,
      ...transaction,
      createdAt: new Date()
    });

    await this.walletRepository.update(wallet);

    // Eventos
    await this.eventBus.publish('wallet.balance_updated', {
      userId,
      newBalance: wallet.balance,
      change: amount
    });

    return wallet;
  }

  // Remover ArenaCoins
  async subtract(
    userId: string,
    amount: number,
    transaction: Partial<Transaction>
  ): Promise<Wallet> {
    const wallet = await this.getWallet(userId);

    // Validar saldo
    if (wallet.balance < amount) {
      throw new Error('Insufficient balance');
    }

    wallet.balance -= amount;

    await this.transactionRepository.create({
      walletId: wallet.id,
      amount: -amount,
      balance: wallet.balance,
      ...transaction,
      createdAt: new Date()
    });

    await this.walletRepository.update(wallet);

    return wallet;
  }

  // Congelar (para partidas valendo)
  async freeze(userId: string, amount: number): Promise<void> {
    const wallet = await this.getWallet(userId);

    if (wallet.balance < amount) {
      throw new Error('Insufficient balance');
    }

    wallet.balance -= amount;
    wallet.frozen += amount;

    await this.walletRepository.update(wallet);
  }

  // Descongelar
  async unfreeze(userId: string, amount: number): Promise<void> {
    const wallet = await this.getWallet(userId);

    wallet.frozen -= amount;
    wallet.balance += amount;

    await this.walletRepository.update(wallet);
  }

  // Transferir entre usuários
  async transfer(
    fromUserId: string,
    toUserId: string,
    amount: number,
    message?: string
  ): Promise<void> {
    // Validações
    if (fromUserId === toUserId) {
      throw new Error('Cannot transfer to yourself');
    }

    const fromWallet = await this.getWallet(fromUserId);
    if (fromWallet.balance < amount) {
      throw new Error('Insufficient balance');
    }

    // Taxa de transferência (5%)
    const fee = amount * 0.05;
    const netAmount = amount - fee;

    // Debitar
    await this.subtract(fromUserId, amount, {
      type: TransactionType.TRANSFER_SENT,
      description: `Transfer to ${toUserId}`,
      metadata: { toUserId, message }
    });

    // Creditar
    await this.add(toUserId, netAmount, {
      type: TransactionType.TRANSFER_RECEIVED,
      description: `Transfer from ${fromUserId}`,
      metadata: { fromUserId, message }
    });

    // Taxa para plataforma
    await this.collectFee(fee);
  }

  // Histórico
  async getTransactions(
    userId: string,
    filters?: TransactionFilters
  ): Promise<Transaction[]> {
    const wallet = await this.getWallet(userId);

    let query = this.transactionRepository
      .where('walletId').equals(wallet.id);

    if (filters?.type) {
      query = query.where('type').equals(filters.type);
    }

    if (filters?.startDate) {
      query = query.where('createdAt').gte(filters.startDate);
    }

    return await query
      .orderBy('createdAt', 'desc')
      .limit(filters?.limit || 50)
      .toArray();
  }
}

7.5 Cashback Gamificado

typescript
interface CashbackProgram {
  // Níveis de cashback baseados em atividade
  tiers: {
    bronze: { minLevel: 1, cashback: 2 },    // 2% em ArenaCoins
    silver: { minLevel: 10, cashback: 3 },   // 3%
    gold: { minLevel: 25, cashback: 5 },     // 5%
    platinum: { minLevel: 50, cashback: 7 }  // 7%
  };

  // Aplicável em
  applies: [
    'item_purchase',
    'staked_match_entry',
    'tournament_entry'
  ];

  // Exemplo
  example: {
    purchase: 1000,  // ArenaCoins gastos
    tier: 'gold',
    cashback: 50     // 5% de 1000
  };
}

class CashbackService {
  async applyCashback(
    userId: string,
    amount: number,
    type: TransactionType
  ): Promise<number> {
    const player = await this.playerService.getPlayer(userId);
    const tier = this.getCashbackTier(player.level);

    if (!this.isEligible(type)) {
      return 0;
    }

    const cashback = amount * (tier.cashback / 100);

    await this.walletService.add(userId, cashback, {
      type: TransactionType.CASHBACK,
      description: `Cashback ${tier.cashback}% (tier: ${tier.name})`,
      metadata: { originalAmount: amount, originalType: type }
    });

    return cashback;
  }
}

8. Campeonatos e Torneios

8.1 Tipos de Torneios

typescript
enum TournamentType {
  SINGLE_ELIMINATION = 'single_elimination',  // Mata-mata
  DOUBLE_ELIMINATION = 'double_elimination',  // Com repescagem
  ROUND_ROBIN = 'round_robin',                // Todos contra todos
  SWISS = 'swiss',                            // Sistema suíço
  LEAGUE = 'league'                           // Liga com pontos corridos
}

enum TournamentAccess {
  PUBLIC = 'public',          // Qualquer um pode entrar
  INVITE_ONLY = 'invite_only', // Apenas convidados
  LEVEL_GATED = 'level_gated', // Requer nível mínimo
  PAID = 'paid'               // Entry fee em ArenaCoins
}

interface Tournament {
  id: string;
  name: string;
  description: string;
  sport: string;

  // Tipo e formato
  type: TournamentType;
  access: TournamentAccess;

  // Participantes
  minParticipants: number;
  maxParticipants: number;
  currentParticipants: Player[];

  // Entry
  entryFee: number;
  minLevel: number;
  minRating?: number;

  // Premiação
  prizePool: number;
  prizeDistribution: TournamentPrizes;

  // Datas
  registrationStart: Date;
  registrationEnd: Date;
  startDate: Date;
  endDate?: Date;

  // Status
  status: 'registration' | 'ready' | 'in_progress' | 'completed' | 'cancelled';

  // Brackets/Grupos
  brackets?: Bracket[];
  groups?: Group[];
  standings?: Standing[];

  // Organizador
  organizerId: string;
  organizer: Player;

  // Metadata
  rules: string;
  streamUrl?: string;
  createdAt: Date;
  updatedAt: Date;
}

interface Bracket {
  round: number;
  matches: BracketMatch[];
}

interface BracketMatch {
  id: string;
  player1: Player;
  player2: Player;
  winner?: Player;
  score?: { player1: number; player2: number };
  scheduledAt?: Date;
  completedAt?: Date;
  status: 'pending' | 'in_progress' | 'completed';
}

interface TournamentPrizes {
  first: number;
  second: number;
  third: number;
  fourth?: number;
  participation?: number;
}

8.2 Gerenciamento de Torneios

typescript
class TournamentService {
  // Criar torneio
  async createTournament(
    organizer: Player,
    config: TournamentConfig
  ): Promise<Tournament> {
    // Validar permissões (requer nível 30)
    if (organizer.level < 30) {
      throw new Error('Level mínimo 30 para criar torneios');
    }

    // Validar prize pool
    if (config.prizePool < config.entryFee * config.minParticipants) {
      throw new Error('Prize pool insuficiente');
    }

    const tournament = await this.tournamentRepository.create({
      ...config,
      organizerId: organizer.id,
      status: 'registration',
      currentParticipants: [],
      createdAt: new Date()
    });

    // Notificar comunidade
    await this.notificationService.broadcastTournament(tournament);

    return tournament;
  }

  // Registrar participante
  async registerParticipant(
    player: Player,
    tournamentId: string
  ): Promise<void> {
    const tournament = await this.getTournament(tournamentId);

    // Validações
    this.validateRegistration(player, tournament);

    // Cobrar entry fee
    if (tournament.entryFee > 0) {
      await this.walletService.subtract(player.id, tournament.entryFee, {
        type: TransactionType.TOURNAMENT_ENTRY,
        description: `Entry: ${tournament.name}`,
        metadata: { tournamentId }
      });

      tournament.prizePool += tournament.entryFee * 0.95; // 5% taxa
    }

    // Adicionar participante
    tournament.currentParticipants.push(player);

    await this.tournamentRepository.update(tournament);

    // Notificar
    await this.notificationService.notifyTournamentRegistration(
      player,
      tournament
    );
  }

  // Gerar brackets (single elimination)
  async generateBrackets(tournamentId: string): Promise<Bracket[]> {
    const tournament = await this.getTournament(tournamentId);

    if (tournament.status !== 'ready') {
      throw new Error('Tournament not ready');
    }

    const participants = tournament.currentParticipants;
    const rounds = Math.ceil(Math.log2(participants.length));

    // Shuffle e seed players
    const seeded = this.seedPlayers(participants);

    const brackets: Bracket[] = [];

    // Round 1
    const round1Matches: BracketMatch[] = [];
    for (let i = 0; i < seeded.length; i += 2) {
      round1Matches.push({
        id: `r1m${i/2}`,
        player1: seeded[i],
        player2: seeded[i + 1],
        status: 'pending'
      });
    }

    brackets.push({ round: 1, matches: round1Matches });

    // Rounds subsequentes (placeholders)
    for (let r = 2; r <= rounds; r++) {
      const matches: BracketMatch[] = [];
      const numMatches = Math.pow(2, rounds - r);

      for (let m = 0; m < numMatches; m++) {
        matches.push({
          id: `r${r}m${m}`,
          player1: null!,  // TBD
          player2: null!,  // TBD
          status: 'pending'
        });
      }

      brackets.push({ round: r, matches });
    }

    tournament.brackets = brackets;
    tournament.status = 'in_progress';

    await this.tournamentRepository.update(tournament);

    return brackets;
  }

  // Registrar resultado de match
  async reportMatchResult(
    tournamentId: string,
    matchId: string,
    winnerId: string,
    score: { player1: number; player2: number }
  ): Promise<void> {
    const tournament = await this.getTournament(tournamentId);
    const match = this.findMatch(tournament.brackets!, matchId);

    if (!match) {
      throw new Error('Match not found');
    }

    // Atualizar match
    match.winner = match.player1.id === winnerId ? match.player1 : match.player2;
    match.score = score;
    match.completedAt = new Date();
    match.status = 'completed';

    // Avançar vencedor para próximo round
    await this.advanceWinner(tournament, match);

    await this.tournamentRepository.update(tournament);

    // Verificar se torneio acabou
    if (this.isTournamentComplete(tournament)) {
      await this.finalizeTournament(tournament);
    }
  }

  // Finalizar torneio e distribuir prêmios
  private async finalizeTournament(tournament: Tournament): Promise<void> {
    const finalMatch = this.getFinalMatch(tournament);
    const champion = finalMatch.winner!;
    const runnerUp = finalMatch.player1.id === champion.id
      ? finalMatch.player2
      : finalMatch.player1;

    // Distribuir prêmios
    const prizes = tournament.prizeDistribution;

    await this.awardPrize(champion.id, prizes.first, tournament, 1);
    await this.awardPrize(runnerUp.id, prizes.second, tournament, 2);

    // 3º e 4º lugar (se houver)
    if (prizes.third) {
      const thirdPlace = this.getThirdPlace(tournament);
      await this.awardPrize(thirdPlace.id, prizes.third, tournament, 3);
    }

    // Badge de campeão
    await this.badgeService.award(champion.id, {
      id: `tournament_winner_${tournament.id}`,
      name: `Campeão: ${tournament.name}`,
      rarity: 'epic'
    });

    tournament.status = 'completed';
    tournament.endDate = new Date();

    await this.tournamentRepository.update(tournament);

    // Anunciar campeão
    await this.notificationService.announceTournamentWinner(
      tournament,
      champion
    );
  }

  private async awardPrize(
    playerId: string,
    amount: number,
    tournament: Tournament,
    position: number
  ): Promise<void> {
    await this.walletService.add(playerId, amount, {
      type: TransactionType.TOURNAMENT_PRIZE,
      description: `${position}º lugar - ${tournament.name}`,
      metadata: {
        tournamentId: tournament.id,
        position
      }
    });
  }
}

8.3 Formatos Especiais

Round Robin (Todos contra todos)

typescript
class RoundRobinTournament {
  generateFixtures(participants: Player[]): Fixture[] {
    const fixtures: Fixture[] = [];
    const n = participants.length;

    // Algoritmo round-robin circular
    for (let round = 0; round < n - 1; round++) {
      for (let i = 0; i < n / 2; i++) {
        const player1 = participants[i];
        const player2 = participants[n - 1 - i];

        fixtures.push({
          round: round + 1,
          player1,
          player2,
          scheduledDate: this.calculateMatchDate(round, i)
        });
      }

      // Rotacionar jogadores (exceto o primeiro)
      participants = [
        participants[0],
        participants[n - 1],
        ...participants.slice(1, n - 1)
      ];
    }

    return fixtures;
  }

  calculateStandings(fixtures: Fixture[]): Standing[] {
    const standings = new Map<string, Standing>();

    for (const fixture of fixtures) {
      if (!fixture.completed) continue;

      // Atualizar estatísticas
      this.updateStanding(standings, fixture.player1, fixture);
      this.updateStanding(standings, fixture.player2, fixture);
    }

    return Array.from(standings.values())
      .sort((a, b) => {
        // Ordenar por: pontos, saldo, gols
        if (b.points !== a.points) return b.points - a.points;
        if (b.goalDifference !== a.goalDifference) return b.goalDifference - a.goalDifference;
        return b.goalsFor - a.goalsFor;
      });
  }
}

9. Vídeo e Highlights

9.1 Sistema de Gravação

typescript
interface MatchRecording {
  matchId: string;
  videoUrl: string;
  duration: number;
  format: 'mp4' | 'webm';
  resolution: '720p' | '1080p' | '4k';

  // Metadata
  sport: string;
  players: Player[];
  finalScore: Score;
  recordedAt: Date;

  // Highlights marcados
  highlights: Highlight[];

  // Processamento
  status: 'processing' | 'ready' | 'failed';
  thumbnailUrl: string;
}

interface Highlight {
  id: string;
  matchId: string;
  playerId: string;

  // Timing
  startTime: number;  // Segundos
  endTime: number;
  duration: number;

  // Tipo
  type: HighlightType;
  description: string;

  // Clipe gerado
  clipUrl: string;
  thumbnailUrl: string;

  // Social
  likes: number;
  views: number;
  shares: number;
  comments: Comment[];

  // Tags
  tags: string[];

  createdAt: Date;
}

enum HighlightType {
  GOAL = 'goal',
  ASSIST = 'assist',
  SAVE = 'save',
  DUNK = 'dunk',
  THREE_POINTER = 'three_pointer',
  BLOCK = 'block',
  STEAL = 'steal',
  CUSTOM = 'custom'
}

9.2 Botão de Highlight Durante Partida

typescript
interface HighlightButton {
  // Estado
  isRecording: boolean;
  isProcessing: boolean;

  // Configurações
  captureWindow: 15;  // Captura 15s antes + 5s depois do clique

  // Buffer
  replayBuffer: CircularBuffer<VideoFrame>;
  bufferSize: 20;  // 20 segundos em buffer
}

class HighlightService {
  // Quando jogador clica no botão
  async captureHighlight(
    matchId: string,
    playerId: string,
    type: HighlightType,
    timestamp: number
  ): Promise<Highlight> {
    // Marcar momento
    const startTime = Math.max(0, timestamp - 15);  // 15s antes
    const endTime = timestamp + 5;                   // 5s depois

    const highlight = await this.highlightRepository.create({
      matchId,
      playerId,
      type,
      startTime,
      endTime,
      duration: 20,
      status: 'processing'
    });

    // Processar assíncrono
    this.processHighlight(highlight);

    return highlight;
  }

  // Processamento de vídeo
  private async processHighlight(highlight: Highlight): Promise<void> {
    const match = await this.matchService.getMatch(highlight.matchId);

    // Extrair clipe do vídeo completo
    const clip = await this.videoService.extractClip(
      match.videoUrl,
      highlight.startTime,
      highlight.endTime
    );

    // Aplicar efeitos
    const enhanced = await this.videoService.enhance(clip, {
      slowMotion: true,
      replay: true,
      overlays: {
        playerName: true,
        timestamp: true,
        score: true
      }
    });

    // Upload
    const clipUrl = await this.storageService.upload(enhanced);

    // Gerar thumbnail
    const thumbnail = await this.videoService.generateThumbnail(enhanced);
    const thumbnailUrl = await this.storageService.upload(thumbnail);

    // Atualizar highlight
    highlight.clipUrl = clipUrl;
    highlight.thumbnailUrl = thumbnailUrl;
    highlight.status = 'ready';

    await this.highlightRepository.update(highlight);

    // Notificar player
    await this.notificationService.notifyHighlightReady(
      highlight.playerId,
      highlight
    );
  }
}

9.3 Melhores Momentos Automáticos

typescript
interface AutoHighlightDetection {
  // ML Model para detectar momentos importantes
  model: 'highlight_detector_v1';

  // Eventos que triggam detecção automática
  triggers: [
    'goal_scored',
    'assist_made',
    'save_made',
    'three_pointer',
    'dunk',
    'comeback_moment',
    'game_winning_play'
  ];

  // Confidence threshold
  minConfidence: 0.85;
}

class AutoHighlightService {
  async detectHighlights(matchId: string): Promise<Highlight[]> {
    const match = await this.matchService.getMatch(matchId);
    const events = await this.eventService.getMatchEvents(matchId);

    const highlights: Highlight[] = [];

    for (const event of events) {
      // Verificar se é highlight-worthy
      if (this.isHighlightEvent(event)) {
        const highlight = await this.createAutoHighlight(match, event);
        highlights.push(highlight);
      }
    }

    // Processar todos
    await Promise.all(
      highlights.map(h => this.highlightService.processHighlight(h))
    );

    return highlights;
  }

  private isHighlightEvent(event: MatchEvent): boolean {
    const highlightEvents = [
      'goal',
      'assist',
      'save',
      'three_pointer',
      'dunk',
      'block',
      'steal',
      'game_winner'
    ];

    return highlightEvents.includes(event.type);
  }

  // Detectar momentos "clutch"
  async detectClutchMoments(matchId: string): Promise<Highlight[]> {
    const match = await this.matchService.getMatch(matchId);
    const clutchMoments: Highlight[] = [];

    // Último minuto com diferença <= 5 pontos
    const finalMinuteEvents = match.events.filter(e =>
      e.timestamp >= match.duration - 60 &&
      Math.abs(e.scoreAfter.team1 - e.scoreAfter.team2) <= 5
    );

    for (const event of finalMinuteEvents) {
      if (['goal', 'basket', 'score'].includes(event.type)) {
        clutchMoments.push(await this.createClutchHighlight(match, event));
      }
    }

    return clutchMoments;
  }
}

9.4 Compartilhamento Social

typescript
interface SocialSharing {
  platforms: ['twitter', 'instagram', 'tiktok', 'whatsapp', 'facebook'];

  // Formatação por plataforma
  formats: {
    twitter: {
      maxDuration: 140,
      aspectRatio: '16:9',
      maxSize: '512MB'
    },
    instagram: {
      maxDuration: 60,
      aspectRatio: '9:16',  // Stories
      maxSize: '100MB'
    },
    tiktok: {
      maxDuration: 60,
      aspectRatio: '9:16',
      maxSize: '287MB'
    }
  };

  // Templates de caption
  captions: {
    goal: "🔥 Golaço! {player} na partida de {sport}",
    assist: "👌 Assistência perfeita de {player}!",
    win: "🏆 Vitória de {player}! {score}",
    highlight: "⚡ Momento épico de {player}!"
  };
}

class SocialSharingService {
  async shareHighlight(
    highlightId: string,
    platform: string,
    customCaption?: string
  ): Promise<ShareResult> {
    const highlight = await this.highlightService.getHighlight(highlightId);

    // Adaptar vídeo para plataforma
    const adapted = await this.adaptForPlatform(highlight, platform);

    // Gerar caption
    const caption = customCaption || this.generateCaption(highlight);

    // Share URL
    const shareUrl = this.generateShareUrl(platform, adapted, caption);

    // Registrar share
    await this.analyticsService.trackShare(highlightId, platform);

    // Incrementar contador
    highlight.shares++;
    await this.highlightRepository.update(highlight);

    return {
      success: true,
      url: shareUrl,
      platform
    };
  }

  // Deep links
  generateShareUrl(platform: string, video: Video, caption: string): string {
    const encodedCaption = encodeURIComponent(caption);
    const videoUrl = encodeURIComponent(video.url);

    const deepLinks = {
      twitter: `https://twitter.com/intent/tweet?text=${encodedCaption}&url=${videoUrl}`,
      instagram: `instagram://library?video=${videoUrl}`,
      tiktok: `snssdk1180://uploadvideo?video=${videoUrl}`,
      whatsapp: `whatsapp://send?text=${encodedCaption}%20${videoUrl}`,
      facebook: `https://www.facebook.com/sharer/sharer.php?u=${videoUrl}&quote=${encodedCaption}`
    };

    return deepLinks[platform] || videoUrl;
  }
}

9.5 Integração com Conquistas

typescript
interface HighlightBadges {
  viral: {
    name: 'Viral',
    description: 'Highlight com 1000+ views',
    requirement: { views: 1000 }
  },
  superstar: {
    name: 'Superstar',
    description: 'Highlight com 10,000+ views',
    requirement: { views: 10000 }
  },
  creator: {
    name: 'Criador de Conteúdo',
    description: 'Criou 50 highlights',
    requirement: { highlightsCreated: 50 }
  },
  popular: {
    name: 'Popular',
    description: 'Recebeu 100 likes em highlights',
    requirement: { totalLikes: 100 }
  }
}

class HighlightGamification {
  async checkBadges(playerId: string): Promise<Badge[]> {
    const player = await this.playerService.getPlayer(playerId);
    const highlights = await this.highlightService.getPlayerHighlights(playerId);

    const newBadges: Badge[] = [];

    // Viral
    const viral = highlights.find(h => h.views >= 1000);
    if (viral && !player.badges.includes('viral')) {
      newBadges.push(await this.badgeService.award(playerId, 'viral'));
    }

    // Superstar
    const superstar = highlights.find(h => h.views >= 10000);
    if (superstar && !player.badges.includes('superstar')) {
      newBadges.push(await this.badgeService.award(playerId, 'superstar'));
    }

    // Creator
    if (highlights.length >= 50 && !player.badges.includes('creator')) {
      newBadges.push(await this.badgeService.award(playerId, 'creator'));
    }

    // Popular
    const totalLikes = highlights.reduce((sum, h) => sum + h.likes, 0);
    if (totalLikes >= 100 && !player.badges.includes('popular')) {
      newBadges.push(await this.badgeService.award(playerId, 'popular'));
    }

    return newBadges;
  }

  // XP por engagement
  async rewardEngagement(highlightId: string): Promise<void> {
    const highlight = await this.highlightService.getHighlight(highlightId);

    // XP por likes
    if (highlight.likes >= 10) {
      await this.xpService.add(highlight.playerId, 50, {
        source: 'highlight_likes',
        highlightId
      });
    }

    // XP por views
    if (highlight.views >= 100) {
      await this.xpService.add(highlight.playerId, 100, {
        source: 'highlight_views',
        highlightId
      });
    }

    // Bonus por viralização
    if (highlight.views >= 1000) {
      await this.xpService.add(highlight.playerId, 500, {
        source: 'highlight_viral',
        highlightId
      });
    }
  }
}

10. Métricas de Engajamento

10.1 Core Metrics

typescript
interface EngagementMetrics {
  // DAU, WAU, MAU
  dau: number;  // Daily Active Users
  wau: number;  // Weekly Active Users
  mau: number;  // Monthly Active Users

  // Stickiness
  stickiness: number;  // DAU/MAU ratio (ideal: 20%+)

  // Retention
  d1Retention: number;   // Voltam no dia seguinte
  d7Retention: number;   // Voltam na primeira semana
  d30Retention: number;  // Voltam no primeiro mês

  // Session
  avgSessionTime: number;     // Minutos
  avgSessionsPerDay: number;
  avgMatchesPerSession: number;

  // Engagement
  xpEarnedPerDay: number;
  badgesUnlockedPerDay: number;
  matchesPlayedPerDay: number;

  // Monetização (ArenaCoins)
  arenaCoinsSpent: number;
  arenaCoinsEarned: number;
  arenaCoinsBalance: number;
  purchaseConversionRate: number;  // % que compraram coins

  // Social
  friendInviteRate: number;
  highlightShareRate: number;
  matchesWithFriends: number;
}

class AnalyticsService {
  // Calcular DAU
  async calculateDAU(date: Date): Promise<number> {
    const startOfDay = startOfDay(date);
    const endOfDay = endOfDay(date);

    return await this.userRepository
      .where('lastActiveAt')
      .between(startOfDay, endOfDay)
      .count();
  }

  // Calcular stickiness
  async calculateStickiness(date: Date): Promise<number> {
    const dau = await this.calculateDAU(date);
    const mau = await this.calculateMAU(date);

    return (dau / mau) * 100;
  }

  // Calcular retention
  async calculateRetention(cohortDate: Date, days: number): Promise<number> {
    // Usuários que fizeram signup em cohortDate
    const cohort = await this.userRepository
      .where('createdAt')
      .between(startOfDay(cohortDate), endOfDay(cohortDate))
      .toArray();

    // Quantos voltaram após N dias
    const targetDate = addDays(cohortDate, days);
    const retained = cohort.filter(user =>
      this.wasActiveOn(user, targetDate)
    );

    return (retained.length / cohort.length) * 100;
  }

  // Session time
  async calculateAvgSessionTime(userId: string, period: Date): Promise<number> {
    const sessions = await this.sessionRepository
      .where('userId').equals(userId)
      .where('startedAt').gte(period)
      .toArray();

    const totalTime = sessions.reduce((sum, s) => sum + s.duration, 0);
    return totalTime / sessions.length;
  }
}

10.2 Funnel Metrics

typescript
interface OnboardingFunnel {
  // Step 1: Sign up
  signups: number;

  // Step 2: Complete profile
  profileCompleted: number;
  profileCompletionRate: number;

  // Step 3: First match
  firstMatch: number;
  firstMatchRate: number;

  // Step 4: Second session
  secondSession: number;
  secondSessionRate: number;

  // Step 5: Week 1 retention
  week1Retention: number;
  week1RetentionRate: number;

  // Drop-off points
  dropoffs: {
    afterSignup: number;
    afterProfile: number;
    afterFirstMatch: number;
    afterSecondSession: number;
  };
}

class FunnelAnalytics {
  async analyzeFunnel(startDate: Date, endDate: Date): Promise<OnboardingFunnel> {
    const signups = await this.getSignups(startDate, endDate);

    const funnel: OnboardingFunnel = {
      signups: signups.length,
      profileCompleted: 0,
      profileCompletionRate: 0,
      firstMatch: 0,
      firstMatchRate: 0,
      secondSession: 0,
      secondSessionRate: 0,
      week1Retention: 0,
      week1RetentionRate: 0,
      dropoffs: {
        afterSignup: 0,
        afterProfile: 0,
        afterFirstMatch: 0,
        afterSecondSession: 0
      }
    };

    for (const user of signups) {
      // Step 2: Profile
      if (user.profileCompleted) {
        funnel.profileCompleted++;

        // Step 3: First match
        if (user.matchesPlayed >= 1) {
          funnel.firstMatch++;

          // Step 4: Second session
          if (user.sessions >= 2) {
            funnel.secondSession++;

            // Step 5: Week 1
            if (this.wasActiveInFirstWeek(user)) {
              funnel.week1Retention++;
            } else {
              funnel.dropoffs.afterSecondSession++;
            }
          } else {
            funnel.dropoffs.afterFirstMatch++;
          }
        } else {
          funnel.dropoffs.afterProfile++;
        }
      } else {
        funnel.dropoffs.afterSignup++;
      }
    }

    // Calcular rates
    funnel.profileCompletionRate = (funnel.profileCompleted / funnel.signups) * 100;
    funnel.firstMatchRate = (funnel.firstMatch / funnel.profileCompleted) * 100;
    funnel.secondSessionRate = (funnel.secondSession / funnel.firstMatch) * 100;
    funnel.week1RetentionRate = (funnel.week1Retention / funnel.secondSession) * 100;

    return funnel;
  }
}

10.3 Cohort Analysis

typescript
interface CohortAnalysis {
  cohortDate: Date;
  cohortSize: number;

  // Retention por período
  retention: {
    day1: number;
    day3: number;
    day7: number;
    day14: number;
    day30: number;
    day60: number;
    day90: number;
  };

  // Engagement por período
  engagement: {
    avgMatchesPerUser: number;
    avgXPEarned: number;
    avgSessionTime: number;
  };

  // Monetização
  monetization: {
    payers: number;
    payerRate: number;
    arpu: number;  // Average Revenue Per User
    arppu: number; // Average Revenue Per Paying User
  };
}

class CohortAnalytics {
  async analyzeCohort(cohortDate: Date): Promise<CohortAnalysis> {
    const cohort = await this.getCohort(cohortDate);

    const analysis: CohortAnalysis = {
      cohortDate,
      cohortSize: cohort.length,
      retention: {
        day1: await this.calculateRetention(cohort, 1),
        day3: await this.calculateRetention(cohort, 3),
        day7: await this.calculateRetention(cohort, 7),
        day14: await this.calculateRetention(cohort, 14),
        day30: await this.calculateRetention(cohort, 30),
        day60: await this.calculateRetention(cohort, 60),
        day90: await this.calculateRetention(cohort, 90)
      },
      engagement: await this.calculateEngagement(cohort),
      monetization: await this.calculateMonetization(cohort)
    };

    return analysis;
  }

  // Visualização de cohort
  async generateCohortTable(startDate: Date, endDate: Date): Promise<CohortTable> {
    const cohorts: CohortAnalysis[] = [];

    let currentDate = startDate;
    while (currentDate <= endDate) {
      cohorts.push(await this.analyzeCohort(currentDate));
      currentDate = addDays(currentDate, 7); // Cohorts semanais
    }

    return {
      cohorts,
      avgRetention: this.calculateAverageRetention(cohorts),
      bestCohort: this.findBestCohort(cohorts),
      worstCohort: this.findWorstCohort(cohorts)
    };
  }
}

10.4 Feature Adoption

typescript
interface FeatureAdoption {
  featureName: string;
  launchDate: Date;

  // Adoção
  totalUsers: number;
  adoptedUsers: number;
  adoptionRate: number;

  // Tempo para adoção
  avgTimeToAdopt: number;  // Dias

  // Engagement
  avgUsagePerUser: number;
  dailyActiveInFeature: number;

  // Por segmento
  byLevel: Map<number, FeatureUsage>;
  byTenure: Map<string, FeatureUsage>;
}

interface FeatureUsage {
  users: number;
  adoptionRate: number;
  avgUsage: number;
}

class FeatureAnalytics {
  // Analisar adoção de feature
  async analyzeFeature(featureName: string): Promise<FeatureAdoption> {
    const feature = await this.featureRepository.findByName(featureName);
    const users = await this.userRepository.findAll();

    const adopted = users.filter(u =>
      this.hasUsedFeature(u, featureName)
    );

    return {
      featureName,
      launchDate: feature.launchDate,
      totalUsers: users.length,
      adoptedUsers: adopted.length,
      adoptionRate: (adopted.length / users.length) * 100,
      avgTimeToAdopt: this.calculateAvgTimeToAdopt(adopted, feature),
      avgUsagePerUser: this.calculateAvgUsage(adopted, featureName),
      dailyActiveInFeature: await this.getDailyActive(featureName),
      byLevel: await this.getAdoptionByLevel(featureName),
      byTenure: await this.getAdoptionByTenure(featureName)
    };
  }

  // Comparar features
  async compareFeatures(features: string[]): Promise<FeatureComparison> {
    const analyses = await Promise.all(
      features.map(f => this.analyzeFeature(f))
    );

    return {
      features: analyses,
      mostAdopted: this.findMostAdopted(analyses),
      leastAdopted: this.findLeastAdopted(analyses),
      fastestAdoption: this.findFastestAdoption(analyses),
      recommendations: this.generateRecommendations(analyses)
    };
  }
}

10.5 Dashboards

typescript
interface GamificationDashboard {
  // Overview
  overview: {
    totalPlayers: number;
    activePlayers: number;
    totalMatches: number;
    totalXPEarned: number;
    totalBadgesUnlocked: number;
  };

  // Engagement
  engagement: {
    dau: number;
    wau: number;
    mau: number;
    stickiness: number;
    avgSessionTime: number;
    matchesPerUser: number;
  };

  // Progression
  progression: {
    avgLevel: number;
    levelDistribution: Map<number, number>;
    xpEarnedToday: number;
    badgesUnlockedToday: number;
  };

  // Social
  social: {
    highlightsCreated: number;
    highlightsShared: number;
    friendInvites: number;
    activeFriendships: number;
  };

  // Economy
  economy: {
    arenaCoinsInCirculation: number;
    arenaCoinsSpentToday: number;
    arenaCoinsEarnedToday: number;
    topSpenders: Player[];
  };

  // Top performers
  leaderboards: {
    topXP: Player[];
    topLevel: Player[];
    topWinRate: Player[];
    topStreak: Player[];
  };

  // Alerts
  alerts: Alert[];
}

class DashboardService {
  async generateDashboard(date: Date): Promise<GamificationDashboard> {
    return {
      overview: await this.getOverview(),
      engagement: await this.getEngagement(date),
      progression: await this.getProgression(date),
      social: await this.getSocial(date),
      economy: await this.getEconomy(date),
      leaderboards: await this.getLeaderboards(),
      alerts: await this.getAlerts()
    };
  }

  private async getAlerts(): Promise<Alert[]> {
    const alerts: Alert[] = [];

    // Queda no DAU
    const dauToday = await this.analyticsService.calculateDAU(new Date());
    const dauYesterday = await this.analyticsService.calculateDAU(subDays(new Date(), 1));

    if (dauToday < dauYesterday * 0.9) {
      alerts.push({
        type: 'warning',
        message: `DAU dropped ${((1 - dauToday/dauYesterday) * 100).toFixed(1)}%`,
        severity: 'high'
      });
    }

    // Retention baixa
    const d1Retention = await this.analyticsService.calculateRetention(subDays(new Date(), 1), 1);
    if (d1Retention < 40) {
      alerts.push({
        type: 'warning',
        message: `D1 retention is ${d1Retention.toFixed(1)}% (target: 40%+)`,
        severity: 'high'
      });
    }

    return alerts;
  }
}

11. Implementação Técnica

11.1 Entidades de Domínio

typescript
// Player Gamification Profile
interface PlayerGamification {
  playerId: string;

  // Progressão
  level: number;
  currentXP: number;
  totalXP: number;
  title: string;

  // Stats
  matchesPlayed: number;
  wins: number;
  losses: number;
  winRate: number;

  // Rating
  rating: number;
  ratingHistory: RatingHistory[];

  // Badges
  badges: Badge[];
  badgeProgress: BadgeProgress[];

  // Streaks
  loginStreak: Streak;
  winStreak: Streak;

  // Challenges
  activeChallenges: Challenge[];
  completedChallenges: Challenge[];

  // Skills
  skills: Map<string, Skill>;

  // Wallet
  walletId: string;

  // Preferences
  preferences: {
    showInLeaderboard: boolean;
    showStats: boolean;
    notifications: NotificationPreferences;
  };

  // Timestamps
  createdAt: Date;
  updatedAt: Date;
  lastActiveAt: Date;
}

// Badge Progress (para badges em progresso)
interface BadgeProgress {
  badgeId: string;
  currentProgress: number;
  targetProgress: number;
  progressPercent: number;
  unlockedAt?: Date;
}

// Rating History (para gráficos)
interface RatingHistory {
  rating: number;
  change: number;
  matchId: string;
  timestamp: Date;
}

11.2 Eventos

typescript
// Domain Events
enum GamificationEvent {
  // XP
  XP_EARNED = 'xp.earned',
  LEVEL_UP = 'level.up',

  // Badges
  BADGE_UNLOCKED = 'badge.unlocked',
  BADGE_PROGRESS = 'badge.progress',

  // Challenges
  CHALLENGE_ASSIGNED = 'challenge.assigned',
  CHALLENGE_PROGRESS = 'challenge.progress',
  CHALLENGE_COMPLETED = 'challenge.completed',

  // Streaks
  STREAK_STARTED = 'streak.started',
  STREAK_CONTINUED = 'streak.continued',
  STREAK_MILESTONE = 'streak.milestone',
  STREAK_BROKEN = 'streak.broken',

  // Wallet
  COINS_EARNED = 'coins.earned',
  COINS_SPENT = 'coins.spent',
  COINS_TRANSFERRED = 'coins.transferred',

  // Match
  MATCH_COMPLETED = 'match.completed',
  MATCH_WON = 'match.won',
  MATCH_MVP = 'match.mvp',

  // Social
  HIGHLIGHT_CREATED = 'highlight.created',
  HIGHLIGHT_SHARED = 'highlight.shared',
  FRIEND_INVITED = 'friend.invited',

  // Ranking
  RANK_CHANGED = 'rank.changed',
  RANK_MILESTONE = 'rank.milestone'
}

interface Event<T = any> {
  id: string;
  type: GamificationEvent;
  aggregateId: string;  // playerId
  data: T;
  timestamp: Date;
  metadata?: object;
}

// Event Bus
class EventBus {
  private handlers = new Map<GamificationEvent, EventHandler[]>();

  subscribe(event: GamificationEvent, handler: EventHandler): void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, []);
    }
    this.handlers.get(event)!.push(handler);
  }

  async publish<T>(event: GamificationEvent, data: T): Promise<void> {
    const handlers = this.handlers.get(event) || [];

    await Promise.all(
      handlers.map(handler => handler.handle({
        id: uuidv4(),
        type: event,
        aggregateId: data.playerId || data.userId,
        data,
        timestamp: new Date()
      }))
    );
  }
}

11.3 Rule Engine

typescript
// Rule Engine para badges e achievements
interface Rule {
  id: string;
  name: string;
  description: string;
  conditions: Condition[];
  action: Action;
  priority: number;
}

interface Condition {
  type: 'equals' | 'greaterThan' | 'lessThan' | 'between' | 'contains';
  field: string;
  value: any;
  operator?: 'and' | 'or';
}

interface Action {
  type: 'award_badge' | 'add_xp' | 'add_coins' | 'unlock_feature';
  params: object;
}

class RuleEngine {
  private rules: Rule[] = [];

  registerRule(rule: Rule): void {
    this.rules.push(rule);
    this.rules.sort((a, b) => b.priority - a.priority);
  }

  async evaluate(player: Player, context: Context): Promise<Action[]> {
    const actions: Action[] = [];

    for (const rule of this.rules) {
      if (await this.evaluateConditions(rule.conditions, player, context)) {
        actions.push(rule.action);
      }
    }

    return actions;
  }

  private async evaluateConditions(
    conditions: Condition[],
    player: Player,
    context: Context
  ): Promise<boolean> {
    for (const condition of conditions) {
      const value = this.getFieldValue(player, context, condition.field);
      const result = this.evaluateCondition(condition, value);

      if (!result) return false;
    }

    return true;
  }

  private evaluateCondition(condition: Condition, value: any): boolean {
    switch (condition.type) {
      case 'equals':
        return value === condition.value;
      case 'greaterThan':
        return value > condition.value;
      case 'lessThan':
        return value < condition.value;
      case 'between':
        return value >= condition.value[0] && value <= condition.value[1];
      case 'contains':
        return Array.isArray(value) && value.includes(condition.value);
      default:
        return false;
    }
  }
}

// Exemplo: Registrar regras para badges
const badgeRules: Rule[] = [
  {
    id: 'first_match_badge',
    name: 'First Match Badge',
    description: 'Award badge after first match',
    conditions: [
      { type: 'equals', field: 'matchesPlayed', value: 1 }
    ],
    action: {
      type: 'award_badge',
      params: { badgeId: 'first_match' }
    },
    priority: 100
  },
  {
    id: 'level_10_badge',
    name: 'Level 10 Badge',
    description: 'Award badge when reaching level 10',
    conditions: [
      { type: 'equals', field: 'level', value: 10 }
    ],
    action: {
      type: 'award_badge',
      params: { badgeId: 'rising_star' }
    },
    priority: 90
  }
];

11.4 APIs

typescript
// REST API Endpoints

// GET /api/gamification/profile
interface GetProfileResponse {
  player: PlayerGamification;
  nextLevel: {
    level: number;
    xpRequired: number;
    xpRemaining: number;
    estimatedMatches: number;
  };
  recentAchievements: Achievement[];
}

// GET /api/gamification/leaderboard
interface GetLeaderboardRequest {
  type?: RankingType;
  period?: RankingPeriod;
  sport?: string;
  limit?: number;
  offset?: number;
}

interface GetLeaderboardResponse {
  rankings: Ranking[];
  userRank?: Ranking;
  totalPlayers: number;
}

// GET /api/gamification/badges
interface GetBadgesResponse {
  earned: Badge[];
  inProgress: BadgeProgress[];
  available: Badge[];
  locked: Badge[];
}

// GET /api/gamification/challenges
interface GetChallengesResponse {
  daily: Challenge[];
  weekly: Challenge[];
  special: Challenge[];
  progress: Map<string, number>;
}

// POST /api/gamification/highlight
interface CreateHighlightRequest {
  matchId: string;
  timestamp: number;
  type: HighlightType;
  description?: string;
}

interface CreateHighlightResponse {
  highlight: Highlight;
  estimatedProcessingTime: number;
}

// POST /api/gamification/match/staked
interface CreateStakedMatchRequest {
  sport: string;
  type: MatchType;
  entryFee: number;
  maxPlayers: number;
  minLevel?: number;
  rules: MatchRules;
}

interface CreateStakedMatchResponse {
  match: StakedMatch;
  yourEntry: {
    frozen: number;
    balance: number;
  };
}

// WebSocket Events
interface WebSocketEvents {
  // Real-time updates
  'xp:earned': { amount: number; total: number };
  'level:up': { level: number; title: string };
  'badge:unlocked': Badge;
  'challenge:progress': { challengeId: string; progress: number };
  'rank:changed': { oldRank: number; newRank: number };
  'highlight:ready': Highlight;
  'match:started': StakedMatch;
  'match:completed': { match: StakedMatch; prizes: Prize[] };
}

11.5 Processamento Assíncrono

typescript
// Job Queue para tarefas pesadas
interface Job {
  id: string;
  type: JobType;
  data: any;
  priority: number;
  attempts: number;
  maxAttempts: number;
  createdAt: Date;
  processedAt?: Date;
}

enum JobType {
  PROCESS_HIGHLIGHT = 'process_highlight',
  CALCULATE_RANKINGS = 'calculate_rankings',
  AWARD_BADGES = 'award_badges',
  GENERATE_CHALLENGES = 'generate_challenges',
  UPDATE_STATS = 'update_stats',
  SEND_NOTIFICATIONS = 'send_notifications'
}

class JobQueue {
  async enqueue(job: Omit<Job, 'id' | 'attempts' | 'createdAt'>): Promise<string> {
    const jobId = uuidv4();

    await this.jobRepository.create({
      ...job,
      id: jobId,
      attempts: 0,
      createdAt: new Date()
    });

    // Notificar worker
    await this.redis.publish('jobs:new', jobId);

    return jobId;
  }

  async process(): Promise<void> {
    while (true) {
      const job = await this.jobRepository.getNext();

      if (!job) {
        await this.sleep(1000);
        continue;
      }

      try {
        await this.processJob(job);

        await this.jobRepository.markCompleted(job.id);
      } catch (error) {
        job.attempts++;

        if (job.attempts >= job.maxAttempts) {
          await this.jobRepository.markFailed(job.id, error);
        } else {
          await this.jobRepository.retry(job.id);
        }
      }
    }
  }

  private async processJob(job: Job): Promise<void> {
    const handlers = {
      [JobType.PROCESS_HIGHLIGHT]: this.processHighlight,
      [JobType.CALCULATE_RANKINGS]: this.calculateRankings,
      [JobType.AWARD_BADGES]: this.awardBadges,
      [JobType.GENERATE_CHALLENGES]: this.generateChallenges,
      [JobType.UPDATE_STATS]: this.updateStats,
      [JobType.SEND_NOTIFICATIONS]: this.sendNotifications
    };

    const handler = handlers[job.type];
    if (!handler) {
      throw new Error(`Unknown job type: ${job.type}`);
    }

    await handler.call(this, job.data);
  }
}

Conclusão

Este documento especifica um sistema completo de gamificação baseado em princípios sólidos de neurociência e psicologia comportamental, garantindo:

  1. Engajamento Saudável: Sem dark patterns, com limites e proteções
  2. Progressão Balanceada: Curva de XP justa, múltiplas formas de progressão
  3. Competição Não-Punitiva: Rankings contextuais, celebração de pequenas vitórias
  4. Economia Sustentável: ArenaCoins com fontes e sinks balanceados
  5. Social e Viral: Highlights compartilháveis, conquistas sociais
  6. Métricas Robustas: Dashboards e analytics para otimização contínua

Próximos Passos:

  • Implementação em fases (MVP → Full)
  • A/B testing de mecânicas
  • Balanceamento contínuo baseado em dados
  • Expansão de badges e desafios baseado em feedback

Documento criado por: Escriba (Technical Writer) Data: 2024-01-15 Versão: 1.0