diff --git a/foundation_system/Components/MenuViewComponent.cs b/foundation_system/Components/MenuViewComponent.cs index c90c5c8..775f137 100644 --- a/foundation_system/Components/MenuViewComponent.cs +++ b/foundation_system/Components/MenuViewComponent.cs @@ -19,36 +19,96 @@ public class MenuViewComponent : ViewComponent var userIdClaim = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier); if (userIdClaim == null || !long.TryParse(userIdClaim.Value, out var userId)) { - return View(new List()); + return View(new foundation_system.Models.ViewModels.MenuViewModel()); } var isRoot = HttpContext.User.IsInRole("ROOT"); - - List menuItems; + List userPermisoIds; if (isRoot) { - menuItems = await _context.Permisos - .Include(p => p.Modulo) - .Where(p => p.EsMenu) - .OrderBy(p => p.Modulo!.Orden) - .ThenBy(p => p.Orden) - .ToListAsync(); + userPermisoIds = await _context.Permisos.Select(p => p.Id).ToListAsync(); } else { - menuItems = await _context.RolesUsuario + userPermisoIds = await _context.RolesUsuario .Where(ru => ru.UsuarioId == userId) - .Join(_context.RolesPermisos, ru => ru.RolId, rp => rp.RolId, (ru, rp) => rp) - .Join(_context.Permisos, rp => rp.PermisoId, p => p.Id, (rp, p) => p) - .Include(p => p.Modulo) - .Where(p => p.EsMenu) - .OrderBy(p => p.Modulo!.Orden) - .ThenBy(p => p.Orden) + .Join(_context.RolesPermisos, ru => ru.RolId, rp => rp.RolId, (ru, rp) => rp.PermisoId) .Distinct() .ToListAsync(); } - return View(menuItems); + // Fetch all active modules and permissions + var allModules = await _context.Modulos + .Include(m => m.Permisos) + .Where(m => m.Activo) + .OrderBy(m => m.Orden) + .ToListAsync(); + + var menuViewModel = new foundation_system.Models.ViewModels.MenuViewModel(); + + // Build the tree starting from root modules (ParentId == null) + var rootModules = allModules.Where(m => m.ParentId == null).OrderBy(m => m.Orden); + + foreach (var module in rootModules) + { + var menuItem = BuildModuleMenuItem(module, allModules, userPermisoIds); + if (menuItem != null) + { + menuViewModel.Items.Add(menuItem); + } + } + + return View(menuViewModel); + } + + private foundation_system.Models.ViewModels.MenuItem? BuildModuleMenuItem( + foundation_system.Models.Modulo module, + List allModules, + List userPermisoIds) + { + var item = new foundation_system.Models.ViewModels.MenuItem + { + Title = module.Nombre, + Icon = module.Icono, + IsGroup = true, + Order = module.Orden + }; + + // 1. Add Submodules + var subModules = allModules.Where(m => m.ParentId == module.Id).OrderBy(m => m.Orden); + foreach (var sub in subModules) + { + var subItem = BuildModuleMenuItem(sub, allModules, userPermisoIds); + if (subItem != null) + { + item.Children.Add(subItem); + } + } + + // 2. Add Direct Permissions (Menu Items) + var permissions = module.Permisos + .Where(p => p.EsMenu && userPermisoIds.Contains(p.Id)) + .OrderBy(p => p.Orden); + + foreach (var p in permissions) + { + item.Children.Add(new foundation_system.Models.ViewModels.MenuItem + { + Title = p.Nombre, + Icon = p.Icono, + Url = p.Url, + IsGroup = false, + Order = p.Orden + }); + } + + // Only return the item if it has children (permissions or submodules with permissions) + if (item.Children.Any()) + { + return item; + } + + return null; } } diff --git a/foundation_system/Controllers/HomeController.cs b/foundation_system/Controllers/HomeController.cs index 65e83ac..26c9cf3 100644 --- a/foundation_system/Controllers/HomeController.cs +++ b/foundation_system/Controllers/HomeController.cs @@ -1,8 +1,9 @@ using System.Diagnostics; +using System.Security.Claims; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using foundation_system.Data; using foundation_system.Models; +using foundation_system.Models.ViewModels; +using foundation_system.Services; using Microsoft.AspNetCore.Authorization; namespace foundation_system.Controllers; @@ -11,24 +12,24 @@ namespace foundation_system.Controllers; public class HomeController : Controller { private readonly ILogger _logger; - private readonly ApplicationDbContext _context; + private readonly IDashboardService _dashboardService; - public HomeController(ILogger logger, ApplicationDbContext context) + public HomeController(ILogger logger, IDashboardService dashboardService) { _logger = logger; - _context = context; + _dashboardService = dashboardService; } public async Task Index() { - var totalNinos = await _context.Ninos.CountAsync(n => n.Activo); - var asistenciasHoy = await _context.Asistencias - .CountAsync(a => a.Fecha == DateOnly.FromDateTime(DateTime.Now) && a.Estado == "PRESENTE"); - - ViewBag.TotalNinos = totalNinos; - ViewBag.AsistenciasHoy = asistenciasHoy; - - return View(); + var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier); + if (userIdClaim == null || !long.TryParse(userIdClaim.Value, out var userId)) + { + return View(new DashboardViewModel()); + } + + var vm = await _dashboardService.GetDashboardDataAsync(userId); + return View(vm); } public IActionResult Privacy() diff --git a/foundation_system/Controllers/ModuloController.cs b/foundation_system/Controllers/ModuloController.cs index 38d8fc5..a996c3b 100644 --- a/foundation_system/Controllers/ModuloController.cs +++ b/foundation_system/Controllers/ModuloController.cs @@ -19,19 +19,27 @@ public class ModuloController : Controller // GET: Modulo public async Task Index() { - return View(await _context.Modulos.OrderBy(m => m.Orden).ToListAsync()); + var modulos = await _context.Modulos + .Include(m => m.Parent) + .OrderBy(m => m.Orden) + .ToListAsync(); + return View(modulos); } // GET: Modulo/Create - public IActionResult Create() + public async Task Create() { + ViewBag.ModulosPadre = await _context.Modulos + .Where(m => m.Activo) + .OrderBy(m => m.Orden) + .ToListAsync(); return View(); } // POST: Modulo/Create [HttpPost] [ValidateAntiForgeryToken] - public async Task Create([Bind("Nombre,Icono,Orden,Activo")] Modulo modulo) + public async Task Create([Bind("Nombre,Icono,Orden,Activo,ParentId")] Modulo modulo) { if (ModelState.IsValid) { @@ -40,6 +48,10 @@ public class ModuloController : Controller await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } + ViewBag.ModulosPadre = await _context.Modulos + .Where(m => m.Activo) + .OrderBy(m => m.Orden) + .ToListAsync(); return View(modulo); } @@ -50,13 +62,19 @@ public class ModuloController : Controller var modulo = await _context.Modulos.FindAsync(id); if (modulo == null) return NotFound(); + + // Exclude current module and its children from parent options + ViewBag.ModulosPadre = await _context.Modulos + .Where(m => m.Activo && m.Id != id) + .OrderBy(m => m.Orden) + .ToListAsync(); return View(modulo); } // POST: Modulo/Edit/5 [HttpPost] [ValidateAntiForgeryToken] - public async Task Edit(int id, [Bind("Id,Nombre,Icono,Orden,Activo")] Modulo modulo) + public async Task Edit(int id, [Bind("Id,Nombre,Icono,Orden,Activo,ParentId")] Modulo modulo) { if (id != modulo.Id) return NotFound(); @@ -74,6 +92,10 @@ public class ModuloController : Controller } return RedirectToAction(nameof(Index)); } + ViewBag.ModulosPadre = await _context.Modulos + .Where(m => m.Activo && m.Id != id) + .OrderBy(m => m.Orden) + .ToListAsync(); return View(modulo); } diff --git a/foundation_system/Controllers/ReportesController.cs b/foundation_system/Controllers/ReportesController.cs index 4a0946d..ee99f01 100644 --- a/foundation_system/Controllers/ReportesController.cs +++ b/foundation_system/Controllers/ReportesController.cs @@ -1,5 +1,3 @@ -using System; -using System.Threading.Tasks; using foundation_system.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/foundation_system/Migrations/Scripts/AddAdditionalReportPermissions.sql b/foundation_system/Migrations/Scripts/AddAdditionalReportPermissions.sql index 5a043b2..f445851 100644 --- a/foundation_system/Migrations/Scripts/AddAdditionalReportPermissions.sql +++ b/foundation_system/Migrations/Scripts/AddAdditionalReportPermissions.sql @@ -1,33 +1,72 @@ -- ============================================= -- Author: System -- Create date: 2025-12-31 --- Description: Adds permissions for new financial reports. +-- Description: Adds permissions for new financial reports with hierarchy. -- ============================================= DO $$ DECLARE - v_modulo_id INT; + v_reportes_id INT; + v_financieros_id INT; BEGIN - SELECT id INTO v_modulo_id FROM modulos WHERE nombre = 'Reportes'; + -- 1. Ensure Root Module 'Reportes' exists + SELECT id INTO v_reportes_id FROM modulos WHERE nombre = 'Reportes'; + + IF v_reportes_id IS NULL THEN + INSERT INTO modulos (nombre, icono, orden, activo) + VALUES ('Reportes', 'bi bi-file-earmark-text', 90, true) + RETURNING id INTO v_reportes_id; + END IF; - -- 1. Movimientos - INSERT INTO permisos (modulo_id, codigo, nombre, descripcion, url, icono, orden, es_menu) - SELECT v_modulo_id, 'reportes.movimientos', 'Movimientos de Caja', 'Detalle de ingresos y egresos', '/Reportes/Movimientos', 'bi bi-list-check', 2, true - WHERE NOT EXISTS (SELECT 1 FROM permisos WHERE codigo = 'reportes.movimientos'); + -- 2. Create Sub-Module 'Reportes Financieros' + SELECT id INTO v_financieros_id FROM modulos WHERE nombre = 'Reportes Financieros' AND parent_id = v_reportes_id; - -- 2. Histórico de Saldos - INSERT INTO permisos (modulo_id, codigo, nombre, descripcion, url, icono, orden, es_menu) - SELECT v_modulo_id, 'reportes.saldos', 'Histórico de Saldos', 'Evolución diaria del saldo', '/Reportes/HistoricoSaldos', 'bi bi-graph-up', 3, true - WHERE NOT EXISTS (SELECT 1 FROM permisos WHERE codigo = 'reportes.saldos'); + IF v_financieros_id IS NULL THEN + INSERT INTO modulos (nombre, icono, orden, activo, parent_id) + VALUES ('Reportes Financieros', 'bi bi-cash-coin', 1, true, v_reportes_id) + RETURNING id INTO v_financieros_id; + END IF; - -- 3. Gastos por Categoría - INSERT INTO permisos (modulo_id, codigo, nombre, descripcion, url, icono, orden, es_menu) - SELECT v_modulo_id, 'reportes.gastos', 'Gastos por Categoría', 'Análisis de gastos', '/Reportes/GastosCategoria', 'bi bi-pie-chart', 4, true - WHERE NOT EXISTS (SELECT 1 FROM permisos WHERE codigo = 'reportes.gastos'); + -- 3. Add Permissions (Menu Items) linked to 'Reportes Financieros' + + -- Arqueo de Caja (Move if exists or insert) + IF EXISTS (SELECT 1 FROM permisos WHERE codigo = 'reportes.arqueo') THEN + UPDATE permisos SET modulo_id = v_financieros_id WHERE codigo = 'reportes.arqueo'; + ELSE + INSERT INTO permisos (modulo_id, codigo, nombre, descripcion, url, icono, orden, es_menu) + VALUES (v_financieros_id, 'reportes.arqueo', 'Arqueo de Caja', 'Cierre diario de caja', '/Reportes/ArqueoCaja', 'bi bi-calculator', 1, true); + END IF; - -- 4. Reposiciones - INSERT INTO permisos (modulo_id, codigo, nombre, descripcion, url, icono, orden, es_menu) - SELECT v_modulo_id, 'reportes.reposiciones', 'Reposiciones', 'Reporte de reintegros', '/Reportes/Reposiciones', 'bi bi-cash-stack', 5, true - WHERE NOT EXISTS (SELECT 1 FROM permisos WHERE codigo = 'reportes.reposiciones'); + -- Movimientos + IF EXISTS (SELECT 1 FROM permisos WHERE codigo = 'reportes.movimientos') THEN + UPDATE permisos SET modulo_id = v_financieros_id WHERE codigo = 'reportes.movimientos'; + ELSE + INSERT INTO permisos (modulo_id, codigo, nombre, descripcion, url, icono, orden, es_menu) + VALUES (v_financieros_id, 'reportes.movimientos', 'Movimientos de Caja', 'Detalle de ingresos y egresos', '/Reportes/Movimientos', 'bi bi-list-check', 2, true); + END IF; + + -- Histórico de Saldos + IF EXISTS (SELECT 1 FROM permisos WHERE codigo = 'reportes.saldos') THEN + UPDATE permisos SET modulo_id = v_financieros_id WHERE codigo = 'reportes.saldos'; + ELSE + INSERT INTO permisos (modulo_id, codigo, nombre, descripcion, url, icono, orden, es_menu) + VALUES (v_financieros_id, 'reportes.saldos', 'Histórico de Saldos', 'Evolución diaria del saldo', '/Reportes/HistoricoSaldos', 'bi bi-graph-up', 3, true); + END IF; + + -- Gastos por Categoría + IF EXISTS (SELECT 1 FROM permisos WHERE codigo = 'reportes.gastos') THEN + UPDATE permisos SET modulo_id = v_financieros_id WHERE codigo = 'reportes.gastos'; + ELSE + INSERT INTO permisos (modulo_id, codigo, nombre, descripcion, url, icono, orden, es_menu) + VALUES (v_financieros_id, 'reportes.gastos', 'Gastos por Categoría', 'Análisis de gastos', '/Reportes/GastosCategoria', 'bi bi-pie-chart', 4, true); + END IF; + + -- Reposiciones + IF EXISTS (SELECT 1 FROM permisos WHERE codigo = 'reportes.reposiciones') THEN + UPDATE permisos SET modulo_id = v_financieros_id WHERE codigo = 'reportes.reposiciones'; + ELSE + INSERT INTO permisos (modulo_id, codigo, nombre, descripcion, url, icono, orden, es_menu) + VALUES (v_financieros_id, 'reportes.reposiciones', 'Reposiciones', 'Reporte de reintegros', '/Reportes/Reposiciones', 'bi bi-cash-stack', 5, true); + END IF; END $$; diff --git a/foundation_system/Models/Modulo.cs b/foundation_system/Models/Modulo.cs index 837912d..1123bc6 100644 --- a/foundation_system/Models/Modulo.cs +++ b/foundation_system/Models/Modulo.cs @@ -28,6 +28,14 @@ public class Modulo [Column("creado_en")] public DateTime CreadoEn { get; set; } = DateTime.UtcNow; - // Navigation property + [Column("parent_id")] + public int? ParentId { get; set; } + + // Navigation properties + [ForeignKey("ParentId")] + public virtual Modulo? Parent { get; set; } + + public virtual ICollection SubModulos { get; set; } = new List(); + public virtual ICollection Permisos { get; set; } = new List(); } diff --git a/foundation_system/Program.cs b/foundation_system/Program.cs index bdf932e..3b1cb01 100644 --- a/foundation_system/Program.cs +++ b/foundation_system/Program.cs @@ -18,6 +18,8 @@ builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // Configure cookie authentication builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) diff --git a/foundation_system/Views/Account/Login.cshtml b/foundation_system/Views/Account/Login.cshtml index c0a5915..8fe21a3 100644 --- a/foundation_system/Views/Account/Login.cshtml +++ b/foundation_system/Views/Account/Login.cshtml @@ -1,7 +1,9 @@ @model foundation_system.Models.ViewModels.LoginViewModel +@inject foundation_system.Services.IConfiguracionService ConfigService @{ ViewData["Title"] = "Iniciar Sesión"; Layout = null; + var logoUrl = await ConfigService.GetValorOrDefaultAsync("LOGO_FUNDATION", "/img/logo-placeholder.png"); } @@ -24,13 +26,7 @@

