From 4ab3e9e7562996b9e15d0a532ef31c5d08647930 Mon Sep 17 00:00:00 2001 From: adalberto Date: Tue, 30 Dec 2025 23:30:13 -0600 Subject: [PATCH] Permisos y roles --- .../Components/MenuViewComponent.cs | 52 +++++ .../Controllers/AccountController.cs | 20 +- .../Controllers/AsistenciaController.cs | 96 +++++++++ .../ColaboradorAsistenciaController.cs | 69 +++++++ .../Controllers/ColaboradorController.cs | 192 ++++++++++++++++++ .../Controllers/ConfiguracionController.cs | 3 + .../Controllers/PermisoController.cs | 111 ++++++++++ .../Controllers/RolController.cs | 192 ++++++++++++++++++ .../Controllers/UsuarioController.cs | 58 +++++- .../Data/ApplicationDbContext.cs | 18 ++ .../Filters/PermissionAttribute.cs | 47 +++++ .../Models/AsistenciaColaborador.cs | 32 +++ foundation_system/Models/Colaborador.cs | 48 +++++ foundation_system/Models/Permiso.cs | 45 ++++ foundation_system/Models/RolPermiso.cs | 24 +++ foundation_system/Models/RolSistema.cs | 1 + .../Models/ViewModels/ColaboradorViewModel.cs | 62 ++++++ .../Models/ViewModels/UsuarioViewModel.cs | 3 + foundation_system/Services/AuthService.cs | 19 ++ foundation_system/Services/IAuthService.cs | 5 + .../Views/Asistencia/Index.cshtml | 43 +++- .../Views/Asistencia/ReporteIndividual.cshtml | 182 +++++++++++++++++ .../Views/Asistencia/ReporteMensual.cshtml | 174 ++++++++++++++++ .../Views/Colaborador/Create.cshtml | 123 +++++++++++ .../Views/Colaborador/Edit.cshtml | 130 ++++++++++++ .../Views/Colaborador/Index.cshtml | 106 ++++++++++ .../Views/ColaboradorAsistencia/Index.cshtml | 116 +++++++++++ foundation_system/Views/Permiso/Create.cshtml | 86 ++++++++ foundation_system/Views/Permiso/Edit.cshtml | 87 ++++++++ foundation_system/Views/Permiso/Index.cshtml | 90 ++++++++ foundation_system/Views/Rol/Create.cshtml | 55 +++++ foundation_system/Views/Rol/Edit.cshtml | 57 ++++++ foundation_system/Views/Rol/Index.cshtml | 90 ++++++++ .../Views/Rol/Permissions.cshtml | 62 ++++++ .../Shared/Components/Menu/Default.cshtml | 23 +++ foundation_system/Views/Shared/_Layout.cshtml | 26 +-- foundation_system/Views/Usuario/Create.cshtml | 22 +- foundation_system/Views/Usuario/Edit.cshtml | 19 ++ 38 files changed, 2552 insertions(+), 36 deletions(-) create mode 100644 foundation_system/Components/MenuViewComponent.cs create mode 100644 foundation_system/Controllers/ColaboradorAsistenciaController.cs create mode 100644 foundation_system/Controllers/ColaboradorController.cs create mode 100644 foundation_system/Controllers/PermisoController.cs create mode 100644 foundation_system/Controllers/RolController.cs create mode 100644 foundation_system/Filters/PermissionAttribute.cs create mode 100644 foundation_system/Models/AsistenciaColaborador.cs create mode 100644 foundation_system/Models/Colaborador.cs create mode 100644 foundation_system/Models/Permiso.cs create mode 100644 foundation_system/Models/RolPermiso.cs create mode 100644 foundation_system/Models/ViewModels/ColaboradorViewModel.cs create mode 100644 foundation_system/Views/Asistencia/ReporteIndividual.cshtml create mode 100644 foundation_system/Views/Asistencia/ReporteMensual.cshtml create mode 100644 foundation_system/Views/Colaborador/Create.cshtml create mode 100644 foundation_system/Views/Colaborador/Edit.cshtml create mode 100644 foundation_system/Views/Colaborador/Index.cshtml create mode 100644 foundation_system/Views/ColaboradorAsistencia/Index.cshtml create mode 100644 foundation_system/Views/Permiso/Create.cshtml create mode 100644 foundation_system/Views/Permiso/Edit.cshtml create mode 100644 foundation_system/Views/Permiso/Index.cshtml create mode 100644 foundation_system/Views/Rol/Create.cshtml create mode 100644 foundation_system/Views/Rol/Edit.cshtml create mode 100644 foundation_system/Views/Rol/Index.cshtml create mode 100644 foundation_system/Views/Rol/Permissions.cshtml create mode 100644 foundation_system/Views/Shared/Components/Menu/Default.cshtml diff --git a/foundation_system/Components/MenuViewComponent.cs b/foundation_system/Components/MenuViewComponent.cs new file mode 100644 index 0000000..4f8e706 --- /dev/null +++ b/foundation_system/Components/MenuViewComponent.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using foundation_system.Data; +using System.Security.Claims; + +namespace foundation_system.Components; + +public class MenuViewComponent : ViewComponent +{ + private readonly ApplicationDbContext _context; + + public MenuViewComponent(ApplicationDbContext context) + { + _context = context; + } + + public async Task InvokeAsync() + { + var userIdClaim = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier); + if (userIdClaim == null || !long.TryParse(userIdClaim.Value, out var userId)) + { + return View(new List()); + } + + var isRoot = HttpContext.User.IsInRole("ROOT"); + + List menuItems; + + if (isRoot) + { + menuItems = await _context.Permisos + .Where(p => p.EsMenu) + .OrderBy(p => p.Modulo) + .ThenBy(p => p.Orden) + .ToListAsync(); + } + else + { + menuItems = 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) + .Where(p => p.EsMenu) + .OrderBy(p => p.Modulo) + .ThenBy(p => p.Orden) + .Distinct() + .ToListAsync(); + } + + return View(menuItems); + } +} diff --git a/foundation_system/Controllers/AccountController.cs b/foundation_system/Controllers/AccountController.cs index ad692e0..2e99756 100644 --- a/foundation_system/Controllers/AccountController.cs +++ b/foundation_system/Controllers/AccountController.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using foundation_system.Models.ViewModels; using foundation_system.Services; +using foundation_system.Data; +using Microsoft.EntityFrameworkCore; namespace foundation_system.Controllers; @@ -12,11 +14,13 @@ public class AccountController : Controller { private readonly IAuthService _authService; private readonly ILogger _logger; + private readonly ApplicationDbContext _context; - public AccountController(IAuthService authService, ILogger logger) + public AccountController(IAuthService authService, ILogger logger, ApplicationDbContext context) { _authService = authService; _logger = logger; + _context = context; } // GET: /Account/Login @@ -71,6 +75,20 @@ public class AccountController : Controller { claims.Add(new Claim(ClaimTypes.Role, role)); } + + // Add permissions as claims + var permissions = await _context.RolesUsuario + .Where(ru => ru.UsuarioId == usuario.Id) // Changed user.Id to usuario.Id + .Join(_context.RolesPermisos, ru => ru.RolId, rp => rp.RolId, (ru, rp) => rp) + .Join(_context.Permisos, rp => rp.PermisoId, p => p.Id, (rp, p) => p) + .Select(p => p.Codigo) + .Distinct() + .ToListAsync(); + + foreach (var permission in permissions) + { + claims.Add(new Claim("Permission", permission)); + } var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); diff --git a/foundation_system/Controllers/AsistenciaController.cs b/foundation_system/Controllers/AsistenciaController.cs index e148227..39bb2bd 100644 --- a/foundation_system/Controllers/AsistenciaController.cs +++ b/foundation_system/Controllers/AsistenciaController.cs @@ -209,6 +209,102 @@ public class AsistenciaController : Controller return Json(new { success = true, message = "Cambios guardados correctamente" }); } + [HttpGet] + public async Task ReporteMensual(int? año, int? mes, string? diasSemana) + { + int targetAnio = año ?? DateTime.Now.Year; + int targetMes = mes ?? DateTime.Now.Month; + + var firstDayOfMonth = new DateTime(targetAnio, targetMes, 1); + var lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1); + + var ninos = await _context.Ninos + .Include(n => n.Persona) + .Where(n => n.Activo && n.Estado == "ACTIVO") + .OrderBy(n => n.Persona.Nombres) + .ToListAsync(); + + var firstDateOnly = DateOnly.FromDateTime(firstDayOfMonth); + var lastDateOnly = DateOnly.FromDateTime(lastDayOfMonth); + + var asistencias = await _context.Asistencias + .Where(a => a.Fecha >= firstDateOnly && a.Fecha <= lastDateOnly) + .ToListAsync(); + + var viewModel = new AsistenciaGridViewModel + { + Año = targetAnio, + Mes = targetMes, + NombreMes = firstDayOfMonth.ToString("MMMM", new System.Globalization.CultureInfo("es-ES")).ToUpper(), + DiasSemanaSeleccionados = diasSemana ?? "", + DiasDelMes = Enumerable.Range(0, lastDayOfMonth.Day) + .Select(day => firstDayOfMonth.AddDays(day)) + .ToList(), + Expedientes = ninos, + Asistencias = asistencias.ToDictionary( + a => $"{a.NinoId}_{a.Fecha:yyyy-MM-dd}", + a => a.Estado switch { + "PRESENTE" => "P", + "TARDE" => "T", + "AUSENTE" => "F", + "JUSTIFICADO" => "J", + "ENFERMO" => "E", + _ => "" + } + ) + }; + + return View(viewModel); + } + + [HttpGet] + public async Task ReporteIndividual(long ninoId, int? año, int? mes, string? diasSemana) + { + int targetAnio = año ?? DateTime.Now.Year; + int targetMes = mes ?? DateTime.Now.Month; + + var firstDayOfMonth = new DateTime(targetAnio, targetMes, 1); + var lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1); + + var nino = await _context.Ninos + .Include(n => n.Persona) + .FirstOrDefaultAsync(n => n.Id == ninoId); + + if (nino == null) return NotFound(); + + var firstDateOnly = DateOnly.FromDateTime(firstDayOfMonth); + var lastDateOnly = DateOnly.FromDateTime(lastDayOfMonth); + + var asistencias = await _context.Asistencias + .Where(a => a.NinoId == ninoId && a.Fecha >= firstDateOnly && a.Fecha <= lastDateOnly) + .ToListAsync(); + + var viewModel = new AsistenciaGridViewModel + { + Año = targetAnio, + Mes = targetMes, + NombreMes = firstDayOfMonth.ToString("MMMM", new System.Globalization.CultureInfo("es-ES")).ToUpper(), + DiasSemanaSeleccionados = diasSemana ?? "", + DiasDelMes = Enumerable.Range(0, lastDayOfMonth.Day) + .Select(day => firstDayOfMonth.AddDays(day)) + .ToList(), + Expedientes = new List { nino }, + Asistencias = asistencias.ToDictionary( + a => $"{a.NinoId}_{a.Fecha:yyyy-MM-dd}", + a => a.Estado switch { + "PRESENTE" => "P", + "TARDE" => "T", + "AUSENTE" => "F", + "JUSTIFICADO" => "J", + "ENFERMO" => "E", + _ => "" + } + ) + }; + + return View(viewModel); + } + [HttpGet] public IActionResult ExportarExcel(int año, int mes, string diasSemana) { diff --git a/foundation_system/Controllers/ColaboradorAsistenciaController.cs b/foundation_system/Controllers/ColaboradorAsistenciaController.cs new file mode 100644 index 0000000..adb5121 --- /dev/null +++ b/foundation_system/Controllers/ColaboradorAsistenciaController.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using foundation_system.Data; +using foundation_system.Models; + +namespace foundation_system.Controllers; + +[Authorize] +public class ColaboradorAsistenciaController : Controller +{ + private readonly ApplicationDbContext _context; + + public ColaboradorAsistenciaController(ApplicationDbContext context) + { + _context = context; + } + + // GET: ColaboradorAsistencia + public async Task Index(DateOnly? fecha) + { + var selectedDate = fecha ?? DateOnly.FromDateTime(DateTime.Today); + ViewBag.SelectedDate = selectedDate; + + var colaboradores = await _context.Colaboradores + .Include(c => c.Persona) + .Where(c => c.Activo) + .OrderBy(c => c.Persona.Apellidos) + .ToListAsync(); + + var asistencias = await _context.AsistenciasColaboradores + .Where(a => a.Fecha == selectedDate) + .ToDictionaryAsync(a => a.ColaboradorId); + + ViewBag.Asistencias = asistencias; + + return View(colaboradores); + } + + // POST: ColaboradorAsistencia/Save + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Save(long colaboradorId, DateOnly fecha, string estado, string? observaciones) + { + var asistencia = await _context.AsistenciasColaboradores + .FirstOrDefaultAsync(a => a.ColaboradorId == colaboradorId && a.Fecha == fecha); + + if (asistencia == null) + { + asistencia = new AsistenciaColaborador + { + ColaboradorId = colaboradorId, + Fecha = fecha, + Estado = estado, + Observaciones = observaciones, + CreadoEn = DateTime.UtcNow + }; + _context.AsistenciasColaboradores.Add(asistencia); + } + else + { + asistencia.Estado = estado; + asistencia.Observaciones = observaciones; + } + + await _context.SaveChangesAsync(); + return Json(new { success = true }); + } +} diff --git a/foundation_system/Controllers/ColaboradorController.cs b/foundation_system/Controllers/ColaboradorController.cs new file mode 100644 index 0000000..13c9911 --- /dev/null +++ b/foundation_system/Controllers/ColaboradorController.cs @@ -0,0 +1,192 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using foundation_system.Data; +using foundation_system.Models; +using foundation_system.Models.ViewModels; + +namespace foundation_system.Controllers; + +[Authorize] +public class ColaboradorController : Controller +{ + private readonly ApplicationDbContext _context; + + public ColaboradorController(ApplicationDbContext context) + { + _context = context; + } + + // GET: Colaborador + public async Task Index() + { + var colaboradores = await _context.Colaboradores + .Include(c => c.Persona) + .OrderBy(c => c.Persona.Apellidos) + .ToListAsync(); + return View(colaboradores); + } + + // GET: Colaborador/Create + public IActionResult Create() + { + return View(new ColaboradorViewModel()); + } + + // POST: Colaborador/Create + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Create(ColaboradorViewModel model) + { + if (ModelState.IsValid) + { + using var transaction = await _context.Database.BeginTransactionAsync(); + try + { + var persona = new Persona + { + Nombres = model.Nombres, + Apellidos = model.Apellidos, + Dui = model.Dui, + Nit = model.Nit, + FechaNacimiento = model.FechaNacimiento, + Genero = model.Genero, + Email = model.Email, + Telefono = model.Telefono, + Direccion = model.Direccion, + Activo = true + }; + + _context.Personas.Add(persona); + await _context.SaveChangesAsync(); + + var colaborador = new Colaborador + { + PersonaId = persona.Id, + Cargo = model.Cargo, + TipoColaborador = model.TipoColaborador, + FechaIngreso = model.FechaIngreso, + HorarioEntrada = model.HorarioEntrada, + HorarioSalida = model.HorarioSalida, + Activo = true + }; + + _context.Colaboradores.Add(colaborador); + await _context.SaveChangesAsync(); + + await transaction.CommitAsync(); + TempData["SuccessMessage"] = "Colaborador creado exitosamente."; + return RedirectToAction(nameof(Index)); + } + catch (Exception) + { + await transaction.RollbackAsync(); + ModelState.AddModelError("", "Ocurrió un error al guardar el colaborador."); + } + } + return View(model); + } + + // GET: Colaborador/Edit/5 + public async Task Edit(long? id) + { + if (id == null) return NotFound(); + + var colaborador = await _context.Colaboradores + .Include(c => c.Persona) + .FirstOrDefaultAsync(c => c.Id == id); + + if (colaborador == null) return NotFound(); + + var model = new ColaboradorViewModel + { + Id = colaborador.Id, + PersonaId = colaborador.PersonaId, + Nombres = colaborador.Persona.Nombres, + Apellidos = colaborador.Persona.Apellidos, + Dui = colaborador.Persona.Dui, + Nit = colaborador.Persona.Nit, + FechaNacimiento = colaborador.Persona.FechaNacimiento, + Genero = colaborador.Persona.Genero, + Email = colaborador.Persona.Email, + Telefono = colaborador.Persona.Telefono, + Direccion = colaborador.Persona.Direccion, + Cargo = colaborador.Cargo, + TipoColaborador = colaborador.TipoColaborador, + FechaIngreso = colaborador.FechaIngreso, + HorarioEntrada = colaborador.HorarioEntrada, + HorarioSalida = colaborador.HorarioSalida, + Activo = colaborador.Activo + }; + + return View(model); + } + + // POST: Colaborador/Edit/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Edit(long id, ColaboradorViewModel model) + { + if (id != model.Id) return NotFound(); + + if (ModelState.IsValid) + { + using var transaction = await _context.Database.BeginTransactionAsync(); + try + { + var colaborador = await _context.Colaboradores + .Include(c => c.Persona) + .FirstOrDefaultAsync(c => c.Id == id); + + if (colaborador == null) return NotFound(); + + // Update Persona + colaborador.Persona.Nombres = model.Nombres; + colaborador.Persona.Apellidos = model.Apellidos; + colaborador.Persona.Dui = model.Dui; + colaborador.Persona.Nit = model.Nit; + colaborador.Persona.FechaNacimiento = model.FechaNacimiento; + colaborador.Persona.Genero = model.Genero; + colaborador.Persona.Email = model.Email; + colaborador.Persona.Telefono = model.Telefono; + colaborador.Persona.Direccion = model.Direccion; + + // Update Colaborador + colaborador.Cargo = model.Cargo; + colaborador.TipoColaborador = model.TipoColaborador; + colaborador.FechaIngreso = model.FechaIngreso; + colaborador.HorarioEntrada = model.HorarioEntrada; + colaborador.HorarioSalida = model.HorarioSalida; + colaborador.Activo = model.Activo; + colaborador.ActualizadoEn = DateTime.UtcNow; + + await _context.SaveChangesAsync(); + await transaction.CommitAsync(); + + TempData["SuccessMessage"] = "Colaborador actualizado exitosamente."; + return RedirectToAction(nameof(Index)); + } + catch (Exception) + { + await transaction.RollbackAsync(); + ModelState.AddModelError("", "Ocurrió un error al actualizar el colaborador."); + } + } + return View(model); + } + + // POST: Colaborador/Delete/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Delete(long id) + { + var colaborador = await _context.Colaboradores.FindAsync(id); + if (colaborador != null) + { + colaborador.Activo = false; // Soft delete + await _context.SaveChangesAsync(); + TempData["SuccessMessage"] = "Colaborador desactivado exitosamente."; + } + return RedirectToAction(nameof(Index)); + } +} diff --git a/foundation_system/Controllers/ConfiguracionController.cs b/foundation_system/Controllers/ConfiguracionController.cs index db217f1..8c2d1e0 100644 --- a/foundation_system/Controllers/ConfiguracionController.cs +++ b/foundation_system/Controllers/ConfiguracionController.cs @@ -3,8 +3,11 @@ using Microsoft.EntityFrameworkCore; using foundation_system.Data; using foundation_system.Models; +using Microsoft.AspNetCore.Authorization; + namespace foundation_system.Controllers; +[Authorize(Roles = "ROOT,SUPERADMIN")] public class ConfiguracionController : Controller { private readonly ApplicationDbContext _context; diff --git a/foundation_system/Controllers/PermisoController.cs b/foundation_system/Controllers/PermisoController.cs new file mode 100644 index 0000000..b86ac16 --- /dev/null +++ b/foundation_system/Controllers/PermisoController.cs @@ -0,0 +1,111 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using foundation_system.Data; +using foundation_system.Models; + +namespace foundation_system.Controllers; + +[Authorize(Roles = "ROOT")] +public class PermisoController : Controller +{ + private readonly ApplicationDbContext _context; + + public PermisoController(ApplicationDbContext context) + { + _context = context; + } + + // GET: Permiso + public async Task Index() + { + return View(await _context.Permisos.OrderBy(p => p.Modulo).ThenBy(p => p.Orden).ToListAsync()); + } + + // GET: Permiso/Create + public IActionResult Create() + { + return View(); + } + + // POST: Permiso/Create + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Create([Bind("Modulo,Codigo,Nombre,Descripcion,Url,Icono,Orden,EsMenu")] Permiso permiso) + { + if (ModelState.IsValid) + { + if (await _context.Permisos.AnyAsync(p => p.Codigo == permiso.Codigo)) + { + ModelState.AddModelError("Codigo", "El código ya existe."); + return View(permiso); + } + + permiso.CreadoEn = DateTime.UtcNow; + _context.Add(permiso); + await _context.SaveChangesAsync(); + return RedirectToAction(nameof(Index)); + } + return View(permiso); + } + + // GET: Permiso/Edit/5 + public async Task Edit(int? id) + { + if (id == null) return NotFound(); + + var permiso = await _context.Permisos.FindAsync(id); + if (permiso == null) return NotFound(); + return View(permiso); + } + + // POST: Permiso/Edit/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Edit(int id, [Bind("Id,Modulo,Codigo,Nombre,Descripcion,Url,Icono,Orden,EsMenu")] Permiso permiso) + { + if (id != permiso.Id) return NotFound(); + + if (ModelState.IsValid) + { + try + { + _context.Update(permiso); + await _context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!PermisoExists(permiso.Id)) return NotFound(); + else throw; + } + return RedirectToAction(nameof(Index)); + } + return View(permiso); + } + + // POST: Permiso/Delete/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Delete(int id) + { + var permiso = await _context.Permisos.FindAsync(id); + if (permiso != null) + { + var isUsed = await _context.RolesPermisos.AnyAsync(rp => rp.PermisoId == id); + if (isUsed) + { + TempData["ErrorMessage"] = "No se puede eliminar porque está asignado a roles."; + return RedirectToAction(nameof(Index)); + } + + _context.Permisos.Remove(permiso); + await _context.SaveChangesAsync(); + } + return RedirectToAction(nameof(Index)); + } + + private bool PermisoExists(int id) + { + return _context.Permisos.Any(e => e.Id == id); + } +} diff --git a/foundation_system/Controllers/RolController.cs b/foundation_system/Controllers/RolController.cs new file mode 100644 index 0000000..2c37d86 --- /dev/null +++ b/foundation_system/Controllers/RolController.cs @@ -0,0 +1,192 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using foundation_system.Data; +using foundation_system.Models; + +namespace foundation_system.Controllers; + +[Authorize(Roles = "ROOT")] +public class RolController : Controller +{ + private readonly ApplicationDbContext _context; + + public RolController(ApplicationDbContext context) + { + _context = context; + } + + // GET: Rol + public async Task Index() + { + return View(await _context.RolesSistema + .Include(r => r.RolesPermisos) + .OrderBy(r => r.Nombre) + .ToListAsync()); + } + + // GET: Rol/Create + public IActionResult Create() + { + return View(); + } + + // POST: Rol/Create + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Create([Bind("Codigo,Nombre,Descripcion")] RolSistema rol) + { + if (ModelState.IsValid) + { + if (await _context.RolesSistema.AnyAsync(r => r.Codigo == rol.Codigo)) + { + ModelState.AddModelError("Codigo", "El código de rol ya existe."); + return View(rol); + } + + rol.CreadoEn = DateTime.UtcNow; + _context.Add(rol); + await _context.SaveChangesAsync(); + return RedirectToAction(nameof(Index)); + } + return View(rol); + } + + // GET: Rol/Edit/5 + public async Task Edit(int? id) + { + if (id == null) return NotFound(); + + var rol = await _context.RolesSistema.FindAsync(id); + if (rol == null) return NotFound(); + return View(rol); + } + + // POST: Rol/Edit/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Edit(int id, [Bind("Id,Codigo,Nombre,Descripcion")] RolSistema rol) + { + if (id != rol.Id) return NotFound(); + + if (ModelState.IsValid) + { + try + { + _context.Update(rol); + await _context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!RolExists(rol.Id)) return NotFound(); + else throw; + } + return RedirectToAction(nameof(Index)); + } + return View(rol); + } + + // GET: Rol/Permissions/5 + public async Task Permissions(int? id) + { + if (id == null) return NotFound(); + + var rol = await _context.RolesSistema + .Include(r => r.RolesPermisos) + .ThenInclude(rp => rp.Permiso) + .FirstOrDefaultAsync(r => r.Id == id); + + if (rol == null) return NotFound(); + + // Fetch all permissions from DB + var permissions = await _context.Permisos + .OrderBy(p => p.Modulo) + .ThenBy(p => p.Orden) + .ToListAsync(); + + ViewBag.Rol = rol; + ViewBag.AssignedControllerCodes = rol.RolesPermisos.Select(rp => rp.Permiso.Codigo).ToList(); + + return View(permissions); + } + + // POST: Rol/UpdatePermissions + [HttpPost] + [ValidateAntiForgeryToken] + public async Task UpdatePermissions(int rolId, string[] selectedControllers) + { + var rol = await _context.RolesSistema + .Include(r => r.RolesPermisos) + .FirstOrDefaultAsync(r => r.Id == rolId); + + if (rol == null) return NotFound(); + + using var transaction = await _context.Database.BeginTransactionAsync(); + try + { + // Remove existing permissions + _context.RolesPermisos.RemoveRange(rol.RolesPermisos); + await _context.SaveChangesAsync(); + + // Add new permissions + if (selectedControllers != null) + { + foreach (var controllerCode in selectedControllers) + { + var permiso = await _context.Permisos.FirstOrDefaultAsync(p => p.Codigo == controllerCode); + if (permiso != null) + { + _context.RolesPermisos.Add(new RolPermiso + { + RolId = rolId, + PermisoId = permiso.Id, + AsignadoEn = DateTime.UtcNow + }); + } + } + } + + await _context.SaveChangesAsync(); + await transaction.CommitAsync(); + TempData["SuccessMessage"] = "Permisos actualizados correctamente."; + } + catch (Exception) + { + await transaction.RollbackAsync(); + TempData["ErrorMessage"] = "Ocurrió un error al actualizar los permisos."; + } + + return RedirectToAction(nameof(Index)); + } + + // POST: Rol/Delete/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Delete(int id) + { + var rol = await _context.RolesSistema.FindAsync(id); + if (rol != null) + { + // Check if it's being used by users + var isUsed = await _context.RolesUsuario.AnyAsync(ru => ru.RolId == id); + if (isUsed) + { + TempData["ErrorMessage"] = "No se puede eliminar el rol porque está asignado a uno o más usuarios."; + return RedirectToAction(nameof(Index)); + } + + // Remove permissions first + var permissions = await _context.RolesPermisos.Where(rp => rp.RolId == id).ToListAsync(); + _context.RolesPermisos.RemoveRange(permissions); + + _context.RolesSistema.Remove(rol); + await _context.SaveChangesAsync(); + } + return RedirectToAction(nameof(Index)); + } + + private bool RolExists(int id) + { + return _context.RolesSistema.Any(e => e.Id == id); + } +} diff --git a/foundation_system/Controllers/UsuarioController.cs b/foundation_system/Controllers/UsuarioController.cs index 1a451d2..c713098 100644 --- a/foundation_system/Controllers/UsuarioController.cs +++ b/foundation_system/Controllers/UsuarioController.cs @@ -5,8 +5,11 @@ using foundation_system.Models; using foundation_system.Models.ViewModels; using BCrypt.Net; +using Microsoft.AspNetCore.Authorization; + namespace foundation_system.Controllers; +[Authorize(Roles = "ROOT,SUPERADMIN")] public class UsuarioController : Controller { private readonly ApplicationDbContext _context; @@ -21,13 +24,16 @@ public class UsuarioController : Controller { var usuarios = await _context.Usuarios .Include(u => u.Persona) + .Include(u => u.RolesUsuario) + .ThenInclude(ru => ru.Rol) .ToListAsync(); return View(usuarios); } // GET: Usuario/Create - public IActionResult Create() + public async Task Create() { + ViewBag.Roles = await _context.RolesSistema.ToListAsync(); return View(new UsuarioViewModel()); } @@ -43,16 +49,17 @@ public class UsuarioController : Controller if (ModelState.IsValid) { - // Check if username or email already exists if (await _context.Usuarios.AnyAsync(u => u.NombreUsuario == model.NombreUsuario)) { ModelState.AddModelError("NombreUsuario", "El nombre de usuario ya está en uso"); + ViewBag.Roles = await _context.RolesSistema.ToListAsync(); return View(model); } if (await _context.Usuarios.AnyAsync(u => u.Email == model.Email)) { ModelState.AddModelError("Email", "El correo electrónico ya está en uso"); + ViewBag.Roles = await _context.RolesSistema.ToListAsync(); return View(model); } @@ -85,6 +92,21 @@ public class UsuarioController : Controller _context.Usuarios.Add(usuario); await _context.SaveChangesAsync(); + // Assign Roles + if (model.SelectedRoles != null) + { + foreach (var roleId in model.SelectedRoles) + { + _context.RolesUsuario.Add(new RolUsuario + { + UsuarioId = usuario.Id, + RolId = roleId, + AsignadoEn = DateTime.UtcNow + }); + } + await _context.SaveChangesAsync(); + } + await transaction.CommitAsync(); return RedirectToAction(nameof(Index)); } @@ -94,6 +116,7 @@ public class UsuarioController : Controller ModelState.AddModelError("", "Ocurrió un error al crear el usuario."); } } + ViewBag.Roles = await _context.RolesSistema.ToListAsync(); return View(model); } @@ -104,6 +127,7 @@ public class UsuarioController : Controller var usuario = await _context.Usuarios .Include(u => u.Persona) + .Include(u => u.RolesUsuario) .FirstOrDefaultAsync(u => u.Id == id); if (usuario == null) return NotFound(); @@ -116,9 +140,11 @@ public class UsuarioController : Controller NombreUsuario = usuario.NombreUsuario, Email = usuario.Email, Telefono = usuario.Persona.Telefono, - Activo = usuario.Activo + Activo = usuario.Activo, + SelectedRoles = usuario.RolesUsuario.Select(ru => ru.RolId).ToList() }; + ViewBag.Roles = await _context.RolesSistema.ToListAsync(); return View(model); } @@ -133,10 +159,12 @@ public class UsuarioController : Controller { var usuario = await _context.Usuarios .Include(u => u.Persona) + .Include(u => u.RolesUsuario) .FirstOrDefaultAsync(u => u.Id == id); if (usuario == null) return NotFound(); + using var transaction = await _context.Database.BeginTransactionAsync(); try { // Update Persona @@ -151,21 +179,37 @@ public class UsuarioController : Controller usuario.Activo = model.Activo; usuario.ActualizadoEn = DateTime.UtcNow; - // Update password if provided if (!string.IsNullOrEmpty(model.Contrasena)) { usuario.HashContrasena = BCrypt.Net.BCrypt.HashPassword(model.Contrasena); } + // Update Roles + _context.RolesUsuario.RemoveRange(usuario.RolesUsuario); + if (model.SelectedRoles != null) + { + foreach (var roleId in model.SelectedRoles) + { + _context.RolesUsuario.Add(new RolUsuario + { + UsuarioId = usuario.Id, + RolId = roleId, + AsignadoEn = DateTime.UtcNow + }); + } + } + await _context.SaveChangesAsync(); + await transaction.CommitAsync(); return RedirectToAction(nameof(Index)); } - catch (DbUpdateConcurrencyException) + catch (Exception) { - if (!UsuarioExists(model.Id.Value)) return NotFound(); - else throw; + await transaction.RollbackAsync(); + ModelState.AddModelError("", "Ocurrió un error al actualizar el usuario."); } } + ViewBag.Roles = await _context.RolesSistema.ToListAsync(); return View(model); } diff --git a/foundation_system/Data/ApplicationDbContext.cs b/foundation_system/Data/ApplicationDbContext.cs index 2e9bbbe..6f0fc73 100644 --- a/foundation_system/Data/ApplicationDbContext.cs +++ b/foundation_system/Data/ApplicationDbContext.cs @@ -14,6 +14,10 @@ public class ApplicationDbContext : DbContext public DbSet Usuarios { get; set; } public DbSet RolesSistema { get; set; } public DbSet RolesUsuario { get; set; } + public DbSet Permisos { get; set; } + public DbSet RolesPermisos { get; set; } + public DbSet Colaboradores { get; set; } + public DbSet AsistenciasColaboradores { get; set; } public DbSet Ninos { get; set; } public DbSet Asistencias { get; set; } public DbSet Configuraciones { get; set; } @@ -38,6 +42,20 @@ public class ApplicationDbContext : DbContext .HasOne(ru => ru.Rol) .WithMany(r => r.RolesUsuario) .HasForeignKey(ru => ru.RolId); + + // Configure composite key for RolPermiso + modelBuilder.Entity() + .HasKey(rp => new { rp.RolId, rp.PermisoId }); + + modelBuilder.Entity() + .HasOne(rp => rp.Rol) + .WithMany(r => r.RolesPermisos) + .HasForeignKey(rp => rp.RolId); + + modelBuilder.Entity() + .HasOne(rp => rp.Permiso) + .WithMany() + .HasForeignKey(rp => rp.PermisoId); modelBuilder.Entity() .HasOne(u => u.Persona) diff --git a/foundation_system/Filters/PermissionAttribute.cs b/foundation_system/Filters/PermissionAttribute.cs new file mode 100644 index 0000000..266cd5d --- /dev/null +++ b/foundation_system/Filters/PermissionAttribute.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using foundation_system.Services; +using System.Security.Claims; + +namespace foundation_system.Filters; + +public class PermissionAttribute : TypeFilterAttribute +{ + public PermissionAttribute(string permissionCode) : base(typeof(PermissionFilter)) + { + Arguments = new object[] { permissionCode }; + } +} + +public class PermissionFilter : IAsyncAuthorizationFilter +{ + private readonly string _permissionCode; + private readonly IAuthService _authService; + + public PermissionFilter(string permissionCode, IAuthService authService) + { + _permissionCode = permissionCode; + _authService = authService; + } + + public async Task OnAuthorizationAsync(AuthorizationFilterContext context) + { + if (!context.HttpContext.User.Identity?.IsAuthenticated ?? true) + { + return; + } + + var userIdClaim = context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier); + if (userIdClaim == null || !long.TryParse(userIdClaim.Value, out var userId)) + { + context.Result = new ForbidResult(); + return; + } + + var hasPermission = await _authService.HasPermissionAsync(userId, _permissionCode); + if (!hasPermission) + { + context.Result = new ForbidResult(); + } + } +} diff --git a/foundation_system/Models/AsistenciaColaborador.cs b/foundation_system/Models/AsistenciaColaborador.cs new file mode 100644 index 0000000..2a3869c --- /dev/null +++ b/foundation_system/Models/AsistenciaColaborador.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace foundation_system.Models; + +[Table("asistencia_colaboradores", Schema = "public")] +public class AsistenciaColaborador +{ + [Key] + [Column("id")] + public long Id { get; set; } + + [Column("colaborador_id")] + public long ColaboradorId { get; set; } + + [ForeignKey("ColaboradorId")] + public virtual Colaborador Colaborador { get; set; } = null!; + + [Column("fecha")] + public DateOnly Fecha { get; set; } + + [Required] + [MaxLength(20)] + [Column("estado")] + public string Estado { get; set; } = "PRESENTE"; + + [Column("observaciones")] + public string? Observaciones { get; set; } + + [Column("creado_en")] + public DateTime CreadoEn { get; set; } = DateTime.UtcNow; +} diff --git a/foundation_system/Models/Colaborador.cs b/foundation_system/Models/Colaborador.cs new file mode 100644 index 0000000..15e78dc --- /dev/null +++ b/foundation_system/Models/Colaborador.cs @@ -0,0 +1,48 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace foundation_system.Models; + +[Table("colaboradores", Schema = "public")] +public class Colaborador +{ + [Key] + [Column("id")] + public long Id { get; set; } + + [Column("persona_id")] + public long PersonaId { get; set; } + + [ForeignKey("PersonaId")] + public virtual Persona Persona { get; set; } = null!; + + [Required] + [MaxLength(100)] + [Column("cargo")] + public string Cargo { get; set; } = string.Empty; + + [Required] + [MaxLength(50)] + [Column("tipo_colaborador")] + public string TipoColaborador { get; set; } = string.Empty; + + [Column("fecha_ingreso")] + public DateOnly FechaIngreso { get; set; } = DateOnly.FromDateTime(DateTime.Today); + + [Column("horario_entrada")] + public TimeSpan? HorarioEntrada { get; set; } + + [Column("horario_salida")] + public TimeSpan? HorarioSalida { get; set; } + + [Column("activo")] + public bool Activo { get; set; } = true; + + [Column("creado_en")] + public DateTime CreadoEn { get; set; } = DateTime.UtcNow; + + [Column("actualizado_en")] + public DateTime ActualizadoEn { get; set; } = DateTime.UtcNow; + + public virtual ICollection Asistencias { get; set; } = new List(); +} diff --git a/foundation_system/Models/Permiso.cs b/foundation_system/Models/Permiso.cs new file mode 100644 index 0000000..11c3b57 --- /dev/null +++ b/foundation_system/Models/Permiso.cs @@ -0,0 +1,45 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace foundation_system.Models; + +[Table("permisos")] +public class Permiso +{ + [Key] + [Column("id")] + public int Id { get; set; } + + [Column("modulo")] + [Required] + [StringLength(50)] + public string Modulo { get; set; } = string.Empty; + + [Column("codigo")] + [Required] + [StringLength(100)] + public string Codigo { get; set; } = string.Empty; + + [Column("nombre")] + [Required] + [StringLength(100)] + public string Nombre { get; set; } = string.Empty; + + [Column("descripcion")] + public string? Descripcion { get; set; } + + [Column("url")] + public string? Url { get; set; } + + [Column("icono")] + public string? Icono { get; set; } + + [Column("orden")] + public int Orden { get; set; } = 0; + + [Column("es_menu")] + public bool EsMenu { get; set; } = true; + + [Column("creado_en")] + public DateTime CreadoEn { get; set; } = DateTime.UtcNow; +} diff --git a/foundation_system/Models/RolPermiso.cs b/foundation_system/Models/RolPermiso.cs new file mode 100644 index 0000000..ed61194 --- /dev/null +++ b/foundation_system/Models/RolPermiso.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace foundation_system.Models; + +[Table("roles_permisos")] +public class RolPermiso +{ + [Column("rol_id")] + public int RolId { get; set; } + + [Column("permiso_id")] + public int PermisoId { get; set; } + + [Column("asignado_en")] + public DateTime AsignadoEn { get; set; } = DateTime.UtcNow; + + // Navigation properties + [ForeignKey("RolId")] + public RolSistema Rol { get; set; } = null!; + + [ForeignKey("PermisoId")] + public Permiso Permiso { get; set; } = null!; +} diff --git a/foundation_system/Models/RolSistema.cs b/foundation_system/Models/RolSistema.cs index f8ba9c8..fff7a49 100644 --- a/foundation_system/Models/RolSistema.cs +++ b/foundation_system/Models/RolSistema.cs @@ -27,4 +27,5 @@ public class RolSistema public DateTime CreadoEn { get; set; } = DateTime.UtcNow; public ICollection RolesUsuario { get; set; } = new List(); + public ICollection RolesPermisos { get; set; } = new List(); } diff --git a/foundation_system/Models/ViewModels/ColaboradorViewModel.cs b/foundation_system/Models/ViewModels/ColaboradorViewModel.cs new file mode 100644 index 0000000..1647ec1 --- /dev/null +++ b/foundation_system/Models/ViewModels/ColaboradorViewModel.cs @@ -0,0 +1,62 @@ +using System.ComponentModel.DataAnnotations; + +namespace foundation_system.Models.ViewModels; + +public class ColaboradorViewModel +{ + public long? Id { get; set; } + public long? PersonaId { get; set; } + + [Required(ErrorMessage = "Los nombres son requeridos")] + [Display(Name = "Nombres")] + public string Nombres { get; set; } = string.Empty; + + [Required(ErrorMessage = "Los apellidos son requeridos")] + [Display(Name = "Apellidos")] + public string Apellidos { get; set; } = string.Empty; + + [Display(Name = "DUI")] + public string? Dui { get; set; } + + [Display(Name = "NIT")] + public string? Nit { get; set; } + + [Display(Name = "Fecha de Nacimiento")] + [DataType(DataType.Date)] + public DateOnly? FechaNacimiento { get; set; } + + [Display(Name = "Género")] + public string? Genero { get; set; } + + [EmailAddress(ErrorMessage = "Email inválido")] + public string? Email { get; set; } + + [Display(Name = "Teléfono")] + public string? Telefono { get; set; } + + [Display(Name = "Dirección")] + public string? Direccion { get; set; } + + [Required(ErrorMessage = "El cargo es requerido")] + [Display(Name = "Cargo")] + public string Cargo { get; set; } = string.Empty; + + [Required(ErrorMessage = "El tipo de colaborador es requerido")] + [Display(Name = "Tipo de Colaborador")] + public string TipoColaborador { get; set; } = string.Empty; + + [Required(ErrorMessage = "La fecha de ingreso es requerida")] + [Display(Name = "Fecha de Ingreso")] + [DataType(DataType.Date)] + public DateOnly FechaIngreso { get; set; } = DateOnly.FromDateTime(DateTime.Today); + + [Display(Name = "Horario de Entrada")] + [DataType(DataType.Time)] + public TimeSpan? HorarioEntrada { get; set; } + + [Display(Name = "Horario de Salida")] + [DataType(DataType.Time)] + public TimeSpan? HorarioSalida { get; set; } + + public bool Activo { get; set; } = true; +} diff --git a/foundation_system/Models/ViewModels/UsuarioViewModel.cs b/foundation_system/Models/ViewModels/UsuarioViewModel.cs index 8e402a9..0716e02 100644 --- a/foundation_system/Models/ViewModels/UsuarioViewModel.cs +++ b/foundation_system/Models/ViewModels/UsuarioViewModel.cs @@ -39,4 +39,7 @@ public class UsuarioViewModel [Display(Name = "Teléfono")] public string? Telefono { get; set; } + + [Display(Name = "Roles Asignados")] + public List SelectedRoles { get; set; } = new List(); } diff --git a/foundation_system/Services/AuthService.cs b/foundation_system/Services/AuthService.cs index 678fee9..cf0a779 100644 --- a/foundation_system/Services/AuthService.cs +++ b/foundation_system/Services/AuthService.cs @@ -145,4 +145,23 @@ public class AuthService : IAuthService await _context.SaveChangesAsync(); } } + + public async Task HasPermissionAsync(long userId, string permissionCode) + { + // ROOT has all permissions + var roles = await GetUserRolesAsync(userId); + if (roles.Contains("ROOT")) return true; + + return 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) + .AnyAsync(p => p.Codigo == permissionCode); + } } diff --git a/foundation_system/Services/IAuthService.cs b/foundation_system/Services/IAuthService.cs index 153af78..d17f266 100644 --- a/foundation_system/Services/IAuthService.cs +++ b/foundation_system/Services/IAuthService.cs @@ -24,4 +24,9 @@ public interface IAuthService /// Updates the last login timestamp /// Task UpdateLastLoginAsync(long userId); + + /// + /// Checks if a user has a specific permission + /// + Task HasPermissionAsync(long userId, string permissionCode); } diff --git a/foundation_system/Views/Asistencia/Index.cshtml b/foundation_system/Views/Asistencia/Index.cshtml index fbf7aad..09e7e23 100644 --- a/foundation_system/Views/Asistencia/Index.cshtml +++ b/foundation_system/Views/Asistencia/Index.cshtml @@ -93,6 +93,9 @@ + @@ -143,8 +146,16 @@ -
@nombreCompleto
-
Edad: @edad años
+
+ + + +
+
@nombreCompleto
+
Edad: @edad años
+
+
@foreach (var dia in Model.DiasDelMes) @@ -650,6 +661,34 @@ window.open(url, '_blank'); }); + $('#btnReporte').click(function () { + var año = $('#selectAnio').val(); + var mes = $('#selectMes').val(); + var diasSemana = $('#diasSemanaInput').val(); + + var url = '@Url.Action("ReporteMensual", "Asistencia")' + + '?año=' + año + + '&mes=' + mes + + '&diasSemana=' + diasSemana; + + window.open(url, '_blank'); + }); + + $('.btn-reporte-individual').click(function () { + var ninoId = $(this).data('id'); + var año = $('#selectAnio').val(); + var mes = $('#selectMes').val(); + var diasSemana = $('#diasSemanaInput').val(); + + var url = '@Url.Action("ReporteIndividual", "Asistencia")' + + '?ninoId=' + ninoId + + '&año=' + año + + '&mes=' + mes + + '&diasSemana=' + diasSemana; + + window.open(url, '_blank'); + }); + $('#selectAnio, #selectMes').change(function () { $('#filtroForm').submit(); }); diff --git a/foundation_system/Views/Asistencia/ReporteIndividual.cshtml b/foundation_system/Views/Asistencia/ReporteIndividual.cshtml new file mode 100644 index 0000000..7358cd9 --- /dev/null +++ b/foundation_system/Views/Asistencia/ReporteIndividual.cshtml @@ -0,0 +1,182 @@ +@model foundation_system.Models.ViewModels.AsistenciaGridViewModel +@{ + Layout = null; + ViewData["Title"] = "Reporte Individual de Asistencia"; + var nino = Model.Expedientes.First(); + + var diasSeleccionadosList = new List(); + if (!string.IsNullOrEmpty(Model.DiasSemanaSeleccionados)) + { + diasSeleccionadosList = Model.DiasSemanaSeleccionados + .Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(d => d.Trim()) + .ToList(); + } + + var diasAMostrar = Model.DiasDelMes + .Where(d => diasSeleccionadosList.Count == 0 || + diasSeleccionadosList.Contains(((int)d.DayOfWeek).ToString())) + .ToList(); +} + + + + + + + @ViewData["Title"] + + + + + +
+
+ + +
+ +
+
+
FUNDACIÓN MIES
+
Registro Individual de Asistencia
+
+
+
PERIODO: @Model.NombreMes @Model.Año
+
Fecha de emisión: @DateTime.Now.ToString("dd/MM/yyyy")
+
+
+ +
+
Información del Niño/a
+
+
+

