Files
ACVE/prisma/schema.prisma
2026-03-15 13:52:11 +00:00

289 lines
8.2 KiB
Plaintext
Executable File

// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
// --- ENUMS ---
enum UserRole {
SUPER_ADMIN // You (Full Access)
ORG_ADMIN // Law Firm Manager (View Team Progress, Manage Seats)
TEACHER // Content Creator (Create/Edit their own courses)
LEARNER // Student (View content, take quizzes)
}
enum ContentStatus {
DRAFT
PUBLISHED
ARCHIVED
}
enum ProficiencyLevel {
BEGINNER // A1-A2
INTERMEDIATE // B1-B2
ADVANCED // C1-C2
EXPERT // Legal Specific
}
enum ExerciseType {
MULTIPLE_CHOICE
FILL_IN_BLANK
TRANSLATION
MATCHING
}
enum MiniGameDifficulty {
BEGINNER
INTERMEDIATE
ADVANCED
}
// --- MODELS ---
model Profile {
id String @id @default(uuid()) // Links to Supabase Auth.uid()
email String @unique
fullName String?
avatarUrl String?
role UserRole @default(LEARNER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relationships
memberships Membership[] // B2B Access
enrollments Enrollment[] // B2C Purchases (Mercado Pago)
progress UserProgress[]
certificates Certificate[]
authoredCourses Course[] @relation("CourseAuthor") // Teachers own courses
miniGameAttempts MiniGameAttempt[]
recommendations StudyRecommendation[]
}
model Company {
id String @id @default(uuid())
name String
logoUrl String?
billingEmail String?
maxSeats Int @default(5) // Limit for B2B plans
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
memberships Membership[]
certificates Certificate[]
}
model Membership {
id String @id @default(uuid())
userId String
companyId String
isActive Boolean @default(true)
role String @default("MEMBER") // Internal org role (Member/Admin)
joinedAt DateTime @default(now())
updatedAt DateTime @updatedAt
user Profile @relation(fields: [userId], references: [id], onDelete: Cascade)
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
@@unique([userId, companyId]) // Prevent duplicate memberships
}
model Course {
id String @id @default(uuid())
title Json // { "en": "...", "es": "..." }
slug String @unique // Required for SEO URLs (e.g. /course/contract-law)
description Json // { "en": "...", "es": "..." }
level ProficiencyLevel @default(INTERMEDIATE)
tags String[]
learningOutcomes Json? // "What you will learn"; array of strings now, i18n object later
status ContentStatus @default(DRAFT)
price Decimal @default(0.00) // Price in MXN
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Prerequisites
prerequisiteId String?
prerequisite Course? @relation("CoursePrerequisites", fields: [prerequisiteId], references: [id])
dependents Course[] @relation("CoursePrerequisites")
author Profile @relation("CourseAuthor", fields: [authorId], references: [id])
modules Module[]
certificates Certificate[]
enrollments Enrollment[]
recommendations StudyRecommendation[]
}
model Enrollment {
id String @id @default(uuid())
userId String
courseId String
// Payment Proof (Mercado Pago)
amountPaid Decimal // e.g. 500.00
currency String @default("MXN")
paymentMethod String @default("MERCADO_PAGO")
externalId String? // The Mercado Pago "payment_id" or "preference_id"
purchasedAt DateTime @default(now())
user Profile @relation(fields: [userId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
@@unique([userId, courseId]) // Prevent buying the same course twice
}
model Module {
id String @id @default(uuid())
courseId String
title Json
orderIndex Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
lessons Lesson[]
}
model Lesson {
id String @id @default(uuid())
moduleId String
title Json
description Json?
slug String? // Optional for direct linking
orderIndex Int
videoUrl String?
youtubeUrl String?
estimatedDuration Int // Seconds
version Int @default(1)
isFreePreview Boolean @default(false) // Marketing hook
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
module Module @relation(fields: [moduleId], references: [id], onDelete: Cascade)
exercises Exercise[]
resources Resource[]
userProgress UserProgress[]
}
model Exercise {
id String @id @default(uuid())
lessonId String
type ExerciseType
content Json // { question: "...", options: [...], answer: "..." }
orderIndex Int @default(0)
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
}
model Resource {
id String @id @default(uuid())
lessonId String
fileUrl String
displayName Json
fileType String
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
}
model UserProgress {
id String @id @default(uuid())
userId String
lessonId String
isCompleted Boolean @default(false)
score Int? // For quiz results
startedAt DateTime @default(now())
finishedAt DateTime?
lastPlayedAt DateTime @default(now()) // For "Resume" feature
user Profile @relation(fields: [userId], references: [id], onDelete: Cascade)
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
@@unique([userId, lessonId]) // One progress record per lesson
}
model Certificate {
id String @id @default(uuid())
userId String
courseId String
companyId String? // Captured for co-branding (Nullable for B2C)
certificateNumber String @unique
pdfVersion Int @default(1)
issuedAt DateTime @default(now())
metadataSnapshot Json // Burn the course name/version here
user Profile @relation(fields: [userId], references: [id])
course Course @relation(fields: [courseId], references: [id])
company Company? @relation(fields: [companyId], references: [id])
@@unique([userId, courseId])
}
model MiniGame {
id String @id @default(uuid())
slug String @unique
title String
description String
isActive Boolean @default(true)
difficulty MiniGameDifficulty @default(INTERMEDIATE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
questions MiniGameQuestion[]
attempts MiniGameAttempt[]
}
model MiniGameQuestion {
id String @id @default(uuid())
miniGameId String
prompt String
choices String[]
answerIndex Int
orderIndex Int @default(0)
miniGame MiniGame @relation(fields: [miniGameId], references: [id], onDelete: Cascade)
@@index([miniGameId, orderIndex])
}
model MiniGameAttempt {
id String @id @default(uuid())
userId String
miniGameId String
scorePercent Int
correctCount Int
totalQuestions Int
startedAt DateTime @default(now())
completedAt DateTime @default(now())
user Profile @relation(fields: [userId], references: [id], onDelete: Cascade)
miniGame MiniGame @relation(fields: [miniGameId], references: [id], onDelete: Cascade)
@@index([userId, miniGameId, completedAt])
}
model StudyRecommendation {
id String @id @default(uuid())
userId String
courseId String
reason String
priority Int @default(0)
isActive Boolean @default(true)
createdAt DateTime @default(now())
user Profile @relation(fields: [userId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
@@index([userId, isActive, priority])
}