2025-08-29 09:54:22 -03:00
|
|
|
// 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<AdminController> _logger;
|
|
|
|
|
|
|
|
|
|
public AdminController(EleccionesDbContext dbContext, ILogger<AdminController> logger)
|
|
|
|
|
{
|
|
|
|
|
_dbContext = dbContext;
|
|
|
|
|
_logger = logger;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Endpoint para obtener todas las agrupaciones para el panel de admin
|
|
|
|
|
[HttpGet("agrupaciones")]
|
|
|
|
|
public async Task<IActionResult> GetAgrupaciones()
|
|
|
|
|
{
|
|
|
|
|
var agrupaciones = await _dbContext.AgrupacionesPoliticas
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
.OrderBy(a => a.Nombre)
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
return Ok(agrupaciones);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPut("agrupaciones/{id}")]
|
|
|
|
|
public async Task<IActionResult> 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<IActionResult> UpdateDiputadosOrden([FromBody] List<string> 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<IActionResult> UpdateSenadoresOrden([FromBody] List<string> 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<IActionResult> 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<IActionResult> UpdateConfiguracion([FromBody] Dictionary<string, string> 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<IActionResult> 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<IActionResult> 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();
|
|
|
|
|
}
|
2025-09-01 14:04:40 -03:00
|
|
|
|
|
|
|
|
[HttpGet("logos")]
|
|
|
|
|
public async Task<IActionResult> GetLogos()
|
|
|
|
|
{
|
|
|
|
|
return Ok(await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPut("logos")]
|
|
|
|
|
public async Task<IActionResult> UpdateLogos([FromBody] List<LogoAgrupacionCategoria> logos)
|
|
|
|
|
{
|
|
|
|
|
foreach (var logo in logos)
|
|
|
|
|
{
|
|
|
|
|
var logoExistente = await _dbContext.LogosAgrupacionesCategorias
|
2025-09-02 15:39:17 -03:00
|
|
|
.FirstOrDefaultAsync(l =>
|
|
|
|
|
l.AgrupacionPoliticaId == logo.AgrupacionPoliticaId &&
|
|
|
|
|
l.CategoriaId == logo.CategoriaId &&
|
|
|
|
|
l.AmbitoGeograficoId == logo.AmbitoGeograficoId);
|
2025-09-01 14:04:40 -03:00
|
|
|
|
|
|
|
|
if (logoExistente != null)
|
|
|
|
|
{
|
2025-09-02 15:39:17 -03:00
|
|
|
// Si encontramos el registro exacto, solo actualizamos su URL.
|
2025-09-01 14:04:40 -03:00
|
|
|
logoExistente.LogoUrl = logo.LogoUrl;
|
|
|
|
|
}
|
|
|
|
|
else if (!string.IsNullOrEmpty(logo.LogoUrl))
|
|
|
|
|
{
|
2025-09-02 15:39:17 -03:00
|
|
|
// 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
|
|
|
|
|
});
|
2025-09-01 14:04:40 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
|
return NoContent();
|
|
|
|
|
}
|
2025-09-02 15:39:17 -03:00
|
|
|
|
|
|
|
|
[HttpGet("catalogos/municipios")]
|
|
|
|
|
public async Task<IActionResult> 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);
|
|
|
|
|
}
|
2025-09-05 11:38:25 -03:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Obtiene todos los overrides de candidatos configurados.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[HttpGet("candidatos")]
|
|
|
|
|
public async Task<IActionResult> GetCandidatos()
|
|
|
|
|
{
|
|
|
|
|
var candidatos = await _dbContext.CandidatosOverrides
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
return Ok(candidatos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Guarda (actualiza o crea) una lista de overrides de candidatos.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[HttpPut("candidatos")]
|
2025-09-05 12:58:52 -03:00
|
|
|
public async Task<IActionResult> UpdateCandidatos([FromBody] List<UpdateCandidatoDto> candidatos)
|
2025-09-05 11:38:25 -03:00
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
2025-09-05 12:58:52 -03:00
|
|
|
// El registro ya existe
|
|
|
|
|
if (string.IsNullOrWhiteSpace(candidatoDto.NombreCandidato))
|
2025-09-05 11:38:25 -03:00
|
|
|
{
|
2025-09-05 12:58:52 -03:00
|
|
|
// El usuario envió un nombre vacío -> Eliminar el registro
|
|
|
|
|
_dbContext.CandidatosOverrides.Remove(candidatoExistente);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// El usuario envió un nombre válido -> Actualizar
|
2025-09-05 11:38:25 -03:00
|
|
|
candidatoExistente.NombreCandidato = candidatoDto.NombreCandidato;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-09-05 12:58:52 -03:00
|
|
|
// El registro no existe
|
2025-09-05 11:38:25 -03:00
|
|
|
if (!string.IsNullOrWhiteSpace(candidatoDto.NombreCandidato))
|
|
|
|
|
{
|
2025-09-05 12:58:52 -03:00
|
|
|
// El usuario envió un nombre válido -> Crear nuevo registro
|
2025-09-05 11:38:25 -03:00
|
|
|
_dbContext.CandidatosOverrides.Add(new CandidatoOverride
|
|
|
|
|
{
|
|
|
|
|
AgrupacionPoliticaId = candidatoDto.AgrupacionPoliticaId,
|
|
|
|
|
CategoriaId = candidatoDto.CategoriaId,
|
|
|
|
|
AmbitoGeograficoId = candidatoDto.AmbitoGeograficoId,
|
|
|
|
|
NombreCandidato = candidatoDto.NombreCandidato
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-09-05 12:58:52 -03:00
|
|
|
// Si no existe y el nombre está vacío, no hacemos nada.
|
2025-09-05 11:38:25 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await _dbContext.SaveChangesAsync();
|
2025-09-05 12:58:52 -03:00
|
|
|
return NoContent();
|
2025-09-05 11:38:25 -03:00
|
|
|
}
|
2025-08-29 09:54:22 -03:00
|
|
|
}
|