Files
LearnIT/lib/core/services/gamification_service.dart
2026-05-17 18:27:22 +01:00

776 lines
28 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../models/user_stats.dart';
import '../models/class_stats.dart';
import '../models/achievement.dart';
import '../utils/logger.dart';
/// Serviço para gerenciar gamificação e conquistas
class GamificationService {
static final FirebaseFirestore _firestore = FirebaseFirestore.instance;
static final FirebaseAuth _auth = FirebaseAuth.instance;
/// Atualizar streak diário do usuário
static Future<void> updateDailyStreak(String userId) async {
try {
final userStatsRef = _firestore.collection('users').doc(userId);
final userStatsDoc = await userStatsRef.get();
if (!userStatsDoc.exists) {
// Criar estatísticas iniciais
await userStatsRef.set({
'userId': userId,
'currentStreak': 1,
'longestStreak': 1,
'totalStudyTime': 0,
'lastActivityDate': Timestamp.now(),
'weeklyStudyTime': 0,
'monthlyStudyTime': 0,
'masteredConcepts': [],
'unlockedAchievements': [],
});
return;
}
final userStats = UserStats.fromFirestore(userStatsDoc.data()!, userId);
final now = DateTime.now();
final lastActivity = userStats.lastActivityDate;
int newStreak = userStats.currentStreak;
int newLongestStreak = userStats.longestStreak;
Logger.info('=== UPDATE DAILY STREAK DEBUG ===');
Logger.info('Last activity: $lastActivity');
Logger.info('Current streak before update: $newStreak');
if (lastActivity == null) {
// Primeira atividade - iniciar streak
Logger.info('First activity detected - setting streak to 1');
newStreak = 1;
newLongestStreak = 1;
} else {
// Normalizar para início do dia para comparação correta
final today = DateTime(now.year, now.month, now.day);
final lastDay = DateTime(lastActivity.year, lastActivity.month, lastActivity.day);
final difference = today.difference(lastDay).inDays;
if (difference == 0) {
// Já ativou hoje, não alterar streak mas atualiza timestamp
// Corrigir streak se estiver em 0 (erro de dados)
if (newStreak == 0) {
newStreak = 1;
newLongestStreak = 1;
Logger.info('Correcting invalid streak (0) to 1');
await userStatsRef.update({
'currentStreak': 1,
'longestStreak': 1,
'lastActivityDate': Timestamp.now(),
});
} else {
Logger.info('Already active today, streak unchanged: $newStreak');
await userStatsRef.update({
'lastActivityDate': Timestamp.now(),
});
}
return;
} else if (difference == 1) {
Logger.info('Consecutive activity detected, incrementing streak');
// Atividade consecutiva
newStreak++;
newLongestStreak = newStreak > newLongestStreak ? newStreak : newLongestStreak;
} else {
// Quebrou o streak
newStreak = 1;
newLongestStreak = newStreak > newLongestStreak ? newStreak : newLongestStreak;
}
}
Logger.info('Updating streak to: $newStreak');
await userStatsRef.update({
'currentStreak': newStreak,
'longestStreak': newLongestStreak,
'lastActivityDate': Timestamp.now(),
});
Logger.info('Streak updated successfully');
// Verificar conquistas de streak
await _checkStreakAchievements(userId, newStreak);
Logger.info('=== END UPDATE DAILY STREAK DEBUG ===');
} catch (e) {
Logger.error('Error updating daily streak: $e');
}
}
/// Registrar tempo de estudo
static Future<void> recordStudyTime(String userId, int minutes) async {
try {
final userStatsRef = _firestore.collection('users').doc(userId);
final userStatsDoc = await userStatsRef.get();
if (!userStatsDoc.exists) {
await _createInitialUserStats(userId);
}
final userStats = UserStats.fromFirestore(userStatsDoc.data()!, userId);
final newTotalTime = userStats.totalStudyTime + minutes;
await userStatsRef.update({
'totalStudyTime': newTotalTime,
'weeklyStudyTime': FieldValue.increment(minutes),
'monthlyStudyTime': FieldValue.increment(minutes),
});
// Verificar conquistas de tempo de estudo
await _checkStudyTimeAchievements(userId, newTotalTime);
} catch (e) {
Logger.error('Error recording study time: $e');
}
}
/// Registrar atividade de quiz
static Future<void> recordQuizActivity(String userId, {
required int score,
required int totalQuestions,
required String materialName,
}) async {
try {
final userStatsRef = _firestore.collection('users').doc(userId);
// Atualizar streak
await updateDailyStreak(userId);
// Registrar tempo de estudo (estimado)
await recordStudyTime(userId, 15); // 15 minutos por quiz
// Verificar conquistas de quiz
await _checkQuizAchievements(userId, score, totalQuestions);
// Incrementar contador de quizzes completos
await userStatsRef.update({
'completedQuizzes': FieldValue.increment(1),
});
Logger.info('Incremented completed quizzes count');
// Atualizar conceitos dominados se score >= 50%
Logger.info('Checking if score qualifies for mastered concept: ${score / totalQuestions >= 0.5}');
if (score / totalQuestions >= 0.5) {
Logger.info('Adding mastered concept: $materialName with score: $score');
await _addMasteredConcept(userId, materialName, score);
} else {
Logger.info('Score too low for mastered concept: $score/$totalQuestions');
}
Logger.info('Quiz activity recorded for user $userId: $score/$totalQuestions');
} catch (e) {
Logger.error('Error recording quiz activity: $e');
}
}
/// Obter estatísticas do usuário
static Future<UserStats?> getUserStats(String userId) async {
try {
final doc = await _firestore.collection('users').doc(userId).get();
if (!doc.exists) {
// Criar estatísticas iniciais se não existirem
await _initializeUserStats(userId);
return await getUserStats(userId); // Chamada recursiva após inicialização
}
final data = doc.data() as Map<String, dynamic>;
// Garantir que completedQuizzes exista
if (!data.containsKey('completedQuizzes')) {
await _firestore.collection('users').doc(userId).update({
'completedQuizzes': 0,
});
data['completedQuizzes'] = 0;
}
return UserStats.fromFirestore(data, userId);
} catch (e) {
Logger.error('Error getting user stats: $e');
return null;
}
}
/// Obter estatísticas da turma
static Future<ClassStats?> getClassStats(String classId, {bool forceRefresh = false}) async {
try {
if (forceRefresh) {
// Forçar recálculo completo
return await _calculateClassStats(classId);
}
final classStatsDoc = await _firestore.collection('classStats').doc(classId).get();
if (!classStatsDoc.exists) {
return await _calculateClassStats(classId);
}
// Verificar se os dados estão desatualizados (mais de 1 hora)
final data = classStatsDoc.data()!;
final lastUpdated = data['lastUpdated'] as Timestamp?;
if (lastUpdated == null ||
DateTime.now().difference(lastUpdated.toDate()).inHours > 1) {
return await _calculateClassStats(classId);
}
return ClassStats.fromFirestore(data, classId);
} catch (e) {
Logger.error('Error getting class stats: $e');
return null;
}
}
/// Forçar atualização de estatísticas de todas as turmas de um professor
static Future<void> refreshAllClassStats(String teacherId) async {
try {
final classesSnapshot = await _firestore
.collection('classes')
.where('teacherId', isEqualTo: teacherId)
.get();
for (final classDoc in classesSnapshot.docs) {
await _calculateClassStats(classDoc.id);
}
Logger.info('Refreshed stats for ${classesSnapshot.docs.length} classes');
} catch (e) {
Logger.error('Error refreshing class stats: $e');
}
}
/// Obter ranking de alunos da turma
static Future<List<StudentRanking>> getClassRanking(String classId) async {
try {
// Primeiro, obter todos os alunos matriculados na turma
final enrollmentsSnapshot = await _firestore
.collection('enrollments')
.where('classId', isEqualTo: classId)
.get();
if (enrollmentsSnapshot.docs.isEmpty) {
Logger.info('No students enrolled in class $classId');
return [];
}
final studentIds = enrollmentsSnapshot.docs.map((doc) => doc['studentId'] as String).toList();
final rankings = <StudentRanking>[];
// Obter número real de quizzes disponíveis na turma
final quizzesSnapshot = await _firestore
.collection('teacherQuizzes')
.where('classIds', arrayContains: classId)
.get();
final totalAvailableQuizzes = quizzesSnapshot.docs.length;
// Para cada aluno, obter suas estatísticas
for (final studentId in studentIds) {
try {
final userStats = await getUserStats(studentId);
if (userStats != null) {
// Obter informações do usuário
final userDoc = await _firestore.collection('users').doc(studentId).get();
final userData = userDoc.data() as Map<String, dynamic>?;
// Calcular estatísticas para o ranking
final completedQuizzes = userStats.completedQuizzes;
final totalQuizzes = totalAvailableQuizzes > 0 ? totalAvailableQuizzes : 1;
final quizCompletionRate = completedQuizzes / totalQuizzes;
Logger.info('=== RANKING SCORE DEBUG ===');
Logger.info('Student ID: $studentId');
Logger.info('Completed quizzes: $completedQuizzes');
Logger.info('Total quizzes: $totalQuizzes');
Logger.info('Quiz completion rate: $quizCompletionRate (${(quizCompletionRate * 100).toInt()}%)');
Logger.info('Current streak: ${userStats.currentStreak}');
Logger.info('Total study time: ${userStats.totalStudyTime} minutes');
Logger.info('Mastered concepts: ${userStats.masteredConcepts.length}');
Logger.info('Unlocked achievements: ${userStats.unlockedAchievements.length}');
// Calcular score geral baseado em múltiplos fatores
final overallScore = _calculateOverallScore(userStats, quizCompletionRate);
Logger.info('Overall score calculated: $overallScore (${overallScore.toInt()}%)');
Logger.info('=== END RANKING SCORE DEBUG ===');
// Tentar obter um nome melhor para o aluno
String studentName = 'Aluno $studentId';
if (userData != null) {
studentName = userData['displayName'] ??
userData['email']?.split('@')[0] ??
'Aluno ${studentId.substring(0, 8)}...';
}
rankings.add(StudentRanking(
studentId: studentId,
studentName: studentName,
studentEmail: userData?['email'] ?? '',
overallScore: overallScore,
completedQuizzes: completedQuizzes,
totalQuizzes: totalQuizzes,
quizCompletionRate: quizCompletionRate,
currentStreak: userStats.currentStreak,
studyTimeMinutes: userStats.totalStudyTime,
lastActivity: userStats.lastActivityDate ?? DateTime.now(),
));
}
} catch (e) {
Logger.error('Error getting stats for student $studentId: $e');
continue;
}
}
// Ordenar por score geral
rankings.sort((a, b) => b.overallScore.compareTo(a.overallScore));
return rankings;
} catch (e) {
Logger.error('Error getting class ranking: $e');
return [];
}
}
/// Calcular score geral para ranking
static double _calculateOverallScore(UserStats userStats, double quizCompletionRate) {
// Se completou 100% dos quizzes, score é 100%
if (quizCompletionRate >= 1.0) {
return 100.0;
}
// Para completion < 100%, calcular proporcionalmente
double baseScore = quizCompletionRate * 90; // 90% baseado em completion
// Bônus adicionais (máximo 10% extra)
double bonusScore = 0.0;
// 5% para conceitos dominados
bonusScore += (userStats.masteredConcepts.length / 5.0 * 5).clamp(0.0, 5.0);
// 3% para streak
bonusScore += (userStats.currentStreak / 7.0 * 3).clamp(0.0, 3.0);
// 2% para conquistas
bonusScore += (userStats.unlockedAchievements.length / 10.0 * 2).clamp(0.0, 2.0);
final totalScore = baseScore + bonusScore;
return totalScore.clamp(0.0, 100.0);
}
/// Criar conquista personalizada (professor)
static Future<String> createCustomAchievement({
required String teacherId,
required String name,
required String description,
required String icon,
required String category,
required AchievementRequirement requirements,
required int points,
required String rarity,
}) async {
try {
final achievementRef = await _firestore.collection('achievements').add({
'name': name,
'description': description,
'icon': icon,
'category': category,
'requirements': requirements.toFirestore(),
'points': points,
'rarity': rarity,
'isActive': true,
'createdAt': Timestamp.now(),
'createdBy': teacherId,
});
Logger.info('Custom achievement created: ${achievementRef.id}');
return achievementRef.id;
} catch (e) {
Logger.error('Error creating custom achievement: $e');
rethrow;
}
}
/// Obter conquistas disponíveis
static Future<List<Achievement>> getAvailableAchievements({String? teacherId}) async {
try {
// Sempre incluir conquistas do sistema
List<Achievement> achievements = List.from(SystemAchievements.defaultAchievements);
// Adicionar conquistas personalizadas do professor
Query query = _firestore.collection('achievements').where('isActive', isEqualTo: true);
if (teacherId != null) {
query = query.where('createdBy', isEqualTo: teacherId);
}
final snapshot = await query.get();
achievements.addAll(
snapshot.docs
.map((doc) => Achievement.fromFirestore(doc.data() as Map<String, dynamic>, doc.id))
.toList(),
);
Logger.info('Total achievements loaded: ${achievements.length}');
return achievements;
} catch (e) {
Logger.error('Error getting available achievements: $e');
return SystemAchievements.defaultAchievements;
}
}
/// Métodos privados
static Future<void> _createInitialUserStats(String userId) async {
await _firestore.collection('users').doc(userId).set({
'userId': userId,
'currentStreak': 0,
'longestStreak': 0,
'totalStudyTime': 0,
'lastActivityDate': null, // null para que primeira atividade inicie streak
'weeklyStudyTime': 0,
'monthlyStudyTime': 0,
'masteredConcepts': [],
'unlockedAchievements': [],
});
}
static Future<void> _addMasteredConcept(String userId, String conceptName, int score) async {
try {
final userStatsRef = _firestore.collection('users').doc(userId);
final userStatsDoc = await userStatsRef.get();
if (!userStatsDoc.exists) return;
final userStats = UserStats.fromFirestore(userStatsDoc.data()!, userId);
// Verificar se conceito já está dominado
final existingConcept = userStats.masteredConcepts
.where((c) => c.conceptName == conceptName)
.firstOrNull;
if (existingConcept != null) {
// Atualizar nível de maestria se score maior
if (score > existingConcept.masteryLevel) {
final updatedConcepts = userStats.masteredConcepts.map((c) {
if (c.conceptName == conceptName) {
return MasteredConcept(
conceptName: conceptName,
masteredAt: DateTime.now(),
masteryLevel: score,
);
}
return c;
}).toList();
await userStatsRef.update({
'masteredConcepts': updatedConcepts.map((c) => c.toFirestore()).toList(),
});
}
} else {
// Adicionar novo conceito
final newConcept = MasteredConcept(
conceptName: conceptName,
masteredAt: DateTime.now(),
masteryLevel: score,
);
await userStatsRef.update({
'masteredConcepts': FieldValue.arrayUnion([newConcept.toFirestore()]),
});
}
} catch (e) {
Logger.error('Error adding mastered concept: $e');
}
}
static Future<ClassStats> _calculateClassStats(String classId) async {
try {
// Obter informações da turma
final classDoc = await _firestore.collection('classes').doc(classId).get();
if (!classDoc.exists) {
throw Exception('Class not found');
}
final className = classDoc.data()?['name'] ?? 'Unknown Class';
final teacherId = classDoc.data()?['teacherId'] ?? '';
// Obter alunos matriculados
final enrollmentsSnapshot = await _firestore
.collection('enrollments')
.where('classId', isEqualTo: classId)
.get();
final studentIds = enrollmentsSnapshot.docs.map((doc) => doc.data()['studentId'] as String).toList();
// Calcular estatísticas
int activeStudents = 0;
double totalProgress = 0;
List<StudentNeedingSupport> needingSupport = [];
for (final studentId in studentIds) {
final userStats = await getUserStats(studentId);
// Verificar se está ativo (atividade nos últimos 30 dias - mais realista)
int daysSinceLastActivity = 999; // Valor alto para inatividade
bool hasStats = userStats != null;
if (hasStats && userStats!.lastActivityDate != null) {
daysSinceLastActivity = DateTime.now().difference(userStats.lastActivityDate!).inDays;
if (daysSinceLastActivity <= 30) {
activeStudents++;
}
}
// Calcular progresso baseado em quizzes completos e conceitos dominados
double progress = 0.0;
if (hasStats) {
final completedQuizzes = userStats!.completedQuizzes;
final masteredConcepts = userStats.masteredConcepts.length;
Logger.info('=== PROGRESS CALCULATION DEBUG ===');
Logger.info('Student ID: $studentId');
Logger.info('Completed quizzes: $completedQuizzes');
Logger.info('Mastered concepts: $masteredConcepts');
// Progresso mais representativo: 60% quizzes + 40% conceitos
// Primeiro quiz já dá 30% de progresso (incentivo inicial)
final quizProgress = completedQuizzes > 0 ?
(0.3 + (completedQuizzes - 1) * 0.15).clamp(0.0, 0.6) : 0.0;
// Primeiro conceito já dá 15% de progresso
final conceptProgress = (masteredConcepts * 0.15).clamp(0.0, 0.4);
progress = quizProgress + conceptProgress;
Logger.info('Quiz progress: $quizProgress (${(quizProgress * 100).toInt()}%)');
Logger.info('Concept progress: $conceptProgress (${(conceptProgress * 100).toInt()}%)');
Logger.info('Total progress: $progress (${(progress * 100).toInt()}%)');
Logger.info('=== END PROGRESS CALCULATION DEBUG ===');
} else {
Logger.info('Student $studentId has no stats - progress = 0.0');
}
totalProgress += progress;
// Verificar se precisa de apoio (ajustado para nova fórmula)
if (progress < 0.25 || daysSinceLastActivity > 30) {
final userDoc = await _firestore.collection('users').doc(studentId).get();
final userData = userDoc.data();
// Tentar obter um nome melhor para o aluno
String studentName = 'Aluno ${studentId.substring(0, 8)}...';
if (userData != null) {
studentName = userData['displayName'] ??
userData['email']?.split('@')[0] ??
'Aluno ${studentId.substring(0, 8)}...';
}
needingSupport.add(StudentNeedingSupport(
studentId: studentId,
studentName: studentName,
reason: progress < 0.3 ? 'low_scores' : 'inactivity',
lastActivity: hasStats ? userStats!.lastActivityDate ?? DateTime.now() : DateTime.now().subtract(const Duration(days: 45)),
averageScore: progress * 100,
));
}
}
final averageProgress = studentIds.isEmpty ? 0.0 : totalProgress / studentIds.length;
Logger.info('=== AVERAGE PROGRESS DEBUG ===');
Logger.info('Total students: ${studentIds.length}');
Logger.info('Total progress sum: $totalProgress');
Logger.info('Average progress: $averageProgress (${(averageProgress * 100).toInt()}%)');
Logger.info('=== END AVERAGE PROGRESS DEBUG ===');
// Obter estatísticas de quizzes e conteúdos
final quizzesSnapshot = await _firestore
.collection('teacherQuizzes')
.where('classIds', arrayContains: classId)
.get();
// Obter materiais/conteúdos da turma
final materialsSnapshot = await _firestore
.collection('materials')
.where('classId', isEqualTo: classId)
.get();
// Contar quizzes ativos (últimos 30 dias)
final thirtyDaysAgo = DateTime.now().subtract(const Duration(days: 30));
final activeQuizzesCount = quizzesSnapshot.docs.where((doc) {
final createdAt = (doc.data()['createdAt'] as Timestamp?)?.toDate();
return createdAt != null && createdAt.isAfter(thirtyDaysAgo);
}).length;
final classStats = ClassStats(
classId: classId,
teacherId: teacherId,
className: className,
totalStudents: studentIds.length,
activeStudents: activeStudents,
averageProgress: averageProgress,
totalQuizzes: quizzesSnapshot.docs.length,
activeQuizzes: activeQuizzesCount,
totalContent: materialsSnapshot.docs.length,
weeklyStats: [],
studentsNeedingSupport: needingSupport,
lastUpdated: DateTime.now(),
);
// Limpar cache primeiro e depois salvar estatísticas calculadas
await _firestore.collection('classStats').doc(classId).delete();
await _firestore.collection('classStats').doc(classId).set(classStats.toFirestore());
Logger.info('Class stats refreshed and saved for class $classId');
return classStats;
} catch (e) {
Logger.error('Error calculating class stats: $e');
rethrow;
}
}
static Future<void> _checkStreakAchievements(String userId, int streakDays) async {
final achievements = await getAvailableAchievements();
final streakAchievements = achievements.where((a) => a.category == 'streak');
for (final achievement in streakAchievements) {
if (achievement.requirements.checkCondition(streakDays)) {
await _unlockAchievement(userId, achievement.id);
}
}
}
static Future<void> _checkStudyTimeAchievements(String userId, int totalMinutes) async {
final achievements = await getAvailableAchievements();
final studyAchievements = achievements.where((a) => a.category == 'study_time');
for (final achievement in studyAchievements) {
if (achievement.requirements.checkCondition(totalMinutes)) {
await _unlockAchievement(userId, achievement.id);
}
}
}
static Future<void> _checkQuizAchievements(String userId, int score, int totalQuestions) async {
Logger.info('=== CHECKING QUIZ ACHIEVEMENTS ===');
Logger.info('Score: $score/$totalQuestions');
final achievements = await getAvailableAchievements();
final userStats = await getUserStats(userId);
if (userStats == null) {
Logger.error('User stats null for achievement checking');
return;
}
final percentage = (score / totalQuestions) * 100;
// Usar contador real de quizzes completos
final completedQuizzes = userStats.completedQuizzes;
Logger.info('Percentage: $percentage%');
Logger.info('Completed quizzes: $completedQuizzes');
Logger.info('Mastered concepts: ${userStats.masteredConcepts.length}');
Logger.info('Available achievements: ${achievements.length}');
for (final achievement in achievements) {
if (achievement.category == 'quiz' && achievement.requirements.type == 'quiz_score' &&
achievement.requirements.checkCondition(percentage)) {
await _unlockAchievement(userId, achievement.id);
} else if (achievement.category == 'quiz_count' && achievement.requirements.type == 'quiz_completion' &&
achievement.requirements.checkCondition(completedQuizzes)) {
await _unlockAchievement(userId, achievement.id);
} else if (achievement.category == 'quiz' && achievement.requirements.type == 'quiz_completion' &&
achievement.id == 'first_quiz' &&
achievement.requirements.checkCondition(1)) {
await _unlockAchievement(userId, achievement.id);
} else if (achievement.category == 'concept' && achievement.requirements.type == 'concepts_mastered' &&
achievement.requirements.checkCondition(userStats.masteredConcepts.length)) {
await _unlockAchievement(userId, achievement.id);
} else {
Logger.info('Achievement not matched: ${achievement.id} - category: ${achievement.category}, type: ${achievement.requirements.type}');
}
}
Logger.info('=== END CHECKING QUIZ ACHIEVEMENTS ===');
}
static Future<void> _unlockAchievement(String userId, String achievementId) async {
try {
Logger.info('=== ATTEMPTING TO UNLOCK ACHIEVEMENT ===');
Logger.info('Achievement ID: $achievementId');
Logger.info('User ID: $userId');
final userStatsRef = _firestore.collection('users').doc(userId);
final userStatsDoc = await userStatsRef.get();
if (!userStatsDoc.exists) {
Logger.error('User stats document does not exist for user $userId');
return;
}
final userStats = UserStats.fromFirestore(userStatsDoc.data()!, userId);
Logger.info('Current unlocked achievements count: ${userStats.unlockedAchievements.length}');
// Verificar se já desbloqueou
final alreadyUnlocked = userStats.unlockedAchievements
.any((a) => a.achievementId == achievementId);
Logger.info('Already unlocked: $alreadyUnlocked');
if (!alreadyUnlocked) {
final unlockedAchievement = UnlockedAchievement(
achievementId: achievementId,
unlockedAt: DateTime.now(),
metadata: {},
);
Logger.info('Adding achievement to Firestore...');
await userStatsRef.update({
'unlockedAchievements': FieldValue.arrayUnion([unlockedAchievement.toFirestore()]),
});
Logger.info('Achievement unlocked successfully: $achievementId for user $userId');
} else {
Logger.info('Achievement $achievementId already unlocked for user $userId');
}
} catch (e) {
Logger.error('Error unlocking achievement: $e');
}
}
/// Inicializar estatísticas do usuário
static Future<void> _initializeUserStats(String userId) async {
try {
final userStatsRef = _firestore.collection('users').doc(userId);
// Verificar se já existe
final doc = await userStatsRef.get();
if (doc.exists) {
// Apenas atualizar com completedQuizzes se não existir
final data = doc.data() as Map<String, dynamic>;
if (!data.containsKey('completedQuizzes')) {
await userStatsRef.update({
'completedQuizzes': 0,
});
}
} else {
// Criar documento inicial
await userStatsRef.set({
'completedQuizzes': 0,
'currentStreak': 0,
'longestStreak': 0,
'totalStudyTime': 0,
'weeklyStudyTime': 0,
'monthlyStudyTime': 0,
'masteredConcepts': [],
'unlockedAchievements': [],
});
}
Logger.info('User stats initialized for user $userId');
} catch (e) {
Logger.error('Error initializing user stats: $e');
}
}
}