fixed commit

This commit is contained in:
2026-01-01 17:21:33 -06:00
parent 20d550f12c
commit e66005f9e7
8 changed files with 288 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
-- =============================================
-- Author: System
-- Create date: 2025-12-31
-- Description: Adds parent_id to modulos table for hierarchical support.
-- =============================================
ALTER TABLE modulos
ADD COLUMN IF NOT EXISTS parent_id INT REFERENCES modulos(id);
-- Optional: Create an index for performance
CREATE INDEX IF NOT EXISTS idx_modulos_parent_id ON modulos(parent_id);

View File

@@ -0,0 +1,33 @@
namespace foundation_system.Models.ViewModels;
public class DashboardViewModel
{
// Permission flags
public bool TienePermisoExpediente { get; set; }
public bool TienePermisoAsistencia { get; set; }
public bool TienePermisoCajaChica { get; set; }
// Expediente stats (visible if TienePermisoExpediente)
public int TotalNinosActivos { get; set; }
public int CumpleanerosMes { get; set; }
public int NinosMayores14 { get; set; }
public List<NinoCumpleanero> ListaCumpleaneros { get; set; } = new();
// Attendance stats (visible if TienePermisoAsistencia)
public int AsistenciasHoy { get; set; }
public int TotalNinosParaAsistencia { get; set; }
public decimal PorcentajeAsistenciaHoy { get; set; }
// Petty Cash stats (visible if TienePermisoCajaChica)
public decimal SaldoCajaChica { get; set; }
public decimal GastosMes { get; set; }
public int MovimientosMes { get; set; }
public string? NombreCajaActiva { get; set; }
}
public class NinoCumpleanero
{
public string NombreCompleto { get; set; } = string.Empty;
public int Edad { get; set; }
public int DiaCumple { get; set; }
}

View File

