diff --git a/ai/tasks/done/TASK-250-fix-historical-ranking-labels-table-uniformity-and-steam-icon.md b/ai/tasks/done/TASK-250-fix-historical-ranking-labels-table-uniformity-and-steam-icon.md new file mode 100644 index 0000000..73675b6 --- /dev/null +++ b/ai/tasks/done/TASK-250-fix-historical-ranking-labels-table-uniformity-and-steam-icon.md @@ -0,0 +1,113 @@ +# TASK-250 - Fix historical ranking labels, table uniformity and Steam icon + +## Summary + +This task fixes public labels that were still misleading in historical leaderboards, aligns the visible column order and wording between Stats and Ranking, and audits the Steam brand icon path used by external profiles. + +## Files Read First + +- `frontend/historico.html` +- `frontend/assets/js/historico.js` +- `frontend/ranking.html` +- `frontend/assets/js/ranking.js` +- `frontend/assets/js/stats.js` +- `frontend/assets/js/historico-partida.js` + +## Historical Leaderboards + +The weekly/monthly leaderboard table now adapts its ratio column to the active metric: + +- `kills` -> `Kills/partida` +- `deaths` -> `Muertes/partida` +- `support` -> `Soporte/partida` +- `matches_over_100_kills` -> ratio column hidden + +This removes the incorrect `Kills/partida` label from: + +- Top muertes +- Top soporte +- Partidas 100+ kills + +No aggregated real KPM was added in this task. + +## Stats / Ranking Uniformity + +Visible labels were normalized to Spanish or accepted abbreviations: + +- `Deaths` -> `Muertes` +- `Teamkills` -> `TK` +- `matches_considered` -> `Partidas` +- `kd_ratio` -> `KD` +- `kills_per_match` -> `Kills/partida` + +Shared player ranking column order now stays consistent: + +- `Pos.` +- `Jugador` +- `Partidas` +- `Kills` +- `Muertes` +- `TK` +- `KD` +- `Kills/partida` + +`ranking.html` keeps its extra `Valor activo` column before the shared block because that table is metric-driven. + +## Steam Icon Audit + +The code path in `historico-partida.js` is already correct: + +- `./assets/img/brands/steam.png` + +The local file also exists: + +- `frontend/assets/img/brands/steam.png` + +It is currently untracked in git: + +- `git status --short --untracked-files=all -- frontend/assets/img/brands` + - `?? frontend/assets/img/brands/steam.png` + +Production currently returns: + +- `https://comunidadhll.devzamode.es/assets/img/brands/steam.png` -> `404` + +So the remaining issue is deployment scope, not the frontend path. + +The defensive `onerror` behavior remains in place. + +Because this exact local asset exists and is the only Steam brand asset detected in: + +- `frontend/assets/img/brands/` +- `frontend/assets/img/` +- `frontend/assets/` + +it is the one asset that should be included with the TASK-250 frontend commit if the goal is to make the icon visible in production. + +## Validation + +Executed: + +- `node --check frontend/assets/js/historico.js` +- `node --check frontend/assets/js/stats.js` +- `node --check frontend/assets/js/ranking.js` + +Static validation performed: + +- no visible `Deaths` label remains in Stats / Ranking tables or selects +- no visible `Teamkills` label remains in Stats / Ranking tables or selects +- `Kills/partida` no longer appears for Top muertes, Top soporte or Partidas 100+ kills +- Top muertes now uses `Muertes/partida` +- Stats and Ranking keep the same relative order for shared columns + +Operational validation performed: + +- production Steam icon URL returns `404` +- local `frontend/assets/img/brands/steam.png` exists but is not tracked +- no alternate local Steam asset name or extension was detected + +## Notes + +- No backend changes were required. +- No map, weapon or clan asset was modified. +- To fully fix the Steam icon in production, the existing local `steam.png` must be included in a later isolated commit. diff --git a/frontend/assets/img/brands/steam.png b/frontend/assets/img/brands/steam.png new file mode 100644 index 0000000..d4baa06 Binary files /dev/null and b/frontend/assets/img/brands/steam.png differ diff --git a/frontend/assets/js/historico.js b/frontend/assets/js/historico.js index 2821349..6a55f07 100644 --- a/frontend/assets/js/historico.js +++ b/frontend/assets/js/historico.js @@ -45,24 +45,32 @@ const LEADERBOARD_METRICS = Object.freeze([ key: "kills", title: "Top kills", valueHeading: "Kills", + ratioHeading: "Kills/partida", + ratioMode: "kills", emptyMessage: "Sin datos historicos suficientes para mostrar este ranking de kills.", }, { key: "deaths", title: "Top muertes", valueHeading: "Muertes", + ratioHeading: "Muertes/partida", + ratioMode: "deaths", emptyMessage: "Sin datos historicos suficientes para mostrar este ranking de muertes.", }, { key: "matches_over_100_kills", title: "Top partidas con 100+ kills", valueHeading: "Partidas 100+", + ratioHeading: null, + ratioMode: null, emptyMessage: "Ningun jugador ha registrado partidas de 100+ kills en esta ventana.", }, { key: "support", title: "Top puntos de soporte", valueHeading: "Soporte", + ratioHeading: "Soporte/partida", + ratioMode: "support", emptyMessage: "Sin datos historicos suficientes para mostrar este ranking de soporte.", }, ]); @@ -94,6 +102,7 @@ document.addEventListener("DOMContentLoaded", () => { const weeklyTableNode = document.getElementById("weekly-leaderboard-table"); const weeklyBodyNode = document.getElementById("weekly-leaderboard-body"); const weeklyValueHeadingNode = document.getElementById("weekly-leaderboard-value-heading"); + const weeklyRatioHeadingNode = document.getElementById("weekly-leaderboard-ratio-heading"); const weeklyWindowNoteNode = document.getElementById("weekly-window-note"); const weeklySnapshotMetaNode = document.getElementById( "weekly-leaderboard-snapshot-meta", @@ -210,6 +219,7 @@ document.addEventListener("DOMContentLoaded", () => { weeklyBodyNode, weeklyTitleNode, weeklyValueHeadingNode, + weeklyRatioHeadingNode, weeklyWindowNoteNode, weeklySnapshotMetaNode, activeMetricConfig, @@ -547,6 +557,7 @@ function hydrateWeeklyLeaderboard( bodyNode, titleNode, valueHeadingNode, + ratioHeadingNode, noteNode, snapshotMetaNode, metricConfig, @@ -555,6 +566,7 @@ function hydrateWeeklyLeaderboard( const targetServerSlug = result.value?.data?.server_slug || activeServerSlug; const resolvedTimeframeKey = result.value?.data?.timeframe || timeframeKey; valueHeadingNode.textContent = metricConfig.valueHeading; + syncLeaderboardRatioColumn(tableNode, ratioHeadingNode, bodyNode, metricConfig); if (result.status !== "fulfilled") { titleNode.textContent = buildLeaderboardTitle( metricConfig, @@ -614,13 +626,11 @@ function hydrateWeeklyLeaderboard( bodyNode.innerHTML = items .map( (item) => { - const kills = resolveHistoricalKills(item, metricConfig); const matches = Number(item.matches_considered); - const killsPerMatch = formatHistoricalKillsPerMatch( - item?.kills_per_match, - kills, - matches, - ); + const ratioValue = formatHistoricalRatio(item, metricConfig, matches); + const ratioCell = metricConfig.ratioMode + ? `${escapeHtml(ratioValue)}` + : ""; return ` @@ -628,13 +638,14 @@ function hydrateWeeklyLeaderboard( ${escapeHtml(item.player?.name || "Jugador no identificado")} ${escapeHtml(formatNumber(item.metric_value))} ${escapeHtml(formatNumber(item.matches_considered))} - ${escapeHtml(killsPerMatch)} + ${ratioCell} `; }, ) .join(""); stateNode.hidden = true; + syncLeaderboardRatioColumn(tableNode, ratioHeadingNode, bodyNode, metricConfig); tableNode.hidden = false; } @@ -1724,13 +1735,43 @@ function resolveHistoricalKills(item, metricConfig) { return Number.NaN; } -function formatHistoricalKillsPerMatch(rawKillsPerMatch, rawKills, rawMatches) { - const directValue = Number(rawKillsPerMatch); +function syncLeaderboardRatioColumn(tableNode, ratioHeadingNode, bodyNode, metricConfig) { + if (!tableNode || !ratioHeadingNode || !bodyNode) { + return; + } + const showRatio = Boolean(metricConfig?.ratioMode); + ratioHeadingNode.hidden = !showRatio; + ratioHeadingNode.textContent = metricConfig?.ratioHeading || ""; + const ratioColumnIndex = ratioHeadingNode.cellIndex; + bodyNode.querySelectorAll("tr").forEach((row) => { + const ratioCell = row.children[ratioColumnIndex]; + if (ratioCell) { + ratioCell.hidden = !showRatio; + } + }); +} + +function formatHistoricalRatio(item, metricConfig, matches) { + if (!metricConfig?.ratioMode) { + return ""; + } + if (metricConfig.ratioMode === "kills") { + const kills = resolveHistoricalKills(item, metricConfig); + return formatHistoricalPerMatch(item?.kills_per_match, kills, matches); + } + if (metricConfig.ratioMode === "deaths" || metricConfig.ratioMode === "support") { + return formatHistoricalPerMatch(null, Number(item?.metric_value), matches); + } + return ""; +} + +function formatHistoricalPerMatch(rawDirectValue, rawTotalValue, rawMatches) { + const directValue = Number(rawDirectValue); if (Number.isFinite(directValue)) { return formatDecimal(directValue, 2); } - const kills = Number(rawKills); + const kills = Number(rawTotalValue); const matches = Number(rawMatches); if (!Number.isFinite(kills) || !Number.isFinite(matches) || matches <= 0) { return "-"; diff --git a/frontend/assets/js/ranking.js b/frontend/assets/js/ranking.js index 22bb677..a99770f 100644 --- a/frontend/assets/js/ranking.js +++ b/frontend/assets/js/ranking.js @@ -271,7 +271,7 @@ if (statusCode === 400 && normalizedMessage.includes("metric")) { setRankingState("warning", "La m\u00e9trica solicitada no est\u00e1 soportada."); renderEmptyState( - "Usa kills, deaths, teamkills, partidas jugadas, K/D o Kills/partida.", + "Usa Kills, Muertes, TK, Partidas, KD o Kills/partida.", ); return; } @@ -397,10 +397,10 @@ ${formatMetricValue(item.metric_value, metric)} + ${safeInt(item.matches_considered, 0)} ${safeInt(item.kills, 0)} ${safeInt(item.deaths, 0)} ${safeInt(item.teamkills, 0)} - ${safeInt(item.matches_considered, 0)} ${safeDecimal(item.kd_ratio, 2, "0.00")} ${hideKppColumn ? "" : `${killsPerMatch}`} @@ -454,10 +454,10 @@ function labelForMetric(metric) { const labels = { kills: "Kills", - deaths: "Deaths", - teamkills: "Teamkills", - matches_considered: "Partidas jugadas", - kd_ratio: "K/D", + deaths: "Muertes", + teamkills: "TK", + matches_considered: "Partidas", + kd_ratio: "KD", kills_per_match: "Kills/partida", }; return labels[metric] || "Kills"; diff --git a/frontend/assets/js/stats.js b/frontend/assets/js/stats.js index 1fc2feb..44b51a6 100644 --- a/frontend/assets/js/stats.js +++ b/frontend/assets/js/stats.js @@ -359,8 +359,8 @@ .map((item) => { const rank = safeInt(item.ranking_position, 0); const playerName = escapeHtml(String(item.player_name || "Jugador sin nombre")); - const kills = safeInt(firstFiniteValue(item.kills, item.metric_value), 0); const matches = safeInt(item.matches_considered, 0); + const kills = safeInt(firstFiniteValue(item.kills, item.metric_value), 0); const killsPerMatch = formatKillsPerMatch( item.kills_per_match, kills, @@ -378,12 +378,12 @@ ${playerName} - ${kills} ${matches} - ${killsPerMatch} + ${kills} ${deaths} ${teamkills} ${kd} + ${killsPerMatch} `; }) @@ -396,12 +396,12 @@ Posici\u00f3n Jugador - Kills Partidas - Kills/partida + Kills Muertes - Teamkills - K/D + TK + KD + Kills/partida ${rowsMarkup} @@ -574,8 +574,8 @@

