Fix historical match detail backend URL and remove false KPM

This commit is contained in:
devRaGonSa
2026-06-10 16:27:35 +02:00
parent a5e214aa38
commit a3c15e42e0
3 changed files with 181 additions and 32 deletions

View File

@@ -0,0 +1,154 @@
---
id: TASK-222
title: Fix historical match detail backend URL and false KPM
status: done
type: frontend
team: Frontend Senior
supporting_teams:
- Backend Senior
roadmap_item: foundation
priority: high
---
# TASK-222 - Fix historical match detail backend URL and false KPM
## Goal
Corregir `frontend/historico-partida.html` para que el detalle de una partida historica no intente conectar con `127.0.0.1:8000` en produccion y eliminar el KPM falso calculado con la duracion total de la partida.
## Context
La pagina de detalle historico lee `/api/historical/matches/detail?server=<server>&match=<match>`. En produccion, el frontend no debe usar `http://127.0.0.1:8000` como backend por defecto porque eso apunta al PC del usuario. Ademas, la tabla de jugadores mostraba KPM calculado como `kills / duration_seconds` de la partida completa, lo que no representa tiempo real jugado por jugador.
Preserve the current product identity: Spanish-speaking HLL Vietnam community, military/Vietnam/tactical/sober visual direction and controlled repository evolution.
## Steps
1. Leer primero los archivos listados y confirmar el fallback de backend y el uso de KPM.
2. Corregir la resolucion de URL de backend en `historico-partida.js` sin ampliar el alcance a otros modulos.
3. Confirmar que el detalle sigue llamando a `/api/historical/matches/detail?server=<server>&match=<match>`.
4. Eliminar de `historico-partida` la exposicion de KPM si no existe `player_active_seconds` real.
5. Validar sintaxis y revisar el alcance del diff.
## Files to Read First
- `ai/architecture-index.md`
- `ai/repo-context.md`
- `ai/orchestrator/frontend-senior.md`
- `frontend/historico-partida.html`
- `frontend/assets/js/historico-partida.js`
- `docs/REAL_KPM_PLAYER_ACTIVE_SECONDS_DESIGN.md`
Tambien se revisaron:
- `frontend/assets/js/historico.js`
- `frontend/assets/js/historico-recent-live.js`
- `frontend/assets/js/ranking.js`
- `frontend/assets/js/config.js`
- `frontend/assets/css/historico.css`
- `ai/tasks/done/TASK-216-clean-ranking-ui-and-fix-kpm-metric.md`
## Expected Files to Modify
- `frontend/historico-partida.html`
- `frontend/assets/js/historico-partida.js`
- `ai/tasks/done/TASK-222-fix-historical-match-detail-backend-url-and-false-kpm.md`
## Constraints
- Keep the change minimal.
- Preserve HLL Vietnam project identity.
- Do not introduce unnecessary frameworks or dependencies.
- Do not implement backend functionality unless frontend diagnosis proves it is necessary.
- No ejecutar `ai-platform run`.
- No hacer push.
- No tocar assets de armas.
- No tocar SVGs.
- No modificar imagenes fisicas.
- No tocar `ai/system-metrics.md`.
- No reactivar Elo/MMR.
- No reintroducir Comunidad Hispana #03.
- No incluir cambios previos no relacionados.
- Mantener compatibilidad con desarrollo local si existe `data-backend-base-url`.
- No publicar KPM sin `player_active_seconds` real.
## Validation
Before completing the task ensure:
- `node --check frontend/assets/js/historico-partida.js`
- Si se tocan otros JS, `node --check` tambien.
- Validar por navegador o inspeccion que el detalle no intenta `127.0.0.1`.
- Confirmar que la llamada de detalle usa `/api/historical/matches/detail?server=<server>&match=<match>`.
- Validar que no queda texto KPM falso en `historico-partida`.
- `git diff --name-only` matches the expected scope, except pre-existing unrelated changes.
- Confirmar que no se tocaron backend, assets de armas, SVGs, imagenes fisicas ni `ai/system-metrics.md`.
## Outcome
Archivos modificados por esta task:
- `frontend/historico-partida.html`
- `frontend/assets/js/historico-partida.js`
- `ai/tasks/done/TASK-222-fix-historical-match-detail-backend-url-and-false-kpm.md`
Causa exacta corregida:
- `frontend/historico-partida.html` tenia `data-backend-base-url="http://127.0.0.1:8000"`.
- `frontend/assets/js/historico-partida.js` usaba `document.body.dataset.backendBaseUrl || "http://127.0.0.1:8000"`.
- En produccion, esa combinacion podia construir llamadas contra el localhost del usuario. La pagina de detalle ya no define localhost en el HTML y el JS resuelve primero `data-backend-base-url` si existe, luego `window.HLL_FRONTEND_CONFIG.backendBaseUrl`, y si no hay configuracion usa URL relativa para `/api`.
Endpoint de detalle:
- Se confirmo que `historico-partida.js` mantiene la llamada a `/api/historical/matches/detail?server=<server>&match=<match>`.
- El backend local no estaba escuchando en `http://127.0.0.1:8000`, por lo que no se pudo obtener una respuesta real del endpoint local.
- No se toco backend porque el fallo confirmado en esta task era frontend y no hubo evidencia de error backend.
Decision sobre KPM falso:
- Se elimino KPM del detalle de partida hasta que exista `player_active_seconds` real.
- Se quitaron la opcion de orden `KPM`, la cabecera de columna `KPM`, la celda por jugador, el chip del panel ampliado y las funciones `formatKpm`/`getKpmValue`.
- `duration_seconds` permanece solo para mostrar la duracion de la partida en el marcador, no para calcular rendimiento por jugador.
- No se toco la logica de ranking `Kills/partida` corregida en `TASK-216`.
Otros fallbacks localhost detectados:
- Siguen existiendo paginas publicas con `data-backend-base-url="http://127.0.0.1:8000"` o JS que lee ese dataset en `stats`, `historico`, `ranking`, `partida-actual`, `index` y `historico-recent-live`.
- No se corrigieron en esta task para no ampliar alcance. `frontend/assets/js/config.js` ya tiene una capa de reescritura defensiva, pero esos puntos quedan como deuda de endurecimiento fuera de esta causa directa.
Validaciones ejecutadas:
- `node --check frontend/assets/js/historico-partida.js`
- `rg -n "KPM|getKpmValue|formatKpm|kpm|kills_per_minute|player_active_seconds" frontend/historico-partida.html frontend/assets/js/historico-partida.js`
- `rg -n "api/historical/matches/detail|server=|match=" frontend/assets/js/historico-partida.js`
- `Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:8000/api/historical/matches/detail?server=all-servers&match=__validation__' -TimeoutSec 5`
- `git diff --name-only`
- `git status --short -- ai/tasks/pending ai/tasks/in-progress ai/tasks/done frontend/historico-partida.html frontend/assets/js/historico-partida.js backend frontend/assets/img/weapons ai/system-metrics.md`
Resultado de validaciones:
- `node --check` paso sin errores.
- No queda texto ni funcion KPM en `historico-partida.html` ni `historico-partida.js`.
- La llamada de detalle conserva el endpoint esperado.
- La prueba HTTP local fallo con `No es posible conectar con el servidor remoto`; se documenta como backend local no disponible, no como fallo del endpoint.
- El navegador integrado no estaba disponible en esta sesion (`iab` no expuesto), asi que la validacion de red en navegador no pudo ejecutarse. La URL construida queda relativa cuando no hay backend configurado, por lo que ya no apunta a `127.0.0.1`.
Confirmacion de exclusiones:
- No se ejecuto `ai-platform run`.
- No se hizo push.
- No se hizo commit.
- No se toco backend.
- No se tocaron assets de armas.
- No se tocaron SVGs.
- No se modificaron imagenes fisicas.
- No se toco `ai/system-metrics.md`.
- No se reactivo Elo/MMR.
- No se reintrodujo Comunidad Hispana #03.
- No se incluyeron cambios previos no relacionados.
## Change Budget
- Prefer fewer than 5 modified files.
- Prefer changes under 200 lines when feasible.
- Split the work into follow-up tasks if limits are exceeded.

