🎓 Initial commit: Math2 Platform - Plataforma de Álgebra Lineal PRO
Some checks failed
Test Suite / test-backend (push) Has been cancelled
Test Suite / test-frontend (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / coverage-check (push) Has been cancelled

 Características:
- 45 ejercicios universitarios (Basic → Advanced)
- Renderizado LaTeX profesional
- IA generativa (Z.ai/DashScope)
- Docker 9 servicios
- Tests 123/123 pasando
- Seguridad enterprise (JWT, XSS, Rate limiting)

🐳 Infraestructura:
- Next.js 14 + Node.js 20
- PostgreSQL 15 + Redis 7
- Docker Compose completo
- Nginx + SSL ready

📚 Documentación:
- 5 informes técnicos completos
- README profesional
- Scripts de deployment automatizados

Estado: Producción lista 
This commit is contained in:
Renato
2026-03-31 11:27:11 -03:00
commit bc43c9e772
309 changed files with 84845 additions and 0 deletions

View File

@@ -0,0 +1,439 @@
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
}