Nombre Completo: @nino.Persona.Nombres @nino.Persona.Apellidos

+

Código: @nino.CodigoInscripcion

+

Estado: @nino.Estado

+
+
+ @{ + var totalP = 0; var totalT = 0; var totalF = 0; var totalJ = 0; var totalE = 0; + foreach (var dia in diasAMostrar) { + var key = $"{nino.Id}_{dia:yyyy-MM-dd}"; + var estado = Model.Asistencias.ContainsKey(key) ? Model.Asistencias[key] : ""; + switch(estado) { + case "P": totalP++; break; + case "T": totalT++; break; + case "F": totalF++; break; + case "J": totalJ++; break; + case "E": totalE++; break; + } + } + var totalRegistros = totalP + totalT + totalF + totalJ + totalE; + var porcentaje = totalRegistros > 0 ? (totalP * 100.0 / totalRegistros) : 0; + } +
+
Resumen de Asistencia
+
Presentes (P): @totalP
+
Tardes (T): @totalT
+
Faltas (F): @totalF
+
Justificados (J): @totalJ
+
Enfermos (E): @totalE
+
+ Asistencia Efectiva: + @porcentaje.ToString("F1")% +
+
+
+
+
+ +
+
+ + + + + + + + + + @foreach (var dia in diasAMostrar) + { + var key = $"{nino.Id}_{dia:yyyy-MM-dd}"; + var estado = Model.Asistencias.ContainsKey(key) ? Model.Asistencias[key] : ""; + var nombreDia = dia.ToString("dddd", new System.Globalization.CultureInfo("es-ES")); + var esFinDeSemana = dia.DayOfWeek == DayOfWeek.Saturday || dia.DayOfWeek == DayOfWeek.Sunday; + + + + + + + } + +
FechaDíaEstado
@dia.ToString("dd/MM/yyyy")@nombreDia + @if (!string.IsNullOrEmpty(estado)) + { + + @(estado switch { + "P" => "PRESENTE", + "T" => "TARDE", + "F" => "AUSENTE", + "J" => "JUSTIFICADO", + "E" => "ENFERMO", + _ => "" + }) + + } + else + { + - Sin registro - + } +
+
+
+ +
+
+
+
Firma del Responsable
+
Control de Asistencia
+
+
+
+
Sello de la Institución
+
+
+
+ + diff --git a/foundation_system/Views/Asistencia/ReporteMensual.cshtml b/foundation_system/Views/Asistencia/ReporteMensual.cshtml new file mode 100644 index 0000000..9823011 --- /dev/null +++ b/foundation_system/Views/Asistencia/ReporteMensual.cshtml @@ -0,0 +1,174 @@ +@model foundation_system.Models.ViewModels.AsistenciaGridViewModel +@{ + Layout = null; + ViewData["Title"] = "Reporte Mensual de Asistencia"; + + var diasSeleccionadosList = new List(); + if (!string.IsNullOrEmpty(Model.DiasSemanaSeleccionados)) + { + diasSeleccionadosList = Model.DiasSemanaSeleccionados + .Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(d => d.Trim()) + .ToList(); + } + + var diasAMostrar = Model.DiasDelMes + .Where(d => diasSeleccionadosList.Count == 0 || + diasSeleccionadosList.Contains(((int)d.DayOfWeek).ToString())) + .ToList(); +} + + + + + + + @ViewData["Title"] + + + + + +
+
+ + +
+ +
+
+
FUNDACIÓN MIES
+
Reporte Mensual de Asistencia
+
+
+
MES: @Model.NombreMes @Model.Año
+
Generado el: @DateTime.Now.ToString("dd/MM/yyyy HH:mm")
+
+
+ +
+ + + + + + + + + @foreach (var dia in diasAMostrar) + { + + } + + + + + + + + + @foreach (var nino in Model.Expedientes) + { + var totalP = 0; + var totalT = 0; + var totalF = 0; + var totalJ = 0; + var totalE = 0; + + + + @foreach (var dia in diasAMostrar) + { + var key = $"{nino.Id}_{dia:yyyy-MM-dd}"; + var estado = Model.Asistencias.ContainsKey(key) ? Model.Asistencias[key] : ""; + + switch(estado) { + case "P": totalP++; break; + case "T": totalT++; break; + case "F": totalF++; break; + case "J": totalJ++; break; + case "E": totalE++; break; + } + + + } + + + + + + + } + + + + + @foreach (var dia in diasAMostrar) + { + var totalDia = 0; + foreach (var nino in Model.Expedientes) + { + var key = $"{nino.Id}_{dia:yyyy-MM-dd}"; + if (Model.Asistencias.ContainsKey(key) && !string.IsNullOrEmpty(Model.Asistencias[key])) + { + totalDia++; + } + } + + } + + + +
Nombre del Niño/aDías del MesTotales
@dia.DayPTFJE
@nino.Persona.Nombres @nino.Persona.Apellidos@estado@totalP@totalT@totalF@totalJ@totalE
TOTALES POR DÍA@totalDia
+
+ +
+
+
+
Elaborado por
+
+
+
+
Revisado por
+
+
+
+
Sello de la Institución
+
+
+ +
+

