Saltar al contenido principal

🎭 Flujos de Usuarios - PuntoHack MVP

📋 Resumen de Actores

RolCantidad esperadaPropósito principal
PARTICIPANTAlta (50-500 por hackathon)Competir, formar equipos, enviar proyectos
JUDGEMedia (3-10 por hackathon)Evaluar proyectos según criterios
ORGANIZERBaja (1-3 por hackathon)Crear y gestionar hackathons
SPONSORBaja (1-5 por hackathon)Patrocinar, crear challenges, shortlist
ADMINMuy baja (1-2 en sistema)Control total, gestión de roles

1️⃣ Flujo: PARTICIPANT

Onboarding

  1. Registrarse con Clerk (email/Google/GitHub)
  2. Crear perfil en /onboarding:
    • name, bio, techStack, avatarUrl
    • role por defecto: PARTICIPANT
    • Sistema crea registro en Profile con userId de Clerk
  1. Ver lista de hackathons disponibles en /hackathons
    • Filtro: solo hackathons con status = REGISTRATION
    • Mostrar: name, description, fechas, maxTeamSize
  2. 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) y
  • now < submissionDeadline
  1. Opción A - Crear equipo:
  • Ingresar: name, description
  • Validar: hackathon.status IN (REGISTRATION, RUNNING) y now < submissionDeadline
  • Sistema genera code único (ej: “HX42YZ”)
  • Crear registro en Team
  • Crear registro en TeamMember (creador es primer miembro)
  • Mostrar código para compartir
  1. Opción B - Unirse a equipo:

    • Ingresar código de invitación
    • Validar:
      • hackathon.status IN (REGISTRATION, RUNNING) y now < 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
  2. Salir del equipo (solo hasta submissionDeadline):

  • Validar: hackathon.status IN (REGISTRATION, RUNNING) y now < 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

  1. Trabajar en proyecto (fuera de la app)
  • Fecha límite: antes de submissionDeadline
  1. 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)
  1. 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

  1. Esperar evaluación (estado JUDGING)
  2. 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

  1. ADMIN asigna rol JUDGE al perfil
  2. ORGANIZER asigna juez a hackathon:
    • Crear registro en HackathonJudge
  • El juez recibe notificación (email de Clerk)

Espera

  1. El juez espera a que el hackathon llegue a estado JUDGING
  • El panel muestra: “La evaluación empieza en [judgingStartsAt]”

Evaluación

  1. Ver submissions en /judge/hackathons/[slug] (solo cuando el hackathon está en JUDGING)

    • Ver TODOS los submissions del hackathon asignado
    • Por cada submission ver:
      • title, description, repoUrl, demoUrl
      • teamName, team members
      • criterios con pesos
  2. 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
  • 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

  1. Panel muestra progreso:
  • "Evaluadas: 8/15 submissions"
  • Lista de pendientes

3️⃣ Flujo: ORGANIZER

Creación de Hackathon

  1. Admin asigna rol ORGANIZER al profile
  2. 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

  1. 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)

Asignación de Jueces

  1. 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
    • Enviar email de notificación (Clerk)

Publicación

  1. 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

Monitoreo

  1. 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 TeamMember en ese hackathon, también se debe borrar su TeamMember.
        • Si al borrar TeamMember el 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).
    • 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).

Gestión de Fechas (Extensiones)

  1. 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 alargar startsAt, submissionDeadline, judgingStartsAt
      • Si está en RUNNING, puede alargar submissionDeadline, judgingStartsAt
      • Si está en JUDGING, puede alargar judgingEndsAt
    • Razón: No afecta a participantes en fase actual, evita confusión

Cambios de Estado Automáticos

  1. 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

  1. 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

  1. Admin asigna rol SPONSOR al profile
  2. Crear organización en /sponsor/organizations/create:
    • name, description, logoUrl, website
    • Crear registro en Organization
    • Crear registro en OrganizationMember (sponsor es owner)

Patrocinio

  1. 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

  1. 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

  1. Panel de sponsor (/sponsor):
    • Ver lista de challenges propios
    • Por cada challenge, ver submissions que lo eligieron:
      • Filtro: submission.challengeId = challenge.id
    • Regla temporal (MVP): submissions solo existen cuando el hackathon está en RUNNING; por lo tanto, esta vista aplica a RUNNING/JUDGING/FINISHED (en REGISTRATION no hay submissions).
  • Clic en submission para ver detalle:
    • title, description, repoUrl, demoUrl
    • teamName, members
    • NO puede ver scores de jueces

Shortlist

  1. 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

Post-Hackathon

  1. 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
  1. 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
  2. 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

  1. Ver todos los usuarios en /admin/users:

    • Tabla con: name, email, role, createdAt
    • Filtros por role
    • Búsqueda por name/email
  2. Cambiar roles:

  • Clic en "Editar" en usuario
  • Dropdown: PARTICIPANT, JUDGE, ORGANIZER, ADMIN, SPONSOR
  • Actualizar profile.role
  • Solo ADMIN puede asignar rol ADMIN
  1. Eliminar usuarios:
    • Soft delete (marcar deletedAt) o
    • Hard delete (borrar de DB)
    • Cascade: borrar participaciones, scores, etc.

