diff --git a/.opencode-rules b/.opencode-rules new file mode 100644 index 0000000..0e7e418 --- /dev/null +++ b/.opencode-rules @@ -0,0 +1,18 @@ +Actúa como un Ingeniero de Software Principal y Agente Autónomo de Desarrollo. Tu objetivo es cumplir el plan de desarrollo del repositorio de forma iterativa y segura. + +### Contexto del Proyecto +- El proyecto utiliza Git para el control de versiones. +- El estado actual y la descripción general están en `Readme.md`. +- La hoja de ruta y los objetivos están detallados en el archivo del plan de desarrollo. + +### Instrucciones de Operación (Ciclo de Ejecución) +1. ANALIZAR: Lee el plan de desarrollo y compáralo con el código actual para identificar la próxima tarea pendiente. +2. PLANIFICAR: Divide esa tarea en subpasos técnicos sumamente específicos antes de escribir código. +3. EJECUTAR: Modifica o crea los archivos necesarios para completar el subpaso actual. +4. VALIDAR: Asegúrate de que los cambios compilen, no rompan la arquitectura y cumplan con el objetivo. +5. AVANZAR: Una vez completada una tarea, actualiza el archivo del plan de desarrollo marcándola como completada y pasa a la siguiente. + +### Reglas Estrictas +- No intentes resolver todo el plan de desarrollo de un solo golpe; trabaja en tareas atómicas y secuenciales. +- Mantén el código limpio, documentado y modular. +- Si encuentras un bloqueo o ambigüedad en el plan, detén la ejecución y pregunta al usuario. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7e70178 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,25 @@ +# AGENTS.md + +## Tecnología +- ASP.NET Core 8 + MVC con Razor Views. +- MySQL via Pomelo.EntityFrameworkCore.MySql para acceso a datos. +- Identity para gestión de usuarios y roles (Admin, Usuario). + +## Commands +- `dotnet build` - compilar sin restaurar packages. +- `dotnet restore` - restore packages desde nuget.org. +- `dotnet run --urls https://*:5001;http://*:5000` - launch dev server con HTTPS y HTTP. + +## Estructura clave +- Módulos por controller: UsuariosController, AccountController, RolesController. +- Models: ApplicationUser (extiende IdentityUser), ErrorViewModel. +- Data: ApplicationDbContext (EF Core DbContext) en `Data/ApplicationDbContext.cs`. +- Views: Razor views bajo `Views/` folder. + +## Seeds +- Program.cs incluye seeds para roles y usuario admin (`admin@example.com` / "Admin123!"). +- Roles creados: Admin, Usuario. + +## Migraciones +- Usa `dotnet ef migrations add -c ApplicationDbContext`. +- Aplica con `dotnet ef database update`. diff --git a/Controllers/RolesController.cs b/Controllers/RolesController.cs index e0d6013..ee9bd6f 100644 --- a/Controllers/RolesController.cs +++ b/Controllers/RolesController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Threading.Tasks; -using System.Linq; +using vita_asistente.Models; namespace vita_asistente.Controllers { @@ -61,13 +61,82 @@ namespace vita_asistente.Controllers return View(); } + // GET: Roles/Edit/5 + public async Task Edit(string id) + { + if (id == null) return NotFound(); + var role = await _roleManager.FindByIdAsync(id); + if (role == null) return NotFound(); + ViewBag.RoleName = role.Name; + return View(role); + } + + // POST: Roles/Edit/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Edit(string id, IdentityRole roleActualizado) + { + if (id != roleActualizado.Id) return NotFound(); + + var roleExistente = await _roleManager.FindByIdAsync(id); + if (roleExistente == null) return NotFound(); + + // Actualizar solo el nombre + roleExistente.Name = roleActualizado.Name; + var result = await _roleManager.UpdateAsync(roleExistente); + + if (result.Succeeded) + { + return RedirectToAction(nameof(Index)); + } + foreach (var error in result.Errors) + { + ModelState.AddModelError("", error.Description); + } + return View(roleActualizado); + } + + // GET: Roles/Disable/5 + public async Task Disable(string id) + { + if (id == null) return NotFound(); + var role = await _roleManager.FindByIdAsync(id); + if (role == null) return NotFound(); + return View(role); + } + + // POST: Roles/Disable/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task DisableConfirmed(string id) + { + var role = await _roleManager.FindByIdAsync(id); + if (role != null) + { + // En Identity, "deshabilitar" un rol implica eliminarlo + // Pero primero verificamos si tiene usuarios asignados + using var scope = HttpContext.RequestServices.CreateScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); + var usersInRole = await userManager.GetUsersInRoleAsync(role.Name); + + if (usersInRole.Any()) + { + ModelState.AddModelError("", "No se puede deshabilitar un rol que tiene usuarios asignados."); + return View("Disable", role); + } + + await _roleManager.DeleteAsync(role); + } + return RedirectToAction(nameof(Index)); + } + // GET: Roles/Delete/5 public async Task Delete(string id) { if (id == null) return NotFound(); - var rol = await _roleManager.FindByIdAsync(id); - if (rol == null) return NotFound(); - return View(rol); + var role = await _roleManager.FindByIdAsync(id); + if (role == null) return NotFound(); + return View(role); } // POST: Roles/Delete/5 @@ -75,10 +144,28 @@ namespace vita_asistente.Controllers [ValidateAntiForgeryToken] public async Task DeleteConfirmed(string id) { - var rol = await _roleManager.FindByIdAsync(id); - if (rol != null) + var role = await _roleManager.FindByIdAsync(id); + if (role != null) { - await _roleManager.DeleteAsync(rol); + // Verificar que no sea el rol "Admin" (o cualquier otro rol esencial) + if (role.Name == "Admin" || role.Name == "Administrador") + { + ModelState.AddModelError("", "No se puede eliminar el rol Admin/Administrador."); + return View(role); + } + + // Verificar que no tenga usuarios asignados + using var scope = HttpContext.RequestServices.CreateScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); + var usersInRole = await userManager.GetUsersInRoleAsync(role.Name); + + if (usersInRole.Any()) + { + ModelState.AddModelError("", "No se puede eliminar un rol que tiene usuarios asignados."); + return View(role); + } + + await _roleManager.DeleteAsync(role); } return RedirectToAction(nameof(Index)); } diff --git a/Views/Roles/Disable.cshtml b/Views/Roles/Disable.cshtml new file mode 100644 index 0000000..63a7e88 --- /dev/null +++ b/Views/Roles/Disable.cshtml @@ -0,0 +1,18 @@ +@model IdentityRole + +@{ + ViewData["Title"] = "Deshabilitar rol"; +} + +

Deshabilitar rol: @Model.Name

+ +

¿Está seguro que desea deshabilitar este rol? Esto afectará a los usuarios con este rol.

+ +
+ + @Html.AntiForgeryToken() +
+ + Cancelar +
+
\ No newline at end of file diff --git a/Views/Roles/Edit.cshtml b/Views/Roles/Edit.cshtml new file mode 100644 index 0000000..7998d19 --- /dev/null +++ b/Views/Roles/Edit.cshtml @@ -0,0 +1,24 @@ +@model IdentityRole + +@{ + ViewData["Title"] = "Editar rol"; +} + +

Editar rol: @Model.Name

+ +
+ +
+ + + +
+
+ + Cancelar +
+
+ +section scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} \ No newline at end of file diff --git a/Views/Roles/Index.cshtml b/Views/Roles/Index.cshtml index 9ae2a8c..1d6d4ce 100644 --- a/Views/Roles/Index.cshtml +++ b/Views/Roles/Index.cshtml @@ -11,9 +11,77 @@ @role.Name - Eliminar - - - } - - \ No newline at end of file + Editar + + + + + Deshabilitar + + + + + +