Leyenda: P: Presente, T: Tarde, F: Falta, J: Justificado, E: Enfermo

+
+
+ + diff --git a/foundation_system/Views/Colaborador/Create.cshtml b/foundation_system/Views/Colaborador/Create.cshtml new file mode 100644 index 0000000..3c2ea40 --- /dev/null +++ b/foundation_system/Views/Colaborador/Create.cshtml @@ -0,0 +1,123 @@ +@model foundation_system.Models.ViewModels.ColaboradorViewModel + +@{ + ViewData["Title"] = "Nuevo Colaborador"; +} + +
+
+
+
+
+
@ViewData["Title"]
+
+
+
+
+ +
Información Personal
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ +
Información Laboral
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ +
+ + Volver + + +
+
+
+
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/foundation_system/Views/Colaborador/Edit.cshtml b/foundation_system/Views/Colaborador/Edit.cshtml new file mode 100644 index 0000000..0c44b07 --- /dev/null +++ b/foundation_system/Views/Colaborador/Edit.cshtml @@ -0,0 +1,130 @@ +@model foundation_system.Models.ViewModels.ColaboradorViewModel + +@{ + ViewData["Title"] = "Editar Colaborador"; +} + +
+
+
+
+
+
@ViewData["Title"]
+
+
+
+ + +
+ +
Información Personal
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ +
Información Laboral
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+ + +
+
+
+ +
+ + Volver + + +
+
+
+
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/foundation_system/Views/Colaborador/Index.cshtml b/foundation_system/Views/Colaborador/Index.cshtml new file mode 100644 index 0000000..953ca74 --- /dev/null +++ b/foundation_system/Views/Colaborador/Index.cshtml @@ -0,0 +1,106 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Gestión de Colaboradores"; +} + +
+
+

