From 5212e31a0370e0d8c1cba990aa75fecd34198e9c Mon Sep 17 00:00:00 2001 From: dmolinari Date: Mon, 23 Mar 2026 14:09:26 -0300 Subject: [PATCH] =?UTF-8?q?Feat:=20Baja=20L=C3=B3gica=20de=20Distribuidore?= =?UTF-8?q?s=20(Selectores=20Dropdown)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Backend/EliminacionLogicaDistribuidores.sql | 27 ++++++ .../Distribucion/DistribuidoresController.cs | 29 +++++- .../Repositories/Contables/SaldoRepository.cs | 32 +++++-- .../Distribucion/DistribuidorRepository.cs | 91 +++++++++++++++---- .../Distribucion/IDistribuidorRepository.cs | 5 +- .../Distribucion/PorcPagoRepository.cs | 2 +- .../Models/Distribucion/Distribuidor.cs | 2 + .../Distribucion/DistribuidorHistorico.cs | 2 + .../Dtos/Distribucion/DistribuidorDto.cs | 2 + .../Distribucion/ToggleBajaDistribuidorDto.cs | 10 ++ Backend/GestionIntegral.Api/Program.cs | 2 +- .../Services/Anomalia/AlertaService.cs | 17 +++- .../Distribucion/DistribuidorService.cs | 37 +++++++- .../Distribucion/IDistribuidorService.cs | 5 +- .../dtos/Distribucion/DistribuidorDto.ts | 2 + .../GestionarDistribuidoresPage.tsx | 62 +++++++++++-- .../Distribucion/distribuidorService.ts | 15 ++- 17 files changed, 289 insertions(+), 53 deletions(-) create mode 100644 Backend/EliminacionLogicaDistribuidores.sql create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Distribucion/ToggleBajaDistribuidorDto.cs diff --git a/Backend/EliminacionLogicaDistribuidores.sql b/Backend/EliminacionLogicaDistribuidores.sql new file mode 100644 index 0000000..7c20a58 --- /dev/null +++ b/Backend/EliminacionLogicaDistribuidores.sql @@ -0,0 +1,27 @@ +-- Script para agregar borrado lógico a Distribuidores + +-- 1. Agregar columnas a la tabla principal +ALTER TABLE dbo.dist_dtDistribuidores +ADD Baja bit NOT NULL DEFAULT 0; + +ALTER TABLE dbo.dist_dtDistribuidores +ADD FechaBaja datetime2(0) NULL; + +-- 2. Agregar columnas a la tabla histórica +ALTER TABLE dbo.dist_dtDistribuidores_H +ADD Baja bit NULL; + +ALTER TABLE dbo.dist_dtDistribuidores_H +ADD FechaBaja datetime2(0) NULL; + +-- 3. ATENCION: Actualizar Stored Procedures de Reportes +-- Los siguientes Stored Procedures deben ser modificados para incluir la condicion "AND Baja = 0" +-- en las consultas a "dist_dtDistribuidores": +-- - SP_BalanceCuentaDistEntradaSalidaPorEmpresa +-- - SP_BalanceCuentDistDebCredEmpresa +-- - SP_BalanceCuentDistPagosEmpresa +-- - SP_BalanceCuentSaldosEmpresas +-- - SP_CantidadEntradaSalida +-- - SP_CantidadEntradaSalidaCPromAgDia + +PRINT 'Se agregaron correctamente las columnas Baja y FechaBaja a dist_dtDistribuidores y dist_dtDistribuidores_H'; diff --git a/Backend/GestionIntegral.Api/Controllers/Distribucion/DistribuidoresController.cs b/Backend/GestionIntegral.Api/Controllers/Distribucion/DistribuidoresController.cs index c1219a2..da0fb0e 100644 --- a/Backend/GestionIntegral.Api/Controllers/Distribucion/DistribuidoresController.cs +++ b/Backend/GestionIntegral.Api/Controllers/Distribucion/DistribuidoresController.cs @@ -40,19 +40,19 @@ namespace GestionIntegral.Api.Controllers.Distribucion [HttpGet] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task GetAllDistribuidores([FromQuery] string? nombre, [FromQuery] string? nroDoc) + public async Task GetAllDistribuidores([FromQuery] string? nombre, [FromQuery] string? nroDoc, [FromQuery] bool? soloActivos = true) { if (!TienePermiso(PermisoVer)) return Forbid(); - var distribuidores = await _distribuidorService.ObtenerTodosAsync(nombre, nroDoc); + var distribuidores = await _distribuidorService.ObtenerTodosAsync(nombre, nroDoc, soloActivos); return Ok(distribuidores); } [HttpGet("dropdown")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task GetAllDropdownDistribuidores() + public async Task GetAllDropdownDistribuidores([FromQuery] bool? soloActivos = true) { - var distribuidores = await _distribuidorService.GetAllDropdownAsync(); + var distribuidores = await _distribuidorService.GetAllDropdownAsync(soloActivos); return Ok(distribuidores); } @@ -117,6 +117,27 @@ namespace GestionIntegral.Api.Controllers.Distribucion return NoContent(); } + [HttpPut("{id:int}/toggle-baja")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task ToggleBajaDistribuidor(int id, [FromBody] ToggleBajaDistribuidorDto dto) + { + if (!TienePermiso(PermisoModificar)) return Forbid(); + if (!ModelState.IsValid) return BadRequest(ModelState); + var userId = GetCurrentUserId(); + if (userId == null) return Unauthorized(); + + var (exito, error) = await _distribuidorService.ToggleBajaAsync(id, dto.DarDeBaja, dto.FechaBaja, userId.Value); + if (!exito) + { + if (error == "Distribuidor no encontrado.") return NotFound(new { message = error }); + return BadRequest(new { message = error }); + } + return NoContent(); + } + [HttpDelete("{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Contables/SaldoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Contables/SaldoRepository.cs index a3381b4..8f64986 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Contables/SaldoRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Contables/SaldoRepository.cs @@ -23,7 +23,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables public async Task> GetAllDistribuidorIdsAsync() { - var sql = "SELECT Id_Distribuidor FROM dbo.dist_dtDistribuidores"; + var sql = "SELECT Id_Distribuidor FROM dbo.dist_dtDistribuidores WHERE Baja = 0"; try { using (var connection = _connectionFactory.CreateConnection()) @@ -138,25 +138,45 @@ namespace GestionIntegral.Api.Data.Repositories.Contables public async Task> GetSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter) { - var sqlBuilder = new StringBuilder("SELECT Id_Saldo AS IdSaldo, Destino, Id_Destino AS IdDestino, Monto, Id_Empresa AS IdEmpresa, FechaUltimaModificacion FROM dbo.cue_Saldos WHERE 1=1"); + var sqlBuilder = new StringBuilder(@" + SELECT Id_Saldo AS IdSaldo, Destino, Id_Destino AS IdDestino, Monto, Id_Empresa AS IdEmpresa, FechaUltimaModificacion + FROM dbo.cue_Saldos s + WHERE 1=1"); + var parameters = new DynamicParameters(); if (!string.IsNullOrWhiteSpace(destinoFilter)) { - sqlBuilder.Append(" AND Destino = @Destino"); + sqlBuilder.Append(" AND s.Destino = @Destino"); parameters.Add("Destino", destinoFilter); + + // Filtro para excluir distribuidores de baja si el tipo es Distribuidores + // No se aplica a Canillas por requerimiento explícito del usuario + if (destinoFilter == "Distribuidores") + { + sqlBuilder.Append(" AND EXISTS (SELECT 1 FROM dbo.dist_dtDistribuidores d WHERE d.Id_Distribuidor = s.Id_Destino AND d.Baja = 0)"); + } } + else + { + // Si no hay filtro de destino, aplicamos el filtro de baja solo para Distribuidores + sqlBuilder.Append(@" AND ( + (s.Destino = 'Distribuidores' AND EXISTS (SELECT 1 FROM dbo.dist_dtDistribuidores d WHERE d.Id_Distribuidor = s.Id_Destino AND d.Baja = 0)) + OR (s.Destino != 'Distribuidores') + )"); + } + if (idDestinoFilter.HasValue) { - sqlBuilder.Append(" AND Id_Destino = @IdDestino"); + sqlBuilder.Append(" AND s.Id_Destino = @IdDestino"); parameters.Add("IdDestino", idDestinoFilter.Value); } if (idEmpresaFilter.HasValue) { - sqlBuilder.Append(" AND Id_Empresa = @IdEmpresa"); + sqlBuilder.Append(" AND s.Id_Empresa = @IdEmpresa"); parameters.Add("IdEmpresa", idEmpresaFilter.Value); } - sqlBuilder.Append(" ORDER BY Destino, Id_Empresa, Id_Destino;"); + sqlBuilder.Append(" ORDER BY s.Destino, s.Id_Empresa, s.Id_Destino;"); using var connection = _connectionFactory.CreateConnection(); return await connection.QueryAsync(sqlBuilder.ToString(), parameters); diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/DistribuidorRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/DistribuidorRepository.cs index 0953fa4..166c63e 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/DistribuidorRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/DistribuidorRepository.cs @@ -22,12 +22,12 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion _logger = logger; } - public async Task> GetAllAsync(string? nombreFilter, string? nroDocFilter) + public async Task> GetAllAsync(string? nombreFilter, string? nroDocFilter, bool? soloActivos = true) { var sqlBuilder = new StringBuilder(@" SELECT d.Id_Distribuidor AS IdDistribuidor, d.Nombre, d.Contacto, d.NroDoc, d.Id_Zona AS IdZona, - d.Calle, d.Numero, d.Piso, d.Depto, d.Telefono, d.Email, d.Localidad, + d.Calle, d.Numero, d.Piso, d.Depto, d.Telefono, d.Email, d.Localidad, d.Baja, d.FechaBaja, z.Nombre AS NombreZona FROM dbo.dist_dtDistribuidores d LEFT JOIN dbo.dist_dtZonas z ON d.Id_Zona = z.Id_Zona @@ -44,6 +44,11 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion sqlBuilder.Append(" AND d.NroDoc LIKE @NroDocParam"); parameters.Add("NroDocParam", $"%{nroDocFilter}%"); } + if (soloActivos.HasValue) + { + sqlBuilder.Append(" AND d.Baja = @BajaStatus "); + parameters.Add("BajaStatus", !soloActivos.Value); + } sqlBuilder.Append(" ORDER BY d.Nombre;"); try @@ -63,7 +68,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion } } - public async Task> GetAllDropdownAsync() + public async Task> GetAllDropdownAsync(bool? soloActivos = true) { var sqlBuilder = new StringBuilder(@" SELECT @@ -71,6 +76,13 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion FROM dbo.dist_dtDistribuidores WHERE 1=1"); var parameters = new DynamicParameters(); + + if (soloActivos.HasValue) + { + sqlBuilder.Append(" AND Baja = @BajaStatus "); + parameters.Add("BajaStatus", !soloActivos.Value); + } + sqlBuilder.Append(" ORDER BY Nombre;"); try { @@ -92,7 +104,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion const string sql = @" SELECT d.Id_Distribuidor AS IdDistribuidor, d.Nombre, d.Contacto, d.NroDoc, d.Id_Zona AS IdZona, - d.Calle, d.Numero, d.Piso, d.Depto, d.Telefono, d.Email, d.Localidad, + d.Calle, d.Numero, d.Piso, d.Depto, d.Telefono, d.Email, d.Localidad, d.Baja, d.FechaBaja, z.Nombre AS NombreZona FROM dbo.dist_dtDistribuidores d LEFT JOIN dbo.dist_dtZonas z ON d.Id_Zona = z.Id_Zona @@ -139,7 +151,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion const string sql = @" SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona, - Calle, Numero, Piso, Depto, Telefono, Email, Localidad + Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdParam"; try @@ -223,10 +235,10 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion public async Task CreateAsync(Distribuidor nuevoDistribuidor, int idUsuario, IDbTransaction transaction) { const string sqlInsert = @" - INSERT INTO dbo.dist_dtDistribuidores (Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad) + INSERT INTO dbo.dist_dtDistribuidores (Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja) OUTPUT INSERTED.Id_Distribuidor AS IdDistribuidor, INSERTED.Nombre, INSERTED.Contacto, INSERTED.NroDoc, INSERTED.Id_Zona AS IdZona, - INSERTED.Calle, INSERTED.Numero, INSERTED.Piso, INSERTED.Depto, INSERTED.Telefono, INSERTED.Email, INSERTED.Localidad - VALUES (@Nombre, @Contacto, @NroDoc, @IdZona, @Calle, @Numero, @Piso, @Depto, @Telefono, @Email, @Localidad);"; + INSERTED.Calle, INSERTED.Numero, INSERTED.Piso, INSERTED.Depto, INSERTED.Telefono, INSERTED.Email, INSERTED.Localidad, INSERTED.Baja, INSERTED.FechaBaja + VALUES (@Nombre, @Contacto, @NroDoc, @IdZona, @Calle, @Numero, @Piso, @Depto, @Telefono, @Email, @Localidad, 0, NULL);"; var connection = transaction.Connection!; var inserted = await connection.QuerySingleAsync(sqlInsert, nuevoDistribuidor, transaction); @@ -234,8 +246,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion const string sqlInsertHistorico = @" INSERT INTO dbo.dist_dtDistribuidores_H - (Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod) - VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);"; + (Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod) + VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @BajaParam, @FechaBajaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);"; await connection.ExecuteAsync(sqlInsertHistorico, new { @@ -251,6 +263,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion TelefonoParam = inserted.Telefono, EmailParam = inserted.Email, LocalidadParam = inserted.Localidad, + BajaParam = inserted.Baja, + FechaBajaParam = inserted.FechaBaja, IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado" @@ -263,7 +277,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion var connection = transaction.Connection!; var actual = await connection.QuerySingleOrDefaultAsync( @"SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona, - Calle, Numero, Piso, Depto, Telefono, Email, Localidad + Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdDistribuidorParam", new { IdDistribuidorParam = distribuidorAActualizar.IdDistribuidor }, transaction); if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado."); @@ -275,8 +289,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion WHERE Id_Distribuidor = @IdDistribuidor;"; const string sqlInsertHistorico = @" INSERT INTO dbo.dist_dtDistribuidores_H - (Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod) - VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);"; + (Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod) + VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @BajaParam, @FechaBajaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);"; await connection.ExecuteAsync(sqlInsertHistorico, new { @@ -292,6 +306,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion TelefonoParam = actual.Telefono, EmailParam = actual.Email, LocalidadParam = actual.Localidad, + BajaParam = actual.Baja, + FechaBajaParam = actual.FechaBaja, IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado" @@ -306,7 +322,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion var connection = transaction.Connection!; var actual = await connection.QuerySingleOrDefaultAsync( @"SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona, - Calle, Numero, Piso, Depto, Telefono, Email, Localidad + Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdParam", new { IdParam = id }, transaction); if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado."); @@ -314,8 +330,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion const string sqlDelete = "DELETE FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdParam"; const string sqlInsertHistorico = @" INSERT INTO dbo.dist_dtDistribuidores_H - (Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod) - VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);"; + (Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod) + VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @BajaParam, @FechaBajaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);"; await connection.ExecuteAsync(sqlInsertHistorico, new { @@ -331,6 +347,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion TelefonoParam = actual.Telefono, EmailParam = actual.Email, LocalidadParam = actual.Localidad, + BajaParam = actual.Baja, + FechaBajaParam = actual.FechaBaja, IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado" @@ -340,6 +358,47 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion return rowsAffected == 1; } + public async Task ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario, IDbTransaction transaction) + { + var connection = transaction.Connection!; + var actual = await connection.QuerySingleOrDefaultAsync( + @"SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona, + Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja + FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdDistribuidorParam", + new { IdDistribuidorParam = id }, transaction); + if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado para dar de baja/alta."); + + const string sqlUpdate = "UPDATE dbo.dist_dtDistribuidores SET Baja = @BajaParam, FechaBaja = @FechaBajaParam WHERE Id_Distribuidor = @IdDistribuidorParam;"; + const string sqlInsertHistorico = @" + INSERT INTO dbo.dist_dtDistribuidores_H + (Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod) + VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @BajaNuevaParam, @FechaBajaNuevaParam, @IdUsuarioParam, @FechaModParam, @TipoModHistParam);"; + + await connection.ExecuteAsync(sqlInsertHistorico, new + { + IdDistribuidorParam = actual.IdDistribuidor, + NombreParam = actual.Nombre, + ContactoParam = actual.Contacto, + NroDocParam = actual.NroDoc, + IdZonaParam = actual.IdZona, + CalleParam = actual.Calle, + NumeroParam = actual.Numero, + PisoParam = actual.Piso, + DeptoParam = actual.Depto, + TelefonoParam = actual.Telefono, + EmailParam = actual.Email, + LocalidadParam = actual.Localidad, + BajaNuevaParam = darDeBaja, + FechaBajaNuevaParam = (darDeBaja ? fechaBaja : null), + IdUsuarioParam = idUsuario, + FechaModParam = DateTime.Now, + TipoModHistParam = (darDeBaja ? "Baja" : "Alta") + }, transaction); + + var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new { BajaParam = darDeBaja, FechaBajaParam = (darDeBaja ? fechaBaja : null), IdDistribuidorParam = id }, transaction); + return rowsAffected == 1; + } + public async Task> GetHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModifico, string? tipoModificacion, diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IDistribuidorRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IDistribuidorRepository.cs index 5120c37..c5ceec3 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IDistribuidorRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IDistribuidorRepository.cs @@ -8,16 +8,17 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion { public interface IDistribuidorRepository { - Task> GetAllAsync(string? nombreFilter, string? nroDocFilter); + Task> GetAllAsync(string? nombreFilter, string? nroDocFilter, bool? soloActivos = true); Task<(Distribuidor? Distribuidor, string? NombreZona)> GetByIdAsync(int id); Task GetByIdSimpleAsync(int id); // Para uso interno en el servicio Task CreateAsync(Distribuidor nuevoDistribuidor, int idUsuario, IDbTransaction transaction); Task UpdateAsync(Distribuidor distribuidorAActualizar, int idUsuario, IDbTransaction transaction); Task DeleteAsync(int id, int idUsuario, IDbTransaction transaction); + Task ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario, IDbTransaction transaction); Task ExistsByNroDocAsync(string nroDoc, int? excludeIdDistribuidor = null); Task ExistsByNameAsync(string nombre, int? excludeIdDistribuidor = null); Task IsInUseAsync(int id); // Verificar en dist_EntradasSalidas, cue_PagosDistribuidor, dist_PorcPago - Task> GetAllDropdownAsync(); + Task> GetAllDropdownAsync(bool? soloActivos = true); Task ObtenerLookupPorIdAsync(int id); Task> GetHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/PorcPagoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/PorcPagoRepository.cs index 972e8a7..685bff6 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/PorcPagoRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/PorcPagoRepository.cs @@ -30,7 +30,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion d.Nombre AS NombreDistribuidor FROM dbo.dist_PorcPago pp INNER JOIN dbo.dist_dtDistribuidores d ON pp.Id_Distribuidor = d.Id_Distribuidor - WHERE pp.Id_Publicacion = @IdPublicacionParam + WHERE pp.Id_Publicacion = @IdPublicacionParam AND d.Baja = 0 ORDER BY d.Nombre, pp.VigenciaD DESC"; try { diff --git a/Backend/GestionIntegral.Api/Models/Distribucion/Distribuidor.cs b/Backend/GestionIntegral.Api/Models/Distribucion/Distribuidor.cs index 2a5dca7..1582510 100644 --- a/Backend/GestionIntegral.Api/Models/Distribucion/Distribuidor.cs +++ b/Backend/GestionIntegral.Api/Models/Distribucion/Distribuidor.cs @@ -14,5 +14,7 @@ namespace GestionIntegral.Api.Models.Distribucion public string? Telefono { get; set; } public string? Email { get; set; } public string? Localidad { get; set; } + public bool Baja { get; set; } // Baja (bit, NOT NULL, DEFAULT 0) + public DateTime? FechaBaja { get; set; } // FechaBaja (datetime2(0), NULL) } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Distribucion/DistribuidorHistorico.cs b/Backend/GestionIntegral.Api/Models/Distribucion/DistribuidorHistorico.cs index 3a68b68..fbf2d7b 100644 --- a/Backend/GestionIntegral.Api/Models/Distribucion/DistribuidorHistorico.cs +++ b/Backend/GestionIntegral.Api/Models/Distribucion/DistribuidorHistorico.cs @@ -16,6 +16,8 @@ namespace GestionIntegral.Api.Models.Distribucion public string? Telefono { get; set; } public string? Email { get; set; } public string? Localidad { get; set; } + public bool? Baja { get; set; } + public DateTime? FechaBaja { get; set; } public int Id_Usuario { get; set; } public DateTime FechaMod { get; set; } public string TipoMod { get; set; } = string.Empty; diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/DistribuidorDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/DistribuidorDto.cs index b415a14..889546f 100644 --- a/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/DistribuidorDto.cs +++ b/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/DistribuidorDto.cs @@ -15,5 +15,7 @@ namespace GestionIntegral.Api.Dtos.Distribucion public string? Telefono { get; set; } public string? Email { get; set; } public string? Localidad { get; set; } + public bool Baja { get; set; } + public DateTime? FechaBaja { get; set; } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/ToggleBajaDistribuidorDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/ToggleBajaDistribuidorDto.cs new file mode 100644 index 0000000..ff5f59b --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/ToggleBajaDistribuidorDto.cs @@ -0,0 +1,10 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Distribucion +{ + public class ToggleBajaDistribuidorDto + { + public bool DarDeBaja { get; set; } + public DateTime? FechaBaja { get; set; } + } +} diff --git a/Backend/GestionIntegral.Api/Program.cs b/Backend/GestionIntegral.Api/Program.cs index 87684e2..064ad81 100644 --- a/Backend/GestionIntegral.Api/Program.cs +++ b/Backend/GestionIntegral.Api/Program.cs @@ -187,7 +187,7 @@ builder.Services.AddCors(options => policy => { policy.WithOrigins( - "http://localhost:5175", // Para desarrollo local + "http://localhost:5173", // Para desarrollo local "https://gestion.eldiaservicios.com" // Para producción ) .AllowAnyHeader() diff --git a/Backend/GestionIntegral.Api/Services/Anomalia/AlertaService.cs b/Backend/GestionIntegral.Api/Services/Anomalia/AlertaService.cs index 2267c2b..ec732f7 100644 --- a/Backend/GestionIntegral.Api/Services/Anomalia/AlertaService.cs +++ b/Backend/GestionIntegral.Api/Services/Anomalia/AlertaService.cs @@ -22,13 +22,16 @@ namespace GestionIntegral.Api.Services.Anomalia public async Task> ObtenerAlertasNoLeidasAsync() { // Apunta a la nueva tabla genérica 'Sistema_Alertas' - var query = "SELECT * FROM Sistema_Alertas WHERE Leida = 0 ORDER BY FechaDeteccion DESC"; + //var query = "SELECT * FROM Sistema_Alertas WHERE Leida = 0 ORDER BY FechaDeteccion DESC"; try { using (var connection = _dbConnectionFactory.CreateConnection()) { + /* var alertas = await connection.QueryAsync(query); - return alertas ?? Enumerable.Empty(); + return alertas ?? Enumerable.Empty(); + */ + return Enumerable.Empty(); } } catch (System.Exception ex) @@ -40,17 +43,20 @@ namespace GestionIntegral.Api.Services.Anomalia public async Task<(bool Exito, string? Error)> MarcarComoLeidaAsync(int idAlerta) { - var query = "UPDATE Sistema_Alertas SET Leida = 1 WHERE IdAlerta = @IdAlerta"; + //var query = "UPDATE Sistema_Alertas SET Leida = 1 WHERE IdAlerta = @IdAlerta"; try { using (var connection = _dbConnectionFactory.CreateConnection()) { + /* var result = await connection.ExecuteAsync(query, new { IdAlerta = idAlerta }); if (result > 0) { return (true, null); } return (false, "La alerta no fue encontrada o ya estaba marcada."); + */ + return (true, null); // Retornar éxito silencioso por ahora } } catch (System.Exception ex) @@ -62,15 +68,18 @@ namespace GestionIntegral.Api.Services.Anomalia public async Task<(bool Exito, string? Error)> MarcarGrupoComoLeidoAsync(string tipoAlerta, int idEntidad) { - var query = "UPDATE Sistema_Alertas SET Leida = 1 WHERE TipoAlerta = @TipoAlerta AND IdEntidad = @IdEntidad AND Leida = 0"; + //var query = "UPDATE Sistema_Alertas SET Leida = 1 WHERE TipoAlerta = @TipoAlerta AND IdEntidad = @IdEntidad AND Leida = 0"; try { using (var connection = _dbConnectionFactory.CreateConnection()) { + /* var result = await connection.ExecuteAsync(query, new { TipoAlerta = tipoAlerta, IdEntidad = idEntidad }); // No es un error si no se actualizan filas (puede que no hubiera ninguna para ese grupo) _logger.LogInformation("Marcadas como leídas {Count} alertas para Tipo: {Tipo}, EntidadID: {IdEntidad}", result, tipoAlerta, idEntidad); return (true, null); + */ + return (true, null); } } catch (System.Exception ex) diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/DistribuidorService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/DistribuidorService.cs index 0f02456..120dd7c 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/DistribuidorService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/DistribuidorService.cs @@ -56,20 +56,22 @@ namespace GestionIntegral.Api.Services.Distribucion Depto = data.Distribuidor.Depto, Telefono = data.Distribuidor.Telefono, Email = data.Distribuidor.Email, - Localidad = data.Distribuidor.Localidad + Localidad = data.Distribuidor.Localidad, + Baja = data.Distribuidor.Baja, + FechaBaja = data.Distribuidor.FechaBaja }; } - public async Task> ObtenerTodosAsync(string? nombreFilter, string? nroDocFilter) + public async Task> ObtenerTodosAsync(string? nombreFilter, string? nroDocFilter, bool? soloActivos = true) { - var data = await _distribuidorRepository.GetAllAsync(nombreFilter, nroDocFilter); + var data = await _distribuidorRepository.GetAllAsync(nombreFilter, nroDocFilter, soloActivos); // Filtrar nulos y asegurar al compilador que no hay nulos en la lista final return data.Select(MapToDto).Where(dto => dto != null).Select(dto => dto!); } - public async Task> GetAllDropdownAsync() + public async Task> GetAllDropdownAsync(bool? soloActivos = true) { - var data = await _distribuidorRepository.GetAllDropdownAsync(); + var data = await _distribuidorRepository.GetAllDropdownAsync(soloActivos); // Asegurar que el resultado no sea nulo y no contiene elementos nulos if (data == null) { @@ -223,6 +225,31 @@ namespace GestionIntegral.Api.Services.Distribucion } } + public async Task<(bool Exito, string? Error)> ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario) + { + var distribuidorExistente = await _distribuidorRepository.GetByIdSimpleAsync(id); + if (distribuidorExistente == null) return (false, "Distribuidor no encontrado."); + + using var connection = _connectionFactory.CreateConnection(); + if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); + using var transaction = connection.BeginTransaction(); + try + { + var toggled = await _distribuidorRepository.ToggleBajaAsync(id, darDeBaja, fechaBaja, idUsuario, transaction); + if (!toggled) throw new DataException("Error al cambiar estado de baja."); + transaction.Commit(); + _logger.LogInformation("Distribuidor ID {IdDistribuidor} dado de {Estado} por Usuario ID {IdUsuario}.", id, darDeBaja ? "baja" : "alta", idUsuario); + return (true, null); + } + catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Distribuidor no encontrado."); } + catch (Exception ex) + { + try { transaction.Rollback(); } catch { } + _logger.LogError(ex, "Error ToggleBajaAsync Distribuidor ID: {IdDistribuidor}", id); + return (false, $"Error interno: {ex.Message}"); + } + } + public async Task> ObtenerHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModifico, string? tipoModificacion, diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/IDistribuidorService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/IDistribuidorService.cs index bb14439..69ab155 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/IDistribuidorService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/IDistribuidorService.cs @@ -7,12 +7,13 @@ namespace GestionIntegral.Api.Services.Distribucion { public interface IDistribuidorService { - Task> ObtenerTodosAsync(string? nombreFilter, string? nroDocFilter); + Task> ObtenerTodosAsync(string? nombreFilter, string? nroDocFilter, bool? soloActivos = true); Task ObtenerPorIdAsync(int id); Task<(DistribuidorDto? Distribuidor, string? Error)> CrearAsync(CreateDistribuidorDto createDto, int idUsuario); Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateDistribuidorDto updateDto, int idUsuario); Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario); - Task> GetAllDropdownAsync(); + Task<(bool Exito, string? Error)> ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario); + Task> GetAllDropdownAsync(bool? soloActivos = true); Task ObtenerLookupPorIdAsync(int id); Task> ObtenerHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, diff --git a/Frontend/src/models/dtos/Distribucion/DistribuidorDto.ts b/Frontend/src/models/dtos/Distribucion/DistribuidorDto.ts index 34e29e8..133b892 100644 --- a/Frontend/src/models/dtos/Distribucion/DistribuidorDto.ts +++ b/Frontend/src/models/dtos/Distribucion/DistribuidorDto.ts @@ -12,4 +12,6 @@ export interface DistribuidorDto { telefono?: string | null; email?: string | null; localidad?: string | null; + baja?: boolean; + fechaBaja?: string | null; } \ No newline at end of file diff --git a/Frontend/src/pages/Distribucion/GestionarDistribuidoresPage.tsx b/Frontend/src/pages/Distribucion/GestionarDistribuidoresPage.tsx index 461dbe7..4078d9e 100644 --- a/Frontend/src/pages/Distribucion/GestionarDistribuidoresPage.tsx +++ b/Frontend/src/pages/Distribucion/GestionarDistribuidoresPage.tsx @@ -1,14 +1,16 @@ // src/pages/Distribucion/GestionarDistribuidoresPage.tsx import React, { useState, useEffect, useCallback } from 'react'; import { - Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, + Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Switch, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination, - CircularProgress, Alert + CircularProgress, Alert, Chip, FormControlLabel, ListItemIcon, ListItemText } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import EditIcon from '@mui/icons-material/Edit'; import TrashIcon from '@mui/icons-material/Delete'; import MoreVertIcon from '@mui/icons-material/MoreVert'; +import ToggleOnIcon from '@mui/icons-material/ToggleOn'; +import ToggleOffIcon from '@mui/icons-material/ToggleOff'; import distribuidorService from '../../services/Distribucion/distribuidorService'; import type { DistribuidorDto } from '../../models/dtos/Distribucion/DistribuidorDto'; import type { CreateDistribuidorDto } from '../../models/dtos/Distribucion/CreateDistribuidorDto'; @@ -24,6 +26,7 @@ const GestionarDistribuidoresPage: React.FC = () => { const [error, setError] = useState(null); const [filtroNombre, setFiltroNombre] = useState(''); const [filtroNroDoc, setFiltroNroDoc] = useState(''); + const [filtroSoloActivos, setFiltroSoloActivos] = useState(true); const [modalOpen, setModalOpen] = useState(false); const [editingDistribuidor, setEditingDistribuidor] = useState(null); @@ -49,12 +52,12 @@ const GestionarDistribuidoresPage: React.FC = () => { } setLoading(true); setError(null); setApiErrorMessage(null); try { - const data = await distribuidorService.getAllDistribuidores(filtroNombre, filtroNroDoc); + const data = await distribuidorService.getAllDistribuidores(filtroNombre, filtroNroDoc, filtroSoloActivos); setDistribuidores(data); } catch (err) { console.error(err); setError('Error al cargar los distribuidores.'); } finally { setLoading(false); } - }, [filtroNombre, filtroNroDoc, puedeVer]); + }, [filtroNombre, filtroNroDoc, filtroSoloActivos, puedeVer]); useEffect(() => { cargarDistribuidores(); }, [cargarDistribuidores]); @@ -94,6 +97,21 @@ const GestionarDistribuidoresPage: React.FC = () => { handleMenuClose(); }; + const handleToggleBaja = async (distribuidor: DistribuidorDto) => { + setApiErrorMessage(null); + const accion = distribuidor.baja ? "reactivar" : "dar de baja"; + if (window.confirm(`¿Está seguro de que desea ${accion} a ${distribuidor.nombre}?`)) { + try { + await distribuidorService.toggleBajaDistribuidor(distribuidor.idDistribuidor, { darDeBaja: !distribuidor.baja, fechaBaja: !distribuidor.baja ? new Date().toISOString() : null }); + cargarDistribuidores(); + } catch (err: any) { + const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : `Error al ${accion} el distribuidor.`; + setApiErrorMessage(message); + } + } + handleMenuClose(); + }; + const handleMenuOpen = (event: React.MouseEvent, distribuidor: DistribuidorDto) => { setAnchorEl(event.currentTarget); setSelectedDistribuidorRow(distribuidor); }; @@ -132,7 +150,17 @@ const GestionarDistribuidoresPage: React.FC = () => { onChange={(e) => setFiltroNroDoc(e.target.value)} sx={{ flexGrow: 1, minWidth: '200px' }} /> - {/* */} + setFiltroSoloActivos(e.target.checked)} + size="small" + /> + } + label="Ver Activos" + sx={{ flexShrink: 0 }} + /> {puedeCrear && ( @@ -150,6 +178,7 @@ const GestionarDistribuidoresPage: React.FC = () => { NombreNro. Doc. ContactoZona TeléfonoLocalidad + Estado {(puedeModificar || puedeEliminar) && Acciones} @@ -157,10 +186,11 @@ const GestionarDistribuidoresPage: React.FC = () => { No se encontraron distribuidores. ) : ( displayData.map((d) => ( - + {d.nombre}{d.nroDoc} {d.contacto || '-'}{d.nombreZona || '-'} {d.telefono || '-'}{d.localidad || '-'} + {d.baja ? : } {(puedeModificar || puedeEliminar) && ( handleMenuOpen(e, d)} disabled={!puedeModificar && !puedeEliminar}> @@ -179,8 +209,24 @@ const GestionarDistribuidoresPage: React.FC = () => { )} - {puedeModificar && ( { handleOpenModal(selectedDistribuidorRow!); handleMenuClose(); }}>Modificar)} - {puedeEliminar && ( handleDelete(selectedDistribuidorRow!.idDistribuidor)}>Eliminar)} + {puedeModificar && selectedDistribuidorRow && ( + { handleOpenModal(selectedDistribuidorRow); handleMenuClose(); }}> + + Modificar + + )} + {puedeEliminar && selectedDistribuidorRow && ( + handleToggleBaja(selectedDistribuidorRow)}> + {selectedDistribuidorRow.baja ? : } + {selectedDistribuidorRow.baja ? 'Reactivar' : 'Dar de Baja'} + + )} + {puedeEliminar && selectedDistribuidorRow && ( + handleDelete(selectedDistribuidorRow.idDistribuidor)}> + + Eliminar (Físico) + + )} {(!puedeModificar && !puedeEliminar) && Sin acciones} diff --git a/Frontend/src/services/Distribucion/distribuidorService.ts b/Frontend/src/services/Distribucion/distribuidorService.ts index 85dc49d..dcc74cd 100644 --- a/Frontend/src/services/Distribucion/distribuidorService.ts +++ b/Frontend/src/services/Distribucion/distribuidorService.ts @@ -5,8 +5,10 @@ import type { UpdateDistribuidorDto } from '../../models/dtos/Distribucion/Updat import type { DistribuidorDropdownDto } from '../../models/dtos/Distribucion/DistribuidorDropdownDto'; import type { DistribuidorLookupDto } from '../../models/dtos/Distribucion/DistribuidorLookupDto'; -const getAllDistribuidores = async (nombreFilter?: string, nroDocFilter?: string): Promise => { - const params: Record = {}; +const getAllDistribuidores = async (nombreFilter?: string, nroDocFilter?: string, soloActivos: boolean = true): Promise => { + const params: Record = { + soloActivos: soloActivos + }; if (nombreFilter) params.nombre = nombreFilter; if (nroDocFilter) params.nroDoc = nroDocFilter; @@ -37,11 +39,15 @@ const deleteDistribuidor = async (id: number): Promise => { await apiClient.delete(`/distribuidores/${id}`); }; -const getAllDistribuidoresDropdown = async (): Promise => { - const response = await apiClient.get('/distribuidores/dropdown'); +const getAllDistribuidoresDropdown = async (soloActivos: boolean = true): Promise => { + const response = await apiClient.get('/distribuidores/dropdown', { params: { soloActivos } }); return response.data; }; +const toggleBajaDistribuidor = async (id: number, data: { darDeBaja: boolean, fechaBaja: string | null }): Promise => { + await apiClient.put(`/distribuidores/${id}/toggle-baja`, data); +}; + const distribuidorService = { getAllDistribuidores, getDistribuidorById, @@ -50,6 +56,7 @@ const distribuidorService = { deleteDistribuidor, getAllDistribuidoresDropdown, getDistribuidorLookupById, + toggleBajaDistribuidor, }; export default distribuidorService; \ No newline at end of file