chore: estructura inicial del MVP

Backend (Express + MariaDB) y frontend (React + Vite, identidad Fluent/Windows) con el
nucleo funcional: seguridad, organizacion, proyectos, actividades, subtareas, dependencias,
dashboard, calendario, gantt, comentarios, adjuntos, notificaciones in-app y catalogos.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-09-03 08:37:58 -05:00
commit 1d22adc42e
135 changed files with 10913 additions and 0 deletions
@@ -0,0 +1,72 @@
import { pool } from '../../config/db.js';
export async function summary() {
const [[activeProjects]] = await pool.query(
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id WHERE ps.is_closed = 0`
);
const [[overdueProjects]] = await pool.query(
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
WHERE ps.is_closed = 0 AND p.estimated_end_date IS NOT NULL AND p.estimated_end_date < CURDATE()`
);
const [[dueSoonProjects]] = await pool.query(
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
WHERE ps.is_closed = 0 AND p.estimated_end_date IS NOT NULL
AND p.estimated_end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)`
);
const [[pendingActivities]] = await pool.query(
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id WHERE st.is_closed = 0`
);
const [[overdueActivities]] = await pool.query(
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id
WHERE st.is_closed = 0 AND a.due_date IS NOT NULL AND a.due_date < CURDATE()`
);
const [[todayActivities]] = await pool.query(
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id
WHERE st.is_closed = 0 AND a.due_date = CURDATE()`
);
const [[avgProgress]] = await pool.query(
`SELECT AVG(progress_pct) AS avg_pct FROM projects p JOIN project_statuses ps ON ps.id = p.status_id WHERE ps.is_closed = 0`
);
return {
activeProjects: activeProjects.c,
overdueProjects: overdueProjects.c,
dueSoonProjects: dueSoonProjects.c,
pendingActivities: pendingActivities.c,
overdueActivities: overdueActivities.c,
todayActivities: todayActivities.c,
averageProgress: avgProgress.avg_pct === null ? 0 : Math.round(Number(avgProgress.avg_pct) * 100) / 100,
};
}
export async function myPendingActivities(userId) {
const [rows] = await pool.query(
`SELECT a.id, a.code, a.name, a.due_date, a.progress_pct, p.id AS project_id, p.name AS project_name
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 a.responsible_user_id = ?
ORDER BY (a.due_date IS NULL), a.due_date LIMIT 20`,
[userId]
);
return rows;
}
export async function calendarActivities(monthStart, monthEnd) {
const [rows] = await pool.query(
`SELECT a.id, a.code, a.name, a.due_date, a.is_milestone, a.progress_pct,
st.is_closed AS status_is_closed, p.id AS project_id, p.name AS project_name
FROM activities a JOIN activity_statuses st ON st.id = a.status_id JOIN projects p ON p.id = a.project_id
WHERE a.due_date BETWEEN ? AND ?
ORDER BY a.due_date`,
[monthStart, monthEnd]
);
return rows;
}
export async function projectsByStatus() {
const [rows] = await pool.query(
`SELECT ps.name, ps.color, COUNT(p.id) AS total
FROM project_statuses ps LEFT JOIN projects p ON p.status_id = ps.id
GROUP BY ps.id ORDER BY ps.sort_order`
);
return rows;
}