Saltar al contenido principal

🏆 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:

  1. SPONSOR va a /sponsor/organizations/create
  2. Completa formulario:
    • name (requerido)
    • description (opcional)
    • logoUrl (opcional)
    • website (opcional)
  3. Sistema crea:
    • Organization
    • OrganizationMember con role: OWNER
  4. 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:

  1. SPONSOR va a /sponsor/sponsorships/create
  2. Selecciona organización
  3. Selecciona hackathon de lista disponible
  4. Elige tier:
    • DIAMOND
    • PLATINUM
    • GOLD
    • SILVER
    • BRONZE
    • PARTNER
  5. Define benefits (JSON):
    {
    "logoPlacement": "homepage",
    "boothSpace": "10x10",
    "speakingSlot": true,
    "socialMediaShoutout": true
    }
  6. Sistema crea Sponsorship
  7. 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:

  1. SPONSOR va a /sponsor/challenges/create
  2. Selecciona sponsorship
  3. 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)
  4. Sistema crea Challenge
  5. 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:

  1. SPONSOR va a /sponsor/challenges/[challengeId]
  2. Sistema muestra:
    • Información del challenge
    • Lista de submissions que eligieron este challenge
    • Para cada submission:
      • title, description
      • repoUrl, demoUrl
      • teamName, teamMembers
      • NO muestra scores de jueces (solo en FINISHED)
  3. 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:

  1. SPONSOR ve submission en lista
  2. Clic en "Agregar a shortlist"
  3. Opcional: Agrega notes privadas (ej: "Excelente implementación, contactar para colaboración")
  4. Sistema crea ShortlistItem:
    • submissionId
    • organizationId
    • challengeId
    • notes
  5. 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:

  1. SPONSOR va a /sponsor/challenges/[challengeId]/winner
  2. Sistema calcula:
    • Submissions que eligieron el challenge
    • Scores finales de cada submission
    • Ganador: submission con mejor score
  3. 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:

  1. SPONSOR va a /sponsor/shortlist
  2. Ve lista de submissions en shortlist
  3. Clic en "Ver perfil" de un miembro del equipo
  4. Sistema muestra:
    • bio, techStack
    • avatarUrl
    • 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:

  1. SPONSOR ve submission en shortlist
  2. Clic en "Contactar Equipo"
  3. Sistema muestra formulario:
    • Asunto
    • Mensaje
    • Seleccionar miembros a contactar
  4. 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
  5. Mensaje de éxito: "Email enviado exitosamente"

Nota: Consentimiento implícito - al enviar submission, participantes aceptan contacto de sponsors vía plataforma.

Restricciones

  • 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
  • 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


Siguiente: Admin Flow