@ViewData["Title"]

+ + Nuevo Colaborador + +
+ +
+
+
+ + + + + + + + + + + + + + @foreach (var item in Model) + { + + + + + + + + + + } + +
Nombre CompletoCargoTipoDUITeléfonoEstadoAcciones
+
+
+ @item.Persona.Nombres.Substring(0, 1)@item.Persona.Apellidos.Substring(0, 1) +
+
+
@item.Persona.Nombres @item.Persona.Apellidos
+ @item.Persona.Email +
+
+
@item.Cargo@item.TipoColaborador@item.Persona.Dui@item.Persona.Telefono + @if (item.Activo) + { + Activo + } + else + { + Inactivo + } + +
+ + + + +
+
+
+
+
+
+ + + + + +@section Scripts { + +} diff --git a/foundation_system/Views/ColaboradorAsistencia/Index.cshtml b/foundation_system/Views/ColaboradorAsistencia/Index.cshtml new file mode 100644 index 0000000..524a8f6 --- /dev/null +++ b/foundation_system/Views/ColaboradorAsistencia/Index.cshtml @@ -0,0 +1,116 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Asistencia de Colaboradores"; + var selectedDate = (DateOnly)ViewBag.SelectedDate; + var asistencias = (Dictionary)ViewBag.Asistencias; +} + +
+
+
+
@ViewData["Title"]
+
+ + +
+
+
+
+ + + + + + + + + + + @foreach (var item in Model) + { + var asistencia = asistencias.ContainsKey(item.Id) ? asistencias[item.Id] : null; + var estadoActual = asistencia?.Estado ?? ""; + + + + + + + + } + +
ColaboradorCargoEstado de AsistenciaObservaciones
+
@item.Persona.Nombres @item.Persona.Apellidos
+ @item.HorarioEntrada?.ToString(@"hh\:mm") - @item.HorarioSalida?.ToString(@"hh\:mm") +
@item.Cargo +
+ + + + + + + + + + + +
+
+ +
+
+
+
+
+ +@section Scripts { + +} + +@Html.AntiForgeryToken() diff --git a/foundation_system/Views/Permiso/Create.cshtml b/foundation_system/Views/Permiso/Create.cshtml new file mode 100644 index 0000000..811906b --- /dev/null +++ b/foundation_system/Views/Permiso/Create.cshtml @@ -0,0 +1,86 @@ +@model foundation_system.Models.Permiso + +@{ + ViewData["Title"] = "Nueva Opción de Menú"; +} + +
+
+
+
+
+
@ViewData["Title"]
+
+
+
+
+ +
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+
+ + +
+
+ +
+ + + +
+
+ +
+ + Volver + + +
+
+
+
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/foundation_system/Views/Permiso/Edit.cshtml b/foundation_system/Views/Permiso/Edit.cshtml new file mode 100644 index 0000000..928a810 --- /dev/null +++ b/foundation_system/Views/Permiso/Edit.cshtml @@ -0,0 +1,87 @@ +@model foundation_system.Models.Permiso + +@{ + ViewData["Title"] = "Editar Opción de Menú"; +} + +
+
+
+
+
+
@ViewData["Title"]
+
+
+
+ +
+ +
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+
+ + +
+
+ +
+ + + +
+
+ +
+ + Volver + + +
+
+
+
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/foundation_system/Views/Permiso/Index.cshtml b/foundation_system/Views/Permiso/Index.cshtml new file mode 100644 index 0000000..8fde6ab --- /dev/null +++ b/foundation_system/Views/Permiso/Index.cshtml @@ -0,0 +1,90 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Gestión de Menú y Permisos"; +} + +
+
+

