// src/Elecciones.Api/Controllers/AdminController.cs using Elecciones.Database; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Elecciones.Core.DTOs.ApiRequests; using Elecciones.Database.Entities; using Microsoft.AspNetCore.Authorization; using Elecciones.Core.Enums; namespace Elecciones.Api.Controllers; [ApiController] [Route("api/[controller]")] [Authorize] public class AdminController : ControllerBase { private readonly EleccionesDbContext _dbContext; private readonly ILogger _logger; public AdminController(EleccionesDbContext dbContext, ILogger logger) { _dbContext = dbContext; _logger = logger; } // Endpoint para obtener todas las agrupaciones para el panel de admin [HttpGet("agrupaciones")] public async Task GetAgrupaciones() { var agrupaciones = await _dbContext.AgrupacionesPoliticas .AsNoTracking() .OrderBy(a => a.Nombre) .ToListAsync(); return Ok(agrupaciones); } [HttpPut("agrupaciones/{id}")] public async Task UpdateAgrupacion(string id, [FromBody] UpdateAgrupacionDto agrupacionDto) { // Buscamos la agrupación en la base de datos por su ID. var agrupacion = await _dbContext.AgrupacionesPoliticas.FindAsync(id); if (agrupacion == null) { // Si no existe, devolvemos un error 404 Not Found. return NotFound(new { message = $"No se encontró la agrupación con ID {id}" }); } // Actualizamos las propiedades de la entidad con los valores del DTO. agrupacion.NombreCorto = agrupacionDto.NombreCorto; agrupacion.Color = agrupacionDto.Color; // Guardamos los cambios en la base de datos. await _dbContext.SaveChangesAsync(); _logger.LogInformation("Se actualizó la agrupación: {Id}", id); // Devolvemos una respuesta 204 No Content, que es el estándar para un PUT exitoso sin devolver datos. return NoContent(); } [HttpPut("agrupaciones/orden-diputados")] public async Task UpdateDiputadosOrden([FromBody] List idsAgrupacionesOrdenadas) { // Reseteamos solo el orden de diputados await _dbContext.AgrupacionesPoliticas.ExecuteUpdateAsync(s => s.SetProperty(a => a.OrdenDiputados, (int?)null)); for (int i = 0; i < idsAgrupacionesOrdenadas.Count; i++) { var agrupacion = await _dbContext.AgrupacionesPoliticas.FindAsync(idsAgrupacionesOrdenadas[i]); if (agrupacion != null) agrupacion.OrdenDiputados = i + 1; } await _dbContext.SaveChangesAsync(); return Ok(); } [HttpPut("agrupaciones/orden-senadores")] public async Task UpdateSenadoresOrden([FromBody] List idsAgrupacionesOrdenadas) { // Reseteamos solo el orden de senadores await _dbContext.AgrupacionesPoliticas.ExecuteUpdateAsync(s => s.SetProperty(a => a.OrdenSenadores, (int?)null)); for (int i = 0; i < idsAgrupacionesOrdenadas.Count; i++) { var agrupacion = await _dbContext.AgrupacionesPoliticas.FindAsync(idsAgrupacionesOrdenadas[i]); if (agrupacion != null) agrupacion.OrdenSenadores = i + 1; } await _dbContext.SaveChangesAsync(); return Ok(); } // LEER todas las configuraciones [HttpGet("configuracion")] public async Task GetConfiguracion() { var configs = await _dbContext.Configuraciones.AsNoTracking().ToListAsync(); // Devolvemos un diccionario para que sea fácil de consumir en el frontend return Ok(configs.ToDictionary(c => c.Clave, c => c.Valor)); } // GUARDAR un conjunto de configuraciones [HttpPut("configuracion")] public async Task UpdateConfiguracion([FromBody] Dictionary nuevasConfiguraciones) { foreach (var kvp in nuevasConfiguraciones) { var config = await _dbContext.Configuraciones.FindAsync(kvp.Key); if (config == null) { // Si no existe, la creamos _dbContext.Configuraciones.Add(new Configuracion { Clave = kvp.Key, Valor = kvp.Value }); } else { // Si existe, la actualizamos config.Valor = kvp.Value; } } await _dbContext.SaveChangesAsync(); return NoContent(); } // LEER: Obtener todas las bancadas para una cámara, con su partido y ocupante actual [HttpGet("bancadas/{camara}")] public async Task GetBancadas(TipoCamara camara) { var bancadas = await _dbContext.Bancadas .AsNoTracking() .Include(b => b.AgrupacionPolitica) .Include(b => b.Ocupante) .Where(b => b.Camara == camara) .OrderBy(b => b.Id) // Ordenar por ID para consistencia .ToListAsync(); return Ok(bancadas); } // ACTUALIZAR: Asignar un partido y/o un ocupante a una bancada específica [HttpPut("bancadas/{bancadaId}")] public async Task UpdateBancada(int bancadaId, [FromBody] UpdateBancadaDto dto) { var bancada = await _dbContext.Bancadas .Include(b => b.Ocupante) .FirstOrDefaultAsync(b => b.Id == bancadaId); if (bancada == null) { return NotFound(new { message = "La bancada especificada no existe." }); } // 1. Actualizar la agrupación de la bancada bancada.AgrupacionPoliticaId = dto.AgrupacionPoliticaId; // 2. Lógica de "Upsert" (Update/Insert/Delete) para el ocupante if (string.IsNullOrEmpty(dto.NombreOcupante)) { // Si no se envía nombre, se elimina el ocupante existente if (bancada.Ocupante != null) { _dbContext.OcupantesBancas.Remove(bancada.Ocupante); } } else { // Si se envía un nombre, se crea o actualiza el ocupante if (bancada.Ocupante == null) { bancada.Ocupante = new OcupanteBanca(); // Crea uno nuevo si no existía } bancada.Ocupante.NombreOcupante = dto.NombreOcupante; bancada.Ocupante.FotoUrl = dto.FotoUrl; bancada.Ocupante.Periodo = dto.Periodo; } await _dbContext.SaveChangesAsync(); _logger.LogInformation("Se actualizó la bancada con ID: {BancadaId}", bancadaId); return NoContent(); } [HttpGet("logos")] public async Task GetLogos() { return Ok(await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync()); } [HttpPut("logos")] public async Task UpdateLogos([FromBody] List logos) { foreach (var logo in logos) { var logoExistente = await _dbContext.LogosAgrupacionesCategorias .FirstOrDefaultAsync(l => l.AgrupacionPoliticaId == logo.AgrupacionPoliticaId && l.CategoriaId == logo.CategoriaId && l.AmbitoGeograficoId == logo.AmbitoGeograficoId); if (logoExistente != null) { // Si encontramos el registro exacto, solo actualizamos su URL. logoExistente.LogoUrl = logo.LogoUrl; } else if (!string.IsNullOrEmpty(logo.LogoUrl)) { // Si no se encontró un registro exacto (es un override nuevo), // lo añadimos a la base de datos. _dbContext.LogosAgrupacionesCategorias.Add(new LogoAgrupacionCategoria { AgrupacionPoliticaId = logo.AgrupacionPoliticaId, CategoriaId = logo.CategoriaId, AmbitoGeograficoId = logo.AmbitoGeograficoId, LogoUrl = logo.LogoUrl }); } } await _dbContext.SaveChangesAsync(); return NoContent(); } [HttpGet("catalogos/municipios")] public async Task GetMunicipiosForAdmin() { var municipios = await _dbContext.AmbitosGeograficos .AsNoTracking() .Where(a => a.NivelId == 30) // Nivel 30 = Municipio .OrderBy(a => a.Nombre) .Select(a => new { // Devolvemos el ID de la base de datos como un string, // que es lo que el componente Select espera. Id = a.Id.ToString(), Nombre = a.Nombre }) .ToListAsync(); return Ok(municipios); } /// /// Obtiene todos los overrides de candidatos configurados. /// [HttpGet("candidatos")] public async Task GetCandidatos() { var candidatos = await _dbContext.CandidatosOverrides .AsNoTracking() .ToListAsync(); return Ok(candidatos); } /// /// Guarda (actualiza o crea) una lista de overrides de candidatos. /// [HttpPut("candidatos")] public async Task UpdateCandidatos([FromBody] List candidatos) { foreach (var candidatoDto in candidatos) { var candidatoExistente = await _dbContext.CandidatosOverrides .FirstOrDefaultAsync(c => c.AgrupacionPoliticaId == candidatoDto.AgrupacionPoliticaId && c.CategoriaId == candidatoDto.CategoriaId && c.AmbitoGeograficoId == candidatoDto.AmbitoGeograficoId); if (candidatoExistente != null) { // El registro ya existe if (string.IsNullOrWhiteSpace(candidatoDto.NombreCandidato)) { // El usuario envió un nombre vacío -> Eliminar el registro _dbContext.CandidatosOverrides.Remove(candidatoExistente); } else { // El usuario envió un nombre válido -> Actualizar candidatoExistente.NombreCandidato = candidatoDto.NombreCandidato; } } else { // El registro no existe if (!string.IsNullOrWhiteSpace(candidatoDto.NombreCandidato)) { // El usuario envió un nombre válido -> Crear nuevo registro _dbContext.CandidatosOverrides.Add(new CandidatoOverride { AgrupacionPoliticaId = candidatoDto.AgrupacionPoliticaId, CategoriaId = candidatoDto.CategoriaId, AmbitoGeograficoId = candidatoDto.AmbitoGeograficoId, NombreCandidato = candidatoDto.NombreCandidato }); } // Si no existe y el nombre está vacío, no hacemos nada. } } await _dbContext.SaveChangesAsync(); return NoContent(); } }