🎭 Flujos de Usuarios - PuntoHack MVP
📋 Resumen de Actores
| Rol | Cantidad esperada | Propósito principal |
|---|---|---|
| PARTICIPANT | Alta (50-500 por hackathon) | Competir, formar equipos, enviar proyectos |
| JUDGE | Media (3-10 por hackathon) | Evaluar proyectos según criterios |
| ORGANIZER | Baja (1-3 por hackathon) | Crear y gestionar hackathons |
| SPONSOR | Baja (1-5 por hackathon) | Patrocinar, crear challenges, shortlist |
| ADMIN | Muy baja (1-2 en sistema) | Control total, gestión de roles |
1️⃣ Flujo: PARTICIPANT
Onboarding
- Registrarse con Clerk (email/Google/GitHub)
- Crear perfil en
/onboarding:- name, bio, techStack, avatarUrl
- role por defecto: PARTICIPANT
- Sistema crea registro en
ProfileconuserIdde Clerk
Navegación y Registro
- Ver lista de hackathons disponibles en
/hackathons- Filtro: solo hackathons con
status = REGISTRATION - Mostrar: name, description, fechas, maxTeamSize
- Filtro: solo hackathons con
- Registrarse en hackathon: clic en “Participate”
- Validar: rol = PARTICIPANT,
status = REGISTRATION,now >= registrationOpensAt && now <= registrationClosesAt - Crear registro en
HackathonParticipation - Mensaje: “¡Registro exitoso! Ahora crea o únete a un equipo.”
Formación de Equipos (solo hasta submissionDeadline)
Regla operativa: crear/unirse/salir de equipo permitido solo si:
hackathon.status IN (REGISTRATION, RUNNING)ynow < submissionDeadline
- Opción A - Crear equipo:
- Ingresar: name, description
- Validar:
hackathon.status IN (REGISTRATION, RUNNING)ynow < submissionDeadline - Sistema genera
codeúnico (ej: “HX42YZ”) - Crear registro en
Team - Crear registro en
TeamMember(creador es primer miembro) - Mostrar código para compartir
-
Opción B - Unirse a equipo:
- Ingresar código de invitación
- Validar:
hackathon.status IN (REGISTRATION, RUNNING)ynow < submissionDeadline- Código existe
- Equipo pertenece al mismo hackathon
- Equipo no ha llegado a
maxTeamSize - Usuario está registrado en ese hackathon y no pertenece a otro equipo allí
- Crear registro en
TeamMember
-
Salir del equipo (solo hasta
submissionDeadline):
- Validar:
hackathon.status IN (REGISTRATION, RUNNING)ynow < submissionDeadline - Si el equipo no tiene submission y queda vacío, se borra inmediatamente
- Si el equipo tiene submission, no se permite que el último miembro salga
- Después de
submissionDeadline, la membresía queda congelada (solo lectura)
Desarrollo y Submission
- Trabajar en proyecto (fuera de la app)
- Fecha límite: antes de
submissionDeadline
- Enviar submission en
/hackathons/[slug]/submit:- Validar: usuario es miembro del equipo
- Validar:
hackathon.status = RUNNING - Validar: tamaño del equipo
>= minTeamSize(si no cumple, bloquear submission) - Validar:
now < submissionDeadline - Campos:
- title, description (requeridos)
- repoUrl, demoUrl (opcionales pero recomendados)
- challengeId (opcional - elegir de lista)
- extraLinks (JSON - otros links relevantes)
- Crear registro en
Submission - Una sola submission por equipo (validar)
- Editar submission (antes de deadline):
- Si
now < submissionDeadline: puede editar todos los campos - Si
now >= submissionDeadline: submission bloqueada (solo lectura) - Mostrar countdown: “Puedes editar hasta [submissionDeadline]”
- Botón “Edit” desaparece después de la fecha límite
- Si intenta enviar otra submission, redirigir/avisar: “Tu equipo ya envió un proyecto.”
Ver Resultados
- Esperar evaluación (estado
JUDGING) - Ver leaderboard cuando el hackathon pase a
FINISHED- Ver puntaje total ponderado de su equipo
- Ver posición en ranking
- Ver feedback de jueces (si lo dejaron)
2️⃣ Flujo: JUDGE
Asignación
- ADMIN asigna rol JUDGE al perfil
- ORGANIZER asigna juez a hackathon:
- Crear registro en
HackathonJudge
- Crear registro en
- El juez recibe notificación (email de Clerk)
Espera
- El juez espera a que el hackathon llegue a estado
JUDGING
- El panel muestra: “La evaluación empieza en [judgingStartsAt]”
Evaluación
-
Ver submissions en
/judge/hackathons/[slug](solo cuando el hackathon está enJUDGING)- Ver TODOS los submissions del hackathon asignado
- Por cada submission ver:
- title, description, repoUrl, demoUrl
- teamName, team members
- criterios con pesos
-
Evaluar cada submission (modal o página):
- Por cada criterio (ej: Innovation, Technical, Design):
- Ver: criterion.name, criterion.description, criterion.maxScore
- Ingresar: puntaje (0 - maxScore)
- Opcional: comentario específico para ese criterio
- Por cada criterio (ej: Innovation, Technical, Design):
- Clic en “Enviar evaluación”
- Crear múltiples registros en
Score(uno por criterio)
Restricciones Importantes
- ❌ NO puede ver scores de otros jueces (query filtra por
judgeId) - ❌ NO puede formar equipo en el mismo hackathon que juzga
- ❌ NO puede registrarse como participante en el mismo hackathon
- ❌ NO ve submissions antes de
JUDGING - ❌ No puede editar scores enviados (solo ADMIN puede override)
- ✅ Si se remueve al juez, sus scores permanecen en DB pero se excluyen del cálculo
Post-Evaluación
- Panel muestra progreso:
- "Evaluadas: 8/15 submissions"
- Lista de pendientes
3️⃣ Flujo: ORGANIZER
Creación de Hackathon
- Admin asigna rol ORGANIZER al profile
- Crear hackathon en
/admin/hackathons/create:- Campos básicos: name, slug, description
- Estado inicial: DRAFT
- Fechas (validar orden):
- registrationOpensAt
- registrationClosesAt
- startsAt
- endsAt (informativo; no cambia estados en el cron)
- submissionDeadline (fecha límite para enviar/editar submissions)
- judgingStartsAt
- judgingEndsAt
- Config: maxTeamSize (default 5), minTeamSize (default 1)
- Crear registro en
Hackathon
Configuración de Evaluación
- Crear criterios en
/admin/hackathons/[slug]/criteria:- Por cada criterio:
- name (ej: "Innovación")
- description (ej: "¿Qué tan innovadora es la solución?")
- weight (1-10, default 1)
- maxScore (default 10)
- Crear registros en
Criterion - Ejemplo de configuración:
Innovación (weight: 3, maxScore: 10)
Técnico (weight: 4, maxScore: 10)
Diseño (weight: 2, maxScore: 10)
Presentación (weight: 1, maxScore: 10)
- Por cada criterio:
Asignación de Jueces
- Asignar jueces en
/admin/hackathons/[slug]/judges:- Ver lista de profiles con
role = JUDGE - Seleccionar jueces (checkbox)
- Por cada juez seleccionado:
- Crear registro en
HackathonJudge
- Crear registro en
- Enviar email de notificación (Clerk)
- Ver lista de profiles con
Publicación
- Publicar hackathon (manual; el cron NO publica):
- Validar:
- Tiene al menos 1 criterio
- Tiene al menos 1 juez asignado
- Fechas son coherentes y
now >= registrationOpensAt
- Cambiar estado: DRAFT → REGISTRATION (solo manual)
- Ya es visible para participants
- Validar:
Monitoreo
- Panel de organizador (
/admin/hackathons/[slug]/dashboard):- Stats:
- Total participaciones
- Total equipos formados
- Promedio de miembros por equipo
- Submissions enviados
- Lista de participantes con acciones:
- Ver perfil
- Eliminar usuario (borrar
HackathonParticipation)- Regla de integridad: si el usuario tiene
TeamMemberen ese hackathon, también se debe borrar suTeamMember. - Si al borrar
TeamMemberel equipo queda vacío:- Si NO hay submission del equipo: borrar el
Team. - Si SÍ hay submission del equipo: bloquear la acción (requiere intervención ADMIN en MVP).
- Si NO hay submission del equipo: borrar el
- Regla de integridad: si el usuario tiene
- Lista de equipos:
- Ver miembros
- Expulsar miembro específico (borrar
TeamMember)- Si esa expulsión deja el equipo vacío: misma regla que arriba (borrar team solo si no tiene submission; si tiene submission, bloquear y escalar a ADMIN).
- Stats:
Gestión de Fechas (Extensiones)
- ORGANIZER puede alargar fechas con restricciones (MVP estricto):
- ✅ Solo puede alargar la fase SIGUIENTE (no la actual)
- ✅ Máximo +7 días de extensión
- ✅ Nunca acortar
- ✅ Solo cuando hackathon está en curso (no DRAFT o FINISHED)
- Ejemplo:
- Si está en
REGISTRATION, puede alargarstartsAt,submissionDeadline,judgingStartsAt - Si está en
RUNNING, puede alargarsubmissionDeadline,judgingStartsAt - Si está en
JUDGING, puede alargarjudgingEndsAt
- Si está en
- Razón: No afecta a participantes en fase actual, evita confusión
Cambios de Estado Automáticos
- Sistema cambia estados basado en fechas (cron job cada minuto, solo hackathons publicados):
- Cuando
now >= registrationClosesAt: REGISTRATION → RUNNING - Cuando
now >= judgingStartsAt: RUNNING → JUDGING - Cuando
now >= judgingEndsAt: JUDGING → FINISHED
Ver Resultados
- Leaderboard final en
/admin/hackathons/[slug]/leaderboard:- Ver puntajes finales ponderados
- Ver comentarios de jueces por submission
- Export (futuro, no MVP)
4️⃣ Flujo: SPONSOR
Configuración inicial
- Admin asigna rol SPONSOR al profile
- Crear organización en
/sponsor/organizations/create:- name, description, logoUrl, website
- Crear registro en
Organization - Crear registro en
OrganizationMember(sponsor es owner)
Patrocinio
- Crear sponsorship en
/sponsor/sponsorships/create:- Seleccionar hackathon de lista disponible
- Elegir tier:
- DIAMOND, PLATINUM, GOLD, SILVER, BRONZE, PARTNER
- Definir benefits (JSON):
{
"logoPlacement": "homepage",
"boothSpace": "10x10",
"speakingSlot": true,
"socialMediaShoutout": true
} - Crear registro en
Sponsorship
Challenge
- Crear challenge en
/sponsor/challenges/create:- Asociado a sponsorship específico
- Campos:
- title (ej: "Best use of OpenAI API")
- description (explicación detallada)
- tags (ej: ["AI", "NLP", "API"])
- prizeDetails (ej: "$5,000 + 1 year API credits")
- Crear registro en
Challenge - Challenge visible para participants al enviar submission
Ver Submissions
- Panel de sponsor (
/sponsor):- Ver lista de challenges propios
- Por cada challenge, ver submissions que lo eligieron:
- Filtro:
submission.challengeId = challenge.id
- Filtro:
- Regla temporal (MVP): submissions solo existen cuando el hackathon está en
RUNNING; por lo tanto, esta vista aplica aRUNNING/JUDGING/FINISHED(enREGISTRATIONno hay submissions).
- Clic en submission para ver detalle:
- title, description, repoUrl, demoUrl
- teamName, members
- NO puede ver scores de jueces
Shortlist
- Marcar favoritos (botón "Agregar a shortlist"):
- Crear registro en
ShortlistItem:- submissionId
- organizationId
- challengeId
- notes (ej: "Excelente implementación, contactar para colaboración")
- Ver lista de shortlisted en
/sponsor/shortlist
- Crear registro en
Post-Hackathon
- Ver resultados:
- Ver leaderboard final del hackathon completo
- Ver ganador específico de su challenge (si nadie lo eligió → "Sin ganador"; si no hay scores aún → "Ganador pendiente")
- Ver puntajes finales de proyectos que eligieron su challenge
-
Ver perfiles de candidatos:
- En shortlist, puede ver perfiles completos de usuarios:
- Bio, tech stack, avatarUrl
- Historial de participaciones (otros hackathons)
- Proyectos previos
- Similar a LinkedIn profile view
- Útil para: reclutamiento, partnerships, mentorías
- En shortlist, puede ver perfiles completos de usuarios:
-
Contactar equipos:
- Ver lista de shortlist
- Botón “Contactar” que envía email vía Clerk sin exponer correos
- Consentimiento: al enviar submission aceptan contacto de sponsors vía plataforma (sin compartir email)
Restricciones
- ❌ NO puede evaluar (no crea
Score) - ❌ NO puede ver scores individuales de jueces (solo resultado final)
- ✅ Puede ver todos los submissions de su challenge
- ✅ Puede shortlist cualquier submission que eligió su challenge
- ✅ Puede ver leaderboard final completo
- ✅ Puede ver perfiles completos de usuarios en shortlist
5️⃣ Flujo: ADMIN
Gestión de Usuarios
-
Ver todos los usuarios en
/admin/users:- Tabla con: name, email, role, createdAt
- Filtros por role
- Búsqueda por name/email
-
Cambiar roles:
- Clic en "Editar" en usuario
- Dropdown: PARTICIPANT, JUDGE, ORGANIZER, ADMIN, SPONSOR
- Actualizar
profile.role - Solo ADMIN puede asignar rol ADMIN
- Eliminar usuarios:
- Soft delete (marcar
deletedAt) o - Hard delete (borrar de DB)
- Cascade: borrar participaciones, scores, etc.
- Soft delete (marcar
Gestión de Hackathons
- Acceso completo:
- Ver todos los hackathons (cualquier estado)
- Editar cualquier hackathon
- Eliminar hackathons
- Cambiar estados manualmente (override de fechas)
Gestión de Organizaciones
- CRUD completo de organizations:
- Crear, editar, eliminar
- Asignar miembros
Gestión de Challenges
- Ver/editar/eliminar cualquier challenge
Stats Globales
- Panel admin (
/admin/dashboard):- Total usuarios por role
- Total hackathons por estado
- Total equipos formados
- Total submissions
- Gráficos básicos (opcional MVP)
Dios Mode
- ✅ Acceso completo a todo el CRUD
- ✅ Sin restricciones de RBAC en queries
- ⚠️ Futuro: Agregar audit logs para rastrear acciones admin
🔄 Cambios de Estado Automáticos
Implementación Técnica
Opción 1: Cron Job (recomendado para MVP)
// src/app/api/cron/update-hackathon-states/route.ts
export async function GET(request: Request) {
// Validar cron secret
if (request.headers.get('authorization') !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('Unauthorized', { status: 401 });
}
const now = new Date();
// REGISTRATION → RUNNING
const regToRunning = await db.hackathon.updateMany({
where: {
status: 'REGISTRATION',
registrationClosesAt: { lte: now }
},
data: { status: 'RUNNING' }
});
// RUNNING → JUDGING
const runningToJudging = await db.hackathon.updateMany({
where: {
status: 'RUNNING',
judgingStartsAt: { lte: now }
},
data: { status: 'JUDGING' }
});
// JUDGING → FINISHED
const judgingToFinished = await db.hackathon.updateMany({
where: {
status: 'JUDGING',
judgingEndsAt: { lte: now }
},
data: { status: 'FINISHED' }
});
return Response.json({
success: true,
updates: {
regToRunning: regToRunning.count,
runningToJudging: runningToJudging.count,
judgingToFinished: judgingToFinished.count
}
});
}
Nota: El cron procesa solo hackathons ya publicados (status != DRAFT).
Configurar en Vercel Cron:
// vercel.json
{
"crons": [{
"path": "/api/cron/update-hackathon-states",
"schedule": "* * * * *"
}]
}
Opción 2: Middleware (solo para requests)
// src/middleware.ts
export async function middleware(request: NextRequest) {
// Solo en rutas de hackathons
if (request.nextUrl.pathname.startsWith('/hackathons')) {
await updateHackathonStates();
}
return NextResponse.next();
}
🚫 Validaciones Críticas
Al Registrarse en Hackathon
- ✅ Hackathon debe estar en estado REGISTRATION
- ✅ Fecha actual entre registrationOpensAt y registrationClosesAt
- ✅ Usuario no registrado previamente en mismo hackathon
Al Formar Equipo
- ✅ Usuario registrado en hackathon
- ✅ Usuario no pertenece a otro equipo en mismo hackathon
- ✅ Código de equipo único (8 caracteres alfanuméricos)
Al Unirse a Equipo
- ✅ Código existe
- ✅ Equipo pertenece al mismo hackathon
- ✅ Equipo no ha llegado a maxTeamSize
- ✅ Usuario registrado en hackathon
- ✅ Usuario no pertenece a otro equipo en mismo hackathon
Al Enviar Submission
- ✅ Usuario es miembro del equipo
- ✅ Hackathon en estado RUNNING
- ✅ Fecha actual antes de
submissionDeadline - ✅ Equipo no ha enviado submission previamente
- ✅ Si challengeId presente, pertenece al mismo hackathon
Al Editar Submission
- ✅ Usuario es miembro del equipo que creó el submission
- ✅ Fecha actual antes de
submissionDeadline - ✅ Hackathon NO está en estado JUDGING o FINISHED
- ❌ Si ya pasó la fecha límite: Botón "Editar" desaparece, campos de solo lectura
Al Alargar Fechas (Organizer)
- ✅ Usuario tiene role ORGANIZER o ADMIN
- ✅ Hackathon en curso (REGISTRATION, RUNNING, o JUDGING)
- ✅ Solo puede alargar fechas de fase SIGUIENTE (no la actual)
- ✅ Extensión máxima: +7 días
- ✅ Validar que nuevas fechas mantienen orden lógico
- Ejemplo:
- Estado: REGISTRATION → Puede alargar: startsAt, submissionDeadline, judgingStartsAt, judgingEndsAt
- Estado: RUNNING → Puede alargar: submissionDeadline, judgingStartsAt, judgingEndsAt
- Estado: JUDGING → Puede alargar: judgingEndsAt
Al Evaluar (Judge)
- ✅ Judge asignado al hackathon (
HackathonJudgeexiste) - ✅ Hackathon en estado JUDGING
- ✅ Submission pertenece al hackathon asignado
- ✅ Score value entre 0 y criterion.maxScore
- ✅ Judge no ha evaluado previamente esa submission+criterion (unique constraint)
Al Crear Challenge (Sponsor)
- ✅ Sponsorship existe (sponsor patrocina ese hackathon)
- ✅ Hackathon no está FINISHED
Al Shortlist (Sponsor)
- ✅ Submission pertenece a challenge de la organization
- ✅ No duplicar shortlist (unique constraint)
📊 Cálculo de Leaderboard
Fórmula de Puntaje Ponderado
// Por cada submission
const totalScore = criteria.reduce((acc, criterion) => {
// Nota: solo contar scores de jueces con HackathonJudge vigente en el hackathon
// Promedio de scores de jueces vigentes para este criterio
const scoresForCriterion = scores.filter(s =>
s.criterionId === criterion.id &&
s.submissionId === submission.id &&
activeJudgeIds.includes(s.judgeId)
);
// Regla MVP: si faltan scores para el criterio (0 jueces vigentes evaluaron), avgScore = 0
const avgScore = scoresForCriterion.length === 0
? 0
: scoresForCriterion.reduce((sum, s) => sum + s.value, 0) / scoresForCriterion.length;
// Normalizar a escala 0-10
const normalized = (avgScore / criterion.maxScore) * 10;
// Aplicar peso
return acc + (normalized * criterion.weight);
}, 0);
// Normalizar al total de pesos
const totalWeight = criteria.reduce((sum, c) => sum + c.weight, 0);
const finalScore = totalScore / totalWeight;
Ejemplo
Configuración:
- Innovación (weight: 3, maxScore: 10)
- Técnico (weight: 4, maxScore: 10)
- Diseño (weight: 2, maxScore: 10)
Submission A - Puntajes:
- Juez 1: Innovación=9, Técnico=8, Diseño=7
- Juez 2: Innovación=8, Técnico=9, Diseño=8
- Juez 3: Innovación=9, Técnico=8, Diseño=6
Cálculo:
Innovación avg: (9+8+9)/3 = 8.67 → normalized = 8.67 → weighted = 8.67*3 = 26.01
Técnico avg: (8+9+8)/3 = 8.33 → normalized = 8.33 → weighted = 8.33*4 = 33.32
Diseño avg: (7+8+6)/3 = 7.00 → normalized = 7.00 → weighted = 7.00*2 = 14.00
Total: 26.01 + 33.32 + 14.00 = 73.33
Total weight: 3 + 4 + 2 = 9
Final score: 73.33 / 9 = 8.15
✅ Resumen de Decisiones Tomadas (coherentes)
| # | Problema | Decisión Final |
|---|---|---|
| 1 | ¿JUDGE puede participar en mismo hackathon? | ❌ NO - Conflicto de interés |
| 2 | ¿Asignación Judge → Hackathon o Submissions? | Judge → Hackathon (por ahora 1 a la vez) |
| 3 | ¿Juez ve scores de otros? | 🚦 No durante JUDGING; ✅ Sí en FINISHED (transparencia interna) |
| 4 | ¿ORGANIZER puede cambiar roles? | ❌ NO - Solo ADMIN. ORGANIZER gestiona participaciones/equipos |
| 5 | ¿Estados automáticos o manuales? | ✅ Automáticos por cron; publicación es manual y solo si now >= registrationOpensAt |
| 6 | ¿Shortlist de sponsor? | Tabla ShortlistItem; sponsor marca favoritos de su challenge |
| 7 | ¿SPONSOR puede evaluar? | ❌ NO - Solo ver, shortlist y contactar |
| 8 | ¿Flujo ADMIN? | "Dios mode" - Acceso completo; puede override con advertencia |
| 9 | ¿PARTICIPANT edita submission? | ✅ Hasta submissionDeadline; luego bloqueo y solo lectura |
| 10 | ¿JUDGE impedido? | ⏳ Pendiente (no MVP) |
| 11 | ¿Extensión de fechas? | ✅ Solo fase SIGUIENTE, máximo +7d, solo extender, mantener orden fechas |
| 12 | ¿Sponsor ve leaderboard/perfiles? | ✅ Ve leaderboard final y ganador de su challenge; ve perfiles de shortlist |
| 13 | ¿Notificaciones? | 📧 Solo email (Clerk) en MVP |
| 14 | ¿Asignar juez si ya es participante? | ❌ Bloquear asignación si el profile ya participa en ese hackathon |
| 15 | ¿Salir del equipo? | ✅ Hasta submissionDeadline; si team vacío y sin submission → eliminar team; si hay submission → el último miembro no puede salir |
| 16 | ¿Empates y faltantes? | Empate permitido: múltiples ganadores; si no hay scores suficientes → ganador pendiente |
| 17 | ¿Emails a sponsors? | No mostrar emails; botón “Contactar” envía vía Clerk sin exponer correo |
📝 Pendientes para Futuras Iteraciones
- Judge impedido por submission específica
- Notificaciones in-app
- Cancelación de hackathon con avisos masivos
- Edición colaborativa de submissions
- Comentarios públicos en submissions
- Analytics avanzados y export (CSV/PDF)
- Empates avanzados (criterios de desempate custom)
🎯 Próximos Pasos
- Actualizar ROADMAP.md con stack/formularios coherente (HTML nativo + Server Actions + Zod server-side).
- Refinar ARCHITECTURE.md: constraint de submission única por team; orden duro de fechas; publicación manual; reglas de juez participante.
- Alinear MVP-ESTRICTO.md con reglas finales de visibilidad, contacto y extensiones.
- Comenzar Fase 0: Configuración + Core + Módulo Users con RBAC.
📖 Historias Técnicas Detalladas (MVP)
Narradas paso a paso, con validaciones y restricciones ya acordadas. Todas las notificaciones son vía email (Clerk); no hay notificaciones in-app en el MVP.
Historia 1: María (PARTICIPANT) edita submission antes de la fecha límite
- Onboarding: Registro (Clerk) →
/onboarding→ creaProfilecon rolPARTICIPANT. - Registro: En
/hackathons/[slug]ve estadoREGISTRATION→ valida ventana de fechas → creaHackathonParticipation. - Equipo: Crea equipo → genera
codeúnico →Team+TeamMember(creador) → invita a su equipo. - Trabajo: Desarrolla fuera de la app. Ve countdown a
submissionDeadline. - Submission inicial: En
/hackathons/[slug]/submitvalida:- Es miembro del equipo.
- Hackathon en
RUNNING. now < submissionDeadline.- Equipo sin submission previo.
- Crea
Submission(opcionalchallengeId).
- Edición antes de la fecha límite: Regresa a “Editar” mientras
now < submissionDeadline→ puede cambiar title/description/links/challengeId. - Bloqueo: Cuando
now >= submissionDeadline, botón “Editar” desaparece, formulario de solo lectura. Si intenta editar, error: “Submission bloqueada (fecha límite superada)”. - Resultados: Cuando estado
FINISHED, ve leaderboard, puntaje ponderado y feedback de jueces.
Historia 2: Carlos (JUDGE) evalúa sin ver scores de otros
- Asignación de rol: Admin cambia rol a
JUDGE(email de aviso). - Asignación a hackathon: Organizer selecciona a Carlos → crea
HackathonJudge(email de aviso). Un juez solo evalúa un hackathon a la vez en MVP. - Pre-judging: Ve panel con criterios y fecha de inicio de judging.
- Judging en vivo (
status=JUDGING): En/judge/hackathons/[slug]/evaluateve submissions del hackathon asignado. Para cada submission:- Valida que submission pertenece al hackathon asignado.
- Ingresa scores 0-maxScore por criterio + comentario opcional.
- Server Action crea
Scorepor criterio; unique constraint por (submission, judge, criterion).
- Aislamiento: No puede ver scores de otros jueces hasta que el hackathon esté
FINISHED. - Conflicto de interés: No puede registrarse ni unirse a equipos en el mismo hackathon que juzga.
- Progreso: Ve contador “Evaluated X/Y”. Puede pausar y seguir después.
Historia 3: Patricia (ORGANIZER) alarga la siguiente fase sin afectar la actual
- Creación: Rol
ORGANIZER→ crea hackathon enDRAFTcon fechas ordenadas: registrationOpensAt < registrationClosesAt < startsAt < submissionDeadline < judgingStartsAt < judgingEndsAt. - Criterios y jueces: Crea
Criterion(peso 1-10, maxScore) y asigna jueces (HackathonJudge). - Publicación (manual): Solo puede pasar DRAFT → REGISTRATION si
now >= registrationOpensAty el orden de fechas es válido. El cron no publica drafts. - Extensión controlada (regla MVP):
- Solo puede alargar la fase siguiente, no la actual.
- Máximo +7 días por cambio.
- Validar nuevo orden de fechas.
- Ejemplos:
- Si está en
REGISTRATION: puede alargar startsAt, submissionDeadline, judgingStartsAt, judgingEndsAt (no registrationClosesAt). - Si está en
RUNNING: puede alargar submissionDeadline, judgingStartsAt, judgingEndsAt. - Si está en
JUDGING: puede alargar solo judgingEndsAt.
- Si está en
- Estados automáticos (cron):
- registrationClosesAt →
RUNNING - judgingStartsAt →
JUDGING - judgingEndsAt →
FINISHED
- registrationClosesAt →
- Monitoreo: Ve stats, elimina participaciones, expulsa miembros de equipo, pero no cambia roles.
Historia 4: Roberto (SPONSOR) marca shortlist y ve perfiles
- Rol y organización: Rol
SPONSOR→ creaOrganizationy esOrganizationMemberowner. - Sponsorship: Crea
Sponsorship(tier, benefits JSON) para un hackathon. - Challenge: Crea
Challengeligado a sponsorship (title, description, tags, prizeDetails). - Durante el evento: En panel sponsor ve submissions que eligieron su challenge (filtro por
challengeId). No ve scores de jueces durante JUDGING. - Shortlist: Marca favoritos → crea
ShortlistItem(submissionId, organizationId, challengeId, notes). Único por submission+org. - Post-hackathon:
- Ve leaderboard final completo.
- Ve ganador de su challenge (submissions con su challengeId y mejor score final).
- Ve perfiles completos de usuarios en shortlist (bio, techStack, historial de participaciones) para scouting/reclutamiento.
- Puede contactar vía botón “Contactar” (email vía Clerk sin exponer correos). No hay notificaciones in-app.
Historia 5: Laura (ADMIN) “Dios mode”
- Usuarios: Cambia roles (incluido ADMIN), elimina usuarios (cascade: participations, team members, scores, etc.).
- Hackathons: Edita cualquier campo, incluso forzando validaciones (override). Puede borrar hackathons.
- Organizaciones/Challenges: CRUD completo sobre organizations, sponsorships y challenges.
- Visibilidad total: Ve todos los scores, submissions, leaderboard, stats globales.
- Nota: No hay límites en ADMIN en el MVP; futuro: audit logs.
🔍 Checklist de coherencia (rápido)
- PARTICIPANT: CRUD de participación, equipos, submissions; edita hasta
submissionDeadline; salir del equipo hasta la fecha límite; una submission por equipo (DB constraint). - JUDGE: Evalúa solo su hackathon; no ve scores ajenos durante JUDGING, sí en FINISHED; no puede ser participante del mismo hackathon; asignación bloquea si ya participa.
- ORGANIZER: Configura, asigna jueces, publica manual (solo si now >= registrationOpensAt); alarga solo fase siguiente (+7d), mantener orden de fechas; no cambia roles.
- SPONSOR: Ve submissions de su challenge, shortlist, perfiles; ve leaderboard final y ganador de su challenge; contacto vía Clerk sin exponer emails; no evalúa.
- ADMIN: Control total, roles y override; sin restricciones en MVP (advertir en UI al override).
📚 Historias Detalladas v2 (validación completa)
Escenarios con pasos, validaciones (server/DB), estados y bordes. Notificaciones solo email (Clerk). Sin in-app.
PARTICIPANT (María) – Registro, equipo, submission, edición, leave
- Onboarding: Clerk sign-up →
/onboarding→ creaProfilerol PARTICIPANT. - Registro en hackathon (
REGISTRATIONy fechas vigentes): creaHackathonParticipation.
- Server valida: estado REGISTRATION,
nowentre registrationOpensAt y registrationClosesAt, no participación previa.
- Crear equipo: genera
codeúnico →Team+TeamMember(creador).
- DB:
TeamMemberunique (teamId, profileId).
- Unirse por código (en contexto del hackathon):
- Server valida: team.hackathonId == current hackathon; código existe; no alcanzó maxTeamSize; usuario participa y no tiene otro equipo en ese hackathon.
- Salir del equipo (permitido hasta
submissionDeadline):
- Si el equipo no tiene submission y queda vacío: eliminar
Team(cleanup inmediato). - Si el equipo tiene submission: no se permite que el último miembro salga.
- No se permite leave después del deadline.
- Submission (estado RUNNING,
now < submissionDeadline):
- Una sola submission por equipo (DB:
@@unique([teamId])). - Validar miembro del equipo; aún no enviada; si
challengeId, pertenece al mismo hackathon.
- Editar submission: permitido mientras
now < submissionDeadline; luego solo lectura y botón oculto. - Resultados: en FINISHED ve leaderboard, score ponderado y feedback.
JUDGE (Carlos) – Asignación, evaluación, visibilidad de scores
- Rol: Admin cambia a JUDGE.
- Asignación: Organizer intenta crear
HackathonJudge.
- Server valida: el profile NO participa en ese hackathon (bloquea si tiene participation/team).
- Pre-judging: ve criterios y fecha de inicio.
- Evaluar (
status=JUDGING): para cada submission del hackathon asignado:
- Valida pertenencia al hackathon;
Scorepor criterio; unique (submission, judge, criterion); value 0-maxScore.
- Visibilidad de scores: no ve scores ajenos durante JUDGING; puede verlos en FINISHED. Solo cuentan scores de jueces con
HackathonJudgevigente. - Conflicto: no puede formar equipo ni participar en el mismo hackathon.
ORGANIZER (Patricia) – Publicar, extender fechas, monitorear
- Configura hackathon en DRAFT con orden duro de fechas:
registrationOpensAt < registrationClosesAt < startsAt < submissionDeadline < judgingStartsAt < judgingEndsAt. - Criterios y jueces: crea Criterion (peso 1-10, maxScore), asigna jueces.
- Publicar (manual): DRAFT → REGISTRATION solo si
now >= registrationOpensAty orden válido; cron no publica drafts. - Cron estados: registrationClosesAt→RUNNING; judgingStartsAt→JUDGING; judgingEndsAt→FINISHED.
submissionDeadlineno cambia estado, solo bloquea edición (validación en Server Actions). - Extender fechas: solo fase SIGUIENTE, máximo +7d, solo extender, mantener orden; si estado REGISTRATION no puede tocar registrationClosesAt.
- Monitoreo: ve stats, elimina participaciones, expulsa miembros de equipo; no cambia roles.
SPONSOR (Roberto) – Challenge, shortlist, contacto privado
- Org y sponsorship: crea Organization y Sponsorship (tier, benefits JSON) para un hackathon.
- Challenge: ligado a sponsorship; visible al enviar submission.
- Durante evento: ve submissions con su
challengeId; no ve scores en JUDGING. - Shortlist:
ShortlistItem(submissionId, organizationId, challengeId, notes); unique por submission+org. - Post-hackathon:
- Ve leaderboard final y ganador de su challenge (mejor score; empates permitidos → múltiples ganadores).
- Ve perfiles completos de shortlist (bio, techStack, historial).
- Botón “Contact” envía email vía Clerk sin exponer correos (consentimiento implícito al enviar submission).
ADMIN (Laura) – Overrides y riesgo controlado
- Roles: puede asignar cualquier rol (incluido ADMIN).
- Overrides: puede editar fechas y estados; la UI debe advertir riesgo de romper reglas.
- Eliminación: puede borrar usuarios (cascade participations/teams/scores) y hackathons.
- Visibilidad total: todos los scores, submissions, leaderboard, stats.
✅ Checklist de validaciones (DB vs servidor)
-
DB:
- Submission:
@@unique([teamId])(una submission por equipo). - TeamMember:
@@unique([teamId, profileId]). - Score:
@@unique([submissionId, judgeId, criterionId]). - ShortlistItem:
@@unique([submissionId, organizationId]).
- Submission:
-
Server (Zod + lógica):
- Orden de fechas duro: registrationOpensAt < registrationClosesAt < startsAt < submissionDeadline < judgingStartsAt < judgingEndsAt.
- Publicar solo si
now >= registrationOpensAt. - Editar submission solo si
now < submissionDeadliney estado != JUDGING/FINISHED. - Asignar juez bloqueado si el profile participa en ese hackathon.
- Unirse a equipo: team.hackathonId == hackathon actual; usuario participa en ese hackathon; no otro equipo; espacio disponible.
- Salir del equipo permitido hasta submissionDeadline; si team vacío → eliminar team y submission.
- Challenge en submission debe pertenecer al mismo hackathon.
- Sponsor contact: enviar email vía Clerk, no exponer correos.