View File

@@ -1,6 +1,5 @@
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const backendBaseUrl = const backendBaseUrl = resolveBackendBaseUrl();
document.body.dataset.backendBaseUrl || "http://127.0.0.1:8000";
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const serverSlug = params.get("server") || ""; const serverSlug = params.get("server") || "";
const matchId = params.get("match") || ""; const matchId = params.get("match") || "";
@@ -80,12 +79,33 @@ async function loadMatchDetail({ backendBaseUrl, serverSlug, matchId, nodes }) {
renderMatchDetail(item, nodes); renderMatchDetail(item, nodes);
} catch (error) { } catch (error) {
nodes.title.textContent = "Detalle no disponible"; nodes.title.textContent = "Detalle no disponible";
nodes.summary.textContent = "No se pudo conectar con el backend local."; nodes.summary.textContent = "No se pudo cargar el detalle desde el backend.";
nodes.note.textContent = ""; nodes.note.textContent = "";
setState(nodes.state, "Error al cargar el detalle de la partida.", true); setState(nodes.state, "Error al cargar el detalle de la partida.", true);
} }
} }
function resolveBackendBaseUrl() {
const bodyBackendBaseUrl = document.body.dataset.backendBaseUrl;
if (typeof bodyBackendBaseUrl === "string" && bodyBackendBaseUrl.trim()) {
return trimTrailingSlash(bodyBackendBaseUrl);
}
const configuredBackendBaseUrl = window.HLL_FRONTEND_CONFIG?.backendBaseUrl;
if (
typeof configuredBackendBaseUrl === "string" &&
configuredBackendBaseUrl.trim()
) {
return trimTrailingSlash(configuredBackendBaseUrl);
}
return "";
}
function trimTrailingSlash(value) {
return String(value || "").trim().replace(/\/+$/, "");
}
function renderMatchDetail(item, nodes) { function renderMatchDetail(item, nodes) {
const mapName = item.map?.pretty_name || item.map?.name || "Mapa no disponible"; const mapName = item.map?.pretty_name || item.map?.name || "Mapa no disponible";
const serverName = item.server?.name || item.server?.slug || "Servidor no disponible"; const serverName = item.server?.name || item.server?.slug || "Servidor no disponible";
@@ -354,10 +374,7 @@ function comparePlayerSortValue(a, b, item, sort) {
if (sort === "kd") { if (sort === "kd") {
return compareNumericStat(getKdRatioValue(a.player), getKdRatioValue(b.player)); return compareNumericStat(getKdRatioValue(a.player), getKdRatioValue(b.player));
} }
return compareNumericStat( return compareNumericStat(a.player.kills, b.player.kills);
getKpmValue(a.player.kills, item.duration_seconds),
getKpmValue(b.player.kills, item.duration_seconds),
);
} }
function compareInactivePriority(a, b) { function compareInactivePriority(a, b) {
@@ -383,7 +400,6 @@ function renderPlayerRows(player, item, index, inactive = false) {
const rowId = `match-player-row-${index}`; const rowId = `match-player-row-${index}`;
const panelId = `match-player-panel-${index}`; const panelId = `match-player-panel-${index}`;
const playerName = getPlayerName(player); const playerName = getPlayerName(player);
const kpm = formatKpm(player.kills, item.duration_seconds);
return ` return `
<tr <tr
class="historical-player-row historical-player-row--${team.key} ${inactive ? "is-inactive" : ""}" class="historical-player-row historical-player-row--${team.key} ${inactive ? "is-inactive" : ""}"
@@ -410,15 +426,14 @@ function renderPlayerRows(player, item, index, inactive = false) {
<td>${escapeHtml(formatOptionalNumber(player.deaths))}</td> <td>${escapeHtml(formatOptionalNumber(player.deaths))}</td>
<td>${escapeHtml(formatOptionalNumber(player.teamkills))}</td> <td>${escapeHtml(formatOptionalNumber(player.teamkills))}</td>
<td>${escapeHtml(formatKdRatio(player))}</td> <td>${escapeHtml(formatKdRatio(player))}</td>
<td>${escapeHtml(kpm)}</td>
</tr> </tr>
<tr <tr
class="historical-player-detail-row" class="historical-player-detail-row"
id="${escapeHtml(panelId)}" id="${escapeHtml(panelId)}"
aria-labelledby="${escapeHtml(rowId)}" aria-labelledby="${escapeHtml(rowId)}"
> >
<td colspan="7"> <td colspan="6">
${renderPlayerStatsPanel(player, item, { team, playerName, kpm })} ${renderPlayerStatsPanel(player, item, { team, playerName })}
</td> </td>
</tr> </tr>
`; `;
@@ -510,7 +525,6 @@ function renderPlayerStatsPanel(player, item, context) {
${renderPlayerStatChip("Muertes", formatOptionalNumber(player.deaths))} ${renderPlayerStatChip("Muertes", formatOptionalNumber(player.deaths))}
${renderPlayerStatChip("TK", formatOptionalNumber(player.teamkills))} ${renderPlayerStatChip("TK", formatOptionalNumber(player.teamkills))}
${renderPlayerStatChip("KD", formatKdRatio(player))} ${renderPlayerStatChip("KD", formatKdRatio(player))}
${renderPlayerStatChip("KPM", context.kpm)}
</div> </div>
</div> </div>
${renderExternalProfilesSection(player)} ${renderExternalProfilesSection(player)}
@@ -875,23 +889,6 @@ function getKdRatioValue(player) {
return deaths > 0 ? kills / deaths : kills; return deaths > 0 ? kills / deaths : kills;
} }
function formatKpm(kills, durationSeconds) {
return formatDecimal(getKpmValue(kills, durationSeconds), 2);
}
function getKpmValue(kills, durationSeconds) {
const parsedKills = Number(kills);
const parsedDurationSeconds = Number(durationSeconds);
if (
!Number.isFinite(parsedKills) ||
!Number.isFinite(parsedDurationSeconds) ||
parsedDurationSeconds <= 0
) {
return 0;
}
return parsedKills / (parsedDurationSeconds / 60);
}
function formatNamedCounts(items) { function formatNamedCounts(items) {
if (!Array.isArray(items) || items.length === 0) { if (!Array.isArray(items) || items.length === 0) {
return "No disponible"; return "No disponible";

View File

@@ -12,7 +12,7 @@
<link rel="stylesheet" href="./assets/css/historico.css" /> <link rel="stylesheet" href="./assets/css/historico.css" />
<link rel="stylesheet" href="./assets/css/historico-scoreboard-detail.css" /> <link rel="stylesheet" href="./assets/css/historico-scoreboard-detail.css" />
</head> </head>
<body data-backend-base-url="http://127.0.0.1:8000"> <body>
<div class="page-shell historical-shell"> <div class="page-shell historical-shell">
<header class="hero historical-hero"> <header class="hero historical-hero">
<div class="hero__overlay"></div> <div class="hero__overlay"></div>
@@ -127,7 +127,6 @@
<option value="deaths">Muertes</option> <option value="deaths">Muertes</option>
<option value="teamkills">TK</option> <option value="teamkills">TK</option>
<option value="kd">KD</option> <option value="kd">KD</option>
<option value="kpm">KPM</option>
<option value="name">Jugador</option> <option value="name">Jugador</option>
</select> </select>
</label> </label>
@@ -149,7 +148,6 @@
<th>Muertes</th> <th>Muertes</th>
<th>TK</th> <th>TK</th>
<th>KD</th> <th>KD</th>
<th>KPM</th>
</tr> </tr>
</thead> </thead>
<tbody id="match-detail-players-body"></tbody> <tbody id="match-detail-players-body"></tbody>