🚀 Roadmap de Implementación - PuntoHack MVP
📊 Visión General
Este documento detalla el plan de implementación del proyecto PuntoHack, dividido en 4 fases incrementales.
⚡ Enfoque del Proyecto
IMPORTANTE: El objetivo es tener un CORE funcional robusto, no un frontend elaborado.
80% del esfuerzo: Core + Database + Testing
20% del esfuerzo: Frontend minimalista para demostrar
Timeline Estimado
Fase 0: Infraestructura (1 semana) - 70% core, 30% frontend
Fase 1: Hackathons (5-7 días) - 80% core, 20% frontend
Fase 2: Equipos y Evaluación (1.5 sem) - 85% core, 15% frontend
Fase 3: Sponsors (5 días) - 80% core, 20% frontend
────────────────────────────────────
Total: 4-5 semanas (reducido por frontend simple)
Estrategia
- ✅ Implementación incremental por fases
- ✅ Testing desde el inicio (80%+ coverage)
- ✅ RBAC en cada acción
- ✅ Validaciones Zod exhaustivas
- ⚠️ Frontend: Solo formularios HTML + Tailwind básico
- ⚠️ Sin librerías UI complejas (Radix, shadcn, etc.)
🏗️ Fase 0: Infraestructura Core
Duración: 1-2 semanas
Objetivo: Base sólida del proyecto
Configuración Inicial
# Crear proyecto Next.js 16 con Turbopack
npx create-next-app@latest puntohack \
--typescript \
--tailwind \
--app \
--src-dir \
--turbo
# Instalar dependencias CORE (⚡ PRIORIDAD)
pnpm add @prisma/client @clerk/nextjs zod@^3.22.0
pnpm add -D prisma @types/node vitest @vitest/ui
pnpm add @sentry/nextjs
# NO instalar: Radix UI, shadcn/ui, React Hook Form
# Usaremos formularios HTML nativos con Server Actions
# Configurar Prisma con Supabase
pnpm prisma init
# Configurar DATABASE_URL en .env con conexión de Supabase
Nota: Mantenemos las dependencias al mínimo. El frontend usará formularios HTML nativos.
Checklist de Tareas
1. Configuración del Proyecto
- Crear proyecto Next.js 16
- Configurar TypeScript (modo estricto)
- Configurar Tailwind CSS 4
- Configurar ESLint + Prettier
- Crear estructura de carpetas base
2. Authentication (Clerk)
- Crear cuenta en Clerk
- Instalar
@clerk/nextjs - Configurar ClerkProvider en layout
- Crear páginas
/sign-iny/sign-up - Configurar middleware de autenticación
- Proteger rutas privadas
3. Database (Prisma + Supabase)
- Crear proyecto en Supabase
- Obtener connection string de Supabase PostgreSQL
- Configurar
DATABASE_URLen.env - Configurar Prisma con Supabase
- Definir schema inicial: Profile model
- Correr primera migración (
pnpm prisma db push) - Configurar Prisma Client
- Verificar conexión con
pnpm prisma studio
4. Core Layer
Archivos a crear:
-
src/core/db.ts- Prisma Client singleton -
src/core/rbac.ts- Sistema de roles y permisos -
src/core/errors.ts- Manejo de errores + Sentry -
src/core/auth.ts- Helpers de autenticación (Clerk) -
src/core/validations.ts- Helpers de validación de fechas
src/core/db.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const db = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = db;
}
src/core/rbac.ts
import { Role } from '@prisma/client';
export type User = {
id: string;
profile: {
id: string;
role: Role;
};
};
export function hasRole(user: User, roles: Role[]): boolean {
return roles.includes(user.profile.role);
}
export function requireRole(user: User, roles: Role[]): void {
if (!hasRole(user, roles)) {
throw new Error('Unauthorized: Insufficient permissions');
}
}
src/core/errors.ts
import * as Sentry from '@sentry/nextjs';
export function captureError(
error: unknown,
context?: Record<string, any>
) {
if (process.env.NODE_ENV === 'development') {
console.error('[Error]', error, context);
}
Sentry.captureException(error, {
extra: context
});
}
src/core/auth.ts
import { auth } from '@clerk/nextjs/server';
import { getUserByClerkId } from '@/modules/users/queries';
export async function getCurrentUser() {
const { userId } = await auth();
if (!userId) return null;
const profile = await getUserByClerkId(userId);
if (!profile) return null;
return {
id: userId,
profile
};
}
src/core/validations.ts
// Helper centralizado para validación de fechas de hackathons
// Ver ARCHITECTURE.md para implementación completa
// - validateHackathonDates()
// - validateDateExtension()
// - hackathonDatesSchema (Zod)
5. Users Module
Schema Prisma:
model Profile {
id String @id @default(cuid())
userId String @unique
name String
email String @unique
avatarUrl String?
bio String?
techStack String[]
role Role @default(PARTICIPANT)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([email])
}
enum Role {
PARTICIPANT
JUDGE
ORGANIZER
ADMIN
SPONSOR
}
Files to create:
-
src/modules/users/queries.ts -
src/modules/users/actions.ts -
src/modules/users/validations.ts -
src/modules/users/types.ts
6. UI Básica
Pages to create:
-
/app/page.tsx- Página de inicio (landing) -
/app/onboarding/page.tsx- Onboarding de usuario -
/app/dashboard/page.tsx- Panel principal -
/app/admin/page.tsx- Panel de admin -
/app/admin/users/page.tsx- Gestión de usuarios
Components to create:
-
UserButton- Clerk user button -
RoleSelect- Role selection dropdown -
LoadingSpinner- Loading indicator
7. Configuración de testing
- Configurar Vitest
- Crear
tests/setup.ts - Crear
tests/test-utils.tsx - Escribir primeros tests:
- Validations
- Queries
- Actions
8. Cron Job para Cambios de Estado
- Crear
src/app/api/cron/update-hackathon-states/route.ts - Implementar lógica de transiciones automáticas
- Configurar
CRON_SECRETen variables de entorno - Crear
vercel.jsoncon configuración de cron - Probar cron localmente (opcional: usar Vercel CLI)
- Documentar reglas del cron (solo hackathons publicados)
9. Helper de Validación de Fechas
- Implementar
validateHackathonDates()ensrc/core/validations.ts - Implementar
validateDateExtension()para ORGANIZER - Crear
hackathonDatesSchemacon Zod - Escribir tests para validaciones de fechas
- Integrar en Server Actions de hackathons
10. Monitoring
- Configurar Sentry
- Crear proyecto en Sentry
- Configurar
instrumentation.ts - Probar captureError()
Criterios de Completitud (Fase 0)
CORE (80% del valor):
- ✅ Sistema RBAC funciona perfectamente (hasRole, requireRole)
- ✅ Validaciones Zod completas para users
- ✅ Helper de validación de fechas implementado y probado
- ✅ Cron job configurado y funcionando (Vercel)
- ✅ Queries y Actions probadas con tests
- ✅ Tests tienen >80% coverage
- ✅ Sentry captura errores correctamente
- ✅ Prisma Client configurado y funcional con Supabase
FRONTEND (20% del valor):
- ✅ Usuario puede registrarse con Clerk (UI default)
- ✅ Usuario puede completar onboarding (formulario HTML simple)
- ✅ Admin puede ver usuarios (tabla HTML + Tailwind)
- ✅ Admin puede cambiar roles (dropdown + Server Action)
NO REQUERIDO: Diseño elaborado, animaciones, componentes reutilizables complejos
🎪 Fase 1: Módulo Hackathons
Duración: 1 semana
Objetivo: CRUD completo de hackathons
Schema Prisma
model Hackathon {
id String @id @default(cuid())
name String
slug String @unique
description String @db.Text
status HackathonStatus @default(DRAFT)
startsAt DateTime
endsAt DateTime
registrationOpensAt DateTime
registrationClosesAt DateTime
submissionDeadline DateTime
judgingStartsAt DateTime
judgingEndsAt DateTime
maxTeamSize Int @default(5)
minTeamSize Int @default(1)
criteria Criterion[]
participations HackathonParticipation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([slug])
@@index([status])
}
enum HackathonStatus {
DRAFT
REGISTRATION
RUNNING
JUDGING
FINISHED
}
model Criterion {
id String @id @default(cuid())
hackathonId String
name String
description String?
weight Int @default(1)
maxScore Int @default(10)
hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([hackathonId])
}
model HackathonParticipation {
id String @id @default(cuid())
hackathonId String
profileId String
hackathon Hackathon @relation(fields: [hackathonId], references: [id])
profile Profile @relation(fields: [profileId], references: [id])
createdAt DateTime @default(now())
@@unique([hackathonId, profileId])
@@index([hackathonId])
@@index([profileId])
}
Checklist de Tareas
1. Módulo Hackathons
- Crear schema en Prisma
- Correr migración
-
modules/hackathons/queries.ts- getHackathon(slug)
- getHackathonById(id)
- listHackathons(filters)
- getHackathonStats(id)
-
modules/hackathons/actions.ts- createHackathon()
- updateHackathon()
- deleteHackathon()
- registerForHackathon()
- unregisterFromHackathon()
-
modules/hackathons/validations.ts- createHackathonSchema
- criterionSchema
-
modules/hackathons/types.ts
2. UI - Lista y Detalle
-
/app/hackathons/page.tsx- Lista pública -
/app/hackathons/[slug]/page.tsx- Detalle -
components/hackathons/hackathon-card.tsx -
components/hackathons/status-badge.tsx - Implementar filtros (status, fecha)
- Implementar búsqueda
3. UI - Crear/Editar
-
/app/hackathons/create/page.tsx -
/app/hackathons/[slug]/edit/page.tsx -
components/hackathons/hackathon-form.tsx -
components/hackathons/criterion-manager.tsx - Formularios HTML nativos + Server Actions + validación Zod (solo servidor)
4. Panel de Organizador
-
/app/hackathons/[slug]/dashboard/page.tsx - Mostrar estadísticas:
- Participantes registrados
- Estado actual
- Fechas importantes
- Acciones rápidas:
- Publicar
- Editar
- Ver participantes
5. Testing
- Tests de validaciones
- Tests de queries
- Tests de actions
- Tests de RBAC (solo ORGANIZER puede crear)
Criterios de Completitud (Fase 1)
- ✅ ORGANIZER puede crear hackathon completo
- ✅ Sistema de criterios funciona
- ✅ Participantes pueden registrarse
- ✅ Estados del ciclo de vida funcionan
- ✅ Panel de organizador muestra stats
- ✅ Tests tienen >80% coverage
👥 Fase 2: Equipos y Evaluación
Duración: 2 semanas
Objetivo: Sistema completo de equipos y evaluación
Schema Prisma
model Team {
id String @id @default(cuid())
hackathonId String
name String
code String @unique
description String?
hackathon Hackathon @relation(fields: [hackathonId], references: [id])
members TeamMember[]
submissions Submission[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([hackathonId])
@@index([code])
}
model TeamMember {
id String @id @default(cuid())
teamId String
profileId String
team Team @relation(fields: [teamId], references: [id])
profile Profile @relation(fields: [profileId], references: [id])
createdAt DateTime @default(now())
@@unique([teamId, profileId])
}
model Submission {
id String @id @default(cuid())
hackathonId String
teamId String
title String
description String @db.Text
repoUrl String?
demoUrl String?
extraLinks Json?
hackathon Hackathon @relation(fields: [hackathonId], references: [id])
team Team @relation(fields: [teamId], references: [id])
scores Score[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([hackathonId])
@@index([teamId])
}
model HackathonJudge {
id String @id @default(cuid())
hackathonId String
profileId String
hackathon Hackathon @relation(fields: [hackathonId], references: [id])
profile Profile @relation(fields: [profileId], references: [id])
createdAt DateTime @default(now())
@@unique([hackathonId, profileId])
}
model Score {
id String @id @default(cuid())
submissionId String
judgeId String
criterionId String
value Int
comment String? @db.Text
submission Submission @relation(fields: [submissionId], references: [id])
judge Profile @relation(fields: [judgeId], references: [id])
criterion Criterion @relation(fields: [criterionId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([submissionId, judgeId, criterionId])
@@index([submissionId])
@@index([judgeId])
}
Checklist de Tareas
Semana 1: Teams & Submissions
1. Módulo Teams
- Migración de schema
-
modules/teams/queries.ts -
modules/teams/actions.ts- createTeam() - Generar código único
- joinTeam(code) - Unirse con código
- leaveTeam()
-
modules/teams/validations.ts
2. UI Teams
- Página crear equipo
- Componente mostrar código de invitación
- Input para unirse con código
- Lista de miembros del equipo
3. Módulo Submissions
-
modules/submissions/queries.ts -
modules/submissions/actions.ts- createSubmission()
- updateSubmission()
-
modules/submissions/validations.ts
4. UI Submissions
- Formulario de submission
- Vista de submission (para jueces)
- Lista de submissions por hackathon
Semana 2: Evaluation
1. Sistema de Asignación
-
modules/evaluation/actions.ts- assignJudge() - Manual
- UI para asignar jueces (ORGANIZER)
2. Panel de Juez
-
/app/judge/page.tsx- Lista de hackathons -
/app/judge/hackathons/[slug]/page.tsx- Submissions del hackathon asignado
3. Sistema de Scoring
-
modules/evaluation/queries.ts- getHackathonSubmissionsForJudge()
- getScores()
- calculateLeaderboard()
-
modules/evaluation/actions.ts- submitScore()
- updateScore()
4. UI de Scoring
- Formulario de evaluación por criterio
- Input numérico con validación
- Textarea para comentarios
- Guardar borrador
5. Leaderboard
- Cálculo de puntaje ponderado
-
components/evaluation/leaderboard.tsx - Vista pública (después de JUDGING)
- Actualización en tiempo real
6. Testing
- Tests de teams
- Tests de submissions
- Tests de evaluation
- Tests de cálculo de scores
Criterios de Completitud (Fase 2)
- ✅ Participantes pueden crear equipos
- ✅ Sistema de códigos de invitación funciona
- ✅ Equipos pueden enviar proyectos
- ✅ Jueces son asignados correctamente
- ✅ Sistema de scoring funciona
- ✅ Leaderboard calcula correctamente
- ✅ Tests tienen >80% coverage
🏆 Fase 3: Sponsors
Duración: 1 semana
Objetivo: Sistema de sponsors y challenges
Schema Prisma
model Organization {
id String @id @default(cuid())
name String
description String? @db.Text
logoUrl String?
website String?
members OrganizationMember[]
sponsorships Sponsorship[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model OrganizationMember {
id String @id @default(cuid())
organizationId String
profileId String
role OrgMemberRole @default(MEMBER)
organization Organization @relation(fields: [organizationId], references: [id])
profile Profile @relation(fields: [profileId], references: [id])
createdAt DateTime @default(now())
@@unique([organizationId, profileId])
}
enum OrgMemberRole {
OWNER
ADMIN
MEMBER
}
model Sponsorship {
id String @id @default(cuid())
organizationId String
hackathonId String
tier SponsorshipTier
benefits Json?
organization Organization @relation(fields: [organizationId], references: [id])
hackathon Hackathon @relation(fields: [hackathonId], references: [id])
challenges Challenge[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([organizationId, hackathonId])
}
enum SponsorshipTier {
DIAMOND
PLATINUM
GOLD
SILVER
BRONZE
PARTNER
}
model Challenge {
id String @id @default(cuid())
hackathonId String
sponsorshipId String
title String
description String @db.Text
tags String[]
prizeDetails String? @db.Text
hackathon Hackathon @relation(fields: [hackathonId], references: [id])
sponsorship Sponsorship @relation(fields: [sponsorshipId], references: [id])
submissions Submission[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([hackathonId])
}
model ShortlistItem {
id String @id @default(cuid())
submissionId String
organizationId String
challengeId String
notes String? @db.Text
submission Submission @relation(fields: [submissionId], references: [id])
organization Organization @relation(fields: [organizationId], references: [id])
challenge Challenge @relation(fields: [challengeId], references: [id])
createdAt DateTime @default(now())
@@unique([submissionId, organizationId])
}
Checklist de Tareas
1. Módulo Organizations
- Migración de schema
-
modules/sponsors/queries.ts -
modules/sponsors/actions.ts- createOrganization()
- addMember()
- UI crear organización
2. Sistema de Sponsorships
- Actions para crear sponsorship
- UI seleccionar tier
- UI configurar benefits (JSON)
3. Challenges
- Actions CRUD de challenges
-
/app/sponsor/[hackathonId]/challenges/page.tsx - Formulario crear challenge
- Lista de challenges por hackathon
4. Shortlist
- Action shortlistSubmission()
- UI para marcar proyectos favoritos
- Panel con shortlist
5. Panel de Sponsor
-
/app/sponsor/page.tsx - Ver hackathons patrocinados
- Ver submissions por challenge
- Estadísticas básicas
6. Testing
- Tests de sponsors module
- Tests de challenges
- Tests de shortlist
Criterios de Completitud (Fase 3)
- ✅ Sponsors pueden crear organizaciones
- ✅ Sistema de sponsorships funciona
- ✅ Challenges pueden ser creados
- ✅ Shortlist funciona correctamente
- ✅ Panel de sponsor muestra info relevante
- ✅ Tests tienen >80% coverage
🔧 Consideraciones Generales
Performance
- Usar Server Components por defecto
- Client Components solo para interactividad
- Índices en columnas de búsqueda frecuente
- Limitar queries con
take/skippara paginación
Security
- RBAC en todas las Server Actions
- Validar con Zod TODOS los inputs
- Verificar ownership antes de editar/eliminar
- Nunca exponer datos sensibles al cliente
Testing
- Objetivo: >80% coverage en cada módulo
- Priorizar tests de lógica de negocio
- Tests de validaciones Zod (100%)
- Tests de RBAC
Deployment
Opciones recomendadas:
- Vercel: Deploy automático, edge functions
- Railway: Database + App en un solo lugar
- Self-hosted: Docker + PostgreSQL
📋 Quick Reference
Comandos Útiles
# Development
pnpm dev
# Database
pnpm prisma generate
pnpm prisma db push
pnpm prisma studio
# Testing
pnpm test
pnpm test:coverage
# Build
pnpm build
Estructura de Módulo
modules/[domain]/
├── queries.ts # Read operations
├── actions.ts # Write operations
├── types.ts # TypeScript types
└── validations.ts # Zod schemas
Pattern de Server Action
'use server';
export async function myAction(formData: FormData) {
try {
// 1. Auth
const user = await getCurrentUser();
requireRole(user, ['ADMIN']);
// 2. Validate
const validated = schema.parse(data);
// 3. Business Logic
const result = await db.model.create({ data: validated });
// 4. Revalidate
revalidatePath('/path');
return { success: true, data: result };
} catch (error) {
captureError(error, { context: 'myAction' });
return { success: false, error: error.message };
}
}
Última Actualización: 30 de diciembre, 2025
Versión: 2.0 - Simplificado
Estado: 📋 Listo para la Fase 0