📋 Plan de Ejecución - Módulo 4: Sponsors Module
Fecha de Creación: 1 de enero, 2025
Estado: 📝 Planificado
Dependencias: ✅ Core Layer, ✅ Users Module, ✅ Hackathons Module, ✅ Teams Module, ✅ Submissions Module
🎯 Objetivo del Módulo
Implementar el sistema completo de sponsors con:
- Gestión de organizaciones (organizations)
- Sistema de sponsorships con tiers
- Challenges de sponsors
- Shortlist de proyectos favoritos
- Panel de sponsor para gestión
📊 Análisis de Requerimientos
1. Schema Prisma (5 modelos nuevos)
Model Organization
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
@@index([name])
}
Model OrganizationMember
model OrganizationMember {
id String @id @default(cuid())
organizationId String
profileId String
role OrgMemberRole @default(MEMBER)
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
profile Profile @relation(fields: [profileId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([organizationId, profileId])
@@index([organizationId])
@@index([profileId])
}
Enum OrgMemberRole
enum OrgMemberRole {
OWNER
ADMIN
MEMBER
}
Model Sponsorship
model Sponsorship {
id String @id @default(cuid())
organizationId String
hackathonId String
tier SponsorshipTier
benefits Json? // Estructura flexible para beneficios del tier
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
challenges Challenge[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([organizationId, hackathonId]) // Una org solo puede patrocinar un hackathon una vez
@@index([hackathonId])
@@index([organizationId])
@@index([tier])
}
Enum SponsorshipTier
enum SponsorshipTier {
DIAMOND
PLATINUM
GOLD
SILVER
BRONZE
PARTNER
}
Model Challenge
model Challenge {
id String @id @default(cuid())
hackathonId String
sponsorshipId String
title String
description String @db.Text
tags String[] // Tags para categorización (ej: "AI", "Blockchain", "Web3")
prizeDetails String? @db.Text // Detalles del premio (opcional)
hackathon Hackathon @relation(fields: [hackathonId], references: [id], onDelete: Cascade)
sponsorship Sponsorship @relation(fields: [sponsorshipId], references: [id], onDelete: Cascade)
shortlistItems ShortlistItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([hackathonId])
@@index([sponsorshipId])
}
Nota: La relación con Submission se maneja a través de ShortlistItem, no directamente.
Model ShortlistItem
model ShortlistItem {
id String @id @default(cuid())
submissionId String
organizationId String
challengeId String
notes String? @db.Text // Notas internas del sponsor sobre el proyecto
submission Submission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
challenge Challenge @relation(fields: [challengeId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([submissionId, organizationId]) // Un proyecto solo puede estar en el shortlist de una org una vez
@@index([submissionId])
@@index([organizationId])
@@index([challengeId])
}
2. Actualización del Schema Existente
Actualizar Profile model
model Profile {
// ... campos existentes ...
// Agregar relaciones
organizationMemberships OrganizationMember[]
}
Actualizar Hackathon model
model Hackathon {
// ... campos existentes ...
// Agregar relaciones
sponsorships Sponsorship[]
}
Actualizar Submission model
model Submission {
// ... campos existentes ...
// Agregar relaciones
shortlistItems ShortlistItem[]
}
🗂️ Estructura del Módulo
Archivos a Crear
src/modules/sponsors/
├── types.ts # TypeScript types
├── validations.ts # Zod schemas
├── queries.ts # Read operations
└── actions.ts # Write operations (Server Actions)
📐 Plan de Ejecución por Fases
FASE 1: Schema y Migración (Prioridad: CRÍTICA)
Objetivo: Definir y migrar el schema de base de datos
Tareas:
-
Actualizar
prisma/schema.prisma- Agregar model
Organization - Agregar model
OrganizationMember - Agregar model
Sponsorship - Agregar model
Challenge - Agregar model
ShortlistItem - Agregar enums
OrgMemberRoleySponsorshipTier - Actualizar relaciones en
Profile,Hackathon,Submission - Definir índices y constraints
- Agregar model
-
Ejecutar migración
pnpm prisma migrate dev --name add_sponsors_module -
Verificar schema en Prisma Studio
Criterio de Completitud:
- Schema completo sin errores
- Migración ejecada exitosamente
- Relaciones funcionando correctamente
- Índices y constraints correctos
Tiempo Estimado: 45-60 minutos
FASE 2: Sponsors Module Core - Types y Validations (Prioridad: CRÍTICA)
Objetivo: Definir tipos TypeScript y schemas de validación
2.1 Types (src/modules/sponsors/types.ts)
Tareas:
- Definir tipos TypeScript:
OrganizationWithRelations(con miembros, sponsorships)OrganizationMemberWithProfileSponsorshipWithRelations(con organization, hackathon, challenges)ChallengeWithRelations(con sponsorship, shortlistItems)ShortlistItemWithRelations(con submission, organization, challenge)CreateOrganizationInputUpdateOrganizationInputCreateSponsorshipInputUpdateSponsorshipInputCreateChallengeInputUpdateChallengeInputShortlistSubmissionInput
Criterio de Completitud:
- Todos los tipos exportados
- Tipos usados en queries/actions
Tiempo Estimado: 20 minutos
2.2 Validations (src/modules/sponsors/validations.ts)
Tareas:
-
Crear
createOrganizationSchema(Zod)- name (min 3, max 100)
- description (opcional, max 1000)
- logoUrl (opcional, URL válida)
- website (opcional, URL válida)
-
Crear
updateOrganizationSchema(Zod)- Todos los campos opcionales
-
Crear
createSponsorshipSchema(Zod)- organizationId (CUID)
- hackathonId (CUID)
- tier (enum SponsorshipTier)
- benefits (opcional, JSON)
-
Crear
updateSponsorshipSchema(Zod)- tier (opcional)
- benefits (opcional)
-
Crear
createChallengeSchema(Zod)- hackathonId (CUID)
- sponsorshipId (CUID)
- title (min 3, max 200)
- description (min 10, max 5000)
- tags (array de strings, max 10 tags)
- prizeDetails (opcional, max 2000)
-
Crear
updateChallengeSchema(Zod)- Todos los campos opcionales
-
Crear
shortlistSubmissionSchema(Zod)- submissionId (CUID)
- organizationId (CUID)
- challengeId (CUID)
- notes (opcional, max 2000)
Criterio de Completitud:
- Todos los schemas creados
- Tests de validaciones pasando
Tiempo Estimado: 45 minutos
FASE 3: Sponsors Module Core - Queries (Prioridad: CRÍTICA)
Objetivo: Implementar todas las queries de lectura
3.1 Queries (src/modules/sponsors/queries.ts)
Tareas:
-
Implementar
getOrganizationById(id: string)- Incluir miembros y sponsorships
-
Implementar
getOrganizationByProfileId(profileId: string)- Obtener organizaciones donde el perfil es miembro
-
Implementar
getOrganizationsByMember(profileId: string)- Lista de organizaciones del usuario
-
Implementar
getSponsorshipById(id: string)- Incluir organization, hackathon, challenges
-
Implementar
getSponsorshipsByHackathon(hackathonId: string)- Lista de sponsorships de un hackathon
- Ordenar por tier (DIAMOND primero)
-
Implementar
getSponsorshipsByOrganization(organizationId: string)- Lista de sponsorships de una organización
-
Implementar
getChallengeById(id: string)- Incluir sponsorship, shortlistItems
-
Implementar
getChallengesBySponsorship(sponsorshipId: string)- Lista de challenges de un sponsorship
-
Implementar
getChallengesByHackathon(hackathonId: string)- Lista de todos los challenges de un hackathon
-
Implementar
getShortlistByOrganization(organizationId: string)- Lista de proyectos en shortlist
- Incluir submission, challenge, team
-
Implementar
getShortlistByChallenge(challengeId: string)- Lista de proyectos en shortlist de un challenge específico
-
Implementar
isShortlisted(submissionId: string, organizationId: string)- Verificar si un proyecto está en el shortlist
Criterio de Completitud:
- Todas las queries implementadas
- Tests de queries pasando (100% coverage)
Tiempo Estimado: 2 horas
FASE 4: Sponsors Module Core - Actions (Prioridad: CRÍTICA)
Objetivo: Implementar todas las Server Actions
4.1 Actions (src/modules/sponsors/actions.ts)
Tareas:
-
Implementar
createOrganization(data: CreateOrganizationInput, currentUserId: string)- RBAC: solo SPONSOR/ADMIN
- Validar con Zod
- Crear Organization
- Crear OrganizationMember con role OWNER
- Revalidar paths
-
Implementar
updateOrganization(organizationId: string, data: UpdateOrganizationInput, currentUserId: string)- RBAC: solo SPONSOR/ADMIN (miembro de la org con role OWNER/ADMIN)
- Validar ownership
- Validar con Zod
- Actualizar Organization
- Revalidar paths
-
Implementar
addOrganizationMember(organizationId: string, profileId: string, role: OrgMemberRole, currentUserId: string)- RBAC: solo SPONSOR/ADMIN (miembro de la org con role OWNER/ADMIN)
- Validar ownership
- Validar que profileId no es ya miembro
- Crear OrganizationMember
- Revalidar paths
-
Implementar
removeOrganizationMember(organizationId: string, profileId: string, currentUserId: string)- RBAC: solo SPONSOR/ADMIN (miembro de la org con role OWNER/ADMIN)
- Validar ownership
- Validar que no se elimina el último OWNER
- Eliminar OrganizationMember
- Revalidar paths
-
Implementar
createSponsorship(data: CreateSponsorshipInput, currentUserId: string)- RBAC: solo SPONSOR (miembro de la organization)
- Validar que organization existe
- Validar que hackathon existe
- Validar que no existe sponsorship previa (unique constraint)
- Validar con Zod
- Crear Sponsorship
- Revalidar paths
-
Implementar
updateSponsorship(sponsorshipId: string, data: UpdateSponsorshipInput, currentUserId: string)- RBAC: solo SPONSOR (miembro de la organization)
- Validar ownership
- Validar con Zod
- Actualizar Sponsorship
- Revalidar paths
-
Implementar
createChallenge(data: CreateChallengeInput, currentUserId: string)- RBAC: solo SPONSOR (miembro de la organization del sponsorship)
- Validar que sponsorship existe
- Validar que hackathon del sponsorship existe
- Validar con Zod
- Crear Challenge
- Revalidar paths
-
Implementar
updateChallenge(challengeId: string, data: UpdateChallengeInput, currentUserId: string)- RBAC: solo SPONSOR (miembro de la organization del sponsorship)
- Validar ownership
- Validar con Zod
- Actualizar Challenge
- Revalidar paths
-
Implementar
deleteChallenge(challengeId: string, currentUserId: string)- RBAC: solo SPONSOR (miembro de la organization del sponsorship)
- Validar ownership
- Eliminar Challenge (cascade elimina shortlistItems)
- Revalidar paths
-
Implementar
shortlistSubmission(data: ShortlistSubmissionInput, currentUserId: string)- RBAC: solo SPONSOR (miembro de la organization)
- Validar que submission existe
- Validar que challenge existe y pertenece a la organization
- Validar que submission pertenece al hackathon del challenge
- Validar que no está ya en shortlist (unique constraint)
- Validar con Zod
- Crear ShortlistItem
- Revalidar paths
-
Implementar
removeFromShortlist(submissionId: string, organizationId: string, currentUserId: string)- RBAC: solo SPONSOR (miembro de la organization)
- Validar ownership
- Eliminar ShortlistItem
- Revalidar paths
Criterio de Completitud:
- Todas las actions implementadas
- RBAC funcionando
- Validaciones funcionando
- Tests pasando
Tiempo Estimado: 3-4 horas
FASE 5: Testing (Prioridad: CRÍTICA)
Objetivo: Tests completos con >80% coverage
Tareas:
-
Crear
tests/modules/sponsors/validations.test.ts- Tests de todos los schemas Zod
- Tests de edge cases
-
Crear
tests/modules/sponsors/queries.test.ts- Tests de todas las queries
- Tests de relaciones cargadas
- Tests de filtros
-
Crear
tests/modules/sponsors/actions.test.ts- Tests de todas las actions
- Tests de RBAC
- Tests de validaciones de negocio
- Tests de constraints (unique, cascade)
Criterio de Completitud:
- Coverage >80%
- Todos los tests pasando
- Tests de RBAC completos
Tiempo Estimado: 3-4 horas
FASE 6: UI - Organizations (Prioridad: ALTA)
Objetivo: UI para gestión de organizaciones
Tareas:
-
Crear
/sponsor/organizations/create/page.tsx- Formulario crear organización
- Campos: name, description, logoUrl, website
- Server Action:
createOrganization - Redirige a
/sponsor/organizations/[id]después de crear
-
Crear
/sponsor/organizations/[id]/page.tsx- Detalle de organización
- Lista de miembros con roles
- Lista de sponsorships
- Botón "Editar" (solo OWNER/ADMIN)
- Botón "Agregar Miembro" (solo OWNER/ADMIN)
-
Crear
/sponsor/organizations/[id]/edit/page.tsx- Formulario editar organización
- Server Action:
updateOrganization
-
Crear
/sponsor/organizations/page.tsx- Lista de organizaciones del usuario actual
- Link a crear nueva organización
Tiempo Estimado: 2 horas
FASE 7: UI - Sponsorships (Prioridad: ALTA)
Objetivo: UI para gestión de sponsorships
Tareas:
-
Crear
/sponsor/sponsorships/create/page.tsx- Formulario crear sponsorship
- Select de organización (solo organizaciones del usuario)
- Select de hackathon (solo hackathons publicados)
- Select de tier (DIAMOND, PLATINUM, GOLD, SILVER, BRONZE, PARTNER)
- Textarea para benefits (JSON opcional)
- Server Action:
createSponsorship
-
Crear
/sponsor/sponsorships/[id]/page.tsx- Detalle de sponsorship
- Información del tier
- Lista de challenges
- Botón "Crear Challenge"
- Botón "Editar" (solo miembros de la org)
-
Crear
/sponsor/sponsorships/[id]/edit/page.tsx- Formulario editar sponsorship
- Server Action:
updateSponsorship
Tiempo Estimado: 2 horas
FASE 8: UI - Challenges (Prioridad: ALTA)
Objetivo: UI para gestión de challenges
Tareas:
-
Crear
/sponsor/challenges/create/page.tsx- Formulario crear challenge
- Select de sponsorship (solo sponsorships de organizaciones del usuario)
- Campos: title, description, tags (input múltiple), prizeDetails
- Server Action:
createChallenge
-
Crear
/sponsor/challenges/[id]/page.tsx- Detalle de challenge
- Información del challenge
- Lista de submissions en shortlist
- Botón "Agregar a Shortlist"
- Botón "Editar" (solo miembros de la org)
- Botón "Eliminar" (solo miembros de la org)
-
Crear
/sponsor/challenges/[id]/edit/page.tsx- Formulario editar challenge
- Server Action:
updateChallenge
-
Crear
/sponsor/challenges/[id]/submissions/page.tsx- Lista de submissions del hackathon
- Filtro por challenge (opcional)
- Botón "Agregar a Shortlist" por cada submission
- Mostrar si ya está en shortlist
Tiempo Estimado: 2.5 horas
FASE 9: UI - Shortlist (Prioridad: ALTA)
Objetivo: UI para gestión de shortlist
Tareas:
-
Crear
/sponsor/shortlist/page.tsx- Lista de proyectos en shortlist
- Filtro por organización
- Filtro por challenge
- Información de cada submission
- Botón "Ver Detalle"
- Botón "Remover de Shortlist"
-
Crear
/sponsor/shortlist/[submissionId]/page.tsx- Detalle de submission en shortlist
- Información del equipo
- Información del challenge
- Notas del sponsor
- Botón "Editar Notas"
- Botón "Contactar Equipo" (email vía Clerk)
Tiempo Estimado: 1.5 horas
FASE 10: UI - Panel de Sponsor (Prioridad: ALTA)
Objetivo: Dashboard principal para sponsors
Tareas:
-
Crear
/sponsor/page.tsx- Dashboard principal
- Estadísticas:
- Total de organizaciones
- Total de sponsorships activas
- Total de challenges
- Total de proyectos en shortlist
- Lista de hackathons patrocinados
- Acciones rápidas:
- Crear organización
- Crear sponsorship
- Ver shortlist
-
Crear
/sponsor/hackathons/[slug]/page.tsx- Vista de hackathon patrocinado
- Lista de challenges del hackathon
- Lista de submissions
- Estadísticas del hackathon
Tiempo Estimado: 2 horas
FASE 11: Integración con Módulos Existentes (Prioridad: MEDIA)
Objetivo: Integrar sponsors con módulos existentes
Tareas:
-
Actualizar
/hackathons/[slug]/page.tsx- Mostrar lista de sponsors (con logos)
- Mostrar lista de challenges disponibles
- Link a challenges en submissions
-
Actualizar
/hackathons/[slug]/submissions/[submissionId]/page.tsx- Mostrar challenges disponibles
- Botón "Aplicar a Challenge" (si es PARTICIPANT y tiene submission)
-
Actualizar
/hackathons/[slug]/teams/[teamId]/submit/page.tsx- Mostrar challenges disponibles al crear/editar submission
- Campo opcional para seleccionar challenge
Nota: La relación entre Submission y Challenge se maneja a través de ShortlistItem, no directamente. Los participantes pueden "aplicar" a un challenge, lo que crea un ShortlistItem.
Tiempo Estimado: 2 horas
📐 Orden de Ejecución Recomendado
Secuencia Lógica:
- FASE 1: Schema y Migración ⚡ (CRÍTICA)
- FASE 2: Types y Validations ⚡ (CRÍTICA)
- FASE 3: Queries ⚡ (CRÍTICA)
- FASE 4: Actions ⚡ (CRÍTICA)
- FASE 5: Testing ⚡ (CRÍTICA)
- FASE 6: UI Organizations (ALTA)
- FASE 7: UI Sponsorships (ALTA)
- FASE 8: UI Challenges (ALTA)
- FASE 9: UI Shortlist (ALTA)
- FASE 10: UI Panel Sponsor (ALTA)
- FASE 11: Integración (MEDIA)
Regla de Oro:
NO avanzar a la siguiente fase hasta que la anterior esté 100% completa y testeada.
✅ Criterios de Completitud por Fase
Fase 1 - Schema
- Schema completo sin errores
- Migración ejecutada
- Relaciones funcionando
- Índices y constraints correctos
Fase 2 - Types y Validations
- Types exportados
- Validations con tests pasando
Fase 3 - Queries
- Todas las queries implementadas
- Tests pasando (100% coverage)
Fase 4 - Actions
- Todas las actions implementadas
- RBAC funcionando
- Validaciones funcionando
- Tests pasando
Fase 5 - Testing
- Coverage >80%
- Todos los tests pasando
- Tests de RBAC completos
Fase 6-11 - UI
- Todas las páginas funcionando
- Validaciones en servidor funcionando
- Navegación funcionando
- Integración con módulos existentes
🎯 Principios de Implementación
RBAC (Role-Based Access Control)
- SPONSOR: Puede crear organizaciones, sponsorships, challenges, shortlist
- ADMIN: Puede hacer todo lo que SPONSOR puede hacer
- ORGANIZER: No tiene acceso al módulo de sponsors (solo puede ver sponsors en hackathons)
- PARTICIPANT: Puede ver challenges y aplicar a ellos (crear ShortlistItem)
- JUDGE: No tiene acceso al módulo de sponsors
Validaciones de Negocio
-
Organizations:
- Un usuario solo puede crear organizaciones si tiene role SPONSOR o ADMIN
- Solo OWNER/ADMIN pueden editar organización
- No se puede eliminar el último OWNER
-
Sponsorships:
- Una organización solo puede patrocinar un hackathon una vez (unique constraint)
- Solo miembros de la organización pueden crear sponsorships
-
Challenges:
- Solo se pueden crear challenges para sponsorships de la organización del usuario
- Challenges pertenecen a un hackathon específico
-
Shortlist:
- Un proyecto solo puede estar en el shortlist de una organización una vez (unique constraint)
- Solo miembros de la organización pueden agregar a shortlist
- El proyecto debe pertenecer al hackathon del challenge
Relaciones y Constraints
- Organization → OrganizationMember: Cascade delete
- Organization → Sponsorship: Cascade delete
- Sponsorship → Challenge: Cascade delete
- Challenge → ShortlistItem: Cascade delete
- Submission → ShortlistItem: Cascade delete
- Unique constraints:
[organizationId, profileId]en OrganizationMember[organizationId, hackathonId]en Sponsorship[submissionId, organizationId]en ShortlistItem
📊 Estimación de Tiempo Total
| Fase | Tiempo Estimado |
|---|---|
| FASE 1: Schema | 45-60 min |
| FASE 2: Types y Validations | 1 hora |
| FASE 3: Queries | 2 horas |
| FASE 4: Actions | 3-4 horas |
| FASE 5: Testing | 3-4 horas |
| FASE 6: UI Organizations | 2 horas |
| FASE 7: UI Sponsorships | 2 horas |
| FASE 8: UI Challenges | 2.5 horas |
| FASE 9: UI Shortlist | 1.5 horas |
| FASE 10: UI Panel Sponsor | 2 horas |
| FASE 11: Integración | 2 horas |
| TOTAL | 22-24 horas (~3 días de trabajo) |
🚨 Consideraciones Importantes
1. Relación Submission-Challenge
Importante: La relación entre Submission y Challenge NO es directa. Se maneja a través de ShortlistItem:
- Un participante puede "aplicar" a un challenge, lo que crea un
ShortlistItem - Un sponsor puede agregar un proyecto a su shortlist, lo que también crea un
ShortlistItem - Esto permite flexibilidad: un proyecto puede estar en múltiples shortlists de diferentes organizaciones
2. Benefits en Sponsorship
El campo benefits es de tipo Json?, lo que permite flexibilidad:
- Puede contener información estructurada sobre beneficios del tier
- Ejemplo:
{ "logo": true, "booth": true, "speaking": false, "prize": 5000 } - La UI puede renderizar esto dinámicamente
3. Tags en Challenge
Los tags son un array de strings, útil para:
- Categorización (ej: ["AI", "Blockchain", "Web3"])
- Búsqueda y filtrado
- Matching automático con submissions
4. Notas en ShortlistItem
Las notes son opcionales y permiten a los sponsors:
- Anotar observaciones sobre proyectos
- Marcar proyectos favoritos
- Comunicarse internamente sobre proyectos
🎯 Objetivos Finales
Al completar este módulo, se debe poder:
- ✅ Sponsors pueden crear y gestionar organizaciones
- ✅ Sponsors pueden crear sponsorships para hackathons
- ✅ Sponsors pueden crear challenges para sus sponsorships
- ✅ Sponsors pueden agregar proyectos a su shortlist
- ✅ Participantes pueden ver challenges y aplicar a ellos
- ✅ Organizadores pueden ver sponsors de sus hackathons
- ✅ Panel de sponsor muestra estadísticas y gestión completa
- ✅ Tests tienen >80% coverage
- ✅ RBAC funciona correctamente en todas las acciones
- ✅ UI es funcional y profesional
📝 Notas Finales
Dependencias Verificadas
- ✅ Core Layer:
rbac.ts,errors.ts,db.ts,auth.ts - ✅ Users Module:
Profilemodel,Roleenum - ✅ Hackathons Module:
Hackathonmodel,HackathonStatusenum - ✅ Teams Module:
Teammodel,TeamMembermodel - ✅ Submissions Module:
Submissionmodel
Patrones a Seguir
- Usar Server Actions para todas las operaciones de escritura
- Usar Server Components por defecto
- Validar con Zod en todas las entradas
- Implementar RBAC en todas las actions
- Escribir tests desde el inicio
- Seguir la estructura de módulos existente
Última Actualización: 1 de enero, 2025
Estado: 📝 Planificado
Próximo Paso: Iniciar FASE 1 - Schema y Migración