feat: version 2.17.9 -- Panel: medidor y detalle en "Actividades vencidas"

Base = actividades no eliminadas, no canceladas y de proyectos no cancelados;
el conteo de vencidas usa el mismo criterio (antes incluia eliminadas).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-21 08:03:22 -05:00
parent 22b287fcd8
commit dda6aa3369
8 changed files with 45 additions and 11 deletions
+16 -5
View File
@@ -45,10 +45,19 @@ export async function summary(memberUserId) {
WHERE a.deleted_at IS NULL AND p.deleted_at IS NULL AND ps.name NOT LIKE 'cancel%' ${memberClause}`,
memberParams
);
// Vencidas y su base (medidor): sin eliminadas, sin actividades canceladas ni de proyectos cancelados.
const [[overdueActivities]] = await pool.query(
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id
JOIN projects p ON p.id = a.project_id
WHERE st.is_closed = 0 AND p.deleted_at IS NULL AND a.due_date IS NOT NULL AND a.due_date < CURDATE() ${memberClause}`,
JOIN projects p ON p.id = a.project_id JOIN project_statuses ps ON ps.id = p.status_id
WHERE st.is_closed = 0 AND a.deleted_at IS NULL AND p.deleted_at IS NULL AND ps.name NOT LIKE 'cancel%'
AND a.due_date IS NOT NULL AND a.due_date < CURDATE() ${memberClause}`,
memberParams
);
const [[nonCancelledActivitiesStrict]] = await pool.query(
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id
JOIN projects p ON p.id = a.project_id JOIN project_statuses ps ON ps.id = p.status_id
WHERE a.deleted_at IS NULL AND p.deleted_at IS NULL AND ps.name NOT LIKE 'cancel%'
AND st.name NOT LIKE 'cancel%' ${memberClause}`,
memberParams
);
const [[todayActivities]] = await pool.query(
@@ -70,15 +79,17 @@ export async function summary(memberUserId) {
dueSoonProjects: dueSoonProjects.c,
pendingActivities: pendingActivities.c,
nonCancelledActivities: nonCancelledActivities.c,
nonCancelledActivitiesStrict: nonCancelledActivitiesStrict.c,
overdueActivities: overdueActivities.c,
todayActivities: todayActivities.c,
averageProgress: avgProgress.avg_pct === null ? 0 : Math.round(Number(avgProgress.avg_pct) * 100) / 100,
};
}
// Mismo criterio que el KPI pendingActivities de summary().
export async function pendingActivitiesList(memberUserId) {
// Mismo criterio que los KPI pendingActivities (overdueOnly=false) / overdueActivities (true).
export async function pendingActivitiesList(memberUserId, overdueOnly = false) {
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
const overdueClause = overdueOnly ? 'AND a.due_date IS NOT NULL AND a.due_date < CURDATE()' : '';
const [rows] = await pool.query(
`SELECT a.id, a.code, a.name, a.due_date, a.progress_pct, p.id AS project_id, p.code AS project_code,
p.name AS project_name, u.name AS responsible_name
@@ -86,7 +97,7 @@ export async function pendingActivitiesList(memberUserId) {
JOIN projects p ON p.id = a.project_id JOIN project_statuses ps ON ps.id = p.status_id
LEFT JOIN users u ON u.id = a.responsible_user_id
WHERE st.is_closed = 0 AND a.deleted_at IS NULL AND p.deleted_at IS NULL
AND ps.name NOT LIKE 'cancel%' ${memberClause}
AND ps.name NOT LIKE 'cancel%' ${overdueClause} ${memberClause}
ORDER BY (a.due_date IS NULL), a.due_date, p.code, a.code`,
memberUserId ? [memberUserId] : []
);