Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"dev": "next dev",
"postinstall": "prisma generate",
"lint": "next lint",
"start": "next start"
"start": "next start",
"pastEditions": "node --loader ts-node/esm src/lib/exportcsv.ts"
},
"prisma": {
"seed": "node --loader ts-node/esm prisma/seed.ts"
Expand Down
86 changes: 86 additions & 0 deletions public/PastEditions.json
Original file line number Diff line number Diff line change
Expand Up @@ -394,5 +394,91 @@
"puntajePistaC": 0,
"puntajeFinal": 0
}
],
"LARC 2025": [
{
"nombreEquipo": "Wall - E",
"puntajePistaA": 140,
"puntajePistaB": 261,
"puntajePistaC": 119,
"puntajeFinal": 520
},
{
"nombreEquipo": "Balboa",
"puntajePistaA": 90,
"puntajePistaB": 74,
"puntajePistaC": 203,
"puntajeFinal": 367
},
{
"nombreEquipo": "CHAPPIE",
"puntajePistaA": 50,
"puntajePistaB": 205,
"puntajePistaC": 42,
"puntajeFinal": 297
},
{
"nombreEquipo": "404 Team Not Found",
"puntajePistaA": 50,
"puntajePistaB": 165,
"puntajePistaC": 55,
"puntajeFinal": 270
},
{
"nombreEquipo": "Henry's Law Enforcement",
"puntajePistaA": 65,
"puntajePistaB": 137,
"puntajePistaC": 12,
"puntajeFinal": 214
},
{
"nombreEquipo": "JADE Dynamics",
"puntajePistaA": 60,
"puntajePistaB": 74,
"puntajePistaC": 38,
"puntajeFinal": 172
},
{
"nombreEquipo": "Vaca Lola",
"puntajePistaA": 0,
"puntajePistaB": 126,
"puntajePistaC": 9,
"puntajeFinal": 135
},
{
"nombreEquipo": "Nova",
"puntajePistaA": 80,
"puntajePistaB": 8,
"puntajePistaC": 37,
"puntajeFinal": 125
},
{
"nombreEquipo": "Tecno-Ready",
"puntajePistaA": 50,
"puntajePistaB": 0,
"puntajePistaC": 56,
"puntajeFinal": 106
},
{
"nombreEquipo": "Shebots",
"puntajePistaA": 40,
"puntajePistaB": 3,
"puntajePistaC": 12,
"puntajeFinal": 55
},
{
"nombreEquipo": "Space Invaders",
"puntajePistaA": 0,
"puntajePistaB": 0,
"puntajePistaC": 0,
"puntajeFinal": 0
},
{
"nombreEquipo": "TECH",
"puntajePistaA": 0,
"puntajePistaB": 0,
"puntajePistaC": 0,
"puntajeFinal": 0
}
]
}
5 changes: 5 additions & 0 deletions public/PastEditionsWinners.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@
"year": 2025,
"imageUrl": "/images/Wall-E.jpg",
"winner": "Wall - E"
},
{
"year": "LARC 2025",
"imageUrl": "/images/Wall-E.jpg",
"winner": "Wall - E"
}
]
7 changes: 3 additions & 4 deletions src/app/(pages)/editions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ type Edition = {
imageUrl: string;
};


