TalentBricksAI usa PostgreSQL con Prisma ORM. Los modelos se definen en app/schema.prisma.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ User │────▶│ Enrollment │◀────│ Course │
└─────────────┘ └─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Certificate │ │LessonProgress│◀───│ Lesson │
└─────────────┘ └─────────────┘ └─────────────┘
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
price Int // en centavos (USD)
currency String @default("USD")
difficulty Difficulty @default(BEGINNER)
isPublished Boolean @default(false)
instructorName String @default("TalentBricksAI")
certificates Certificate[]
| Campo | Tipo | Descripcion |
|---|
id | Int | ID autoincremental |
slug | String | URL-friendly identifier, unico |
price | Int | Precio en centavos (2900 = $29.00) |
difficulty | Enum | BEGINNER, INTERMEDIATE, ADVANCED |
isPublished | Boolean | Solo cursos publicados son visibles |
instructorName | String | Nombre del instructor del curso |
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
course Course @relation(fields: [courseId], references: [id])
duration Int // en segundos
content String? // markdown content
isPreview Boolean @default(false)
progress LessonProgress[]
| Campo | Tipo | Descripcion |
|---|
order | Int | Orden de la leccion en el curso |
duration | Int | Duracion en segundos |
videoUrl | String | URL del video (S3/CloudFront) |
isPreview | Boolean | Lecciones preview son accesibles sin enrollment |
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
course Course @relation(fields: [courseId], references: [id])
progress LessonProgress[]
@@unique([userId, courseId])
El constraint @@unique([userId, courseId]) asegura que un usuario solo puede tener una inscripcion por curso.
id Int @id @default(autoincrement())
updatedAt DateTime @updatedAt
lesson Lesson @relation(fields: [lessonId], references: [id])
enrollment Enrollment @relation(fields: [enrollmentId], references: [id])
watchedSeconds Int @default(0)
isCompleted Boolean @default(false)
@@unique([userId, lessonId])
| Campo | Tipo | Descripcion |
|---|
watchedSeconds | Int | Segundos vistos del video |
isCompleted | Boolean | Leccion marcada como completada |
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
course Course @relation(fields: [courseId], references: [id])
verificationCode String @unique @default(uuid())
// Snapshot data (capturado al momento de generacion)
@@unique([userId, courseId])
@@index([verificationCode])
| Campo | Tipo | Descripcion |
|---|
verificationCode | String | UUID unico para verificacion publica |
studentName | String | Nombre del estudiante (snapshot) |
courseTitle | String | Titulo del curso (snapshot) |
instructorName | String | Nombre del instructor (snapshot) |
totalDurationMinutes | Int | Duracion total en minutos (snapshot) |
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
isAdmin Boolean @default(false)
subscriptionStatus String?
// ...otros campos existentes
// Nuevas relaciones para cursos
certificates Certificate[]
# Iniciar PostgreSQL (dejar corriendo)
# Crear migracion despues de cambiar schema.prisma
# Abrir Prisma Studio (GUI para ver datos)
const courses = await context.entities.Course.findMany({
where: { isPublished: true },
orderBy: { order: 'asc' }
const enrollments = await context.entities.Enrollment.findMany({
where: { userId: context.user.id },
const enrollment = await context.entities.Enrollment.findUnique({
where: { userId_courseId: { userId, courseId } },
course: { include: { lessons: true } },
progress: { where: { isCompleted: true } }
const totalLessons = enrollment.course.lessons.length;
const completedLessons = enrollment.progress.length;
const progressPercent = Math.round((completedLessons / totalLessons) * 100);
Para optimizar consultas frecuentes:
@@index([courseId, order])