generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique username String @unique passwordHash String telegramChatId String? @unique @map("telegram_chat_id") isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastLoginAt DateTime? role UserRole @default(STUDENT) timezone String @default("UTC") @map("timezone") exerciseAttempts ExerciseAttempt[] notifications Notification[] passwordResetTokens PasswordResetToken[] progress Progress[] rankings Ranking[] refreshTokens RefreshToken[] userAchievements UserAchievement[] @@index([email]) @@index([username]) @@index([isActive]) @@index([role]) @@map("users") } model PasswordResetToken { id String @id @default(uuid()) token String @unique userId String expiresAt DateTime used Boolean @default(false) createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([token]) @@index([userId]) @@index([expiresAt]) @@map("password_reset_tokens") } model RefreshToken { id String @id @default(uuid()) token String @unique userId String expiresAt DateTime createdAt DateTime @default(now()) revoked Boolean @default(false) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([token]) @@index([expiresAt]) @@index([revoked]) @@map("refresh_tokens") } model ExerciseAttempt { id String @id @default(cuid()) userId String exerciseId String userAnswer String status AttemptStatus pointsEarned Int @default(0) timeSpentSeconds Int hintsUsed Int @default(0) feedback String? createdAt DateTime @default(now()) attemptNumber Int isPerfect Boolean @default(false) skipped Boolean @default(false) earnedAt DateTime @default(now()) exercises Exercise @relation(fields: [exerciseId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, exerciseId, attemptNumber]) @@index([userId]) @@index([exerciseId]) @@index([createdAt]) @@index([earnedAt]) @@index([status]) @@index([userId, exerciseId]) @@map("exercise_attempts") } model Notification { id String @id @default(cuid()) type NotificationType title String message String user_id String status NotificationStatus @default(PENDING) priority Int @default(0) metadata Json? attempts Int @default(0) lastAttemptAt DateTime? sentAt DateTime? errorMessage String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt users User @relation(fields: [user_id], references: [id], onDelete: Cascade) @@index([createdAt]) @@index([priority]) @@index([status]) @@index([type]) @@index([user_id]) @@map("notifications") } model Progress { id String @id @default(cuid()) userId String moduleId String exercisesCompleted Int @default(0) totalExercises Int @default(0) points Int @default(0) percentage Float @default(0) isStarted Boolean @default(false) isCompleted Boolean @default(false) startedAt DateTime? completedAt DateTime? lastAccessedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt averageScore Float? totalTimeSpent Int @default(0) perfectExercises Int @default(0) attemptsCount Int @default(0) modules modules @relation(fields: [moduleId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, moduleId]) @@index([userId]) @@index([moduleId]) @@index([isCompleted]) @@index([points]) @@map("progress") } model Ranking { id String @id @default(cuid()) userId String moduleId String? position Int points Int @default(0) exercisesCompleted Int @default(0) streak Int @default(0) lastUpdated DateTime @default(now()) perfectExercises Int @default(0) averageScore Float? totalAttempts Int @default(0) achievementsUnlocked Int @default(0) longestStreak Int @default(0) modules modules? @relation(fields: [moduleId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, moduleId]) @@index([points]) @@index([moduleId]) @@index([position]) @@index([streak]) @@map("rankings") } model Achievement { id String @id @default(cuid()) code String @unique name String description String category AchievementCategory rarity AchievementRarity icon String requirementType RequirementType requirementValue Int points Int @default(0) metadata Json? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userAchievements UserAchievement[] @@index([category]) @@index([code]) @@index([isActive]) @@index([rarity]) @@map("achievements") } model UserAchievement { id String @id @default(cuid()) userId String achievementId String progress Int @default(0) unlockedAt DateTime? metadata Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt achievement Achievement @relation(fields: [achievementId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, achievementId]) @@index([userId]) @@index([achievementId]) @@index([unlockedAt]) @@map("user_achievements") } model Exercise { id String @id @default(cuid()) moduleId String topicId String? type ExerciseType difficulty ExerciseDifficulty order Int statement String correctAnswer String solutionSteps Json? formulas Json? hints Json? isAIGenerated Boolean @default(false) isPublished Boolean @default(false) points Int @default(10) timeLimitSeconds Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt multipleChoiceOptions Json? proofRequirements Json? calculationSteps Json? exercise_attempts ExerciseAttempt[] modules modules @relation(fields: [moduleId], references: [id], onDelete: Cascade) topics topics? @relation(fields: [topicId], references: [id]) @@unique([moduleId, order]) @@index([difficulty]) @@index([type]) @@index([isAIGenerated]) @@index([isPublished]) @@index([moduleId]) @@index([topicId]) @@map("exercises") } model SystemConfig { id String @id @default(cuid()) key String @unique value String description String? category String? isPublic Boolean @default(false) isEncrypted Boolean @default(false) dataType String @default("string") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? updatedBy String? changeHistory Json? @@index([category]) @@index([isPublic]) @@map("system_config") } model modules { id String @id name String description String type ModuleType order Int isPublished Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt introduction String? examples Json? exercisesData Json? answers Json? estimatedHours Int? @default(0) difficultyLevel ExerciseDifficulty @default(INTERMEDIATE) totalExercises Int @default(0) exercises Exercise[] progress Progress[] rankings Ranking[] topics topics[] @@unique([type, order]) @@index([isPublished]) @@index([order]) @@index([type]) } model processed_pdfs { id String @id file_name String @unique original_path String type PdfType topicType TopicType? is_processed Boolean @default(false) processing_started_at DateTime? processing_completed_at DateTime? errorMessage String? extractedText Json? exercisesDetected Json? formulasExtracted Json? metadata Json? totalPages Int? processingVersion String? checksum String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([file_name]) @@index([is_processed]) @@index([topicType]) @@index([type]) } model topics { id String @id moduleId String name String type TopicType order Int description String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt theoryContent Json? formulas Json? keyPoints Json? commonMistakes Json? exercises Exercise[] modules modules @relation(fields: [moduleId], references: [id], onDelete: Cascade) @@unique([moduleId, order]) @@index([moduleId]) @@index([type]) } enum UserRole { STUDENT TEACHER ADMIN } enum AchievementCategory { EXERCISES MODULES STREAKS RANKING SPECIAL } enum AchievementRarity { COMMON RARE EPIC LEGENDARY } enum AttemptStatus { CORRECT INCORRECT PARTIAL PENDING } enum ExerciseDifficulty { BASIC INTERMEDIATE ADVANCED EXPERT } enum ExerciseType { MULTIPLE_CHOICE OPEN_RESPONSE CALCULATION PROOF TRUE_FALSE } enum ModuleType { FUNDAMENTOS SISTEMAS APLICACIONES } enum NotificationStatus { PENDING SENT FAILED } enum NotificationType { NEW_USER EXERCISE_COMPLETED MODULE_COMPLETED ACHIEVEMENT_UNLOCKED SYSTEM_ERROR DAILY_SUMMARY RANKING_CHANGED } enum PdfType { TEXTBOOK PRACTICE PRACTICE_ANSWERS EXAM ADDITIONAL_MATERIAL } enum RequirementType { EXERCISES_COMPLETED MODULES_COMPLETED PERFECT_SCORES STREAK_DAYS RANKING_POSITION EXERCISES_WITHOUT_HINTS EARLY_BIRD NIGHT_OWL PERFECT_MODULE } enum TopicType { VECTORES MATRICES SISTEMAS ESPACIOS_VECTORIALES PROGRAMACION_LINEAL }