export default function HistoryPage() {
return (
<main className="bg-black text-white">
Expand All @@ -38,9 +37,9 @@ export default function HistoryPage() {
</div>

<div className="mt-12 text-center">
<ScoreboardTable year={2024} />
<ScoreboardTable year={2025} />

<ScoreboardTable year={"LARC 2025"} />
<ScoreboardTable year={2025} />
<ScoreboardTable year={2024} />
</div>
</section>

Expand Down
46 changes: 33 additions & 13 deletions src/app/_components/ScoreboardTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ type Team = {
puntajeFinal: number;
};

const scoreboard: Record<number, Team[]> = scoreboardJson;
const scoreboard: Record<number | string, Team[]> = scoreboardJson;

interface ScoreboardTableProps {
year: number;
year: number | string;
}

const ScoreboardTable = ({ year }: ScoreboardTableProps) => {
Expand All @@ -26,16 +26,28 @@ const ScoreboardTable = ({ year }: ScoreboardTableProps) => {
{scoreboardData.length === 0 ? (
<p className="text-center text-gray-400">No hay datos para este año.</p>
) : (
<div className="hidden md:block overflow-x-auto rounded-lg border border-gray-700">
<div className="hidden overflow-x-auto rounded-lg border border-gray-700 md:block">
<table className="w-full text-left">
<thead className="bg-gray-800 text-sm uppercase tracking-wider text-roboblue">
<tr>
<th scope="col" className="p-4 text-center font-anton">#</th>
<th scope="col" className="p-4 font-anton">Equipo</th>
<th scope="col" className="p-4 text-center font-anton">Pista A</th>
<th scope="col" className="p-4 text-center font-anton">Pista B</th>
<th scope="col" className="p-4 text-center font-anton">Pista C</th>
<th scope="col" className="p-4 text-center font-anton">Puntaje Final</th>
<th scope="col" className="p-4 text-center font-anton">
#
</th>
<th scope="col" className="p-4 font-anton">
Equipo
</th>
<th scope="col" className="p-4 text-center font-anton">
Pista A
</th>
<th scope="col" className="p-4 text-center font-anton">
Pista B
</th>
<th scope="col" className="p-4 text-center font-anton">
Pista C
</th>
<th scope="col" className="p-4 text-center font-anton">
Puntaje Final
</th>
</tr>
</thead>
<tbody>
Expand All @@ -44,13 +56,21 @@ const ScoreboardTable = ({ year }: ScoreboardTableProps) => {
key={team.nombreEquipo}
className="border-b border-gray-700 transition-colors hover:bg-gray-800/50"
>
<td className="p-4 text-center font-bold text-gray-400">{index + 1}</td>
<td className="p-4 text-center font-bold text-gray-400">
{index + 1}
</td>
<td className="p-4 font-anton text-xl font-bold text-white">
{team.nombreEquipo}
</td>
<td className="p-4 text-center text-lg text-gray-300">{team.puntajePistaA}</td>
<td className="p-4 text-center text-lg text-gray-300">{team.puntajePistaB}</td>
<td className="p-4 text-center text-lg text-gray-300">{team.puntajePistaC}</td>
<td className="p-4 text-center text-lg text-gray-300">
{team.puntajePistaA}
</td>
<td className="p-4 text-center text-lg text-gray-300">
{team.puntajePistaB}
</td>
<td className="p-4 text-center text-lg text-gray-300">
{team.puntajePistaC}
</td>
<td className="p-4 text-center text-xl font-bold text-roboblue">
{team.puntajeFinal}
</td>
Expand Down
168 changes: 168 additions & 0 deletions src/lib/exportcsv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { PrismaClient } from "@prisma/client";
import fs from "fs/promises";
import path from "path";
const prisma = new PrismaClient();

const EDITION_NAME = "LARC 2025";
const OUTPUT_FILE = path.resolve("public/PastEditions.json");

type TeamScore = {
nombreEquipo: string;
puntajePistaA: number;
puntajePistaB: number;
puntajePistaC: number;
puntajeFinal: number;
};

function isTeamScoreArray(val: unknown): val is TeamScore[] {
return (
Array.isArray(val) &&
val.every(
(v) =>
v &&
typeof v === "object" &&
"nombreEquipo" in v &&
"puntajePistaA" in v &&
"puntajePistaB" in v &&
"puntajePistaC" in v &&
"puntajeFinal" in v,
)
);
}

function isRecordTeamScoreArray(
val: unknown,
): val is Record<string, TeamScore[]> {
if (!val || typeof val !== "object") return false;
return Object.values(val).every(isTeamScoreArray);
}

// Helper to aggregate latest entries per (teamId, roundId), returning per-team round points
function accumulateLatest<
T extends {
teamId: string;
roundId: string;
createdAt: Date;
points: number;
team: { name: string };
},
>(records: T[]) {
const latest: Record<string, Record<string, T>> = {};
for (const r of records) {
const teamMap = latest[r.teamId] ?? (latest[r.teamId] = {});
const existing = teamMap[r.roundId];
if (!existing || existing.createdAt < r.createdAt) teamMap[r.roundId] = r;
}
const perTeam: Record<string, { name: string; rounds: number[] }> = {};
for (const teamId of Object.keys(latest)) {
const rounds = Object.values(latest[teamId]!).map((r) => r.points);
perTeam[teamId] = {
name: Object.values(latest[teamId]!)[0]!.team.name,
rounds,
};
}
return perTeam;
}

function sumTopTwo(values: number[]): number {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good detail

if (!values || values.length === 0) return 0;
const sorted = [...values].sort((a, b) => b - a);
const first = sorted[0] ?? 0;
const second = sorted[1] ?? 0;
return first + second;
}

export async function exportUsersToCSV() {
try {
// Fetch challenge records with minimal fields.
const [aRecords, bRecords, cRecords] = await Promise.all([
prisma.challengeA.findMany({
select: {
teamId: true,
roundId: true,
createdAt: true,
points: true,
team: { select: { name: true } },
},
}),
prisma.challengeB.findMany({
select: {
teamId: true,
roundId: true,
createdAt: true,
points: true,
team: { select: { name: true } },
},
}),
prisma.challengeC.findMany({
select: {
teamId: true,
roundId: true,
createdAt: true,
points: true,
team: { select: { name: true } },
},
}),
]);

const aPerTeam = accumulateLatest(aRecords);
const bPerTeam = accumulateLatest(bRecords);
const cPerTeam = accumulateLatest(cRecords);

// Collect unique teamIds from all challenges
const teamIds = new Set<string>([
...Object.keys(aPerTeam),
...Object.keys(bPerTeam),
...Object.keys(cPerTeam),
]);

// const yearKey = new Date().getFullYear().toString();
const scores: TeamScore[] = [];
for (const teamId of teamIds) {
const name =
aPerTeam[teamId]?.name ??
bPerTeam[teamId]?.name ??
cPerTeam[teamId]?.name ??
"Unknown";
const puntajePistaA = sumTopTwo(aPerTeam[teamId]?.rounds ?? []);
const puntajePistaB = sumTopTwo(bPerTeam[teamId]?.rounds ?? []);
const puntajePistaC = sumTopTwo(cPerTeam[teamId]?.rounds ?? []);
const puntajeFinal = puntajePistaA + puntajePistaB + puntajePistaC;
scores.push({
nombreEquipo: name,
puntajePistaA,
puntajePistaB,
puntajePistaC,
puntajeFinal,
});
}

// Sort descending by final score
scores.sort((x, y) => y.puntajeFinal - x.puntajeFinal);

// Load existing JSON file (if exists) and merge
let existing: Record<string, TeamScore[]> = {};
try {
const raw = await fs.readFile(OUTPUT_FILE, "utf8");
const parsed: unknown = JSON.parse(raw);
if (isRecordTeamScoreArray(parsed)) existing = parsed;
} catch (e) {
// ignore; keep empty existing
}

existing[EDITION_NAME] = scores; // Replace / create current year entry

await fs.writeFile(OUTPUT_FILE, JSON.stringify(existing, null, 2), "utf8");
return scores;
} catch (error) {
console.error("Failed to export edition scores:", error);
throw error;
}
}

// Invoke immediately when this module is run (script usage)
try {
await exportUsersToCSV();
} finally {
await prisma.$disconnect();
}