Gestión de Hackathons

  1. Acceso completo:
    • Ver todos los hackathons (cualquier estado)
    • Editar cualquier hackathon
    • Eliminar hackathons
    • Cambiar estados manualmente (override de fechas)

Gestión de Organizaciones

  1. CRUD completo de organizations:
    • Crear, editar, eliminar
    • Asignar miembros

Gestión de Challenges

  1. Ver/editar/eliminar cualquier challenge

Stats Globales

  1. 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 (HackathonJudge existe)
  • ✅ 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)

#ProblemaDecisió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

  1. Actualizar ROADMAP.md con stack/formularios coherente (HTML nativo + Server Actions + Zod server-side).
  2. Refinar ARCHITECTURE.md: constraint de submission única por team; orden duro de fechas; publicación manual; reglas de juez participante.
  3. Alinear MVP-ESTRICTO.md con reglas finales de visibilidad, contacto y extensiones.
  4. 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 → crea Profile con rol PARTICIPANT.
  • Registro: En /hackathons/[slug] ve estado REGISTRATION → valida ventana de fechas → crea HackathonParticipation.
  • 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]/submit valida:
    • Es miembro del equipo.
    • Hackathon en RUNNING.
    • now < submissionDeadline.
    • Equipo sin submission previo.
    • Crea Submission (opcional challengeId).
  • 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]/evaluate ve 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 Score por 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 en DRAFT con 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 >= registrationOpensAt y 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.
  • Estados automáticos (cron):
    • registrationClosesAt → RUNNING
    • judgingStartsAt → JUDGING
    • judgingEndsAt → FINISHED
  • 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 → crea Organization y es OrganizationMember owner.
  • Sponsorship: Crea Sponsorship (tier, benefits JSON) para un hackathon.
  • Challenge: Crea Challenge ligado 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

  1. Onboarding: Clerk sign-up → /onboarding → crea Profile rol PARTICIPANT.
  2. Registro en hackathon (REGISTRATION y fechas vigentes): crea HackathonParticipation.
  • Server valida: estado REGISTRATION, now entre registrationOpensAt y registrationClosesAt, no participación previa.
  1. Crear equipo: genera code único → Team + TeamMember (creador).
  • DB: TeamMember unique (teamId, profileId).
  1. 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.
  1. 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.
  1. 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.
  1. Editar submission: permitido mientras now < submissionDeadline; luego solo lectura y botón oculto.
  2. Resultados: en FINISHED ve leaderboard, score ponderado y feedback.

JUDGE (Carlos) – Asignación, evaluación, visibilidad de scores

  1. Rol: Admin cambia a JUDGE.
  2. Asignación: Organizer intenta crear HackathonJudge.
  • Server valida: el profile NO participa en ese hackathon (bloquea si tiene participation/team).
  1. Pre-judging: ve criterios y fecha de inicio.
  2. Evaluar (status=JUDGING): para cada submission del hackathon asignado:
  • Valida pertenencia al hackathon; Score por criterio; unique (submission, judge, criterion); value 0-maxScore.
  1. Visibilidad de scores: no ve scores ajenos durante JUDGING; puede verlos en FINISHED. Solo cuentan scores de jueces con HackathonJudge vigente.
  2. Conflicto: no puede formar equipo ni participar en el mismo hackathon.

ORGANIZER (Patricia) – Publicar, extender fechas, monitorear

  1. Configura hackathon en DRAFT con orden duro de fechas: registrationOpensAt < registrationClosesAt < startsAt < submissionDeadline < judgingStartsAt < judgingEndsAt.
  2. Criterios y jueces: crea Criterion (peso 1-10, maxScore), asigna jueces.
  3. Publicar (manual): DRAFT → REGISTRATION solo si now >= registrationOpensAt y orden válido; cron no publica drafts.
  4. Cron estados: registrationClosesAt→RUNNING; judgingStartsAt→JUDGING; judgingEndsAt→FINISHED. submissionDeadline no cambia estado, solo bloquea edición (validación en Server Actions).
  5. Extender fechas: solo fase SIGUIENTE, máximo +7d, solo extender, mantener orden; si estado REGISTRATION no puede tocar registrationClosesAt.
  6. Monitoreo: ve stats, elimina participaciones, expulsa miembros de equipo; no cambia roles.
  1. Org y sponsorship: crea Organization y Sponsorship (tier, benefits JSON) para un hackathon.
  2. Challenge: ligado a sponsorship; visible al enviar submission.
  3. Durante evento: ve submissions con su challengeId; no ve scores en JUDGING.
  4. Shortlist: ShortlistItem (submissionId, organizationId, challengeId, notes); unique por submission+org.
  5. 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

  1. Roles: puede asignar cualquier rol (incluido ADMIN).
  2. Overrides: puede editar fechas y estados; la UI debe advertir riesgo de romper reglas.
  3. Eliminación: puede borrar usuarios (cascade participations/teams/scores) y hackathons.
  4. 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]).
  • Server (Zod + lógica):

    • Orden de fechas duro: registrationOpensAt < registrationClosesAt < startsAt < submissionDeadline < judgingStartsAt < judgingEndsAt.
    • Publicar solo si now >= registrationOpensAt.
    • Editar submission solo si now < submissionDeadline y 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.