diff --git a/CHANGELOG.md b/CHANGELOG.md
index db5253d..b9b5ce7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog - project.linkedcorp.net
+## [2.17.6] - 2026-10-21
+### Changed
+- Panel → "Proyectos atrasados": el centro del medidor muestra el porcentaje (atrasados sobre
+ activos) y debajo "N de M", para que se entienda de cuántos.
+
## [2.17.5] - 2026-10-20
### Added
- Panel: la tarjeta "Proyectos atrasados" muestra un medidor (atrasados sobre activos) con el
diff --git a/VERSION b/VERSION
index 00db226..728789a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.17.5
+2.17.6
diff --git a/backend/package.json b/backend/package.json
index 704b910..4e8becc 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "project-linkedcorp-backend",
- "version": "2.17.5",
+ "version": "2.17.6",
"private": true,
"type": "module",
"main": "src/server.js",
diff --git a/frontend/package.json b/frontend/package.json
index b92ce3e..9482ea0 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "project-linkedcorp-frontend",
"private": true,
- "version": "2.17.5",
+ "version": "2.17.6",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/components/KpiCard.jsx b/frontend/src/components/KpiCard.jsx
index 2d2eab6..59fa20d 100644
--- a/frontend/src/components/KpiCard.jsx
+++ b/frontend/src/components/KpiCard.jsx
@@ -1,12 +1,13 @@
// Medidor semicircular (tipo odometro): arco de fondo + arco relleno segun ratio (0..1), con el
// valor del KPI al centro. Sin libreria -- un SVG con stroke-dasharray sobre el semicirculo.
-function Gauge({ ratio, color, value }) {
+function Gauge({ ratio, color, value, caption }) {
const r = 40;
const length = Math.PI * r;
const clamped = Math.max(0, Math.min(1, ratio || 0));
const arc = `M ${50 - r} 50 A ${r} ${r} 0 0 1 ${50 + r} 50`;
return (
+
+ {caption &&
{caption}
}
);
}
@@ -30,7 +33,7 @@ export function KpiCard({ label, value, gauge, onClick, selected }) {
tabIndex={clickable ? 0 : undefined}
onKeyDown={clickable ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(); } } : undefined}
>
- {gauge ? : {value}
}
+ {gauge ? : {value}
}
{label}
);
diff --git a/frontend/src/i18n/messages.js b/frontend/src/i18n/messages.js
index a515854..d8e2752 100644
--- a/frontend/src/i18n/messages.js
+++ b/frontend/src/i18n/messages.js
@@ -156,6 +156,7 @@ export const messages = {
dashboard_average_progress: 'Avance promedio',
dashboard_my_activities: 'Mis actividades pendientes',
dashboard_my_overdue_projects: 'Mis proyectos atrasados',
+ common_of: 'de',
dashboard_back_to_my_activities: 'Ver mis actividades',
dashboard_projects_by_status: 'Proyectos por estado',
@@ -643,6 +644,7 @@ export const messages = {
dashboard_average_progress: 'Average progress',
dashboard_my_activities: 'My pending activities',
dashboard_my_overdue_projects: 'My overdue projects',
+ common_of: 'of',
dashboard_back_to_my_activities: 'Show my activities',
dashboard_projects_by_status: 'Projects by status',
diff --git a/frontend/src/pages/Dashboard/DashboardPage.jsx b/frontend/src/pages/Dashboard/DashboardPage.jsx
index 316d0b3..792d353 100644
--- a/frontend/src/pages/Dashboard/DashboardPage.jsx
+++ b/frontend/src/pages/Dashboard/DashboardPage.jsx
@@ -17,6 +17,8 @@ export default function DashboardPage() {
if (!data) return {t('common_loading')}
;
const { kpis, myActivities, projectsByStatus, overdueProjects } = data;
const activeTotal = Number(kpis.activeProjects) || 0;
+ const overdueRatio = activeTotal ? Number(kpis.overdueProjects) / activeTotal : 0;
+ const overduePct = Math.round(overdueRatio * 100);
return (
@@ -26,8 +28,8 @@ export default function DashboardPage() {
toggleDetail('overdue')}
selected={detail === 'overdue'}
/>
diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css
index c64c6d2..e7e38f7 100644
--- a/frontend/src/styles/app.css
+++ b/frontend/src/styles/app.css
@@ -244,9 +244,11 @@ table.list-table { width: 100%; border-collapse: collapse; font-size: .8rem; }
.kpi-card-clickable { cursor: pointer; transition: border-color .15s ease; }
.kpi-card-clickable:hover { border-color: rgb(var(--accent) / .5); }
.kpi-card.selected { border-color: rgb(var(--accent)); box-shadow: 0 0 0 1px rgb(var(--accent)); }
-.kpi-gauge { position: relative; width: 120px; max-width: 100%; }
+.kpi-gauge { width: 120px; max-width: 100%; }
+.kpi-gauge-dial { position: relative; }
.kpi-gauge svg { display: block; width: 100%; }
.kpi-gauge-value { position: absolute; left: 0; right: 0; bottom: 0; text-align: center; font-size: 1.4rem; font-weight: 700; line-height: 1; }
+.kpi-gauge-caption { text-align: center; font-size: .68rem; color: rgb(var(--muted)); margin-top: 2px; }
.panel-card { background: rgb(var(--surface)); border: 1px solid rgb(var(--line)); border-radius: var(--r-lg); padding: 16px; margin-bottom: 16px; }
.panel-card h3 { margin: 0 0 12px; font-size: .95rem; }
.company-logo-preview {