@@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace foundation_system.Models.ViewModels
{
public class MenuViewModel
{
public List<MenuItem> Items { get; set; } = new List<MenuItem>();
}
public class MenuItem
{
public string Title { get; set; } = string.Empty;
public string? Icon { get; set; }
public string? Url { get; set; }
public bool IsActive { get; set; }
public bool IsGroup { get; set; } // True if it's a module with children
public List<MenuItem> Children { get; set; } = new List<MenuItem>();
public int Order { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore;
using foundation_system.Data;
namespace foundation_system.Services;
public class ConfiguracionService : IConfiguracionService
{
private readonly ApplicationDbContext _context;
public ConfiguracionService(ApplicationDbContext context)
{
_context = context;
}
public async Task<string?> GetValorAsync(string clave)
{
var config = await _context.Configuraciones
.FirstOrDefaultAsync(c => c.Clave == clave);
return config?.Valor;
}
public async Task<string> GetValorOrDefaultAsync(string clave, string defaultValue)
{
var valor = await GetValorAsync(clave);
return valor ?? defaultValue;
}
}

View File

@@ -0,0 +1,134 @@
using Microsoft.EntityFrameworkCore;
using foundation_system.Data;
using foundation_system.Models.ViewModels;
namespace foundation_system.Services;
public class DashboardService : IDashboardService
{
private readonly ApplicationDbContext _context;
public DashboardService(ApplicationDbContext context)
{
_context = context;
}
public async Task<List<string>> GetUserPermissionCodesAsync(long userId)
{
// Get all permission codes for the user through their roles
var permissionCodes = await _context.RolesUsuario
.Where(ru => ru.UsuarioId == userId)
.Join(_context.RolesPermisos, ru => ru.RolId, rp => rp.RolId, (ru, rp) => rp.PermisoId)
.Join(_context.Permisos, permisoId => permisoId, p => p.Id, (permisoId, p) => p.Codigo)
.Distinct()
.ToListAsync();
return permissionCodes;
}
public async Task<bool> UserHasPermissionAsync(long userId, string permisoCodigo)
{
var codes = await GetUserPermissionCodesAsync(userId);
// Check if user has a permission that starts with the given code (e.g., "Expediente" matches "Expediente.Index")
return codes.Any(c => c.StartsWith(permisoCodigo, StringComparison.OrdinalIgnoreCase));
}
public async Task<DashboardViewModel> GetDashboardDataAsync(long userId)
{
var vm = new DashboardViewModel();
// Check if user is ROOT (has all permissions)
var isRoot = await _context.RolesUsuario
.Where(ru => ru.UsuarioId == userId)
.Join(_context.RolesSistema, ru => ru.RolId, r => r.Id, (ru, r) => r.Codigo)
.AnyAsync(codigo => codigo == "ROOT");
var permissionCodes = await GetUserPermissionCodesAsync(userId);
// Set permission flags
vm.TienePermisoExpediente = isRoot || permissionCodes.Any(c => c.StartsWith("Expediente", StringComparison.OrdinalIgnoreCase));
vm.TienePermisoAsistencia = isRoot || permissionCodes.Any(c => c.StartsWith("Asistencia", StringComparison.OrdinalIgnoreCase));
vm.TienePermisoCajaChica = isRoot || permissionCodes.Any(c =>
c.StartsWith("CajaChica", StringComparison.OrdinalIgnoreCase) ||
c.StartsWith("MovimientoCaja", StringComparison.OrdinalIgnoreCase));
// Expediente Stats
if (vm.TienePermisoExpediente)
{
vm.TotalNinosActivos = await _context.Ninos.CountAsync(n => n.Activo);
var today = DateTime.Today;
var currentMonth = today.Month;
// Birthday list for current month
var cumpleaneros = await _context.Ninos
.Where(n => n.Activo)
.Include(n => n.Persona)
.Where(n => n.Persona.FechaNacimiento != null && n.Persona.FechaNacimiento.Value.Month == currentMonth)
.Select(n => new {
n.Persona.Nombres,
n.Persona.Apellidos,
n.Persona.FechaNacimiento
})
.ToListAsync();
vm.CumpleanerosMes = cumpleaneros.Count;
vm.ListaCumpleaneros = cumpleaneros.Select(c => new NinoCumpleanero
{
NombreCompleto = $"{c.Nombres} {c.Apellidos}",
Edad = today.Year - c.FechaNacimiento!.Value.Year,
DiaCumple = c.FechaNacimiento.Value.Day
}).OrderBy(c => c.DiaCumple).ToList();
// Children older than 14
var cutoffDate = DateOnly.FromDateTime(today.AddYears(-14));
vm.NinosMayores14 = await _context.Ninos
.Where(n => n.Activo)
.Include(n => n.Persona)
.CountAsync(n => n.Persona.FechaNacimiento != null && n.Persona.FechaNacimiento <= cutoffDate);
}
// Attendance Stats
if (vm.TienePermisoAsistencia)
{
var today = DateOnly.FromDateTime(DateTime.Today);
vm.TotalNinosParaAsistencia = await _context.Ninos.CountAsync(n => n.Activo);
vm.AsistenciasHoy = await _context.Asistencias
.CountAsync(a => a.Fecha == today && a.Estado == "PRESENTE");
vm.PorcentajeAsistenciaHoy = vm.TotalNinosParaAsistencia > 0
? Math.Round((decimal)vm.AsistenciasHoy / vm.TotalNinosParaAsistencia * 100, 1)
: 0;
}
// Petty Cash Stats
if (vm.TienePermisoCajaChica)
{
var cajaActiva = await _context.CajasChicas
.Where(c => c.Estado == "ABIERTA")
.FirstOrDefaultAsync();
if (cajaActiva != null)
{
vm.NombreCajaActiva = cajaActiva.Nombre;
vm.SaldoCajaChica = cajaActiva.SaldoActual;
var inicioMes = new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1);
var finMes = inicioMes.AddMonths(1).AddDays(-1);
var movimientosMes = await _context.CajaChicaMovimientos
.Where(m => m.CajaChicaId == cajaActiva.Id &&
m.FechaMovimiento >= inicioMes &&
m.FechaMovimiento <= finMes)
.ToListAsync();
vm.MovimientosMes = movimientosMes.Count;
vm.GastosMes = movimientosMes
.Where(m => m.TipoMovimiento == "GASTO")
.Sum(m => m.Monto);
}
}
return vm;
}
}

View File

@@ -0,0 +1,7 @@
namespace foundation_system.Services;
public interface IConfiguracionService
{
Task<string?> GetValorAsync(string clave);
Task<string> GetValorOrDefaultAsync(string clave, string defaultValue);
}

View File

@@ -0,0 +1,10 @@
using foundation_system.Models.ViewModels;
namespace foundation_system.Services;
public interface IDashboardService
{
Task<DashboardViewModel> GetDashboardDataAsync(long userId);
Task<bool> UserHasPermissionAsync(long userId, string permisoCodigo);
Task<List<string>> GetUserPermissionCodesAsync(long userId);
}

View File

@@ -0,0 +1,46 @@
@using foundation_system.Models.ViewModels
@model MenuItem
@{
var currentUrl = ViewData["CurrentUrl"] as string ?? "";
var collapseId = "menu-" + Guid.NewGuid().ToString("N");
// Helper function to check active state recursively
bool IsItemOrChildActive(MenuItem item, string url)
{
if (!item.IsGroup && !string.IsNullOrEmpty(item.Url))
{
return url.StartsWith(item.Url, StringComparison.OrdinalIgnoreCase);
}
return item.Children.Any(c => IsItemOrChildActive(c, url));
}
bool isExpanded = Model.IsGroup && IsItemOrChildActive(Model, currentUrl);
}
@if (Model.IsGroup)
{
<div class="nav-section-title mt-3" data-bs-toggle="collapse" data-bs-target="#@collapseId" aria-expanded="@isExpanded.ToString().ToLower()" style="cursor:pointer">
@if (!string.IsNullOrEmpty(Model.Icon))
{
<i class="bi @Model.Icon me-1"></i>
}
@Model.Title
<i class="bi bi-chevron-down float-end small"></i>
</div>
<div class="collapse @(isExpanded ? "show" : "")" id="@collapseId">
<div class="ms-3 border-start ps-2">
@foreach (var child in Model.Children)
{
<partial name="Components/Menu/_MenuItem" model="child" view-data="ViewData" />
}
</div>
</div>
}
else
{
var isActive = !string.IsNullOrEmpty(Model.Url) && currentUrl.StartsWith(Model.Url, StringComparison.OrdinalIgnoreCase);
<a class="nav-link-custom @(isActive ? "active" : "")" href="@Model.Url">
<i class="bi @Model.Icon"></i> @Model.Title
</a>
}