🏆 Flujo de Sponsor
Visión General
El flujo del SPONSOR se enfoca en crear challenges, hacer shortlist de proyectos y contactar equipos después del hackathon.
Diagrama de Flujo Completo
Casos de Uso Detallados
CU-1: Crear Organización
Actor: SPONSOR
Precondiciones: Usuario tiene role: SPONSOR
Flujo Principal:
- SPONSOR va a
/sponsor/organizations/create - Completa formulario:
name(requerido)description(opcional)logoUrl(opcional)website(opcional)
- Sistema crea:
OrganizationOrganizationMemberconrole: OWNER
- Redirige a
/sponsor/organizations
CU-2: Crear Sponsorship
Actor: SPONSOR (miembro de organización)
Precondiciones:
- Usuario es miembro de organización (OWNER o ADMIN)
- Hackathon existe
Flujo Principal:
- SPONSOR va a
/sponsor/sponsorships/create - Selecciona organización
- Selecciona hackathon de lista disponible
- Elige
tier:- DIAMOND
- PLATINUM
- GOLD
- SILVER
- BRONZE
- PARTNER
- Define
benefits(JSON):{
"logoPlacement": "homepage",
"boothSpace": "10x10",
"speakingSlot": true,
"socialMediaShoutout": true
} - Sistema crea
Sponsorship - Redirige a
/sponsor/sponsorships
Validación:
export async function createSponsorship(formData: FormData) {
const user = await getCurrentUser();
requireRole(user, ['SPONSOR']);
const organizationId = formData.get('organizationId');
const hackathonId = formData.get('hackathonId');
// Verificar que usuario es miembro de la organización
const membership = await db.organizationMember.findUnique({
where: {
organizationId_profileId: {
organizationId: organizationId as string,
profileId: user.profile.id,
},
},
});
if (!membership || !['OWNER', 'ADMIN'].includes(membership.role)) {
throw new Error('No tienes permiso para crear sponsorships en esta organización');
}
// Verificar que no existe sponsorship previo
const existing = await db.sponsorship.findUnique({
where: {
organizationId_hackathonId: {
organizationId: organizationId as string,
hackathonId: hackathonId as string,
},
},
});
if (existing) {
throw new Error('Esta organización ya patrocina este hackathon');
}
await db.sponsorship.create({
data: {
organizationId: organizationId as string,
hackathonId: hackathonId as string,
tier: formData.get('tier') as SponsorshipTier,
benefits: JSON.parse(formData.get('benefits') as string),
},
});
}
CU-3: Crear Challenge
Actor: SPONSOR
Precondiciones:
- Sponsorship existe
- Usuario es miembro de la organización del sponsorship
Flujo Principal:
- SPONSOR va a
/sponsor/challenges/create - Selecciona sponsorship
- Completa formulario:
title(requerido, ej: "Best use of OpenAI API")description(requerido, explicación detallada)tags(array, ej: ["AI", "NLP", "API"])prizeDetails(opcional, ej: "$5,000 + 1 year API credits")evaluationCriteria(opcional, criterios específicos)
- Sistema crea
Challenge - Challenge visible para PARTICIPANTs al enviar submission
CU-4: Ver Submissions de Challenge
Actor: SPONSOR
Precondiciones:
- Challenge existe
- Hackathon en estado RUNNING, JUDGING o FINISHED
Flujo Principal:
- SPONSOR va a
/sponsor/challenges/[challengeId] - Sistema muestra:
- Información del challenge
- Lista de submissions que eligieron este challenge
- Para cada submission:
title,descriptionrepoUrl,demoUrlteamName,teamMembers- NO muestra scores de jueces (solo en FINISHED)
- Puede hacer clic en submission para ver detalles completos
Query:
export async function getSubmissionsForChallenge(challengeId: string) {
return db.submission.findMany({
where: {
challenges: {
some: {
challengeId,
},
},
},
include: {
team: {
include: {
members: {
include: {
profile: true,
},
},
},
},
hackathon: {
select: {
id: true,
name: true,
status: true,
},
},
},
});
}
CU-5: Agregar a Shortlist
Actor: SPONSOR
Precondiciones:
- Submission existe
- Submission eligió challenge de la organización del sponsor
Flujo Principal:
- SPONSOR ve submission en lista
- Clic en "Agregar a shortlist"
- Opcional: Agrega
notesprivadas (ej: "Excelente implementación, contactar para colaboración") - Sistema crea
ShortlistItem:submissionIdorganizationIdchallengeIdnotes
- Submission aparece en
/sponsor/shortlist
Validación:
export async function addToShortlist(
submissionId: string,
organizationId: string,
challengeId: string,
notes?: string
) {
const user = await getCurrentUser();
requireRole(user, ['SPONSOR']);
// Verificar que usuario es miembro de la organización
const membership = await db.organizationMember.findUnique({
where: {
organizationId_profileId: {
organizationId,
profileId: user.profile.id,
},
},
});
if (!membership) {
throw new Error('No eres miembro de esta organización');
}
// Verificar que submission eligió el challenge
const submissionChallenge = await db.submissionChallenge.findUnique({
where: {
submissionId_challengeId: {
submissionId,
challengeId,
},
},
});
if (!submissionChallenge) {
throw new Error('Esta submission no eligió este challenge');
}
// Verificar que challenge pertenece a la organización
const challenge = await db.challenge.findUnique({
where: { id: challengeId },
include: {
sponsorship: true,
},
});
if (challenge.sponsorship.organizationId !== organizationId) {
throw new Error('El challenge no pertenece a esta organización');
}
// Crear shortlist item (unique constraint previene duplicados)
await db.shortlistItem.create({
data: {
submissionId,
organizationId,
challengeId,
notes,
},
});
}
CU-6: Ver Ganador de Challenge
Actor: SPONSOR
Precondiciones:
- Hackathon en estado FINISHED
- Challenge tiene submissions
Flujo Principal:
- SPONSOR va a
/sponsor/challenges/[challengeId]/winner - Sistema calcula:
- Submissions que eligieron el challenge
- Scores finales de cada submission
- Ganador: submission con mejor score
- Muestra:
- Ganador (puede haber empates)
- Score final
- Información del equipo
- Si no hay submissions: "Sin ganador"
- Si no hay scores aún: "Ganador pendiente"
Cálculo:
export async function getChallengeWinner(challengeId: string) {
// Obtener submissions del challenge
const submissions = await db.submission.findMany({
where: {
challenges: {
some: { challengeId },
},
},
include: {
team: true,
hackathon: true,
},
});
if (submissions.length === 0) {
return { winner: null, message: 'Sin ganador - No hay submissions' };
}
// Calcular leaderboard solo para estas submissions
const leaderboard = await calculateLeaderboard(submissions[0].hackathonId);
// Filtrar solo submissions del challenge
const challengeLeaderboard = leaderboard.filter((entry) =>
submissions.some((s) => s.id === entry.submission.id)
);
if (challengeLeaderboard.length === 0) {
return { winner: null, message: 'Ganador pendiente - No hay scores aún' };
}
// Ordenar por score descendente
challengeLeaderboard.sort((a, b) => b.weightedScore - a.weightedScore);
const topScore = challengeLeaderboard[0].weightedScore;
const winners = challengeLeaderboard.filter(
(entry) => entry.weightedScore === topScore
);
return {
winners,
topScore,
message: winners.length > 1 ? 'Empate - Múltiples ganadores' : 'Ganador único',
};
}
CU-7: Ver Perfiles Completos
Actor: SPONSOR
Precondiciones:
- Submission está en shortlist de la organización
Flujo Principal:
- SPONSOR va a
/sponsor/shortlist - Ve lista de submissions en shortlist
- Clic en "Ver perfil" de un miembro del equipo
- Sistema muestra:
bio,techStackavatarUrl- Historial de participaciones (otros hackathons)
- Proyectos previos
- Similar a LinkedIn profile view
Query:
export async function getProfileWithHistory(profileId: string) {
return db.profile.findUnique({
where: { id: profileId },
include: {
participations: {
include: {
hackathon: {
select: {
id: true,
name: true,
slug: true,
status: true,
},
},
},
},
teamMemberships: {
include: {
team: {
include: {
submissions: {
select: {
id: true,
title: true,
hackathonId: true,
},
},
},
},
},
},
},
});
}
CU-8: Contactar Equipo
Actor: SPONSOR
Precondiciones:
- Submission está en shortlist
- Usuario es miembro de la organización
Flujo Principal:
- SPONSOR ve submission en shortlist
- Clic en "Contactar Equipo"
- Sistema muestra formulario:
- Asunto
- Mensaje
- Seleccionar miembros a contactar
- Sistema envía email vía Clerk:
- Sin exponer correos del sponsor
- Sin exponer correos de los miembros
- Email enviado a través de Clerk API
- Mensaje de éxito: "Email enviado exitosamente"
Nota: Consentimiento implícito - al enviar submission, participantes aceptan contacto de sponsors vía plataforma.
Restricciones
SPONSOR NO puede:
- ❌ Evaluar proyectos: No puede asignar scores (solo jueces)
- ❌ Ver scores individuales: Solo ve resultado final en leaderboard
- ❌ Gestionar hackathons: No puede crear ni editar hackathons
- ❌ Cambiar roles: No puede cambiar roles de usuarios
SPONSOR SÍ puede:
- ✅ Ver submissions: Ver todas las submissions que eligieron sus challenges
- ✅ Shortlist: Marcar proyectos favoritos
- ✅ Ver perfiles: Ver perfiles completos de usuarios en shortlist
- ✅ Contactar: Enviar emails vía Clerk sin exponer correos
- ✅ Ver leaderboard: Ver leaderboard final completo
- ✅ Ver ganador: Ver ganador específico de su challenge
Diagrama de Estados del Challenge
Próximos Pasos
- Admin Flow - Flujo del admin
- Challenge System - Sistema de challenges completo
- Leaderboard Calculation - Cálculo de leaderboard
Siguiente: Admin Flow