@ViewData["Title"]

+ + Nueva Opción + +
+ + @if (TempData["ErrorMessage"] != null) + { + + } + +
+
+
+ + + + + + + + + + + + + + @foreach (var item in Model) + { + + + + + + + + + + } + +
OrdenMóduloNombreRuta (URL)IconoEs MenúAcciones
@item.Orden@item.Modulo@item.Nombre@item.Url@item.Icono + @if (item.EsMenu) + { + + } + else + { + + } + +
+ + + + +
+
+
+
+
+
+ + + +@section Scripts { + +} diff --git a/foundation_system/Views/Rol/Create.cshtml b/foundation_system/Views/Rol/Create.cshtml new file mode 100644 index 0000000..3595e62 --- /dev/null +++ b/foundation_system/Views/Rol/Create.cshtml @@ -0,0 +1,55 @@ +@model foundation_system.Models.RolSistema + +@{ + ViewData["Title"] = "Nuevo Rol"; +} + +
+
+
+
+
+
@ViewData["Title"]
+
+
+
+
+ +
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+ +
+ + Volver al listado + + +
+
+
+
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/foundation_system/Views/Rol/Edit.cshtml b/foundation_system/Views/Rol/Edit.cshtml new file mode 100644 index 0000000..8aa81d0 --- /dev/null +++ b/foundation_system/Views/Rol/Edit.cshtml @@ -0,0 +1,57 @@ +@model foundation_system.Models.RolSistema + +@{ + ViewData["Title"] = "Editar Rol"; +} + +
+
+
+
+
+
@ViewData["Title"]
+
+
+
+ +
+ +
+
+ + + +
El código no se puede modificar.
+
+ +
+ + + +
+ +
+ + + +
+
+ +
+ + Volver al listado + + +
+
+
+
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/foundation_system/Views/Rol/Index.cshtml b/foundation_system/Views/Rol/Index.cshtml new file mode 100644 index 0000000..d08d425 --- /dev/null +++ b/foundation_system/Views/Rol/Index.cshtml @@ -0,0 +1,90 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Gestión de Roles"; +} + +
+
+

