feat: implementacion de dashboards por rol, soporte de clases virtuales concurrentes en importador de sheets y cartelera del dia

This commit is contained in:
2026-09-18 23:04:48 -03:00
parent 47b50ede7e
commit 1080acd43a
240 changed files with 8354 additions and 8886 deletions
+170
View File
@@ -0,0 +1,170 @@
const express = require('express');
const router = express.Router();
const { requireAuth } = require('../middlewares/authMiddleware');
const { createFormMock } = require('../utils/formHelper');
router.use(requireAuth);
// Mock storage of buildings to support CRUD in memory/BFF layer
let buildingsData = [
{
id: 1,
code: 'CENTRAL',
name: 'Edificio Central',
is_active: true,
address: 'Av. Paseo Colón 255, CABA',
description: 'Sede académica principal, aulas de grado, laboratorios y auditorio.',
floors: 4,
classrooms_count: 29,
total_capacity: 1250
},
{
id: 2,
code: 'VIRTUAL',
name: 'Campus Virtual',
is_active: true,
address: 'campus.unicaba.edu.ar',
description: 'Entorno de aprendizaje digital, aulas sincrónicas y videoconferencias.',
floors: 1,
classrooms_count: 2,
total_capacity: 9999
}
];
// Helper to compute live totals
const getLiveStats = async (apiClient) => {
try {
const response = await apiClient.get('/classrooms');
const classrooms = (response.data && response.data.classrooms) || [];
const physical = classrooms.filter(c => !c.is_virtual && (!c.code || !c.code.includes('VIRTUAL')));
const virtual = classrooms.filter(c => c.is_virtual || (c.code && c.code.includes('VIRTUAL')));
const physCap = physical.reduce((acc, c) => acc + (c.capacity || 0), 0);
if (buildingsData[0]) {
buildingsData[0].classrooms_count = physical.length;
buildingsData[0].total_capacity = physCap;
}
if (buildingsData[1]) {
buildingsData[1].classrooms_count = virtual.length;
}
const totalRooms = classrooms.length;
const totalCap = physCap + (buildingsData[1]?.total_capacity || 9999);
return { totalRooms, totalCap };
} catch (e) {
return { totalRooms: 31, totalCap: 11249 };
}
};
// 1. List Buildings
const handleListBuildings = async (req, res) => {
try {
const { search = '', status = '' } = req.query;
const stats = await getLiveStats(req.apiClient);
let filtered = [...buildingsData];
if (search) {
const s = search.toLowerCase();
filtered = filtered.filter(b =>
(b.name && b.name.toLowerCase().includes(s)) ||
(b.code && b.code.toLowerCase().includes(s)) ||
(b.address && b.address.toLowerCase().includes(s))
);
}
if (status === 'active') {
filtered = filtered.filter(b => b.is_active);
} else if (status === 'inactive') {
filtered = filtered.filter(b => !b.is_active);
}
res.render('buildings/list', {
title: 'Gestión de Edificios - Edu-Space Admin',
buildings: filtered,
total_classrooms: stats.totalRooms,
total_capacity: stats.totalCap,
search,
status_filter: status
});
} catch (error) {
console.error('Error rendering buildings list:', error.message);
res.render('buildings/list', {
title: 'Gestión de Edificios (Offline)',
buildings: buildingsData,
total_classrooms: 31,
total_capacity: 11249,
search: '',
status_filter: ''
});
}
};
router.get('/', handleListBuildings);
router.get('/list', handleListBuildings);
router.get('/list_buildings', handleListBuildings);
// 2. Add Building
router.get(['/add', '/add_building'], (req, res) => {
const form = createFormMock({
is_active: true,
floors: 3
});
res.render('buildings/form', {
title: 'Nuevo Edificio - Edu-Space Admin',
building: null,
form
});
});
router.post(['/add', '/add_building'], (req, res) => {
const { name, code, address, floors, description, is_active } = req.body;
const newBuilding = {
id: buildingsData.length + 1,
name: name || `Edificio ${buildingsData.length + 1}`,
code: code || `EDIF-${buildingsData.length + 1}`,
address: address || '',
floors: parseInt(floors, 10) || 1,
description: description || '',
is_active: is_active === 'true' || is_active === 'on' || is_active === true,
classrooms_count: 0,
total_capacity: 0
};
buildingsData.push(newBuilding);
res.redirect('/buildings/list_buildings');
});
// 3. View / Edit / Toggle Building
router.get(['/view/:id', '/view_building'], (req, res) => {
const id = parseInt(req.params.id || req.query.id, 10) || 1;
const building = buildingsData.find(b => b.id === id) || buildingsData[0];
res.render('buildings/view', {
title: `Edificio ${building.name} - Edu-Space Admin`,
building,
classrooms: []
});
});
router.get(['/edit/:id', '/edit_building'], (req, res) => {
const id = parseInt(req.params.id || req.query.id, 10) || 1;
const building = buildingsData.find(b => b.id === id) || buildingsData[0];
const form = createFormMock(building);
res.render('buildings/form', {
title: `Editar Edificio - ${building.name}`,
building,
form
});
});
router.post(['/toggle/:id', '/toggle_building'], (req, res) => {
const id = parseInt(req.params.id || req.query.id, 10) || 1;
const building = buildingsData.find(b => b.id === id);
if (building) {
building.is_active = !building.is_active;
}
res.redirect('/buildings/list_buildings');
});
module.exports = router;