Saltar al contenido principal

🗄️ Database Schema

Modelo de Datos Completo

PuntoHack utiliza 17 modelos Prisma principales organizados en dominios lógicos.

Diagrama ERM Completo

Modelos por Dominio

1. Core Domain

Profile

Usuario del sistema con información completa.

model Profile {
id String @id @default(cuid())
userId String @unique // Clerk ID
name String
email String @unique
avatarUrl String?
bio String?
techStack String[]
role Role @default(PARTICIPANT)

// Relaciones
participations HackathonParticipation[]
organizedHackathons Hackathon[]
teamMemberships TeamMember[]
judgeAssignments HackathonJudge[]
scores Score[]
organizationMemberships OrganizationMember[]
notifications Notification[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

Índices:

  • userId (único)
  • email (único)
  • role (para filtros)

Hackathon

Evento principal del sistema.

model Hackathon {
id String @id @default(cuid())
name String
slug String @unique
description String @db.Text
status HackathonStatus @default(DRAFT)

// Fechas (orden estricto)
registrationOpensAt DateTime
registrationClosesAt DateTime
startsAt DateTime
endsAt DateTime
submissionDeadline DateTime
judgingStartsAt DateTime
judgingEndsAt DateTime

// Configuración
maxTeamSize Int @default(5)
minTeamSize Int @default(1)

// Ownership
organizerId String?
organizer Profile? @relation("OrganizedHackathons", fields: [organizerId], references: [id])

// Relaciones
participations HackathonParticipation[]
criteria Criterion[]
teams Team[]
submissions Submission[]
judges HackathonJudge[]
sponsorships Sponsorship[]
challenges Challenge[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

Estados: DRAFT, REGISTRATION, RUNNING, JUDGING, FINISHED

Índices:

  • slug (único, para URLs)
  • status (para filtros)
  • organizerId (para queries de organizador)

2. Teams Domain

Team

Equipo de participantes.

model Team {
id String @id @default(cuid())
hackathonId String
name String
code String @unique // Código de invitación
description String? @db.Text

hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
members TeamMember[]
submissions Submission[]
invitations TeamInvitation[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

Características:

  • code único para invitaciones
  • Cascade delete con hackathon

TeamMember

Miembro de un equipo.

model TeamMember {
id String @id @default(cuid())
teamId String
profileId String
isLeader Boolean @default(false)
joinedAt DateTime @default(now())

team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
profile Profile @relation(fields: [profileId], references: [id], onDelete: Cascade)

createdAt DateTime @default(now())

@@unique([teamId, profileId])
}

Características:

  • isLeader para identificar líder
  • joinedAt para determinar antigüedad
  • Unique constraint: un usuario solo puede estar una vez en un equipo

3. Submissions Domain

Submission

Proyecto enviado por un equipo.

model Submission {
id String @id @default(cuid())
hackathonId String
teamId String
title String
description String @db.Text
repoUrl String?
demoUrl String?
extraLinks Json?

// Información técnica detallada
technicalStack String? @db.Text
architecture String? @db.Text
keyFeatures String? @db.Text
technicalChallenges String? @db.Text
futureImprovements String? @db.Text

// Evidencia de challenges
challengeEvidence Json?

// Documentación
documentationUrl String?
apiDocumentation String?
deploymentGuide String?

// Métricas
testCoverage Int?
performanceMetrics Json?

hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
scores Score[]
shortlistItems ShortlistItem[]
challenges SubmissionChallenge[]
challengeEvaluations ChallengeEvaluation[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@unique([teamId]) // Un equipo solo puede tener una submission
}

Características:

  • Unique constraint: un equipo solo puede tener una submission
  • Campos técnicos detallados para evaluación
  • Evidencia de cumplimiento de challenges

4. Evaluation Domain

Criterion

Criterio de evaluación.

model Criterion {
id String @id @default(cuid())
hackathonId String
name String
description String? @db.Text
weight Int @default(1) // 1-10
maxScore Int @default(10)

hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
scores Score[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

12 Criterios Estándar:

  1. Stack Tecnológico
  2. Arquitectura del Sistema
  3. Características Principales
  4. Resolución de Desafíos Técnicos
  5. Visión y Mejoras Futuras
  6. Documentación Técnica
  7. Documentación de API
  8. Guía de Despliegue
  9. Cobertura de Tests
  10. Métricas de Rendimiento
  11. Repositorio y Código
  12. Demo Funcional

Score

Puntaje asignado por un juez.

model Score {
id String @id @default(cuid())
submissionId String
judgeId String
criterionId String
value Int // 0 - maxScore del criterio
comment String? @db.Text

submission Submission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
judge Profile @relation(fields: [judgeId], references: [id])
criterion Criterion @relation(fields: [criterionId], references: [id], onDelete: Cascade)

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@unique([submissionId, judgeId, criterionId]) // Un juez solo puede puntuar un criterio una vez
}

Características:

  • Unique constraint: un juez solo puede puntuar un criterio una vez por submission
  • value debe estar entre 0 y criterion.maxScore

5. Sponsors Domain

Organization

Organización patrocinadora.

model Organization {
id String @id @default(cuid())
name String
description String? @db.Text
logoUrl String?
website String?

members OrganizationMember[]
sponsorships Sponsorship[]
shortlistItems ShortlistItem[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

Sponsorship

Patrocinio de un hackathon.

model Sponsorship {
id String @id @default(cuid())
organizationId String
hackathonId String
tier SponsorshipTier
benefits Json?

organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
challenges Challenge[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@unique([organizationId, hackathonId]) // Una org solo puede patrocinar un hackathon una vez
}

Tiers: DIAMOND, PLATINUM, GOLD, SILVER, BRONZE, PARTNER

Challenge

Desafío de un sponsor.

model Challenge {
id String @id @default(cuid())
hackathonId String
sponsorshipId String
title String
description String @db.Text
tags String[]
prizeDetails String? @db.Text
evaluationCriteria String? @db.Text

hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
sponsorship Sponsorship @relation(fields: [sponsorshipId], references: [id], onDelete: Cascade)
shortlistItems ShortlistItem[]
submissions SubmissionChallenge[]
evaluations ChallengeEvaluation[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

Relaciones Principales

1. Profile → Hackathon

  • HackathonParticipation: Participación en hackathons
  • HackathonJudge: Asignación como juez
  • OrganizedHackathons: Hackathons organizados (ORGANIZER)

2. Team → Submission

  • 1:1: Un equipo solo puede tener una submission
  • Cascade delete: si se elimina el equipo, se elimina la submission

3. Submission → Score

  • 1:N: Una submission puede tener múltiples scores
  • Agrupado por (submissionId, judgeId, criterionId)

4. Challenge → Submission

  • N:M: Múltiples submissions pueden elegir múltiples challenges
  • Tabla intermedia: SubmissionChallenge

Constraints y Validaciones

Constraints de Base de Datos

  1. Unique Constraints:

    • Profile.userId (Clerk ID único)
    • Profile.email (email único)
    • Hackathon.slug (slug único para URLs)
    • Team.code (código de invitación único)
    • TeamMember(teamId, profileId) (un usuario solo una vez en un equipo)
    • Submission.teamId (un equipo solo una submission)
    • Score(submissionId, judgeId, criterionId) (un juez solo una vez por criterio)
  2. Foreign Keys con Cascade:

    • Eliminar hackathon → elimina participations, teams, submissions, etc.
    • Eliminar team → elimina members, submissions
    • Eliminar submission → elimina scores

Validaciones a Nivel de Aplicación

  1. Orden de Fechas (validado en core/validations.ts):

    registrationOpensAt < registrationClosesAt < startsAt < 
    submissionDeadline < judgingStartsAt < judgingEndsAt
  2. Tamaño de Equipo:

    • minTeamSize <= team.members.length <= maxTeamSize
  3. Scores:

    • 0 <= score.value <= criterion.maxScore

Índices Optimizados

Índices de Búsqueda Frecuente

// Profile
@@index([userId]) // Búsqueda por Clerk ID
@@index([email]) // Búsqueda por email
@@index([role]) // Filtros por rol

// Hackathon
@@index([slug]) // Búsqueda por slug (URLs)
@@index([status]) // Filtros por estado
@@index([organizerId]) // Hackathons de un organizador

// Team
@@index([hackathonId]) // Equipos de un hackathon
@@index([code]) // Búsqueda por código

// Score
@@index([submissionId]) // Scores de una submission
@@index([judgeId]) // Scores de un juez
@@index([criterionId]) // Scores de un criterio

Próximos Pasos


Siguiente: RBAC System