@ViewData["Title"]

+ + Nuevo Rol + +
+ + @if (TempData["SuccessMessage"] != null) + { + + } + + @if (TempData["ErrorMessage"] != null) + { + + } + +
+
+
+ + + + + + + + + + + + @foreach (var item in Model) + { + + + + + + + + } + +
CódigoNombreDescripciónPermisosAcciones
@item.Codigo@item.Nombre@item.Descripcion + @item.RolesPermisos.Count asignados + +
+ + + + + + + +
+
+
+
+
+
+ + + +@section Scripts { + +} diff --git a/foundation_system/Views/Rol/Permissions.cshtml b/foundation_system/Views/Rol/Permissions.cshtml new file mode 100644 index 0000000..cd15843 --- /dev/null +++ b/foundation_system/Views/Rol/Permissions.cshtml @@ -0,0 +1,62 @@ +@model IEnumerable + +@{ + var rol = ViewBag.Rol as foundation_system.Models.RolSistema; + var assignedCodes = ViewBag.AssignedControllerCodes as List; + ViewData["Title"] = "Gestionar Acceso a Menú: " + rol?.Nombre; +} + +
+
+
+
@ViewData["Title"]
+ @rol?.Codigo +
+
+

Seleccione las opciones del menú que estarán disponibles para este rol.

+ +
+ + + @{ + var groupedControllers = Model.GroupBy(c => c.Modulo); + } + +
+ @foreach (var group in groupedControllers) + { +
+
+
+
@group.Key
+
+
+ @foreach (var controller in group) + { +
+ + +
+ } +
+
+
+ } +
+ +
+ + Cancelar + + +
+
+
+
+
diff --git a/foundation_system/Views/Shared/Components/Menu/Default.cshtml b/foundation_system/Views/Shared/Components/Menu/Default.cshtml new file mode 100644 index 0000000..8d99ec6 --- /dev/null +++ b/foundation_system/Views/Shared/Components/Menu/Default.cshtml @@ -0,0 +1,23 @@ +@model IEnumerable + + diff --git a/foundation_system/Views/Shared/_Layout.cshtml b/foundation_system/Views/Shared/_Layout.cshtml index 9c6fc5a..9df45d3 100644 --- a/foundation_system/Views/Shared/_Layout.cshtml +++ b/foundation_system/Views/Shared/_Layout.cshtml @@ -24,30 +24,8 @@ MIES -