Ventana semanal

Kills: ${safeInt(weeklyData?.kills, 0)}

-

Deaths: ${safeInt(weeklyData?.deaths, 0)}

-

Teamkills: ${safeInt(weeklyData?.teamkills, 0)}

+

Muertes: ${safeInt(weeklyData?.deaths, 0)}

+

TK: ${safeInt(weeklyData?.teamkills, 0)}

`; @@ -590,8 +590,8 @@

Ventana mensual

Kills: ${safeInt(monthlyData?.kills, 0)}

-

Deaths: ${safeInt(monthlyData?.deaths, 0)}

-

Teamkills: ${safeInt(monthlyData?.teamkills, 0)}

+

Muertes: ${safeInt(monthlyData?.deaths, 0)}

+

TK: ${safeInt(monthlyData?.teamkills, 0)}

`; @@ -697,16 +697,16 @@ ${kills}
- Deaths + Muertes ${deaths}
- K/D + KD ${kdRatio}

- Kills por partida: ${killsPerMatch} + Kills/partida: ${killsPerMatch}

${escapeHtml(rankingState.detail)}

@@ -739,11 +739,11 @@ ${safeDecimal(monthlyKillsPerMatch, 2, "0.00")}
- K/D semanal + KD semanal ${safeDecimal(weeklyKd, 2, "0.00")}
- K/D mensual + KD mensual ${safeDecimal(monthlyKd, 2, "0.00")}
@@ -775,7 +775,7 @@ } const rank = safeInt(ranking.ranking_position, 0); - const metric = escapeHtml(String(ranking.metric || annualMetric)); + const metric = escapeHtml(labelForStatsMetric(ranking.metric || annualMetric)); return `

Posici\u00f3n: ${rank}

@@ -863,6 +863,18 @@ return `Ventana: ${windowStart} - ${windowEnd}`; } + function labelForStatsMetric(metric) { + const labels = { + kills: "Kills", + deaths: "Muertes", + teamkills: "TK", + matches_considered: "Partidas", + kd_ratio: "KD", + kills_per_match: "Kills/partida", + }; + return labels[String(metric || "").trim()] || "Kills"; + } + function safeInt(value, fallback = 0) { const parsed = Number(value); if (!Number.isFinite(parsed)) { diff --git a/frontend/historico.html b/frontend/historico.html index a3f052b..d0a4ab6 100644 --- a/frontend/historico.html +++ b/frontend/historico.html @@ -199,7 +199,7 @@ Jugador Kills Partidas - Kills/partida + Kills/partida diff --git a/frontend/ranking.html b/frontend/ranking.html index af399eb..42a1874 100644 --- a/frontend/ranking.html +++ b/frontend/ranking.html @@ -87,11 +87,11 @@ @@ -120,11 +120,11 @@ Pos. Jugador Valor activo - Kills - Deaths - Teamkills Partidas - K/D + Kills + Muertes + TK + KD Kills/partida