Saltar al contenido principal

🚀 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-in y /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_URL en .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_SECRET en variables de entorno
  • Crear vercel.json con 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() en src/core/validations.ts
  • Implementar validateDateExtension() para ORGANIZER
  • Crear hackathonDatesSchema con 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/skip para 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