📋 Plan de Ejecución - Módulo 3: Teams & Evaluation Module
Fecha de Creación: 31 de diciembre, 2025
Estado: 📝 Planificado
Dependencias: ✅ Core Layer, ✅ Users Module, ✅ Hackathons Module
🎯 Objetivo del Módulo
Implementar el sistema completo de equipos y evaluación con:
- Sistema de equipos con códigos de invitación
- Submissions de proyectos
- Asignación de jueces (manual)
- Sistema de scoring por criterio
- Leaderboard con puntaje ponderado
📊 Análisis de Requerimientos
1. Schema Prisma (5 modelos nuevos)
Model Team
model Team {
id String @id @default(cuid())
hackathonId String
name String
code String @unique // Código de invitación único
description String?
hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
members TeamMember[]
submissions Submission[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([hackathonId])
@@index([code])
}
Model TeamMember
model TeamMember {
id String @id @default(cuid())
teamId String
profileId String
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])
@@index([teamId])
@@index([profileId])
}
Model Submission
model Submission {
id String @id @default(cuid())
hackathonId String
teamId String
title String
description String @db.Text
repoUrl String?
demoUrl String?
extraLinks Json? // Para links adicionales (opcional)
hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
scores Score[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([teamId]) // Un equipo solo puede tener una submission
@@index([hackathonId])
@@index([teamId])
}
Model HackathonJudge
model HackathonJudge {
id String @id @default(cuid())
hackathonId String
profileId String
hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
profile Profile @relation(fields: [profileId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([hackathonId, profileId])
@@index([hackathonId])
@@index([profileId])
}
Model Score
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
@@index([submissionId])
@@index([judgeId])
@@index([criterionId])
}
Actualizaciones a Modelos Existentes
Profile:
model Profile {
// ... campos existentes
teamMemberships TeamMember[]
judgeAssignments HackathonJudge[]
scores Score[]
}
Hackathon:
model Hackathon {
// ... campos existentes
teams Team[]
submissions Submission[]
judges HackathonJudge[]
}
Criterion:
model Criterion {
// ... campos existentes
scores Score[]
}
2. Funcionalidades Core Requeridas
Teams Module
Queries (4 funciones):
getTeamById(id: string)- Obtener equipo por IDgetTeamByCode(code: string)- Obtener equipo por código de invitacióngetUserTeams(profileId: string, hackathonId?: string)- Equipos del usuariogetTeamMembers(teamId: string)- Miembros de un equipo
Actions (3 Server Actions):
-
createTeam(hackathonId: string, data: CreateTeamInput, currentUserId: string)- RBAC: PARTICIPANT
- Validar: usuario registrado en hackathon, no tiene equipo en ese hackathon,
now < submissionDeadline - Generar código único de invitación (6-8 caracteres alfanuméricos)
- Crear Team + TeamMember (creator como miembro)
-
joinTeam(inviteCode: string, currentUserId: string)- RBAC: PARTICIPANT
- Validar: código válido, usuario registrado en hackathon, no tiene equipo en ese hackathon,
now < submissionDeadline - Validar: equipo no está lleno (maxTeamSize del hackathon)
- Agregar TeamMember
-
leaveTeam(teamId: string, currentUserId: string)- RBAC: PARTICIPANT
- Validar: usuario es miembro del equipo,
now < submissionDeadline - Validar: si equipo tiene submission y es el último miembro → error
- Si equipo queda vacío y NO tiene submission → eliminar equipo
- Eliminar TeamMember
Validations:
createTeamSchema- name, description (opcional)joinTeamSchema- inviteCodeleaveTeamSchema- teamId
Submissions Module
Queries (4 funciones):
getSubmissionById(id: string)- Obtener submission por IDgetSubmissionByTeamId(teamId: string)- Obtener submission del equipogetHackathonSubmissions(hackathonId: string)- Todas las submissions del hackathongetHackathonSubmissionsForJudge(hackathonId: string, judgeId: string)- Submissions para un juez (solo en JUDGING)
Actions (2 Server Actions):
-
createSubmission(hackathonId: string, teamId: string, data: CreateSubmissionInput, currentUserId: string)- RBAC: PARTICIPANT
- Validar: usuario es miembro del equipo, hackathon en estado RUNNING,
now < submissionDeadline - Validar: equipo no tiene submission (unique constraint)
- Crear Submission
-
updateSubmission(submissionId: string, data: UpdateSubmissionInput, currentUserId: string)- RBAC: PARTICIPANT
- Validar: usuario es miembro del equipo,
now < submissionDeadline - Actualizar Submission
Validations:
createSubmissionSchema- title, description, repoUrl (opcional), demoUrl (opcional), extraLinks (opcional)updateSubmissionSchema- campos opcionales
Evaluation Module
Queries (4 funciones):
getHackathonJudges(hackathonId: string)- Jueces asignados al hackathongetJudgeAssignments(judgeId: string)- Hackathons asignados a un juezgetScoresForSubmission(submissionId: string)- Scores de una submissioncalculateLeaderboard(hackathonId: string)- Calcular leaderboard con puntaje ponderado
Actions (3 Server Actions):
-
assignJudge(hackathonId: string, judgeId: string, currentUserId: string)- RBAC: ORGANIZER/ADMIN
- Validar: juez tiene rol JUDGE
- Validar: juez NO participa en el hackathon (no tiene participation ni team)
- Validar: juez no está ya asignado (unique constraint)
- Crear HackathonJudge
-
removeJudge(hackathonId: string, judgeId: string, currentUserId: string)- RBAC: ORGANIZER/ADMIN
- Validar: juez está asignado
- Eliminar HackathonJudge (scores permanecen pero no cuentan)
-
submitScore(submissionId: string, criterionId: string, value: number, comment?: string, currentUserId: string)- RBAC: JUDGE
- Validar: juez está asignado al hackathon, hackathon en estado JUDGING
- Validar: submission pertenece al hackathon asignado
- Validar: value está entre 0 y maxScore del criterio
- Crear o actualizar Score (unique constraint)
Validations:
assignJudgeSchema- hackathonId, judgeIdsubmitScoreSchema- submissionId, criterionId, value (0-maxScore), comment (opcional)
3. UI Funcional Requerida
Teams UI (3 páginas/componentes)
-
/hackathons/[slug]/teams/create- Crear equipo- Formulario: name, description (opcional)
- Mostrar código de invitación después de crear
- Botón copiar código
-
/hackathons/[slug]/teams/join- Unirse a equipo- Input para código de invitación
- Validación en servidor
-
/hackathons/[slug]/teams/[teamId]- Detalle del equipo- Lista de miembros
- Botón "Salir del equipo" (si es miembro)
- Mostrar submission si existe
Submissions UI (3 páginas/componentes)
-
/hackathons/[slug]/teams/[teamId]/submit- Crear/editar submission- Formulario: title, description, repoUrl, demoUrl, extraLinks
- Validación: solo hasta submissionDeadline
- Botón "Enviar" o "Actualizar"
-
/hackathons/[slug]/submissions- Lista de submissions (público cuando FINISHED)- Mostrar todas las submissions del hackathon
- Link a detalle
-
/hackathons/[slug]/submissions/[submissionId]- Detalle de submission- Información completa
- Leaderboard (si FINISHED)
Evaluation UI (3 páginas/componentes)
-
/admin/hackathons/[slug]/judges- Asignar jueces (ORGANIZER/ADMIN)- Lista de jueces disponibles (rol JUDGE)
- Botón asignar/remover
- Validar conflictos (participación en hackathon)
-
/judge/hackathons/[slug]- Panel de juez- Lista de submissions del hackathon asignado (solo en JUDGING)
- Botón "Evaluar" por cada submission
- Mostrar progreso: "Evaluadas: X/Y submissions"
-
/judge/hackathons/[slug]/submissions/[submissionId]/evaluate- Formulario de evaluación- Mostrar criterios con pesos
- Input de puntaje por criterio (0-maxScore)
- Textarea de comentario (opcional) por criterio
- Botón "Enviar evaluación"
Leaderboard UI (1 página)
/hackathons/[slug]/leaderboard- Leaderboard público- Solo visible cuando hackathon está FINISHED
- Mostrar ranking con puntaje ponderado
- Fórmula:
sum(score * criterion.weight) / sum(maxScore * criterion.weight) * 100
🗂️ Plan de Ejecución por Fases
FASE 1: Schema y Migración (Prioridad: CRÍTICA)
Objetivo: Definir y migrar el schema de base de datos
Tareas:
-
Actualizar
prisma/schema.prisma- Agregar model
Team - Agregar model
TeamMember - Agregar model
Submission - Agregar model
HackathonJudge - Agregar model
Score - Actualizar relaciones en
Profile,Hackathon,Criterion - Definir índices y constraints
- Agregar model
-
Ejecutar migración
pnpm prisma migrate dev --name add_teams_and_evaluation_module -
Verificar schema en Prisma Studio
Criterio de Completitud:
- Schema completo sin errores
- Migración ejecutada exitosamente
- Relaciones funcionando correctamente
- Índices y constraints correctos
Tiempo Estimado: 45-60 minutos
FASE 2: Teams Module - Core (Prioridad: CRÍTICA)
Objetivo: Implementar la lógica core del módulo de equipos
2.1 Types (src/modules/teams/types.ts)
Tareas:
- Definir tipos TypeScript
TeamWithRelations(con miembros, submission)CreateTeamInputJoinTeamInputLeaveTeamInputTeamMemberWithProfile
Criterio de Completitud:
- Todos los tipos exportados
- Tipos usados en queries/actions
Tiempo Estimado: 15 minutos
2.2 Validations (src/modules/teams/validations.ts)
Tareas:
-
Crear
createTeamSchema(Zod)- name (min 3, max 100)
- description (opcional, max 500)
-
Crear
joinTeamSchema(Zod)- inviteCode (string, min 6, max 8)
-
Crear
leaveTeamSchema(Zod)- teamId (CUID)
Criterio de Completitud:
- Todos los schemas creados
- Tests de validaciones pasando
Tiempo Estimado: 30 minutos
2.3 Queries (src/modules/teams/queries.ts)
Tareas:
-
Implementar
getTeamById(id: string)- Incluir miembros y submission
-
Implementar
getTeamByCode(code: string)- Incluir miembros y submission
-
Implementar
getUserTeams(profileId: string, hackathonId?: string)- Equipos del usuario (opcionalmente filtrado por hackathon)
-
Implementar
getTeamMembers(teamId: string)- Lista de miembros con perfil
Criterio de Completitud:
- Todas las queries implementadas
- Tests de queries pasando (100% coverage)
Tiempo Estimado: 1 hora
2.4 Actions (src/modules/teams/actions.ts)
Tareas:
-
Implementar
createTeam- Validar RBAC (PARTICIPANT)
- Validar registro en hackathon
- Validar no tiene equipo en hackathon
- Validar
now < submissionDeadline - Generar código único (función helper)
- Crear Team + TeamMember en transaction
-
Implementar
joinTeam- Validar RBAC (PARTICIPANT)
- Validar código válido
- Validar registro en hackathon
- Validar no tiene equipo en hackathon
- Validar
now < submissionDeadline - Validar equipo no lleno
- Agregar TeamMember
-
Implementar
leaveTeam- Validar RBAC (PARTICIPANT)
- Validar es miembro del equipo
- Validar
now < submissionDeadline - Validar: si tiene submission y es último miembro → error
- Si equipo vacío sin submission → eliminar equipo
- Eliminar TeamMember
Criterio de Completitud:
- Todas las actions implementadas
- RBAC funcionando
- Validaciones funcionando
- Tests pasando
Tiempo Estimado: 2 horas
FASE 3: Submissions Module - Core (Prioridad: CRÍTICA)
Objetivo: Implementar la lógica core del módulo de submissions
3.1 Types (src/modules/submissions/types.ts)
Tareas:
- Definir tipos TypeScript
SubmissionWithRelations(con team, scores)CreateSubmissionInputUpdateSubmissionInput
Tiempo Estimado: 15 minutos
3.2 Validations (src/modules/submissions/validations.ts)
Tareas:
-
Crear
createSubmissionSchema(Zod)- title (min 3, max 200)
- description (min 10, max 5000)
- repoUrl (URL opcional)
- demoUrl (URL opcional)
- extraLinks (JSON opcional)
-
Crear
updateSubmissionSchema(Zod)- Campos opcionales
Tiempo Estimado: 30 minutos
3.3 Queries (src/modules/submissions/queries.ts)
Tareas:
- Implementar
getSubmissionById(id: string) - Implementar
getSubmissionByTeamId(teamId: string) - Implementar
getHackathonSubmissions(hackathonId: string) - Implementar
getHackathonSubmissionsForJudge(hackathonId: string, judgeId: string)- Solo si hackathon está en JUDGING
- Filtrar por hackathon asignado al juez
Tiempo Estimado: 1 hora
3.4 Actions (src/modules/submissions/actions.ts)
Tareas:
-
Implementar
createSubmission- Validar RBAC (PARTICIPANT)
- Validar es miembro del equipo
- Validar hackathon en RUNNING
- Validar
now < submissionDeadline - Validar equipo no tiene submission
- Crear Submission
-
Implementar
updateSubmission- Validar RBAC (PARTICIPANT)
- Validar es miembro del equipo
- Validar
now < submissionDeadline - Actualizar Submission
Tiempo Estimado: 1.5 horas
FASE 4: Evaluation Module - Core (Prioridad: CRÍTICA)
Objetivo: Implementar la lógica core del módulo de evaluación
4.1 Types (src/modules/evaluation/types.ts)
Tareas:
- Definir tipos TypeScript
LeaderboardEntry(team, submission, weightedScore, position)ScoreWithRelations(con submission, judge, criterion)JudgeAssignment
Tiempo Estimado: 15 minutos
4.2 Validations (src/modules/evaluation/validations.ts)
Tareas:
-
Crear
assignJudgeSchema(Zod)- hackathonId (CUID)
- judgeId (CUID)
-
Crear
submitScoreSchema(Zod)- submissionId (CUID)
- criterionId (CUID)
- value (number, min 0, max se obtiene del criterio)
- comment (opcional, max 1000)
Tiempo Estimado: 30 minutos
4.3 Queries (src/modules/evaluation/queries.ts)
Tareas:
- Implementar
getHackathonJudges(hackathonId: string) - Implementar
getJudgeAssignments(judgeId: string) - Implementar
getScoresForSubmission(submissionId: string)- Filtrar por judgeId si se proporciona (para ocultar scores de otros jueces durante JUDGING)
- Implementar
calculateLeaderboard(hackathonId: string)- Obtener todas las submissions del hackathon
- Para cada submission, calcular puntaje ponderado:
sum(score.value * criterion.weight) / sum(criterion.maxScore * criterion.weight) * 100
- Ordenar por puntaje descendente
- Retornar con posición
Tiempo Estimado: 2 horas
4.4 Actions (src/modules/evaluation/actions.ts)
Tareas:
-
Implementar
assignJudge- Validar RBAC (ORGANIZER/ADMIN)
- Validar juez tiene rol JUDGE
- Validar juez NO participa en hackathon (no participation, no team)
- Validar juez no está ya asignado
- Crear HackathonJudge
-
Implementar
removeJudge- Validar RBAC (ORGANIZER/ADMIN)
- Validar juez está asignado
- Eliminar HackathonJudge
-
Implementar
submitScore- Validar RBAC (JUDGE)
- Validar juez está asignado al hackathon
- Validar hackathon en JUDGING
- Validar submission pertenece al hackathon
- Validar value entre 0 y maxScore del criterio
- Crear o actualizar Score (upsert)
Tiempo Estimado: 2 horas
FASE 5: Testing (Prioridad: CRÍTICA)
Objetivo: Crear tests completos para todos los módulos
Tareas:
-
Tests de Teams Module
tests/modules/teams/validations.test.tstests/modules/teams/queries.test.tstests/modules/teams/actions.test.ts
-
Tests de Submissions Module
tests/modules/submissions/validations.test.tstests/modules/submissions/queries.test.tstests/modules/submissions/actions.test.ts
-
Tests de Evaluation Module
tests/modules/evaluation/validations.test.tstests/modules/evaluation/queries.test.tstests/modules/evaluation/actions.test.ts
Criterio de Completitud:
- Coverage >80%
- Todos los tests pasando
- Tests de RBAC completos
- Tests de validaciones de fechas
Tiempo Estimado: 4-5 horas
FASE 6: Teams UI (Prioridad: ALTA)
Objetivo: Implementar la UI para gestión de equipos
Tareas:
-
Crear
/hackathons/[slug]/teams/create/page.tsx- Formulario crear equipo
- Mostrar código después de crear
- Botón copiar código
-
Crear
/hackathons/[slug]/teams/join/page.tsx- Input código de invitación
- Validación en servidor
-
Crear
/hackathons/[slug]/teams/[teamId]/page.tsx- Detalle del equipo
- Lista de miembros
- Botón salir (si es miembro)
Tiempo Estimado: 2 horas
FASE 7: Submissions UI (Prioridad: ALTA)
Objetivo: Implementar la UI para submissions
Tareas:
-
Crear
/hackathons/[slug]/teams/[teamId]/submit/page.tsx- Formulario crear/editar submission
- Validación de fechas
-
Crear
/hackathons/[slug]/submissions/page.tsx- Lista de submissions (público cuando FINISHED)
-
Crear
/hackathons/[slug]/submissions/[submissionId]/page.tsx- Detalle de submission
- Leaderboard (si FINISHED)
Tiempo Estimado: 2 horas
FASE 8: Evaluation UI (Prioridad: ALTA)
Objetivo: Implementar la UI para evaluación
Tareas:
-
Crear
/admin/hackathons/[slug]/judges/page.tsx- Lista de jueces disponibles
- Asignar/remover jueces
- Validar conflictos
-
Crear
/judge/hackathons/[slug]/page.tsx- Panel de juez
- Lista de submissions (solo en JUDGING)
- Progreso de evaluación
-
Crear
/judge/hackathons/[slug]/submissions/[submissionId]/evaluate/page.tsx- Formulario de evaluación
- Input por criterio
- Enviar evaluación
Tiempo Estimado: 2.5 horas
FASE 9: Leaderboard UI (Prioridad: MEDIA)
Objetivo: Implementar la UI del leaderboard
Tareas:
- Crear
/hackathons/[slug]/leaderboard/page.tsx- Leaderboard público
- Solo visible cuando FINISHED
- Mostrar ranking con puntaje ponderado
Tiempo Estimado: 1 hora
📐 Orden de Ejecución Recomendado
Secuencia Lógica:
- FASE 1: Schema y Migración ⚡ (CRÍTICA)
- FASE 2: Teams Module Core ⚡ (CRÍTICA)
- FASE 3: Submissions Module Core ⚡ (CRÍTICA)
- FASE 4: Evaluation Module Core ⚡ (CRÍTICA)
- FASE 5: Testing ⚡ (CRÍTICA)
- FASE 6: Teams UI (ALTA)
- FASE 7: Submissions UI (ALTA)
- FASE 8: Evaluation UI (ALTA)
- FASE 9: Leaderboard UI (MEDIA)
Regla de Oro:
NO avanzar a la siguiente fase hasta que la anterior esté 100% completa y testeada.
✅ Criterios de Completitud por Fase
Fase 1 - Schema
- Schema completo sin errores
- Migración ejecutada
- Relaciones funcionando
Fase 2-4 - Module Files Core
- Types exportados
- Validations con tests pasando
- Queries con tests pasando (100% coverage)
- Actions con tests pasando
Fase 5 - Testing
- Coverage >80%
- Todos los tests pasando
- Tests de RBAC completos
Fase 6-9 - UI
- Todas las páginas funcionando
- Validaciones en servidor funcionando
- Navegación funcionando
🎯 Principios de Implementación
1. Modularidad
- Cada módulo con responsabilidad única
- Separación clara: queries, actions, validations, types
- Reutilización de helpers de
core/
2. Profesionalismo
- Código limpio y bien documentado
- Nombres descriptivos
- Manejo de errores consistente
- Patrones consistentes con Módulos 1 y 2
3. Seguridad
- RBAC en cada Server Action
- Validación en servidor (nunca confiar en cliente)
- Validación de ownership y membresía
4. Testing
- Tests antes de UI (TDD cuando sea posible)
- Coverage >80%
- Tests de RBAC exhaustivos
- Tests de validaciones de fechas
5. UI
- Server Components por defecto
- Client Components solo cuando necesario
- Formularios HTML nativos + Server Actions
📝 Notas Importantes
Validación de Fechas
- SIEMPRE validar
now < submissionDeadlinepara crear/editar submissions - SIEMPRE validar
now < submissionDeadlinepara crear/unirse/salir de equipos - Usar
validateHackathonDates()yvalidateDateExtension()cuando corresponda
Código de Invitación
- Generar código único de 6-8 caracteres alfanuméricos
- Verificar unicidad antes de crear
- Formato sugerido:
ABC123o similar
Cálculo de Leaderboard
- Fórmula:
sum(score.value * criterion.weight) / sum(criterion.maxScore * criterion.weight) * 100 - Solo considerar scores de jueces con
HackathonJudgevigente - Ordenar por puntaje descendente
Restricciones de Juez
- Juez NO puede participar en el mismo hackathon que juzga
- Juez NO puede ver scores de otros jueces durante JUDGING
- Juez puede ver todos los scores cuando hackathon está FINISHED
⏱️ Estimación Total
Tiempo Total Estimado: 18-22 horas
Desglose:
- Schema: 45-60 min
- Teams Module Core: 3.5 horas
- Submissions Module Core: 3 horas
- Evaluation Module Core: 4.5 horas
- Testing: 4-5 horas
- UI: 5.5 horas
🚀 Siguiente Paso
Comenzar con FASE 1: Schema y Migración
¿Procedemos con la implementación siguiendo este plan?