MIES

Misión Esperanza

diff --git a/foundation_system/Views/Home/Index.cshtml b/foundation_system/Views/Home/Index.cshtml index d0dacea..03f33a1 100644 --- a/foundation_system/Views/Home/Index.cshtml +++ b/foundation_system/Views/Home/Index.cshtml @@ -1,50 +1,154 @@ -@{ +@model foundation_system.Models.ViewModels.DashboardViewModel +@inject foundation_system.Services.IConfiguracionService ConfigService +@{ ViewData["Title"] = "Dashboard"; + var nameFoundation = await ConfigService.GetValorOrDefaultAsync("NAME_FOUNDATION", "foundation_system"); + var description = await ConfigService.GetValorOrDefaultAsync("DESCRIPTION_FOUNDATION", ""); } -
-
-
-
-
- -
-
-
Total Niños
-

@ViewBag.TotalNinos

-
-
-
-
-
-
-
-
- -
-
-
Asistencias Hoy
-

@ViewBag.AsistenciasHoy

-
-
-
-
-
-
-
-
- -
-
-
Expedientes Activos
-

@ViewBag.TotalNinos

-
-
-
-
-
-

Bienvenido al Sistema MIES

-

Utilice el menú lateral para gestionar los expedientes de los niños y el control de asistencia diaria.

-
\ No newline at end of file +

Bienvenido al Sistema @nameFoundation

+ +

+ Este sistema ha sido diseñado para apoyar la gestión de la fundación, + permitiendo administrar de forma segura los expedientes de los niños, + el control de asistencia diaria, entre otros. +

+ +
+ +

+ @description; +

+
+ +
+ @* Expediente Stats - Only visible if user has permission *@ + @if (Model.TienePermisoExpediente) + { +
+
+
+
+ +
+
+
Niños Activos
+

@Model.TotalNinosActivos

+
+
+
+
+
+
+
+
+ +
+
+
Cumpleañeros del Mes
+

@Model.CumpleanerosMes

+
+
+
+
+
+
+
+
+ +
+
+
Mayores de 14 años
+

@Model.NinosMayores14

+
+
+
+
+ } + + @* Attendance Stats - Only visible if user has permission *@ + @if (Model.TienePermisoAsistencia) + { +
+
+
+
+ +
+
+
Asistencia Hoy
+

@Model.AsistenciasHoy / @Model.TotalNinosParaAsistencia

+ @Model.PorcentajeAsistenciaHoy% +
+
+
+
+ } + + @* Petty Cash Stats - Only visible if user has permission *@ + @if (Model.TienePermisoCajaChica) + { +
+
+
+
+ +
+
+
Saldo Caja Chica
+

C$ @Model.SaldoCajaChica.ToString("N2")

+ @if (!string.IsNullOrEmpty(Model.NombreCajaActiva)) + { + @Model.NombreCajaActiva + } +
+
+
+
+
+
+
+
+ +
+
+
Gastos del Mes
+

C$ @Model.GastosMes.ToString("N2")

+ @Model.MovimientosMes movimientos +
+
+
+
+ } +
+ +@* Birthday List - Only visible if user has permission and there are birthdays *@ +@if (Model.TienePermisoExpediente && Model.ListaCumpleaneros.Any()) +{ +
+
Cumpleañeros de @DateTime.Now.ToString("MMMM")
+
+ + + + + + + + + + @foreach (var cumple in Model.ListaCumpleaneros) + { + + + + + + } + +
DíaNombreCumple
@cumple.DiaCumple@cumple.NombreCompleto@cumple.Edad años
+
+
+} diff --git a/foundation_system/Views/Modulo/Create.cshtml b/foundation_system/Views/Modulo/Create.cshtml index 37b7a06..2fc1e2b 100644 --- a/foundation_system/Views/Modulo/Create.cshtml +++ b/foundation_system/Views/Modulo/Create.cshtml @@ -19,13 +19,24 @@
-
+
- + + + Si se selecciona, este será un sub-módulo +
+
+
diff --git a/foundation_system/Views/Modulo/Edit.cshtml b/foundation_system/Views/Modulo/Edit.cshtml index 33f16cb..52504f8 100644 --- a/foundation_system/Views/Modulo/Edit.cshtml +++ b/foundation_system/Views/Modulo/Edit.cshtml @@ -21,13 +21,24 @@
-
+
- + + + Si se selecciona, este será un sub-módulo +
+
+
diff --git a/foundation_system/Views/Shared/Components/Menu/Default.cshtml b/foundation_system/Views/Shared/Components/Menu/Default.cshtml index 100a39a..7c5e83e 100644 --- a/foundation_system/Views/Shared/Components/Menu/Default.cshtml +++ b/foundation_system/Views/Shared/Components/Menu/Default.cshtml @@ -1,29 +1,19 @@ -@model IEnumerable +@model foundation_system.Models.ViewModels.MenuViewModel + +@{ + var currentController = ViewContext.RouteData.Values["controller"]?.ToString(); + var currentAction = ViewContext.RouteData.Values["action"]?.ToString(); + var currentUrl = $"/{currentController}/{currentAction}"; + ViewData["CurrentUrl"] = currentUrl; +} diff --git a/foundation_system/Views/Shared/_Layout.cshtml b/foundation_system/Views/Shared/_Layout.cshtml index d4f2259..89c4d42 100644 --- a/foundation_system/Views/Shared/_Layout.cshtml +++ b/foundation_system/Views/Shared/_Layout.cshtml @@ -1,9 +1,17 @@ - +@inject foundation_system.Services.IConfiguracionService ConfigService +@{ + var logoUrl = await ConfigService.GetValorOrDefaultAsync("LOGO_FOUNDATION", "/assets/home.png"); + var nameShort = await ConfigService.GetValorOrDefaultAsync("NAME_FOUNDATION_SHORT", "foundation_system"); + var nameFoundation = await ConfigService.GetValorOrDefaultAsync("NAME_FOUNDATION", "foundation_system"); + var descriptionShort = await ConfigService.GetValorOrDefaultAsync("DESCRIPTION_SHORT", "Fundacion sin fines de lucro"); + var version = await ConfigService.GetValorOrDefaultAsync("VERSION_SYSTEM", "1.0.0"); +} + - @ViewData["Title"] - MIES + @ViewData["Title"] - @nameShort @@ -20,15 +28,16 @@ @@ -53,7 +62,7 @@