Permisos y roles
This commit is contained in:
52
foundation_system/Components/MenuViewComponent.cs
Normal file
52
foundation_system/Components/MenuViewComponent.cs
Normal file
@@ -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<IViewComponentResult> InvokeAsync()
|
||||||
|
{
|
||||||
|
var userIdClaim = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier);
|
||||||
|
if (userIdClaim == null || !long.TryParse(userIdClaim.Value, out var userId))
|
||||||
|
{
|
||||||
|
return View(new List<foundation_system.Models.Permiso>());
|
||||||
|
}
|
||||||
|
|
||||||
|
var isRoot = HttpContext.User.IsInRole("ROOT");
|
||||||
|
|
||||||
|
List<foundation_system.Models.Permiso> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using foundation_system.Models.ViewModels;
|
using foundation_system.Models.ViewModels;
|
||||||
using foundation_system.Services;
|
using foundation_system.Services;
|
||||||
|
using foundation_system.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace foundation_system.Controllers;
|
namespace foundation_system.Controllers;
|
||||||
|
|
||||||
@@ -12,11 +14,13 @@ public class AccountController : Controller
|
|||||||
{
|
{
|
||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
private readonly ILogger<AccountController> _logger;
|
private readonly ILogger<AccountController> _logger;
|
||||||
|
private readonly ApplicationDbContext _context;
|
||||||
|
|
||||||
public AccountController(IAuthService authService, ILogger<AccountController> logger)
|
public AccountController(IAuthService authService, ILogger<AccountController> logger, ApplicationDbContext context)
|
||||||
{
|
{
|
||||||
_authService = authService;
|
_authService = authService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /Account/Login
|
// GET: /Account/Login
|
||||||
@@ -72,6 +76,20 @@ public class AccountController : Controller
|
|||||||
claims.Add(new Claim(ClaimTypes.Role, role));
|
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);
|
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
|
||||||
var authProperties = new AuthenticationProperties
|
var authProperties = new AuthenticationProperties
|
||||||
|
|||||||
@@ -209,6 +209,102 @@ public class AsistenciaController : Controller
|
|||||||
return Json(new { success = true, message = "Cambios guardados correctamente" });
|
return Json(new { success = true, message = "Cambios guardados correctamente" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> 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<IActionResult> 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> { 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]
|
[HttpGet]
|
||||||
public IActionResult ExportarExcel(int año, int mes, string diasSemana)
|
public IActionResult ExportarExcel(int año, int mes, string diasSemana)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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<IActionResult> 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<IActionResult> 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
192
foundation_system/Controllers/ColaboradorController.cs
Normal file
192
foundation_system/Controllers/ColaboradorController.cs
Normal file
@@ -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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,11 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using foundation_system.Data;
|
using foundation_system.Data;
|
||||||
using foundation_system.Models;
|
using foundation_system.Models;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
namespace foundation_system.Controllers;
|
namespace foundation_system.Controllers;
|
||||||
|
|
||||||
|
[Authorize(Roles = "ROOT,SUPERADMIN")]
|
||||||
public class ConfiguracionController : Controller
|
public class ConfiguracionController : Controller
|
||||||
{
|
{
|
||||||
private readonly ApplicationDbContext _context;
|
private readonly ApplicationDbContext _context;
|
||||||
|
|||||||
111
foundation_system/Controllers/PermisoController.cs
Normal file
111
foundation_system/Controllers/PermisoController.cs
Normal file
@@ -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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
192
foundation_system/Controllers/RolController.cs
Normal file
192
foundation_system/Controllers/RolController.cs
Normal file
@@ -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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,11 @@ using foundation_system.Models;
|
|||||||
using foundation_system.Models.ViewModels;
|
using foundation_system.Models.ViewModels;
|
||||||
using BCrypt.Net;
|
using BCrypt.Net;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
namespace foundation_system.Controllers;
|
namespace foundation_system.Controllers;
|
||||||
|
|
||||||
|
[Authorize(Roles = "ROOT,SUPERADMIN")]
|
||||||
public class UsuarioController : Controller
|
public class UsuarioController : Controller
|
||||||
{
|
{
|
||||||
private readonly ApplicationDbContext _context;
|
private readonly ApplicationDbContext _context;
|
||||||
@@ -21,13 +24,16 @@ public class UsuarioController : Controller
|
|||||||
{
|
{
|
||||||
var usuarios = await _context.Usuarios
|
var usuarios = await _context.Usuarios
|
||||||
.Include(u => u.Persona)
|
.Include(u => u.Persona)
|
||||||
|
.Include(u => u.RolesUsuario)
|
||||||
|
.ThenInclude(ru => ru.Rol)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
return View(usuarios);
|
return View(usuarios);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: Usuario/Create
|
// GET: Usuario/Create
|
||||||
public IActionResult Create()
|
public async Task<IActionResult> Create()
|
||||||
{
|
{
|
||||||
|
ViewBag.Roles = await _context.RolesSistema.ToListAsync();
|
||||||
return View(new UsuarioViewModel());
|
return View(new UsuarioViewModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,16 +49,17 @@ public class UsuarioController : Controller
|
|||||||
|
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
// Check if username or email already exists
|
|
||||||
if (await _context.Usuarios.AnyAsync(u => u.NombreUsuario == model.NombreUsuario))
|
if (await _context.Usuarios.AnyAsync(u => u.NombreUsuario == model.NombreUsuario))
|
||||||
{
|
{
|
||||||
ModelState.AddModelError("NombreUsuario", "El nombre de usuario ya está en uso");
|
ModelState.AddModelError("NombreUsuario", "El nombre de usuario ya está en uso");
|
||||||
|
ViewBag.Roles = await _context.RolesSistema.ToListAsync();
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await _context.Usuarios.AnyAsync(u => u.Email == model.Email))
|
if (await _context.Usuarios.AnyAsync(u => u.Email == model.Email))
|
||||||
{
|
{
|
||||||
ModelState.AddModelError("Email", "El correo electrónico ya está en uso");
|
ModelState.AddModelError("Email", "El correo electrónico ya está en uso");
|
||||||
|
ViewBag.Roles = await _context.RolesSistema.ToListAsync();
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +92,21 @@ public class UsuarioController : Controller
|
|||||||
_context.Usuarios.Add(usuario);
|
_context.Usuarios.Add(usuario);
|
||||||
await _context.SaveChangesAsync();
|
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();
|
await transaction.CommitAsync();
|
||||||
return RedirectToAction(nameof(Index));
|
return RedirectToAction(nameof(Index));
|
||||||
}
|
}
|
||||||
@@ -94,6 +116,7 @@ public class UsuarioController : Controller
|
|||||||
ModelState.AddModelError("", "Ocurrió un error al crear el usuario.");
|
ModelState.AddModelError("", "Ocurrió un error al crear el usuario.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ViewBag.Roles = await _context.RolesSistema.ToListAsync();
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +127,7 @@ public class UsuarioController : Controller
|
|||||||
|
|
||||||
var usuario = await _context.Usuarios
|
var usuario = await _context.Usuarios
|
||||||
.Include(u => u.Persona)
|
.Include(u => u.Persona)
|
||||||
|
.Include(u => u.RolesUsuario)
|
||||||
.FirstOrDefaultAsync(u => u.Id == id);
|
.FirstOrDefaultAsync(u => u.Id == id);
|
||||||
|
|
||||||
if (usuario == null) return NotFound();
|
if (usuario == null) return NotFound();
|
||||||
@@ -116,9 +140,11 @@ public class UsuarioController : Controller
|
|||||||
NombreUsuario = usuario.NombreUsuario,
|
NombreUsuario = usuario.NombreUsuario,
|
||||||
Email = usuario.Email,
|
Email = usuario.Email,
|
||||||
Telefono = usuario.Persona.Telefono,
|
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);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,10 +159,12 @@ public class UsuarioController : Controller
|
|||||||
{
|
{
|
||||||
var usuario = await _context.Usuarios
|
var usuario = await _context.Usuarios
|
||||||
.Include(u => u.Persona)
|
.Include(u => u.Persona)
|
||||||
|
.Include(u => u.RolesUsuario)
|
||||||
.FirstOrDefaultAsync(u => u.Id == id);
|
.FirstOrDefaultAsync(u => u.Id == id);
|
||||||
|
|
||||||
if (usuario == null) return NotFound();
|
if (usuario == null) return NotFound();
|
||||||
|
|
||||||
|
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Update Persona
|
// Update Persona
|
||||||
@@ -151,21 +179,37 @@ public class UsuarioController : Controller
|
|||||||
usuario.Activo = model.Activo;
|
usuario.Activo = model.Activo;
|
||||||
usuario.ActualizadoEn = DateTime.UtcNow;
|
usuario.ActualizadoEn = DateTime.UtcNow;
|
||||||
|
|
||||||
// Update password if provided
|
|
||||||
if (!string.IsNullOrEmpty(model.Contrasena))
|
if (!string.IsNullOrEmpty(model.Contrasena))
|
||||||
{
|
{
|
||||||
usuario.HashContrasena = BCrypt.Net.BCrypt.HashPassword(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 _context.SaveChangesAsync();
|
||||||
|
await transaction.CommitAsync();
|
||||||
return RedirectToAction(nameof(Index));
|
return RedirectToAction(nameof(Index));
|
||||||
}
|
}
|
||||||
catch (DbUpdateConcurrencyException)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
if (!UsuarioExists(model.Id.Value)) return NotFound();
|
await transaction.RollbackAsync();
|
||||||
else throw;
|
ModelState.AddModelError("", "Ocurrió un error al actualizar el usuario.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ViewBag.Roles = await _context.RolesSistema.ToListAsync();
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ public class ApplicationDbContext : DbContext
|
|||||||
public DbSet<Usuario> Usuarios { get; set; }
|
public DbSet<Usuario> Usuarios { get; set; }
|
||||||
public DbSet<RolSistema> RolesSistema { get; set; }
|
public DbSet<RolSistema> RolesSistema { get; set; }
|
||||||
public DbSet<RolUsuario> RolesUsuario { get; set; }
|
public DbSet<RolUsuario> RolesUsuario { get; set; }
|
||||||
|
public DbSet<Permiso> Permisos { get; set; }
|
||||||
|
public DbSet<RolPermiso> RolesPermisos { get; set; }
|
||||||
|
public DbSet<Colaborador> Colaboradores { get; set; }
|
||||||
|
public DbSet<AsistenciaColaborador> AsistenciasColaboradores { get; set; }
|
||||||
public DbSet<Nino> Ninos { get; set; }
|
public DbSet<Nino> Ninos { get; set; }
|
||||||
public DbSet<Asistencia> Asistencias { get; set; }
|
public DbSet<Asistencia> Asistencias { get; set; }
|
||||||
public DbSet<ConfiguracionSistema> Configuraciones { get; set; }
|
public DbSet<ConfiguracionSistema> Configuraciones { get; set; }
|
||||||
@@ -39,6 +43,20 @@ public class ApplicationDbContext : DbContext
|
|||||||
.WithMany(r => r.RolesUsuario)
|
.WithMany(r => r.RolesUsuario)
|
||||||
.HasForeignKey(ru => ru.RolId);
|
.HasForeignKey(ru => ru.RolId);
|
||||||
|
|
||||||
|
// Configure composite key for RolPermiso
|
||||||
|
modelBuilder.Entity<RolPermiso>()
|
||||||
|
.HasKey(rp => new { rp.RolId, rp.PermisoId });
|
||||||
|
|
||||||
|
modelBuilder.Entity<RolPermiso>()
|
||||||
|
.HasOne(rp => rp.Rol)
|
||||||
|
.WithMany(r => r.RolesPermisos)
|
||||||
|
.HasForeignKey(rp => rp.RolId);
|
||||||
|
|
||||||
|
modelBuilder.Entity<RolPermiso>()
|
||||||
|
.HasOne(rp => rp.Permiso)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(rp => rp.PermisoId);
|
||||||
|
|
||||||
modelBuilder.Entity<Usuario>()
|
modelBuilder.Entity<Usuario>()
|
||||||
.HasOne(u => u.Persona)
|
.HasOne(u => u.Persona)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
|
|||||||
47
foundation_system/Filters/PermissionAttribute.cs
Normal file
47
foundation_system/Filters/PermissionAttribute.cs
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
foundation_system/Models/AsistenciaColaborador.cs
Normal file
32
foundation_system/Models/AsistenciaColaborador.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
48
foundation_system/Models/Colaborador.cs
Normal file
48
foundation_system/Models/Colaborador.cs
Normal file
@@ -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<AsistenciaColaborador> Asistencias { get; set; } = new List<AsistenciaColaborador>();
|
||||||
|
}
|
||||||
45
foundation_system/Models/Permiso.cs
Normal file
45
foundation_system/Models/Permiso.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
24
foundation_system/Models/RolPermiso.cs
Normal file
24
foundation_system/Models/RolPermiso.cs
Normal file
@@ -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!;
|
||||||
|
}
|
||||||
@@ -27,4 +27,5 @@ public class RolSistema
|
|||||||
public DateTime CreadoEn { get; set; } = DateTime.UtcNow;
|
public DateTime CreadoEn { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
public ICollection<RolUsuario> RolesUsuario { get; set; } = new List<RolUsuario>();
|
public ICollection<RolUsuario> RolesUsuario { get; set; } = new List<RolUsuario>();
|
||||||
|
public ICollection<RolPermiso> RolesPermisos { get; set; } = new List<RolPermiso>();
|
||||||
}
|
}
|
||||||
|
|||||||
62
foundation_system/Models/ViewModels/ColaboradorViewModel.cs
Normal file
62
foundation_system/Models/ViewModels/ColaboradorViewModel.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -39,4 +39,7 @@ public class UsuarioViewModel
|
|||||||
|
|
||||||
[Display(Name = "Teléfono")]
|
[Display(Name = "Teléfono")]
|
||||||
public string? Telefono { get; set; }
|
public string? Telefono { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Roles Asignados")]
|
||||||
|
public List<int> SelectedRoles { get; set; } = new List<int>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,4 +145,23 @@ public class AuthService : IAuthService
|
|||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,9 @@ public interface IAuthService
|
|||||||
/// Updates the last login timestamp
|
/// Updates the last login timestamp
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task UpdateLastLoginAsync(long userId);
|
Task UpdateLastLoginAsync(long userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a user has a specific permission
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> HasPermissionAsync(long userId, string permissionCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,9 @@
|
|||||||
<button class="btn btn-light btn-sm" id="btnExportar">
|
<button class="btn btn-light btn-sm" id="btnExportar">
|
||||||
<i class="bi bi-file-earmark-excel"></i> Exportar
|
<i class="bi bi-file-earmark-excel"></i> Exportar
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-info btn-sm" id="btnReporte">
|
||||||
|
<i class="bi bi-file-earmark-pdf"></i> Reporte Mensual
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -143,8 +146,16 @@
|
|||||||
|
|
||||||
<tr data-expediente-id="@expediente.Id">
|
<tr data-expediente-id="@expediente.Id">
|
||||||
<td class="sticky-left bg-white" style="min-width: 200px;">
|
<td class="sticky-left bg-white" style="min-width: 200px;">
|
||||||
<div class="fw-bold">@nombreCompleto</div>
|
<div class="d-flex align-items-center">
|
||||||
<div class="small text-muted">Edad: @edad años</div>
|
<a href="javascript:void(0)" class="btn-reporte-individual text-decoration-none"
|
||||||
|
data-id="@expediente.Id" title="Ver reporte individual">
|
||||||
|
<i class="bi bi-file-earmark-person text-info me-2"></i>
|
||||||
|
</a>
|
||||||
|
<div>
|
||||||
|
<div class="fw-bold">@nombreCompleto</div>
|
||||||
|
<div class="small text-muted">Edad: @edad años</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@foreach (var dia in Model.DiasDelMes)
|
@foreach (var dia in Model.DiasDelMes)
|
||||||
@@ -650,6 +661,34 @@
|
|||||||
window.open(url, '_blank');
|
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 () {
|
$('#selectAnio, #selectMes').change(function () {
|
||||||
$('#filtroForm').submit();
|
$('#filtroForm').submit();
|
||||||
});
|
});
|
||||||
|
|||||||
182
foundation_system/Views/Asistencia/ReporteIndividual.cshtml
Normal file
182
foundation_system/Views/Asistencia/ReporteIndividual.cshtml
Normal file
@@ -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<string>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>@ViewData["Title"]</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" />
|
||||||
|
<style>
|
||||||
|
@@media print {
|
||||||
|
.no-print { display: none !important; }
|
||||||
|
body { padding: 0; margin: 0; }
|
||||||
|
.container { width: 100%; max-width: none; padding: 0; }
|
||||||
|
@@page { size: portrait; margin: 1.5cm; }
|
||||||
|
}
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: white; color: #333; }
|
||||||
|
.report-header { border-bottom: 3px solid #2c3e50; margin-bottom: 30px; padding-bottom: 15px; }
|
||||||
|
.foundation-name { font-size: 28px; font-weight: bold; color: #2c3e50; }
|
||||||
|
.report-title { font-size: 20px; color: #7f8c8d; text-transform: uppercase; letter-spacing: 1px; }
|
||||||
|
.info-card { background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; margin-bottom: 30px; }
|
||||||
|
.info-label { font-weight: bold; color: #2c3e50; width: 150px; display: inline-block; }
|
||||||
|
.attendance-table { border: 1px solid #dee2e6; }
|
||||||
|
.attendance-table th { background-color: #2c3e50; color: white; text-align: center; }
|
||||||
|
.attendance-table td { text-align: center; vertical-align: middle; }
|
||||||
|
.state-badge { padding: 5px 10px; border-radius: 4px; font-weight: bold; font-size: 0.9rem; }
|
||||||
|
.bg-P { background-color: #d1e7dd; color: #0f5132; }
|
||||||
|
.bg-F { background-color: #f8d7da; color: #842029; }
|
||||||
|
.bg-T { background-color: #fff3cd; color: #664d03; }
|
||||||
|
.bg-J { background-color: #cff4fc; color: #055160; }
|
||||||
|
.bg-E { background-color: #e2e3e5; color: #41464b; }
|
||||||
|
.summary-box { border: 2px solid #2c3e50; border-radius: 8px; padding: 15px; }
|
||||||
|
.summary-item { display: flex; justify-content: space-between; margin-bottom: 5px; border-bottom: 1px dashed #dee2e6; }
|
||||||
|
.summary-item:last-child { border-bottom: none; }
|
||||||
|
.signature-section { margin-top: 80px; }
|
||||||
|
.signature-line { border-top: 1px solid #000; width: 250px; margin: 0 auto; margin-top: 50px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="no-print mb-4 text-end">
|
||||||
|
<button onclick="window.print()" class="btn btn-primary shadow-sm">
|
||||||
|
<i class="bi bi-printer"></i> Imprimir Reporte
|
||||||
|
</button>
|
||||||
|
<button onclick="window.close()" class="btn btn-outline-secondary shadow-sm">
|
||||||
|
<i class="bi bi-x-lg"></i> Cerrar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="report-header d-flex justify-content-between align-items-end">
|
||||||
|
<div>
|
||||||
|
<div class="foundation-name">FUNDACIÓN MIES</div>
|
||||||
|
<div class="report-title">Registro Individual de Asistencia</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-end">
|
||||||
|
<div class="h5 mb-0">PERIODO: @Model.NombreMes @Model.Año</div>
|
||||||
|
<div class="small text-muted">Fecha de emisión: @DateTime.Now.ToString("dd/MM/yyyy")</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-card shadow-sm">
|
||||||
|
<h5 class="border-bottom pb-2 mb-3"><i class="bi bi-person-badge"></i> Información del Niño/a</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-7">
|
||||||
|
<p><span class="info-label">Nombre Completo:</span> <span class="h6">@nino.Persona.Nombres @nino.Persona.Apellidos</span></p>
|
||||||
|
<p><span class="info-label">Código:</span> <span>@nino.CodigoInscripcion</span></p>
|
||||||
|
<p><span class="info-label">Estado:</span> <span class="badge bg-success">@nino.Estado</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
@{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
<div class="summary-box">
|
||||||
|
<h6 class="text-center mb-3">Resumen de Asistencia</h6>
|
||||||
|
<div class="summary-item"><span>Presentes (P):</span> <strong>@totalP</strong></div>
|
||||||
|
<div class="summary-item"><span>Tardes (T):</span> <strong>@totalT</strong></div>
|
||||||
|
<div class="summary-item"><span>Faltas (F):</span> <strong>@totalF</strong></div>
|
||||||
|
<div class="summary-item"><span>Justificados (J):</span> <strong>@totalJ</strong></div>
|
||||||
|
<div class="summary-item"><span>Enfermos (E):</span> <strong>@totalE</strong></div>
|
||||||
|
<div class="summary-item mt-2 pt-2 border-top border-dark">
|
||||||
|
<span>Asistencia Efectiva:</span>
|
||||||
|
<strong class="@(porcentaje >= 80 ? "text-success" : "text-danger")">@porcentaje.ToString("F1")%</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<table class="table table-bordered attendance-table shadow-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%;">Fecha</th>
|
||||||
|
<th style="width: 20%;">Día</th>
|
||||||
|
<th style="width: 30%;">Estado</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@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;
|
||||||
|
|
||||||
|
<tr class="@(esFinDeSemana ? "bg-light" : "")">
|
||||||
|
<td>@dia.ToString("dd/MM/yyyy")</td>
|
||||||
|
<td class="text-capitalize">@nombreDia</td>
|
||||||
|
<td>
|
||||||
|
@if (!string.IsNullOrEmpty(estado))
|
||||||
|
{
|
||||||
|
<span class="state-badge bg-@estado">
|
||||||
|
@(estado switch {
|
||||||
|
"P" => "PRESENTE",
|
||||||
|
"T" => "TARDE",
|
||||||
|
"F" => "AUSENTE",
|
||||||
|
"J" => "JUSTIFICADO",
|
||||||
|
"E" => "ENFERMO",
|
||||||
|
_ => ""
|
||||||
|
})
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-muted small">- Sin registro -</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row signature-section text-center">
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="signature-line"></div>
|
||||||
|
<div class="mt-2 fw-bold">Firma del Responsable</div>
|
||||||
|
<div class="small text-muted">Control de Asistencia</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="signature-line"></div>
|
||||||
|
<div class="mt-2 fw-bold">Sello de la Institución</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
174
foundation_system/Views/Asistencia/ReporteMensual.cshtml
Normal file
174
foundation_system/Views/Asistencia/ReporteMensual.cshtml
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
@model foundation_system.Models.ViewModels.AsistenciaGridViewModel
|
||||||
|
@{
|
||||||
|
Layout = null;
|
||||||
|
ViewData["Title"] = "Reporte Mensual de Asistencia";
|
||||||
|
|
||||||
|
var diasSeleccionadosList = new List<string>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>@ViewData["Title"]</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" />
|
||||||
|
<style>
|
||||||
|
@@media print {
|
||||||
|
.no-print { display: none !important; }
|
||||||
|
body { padding: 0; margin: 0; }
|
||||||
|
.container-fluid { width: 100%; padding: 0; }
|
||||||
|
.table { font-size: 8pt; }
|
||||||
|
.table th, .table td { padding: 2px !important; }
|
||||||
|
@@page { size: landscape; margin: 1cm; }
|
||||||
|
}
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: white; }
|
||||||
|
.report-header { border-bottom: 2px solid #2c3e50; margin-bottom: 20px; padding-bottom: 10px; }
|
||||||
|
.foundation-name { font-size: 24px; font-weight: bold; color: #2c3e50; }
|
||||||
|
.report-title { font-size: 18px; color: #7f8c8d; }
|
||||||
|
.table-report { border: 1px solid #dee2e6; }
|
||||||
|
.table-report th { background-color: #f8f9fa; text-align: center; vertical-align: middle; border: 1px solid #dee2e6; }
|
||||||
|
.table-report td { text-align: center; vertical-align: middle; border: 1px solid #dee2e6; }
|
||||||
|
.name-column { text-align: left !important; padding-left: 10px !important; min-width: 180px; }
|
||||||
|
.total-column { font-weight: bold; background-color: #f8f9fa; }
|
||||||
|
.signature-section { margin-top: 50px; }
|
||||||
|
.signature-line { border-top: 1px solid #000; width: 200px; margin: 0 auto; margin-top: 40px; }
|
||||||
|
.state-P { color: #198754; font-weight: bold; }
|
||||||
|
.state-F { color: #dc3545; font-weight: bold; }
|
||||||
|
.state-T { color: #ffc107; font-weight: bold; }
|
||||||
|
.state-J { color: #0dcaf0; font-weight: bold; }
|
||||||
|
.state-E { color: #6c757d; font-weight: bold; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid p-4">
|
||||||
|
<div class="no-print mb-4 text-end">
|
||||||
|
<button onclick="window.print()" class="btn btn-primary">
|
||||||
|
<i class="bi bi-printer"></i> Imprimir Reporte
|
||||||
|
</button>
|
||||||
|
<button onclick="window.close()" class="btn btn-secondary">
|
||||||
|
<i class="bi bi-x-lg"></i> Cerrar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="report-header d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<div class="foundation-name">FUNDACIÓN MIES</div>
|
||||||
|
<div class="report-title">Reporte Mensual de Asistencia</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-end">
|
||||||
|
<div class="fw-bold">MES: @Model.NombreMes @Model.Año</div>
|
||||||
|
<div class="small text-muted">Generado el: @DateTime.Now.ToString("dd/MM/yyyy HH:mm")</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-sm table-report">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th rowspan="2" class="name-column">Nombre del Niño/a</th>
|
||||||
|
<th colspan="@diasAMostrar.Count">Días del Mes</th>
|
||||||
|
<th colspan="5">Totales</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
@foreach (var dia in diasAMostrar)
|
||||||
|
{
|
||||||
|
<th>@dia.Day</th>
|
||||||
|
}
|
||||||
|
<th title="Presente">P</th>
|
||||||
|
<th title="Tarde">T</th>
|
||||||
|
<th title="Falta">F</th>
|
||||||
|
<th title="Justificado">J</th>
|
||||||
|
<th title="Enfermo">E</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var nino in Model.Expedientes)
|
||||||
|
{
|
||||||
|
var totalP = 0;
|
||||||
|
var totalT = 0;
|
||||||
|
var totalF = 0;
|
||||||
|
var totalJ = 0;
|
||||||
|
var totalE = 0;
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="name-column">@nino.Persona.Nombres @nino.Persona.Apellidos</td>
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
<td class="state-@estado">@estado</td>
|
||||||
|
}
|
||||||
|
<td class="total-column">@totalP</td>
|
||||||
|
<td class="total-column">@totalT</td>
|
||||||
|
<td class="total-column">@totalF</td>
|
||||||
|
<td class="total-column">@totalJ</td>
|
||||||
|
<td class="total-column">@totalE</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr class="fw-bold">
|
||||||
|
<td class="name-column">TOTALES POR DÍA</td>
|
||||||
|
@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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<td>@totalDia</td>
|
||||||
|
}
|
||||||
|
<td colspan="5" class="bg-light"></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row signature-section text-center">
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="signature-line"></div>
|
||||||
|
<div class="mt-2">Elaborado por</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="signature-line"></div>
|
||||||
|
<div class="mt-2">Revisado por</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="signature-line"></div>
|
||||||
|
<div class="mt-2">Sello de la Institución</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 small text-muted no-print">
|
||||||
|
<p><strong>Leyenda:</strong> P: Presente, T: Tarde, F: Falta, J: Justificado, E: Enfermo</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
123
foundation_system/Views/Colaborador/Create.cshtml
Normal file
123
foundation_system/Views/Colaborador/Create.cshtml
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
@model foundation_system.Models.ViewModels.ColaboradorViewModel
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Nuevo Colaborador";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-person-plus-fill me-2"></i>@ViewData["Title"]</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form asp-action="Create">
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div>
|
||||||
|
|
||||||
|
<h6 class="text-primary border-bottom pb-2 mb-3">Información Personal</h6>
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Nombres" class="form-label"></label>
|
||||||
|
<input asp-for="Nombres" class="form-control" />
|
||||||
|
<span asp-validation-for="Nombres" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Apellidos" class="form-label"></label>
|
||||||
|
<input asp-for="Apellidos" class="form-control" />
|
||||||
|
<span asp-validation-for="Apellidos" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Dui" class="form-label"></label>
|
||||||
|
<input asp-for="Dui" class="form-control" placeholder="00000000-0" />
|
||||||
|
<span asp-validation-for="Dui" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Nit" class="form-label"></label>
|
||||||
|
<input asp-for="Nit" class="form-control" placeholder="0000-000000-000-0" />
|
||||||
|
<span asp-validation-for="Nit" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Genero" class="form-label"></label>
|
||||||
|
<select asp-for="Genero" class="form-select">
|
||||||
|
<option value="">Seleccione...</option>
|
||||||
|
<option value="M">Masculino</option>
|
||||||
|
<option value="F">Femenino</option>
|
||||||
|
<option value="O">Otro</option>
|
||||||
|
</select>
|
||||||
|
<span asp-validation-for="Genero" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="FechaNacimiento" class="form-label"></label>
|
||||||
|
<input asp-for="FechaNacimiento" class="form-control" />
|
||||||
|
<span asp-validation-for="FechaNacimiento" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Telefono" class="form-label"></label>
|
||||||
|
<input asp-for="Telefono" class="form-control" />
|
||||||
|
<span asp-validation-for="Telefono" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Email" class="form-label"></label>
|
||||||
|
<input asp-for="Email" class="form-control" />
|
||||||
|
<span asp-validation-for="Email" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Direccion" class="form-label"></label>
|
||||||
|
<textarea asp-for="Direccion" class="form-control" rows="2"></textarea>
|
||||||
|
<span asp-validation-for="Direccion" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6 class="text-primary border-bottom pb-2 mb-3">Información Laboral</h6>
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Cargo" class="form-label"></label>
|
||||||
|
<input asp-for="Cargo" class="form-control" placeholder="Ej: Maestro, Cocinero, Administrador" />
|
||||||
|
<span asp-validation-for="Cargo" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="TipoColaborador" class="form-label"></label>
|
||||||
|
<select asp-for="TipoColaborador" class="form-select">
|
||||||
|
<option value="Permanente">Permanente</option>
|
||||||
|
<option value="Voluntario">Voluntario</option>
|
||||||
|
<option value="Temporal">Temporal</option>
|
||||||
|
<option value="Por Horas">Por Horas</option>
|
||||||
|
</select>
|
||||||
|
<span asp-validation-for="TipoColaborador" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="FechaIngreso" class="form-label"></label>
|
||||||
|
<input asp-for="FechaIngreso" class="form-control" />
|
||||||
|
<span asp-validation-for="FechaIngreso" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="HorarioEntrada" class="form-label"></label>
|
||||||
|
<input asp-for="HorarioEntrada" class="form-control" type="time" />
|
||||||
|
<span asp-validation-for="HorarioEntrada" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="HorarioSalida" class="form-label"></label>
|
||||||
|
<input asp-for="HorarioSalida" class="form-control" type="time" />
|
||||||
|
<span asp-validation-for="HorarioSalida" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between mt-4">
|
||||||
|
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-arrow-left me-2"></i> Volver
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary px-5">
|
||||||
|
<i class="bi bi-save me-2"></i> Guardar Colaborador
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||||
|
}
|
||||||
130
foundation_system/Views/Colaborador/Edit.cshtml
Normal file
130
foundation_system/Views/Colaborador/Edit.cshtml
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
@model foundation_system.Models.ViewModels.ColaboradorViewModel
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Editar Colaborador";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-pencil-square me-2"></i>@ViewData["Title"]</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form asp-action="Edit">
|
||||||
|
<input type="hidden" asp-for="Id" />
|
||||||
|
<input type="hidden" asp-for="PersonaId" />
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div>
|
||||||
|
|
||||||
|
<h6 class="text-primary border-bottom pb-2 mb-3">Información Personal</h6>
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Nombres" class="form-label"></label>
|
||||||
|
<input asp-for="Nombres" class="form-control" />
|
||||||
|
<span asp-validation-for="Nombres" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Apellidos" class="form-label"></label>
|
||||||
|
<input asp-for="Apellidos" class="form-control" />
|
||||||
|
<span asp-validation-for="Apellidos" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Dui" class="form-label"></label>
|
||||||
|
<input asp-for="Dui" class="form-control" />
|
||||||
|
<span asp-validation-for="Dui" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Nit" class="form-label"></label>
|
||||||
|
<input asp-for="Nit" class="form-control" />
|
||||||
|
<span asp-validation-for="Nit" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Genero" class="form-label"></label>
|
||||||
|
<select asp-for="Genero" class="form-select">
|
||||||
|
<option value="M">Masculino</option>
|
||||||
|
<option value="F">Femenino</option>
|
||||||
|
<option value="O">Otro</option>
|
||||||
|
</select>
|
||||||
|
<span asp-validation-for="Genero" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="FechaNacimiento" class="form-label"></label>
|
||||||
|
<input asp-for="FechaNacimiento" class="form-control" />
|
||||||
|
<span asp-validation-for="FechaNacimiento" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Telefono" class="form-label"></label>
|
||||||
|
<input asp-for="Telefono" class="form-control" />
|
||||||
|
<span asp-validation-for="Telefono" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Email" class="form-label"></label>
|
||||||
|
<input asp-for="Email" class="form-control" />
|
||||||
|
<span asp-validation-for="Email" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Direccion" class="form-label"></label>
|
||||||
|
<textarea asp-for="Direccion" class="form-control" rows="2"></textarea>
|
||||||
|
<span asp-validation-for="Direccion" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6 class="text-primary border-bottom pb-2 mb-3">Información Laboral</h6>
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Cargo" class="form-label"></label>
|
||||||
|
<input asp-for="Cargo" class="form-control" />
|
||||||
|
<span asp-validation-for="Cargo" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="TipoColaborador" class="form-label"></label>
|
||||||
|
<select asp-for="TipoColaborador" class="form-select">
|
||||||
|
<option value="Permanente">Permanente</option>
|
||||||
|
<option value="Voluntario">Voluntario</option>
|
||||||
|
<option value="Temporal">Temporal</option>
|
||||||
|
<option value="Por Horas">Por Horas</option>
|
||||||
|
</select>
|
||||||
|
<span asp-validation-for="TipoColaborador" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="FechaIngreso" class="form-label"></label>
|
||||||
|
<input asp-for="FechaIngreso" class="form-control" />
|
||||||
|
<span asp-validation-for="FechaIngreso" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="HorarioEntrada" class="form-label"></label>
|
||||||
|
<input asp-for="HorarioEntrada" class="form-control" type="time" />
|
||||||
|
<span asp-validation-for="HorarioEntrada" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="HorarioSalida" class="form-label"></label>
|
||||||
|
<input asp-for="HorarioSalida" class="form-control" type="time" />
|
||||||
|
<span asp-validation-for="HorarioSalida" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" asp-for="Activo">
|
||||||
|
<label class="form-check-label" asp-for="Activo">Colaborador Activo</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between mt-4">
|
||||||
|
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-arrow-left me-2"></i> Volver
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary px-5">
|
||||||
|
<i class="bi bi-save me-2"></i> Guardar Cambios
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||||
|
}
|
||||||
106
foundation_system/Views/Colaborador/Index.cshtml
Normal file
106
foundation_system/Views/Colaborador/Index.cshtml
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
@model IEnumerable<foundation_system.Models.Colaborador>
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Gestión de Colaboradores";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="h3 mb-0 text-gray-800"><i class="bi bi-people-fill me-2"></i>@ViewData["Title"]</h2>
|
||||||
|
<a asp-action="Create" class="btn btn-primary">
|
||||||
|
<i class="bi bi-person-plus-fill me-2"></i> Nuevo Colaborador
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle" id="colaboradoresTable">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Nombre Completo</th>
|
||||||
|
<th>Cargo</th>
|
||||||
|
<th>Tipo</th>
|
||||||
|
<th>DUI</th>
|
||||||
|
<th>Teléfono</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
<th class="text-center">Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var item in Model)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="avatar-circle me-3">
|
||||||
|
@item.Persona.Nombres.Substring(0, 1)@item.Persona.Apellidos.Substring(0, 1)
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="fw-bold">@item.Persona.Nombres @item.Persona.Apellidos</div>
|
||||||
|
<small class="text-muted">@item.Persona.Email</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><span class="badge bg-info text-dark">@item.Cargo</span></td>
|
||||||
|
<td>@item.TipoColaborador</td>
|
||||||
|
<td>@item.Persona.Dui</td>
|
||||||
|
<td>@item.Persona.Telefono</td>
|
||||||
|
<td>
|
||||||
|
@if (item.Activo)
|
||||||
|
{
|
||||||
|
<span class="badge bg-success">Activo</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="badge bg-danger">Inactivo</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-outline-primary" title="Editar">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-outline-danger" title="Desactivar"
|
||||||
|
onclick="confirmDelete(@item.Id, '@item.Persona.Nombres @item.Persona.Apellidos')">
|
||||||
|
<i class="bi bi-person-x"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.avatar-circle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<form id="deleteForm" asp-action="Delete" method="post" style="display:none;">
|
||||||
|
<input type="hidden" name="id" id="deleteId" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
function confirmDelete(id, name) {
|
||||||
|
if (confirm(`¿Está seguro de que desea desactivar a "${name}"?`)) {
|
||||||
|
document.getElementById('deleteId').value = id;
|
||||||
|
document.getElementById('deleteForm').submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
116
foundation_system/Views/ColaboradorAsistencia/Index.cshtml
Normal file
116
foundation_system/Views/ColaboradorAsistencia/Index.cshtml
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
@model IEnumerable<foundation_system.Models.Colaborador>
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Asistencia de Colaboradores";
|
||||||
|
var selectedDate = (DateOnly)ViewBag.SelectedDate;
|
||||||
|
var asistencias = (Dictionary<long, foundation_system.Models.AsistenciaColaborador>)ViewBag.Asistencias;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center py-3">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-calendar-check me-2"></i>@ViewData["Title"]</h5>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<label class="me-2 mb-0">Fecha:</label>
|
||||||
|
<input type="date" id="fechaAsistencia" class="form-control form-control-sm"
|
||||||
|
value="@selectedDate.ToString("yyyy-MM-dd")" onchange="changeDate(this.value)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Colaborador</th>
|
||||||
|
<th>Cargo</th>
|
||||||
|
<th class="text-center" style="width: 400px;">Estado de Asistencia</th>
|
||||||
|
<th>Observaciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var item in Model)
|
||||||
|
{
|
||||||
|
var asistencia = asistencias.ContainsKey(item.Id) ? asistencias[item.Id] : null;
|
||||||
|
var estadoActual = asistencia?.Estado ?? "";
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold">@item.Persona.Nombres @item.Persona.Apellidos</div>
|
||||||
|
<small class="text-muted">@item.HorarioEntrada?.ToString(@"hh\:mm") - @item.HorarioSalida?.ToString(@"hh\:mm")</small>
|
||||||
|
</td>
|
||||||
|
<td><span class="badge bg-info text-dark">@item.Cargo</span></td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group w-100" role="group">
|
||||||
|
<input type="radio" class="btn-check" name="estado_@item.Id" id="pres_@item.Id" value="PRESENTE"
|
||||||
|
@(estadoActual == "PRESENTE" ? "checked" : "") onchange="saveAsistencia(@item.Id, 'PRESENTE')">
|
||||||
|
<label class="btn btn-outline-success btn-sm" for="pres_@item.Id">Presente</label>
|
||||||
|
|
||||||
|
<input type="radio" class="btn-check" name="estado_@item.Id" id="aus_@item.Id" value="AUSENTE"
|
||||||
|
@(estadoActual == "AUSENTE" ? "checked" : "") onchange="saveAsistencia(@item.Id, 'AUSENTE')">
|
||||||
|
<label class="btn btn-outline-danger btn-sm" for="aus_@item.Id">Ausente</label>
|
||||||
|
|
||||||
|
<input type="radio" class="btn-check" name="estado_@item.Id" id="just_@item.Id" value="JUSTIFICADO"
|
||||||
|
@(estadoActual == "JUSTIFICADO" ? "checked" : "") onchange="saveAsistencia(@item.Id, 'JUSTIFICADO')">
|
||||||
|
<label class="btn btn-outline-warning btn-sm" for="just_@item.Id">Justif.</label>
|
||||||
|
|
||||||
|
<input type="radio" class="btn-check" name="estado_@item.Id" id="enf_@item.Id" value="ENFERMO"
|
||||||
|
@(estadoActual == "ENFERMO" ? "checked" : "") onchange="saveAsistencia(@item.Id, 'ENFERMO')">
|
||||||
|
<label class="btn btn-outline-info btn-sm" for="enf_@item.Id">Enfermo</label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" class="form-control form-control-sm" id="obs_@item.Id"
|
||||||
|
value="@asistencia?.Observaciones" placeholder="Nota..."
|
||||||
|
onblur="saveAsistencia(@item.Id, null)" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
function changeDate(fecha) {
|
||||||
|
window.location.href = '@Url.Action("Index")?fecha=' + fecha;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAsistencia(colaboradorId, estado) {
|
||||||
|
const fecha = document.getElementById('fechaAsistencia').value;
|
||||||
|
const observaciones = document.getElementById('obs_' + colaboradorId).value;
|
||||||
|
|
||||||
|
// Si el estado es null, tomamos el que esté seleccionado
|
||||||
|
if (!estado) {
|
||||||
|
const checkedRadio = document.querySelector(`input[name="estado_${colaboradorId}"]:checked`);
|
||||||
|
if (!checkedRadio) return;
|
||||||
|
estado = checkedRadio.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('colaboradorId', colaboradorId);
|
||||||
|
formData.append('fecha', fecha);
|
||||||
|
formData.append('estado', estado);
|
||||||
|
formData.append('observaciones', observaciones);
|
||||||
|
|
||||||
|
fetch('@Url.Action("Save")', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Opcional: Mostrar feedback visual sutil
|
||||||
|
console.log('Guardado');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
86
foundation_system/Views/Permiso/Create.cshtml
Normal file
86
foundation_system/Views/Permiso/Create.cshtml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
@model foundation_system.Models.Permiso
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Nueva Opción de Menú";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-plus-circle me-2"></i>@ViewData["Title"]</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form asp-action="Create">
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Modulo" class="form-label">Módulo (Sección)</label>
|
||||||
|
<input asp-for="Modulo" class="form-control" placeholder="Ej: Gestión, Administración" />
|
||||||
|
<span asp-validation-for="Modulo" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Codigo" class="form-label">Código Único (Controlador)</label>
|
||||||
|
<input asp-for="Codigo" class="form-control" placeholder="Ej: Expediente" />
|
||||||
|
<span asp-validation-for="Codigo" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label asp-for="Nombre" class="form-label">Nombre en Menú</label>
|
||||||
|
<input asp-for="Nombre" class="form-control" placeholder="Ej: Expedientes" />
|
||||||
|
<span asp-validation-for="Nombre" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Orden" class="form-label">Orden</label>
|
||||||
|
<input asp-for="Orden" class="form-control" type="number" />
|
||||||
|
<span asp-validation-for="Orden" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label asp-for="Url" class="form-label">Ruta (URL)</label>
|
||||||
|
<input asp-for="Url" class="form-control" placeholder="Ej: /Expediente/Index" />
|
||||||
|
<span asp-validation-for="Url" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Icono" class="form-label">Icono (Bootstrap Icons)</label>
|
||||||
|
<input asp-for="Icono" class="form-control" placeholder="Ej: bi-folder" />
|
||||||
|
<span asp-validation-for="Icono" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" asp-for="EsMenu">
|
||||||
|
<label class="form-check-label" asp-for="EsMenu">Mostrar en el menú lateral</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||||
|
<textarea asp-for="Descripcion" class="form-control" rows="2"></textarea>
|
||||||
|
<span asp-validation-for="Descripcion" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 d-flex justify-content-between">
|
||||||
|
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-arrow-left"></i> Volver
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary px-4">
|
||||||
|
<i class="bi bi-save me-2"></i> Guardar Opción
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||||
|
}
|
||||||
87
foundation_system/Views/Permiso/Edit.cshtml
Normal file
87
foundation_system/Views/Permiso/Edit.cshtml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
@model foundation_system.Models.Permiso
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Editar Opción de Menú";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-pencil-square me-2"></i>@ViewData["Title"]</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form asp-action="Edit">
|
||||||
|
<input type="hidden" asp-for="Id" />
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Modulo" class="form-label">Módulo (Sección)</label>
|
||||||
|
<input asp-for="Modulo" class="form-control" />
|
||||||
|
<span asp-validation-for="Modulo" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Codigo" class="form-label">Código Único</label>
|
||||||
|
<input asp-for="Codigo" class="form-control" readonly />
|
||||||
|
<span asp-validation-for="Codigo" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label asp-for="Nombre" class="form-label">Nombre en Menú</label>
|
||||||
|
<input asp-for="Nombre" class="form-control" />
|
||||||
|
<span asp-validation-for="Nombre" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Orden" class="form-label">Orden</label>
|
||||||
|
<input asp-for="Orden" class="form-control" type="number" />
|
||||||
|
<span asp-validation-for="Orden" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label asp-for="Url" class="form-label">Ruta (URL)</label>
|
||||||
|
<input asp-for="Url" class="form-control" />
|
||||||
|
<span asp-validation-for="Url" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label asp-for="Icono" class="form-label">Icono</label>
|
||||||
|
<input asp-for="Icono" class="form-control" />
|
||||||
|
<span asp-validation-for="Icono" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" asp-for="EsMenu">
|
||||||
|
<label class="form-check-label" asp-for="EsMenu">Mostrar en el menú lateral</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||||
|
<textarea asp-for="Descripcion" class="form-control" rows="2"></textarea>
|
||||||
|
<span asp-validation-for="Descripcion" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 d-flex justify-content-between">
|
||||||
|
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-arrow-left"></i> Volver
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary px-4">
|
||||||
|
<i class="bi bi-save me-2"></i> Guardar Cambios
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||||
|
}
|
||||||
90
foundation_system/Views/Permiso/Index.cshtml
Normal file
90
foundation_system/Views/Permiso/Index.cshtml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
@model IEnumerable<foundation_system.Models.Permiso>
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Gestión de Menú y Permisos";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="h3 mb-0 text-gray-800"><i class="bi bi-list-ul me-2"></i>@ViewData["Title"]</h2>
|
||||||
|
<a asp-action="Create" class="btn btn-primary">
|
||||||
|
<i class="bi bi-plus-lg"></i> Nueva Opción
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (TempData["ErrorMessage"] != null)
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill me-2"></i> @TempData["ErrorMessage"]
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Orden</th>
|
||||||
|
<th>Módulo</th>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Ruta (URL)</th>
|
||||||
|
<th>Icono</th>
|
||||||
|
<th class="text-center">Es Menú</th>
|
||||||
|
<th class="text-center">Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var item in Model)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td><span class="badge bg-secondary">@item.Orden</span></td>
|
||||||
|
<td><span class="badge bg-info text-dark">@item.Modulo</span></td>
|
||||||
|
<td class="fw-bold">@item.Nombre</td>
|
||||||
|
<td class="font-monospace small">@item.Url</td>
|
||||||
|
<td><i class="bi @item.Icono me-2"></i>@item.Icono</td>
|
||||||
|
<td class="text-center">
|
||||||
|
@if (item.EsMenu)
|
||||||
|
{
|
||||||
|
<span class="text-success"><i class="bi bi-check-circle-fill"></i></span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-muted"><i class="bi bi-x-circle"></i></span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-outline-primary" title="Editar">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-outline-danger" title="Eliminar"
|
||||||
|
onclick="confirmDelete(@item.Id, '@item.Nombre')">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="deleteForm" asp-action="Delete" method="post" style="display:none;">
|
||||||
|
<input type="hidden" name="id" id="deleteId" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
function confirmDelete(id, name) {
|
||||||
|
if (confirm(`¿Está seguro de que desea eliminar "${name}"?`)) {
|
||||||
|
document.getElementById('deleteId').value = id;
|
||||||
|
document.getElementById('deleteForm').submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
55
foundation_system/Views/Rol/Create.cshtml
Normal file
55
foundation_system/Views/Rol/Create.cshtml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
@model foundation_system.Models.RolSistema
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Nuevo Rol";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-plus-circle me-2"></i>@ViewData["Title"]</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form asp-action="Create">
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Codigo" class="form-label">Código del Rol</label>
|
||||||
|
<input asp-for="Codigo" class="form-control" placeholder="Ej: ADMIN, OPERADOR" />
|
||||||
|
<span asp-validation-for="Codigo" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Nombre" class="form-label">Nombre</label>
|
||||||
|
<input asp-for="Nombre" class="form-control" placeholder="Ej: Administrador" />
|
||||||
|
<span asp-validation-for="Nombre" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||||
|
<textarea asp-for="Descripcion" class="form-control" rows="3" placeholder="Describa las responsabilidades de este rol..."></textarea>
|
||||||
|
<span asp-validation-for="Descripcion" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 d-flex justify-content-between">
|
||||||
|
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-arrow-left"></i> Volver al listado
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary px-4">
|
||||||
|
<i class="bi bi-save me-2"></i> Guardar Rol
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||||
|
}
|
||||||
57
foundation_system/Views/Rol/Edit.cshtml
Normal file
57
foundation_system/Views/Rol/Edit.cshtml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
@model foundation_system.Models.RolSistema
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Editar Rol";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-pencil-square me-2"></i>@ViewData["Title"]</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form asp-action="Edit">
|
||||||
|
<input type="hidden" asp-for="Id" />
|
||||||
|
<div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Codigo" class="form-label">Código del Rol</label>
|
||||||
|
<input asp-for="Codigo" class="form-control" readonly />
|
||||||
|
<span asp-validation-for="Codigo" class="text-danger small"></span>
|
||||||
|
<div class="form-text">El código no se puede modificar.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="Nombre" class="form-label">Nombre</label>
|
||||||
|
<input asp-for="Nombre" class="form-control" />
|
||||||
|
<span asp-validation-for="Nombre" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||||
|
<textarea asp-for="Descripcion" class="form-control" rows="3"></textarea>
|
||||||
|
<span asp-validation-for="Descripcion" class="text-danger small"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 d-flex justify-content-between">
|
||||||
|
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-arrow-left"></i> Volver al listado
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary px-4">
|
||||||
|
<i class="bi bi-save me-2"></i> Guardar Cambios
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||||
|
}
|
||||||
90
foundation_system/Views/Rol/Index.cshtml
Normal file
90
foundation_system/Views/Rol/Index.cshtml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
@model IEnumerable<foundation_system.Models.RolSistema>
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Gestión de Roles";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="h3 mb-0 text-gray-800"><i class="bi bi-person-badge me-2"></i>@ViewData["Title"]</h2>
|
||||||
|
<a asp-action="Create" class="btn btn-primary">
|
||||||
|
<i class="bi bi-plus-lg"></i> Nuevo Rol
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (TempData["SuccessMessage"] != null)
|
||||||
|
{
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-check-circle-fill me-2"></i> @TempData["SuccessMessage"]
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (TempData["ErrorMessage"] != null)
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill me-2"></i> @TempData["ErrorMessage"]
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Código</th>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Descripción</th>
|
||||||
|
<th class="text-center">Permisos</th>
|
||||||
|
<th class="text-center">Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var item in Model)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td class="font-monospace">@item.Codigo</td>
|
||||||
|
<td class="fw-bold">@item.Nombre</td>
|
||||||
|
<td class="text-muted small">@item.Descripcion</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span class="badge bg-secondary">@item.RolesPermisos.Count asignados</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<a asp-action="Permissions" asp-route-id="@item.Id" class="btn btn-outline-info" title="Gestionar Permisos">
|
||||||
|
<i class="bi bi-shield-check"></i>
|
||||||
|
</a>
|
||||||
|
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-outline-primary" title="Editar">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-outline-danger" title="Eliminar"
|
||||||
|
onclick="confirmDelete(@item.Id, '@item.Nombre')">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="deleteForm" asp-action="Delete" method="post" style="display:none;">
|
||||||
|
<input type="hidden" name="id" id="deleteId" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
function confirmDelete(id, name) {
|
||||||
|
if (confirm(`¿Está seguro de que desea eliminar el rol "${name}"?`)) {
|
||||||
|
document.getElementById('deleteId').value = id;
|
||||||
|
document.getElementById('deleteForm').submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
62
foundation_system/Views/Rol/Permissions.cshtml
Normal file
62
foundation_system/Views/Rol/Permissions.cshtml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
@model IEnumerable<foundation_system.Models.Permiso>
|
||||||
|
|
||||||
|
@{
|
||||||
|
var rol = ViewBag.Rol as foundation_system.Models.RolSistema;
|
||||||
|
var assignedCodes = ViewBag.AssignedControllerCodes as List<string>;
|
||||||
|
ViewData["Title"] = "Gestionar Acceso a Menú: " + rol?.Nombre;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-info text-white d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-shield-check me-2"></i>@ViewData["Title"]</h5>
|
||||||
|
<span class="badge bg-light text-dark">@rol?.Codigo</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted mb-4">Seleccione las opciones del menú que estarán disponibles para este rol.</p>
|
||||||
|
|
||||||
|
<form asp-action="UpdatePermissions" method="post">
|
||||||
|
<input type="hidden" name="rolId" value="@rol?.Id" />
|
||||||
|
|
||||||
|
@{
|
||||||
|
var groupedControllers = Model.GroupBy(c => c.Modulo);
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var group in groupedControllers)
|
||||||
|
{
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card h-100 border-light shadow-sm">
|
||||||
|
<div class="card-header bg-light py-2">
|
||||||
|
<h6 class="mb-0 fw-bold text-primary">@group.Key</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@foreach (var controller in group)
|
||||||
|
{
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="checkbox" name="selectedControllers"
|
||||||
|
value="@controller.Codigo" id="ctrl_@controller.Codigo"
|
||||||
|
@(assignedCodes != null && assignedCodes.Contains(controller.Codigo) ? "checked" : "")>
|
||||||
|
<label class="form-check-label" for="ctrl_@controller.Codigo">
|
||||||
|
<span class="fw-semibold">@controller.Nombre</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 d-flex justify-content-between border-top pt-3">
|
||||||
|
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-arrow-left"></i> Cancelar
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary px-5">
|
||||||
|
<i class="bi bi-check-all me-2"></i> Guardar Configuración
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
@model IEnumerable<foundation_system.Models.Permiso>
|
||||||
|
|
||||||
|
<nav class="nav flex-column">
|
||||||
|
<a class="nav-link-custom @(ViewContext.RouteData.Values["controller"]?.ToString() == "Home" ? "active" : "")" asp-controller="Home" asp-action="Index">
|
||||||
|
<i class="bi bi-house-door"></i> Inicio
|
||||||
|
</a>
|
||||||
|
|
||||||
|
@{
|
||||||
|
var groupedMenu = Model.GroupBy(m => m.Modulo);
|
||||||
|
var currentController = ViewContext.RouteData.Values["controller"]?.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@foreach (var group in groupedMenu)
|
||||||
|
{
|
||||||
|
<div class="nav-section-title mt-3">@group.Key</div>
|
||||||
|
@foreach (var item in group)
|
||||||
|
{
|
||||||
|
<a class="nav-link-custom @(currentController == item.Codigo ? "active" : "")" href="@item.Url">
|
||||||
|
<i class="bi @item.Icono"></i> @item.Nombre
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</nav>
|
||||||
@@ -24,30 +24,8 @@
|
|||||||
<i class="bi bi-house-heart-fill me-2"></i>MIES
|
<i class="bi bi-house-heart-fill me-2"></i>MIES
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav p-3">
|
||||||
<div class="nav-section-title">Principal</div>
|
@await Component.InvokeAsync("Menu")
|
||||||
<a class="nav-link-custom @(ViewContext.RouteData.Values["controller"]?.ToString() == "Home" ? "active" : "")" asp-controller="Home" asp-action="Index">
|
|
||||||
<i class="bi bi-speedometer2"></i> Dashboard
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="nav-section-title">Gestión</div>
|
|
||||||
<a class="nav-link-custom @(ViewContext.RouteData.Values["controller"]?.ToString() == "Expediente" ? "active" : "")" asp-controller="Expediente" asp-action="Index">
|
|
||||||
<i class="bi bi-folder2-open"></i> Expedientes
|
|
||||||
</a>
|
|
||||||
<a class="nav-link-custom @(ViewContext.RouteData.Values["controller"]?.ToString() == "Asistencia" ? "active" : "")" asp-controller="Asistencia" asp-action="Index">
|
|
||||||
<i class="bi bi-calendar-check"></i> Asistencia
|
|
||||||
</a>
|
|
||||||
<a class="nav-link-custom @(ViewContext.RouteData.Values["controller"]?.ToString() == "Antecedentes" ? "active" : "")" asp-controller="Antecedentes" asp-action="Index">
|
|
||||||
<i class="bi bi-journal-text"></i> Antecedentes
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="nav-section-title">Administración</div>
|
|
||||||
<a class="nav-link-custom @(ViewContext.RouteData.Values["controller"]?.ToString() == "Usuario" ? "active" : "")" asp-controller="Usuario" asp-action="Index">
|
|
||||||
<i class="bi bi-people"></i> Usuarios
|
|
||||||
</a>
|
|
||||||
<a class="nav-link-custom @(ViewContext.RouteData.Values["controller"]?.ToString() == "Configuracion" ? "active" : "")" asp-controller="Configuracion" asp-action="Index">
|
|
||||||
<i class="bi bi-gear"></i> Configuración
|
|
||||||
</a>
|
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sidebar-footer p-3 border-top border-secondary">
|
<div class="sidebar-footer p-3 border-top border-secondary">
|
||||||
<small class="text-muted">v1.0.0 © 2025</small>
|
<small class="text-muted">v1.0.0 © 2025</small>
|
||||||
|
|||||||
@@ -38,9 +38,27 @@
|
|||||||
<span asp-validation-for="Email" class="text-danger small"></span>
|
<span asp-validation-for="Email" class="text-danger small"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label asp-for="Telefono" class="form-label fw-semibold"></label>
|
<label asp-for="Telefono" class="form-label"></label>
|
||||||
<input asp-for="Telefono" class="form-control" />
|
<input asp-for="Telefono" class="form-control" />
|
||||||
<span asp-validation-for="Telefono" class="text-danger small"></span>
|
<span asp-validation-for="Telefono" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mt-4">
|
||||||
|
<h6 class="border-bottom pb-2 mb-3"><i class="bi bi-person-badge me-2"></i>Asignación de Roles</h6>
|
||||||
|
<div class="row g-3">
|
||||||
|
@foreach (var role in ViewBag.Roles as List<foundation_system.Models.RolSistema>)
|
||||||
|
{
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-check card p-2 border-light shadow-sm">
|
||||||
|
<input class="form-check-input ms-0 me-2" type="checkbox" name="SelectedRoles" value="@role.Id" id="role_@role.Id">
|
||||||
|
<label class="form-check-label" for="role_@role.Id">
|
||||||
|
<strong>@role.Nombre</strong><br />
|
||||||
|
<small class="text-muted">@role.Descripcion</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,25 @@
|
|||||||
<input asp-for="Telefono" class="form-control" />
|
<input asp-for="Telefono" class="form-control" />
|
||||||
<span asp-validation-for="Telefono" class="text-danger small"></span>
|
<span asp-validation-for="Telefono" class="text-danger small"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mt-4">
|
||||||
|
<h6 class="border-bottom pb-2 mb-3"><i class="bi bi-person-badge me-2"></i>Asignación de Roles</h6>
|
||||||
|
<div class="row g-3">
|
||||||
|
@foreach (var role in ViewBag.Roles as List<foundation_system.Models.RolSistema>)
|
||||||
|
{
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-check card p-2 border-light shadow-sm">
|
||||||
|
<input class="form-check-input ms-0 me-2" type="checkbox" name="SelectedRoles" value="@role.Id" id="role_@role.Id"
|
||||||
|
@(Model.SelectedRoles.Contains(role.Id) ? "checked" : "")>
|
||||||
|
<label class="form-check-label" for="role_@role.Id">
|
||||||
|
<strong>@role.Nombre</strong><br />
|
||||||
|
<small class="text-muted">@role.Descripcion</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5 class="mb-4 text-primary border-bottom pb-2">Configuración de Cuenta</h5>
|
<h5 class="mb-4 text-primary border-bottom pb-2">Configuración de Cuenta</h5>
|
||||||
|
|||||||
Reference in New Issue
Block a user