diff --git a/Backend/GestionIntegral.Api/Controllers/Auditoria/AuditoriaController.cs b/Backend/GestionIntegral.Api/Controllers/Auditoria/AuditoriaController.cs new file mode 100644 index 0000000..6c9bb22 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Auditoria/AuditoriaController.cs @@ -0,0 +1,278 @@ +using GestionIntegral.Api.Services.Usuarios; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Security.Claims; // Para ClaimTypes +using GestionIntegral.Api.Services.Contables; // Para IPagoDistribuidorService, etc. +using GestionIntegral.Api.Dtos.Contables; // Para PagoDistribuidorHistorialDto, etc. +using GestionIntegral.Api.Services.Distribucion; +using GestionIntegral.Api.Dtos.Distribucion; +using GestionIntegral.Api.Dtos.Usuarios.Auditoria; +using GestionIntegral.Api.Dtos.Auditoria; + + +namespace GestionIntegral.Api.Controllers +{ + [Route("api/auditoria")] + [ApiController] + [Authorize] + public class AuditoriaController : ControllerBase + { + private readonly IUsuarioService _usuarioService; + private readonly IPagoDistribuidorService _pagoDistribuidorService; + private readonly INotaCreditoDebitoService _notaCreditoDebitoService; + private readonly IEntradaSalidaDistService _esDistService; + private readonly IDistribuidorService _distribuidorService; + private readonly IEntradaSalidaCanillaService _esCanillaService; + private readonly INovedadCanillaService _novedadCanillaService; + private readonly ICanillaService _canillaService; + private readonly ISaldoService _saldoService; + private readonly ITipoPagoService _tipoPagoService; + private readonly IEmpresaService _empresaService; + private readonly ILogger _logger; + + // Permiso general para ver cualquier auditoría. + // Podrías tener permisos más granulares por tipo de auditoría si es necesario. + private const string PermisoVerAuditoria = "AU_GENERAL_VIEW"; // Define este permiso + + public AuditoriaController( + IUsuarioService usuarioService, + IPagoDistribuidorService pagoDistribuidorService, + INotaCreditoDebitoService notaCreditoDebitoService, + IEntradaSalidaDistService esDistService, + IDistribuidorService distribuidorService, + IEntradaSalidaCanillaService esCanillaService, + INovedadCanillaService novedadCanillaService, + ICanillaService canillaService, + ISaldoService saldoService, + ITipoPagoService tipoPagoService, + IEmpresaService empresaService, + ILogger logger) + { + _usuarioService = usuarioService; + _pagoDistribuidorService = pagoDistribuidorService; + _notaCreditoDebitoService = notaCreditoDebitoService; + _esDistService = esDistService; + _distribuidorService = distribuidorService; + _esCanillaService = esCanillaService; + _novedadCanillaService = novedadCanillaService; + _canillaService = canillaService; + _saldoService = saldoService; + _tipoPagoService = tipoPagoService; + _empresaService = empresaService; + _logger = logger; + } + + private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc); + private int? GetCurrentUserId() // Podría no ser necesario aquí si solo filtramos por usuario modificador + { + if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId; + return null; + } + + [HttpGet("pagos-distribuidores")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetHistorialPagosDistribuidor( + [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta, + [FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion, + [FromQuery] int? idPagoAfectado) // Parámetro para filtrar por un ID de pago específico + { + if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); // O un permiso más específico si lo creas + try + { + var historial = await _pagoDistribuidorService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPagoAfectado); + return Ok(historial ?? Enumerable.Empty()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error obteniendo historial de pagos de distribuidores."); + return StatusCode(500, "Error interno al obtener historial de pagos."); + } + } + + [HttpGet("notas-credito-debito")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetHistorialNotasCD( + [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta, + [FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion, + [FromQuery] int? idNotaAfectada) // ID de la nota original + { + if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); + try + { + var historial = await _notaCreditoDebitoService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idNotaAfectada); + return Ok(historial ?? Enumerable.Empty()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error obteniendo historial de notas C/D."); + return StatusCode(500, "Error interno al obtener historial de notas C/D."); + } + } + + [HttpGet("entradas-salidas-dist")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetHistorialEntradasSalidasDist( + [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta, + [FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion, + [FromQuery] int? idParteAfectada) // ID del movimiento original + { + if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); + try + { + var historial = await _esDistService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idParteAfectada); + return Ok(historial ?? Enumerable.Empty()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error obteniendo historial de E/S Distribuidores."); + return StatusCode(500, "Error interno al obtener historial de E/S Distribuidores."); + } + } + + [HttpGet("entradas-salidas-canilla")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetHistorialEntradasSalidasCanilla( + [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta, + [FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion, + [FromQuery] int? idParteAfectada) + { + if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); + try + { + var historial = await _esCanillaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idParteAfectada); + return Ok(historial ?? Enumerable.Empty()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error obteniendo historial de E/S Canillitas."); + return StatusCode(500, "Error interno al obtener historial de E/S Canillitas."); + } + } + + [HttpGet("novedades-canilla")] // Endpoint consistente con el servicio frontend + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetHistorialNovedadesCanilla( + [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta, + [FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion, + [FromQuery] int? idNovedadAfectada) // ID de la novedad original + { + if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); + try + { + var historial = await _novedadCanillaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idNovedadAfectada); + return Ok(historial ?? Enumerable.Empty()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error obteniendo historial de Novedades de Canillitas."); + return StatusCode(500, "Error interno al obtener historial de Novedades de Canillitas."); + } + } + + [HttpGet("ajustes-saldo")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetHistorialAjustesSaldo( + [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta, + [FromQuery] int? idUsuarioModifico, + [FromQuery] string? destino, [FromQuery] int? idDestino, [FromQuery] int? idEmpresa) + { + if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); // O un permiso más específico si lo creas + try + { + var historial = await _saldoService.ObtenerHistorialAjustesAsync(fechaDesde, fechaHasta, idUsuarioModifico, destino, idDestino, idEmpresa); + return Ok(historial ?? Enumerable.Empty()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error obteniendo historial de ajustes de saldo."); + return StatusCode(500, "Error interno al obtener historial de ajustes de saldo."); + } + } + + [HttpGet("tipos-pago")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetHistorialTiposPago( + [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta, + [FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion, + [FromQuery] int? idTipoPagoAfectado) + { + if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); + try + { + // Asumiendo que _tipoPagoService está inyectado + var historial = await _tipoPagoService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idTipoPagoAfectado); + return Ok(historial ?? Enumerable.Empty()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error obteniendo historial de Tipos de Pago."); + return StatusCode(500, "Error interno al obtener historial de Tipos de Pago."); + } + } + + [HttpGet("canillitas-maestro")] // Endpoint para el historial del maestro de canillitas + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetHistorialCanillitasMaestro( + [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta, + [FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion, + [FromQuery] int? idCanillaAfectado) // ID del canillita original + { + if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); + try + { + var historial = await _canillaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idCanillaAfectado); + return Ok(historial ?? Enumerable.Empty()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error obteniendo historial de Canillitas (Maestro)."); + return StatusCode(500, "Error interno al obtener historial de Canillitas (Maestro)."); + } + } + + [HttpGet("distribuidores-maestro")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetHistorialDistribuidoresMaestro( + [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta, + [FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion, + [FromQuery] int? idDistribuidorAfectado) + { + if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); + try + { + var historial = await _distribuidorService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idDistribuidorAfectado); + return Ok(historial ?? Enumerable.Empty()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error obteniendo historial de Distribuidores (Maestro)."); + return StatusCode(500, "Error interno al obtener historial de Distribuidores (Maestro)."); + } + } + + [HttpGet("empresas-maestro")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetHistorialEmpresasMaestro( + [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta, + [FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion, + [FromQuery] int? idEmpresaAfectada) + { + if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); + try + { + // Asumiendo que _empresaService está inyectado + var historial = await _empresaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idEmpresaAfectada); + return Ok(historial ?? Enumerable.Empty()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error obteniendo historial de Empresas (Maestro)."); + return StatusCode(500, "Error interno al obtener historial de Empresas (Maestro)."); + } + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Distribucion/CambiosParadaController.cs b/Backend/GestionIntegral.Api/Controllers/Distribucion/CambiosParadaController.cs new file mode 100644 index 0000000..10abf51 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Distribucion/CambiosParadaController.cs @@ -0,0 +1,129 @@ +using GestionIntegral.Api.Dtos.Distribucion; + using GestionIntegral.Api.Services.Distribucion; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Logging; + using System; + using System.Collections.Generic; + using System.Security.Claims; + using System.Threading.Tasks; + + namespace GestionIntegral.Api.Controllers.Distribucion + { + [Route("api/canillas/{idCanilla}/paradas")] // Anidado bajo canillas + [ApiController] + [Authorize] + public class CambiosParadaController : ControllerBase + { + private readonly ICambioParadaService _paradaService; + private readonly ILogger _logger; + private const string PermisoGestionarParadas = "CG007"; + + public CambiosParadaController(ICambioParadaService paradaService, ILogger logger) + { + _paradaService = paradaService; + _logger = logger; + } + + private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc); + private int? GetCurrentUserId() + { + if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId; + _logger.LogWarning("No se pudo obtener el UserId del token JWT en CambiosParadaController."); + return null; + } + + // GET: api/canillas/{idCanilla}/paradas + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task GetParadasPorCanilla(int idCanilla) + { + // Podría usarse CG001 (Ver Canilla) o CG007 para ver el historial de paradas + if (!TienePermiso("CG001") && !TienePermiso(PermisoGestionarParadas)) return Forbid(); + + var paradas = await _paradaService.ObtenerPorCanillaAsync(idCanilla); + return Ok(paradas); + } + + // POST: api/canillas/{idCanilla}/paradas + [HttpPost] + [ProducesResponseType(typeof(CambioParadaDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task CreateParada(int idCanilla, [FromBody] CreateCambioParadaDto createDto) + { + if (!TienePermiso(PermisoGestionarParadas)) return Forbid(); + if (!ModelState.IsValid) return BadRequest(ModelState); + var userId = GetCurrentUserId(); + if (userId == null) return Unauthorized(); + + var (paradaDto, error) = await _paradaService.CrearNuevaParadaAsync(idCanilla, createDto, userId.Value); + if (error != null) return BadRequest(new { message = error }); + if (paradaDto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear la parada."); + + // La ruta para "GetById" podría estar en otro controlador si decides separar los endpoints de "paradas individuales" + // Por ahora, asumimos que habrá un endpoint para obtener una parada por su ID de registro. + return CreatedAtAction(nameof(GetParadaById), new { idCanilla = idCanilla, idRegistroParada = paradaDto.IdRegistro }, paradaDto); + } + + // GET: api/canillas/{idCanilla}/paradas/{idRegistroParada} (o api/paradas/{idRegistroParada}) + // Este endpoint es opcional, pero útil si necesitas obtener una parada específica por su ID de registro + [HttpGet("{idRegistroParada:int}", Name = "GetParadaById")] + [ProducesResponseType(typeof(CambioParadaDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetParadaById(int idCanilla, int idRegistroParada) // idCanilla es parte de la ruta base + { + if (!TienePermiso("CG001") && !TienePermiso(PermisoGestionarParadas)) return Forbid(); + var parada = await _paradaService.ObtenerPorIdAsync(idRegistroParada); + if (parada == null || parada.IdCanilla != idCanilla) return NotFound(new { message = "Registro de parada no encontrado para este canillita."}); + return Ok(parada); + } + + + // PUT: api/paradas/{idRegistroParada}/cerrar (Ruta ejemplo para cerrar una parada) + // O podrías usar PUT api/canillas/{idCanilla}/paradas/{idRegistroParada} y que el DTO solo tenga VigenciaH + [HttpPut("~/api/paradas/{idRegistroParada}/cerrar")] // Ruta a nivel raíz para paradas si se edita por IdRegistro + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task CerrarVigenciaParada(int idRegistroParada, [FromBody] UpdateCambioParadaDto updateDto) + { + if (!TienePermiso(PermisoGestionarParadas)) return Forbid(); + if (!ModelState.IsValid) return BadRequest(ModelState); + var userId = GetCurrentUserId(); + if (userId == null) return Unauthorized(); + + var (exito, error) = await _paradaService.CerrarParadaAsync(idRegistroParada, updateDto, userId.Value); + if (!exito) + { + if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error }); + return BadRequest(new { message = error }); + } + return NoContent(); + } + + // DELETE: api/paradas/{idRegistroParada} + [HttpDelete("~/api/paradas/{idRegistroParada}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteParada(int idRegistroParada) + { + if (!TienePermiso(PermisoGestionarParadas)) return Forbid(); + var userId = GetCurrentUserId(); + if (userId == null) return Unauthorized(); + + var (exito, error) = await _paradaService.EliminarParadaAsync(idRegistroParada, userId.Value); + if (!exito) + { + if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error }); + return BadRequest(new { message = error }); + } + return NoContent(); + } + } + } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Contables/INotaCreditoDebitoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Contables/INotaCreditoDebitoRepository.cs index 1a3ec4e..be647fe 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Contables/INotaCreditoDebitoRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Contables/INotaCreditoDebitoRepository.cs @@ -16,6 +16,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables Task CreateAsync(NotaCreditoDebito nuevaNota, int idUsuario, IDbTransaction transaction); Task UpdateAsync(NotaCreditoDebito notaAActualizar, int idUsuario, IDbTransaction transaction); Task DeleteAsync(int idNota, int idUsuario, IDbTransaction transaction); - // No se suele validar unicidad por referencia, ya que podría repetirse. + Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idNotaOriginal); // Para filtrar por una nota específica } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Contables/IPagoDistribuidorRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Contables/IPagoDistribuidorRepository.cs index bc5f711..e40b4df 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Contables/IPagoDistribuidorRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Contables/IPagoDistribuidorRepository.cs @@ -17,5 +17,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables Task UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction); Task DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction); Task ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null); + Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idPagoOriginal); // Para filtrar por un pago específico } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Contables/ISaldoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Contables/ISaldoRepository.cs index 6afe7af..7c48384 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Contables/ISaldoRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Contables/ISaldoRepository.cs @@ -24,5 +24,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables Task GetSaldoAsync(string destino, int idDestino, int idEmpresa, IDbTransaction? transaction = null); // Para registrar el historial de ajuste Task CreateSaldoAjusteHistorialAsync(SaldoAjusteHistorial historialEntry, IDbTransaction transaction); + Task> GetHistorialAjustesAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, + string? destino, int? idDestino, int? idEmpresa); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Contables/ITipoPagoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Contables/ITipoPagoRepository.cs index 8c9ca08..97e2328 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Contables/ITipoPagoRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Contables/ITipoPagoRepository.cs @@ -13,5 +13,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables Task DeleteAsync(int id, int idUsuario); // Devuelve true si fue exitoso Task ExistsByNameAsync(string nombre, int? excludeId = null); Task IsInUseAsync(int id); + Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idTipoPagoOriginal); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Contables/NotaCreditoDebitoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Contables/NotaCreditoDebitoRepository.cs index 06d88e9..cf16c5d 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Contables/NotaCreditoDebitoRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Contables/NotaCreditoDebitoRepository.cs @@ -41,7 +41,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables if (idDestino.HasValue) { sqlBuilder.Append(" AND Id_Destino = @IdDestinoParam"); parameters.Add("IdDestinoParam", idDestino.Value); } if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); } if (!string.IsNullOrWhiteSpace(tipoNota)) { sqlBuilder.Append(" AND Tipo = @TipoParam"); parameters.Add("TipoParam", tipoNota); } - + sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Nota DESC;"); try @@ -86,11 +86,20 @@ namespace GestionIntegral.Api.Data.Repositories.Contables var inserted = await transaction.Connection!.QuerySingleAsync(sqlInsert, nuevaNota, transaction); if (inserted == null || inserted.IdNota == 0) throw new DataException("Error al crear la nota o ID no generado."); - await transaction.Connection!.ExecuteAsync(sqlHistorico, new { - IdNotaHist = inserted.IdNota, DestinoHist = inserted.Destino, IdDestinoHist = inserted.IdDestino, - ReferenciaHist = inserted.Referencia, TipoHist = inserted.Tipo, FechaHist = inserted.Fecha, MontoHist = inserted.Monto, - ObservacionesHist = inserted.Observaciones, IdEmpresaHist = inserted.IdEmpresa, - IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creada" + await transaction.Connection!.ExecuteAsync(sqlHistorico, new + { + IdNotaHist = inserted.IdNota, + DestinoHist = inserted.Destino, + IdDestinoHist = inserted.IdDestino, + ReferenciaHist = inserted.Referencia, + TipoHist = inserted.Tipo, + FechaHist = inserted.Fecha, + MontoHist = inserted.Monto, + ObservacionesHist = inserted.Observaciones, + IdEmpresaHist = inserted.IdEmpresa, + IdUsuarioHist = idUsuario, + FechaModHist = DateTime.Now, + TipoModHist = "Creada" }, transaction); return inserted; } @@ -107,23 +116,33 @@ namespace GestionIntegral.Api.Data.Repositories.Contables UPDATE dbo.cue_CreditosDebitos SET Monto = @Monto, Observaciones = @Observaciones WHERE Id_Nota = @IdNota;"; - const string sqlHistorico = @" + const string sqlHistorico = @" INSERT INTO dbo.cue_CreditosDebitos_H (Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod) VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);"; - - await transaction.Connection!.ExecuteAsync(sqlHistorico, new { - IdNotaHist = actual.IdNota, DestinoHist = actual.Destino, IdDestinoHist = actual.IdDestino, ReferenciaHist = actual.Referencia, - TipoHist = actual.Tipo, FechaHist = actual.Fecha, MontoHist = actual.Monto, // Valor ANTERIOR - ObservacionesHist = actual.Observaciones, IdEmpresaHist = actual.IdEmpresa, // Valor ANTERIOR - IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizada" + + await transaction.Connection!.ExecuteAsync(sqlHistorico, new + { + IdNotaHist = actual.IdNota, + DestinoHist = actual.Destino, + IdDestinoHist = actual.IdDestino, + ReferenciaHist = actual.Referencia, + TipoHist = actual.Tipo, + FechaHist = actual.Fecha, + MontoHist = actual.Monto, // Valor ANTERIOR + ObservacionesHist = actual.Observaciones, + IdEmpresaHist = actual.IdEmpresa, // Valor ANTERIOR + IdUsuarioHist = idUsuario, + FechaModHist = DateTime.Now, + TipoModHist = "Actualizada" }, transaction); - var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new { + var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new + { notaAActualizar.Monto, notaAActualizar.Observaciones, notaAActualizar.IdNota - } , transaction); + }, transaction); return rowsAffected == 1; } @@ -140,14 +159,67 @@ namespace GestionIntegral.Api.Data.Repositories.Contables (Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod) VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);"; - await transaction.Connection!.ExecuteAsync(sqlHistorico, new { - IdNotaHist = actual.IdNota, DestinoHist = actual.Destino, IdDestinoHist = actual.IdDestino, ReferenciaHist = actual.Referencia, - TipoHist = actual.Tipo, FechaHist = actual.Fecha, MontoHist = actual.Monto, ObservacionesHist = actual.Observaciones, IdEmpresaHist = actual.IdEmpresa, - IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada" + await transaction.Connection!.ExecuteAsync(sqlHistorico, new + { + IdNotaHist = actual.IdNota, + DestinoHist = actual.Destino, + IdDestinoHist = actual.IdDestino, + ReferenciaHist = actual.Referencia, + TipoHist = actual.Tipo, + FechaHist = actual.Fecha, + MontoHist = actual.Monto, + ObservacionesHist = actual.Observaciones, + IdEmpresaHist = actual.IdEmpresa, + IdUsuarioHist = idUsuario, + FechaModHist = DateTime.Now, + TipoModHist = "Eliminada" }, transaction); var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdNotaParam = idNota }, transaction); return rowsAffected == 1; } + + public async Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idNotaOriginal) + { + using var connection = _cf.CreateConnection(); + var sqlBuilder = new StringBuilder(@" + SELECT + h.Id_Nota, h.Destino, h.Id_Destino, h.Referencia, h.Tipo, h.Fecha, h.Monto, + h.Observaciones, h.Id_Empresa, + h.Id_Usuario, h.FechaMod, h.TipoMod, + u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico + FROM dbo.cue_CreditosDebitos_H h + JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id + WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } + if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } + if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); } + if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); } + if (idNotaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Nota = @IdNotaOriginalParam"); parameters.Add("IdNotaOriginalParam", idNotaOriginal.Value); } + + sqlBuilder.Append(" ORDER BY h.FechaMod DESC;"); + + try + { + var result = await connection.QueryAsync( + sqlBuilder.ToString(), + (hist, userName) => (hist, userName), + parameters, + splitOn: "NombreUsuarioModifico" + ); + return result; + } + catch (Exception ex) + { + _log.LogError(ex, "Error al obtener historial de Notas C/D."); + return Enumerable.Empty<(NotaCreditoDebitoHistorico, string)>(); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Contables/PagoDistribuidorRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Contables/PagoDistribuidorRepository.cs index 78d3e21..87fe0e4 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Contables/PagoDistribuidorRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Contables/PagoDistribuidorRepository.cs @@ -40,7 +40,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables if (idDistribuidor.HasValue) { sqlBuilder.Append(" AND Id_Distribuidor = @IdDistribuidorParam"); parameters.Add("IdDistribuidorParam", idDistribuidor.Value); } if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); } if (!string.IsNullOrWhiteSpace(tipoMovimiento)) { sqlBuilder.Append(" AND TipoMovimiento = @TipoMovParam"); parameters.Add("TipoMovParam", tipoMovimiento); } - + sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Pago DESC;"); try @@ -69,7 +69,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables return null; } } - + public async Task ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null) { var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.cue_PagosDistribuidor WHERE Recibo = @ReciboParam AND TipoMovimiento = @TipoMovParam"); @@ -109,11 +109,20 @@ namespace GestionIntegral.Api.Data.Repositories.Contables var inserted = await transaction.Connection!.QuerySingleAsync(sqlInsert, nuevoPago, transaction); if (inserted == null || inserted.IdPago == 0) throw new DataException("Error al crear pago o ID no generado."); - await transaction.Connection!.ExecuteAsync(sqlHistorico, new { - IdPagoHist = inserted.IdPago, IdDistribuidorHist = inserted.IdDistribuidor, FechaHist = inserted.Fecha, - TipoMovimientoHist = inserted.TipoMovimiento, ReciboHist = inserted.Recibo, MontoHist = inserted.Monto, - IdTipoPagoHist = inserted.IdTipoPago, DetalleHist = inserted.Detalle, IdEmpresaHist = inserted.IdEmpresa, - IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creado" + await transaction.Connection!.ExecuteAsync(sqlHistorico, new + { + IdPagoHist = inserted.IdPago, + IdDistribuidorHist = inserted.IdDistribuidor, + FechaHist = inserted.Fecha, + TipoMovimientoHist = inserted.TipoMovimiento, + ReciboHist = inserted.Recibo, + MontoHist = inserted.Monto, + IdTipoPagoHist = inserted.IdTipoPago, + DetalleHist = inserted.Detalle, + IdEmpresaHist = inserted.IdEmpresa, + IdUsuarioHist = idUsuario, + FechaModHist = DateTime.Now, + TipoModHist = "Creado" }, transaction); return inserted; } @@ -136,12 +145,21 @@ namespace GestionIntegral.Api.Data.Repositories.Contables INSERT INTO dbo.cue_PagosDistribuidor_H (Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod) VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);"; - - await transaction.Connection!.ExecuteAsync(sqlHistorico, new { - IdPagoHist = actual.IdPago, IdDistribuidorHist = actual.IdDistribuidor, FechaHist = actual.Fecha, - TipoMovimientoHist = actual.TipoMovimiento, ReciboHist = actual.Recibo, MontoHist = actual.Monto, // Valor ANTERIOR - IdTipoPagoHist = actual.IdTipoPago, DetalleHist = actual.Detalle, IdEmpresaHist = actual.IdEmpresa, // Valores ANTERIORES - IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizado" + + await transaction.Connection!.ExecuteAsync(sqlHistorico, new + { + IdPagoHist = actual.IdPago, + IdDistribuidorHist = actual.IdDistribuidor, + FechaHist = actual.Fecha, + TipoMovimientoHist = actual.TipoMovimiento, + ReciboHist = actual.Recibo, + MontoHist = actual.Monto, // Valor ANTERIOR + IdTipoPagoHist = actual.IdTipoPago, + DetalleHist = actual.Detalle, + IdEmpresaHist = actual.IdEmpresa, // Valores ANTERIORES + IdUsuarioHist = idUsuario, + FechaModHist = DateTime.Now, + TipoModHist = "Actualizado" }, transaction); var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, pagoAActualizar, transaction); @@ -161,15 +179,67 @@ namespace GestionIntegral.Api.Data.Repositories.Contables (Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod) VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);"; - await transaction.Connection!.ExecuteAsync(sqlHistorico, new { - IdPagoHist = actual.IdPago, IdDistribuidorHist = actual.IdDistribuidor, FechaHist = actual.Fecha, - TipoMovimientoHist = actual.TipoMovimiento, ReciboHist = actual.Recibo, MontoHist = actual.Monto, - IdTipoPagoHist = actual.IdTipoPago, DetalleHist = actual.Detalle, IdEmpresaHist = actual.IdEmpresa, - IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminado" + await transaction.Connection!.ExecuteAsync(sqlHistorico, new + { + IdPagoHist = actual.IdPago, + IdDistribuidorHist = actual.IdDistribuidor, + FechaHist = actual.Fecha, + TipoMovimientoHist = actual.TipoMovimiento, + ReciboHist = actual.Recibo, + MontoHist = actual.Monto, + IdTipoPagoHist = actual.IdTipoPago, + DetalleHist = actual.Detalle, + IdEmpresaHist = actual.IdEmpresa, + IdUsuarioHist = idUsuario, + FechaModHist = DateTime.Now, + TipoModHist = "Eliminado" }, transaction); var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPagoParam = idPago }, transaction); return rowsAffected == 1; } + + public async Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idPagoOriginal) + { + using var connection = _cf.CreateConnection(); + var sqlBuilder = new StringBuilder(@" + SELECT + h.Id_Pago, h.Id_Distribuidor, h.Fecha, h.TipoMovimiento, h.Recibo, h.Monto, + h.Id_TipoPago, h.Detalle, h.Id_Empresa, + h.Id_Usuario, h.FechaMod, h.TipoMod, + u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico + FROM dbo.cue_PagosDistribuidor_H h + JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id + WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } + if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } + if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); } + if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); } + if (idPagoOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Pago = @IdPagoOriginalParam"); parameters.Add("IdPagoOriginalParam", idPagoOriginal.Value); } + + sqlBuilder.Append(" ORDER BY h.FechaMod DESC;"); + + try + { + var result = await connection.QueryAsync( + sqlBuilder.ToString(), + (hist, userName) => (hist, userName), + parameters, + splitOn: "NombreUsuarioModifico" + ); + return result; + } + catch (Exception ex) + { + _log.LogError(ex, "Error al obtener historial de Pagos de Distribuidores."); + return Enumerable.Empty<(PagoDistribuidorHistorico, string)>(); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Contables/SaldoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Contables/SaldoRepository.cs index ec5fa88..a3381b4 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Contables/SaldoRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Contables/SaldoRepository.cs @@ -188,5 +188,49 @@ namespace GestionIntegral.Api.Data.Repositories.Contables await transaction.Connection!.ExecuteAsync(sql, historialEntry, transaction); } + + public async Task> GetHistorialAjustesAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, + string? destino, int? idDestino, int? idEmpresa) + { + using var connection = _connectionFactory.CreateConnection(); + var sqlBuilder = new StringBuilder(@" + SELECT + h.IdSaldoAjusteHist, h.Destino, h.Id_Destino, h.Id_Empresa, + h.MontoAjuste, h.SaldoAnterior, h.SaldoNuevo, h.Justificacion, + h.FechaAjuste, h.Id_UsuarioAjuste, + u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico + FROM dbo.cue_SaldoAjustesHistorial h + JOIN dbo.gral_Usuarios u ON h.Id_UsuarioAjuste = u.Id + WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaAjuste >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } + if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaAjuste <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } + if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_UsuarioAjuste = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); } + if (!string.IsNullOrWhiteSpace(destino)) { sqlBuilder.Append(" AND h.Destino = @DestinoParam"); parameters.Add("DestinoParam", destino); } + if (idDestino.HasValue) { sqlBuilder.Append(" AND h.Id_Destino = @IdDestinoParam"); parameters.Add("IdDestinoParam", idDestino.Value); } + if (idEmpresa.HasValue) { sqlBuilder.Append(" AND h.Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); } + + sqlBuilder.Append(" ORDER BY h.FechaAjuste DESC;"); + + try + { + var result = await connection.QueryAsync( + sqlBuilder.ToString(), + (hist, userName) => (hist, userName), + parameters, + splitOn: "NombreUsuarioModifico" + ); + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener historial de Ajustes de Saldo."); + return Enumerable.Empty<(SaldoAjusteHistorial, string)>(); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Contables/TipoPagoRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Contables/TipoPagoRepository.cs index 164d7bf..b776d09 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Contables/TipoPagoRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Contables/TipoPagoRepository.cs @@ -2,6 +2,7 @@ using Dapper; using GestionIntegral.Api.Models.Contables; using System.Collections.Generic; using System.Data; +using System.Text; using System.Threading.Tasks; namespace GestionIntegral.Api.Data.Repositories.Contables @@ -284,5 +285,47 @@ namespace GestionIntegral.Api.Data.Repositories.Contables return true; // Asumir que está en uso si hay error para prevenir borrado incorrecto } } + + public async Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idTipoPagoOriginal) + { + using var connection = _connectionFactory.CreateConnection(); // Asumiendo _cf es tu DbConnectionFactory + var sqlBuilder = new StringBuilder(@" + SELECT + h.Id_TipoPago, h.Nombre, h.Detalle, + h.Id_Usuario, h.FechaMod, h.TipoMod, + u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico + FROM dbo.cue_dtTipopago_H h + JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id + WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } + if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } + if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); } + if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); } + if (idTipoPagoOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_TipoPago = @IdTipoPagoOriginalParam"); parameters.Add("IdTipoPagoOriginalParam", idTipoPagoOriginal.Value); } + + sqlBuilder.Append(" ORDER BY h.FechaMod DESC;"); + + try + { + var result = await connection.QueryAsync( + sqlBuilder.ToString(), + (hist, userName) => (hist, userName), + parameters, + splitOn: "NombreUsuarioModifico" + ); + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener historial de Tipos de Pago."); // Asumiendo _log + return Enumerable.Empty<(TipoPagoHistorico, string)>(); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/CambioParadaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/CambioParadaRepository.cs new file mode 100644 index 0000000..c22c296 --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/CambioParadaRepository.cs @@ -0,0 +1,139 @@ +using Dapper; +using GestionIntegral.Api.Models.Distribucion; +using Microsoft.Extensions.Logging; // Para ILogger +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; // Para StringBuilder +using System.Threading.Tasks; + +namespace GestionIntegral.Api.Data.Repositories.Distribucion +{ + public class CambioParadaRepository : ICambioParadaRepository + { + private readonly DbConnectionFactory _cf; + private readonly ILogger _logger; + + public CambioParadaRepository(DbConnectionFactory cf, ILogger logger) + { + _cf = cf; + _logger = logger; + } + + private async Task LogHistorialAsync(CambioParadaCanilla paradaOriginal, int idUsuario, string tipoMod, IDbConnection connection, IDbTransaction? transaction) + { + var historial = new CambioParadaCanillaHistorial + { + Id_Registro = paradaOriginal.IdRegistro, + Id_Canilla = paradaOriginal.IdCanilla, + Parada = paradaOriginal.Parada, + VigenciaD = paradaOriginal.VigenciaD, + VigenciaH = paradaOriginal.VigenciaH, + Id_Usuario = idUsuario, + FechaMod = DateTime.Now, + TipoMod = tipoMod + }; + const string sqlHistorial = @" + INSERT INTO dbo.dist_CambiosParadasCanillas_H + (Id_Registro, Id_Canilla, Parada, VigenciaD, VigenciaH, Id_Usuario, FechaMod, TipoMod) + VALUES + (@Id_Registro, @Id_Canilla, @Parada, @VigenciaD, @VigenciaH, @Id_Usuario, @FechaMod, @TipoMod);"; + await connection.ExecuteAsync(sqlHistorial, historial, transaction); + } + + + public async Task> GetByCanillaAsync(int idCanilla) + { + using var connection = _cf.CreateConnection(); + const string sql = @" + SELECT Id_Registro AS IdRegistro, Id_Canilla AS IdCanilla, Parada, VigenciaD, VigenciaH + FROM dbo.dist_CambiosParadasCanillas + WHERE Id_Canilla = @IdCanilla + ORDER BY VigenciaD DESC, Id_Registro DESC;"; + return await connection.QueryAsync(sql, new { IdCanilla = idCanilla }); + } + + public async Task GetByIdAsync(int idRegistro) + { + using var connection = _cf.CreateConnection(); + const string sql = @" + SELECT Id_Registro AS IdRegistro, Id_Canilla AS IdCanilla, Parada, VigenciaD, VigenciaH + FROM dbo.dist_CambiosParadasCanillas + WHERE Id_Registro = @IdRegistro;"; + return await connection.QuerySingleOrDefaultAsync(sql, new { IdRegistro = idRegistro }); + } + + public async Task GetCurrentParadaAsync(int idCanilla, IDbTransaction? transaction = null) + { + const string sql = @" + SELECT TOP 1 Id_Registro AS IdRegistro, Id_Canilla AS IdCanilla, Parada, VigenciaD, VigenciaH + FROM dbo.dist_CambiosParadasCanillas + WHERE Id_Canilla = @IdCanilla AND VigenciaH IS NULL + ORDER BY VigenciaD DESC, Id_Registro DESC;"; // Por si hay un error y quedaron varias abiertas + + var conn = transaction?.Connection ?? _cf.CreateConnection(); + bool manageConnection = transaction == null; + if (manageConnection && conn.State != ConnectionState.Open) { if (conn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else conn.Open(); } + try + { + return await conn.QuerySingleOrDefaultAsync(sql, new { IdCanilla = idCanilla }, transaction); + } + finally + { + if (manageConnection && conn.State == ConnectionState.Open) { if (conn is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else conn.Close(); } + } + } + + + public async Task CreateAsync(CambioParadaCanilla nuevaParada, int idUsuario, IDbTransaction transaction) + { + const string sqlInsert = @" + INSERT INTO dbo.dist_CambiosParadasCanillas (Id_Canilla, Parada, VigenciaD, VigenciaH) + OUTPUT INSERTED.Id_Registro AS IdRegistro, INSERTED.Id_Canilla AS IdCanilla, INSERTED.Parada, INSERTED.VigenciaD, INSERTED.VigenciaH + VALUES (@IdCanilla, @Parada, @VigenciaD, @VigenciaH);"; + + // VigenciaH es null al crear una nueva parada activa + var inserted = await transaction.Connection!.QuerySingleAsync(sqlInsert, + new { nuevaParada.IdCanilla, nuevaParada.Parada, nuevaParada.VigenciaD, VigenciaH = (DateTime?)null }, + transaction); + + if (inserted == null || inserted.IdRegistro == 0) throw new DataException("Error al crear cambio de parada o ID no generado."); + + await LogHistorialAsync(inserted, idUsuario, "Creada", transaction.Connection!, transaction); + return inserted; + } + + public async Task UpdateVigenciaHAsync(int idRegistro, DateTime vigenciaH, int idUsuario, IDbTransaction transaction) + { + var paradaOriginal = await GetByIdAsync(idRegistro); // Obtener para el log + if (paradaOriginal == null) throw new KeyNotFoundException("Registro de parada no encontrado para actualizar VigenciaH."); + + // Loggear ANTES de actualizar + await LogHistorialAsync(paradaOriginal, idUsuario, "Cerrada", transaction.Connection!, transaction); + + const string sqlUpdate = @" + UPDATE dbo.dist_CambiosParadasCanillas + SET VigenciaH = @VigenciaH + WHERE Id_Registro = @IdRegistro;"; + var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, + new { VigenciaH = vigenciaH.Date, IdRegistro = idRegistro }, + transaction); + + return rowsAffected == 1; + } + + public async Task DeleteAsync(int idRegistro, int idUsuario, IDbTransaction transaction) + { + var paradaOriginal = await GetByIdAsync(idRegistro); + if (paradaOriginal == null) throw new KeyNotFoundException("Registro de parada no encontrado para eliminar."); + + // Loggear ANTES de eliminar + await LogHistorialAsync(paradaOriginal, idUsuario, "Eliminada", transaction.Connection!, transaction); + + const string sqlDelete = "DELETE FROM dbo.dist_CambiosParadasCanillas WHERE Id_Registro = @IdRegistro;"; + var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdRegistro = idRegistro }, transaction); + return rowsAffected == 1; + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/CanillaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/CanillaRepository.cs index 9691872..baddbaa 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/CanillaRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/CanillaRepository.cs @@ -259,5 +259,47 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new { BajaParam = darDeBaja, FechaBajaParam = (darDeBaja ? fechaBaja : null), IdCanillaParam = id }, transaction); return rowsAffected == 1; } + public async Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idCanillaOriginal) + { + using var connection = _connectionFactory.CreateConnection(); // Asumiendo _cf es tu DbConnectionFactory + var sqlBuilder = new StringBuilder(@" + SELECT + h.Id_Canilla, h.Legajo, h.NomApe, h.Parada, h.Id_Zona, h.Accionista, h.Obs, + h.Empresa, h.Baja, h.FechaBaja, + h.Id_Usuario, h.FechaMod, h.TipoMod, + u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico + FROM dbo.dist_dtCanillas_H h + JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id + WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } + if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } + if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); } + if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); } + if (idCanillaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Canilla = @IdCanillaOriginalParam"); parameters.Add("IdCanillaOriginalParam", idCanillaOriginal.Value); } + + sqlBuilder.Append(" ORDER BY h.FechaMod DESC;"); + + try + { + var result = await connection.QueryAsync( + sqlBuilder.ToString(), + (hist, userName) => (hist, userName), + parameters, + splitOn: "NombreUsuarioModifico" + ); + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener historial de Canillitas (Maestro)."); + return Enumerable.Empty<(CanillaHistorico, string)>(); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/DistribuidorRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/DistribuidorRepository.cs index a8f0c5e..0953fa4 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/DistribuidorRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/DistribuidorRepository.cs @@ -62,7 +62,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion return Enumerable.Empty<(Distribuidor, string?)>(); } } - + public async Task> GetAllDropdownAsync() { var sqlBuilder = new StringBuilder(@" @@ -70,7 +70,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion Id_Distribuidor AS IdDistribuidor, Nombre FROM dbo.dist_dtDistribuidores WHERE 1=1"); - var parameters = new DynamicParameters(); + var parameters = new DynamicParameters(); sqlBuilder.Append(" ORDER BY Nombre;"); try { @@ -129,7 +129,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion } catch (Exception ex) { - _logger.LogError(ex, "Error al obtener Distribuidor por ID: {IdDistribuidor}", id); + _logger.LogError(ex, "Error al obtener Distribuidor por ID: {IdDistribuidor}", id); return null; } } @@ -149,7 +149,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion } catch (Exception ex) { - _logger.LogError(ex, "Error al obtener Distribuidor (simple) por ID: {IdDistribuidor}", id); + _logger.LogError(ex, "Error al obtener Distribuidor (simple) por ID: {IdDistribuidor}", id); return null; } } @@ -171,7 +171,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion } catch (Exception ex) { - _logger.LogError(ex, "Error en ExistsByNroDocAsync. NroDoc: {NroDoc}", nroDoc); + _logger.LogError(ex, "Error en ExistsByNroDocAsync. NroDoc: {NroDoc}", nroDoc); return true; } } @@ -227,7 +227,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion 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);"; - + var connection = transaction.Connection!; var inserted = await connection.QuerySingleAsync(sqlInsert, nuevoDistribuidor, transaction); if (inserted == null || inserted.IdDistribuidor == 0) throw new DataException("Error al crear distribuidor o al obtener el ID generado."); @@ -239,9 +239,21 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion await connection.ExecuteAsync(sqlInsertHistorico, new { - IdDistribuidorParam = inserted.IdDistribuidor, NombreParam = inserted.Nombre, ContactoParam = inserted.Contacto, NroDocParam = inserted.NroDoc, IdZonaParam = inserted.IdZona, - CalleParam = inserted.Calle, NumeroParam = inserted.Numero, PisoParam = inserted.Piso, DeptoParam = inserted.Depto, TelefonoParam = inserted.Telefono, EmailParam = inserted.Email, LocalidadParam = inserted.Localidad, - IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado" + IdDistribuidorParam = inserted.IdDistribuidor, + NombreParam = inserted.Nombre, + ContactoParam = inserted.Contacto, + NroDocParam = inserted.NroDoc, + IdZonaParam = inserted.IdZona, + CalleParam = inserted.Calle, + NumeroParam = inserted.Numero, + PisoParam = inserted.Piso, + DeptoParam = inserted.Depto, + TelefonoParam = inserted.Telefono, + EmailParam = inserted.Email, + LocalidadParam = inserted.Localidad, + IdUsuarioParam = idUsuario, + FechaModParam = DateTime.Now, + TipoModParam = "Creado" }, transaction); return inserted; } @@ -268,9 +280,21 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion 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, - IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado" + 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, + IdUsuarioParam = idUsuario, + FechaModParam = DateTime.Now, + TipoModParam = "Actualizado" }, transaction); var rowsAffected = await connection.ExecuteAsync(sqlUpdate, distribuidorAActualizar, transaction); @@ -295,13 +319,68 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion 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, - IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado" + 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, + IdUsuarioParam = idUsuario, + FechaModParam = DateTime.Now, + TipoModParam = "Eliminado" }, transaction); var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id }, transaction); return rowsAffected == 1; } + + public async Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idDistribuidorOriginal) + { + using var connection = _connectionFactory.CreateConnection(); // Asumiendo _connectionFactory + var sqlBuilder = new StringBuilder(@" + SELECT + h.Id_Distribuidor, h.Nombre, h.Contacto, h.NroDoc, h.Id_Zona, h.Calle, h.Numero, + h.Piso, h.Depto, h.Telefono, h.Email, h.Localidad, + h.Id_Usuario, h.FechaMod, h.TipoMod, + u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico + FROM dbo.dist_dtDistribuidores_H h + JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id + WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } + if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } + if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); } + if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); } + if (idDistribuidorOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Distribuidor = @IdDistribuidorOriginalParam"); parameters.Add("IdDistribuidorOriginalParam", idDistribuidorOriginal.Value); } + + sqlBuilder.Append(" ORDER BY h.FechaMod DESC;"); + + try + { + var result = await connection.QueryAsync( + sqlBuilder.ToString(), + (hist, userName) => (hist, userName), + parameters, + splitOn: "NombreUsuarioModifico" + ); + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener historial de Distribuidores (Maestro)."); // Asumiendo _logger + return Enumerable.Empty<(DistribuidorHistorico, string)>(); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EmpresaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EmpresaRepository.cs index 7c67261..9c6a15d 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EmpresaRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EmpresaRepository.cs @@ -257,5 +257,47 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion return rowsAffected == 1; } + + public async Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idEmpresaOriginal) + { + using var connection = _connectionFactory.CreateConnection(); // Asumiendo _cf + var sqlBuilder = new StringBuilder(@" + SELECT + h.Id_Empresa, h.Nombre, h.Detalle, + h.Id_Usuario, h.FechaMod, h.TipoMod, + u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico + FROM dbo.dist_dtEmpresas_H h + JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id + WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } + if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } + if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); } + if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); } + if (idEmpresaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Empresa = @IdEmpresaOriginalParam"); parameters.Add("IdEmpresaOriginalParam", idEmpresaOriginal.Value); } + + sqlBuilder.Append(" ORDER BY h.FechaMod DESC;"); + + try + { + var result = await connection.QueryAsync( + sqlBuilder.ToString(), + (hist, userName) => (hist, userName), + parameters, + splitOn: "NombreUsuarioModifico" + ); + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener historial de Empresas (Maestro)."); // Asumiendo _logger + return Enumerable.Empty<(EmpresaHistorico, string)>(); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EntradaSalidaCanillaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EntradaSalidaCanillaRepository.cs index 336a571..8811410 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EntradaSalidaCanillaRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EntradaSalidaCanillaRepository.cs @@ -292,5 +292,48 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction); return rowsAffected == 1; } + + public async Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idParteOriginal) + { + using var connection = _cf.CreateConnection(); + var sqlBuilder = new StringBuilder(@" + SELECT + h.Id_Parte, h.Id_Publicacion, h.Id_Canilla, h.Fecha, + h.CantSalida, h.CantEntrada, h.Id_Precio, h.Id_Recargo, h.Id_PorcMon, h.Observacion, + h.Id_Usuario, h.FechaMod, h.TipoMod, + u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico + FROM dbo.dist_EntradasSalidasCanillas_H h + JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id + WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } + if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } + if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); } + if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); } + if (idParteOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Parte = @IdParteOriginalParam"); parameters.Add("IdParteOriginalParam", idParteOriginal.Value); } + + sqlBuilder.Append(" ORDER BY h.FechaMod DESC;"); + + try + { + var result = await connection.QueryAsync( + sqlBuilder.ToString(), + (hist, userName) => (hist, userName), + parameters, + splitOn: "NombreUsuarioModifico" + ); + return result; + } + catch (Exception ex) + { + _log.LogError(ex, "Error al obtener historial de Entradas/Salidas Canillitas."); + return Enumerable.Empty<(EntradaSalidaCanillaHistorico, string)>(); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EntradaSalidaDistRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EntradaSalidaDistRepository.cs index 8512d8f..814624f 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EntradaSalidaDistRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EntradaSalidaDistRepository.cs @@ -71,7 +71,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion return null; } } - + public async Task ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null) { var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_EntradasSalidas WHERE Remito = @RemitoParam AND TipoMovimiento = @TipoMovimientoParam AND Id_Publicacion = @IdPublicacionParam"); @@ -114,11 +114,22 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion var inserted = await transaction.Connection!.QuerySingleAsync(sqlInsert, nuevaES, transaction); if (inserted == null || inserted.IdParte == 0) throw new DataException("Error al crear Entrada/Salida o ID no generado."); - await transaction.Connection!.ExecuteAsync(sqlHistorico, new { - IdParteHist = inserted.IdParte, IdPublicacionHist = inserted.IdPublicacion, IdDistribuidorHist = inserted.IdDistribuidor, - FechaHist = inserted.Fecha, TipoMovimientoHist = inserted.TipoMovimiento, CantidadHist = inserted.Cantidad, RemitoHist = inserted.Remito, ObservacionHist = inserted.Observacion, - IdPrecioHist = inserted.IdPrecio, IdRecargoHist = inserted.IdRecargo, IdPorcentajeHist = inserted.IdPorcentaje, - IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creada" + await transaction.Connection!.ExecuteAsync(sqlHistorico, new + { + IdParteHist = inserted.IdParte, + IdPublicacionHist = inserted.IdPublicacion, + IdDistribuidorHist = inserted.IdDistribuidor, + FechaHist = inserted.Fecha, + TipoMovimientoHist = inserted.TipoMovimiento, + CantidadHist = inserted.Cantidad, + RemitoHist = inserted.Remito, + ObservacionHist = inserted.Observacion, + IdPrecioHist = inserted.IdPrecio, + IdRecargoHist = inserted.IdRecargo, + IdPorcentajeHist = inserted.IdPorcentaje, + IdUsuarioHist = idUsuario, + FechaModHist = DateTime.Now, + TipoModHist = "Creada" }, transaction); return inserted; } @@ -136,16 +147,27 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion Cantidad = @Cantidad, Observacion = @Observacion -- Publicacion, Distribuidor, Fecha, TipoMovimiento, Remito, Ids de precio/recargo/porc no se modifican aquí WHERE Id_Parte = @IdParte;"; - const string sqlHistorico = @" + const string sqlHistorico = @" INSERT INTO dbo.dist_EntradasSalidas_H (Id_Parte, Id_Publicacion, Id_Distribuidor, Fecha, TipoMovimiento, Cantidad, Remito, Observacion, Id_Precio, Id_Recargo, Id_Porcentaje, Id_Usuario, FechaMod, TipoMod) VALUES (@IdParteHist, @IdPublicacionHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @CantidadHist, @RemitoHist, @ObservacionHist, @IdPrecioHist, @IdRecargoHist, @IdPorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);"; - - await transaction.Connection!.ExecuteAsync(sqlHistorico, new { - IdParteHist = actual.IdParte, IdPublicacionHist = actual.IdPublicacion, IdDistribuidorHist = actual.IdDistribuidor, - FechaHist = actual.Fecha, TipoMovimientoHist = actual.TipoMovimiento, CantidadHist = actual.Cantidad, RemitoHist = actual.Remito, ObservacionHist = actual.Observacion, - IdPrecioHist = actual.IdPrecio, IdRecargoHist = actual.IdRecargo, IdPorcentajeHist = actual.IdPorcentaje, // Valores ANTERIORES - IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizada" + + await transaction.Connection!.ExecuteAsync(sqlHistorico, new + { + IdParteHist = actual.IdParte, + IdPublicacionHist = actual.IdPublicacion, + IdDistribuidorHist = actual.IdDistribuidor, + FechaHist = actual.Fecha, + TipoMovimientoHist = actual.TipoMovimiento, + CantidadHist = actual.Cantidad, + RemitoHist = actual.Remito, + ObservacionHist = actual.Observacion, + IdPrecioHist = actual.IdPrecio, + IdRecargoHist = actual.IdRecargo, + IdPorcentajeHist = actual.IdPorcentaje, // Valores ANTERIORES + IdUsuarioHist = idUsuario, + FechaModHist = DateTime.Now, + TipoModHist = "Actualizada" }, transaction); var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, esAActualizar, transaction); @@ -166,15 +188,69 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion (Id_Parte, Id_Publicacion, Id_Distribuidor, Fecha, TipoMovimiento, Cantidad, Remito, Observacion, Id_Precio, Id_Recargo, Id_Porcentaje, Id_Usuario, FechaMod, TipoMod) VALUES (@IdParteHist, @IdPublicacionHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @CantidadHist, @RemitoHist, @ObservacionHist, @IdPrecioHist, @IdRecargoHist, @IdPorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);"; - await transaction.Connection!.ExecuteAsync(sqlHistorico, new { - IdParteHist = actual.IdParte, IdPublicacionHist = actual.IdPublicacion, IdDistribuidorHist = actual.IdDistribuidor, - FechaHist = actual.Fecha, TipoMovimientoHist = actual.TipoMovimiento, CantidadHist = actual.Cantidad, RemitoHist = actual.Remito, ObservacionHist = actual.Observacion, - IdPrecioHist = actual.IdPrecio, IdRecargoHist = actual.IdRecargo, IdPorcentajeHist = actual.IdPorcentaje, - IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada" + await transaction.Connection!.ExecuteAsync(sqlHistorico, new + { + IdParteHist = actual.IdParte, + IdPublicacionHist = actual.IdPublicacion, + IdDistribuidorHist = actual.IdDistribuidor, + FechaHist = actual.Fecha, + TipoMovimientoHist = actual.TipoMovimiento, + CantidadHist = actual.Cantidad, + RemitoHist = actual.Remito, + ObservacionHist = actual.Observacion, + IdPrecioHist = actual.IdPrecio, + IdRecargoHist = actual.IdRecargo, + IdPorcentajeHist = actual.IdPorcentaje, + IdUsuarioHist = idUsuario, + FechaModHist = DateTime.Now, + TipoModHist = "Eliminada" }, transaction); var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction); return rowsAffected == 1; } + + public async Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idParteOriginal) + { + using var connection = _cf.CreateConnection(); + var sqlBuilder = new StringBuilder(@" + SELECT + h.Id_Parte, h.Id_Publicacion, h.Id_Distribuidor, h.Fecha, h.TipoMovimiento, + h.Cantidad, h.Remito, h.Observacion, h.Id_Precio, h.Id_Recargo, h.Id_Porcentaje, + h.Id_Usuario, h.FechaMod, h.TipoMod, + u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico + FROM dbo.dist_EntradasSalidas_H h + JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id + WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } + if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } + if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); } + if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); } + if (idParteOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Parte = @IdParteOriginalParam"); parameters.Add("IdParteOriginalParam", idParteOriginal.Value); } + + sqlBuilder.Append(" ORDER BY h.FechaMod DESC;"); + + try + { + var result = await connection.QueryAsync( + sqlBuilder.ToString(), + (hist, userName) => (hist, userName), + parameters, + splitOn: "NombreUsuarioModifico" + ); + return result; + } + catch (Exception ex) + { + _log.LogError(ex, "Error al obtener historial de Entradas/Salidas Distribuidores."); + return Enumerable.Empty<(EntradaSalidaDistHistorico, string)>(); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/ICambioParadaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/ICambioParadaRepository.cs new file mode 100644 index 0000000..bdda3c3 --- /dev/null +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/ICambioParadaRepository.cs @@ -0,0 +1,18 @@ +using GestionIntegral.Api.Models.Distribucion; +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading.Tasks; + +namespace GestionIntegral.Api.Data.Repositories.Distribucion +{ + public interface ICambioParadaRepository + { + Task> GetByCanillaAsync(int idCanilla); + Task GetByIdAsync(int idRegistro); + Task GetCurrentParadaAsync(int idCanilla, IDbTransaction? transaction = null); + Task CreateAsync(CambioParadaCanilla nuevaParada, int idUsuario, IDbTransaction transaction); + Task UpdateVigenciaHAsync(int idRegistro, DateTime vigenciaH, int idUsuario, IDbTransaction transaction); + Task DeleteAsync(int idRegistro, int idUsuario, IDbTransaction transaction); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/ICanillaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/ICanillaRepository.cs index a933104..b6f592d 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/ICanillaRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/ICanillaRepository.cs @@ -14,6 +14,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion Task UpdateAsync(Canilla canillaAActualizar, int idUsuario, IDbTransaction transaction); Task ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario, IDbTransaction transaction); Task ExistsByLegajoAsync(int legajo, int? excludeIdCanilla = null); - // IsInUse no es tan directo, ya que las liquidaciones se marcan. Se podría verificar dist_EntradasSalidasCanillas no liquidadas. + Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idCanillaOriginal); // Para filtrar por un canillita específico } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IDistribuidorRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IDistribuidorRepository.cs index c75e552..5120c37 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IDistribuidorRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IDistribuidorRepository.cs @@ -17,7 +17,11 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion 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(); Task ObtenerLookupPorIdAsync(int id); + Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idDistribuidorOriginal); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEmpresaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEmpresaRepository.cs index 1a95f33..93bf8e0 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEmpresaRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEmpresaRepository.cs @@ -17,5 +17,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion Task IsInUseAsync(int id); Task> GetAllDropdownAsync(); Task ObtenerLookupPorIdAsync(int id); + Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idEmpresaOriginal); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEntradaSalidaCanillaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEntradaSalidaCanillaRepository.cs index 5c04ce1..8b4085e 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEntradaSalidaCanillaRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEntradaSalidaCanillaRepository.cs @@ -19,5 +19,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion Task DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction); Task LiquidarAsync(IEnumerable idsPartes, DateTime fechaLiquidacion, int idUsuarioLiquidador, IDbTransaction transaction); Task ExistsByPublicacionCanillaFechaAsync(int idPublicacion, int idCanilla, DateTime fecha, IDbTransaction? transaction = null, int? excludeIdParte = null); + Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idParteOriginal); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEntradaSalidaDistRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEntradaSalidaDistRepository.cs index 75d29af..48c7c39 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEntradaSalidaDistRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/IEntradaSalidaDistRepository.cs @@ -14,5 +14,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion Task UpdateAsync(EntradaSalidaDist esAActualizar, int idUsuario, IDbTransaction transaction); Task DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction); Task ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null); + Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idParteOriginal); // Para filtrar por un movimiento específico } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/INovedadCanillaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/INovedadCanillaRepository.cs index 44991ea..cee06c1 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/INovedadCanillaRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/INovedadCanillaRepository.cs @@ -19,5 +19,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion Task ExistsByCanillaAndFechaAsync(int idCanilla, DateTime fecha, int? excludeIdNovedad = null); Task> GetReporteNovedadesAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); Task> GetReporteGananciasAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); + Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idNovedadOriginal); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/NovedadCanillaRepository.cs b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/NovedadCanillaRepository.cs index ee20c67..be766cf 100644 --- a/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/NovedadCanillaRepository.cs +++ b/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/NovedadCanillaRepository.cs @@ -9,6 +9,7 @@ using Microsoft.Data.SqlClient; // O el proveedor de tu BD using System.Linq; using System.Threading.Tasks; using GestionIntegral.Api.Dtos.Reportes; +using System.Text; namespace GestionIntegral.Api.Data.Repositories.Distribucion { @@ -232,5 +233,46 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion commandType: CommandType.StoredProcedure ); } + public async Task> GetHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idNovedadOriginal) + { + using var connection = _connectionFactory.CreateConnection(); + var sqlBuilder = new StringBuilder(@" + SELECT + h.Id_Novedad, h.Id_Canilla, h.Fecha, h.Detalle, + h.Id_Usuario, h.FechaMod, h.TipoMod, + u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico + FROM dbo.dist_dtNovedadesCanillas_H h + JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id + WHERE 1=1"); + + var parameters = new DynamicParameters(); + + if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } + if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } + if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); } + if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); } + if (idNovedadOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Novedad = @IdNovedadOriginalParam"); parameters.Add("IdNovedadOriginalParam", idNovedadOriginal.Value); } + + sqlBuilder.Append(" ORDER BY h.FechaMod DESC;"); + + try + { + var result = await connection.QueryAsync( + sqlBuilder.ToString(), + (hist, userName) => (hist, userName), + parameters, + splitOn: "NombreUsuarioModifico" + ); + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener historial de Novedades de Canillitas."); + return Enumerable.Empty<(NovedadCanillaHistorico, string)>(); + } + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Contables/TipoPagoHistorico.cs b/Backend/GestionIntegral.Api/Models/Contables/TipoPagoHistorico.cs index ccdef40..0634383 100644 --- a/Backend/GestionIntegral.Api/Models/Contables/TipoPagoHistorico.cs +++ b/Backend/GestionIntegral.Api/Models/Contables/TipoPagoHistorico.cs @@ -1,12 +1,14 @@ +using System; + namespace GestionIntegral.Api.Models.Contables { public class TipoPagoHistorico { - public int IdTipoPago { get; set; } // Este NO es IDENTITY + public int Id_TipoPago { get; set; } // ID del TipoPago original public string Nombre { get; set; } = string.Empty; public string? Detalle { get; set; } - public int IdUsuario { get; set; } + public int Id_Usuario { get; set; } public DateTime FechaMod { get; set; } - public string TipoMod { get; set; } = string.Empty; // "Insertada", "Modificada", "Eliminada" + public string TipoMod { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Distribucion/CambioParadaCanilla.cs b/Backend/GestionIntegral.Api/Models/Distribucion/CambioParadaCanilla.cs new file mode 100644 index 0000000..d0d78e6 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Distribucion/CambioParadaCanilla.cs @@ -0,0 +1,13 @@ +using System; + +namespace GestionIntegral.Api.Models.Distribucion +{ + public class CambioParadaCanilla + { + public int IdRegistro { get; set; } // Id_Registro (PK, Identity) + public int IdCanilla { get; set; } // Id_Canilla (FK) + public string Parada { get; set; } = string.Empty; // Parada (nueva dirección) + public DateTime VigenciaD { get; set; } // VigenciaD (fecha desde que aplica la nueva parada) + public DateTime? VigenciaH { get; set; } // VigenciaH (fecha hasta que aplicó esta parada, NULL si es la actual) + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Distribucion/CambioParadaCanillaHistorial.cs b/Backend/GestionIntegral.Api/Models/Distribucion/CambioParadaCanillaHistorial.cs new file mode 100644 index 0000000..9c48096 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Distribucion/CambioParadaCanillaHistorial.cs @@ -0,0 +1,16 @@ +using System; + +namespace GestionIntegral.Api.Models.Distribucion +{ + public class CambioParadaCanillaHistorial + { + public int Id_Registro { get; set; } // FK al registro original + public int Id_Canilla { get; set; } + public string Parada { get; set; } = string.Empty; + public DateTime VigenciaD { get; set; } + public DateTime? VigenciaH { get; set; } // Nullable + public int Id_Usuario { get; set; } + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Distribucion/CanillaHistorico.cs b/Backend/GestionIntegral.Api/Models/Distribucion/CanillaHistorico.cs index fcf2ee0..e2d63d8 100644 --- a/Backend/GestionIntegral.Api/Models/Distribucion/CanillaHistorico.cs +++ b/Backend/GestionIntegral.Api/Models/Distribucion/CanillaHistorico.cs @@ -1,19 +1,19 @@ +using System; + namespace GestionIntegral.Api.Models.Distribucion { - public class CanillaHistorico + public class CanillaHistorico // Corresponde a dist_dtCanillas_H { - public int IdCanilla { get; set; } + public int Id_Canilla { get; set; } public int? Legajo { get; set; } public string NomApe { get; set; } = string.Empty; public string? Parada { get; set; } - public int IdZona { get; set; } + public int Id_Zona { get; set; } public bool Accionista { get; set; } public string? Obs { get; set; } public int Empresa { get; set; } public bool Baja { get; set; } public DateTime? FechaBaja { get; set; } - - // Campos de Auditoría 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/Distribucion/DistribuidorHistorico.cs b/Backend/GestionIntegral.Api/Models/Distribucion/DistribuidorHistorico.cs index 0af650c..3a68b68 100644 --- a/Backend/GestionIntegral.Api/Models/Distribucion/DistribuidorHistorico.cs +++ b/Backend/GestionIntegral.Api/Models/Distribucion/DistribuidorHistorico.cs @@ -1,12 +1,14 @@ +using System; + namespace GestionIntegral.Api.Models.Distribucion { - public class DistribuidorHistorico + public class DistribuidorHistorico // Corresponde a dist_dtDistribuidores_H { - public int IdDistribuidor { get; set; } // FK + public int Id_Distribuidor { get; set; } public string Nombre { get; set; } = string.Empty; public string? Contacto { get; set; } public string NroDoc { get; set; } = string.Empty; - public int? IdZona { get; set; } + public int? Id_Zona { get; set; } public string? Calle { get; set; } public string? Numero { get; set; } public string? Piso { get; set; } @@ -14,8 +16,6 @@ namespace GestionIntegral.Api.Models.Distribucion public string? Telefono { get; set; } public string? Email { get; set; } public string? Localidad { get; set; } - - // Campos de Auditoría 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/Distribucion/EmpresaHistorico.cs b/Backend/GestionIntegral.Api/Models/Distribucion/EmpresaHistorico.cs index fe5e7b7..f89dab9 100644 --- a/Backend/GestionIntegral.Api/Models/Distribucion/EmpresaHistorico.cs +++ b/Backend/GestionIntegral.Api/Models/Distribucion/EmpresaHistorico.cs @@ -1,11 +1,13 @@ -namespace GestionIntegral.Api.Models.Empresas +using System; + +namespace GestionIntegral.Api.Models.Distribucion { - public class EmpresaHistorico + public class EmpresaHistorico // Corresponde a dist_dtEmpresas_H { - public int IdEmpresa { get; set; } + public int Id_Empresa { get; set; } // ID de la Empresa original public string Nombre { get; set; } = string.Empty; public string? Detalle { get; set; } - public int IdUsuario { 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/Distribucion/EntradaSalidaCanillaHistorico.cs b/Backend/GestionIntegral.Api/Models/Distribucion/EntradaSalidaCanillaHistorico.cs new file mode 100644 index 0000000..6043c97 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Distribucion/EntradaSalidaCanillaHistorico.cs @@ -0,0 +1,23 @@ +using System; + +namespace GestionIntegral.Api.Models.Distribucion // Asegúrate que este namespace sea el correcto +{ + public class EntradaSalidaCanillaHistorico + { + public int Id_Parte { get; set; } + public int Id_Publicacion { get; set; } + public int Id_Canilla { get; set; } + public DateTime Fecha { get; set; } + public int CantSalida { get; set; } + public int CantEntrada { get; set; } + public int Id_Precio { get; set; } + public int Id_Recargo { get; set; } + public int Id_PorcMon { get; set; } + public string? Observacion { get; set; } + public int Id_Usuario { get; set; } + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + // El campo Liquidado, FechaLiquidado, UserLiq no están en dist_EntradasSalidasCanillas_H según tu script. + // Si los necesitas auditar, deberías añadirlos a la tabla _H y al modelo. + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Distribucion/NovedadCanillaHistorico.cs b/Backend/GestionIntegral.Api/Models/Distribucion/NovedadCanillaHistorico.cs new file mode 100644 index 0000000..ecfba72 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Distribucion/NovedadCanillaHistorico.cs @@ -0,0 +1,20 @@ +using System; + +namespace GestionIntegral.Api.Models.Distribucion // Asegúrate que el namespace sea el correcto +{ + public class NovedadCanillaHistorico // Corresponde a la tabla dist_dtNovedadesCanillas_H + { + // Columnas de la tabla dist_dtNovedadesCanillas_H + // No hay una PK propia en la tabla _H, se referencia por Id_Novedad de la tabla principal + public int Id_Novedad { get; set; } + public int Id_Canilla { get; set; } + public DateTime Fecha { get; set; } // Fecha original de la novedad + public string? Detalle { get; set; } + + // Columnas de auditoría estándar + public int Id_Usuario { get; set; } + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/CanillaHistorialDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/CanillaHistorialDto.cs new file mode 100644 index 0000000..a98b01d --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/CanillaHistorialDto.cs @@ -0,0 +1,25 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Auditoria // O Auditoria +{ + public class CanillaHistorialDto + { + public int Id_Canilla { get; set; } + public int? Legajo { get; set; } + public string NomApe { get; set; } = string.Empty; + public string? Parada { get; set; } + public int Id_Zona { get; set; } + // public string NombreZona { get; set; } // Opcional, si lo quieres traer con JOIN + public bool Accionista { get; set; } + public string? Obs { get; set; } + public int Empresa { get; set; } // Id de la empresa + // public string NombreEmpresa { get; set; } // Opcional + public bool Baja { get; set; } + public DateTime? FechaBaja { get; set; } + + public int Id_Usuario { get; set; } + public string NombreUsuarioModifico { get; set; } = string.Empty; + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/DistribuidorHistorialDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/DistribuidorHistorialDto.cs new file mode 100644 index 0000000..c6ddec2 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/DistribuidorHistorialDto.cs @@ -0,0 +1,26 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Auditoria +{ + public class DistribuidorHistorialDto + { + public int Id_Distribuidor { get; set; } + public string Nombre { get; set; } = string.Empty; + public string? Contacto { get; set; } + public string NroDoc { get; set; } = string.Empty; + public int? Id_Zona { get; set; } + // public string NombreZona { get; set; } // Opcional + public string? Calle { get; set; } + public string? Numero { get; set; } + public string? Piso { get; set; } + public string? Depto { get; set; } + public string? Telefono { get; set; } + public string? Email { get; set; } + public string? Localidad { get; set; } + + public int Id_Usuario { get; set; } + public string NombreUsuarioModifico { get; set; } = string.Empty; + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/EmpresaHistorialDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/EmpresaHistorialDto.cs new file mode 100644 index 0000000..472a02a --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/EmpresaHistorialDto.cs @@ -0,0 +1,16 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Auditoria // O Auditoria +{ + public class EmpresaHistorialDto + { + public int Id_Empresa { get; set; } + public string Nombre { get; set; } = string.Empty; + public string? Detalle { get; set; } + + public int Id_Usuario { get; set; } + public string NombreUsuarioModifico { get; set; } = string.Empty; + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/EntradaSalidaCanillaHistorialDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/EntradaSalidaCanillaHistorialDto.cs new file mode 100644 index 0000000..a556eb8 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/EntradaSalidaCanillaHistorialDto.cs @@ -0,0 +1,31 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Auditoria +{ + public class EntradaSalidaCanillaHistorialDto + { + // Campos de la tabla _H (dist_EntradasSalidasCanillas_H) + public int Id_Parte { get; set; } // ID del movimiento original + public int Id_Publicacion { get; set; } + // public string NombrePublicacion { get; set; } // Opcional + public int Id_Canilla { get; set; } + // public string NombreCanilla { get; set; } // Opcional + public DateTime Fecha { get; set; } // Fecha original del movimiento + public int CantSalida { get; set; } + public int CantEntrada { get; set; } + public int Id_Precio { get; set; } + public int Id_Recargo { get; set; } + public int Id_PorcMon { get; set; } // ID de la config de Porcentaje/Monto + public string? Observacion { get; set; } + // Nota: Los campos Liquidado, FechaLiquidado, UserLiq del historial + // podrían o no ser relevantes para mostrar en esta auditoría de *cambios de datos*. + // Si un cambio fue "Liquidado = true", el TipoMod sería "Liquidada" o similar. + // Podrías incluirlos si quieres ver el estado de liquidación en el momento del cambio. + + // Campos de auditoría + public int Id_Usuario { get; set; } + public string NombreUsuarioModifico { get; set; } = string.Empty; + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/EntradaSalidaDistHistorialDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/EntradaSalidaDistHistorialDto.cs new file mode 100644 index 0000000..645ab0f --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/EntradaSalidaDistHistorialDto.cs @@ -0,0 +1,28 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Auditoria +{ + public class EntradaSalidaDistHistorialDto + { + // Campos de la tabla _H (dist_EntradasSalidas_H) + public int Id_Parte { get; set; } // ID del movimiento original + public int Id_Publicacion { get; set; } + // public string NombrePublicacion { get; set; } // Opcional + public int Id_Distribuidor { get; set; } + // public string NombreDistribuidor { get; set; } // Opcional + public DateTime Fecha { get; set; } // Fecha original del movimiento + public string TipoMovimiento { get; set; } = string.Empty; + public int Cantidad { get; set; } + public int Remito { get; set; } + public string? Observacion { get; set; } + public int Id_Precio { get; set; } + public int Id_Recargo { get; set; } + public int Id_Porcentaje { get; set; } + + // Campos de auditoría + public int Id_Usuario { get; set; } + public string NombreUsuarioModifico { get; set; } = string.Empty; + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/NotaCreditoDebitoHistorialDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/NotaCreditoDebitoHistorialDto.cs new file mode 100644 index 0000000..f9cbaeb --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/NotaCreditoDebitoHistorialDto.cs @@ -0,0 +1,26 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Auditoria +{ + public class NotaCreditoDebitoHistorialDto + { + // Campos de la tabla _H (cue_CreditosDebitos_H) + public int Id_Nota { get; set; } // ID de la nota original + public string Destino { get; set; } = string.Empty; + public int Id_Destino { get; set; } + // public string NombreDestinatario { get; set; } // Opcional, si lo quieres traer con JOIN + public string? Referencia { get; set; } + public string Tipo { get; set; } = string.Empty; // "Debito" o "Credito" + public DateTime Fecha { get; set; } // Fecha original de la nota + public decimal Monto { get; set; } + public string? Observaciones { get; set; } + public int Id_Empresa { get; set; } + // public string NombreEmpresa { get; set; } // Opcional + + // Campos de auditoría + public int Id_Usuario { get; set; } + public string NombreUsuarioModifico { get; set; } = string.Empty; + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/NovedadCanillaHistorialDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/NovedadCanillaHistorialDto.cs new file mode 100644 index 0000000..ba1699d --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/NovedadCanillaHistorialDto.cs @@ -0,0 +1,19 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Auditoria +{ + public class NovedadCanillaHistorialDto + { + public int Id_Novedad { get; set; } // ID de la novedad original + public int Id_Canilla { get; set; } + // public string NombreCanilla { get; set; } // Opcional, si quieres traerlo + public DateTime Fecha { get; set; } // Fecha original de la novedad + public string? Detalle { get; set; } + + // Campos de auditoría + public int Id_Usuario { get; set; } + public string NombreUsuarioModifico { get; set; } = string.Empty; + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/PagoDistribuidorHistorialDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/PagoDistribuidorHistorialDto.cs new file mode 100644 index 0000000..0b129ca --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/PagoDistribuidorHistorialDto.cs @@ -0,0 +1,24 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Auditoria +{ + public class PagoDistribuidorHistorialDto + { + // Campos de la tabla _H + public int Id_Pago { get; set; } // El ID del pago original que fue modificado/eliminado + public int Id_Distribuidor { get; set; } + public DateTime Fecha { get; set; } + public string TipoMovimiento { get; set; } = string.Empty; + public int Recibo { get; set; } + public decimal Monto { get; set; } + public int Id_TipoPago { get; set; } + public string? Detalle { get; set; } + public int Id_Empresa { get; set; } + + // Campos de auditoría + public int Id_Usuario { get; set; } // ID del usuario que hizo el cambio + public string NombreUsuarioModifico { get; set; } = string.Empty; // Nombre del usuario + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; // "Creado", "Actualizado", "Eliminado" + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/SaldoAjusteHistorialDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/SaldoAjusteHistorialDto.cs new file mode 100644 index 0000000..0552a9b --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/SaldoAjusteHistorialDto.cs @@ -0,0 +1,24 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Auditoria +{ + public class SaldoAjusteHistorialDto + { + public int IdSaldoAjusteHist { get; set; } + public string Destino { get; set; } = string.Empty; + public int Id_Destino { get; set; } + // public string NombreDestinatario { get; set; } // Opcional + public int Id_Empresa { get; set; } + // public string NombreEmpresa { get; set; } // Opcional + public decimal MontoAjuste { get; set; } + public decimal SaldoAnterior { get; set; } + public decimal SaldoNuevo { get; set; } + public string Justificacion { get; set; } = string.Empty; + public DateTime FechaAjuste { get; set; } // Es la FechaMod en este contexto + + // Campos de auditoría + public int Id_UsuarioAjuste { get; set; } // Corresponde a Id_Usuario en la tabla _H general + public string NombreUsuarioModifico { get; set; } = string.Empty; + // TipoMod no está en cue_SaldoAjustesHistorial, se infiere que es "AjusteManual" + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/TipoPagoHistorialDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/TipoPagoHistorialDto.cs new file mode 100644 index 0000000..58b5c84 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Auditoria/TipoPagoHistorialDto.cs @@ -0,0 +1,16 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Auditoria +{ + public class TipoPagoHistorialDto + { + public int Id_TipoPago { get; set; } + public string Nombre { get; set; } = string.Empty; // Nombre del TipoPago en ese momento del historial + public string? Detalle { get; set; } // Detalle en ese momento del historial + + public int Id_Usuario { get; set; } + public string NombreUsuarioModifico { get; set; } = string.Empty; + public DateTime FechaMod { get; set; } + public string TipoMod { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/CambioParadaDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/CambioParadaDto.cs new file mode 100644 index 0000000..0998846 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/CambioParadaDto.cs @@ -0,0 +1,15 @@ +using System; + +namespace GestionIntegral.Api.Dtos.Distribucion +{ + public class CambioParadaDto + { + public int IdRegistro { get; set; } + public int IdCanilla { get; set; } + public string NombreCanilla { get; set; } = string.Empty; // Para UI + public string Parada { get; set; } = string.Empty; + public string VigenciaD { get; set; } = string.Empty; // yyyy-MM-dd + public string? VigenciaH { get; set; } // yyyy-MM-dd o null + public bool EsActual => VigenciaH == null; // Propiedad calculada + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/CreateCambioParadaDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/CreateCambioParadaDto.cs new file mode 100644 index 0000000..ece9495 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/CreateCambioParadaDto.cs @@ -0,0 +1,16 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace GestionIntegral.Api.Dtos.Distribucion +{ + public class CreateCambioParadaDto + { + // IdCanilla vendrá de la ruta + [Required(ErrorMessage = "La nueva dirección de parada es obligatoria.")] + [StringLength(150)] + public string Parada { get; set; } = string.Empty; + + [Required(ErrorMessage = "La fecha de Vigencia Desde es obligatoria.")] + public DateTime VigenciaD { get; set; } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/UpdateCambioParadaDto.cs b/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/UpdateCambioParadaDto.cs new file mode 100644 index 0000000..03641cc --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Distribucion/UpdateCambioParadaDto.cs @@ -0,0 +1,13 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace GestionIntegral.Api.Dtos.Distribucion +{ + public class UpdateCambioParadaDto // Para cerrar una parada + { + [Required(ErrorMessage = "La fecha de Vigencia Hasta es obligatoria para cerrar una parada.")] + public DateTime VigenciaH { get; set; } + // No se permite modificar Parada o VigenciaD de un registro existente. + // Si se equivocó, se elimina y se crea uno nuevo, o se crea uno nuevo que cierre el anterior. + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Program.cs b/Backend/GestionIntegral.Api/Program.cs index c98bb7d..e9f77fa 100644 --- a/Backend/GestionIntegral.Api/Program.cs +++ b/Backend/GestionIntegral.Api/Program.cs @@ -84,6 +84,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // Servicio de Saldos builder.Services.AddScoped(); // Repositorios de Reportes diff --git a/Backend/GestionIntegral.Api/Services/Contables/INotaCreditoDebitoService.cs b/Backend/GestionIntegral.Api/Services/Contables/INotaCreditoDebitoService.cs index 3b67052..45a12ea 100644 --- a/Backend/GestionIntegral.Api/Services/Contables/INotaCreditoDebitoService.cs +++ b/Backend/GestionIntegral.Api/Services/Contables/INotaCreditoDebitoService.cs @@ -1,3 +1,4 @@ +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Contables; using System; using System.Collections.Generic; @@ -10,10 +11,13 @@ namespace GestionIntegral.Api.Services.Contables Task> ObtenerTodosAsync( DateTime? fechaDesde, DateTime? fechaHasta, string? destino, int? idDestino, int? idEmpresa, string? tipoNota); - Task ObtenerPorIdAsync(int idNota); Task<(NotaCreditoDebitoDto? Nota, string? Error)> CrearAsync(CreateNotaDto createDto, int idUsuario); Task<(bool Exito, string? Error)> ActualizarAsync(int idNota, UpdateNotaDto updateDto, int idUsuario); Task<(bool Exito, string? Error)> EliminarAsync(int idNota, int idUsuario); + Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idNotaAfectada); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Contables/IPagoDistribuidorService.cs b/Backend/GestionIntegral.Api/Services/Contables/IPagoDistribuidorService.cs index 5fa6438..d0f8383 100644 --- a/Backend/GestionIntegral.Api/Services/Contables/IPagoDistribuidorService.cs +++ b/Backend/GestionIntegral.Api/Services/Contables/IPagoDistribuidorService.cs @@ -1,3 +1,4 @@ +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Contables; using System; using System.Collections.Generic; @@ -15,5 +16,9 @@ namespace GestionIntegral.Api.Services.Contables Task<(PagoDistribuidorDto? Pago, string? Error)> CrearAsync(CreatePagoDistribuidorDto createDto, int idUsuario); Task<(bool Exito, string? Error)> ActualizarAsync(int idPago, UpdatePagoDistribuidorDto updateDto, int idUsuario); Task<(bool Exito, string? Error)> EliminarAsync(int idPago, int idUsuario); + Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idPagoAfectado); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Contables/ISaldoService.cs b/Backend/GestionIntegral.Api/Services/Contables/ISaldoService.cs index d16c618..4a746f0 100644 --- a/Backend/GestionIntegral.Api/Services/Contables/ISaldoService.cs +++ b/Backend/GestionIntegral.Api/Services/Contables/ISaldoService.cs @@ -1,3 +1,4 @@ +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Contables; using System.Collections.Generic; using System.Threading.Tasks; @@ -8,5 +9,9 @@ namespace GestionIntegral.Api.Services.Contables { Task> ObtenerSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter); Task<(bool Exito, string? Error, SaldoGestionDto? SaldoActualizado)> RealizarAjusteManualSaldoAsync(AjusteSaldoRequestDto ajusteDto, int idUsuarioAjuste); + Task> ObtenerHistorialAjustesAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, + string? destino, int? idDestino, int? idEmpresa); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Contables/ITipoPagoService.cs b/Backend/GestionIntegral.Api/Services/Contables/ITipoPagoService.cs index 3dc82ae..0e55f64 100644 --- a/Backend/GestionIntegral.Api/Services/Contables/ITipoPagoService.cs +++ b/Backend/GestionIntegral.Api/Services/Contables/ITipoPagoService.cs @@ -1,3 +1,4 @@ +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Contables; using System.Collections.Generic; using System.Threading.Tasks; @@ -11,5 +12,9 @@ namespace GestionIntegral.Api.Services.Contables Task<(TipoPagoDto? TipoPago, string? Error)> CrearAsync(CreateTipoPagoDto createDto, int idUsuario); Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateTipoPagoDto updateDto, int idUsuario); Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario); + Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idTipoPagoAfectado); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Contables/NotaCreditoDebitoService.cs b/Backend/GestionIntegral.Api/Services/Contables/NotaCreditoDebitoService.cs index da162cd..002107d 100644 --- a/Backend/GestionIntegral.Api/Services/Contables/NotaCreditoDebitoService.cs +++ b/Backend/GestionIntegral.Api/Services/Contables/NotaCreditoDebitoService.cs @@ -1,6 +1,7 @@ using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Distribucion; +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Models.Contables; using Microsoft.Extensions.Logging; @@ -55,7 +56,7 @@ namespace GestionIntegral.Api.Services.Contables var canData = await _canillaRepo.GetByIdAsync(nota.IdDestino); nombreDestinatario = canData.Canilla?.NomApe ?? "Canillita Desconocido"; } - + var empresa = await _empresaRepo.GetByIdAsync(nota.IdEmpresa); return new NotaCreditoDebitoDto @@ -102,7 +103,7 @@ namespace GestionIntegral.Api.Services.Contables } else if (createDto.Destino == "Canillas") { - if (await _canillaRepo.GetByIdSimpleAsync(createDto.IdDestino) == null) + if (await _canillaRepo.GetByIdSimpleAsync(createDto.IdDestino) == null) return (null, "El canillita especificado no existe."); } else { return (null, "Tipo de destino inválido."); } @@ -131,16 +132,16 @@ namespace GestionIntegral.Api.Services.Contables if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } transaction = connection.BeginTransaction(); - + var notaCreada = await _notaRepo.CreateAsync(nuevaNota, idUsuario, transaction); if (notaCreada == null) throw new DataException("Error al registrar la nota."); decimal montoParaSaldo; - if (createDto.Tipo == "Credito") + if (createDto.Tipo == "Credito") { montoParaSaldo = -createDto.Monto; } - else + else { montoParaSaldo = createDto.Monto; } @@ -178,14 +179,14 @@ namespace GestionIntegral.Api.Services.Contables if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } transaction = connection.BeginTransaction(); - - var notaExistente = await _notaRepo.GetByIdAsync(idNota); - if (notaExistente == null) + + var notaExistente = await _notaRepo.GetByIdAsync(idNota); + if (notaExistente == null) { transaction.Rollback(); return (false, "Nota no encontrada."); } - + decimal impactoOriginalSaldo = notaExistente.Tipo == "Credito" ? -notaExistente.Monto : notaExistente.Monto; decimal impactoNuevoSaldo = notaExistente.Tipo == "Credito" ? -updateDto.Monto : updateDto.Monto; decimal diferenciaAjusteSaldo = impactoNuevoSaldo - impactoOriginalSaldo; @@ -193,14 +194,14 @@ namespace GestionIntegral.Api.Services.Contables var notaParaActualizarEnRepo = new NotaCreditoDebito { IdNota = notaExistente.IdNota, - Destino = notaExistente.Destino, - IdDestino = notaExistente.IdDestino, - Referencia = notaExistente.Referencia, - Tipo = notaExistente.Tipo, - Fecha = notaExistente.Fecha, - Monto = updateDto.Monto, - Observaciones = updateDto.Observaciones, - IdEmpresa = notaExistente.IdEmpresa + Destino = notaExistente.Destino, + IdDestino = notaExistente.IdDestino, + Referencia = notaExistente.Referencia, + Tipo = notaExistente.Tipo, + Fecha = notaExistente.Fecha, + Monto = updateDto.Monto, + Observaciones = updateDto.Observaciones, + IdEmpresa = notaExistente.IdEmpresa }; var actualizado = await _notaRepo.UpdateAsync(notaParaActualizarEnRepo, idUsuario, transaction); @@ -227,7 +228,7 @@ namespace GestionIntegral.Api.Services.Contables { if (connection.State == ConnectionState.Open) { - if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); + if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); } } } @@ -240,17 +241,17 @@ namespace GestionIntegral.Api.Services.Contables { if (connection.State != ConnectionState.Open) { - if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); + if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } transaction = connection.BeginTransaction(); var notaExistente = await _notaRepo.GetByIdAsync(idNota); - if (notaExistente == null) + if (notaExistente == null) { transaction.Rollback(); return (false, "Nota no encontrada."); } - + decimal montoReversion = notaExistente.Tipo == "Credito" ? notaExistente.Monto : -notaExistente.Monto; var eliminado = await _notaRepo.DeleteAsync(idNota, idUsuario, transaction); @@ -278,5 +279,30 @@ namespace GestionIntegral.Api.Services.Contables } } } + + public async Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idNotaAfectada) + { + var historialData = await _notaRepo.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idNotaAfectada); + + return historialData.Select(h => new NotaCreditoDebitoHistorialDto + { + Id_Nota = h.Historial.Id_Nota, + Destino = h.Historial.Destino, + Id_Destino = h.Historial.Id_Destino, + Referencia = h.Historial.Referencia, + Tipo = h.Historial.Tipo, + Fecha = h.Historial.Fecha, // Fecha original de la nota + Monto = h.Historial.Monto, + Observaciones = h.Historial.Observaciones, + Id_Empresa = h.Historial.Id_Empresa, + Id_Usuario = h.Historial.Id_Usuario, + NombreUsuarioModifico = h.NombreUsuarioModifico, + FechaMod = h.Historial.FechaMod, // Fecha de la auditoría + TipoMod = h.Historial.TipoMod + }).ToList(); + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Contables/PagoDistribuidorService.cs b/Backend/GestionIntegral.Api/Services/Contables/PagoDistribuidorService.cs index 2f36916..403b166 100644 --- a/Backend/GestionIntegral.Api/Services/Contables/PagoDistribuidorService.cs +++ b/Backend/GestionIntegral.Api/Services/Contables/PagoDistribuidorService.cs @@ -1,6 +1,7 @@ using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Distribucion; +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Models.Contables; using Microsoft.Extensions.Logging; @@ -93,7 +94,7 @@ namespace GestionIntegral.Api.Services.Contables if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null) return (null, "Empresa no válida."); if (await _pagoRepo.ExistsByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento)) - return (null, $"Ya existe un pago '{createDto.TipoMovimiento}' con el número de recibo '{createDto.Recibo}'."); + return (null, $"Ya existe un pago '{createDto.TipoMovimiento}' con el número de recibo '{createDto.Recibo}'."); var nuevoPago = new PagoDistribuidor { @@ -116,18 +117,18 @@ namespace GestionIntegral.Api.Services.Contables if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } transaction = connection.BeginTransaction(); - + var pagoCreado = await _pagoRepo.CreateAsync(nuevoPago, idUsuario, transaction); if (pagoCreado == null) throw new DataException("Error al registrar el pago."); decimal montoParaSaldo; if (createDto.TipoMovimiento == "Recibido") { - montoParaSaldo = -createDto.Monto; + montoParaSaldo = -createDto.Monto; } - else + else { - montoParaSaldo = createDto.Monto; + montoParaSaldo = createDto.Monto; } bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoCreado.IdDistribuidor, pagoCreado.IdEmpresa, montoParaSaldo, transaction); @@ -143,7 +144,7 @@ namespace GestionIntegral.Api.Services.Contables _logger.LogError(ex, "Error CrearAsync PagoDistribuidor."); return (null, $"Error interno: {ex.Message}"); } - finally + finally { if (connection.State == ConnectionState.Open) { @@ -164,8 +165,8 @@ namespace GestionIntegral.Api.Services.Contables } transaction = connection.BeginTransaction(); - var pagoExistente = await _pagoRepo.GetByIdAsync(idPago); - if (pagoExistente == null) + var pagoExistente = await _pagoRepo.GetByIdAsync(idPago); + if (pagoExistente == null) { transaction.Rollback(); // Rollback si no se encuentra return (false, "Pago no encontrado."); @@ -176,7 +177,7 @@ namespace GestionIntegral.Api.Services.Contables transaction.Rollback(); return (false, "Tipo de pago no válido."); } - + decimal impactoOriginalSaldo = pagoExistente.TipoMovimiento == "Recibido" ? -pagoExistente.Monto : pagoExistente.Monto; decimal impactoNuevoSaldo = pagoExistente.TipoMovimiento == "Recibido" ? -updateDto.Monto : updateDto.Monto; decimal diferenciaAjusteSaldo = impactoNuevoSaldo - impactoOriginalSaldo; @@ -184,14 +185,14 @@ namespace GestionIntegral.Api.Services.Contables var pagoParaActualizarEnRepo = new PagoDistribuidor { IdPago = pagoExistente.IdPago, - IdDistribuidor = pagoExistente.IdDistribuidor, - Fecha = pagoExistente.Fecha, - TipoMovimiento = pagoExistente.TipoMovimiento, - Recibo = pagoExistente.Recibo, - Monto = updateDto.Monto, - IdTipoPago = updateDto.IdTipoPago, - Detalle = updateDto.Detalle, - IdEmpresa = pagoExistente.IdEmpresa + IdDistribuidor = pagoExistente.IdDistribuidor, + Fecha = pagoExistente.Fecha, + TipoMovimiento = pagoExistente.TipoMovimiento, + Recibo = pagoExistente.Recibo, + Monto = updateDto.Monto, + IdTipoPago = updateDto.IdTipoPago, + Detalle = updateDto.Detalle, + IdEmpresa = pagoExistente.IdEmpresa }; var actualizado = await _pagoRepo.UpdateAsync(pagoParaActualizarEnRepo, idUsuario, transaction); @@ -214,7 +215,7 @@ namespace GestionIntegral.Api.Services.Contables _logger.LogError(ex, "Error ActualizarAsync PagoDistribuidor ID: {Id}", idPago); return (false, $"Error interno: {ex.Message}"); } - finally + finally { if (connection.State == ConnectionState.Open) { @@ -231,17 +232,17 @@ namespace GestionIntegral.Api.Services.Contables { if (connection.State != ConnectionState.Open) { - if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); + if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } transaction = connection.BeginTransaction(); - + var pagoExistente = await _pagoRepo.GetByIdAsync(idPago); - if (pagoExistente == null) + if (pagoExistente == null) { transaction.Rollback(); return (false, "Pago no encontrado."); } - + decimal montoReversion = pagoExistente.TipoMovimiento == "Recibido" ? pagoExistente.Monto : -pagoExistente.Monto; var eliminado = await _pagoRepo.DeleteAsync(idPago, idUsuario, transaction); @@ -269,5 +270,30 @@ namespace GestionIntegral.Api.Services.Contables } } } + + public async Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idPagoAfectado) + { + var historialData = await _pagoRepo.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPagoAfectado); + + return historialData.Select(h => new PagoDistribuidorHistorialDto + { + Id_Pago = h.Historial.Id_Pago, + Id_Distribuidor = h.Historial.Id_Distribuidor, + Fecha = h.Historial.Fecha, + TipoMovimiento = h.Historial.TipoMovimiento, + Recibo = h.Historial.Recibo, + Monto = h.Historial.Monto, + Id_TipoPago = h.Historial.Id_TipoPago, + Detalle = h.Historial.Detalle, + Id_Empresa = h.Historial.Id_Empresa, + Id_Usuario = h.Historial.Id_Usuario, + NombreUsuarioModifico = h.NombreUsuarioModifico, + FechaMod = h.Historial.FechaMod, + TipoMod = h.Historial.TipoMod + }).ToList(); + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Contables/SaldoService.cs b/Backend/GestionIntegral.Api/Services/Contables/SaldoService.cs index 5d63568..b867281 100644 --- a/Backend/GestionIntegral.Api/Services/Contables/SaldoService.cs +++ b/Backend/GestionIntegral.Api/Services/Contables/SaldoService.cs @@ -1,6 +1,7 @@ using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Distribucion; // Para IDistribuidorRepository, ICanillaRepository +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Models.Contables; using Microsoft.Extensions.Logging; @@ -52,7 +53,7 @@ namespace GestionIntegral.Api.Services.Contables var canData = await _canillaRepo.GetByIdAsync(saldo.IdDestino); nombreDestinatario = canData.Canilla?.NomApe ?? $"Can. ID {saldo.IdDestino}"; } - + var empresa = await _empresaRepo.GetByIdAsync(saldo.IdEmpresa); return new SaldoGestionDto @@ -94,11 +95,13 @@ namespace GestionIntegral.Api.Services.Contables { if (await _canillaRepo.GetByIdSimpleAsync(ajusteDto.IdDestino) == null) return (false, "El canillita especificado no existe.", null); - } else { - return (false, "Tipo de destino inválido.", null); + } + else + { + return (false, "Tipo de destino inválido.", null); } if (await _empresaRepo.GetByIdAsync(ajusteDto.IdEmpresa) == null) - return (false, "La empresa especificada no existe.", null); + return (false, "La empresa especificada no existe.", null); using var connection = _connectionFactory.CreateConnection(); @@ -116,7 +119,7 @@ namespace GestionIntegral.Api.Services.Contables } decimal saldoAnterior = saldoActual.Monto; - + bool modificado = await _saldoRepo.ModificarSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, ajusteDto.MontoAjuste, transaction); if (!modificado) { @@ -125,7 +128,7 @@ namespace GestionIntegral.Api.Services.Contables // Obtener el saldo después de la modificación para el historial var saldoDespuesDeModificacion = await _saldoRepo.GetSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, transaction); - if(saldoDespuesDeModificacion == null) throw new DataException("No se pudo obtener el saldo después de la modificación."); + if (saldoDespuesDeModificacion == null) throw new DataException("No se pudo obtener el saldo después de la modificación."); var historial = new SaldoAjusteHistorial @@ -145,20 +148,44 @@ namespace GestionIntegral.Api.Services.Contables transaction.Commit(); _logger.LogInformation("Ajuste manual de saldo realizado para {Destino} ID {IdDestino}, Empresa ID {IdEmpresa} por Usuario ID {IdUsuarioAjuste}. Monto: {MontoAjuste}", ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, idUsuarioAjuste, ajusteDto.MontoAjuste); - + var saldoDtoActualizado = await MapToGestionDto(saldoDespuesDeModificacion); return (true, null, saldoDtoActualizado); } catch (Exception ex) { - try { transaction.Rollback(); } catch (Exception rbEx){ _logger.LogError(rbEx, "Error en Rollback de RealizarAjusteManualSaldoAsync."); } + try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de RealizarAjusteManualSaldoAsync."); } _logger.LogError(ex, "Error en RealizarAjusteManualSaldoAsync."); return (false, $"Error interno al realizar el ajuste: {ex.Message}", null); } finally { - if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); } + if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); } } } + + public async Task> ObtenerHistorialAjustesAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, + string? destino, int? idDestino, int? idEmpresa) + { + var historialData = await _saldoRepo.GetHistorialAjustesAsync(fechaDesde, fechaHasta, idUsuarioModifico, destino, idDestino, idEmpresa); + + return historialData.Select(h => new SaldoAjusteHistorialDto + { + IdSaldoAjusteHist = h.Historial.IdSaldoAjusteHist, + Destino = h.Historial.Destino, + Id_Destino = h.Historial.IdDestino, + Id_Empresa = h.Historial.IdEmpresa, + MontoAjuste = h.Historial.MontoAjuste, + SaldoAnterior = h.Historial.SaldoAnterior, + SaldoNuevo = h.Historial.SaldoNuevo, + Justificacion = h.Historial.Justificacion, + FechaAjuste = h.Historial.FechaAjuste, + Id_UsuarioAjuste = h.Historial.IdUsuarioAjuste, + NombreUsuarioModifico = h.NombreUsuarioModifico + // TipoMod es implícito "AjusteManualSaldo" + }).ToList(); + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Contables/TipoPagoService.cs b/Backend/GestionIntegral.Api/Services/Contables/TipoPagoService.cs index 0ab5797..72789f5 100644 --- a/Backend/GestionIntegral.Api/Services/Contables/TipoPagoService.cs +++ b/Backend/GestionIntegral.Api/Services/Contables/TipoPagoService.cs @@ -1,4 +1,5 @@ using GestionIntegral.Api.Data.Repositories.Contables; +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Models.Contables; // Para TipoPago using GestionIntegral.Api.Services.Contables; @@ -101,8 +102,8 @@ namespace GestionIntegral.Api.Services.Contables if (!actualizado) { - _logger.LogError("Falló la actualización del Tipo de Pago en el repositorio para el ID: {Id}", id); - return (false, "Error al actualizar el tipo de pago en la base de datos."); + _logger.LogError("Falló la actualización del Tipo de Pago en el repositorio para el ID: {Id}", id); + return (false, "Error al actualizar el tipo de pago en la base de datos."); } return (true, null); // Éxito } @@ -130,5 +131,24 @@ namespace GestionIntegral.Api.Services.Contables } return (true, null); // Éxito } + + public async Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idTipoPagoAfectado) + { + var historialData = await _tipoPagoRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idTipoPagoAfectado); + + return historialData.Select(h => new TipoPagoHistorialDto + { + Id_TipoPago = h.Historial.Id_TipoPago, + Nombre = h.Historial.Nombre, + Detalle = h.Historial.Detalle, + Id_Usuario = h.Historial.Id_Usuario, + NombreUsuarioModifico = h.NombreUsuarioModifico, + FechaMod = h.Historial.FechaMod, + TipoMod = h.Historial.TipoMod + }).ToList(); + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/CambioParadaService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/CambioParadaService.cs new file mode 100644 index 0000000..4daa73a --- /dev/null +++ b/Backend/GestionIntegral.Api/Services/Distribucion/CambioParadaService.cs @@ -0,0 +1,185 @@ +using GestionIntegral.Api.Data; +using GestionIntegral.Api.Data.Repositories.Distribucion; +using GestionIntegral.Api.Dtos.Distribucion; +using GestionIntegral.Api.Models.Distribucion; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; + +namespace GestionIntegral.Api.Services.Distribucion +{ + public class CambioParadaService : ICambioParadaService + { + private readonly ICambioParadaRepository _paradaRepo; + private readonly ICanillaRepository _canillaRepo; // Para nombre y validación + private readonly DbConnectionFactory _connectionFactory; + private readonly ILogger _logger; + + public CambioParadaService( + ICambioParadaRepository paradaRepo, + ICanillaRepository canillaRepo, + DbConnectionFactory connectionFactory, + ILogger logger) + { + _paradaRepo = paradaRepo; + _canillaRepo = canillaRepo; + _connectionFactory = connectionFactory; + _logger = logger; + } + + private async Task MapToDto(CambioParadaCanilla parada) + { + var canillaData = await _canillaRepo.GetByIdAsync(parada.IdCanilla); + return new CambioParadaDto + { + IdRegistro = parada.IdRegistro, + IdCanilla = parada.IdCanilla, + NombreCanilla = canillaData.Canilla?.NomApe ?? "N/A", + Parada = parada.Parada, + VigenciaD = parada.VigenciaD.ToString("yyyy-MM-dd"), + VigenciaH = parada.VigenciaH?.ToString("yyyy-MM-dd") + }; + } + + public async Task> ObtenerPorCanillaAsync(int idCanilla) + { + var paradas = await _paradaRepo.GetByCanillaAsync(idCanilla); + var dtos = new List(); + foreach (var p in paradas) { dtos.Add(await MapToDto(p)); } + return dtos; + } + + public async Task ObtenerPorIdAsync(int idRegistro) + { + var parada = await _paradaRepo.GetByIdAsync(idRegistro); + return parada == null ? null : await MapToDto(parada); + } + + public async Task<(CambioParadaDto? Parada, string? Error)> CrearNuevaParadaAsync(int idCanilla, CreateCambioParadaDto createDto, int idUsuario) + { + var canilla = await _canillaRepo.GetByIdSimpleAsync(idCanilla); + if (canilla == null) return (null, "Canillita no encontrado."); + + if (createDto.VigenciaD.Date < DateTime.Today.AddYears(-5) || createDto.VigenciaD.Date > DateTime.Today.AddYears(5)) // Validación básica de fecha + return (null, "Fecha de Vigencia Desde inválida."); + + + using var connection = _connectionFactory.CreateConnection(); + if (connection.State != ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } + using var transaction = connection.BeginTransaction(); + try + { + // 1. Buscar la parada activa actual (si existe) para cerrarla + var paradaActual = await _paradaRepo.GetCurrentParadaAsync(idCanilla, transaction); + if (paradaActual != null) + { + if (createDto.VigenciaD.Date <= paradaActual.VigenciaD.Date) + { + transaction.Rollback(); + return (null, "La Vigencia Desde de la nueva parada debe ser posterior a la Vigencia Desde de la parada activa actual."); + } + // Cerrar la parada actual estableciendo VigenciaH al día anterior de la nueva VigenciaD + DateTime vigenciaHAactual = createDto.VigenciaD.Date.AddDays(-1); + bool cerrada = await _paradaRepo.UpdateVigenciaHAsync(paradaActual.IdRegistro, vigenciaHAactual, idUsuario, transaction); + if (!cerrada) throw new DataException("No se pudo cerrar la parada activa anterior."); + _logger.LogInformation("Parada anterior ID {IdRegistro} cerrada con VigenciaH {VigenciaH}", paradaActual.IdRegistro, vigenciaHAactual); + } + // 2. Crear la nueva parada + var nuevaParada = new CambioParadaCanilla + { + IdCanilla = idCanilla, + Parada = createDto.Parada, + VigenciaD = createDto.VigenciaD.Date, + VigenciaH = null // Nueva parada siempre inicia activa + }; + var paradaCreada = await _paradaRepo.CreateAsync(nuevaParada, idUsuario, transaction); + if (paradaCreada == null) throw new DataException("Error al crear el nuevo registro de parada."); + + // 3. Actualizar la parada principal en dist_dtCanillas + canilla.Parada = paradaCreada.Parada; // Actualizar el campo Parada en la tabla principal + bool canillaActualizado = await _canillaRepo.UpdateAsync(canilla, idUsuario, transaction); // Asume que tu repo de canilla tiene UpdateAsync que toma la entidad + if (!canillaActualizado) throw new DataException("Error al actualizar la parada principal del canillita."); + + + transaction.Commit(); + _logger.LogInformation("Nueva parada ID {IdRegistro} creada para Canilla ID {IdCanilla}. Parada principal actualizada.", paradaCreada.IdRegistro, idCanilla); + return (await MapToDto(paradaCreada), null); + } + catch (Exception ex) + { + try { transaction.Rollback(); } catch { } + _logger.LogError(ex, "Error en CrearNuevaParadaAsync para Canilla ID {IdCanilla}", idCanilla); + return (null, $"Error interno: {ex.Message}"); + } + finally { if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); } } + } + + public async Task<(bool Exito, string? Error)> CerrarParadaAsync(int idRegistro, UpdateCambioParadaDto updateDto, int idUsuario) + { + // Este método es para cerrar una parada manualmente si es necesario, + // por ejemplo, si se cometió un error y no se creó una nueva que la cierre. + var paradaExistente = await _paradaRepo.GetByIdAsync(idRegistro); + if (paradaExistente == null) return (false, "Registro de parada no encontrado."); + if (paradaExistente.VigenciaH.HasValue) return (false, "Esta parada ya tiene una fecha de Vigencia Hasta."); + if (updateDto.VigenciaH.Date < paradaExistente.VigenciaD.Date) + return (false, "La Vigencia Hasta no puede ser anterior a la Vigencia Desde."); + + using var connection = _connectionFactory.CreateConnection(); + if (connection.State != ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } + using var transaction = connection.BeginTransaction(); + try + { + bool actualizada = await _paradaRepo.UpdateVigenciaHAsync(idRegistro, updateDto.VigenciaH.Date, idUsuario, transaction); + if (!actualizada) throw new DataException("Error al actualizar la Vigencia Hasta de la parada."); + + // Opcional: Si esta era la parada activa en dist_dtCanillas, podrías querer limpiarla o marcarla de alguna forma + // Esto depende de si el campo 'Parada' en dist_dtCanillas siempre debe reflejar la última activa. + // Por simplicidad, no se actualiza dist_dtCanillas aquí al cerrar una parada manualmente. + + transaction.Commit(); + _logger.LogInformation("VigenciaH actualizada para Parada ID {IdRegistro}", idRegistro); + return (true, null); + } + catch (Exception ex) + { + try { transaction.Rollback(); } catch { } + _logger.LogError(ex, "Error al cerrar Parada ID {IdRegistro}", idRegistro); + return (false, $"Error interno: {ex.Message}"); + } + finally { if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); } } + } + + public async Task<(bool Exito, string? Error)> EliminarParadaAsync(int idRegistro, int idUsuario) + { + // La eliminación puede ser problemática si rompe la secuencia histórica. + // Considera si realmente quieres permitir esto o solo "cerrar" paradas. + var paradaExistente = await _paradaRepo.GetByIdAsync(idRegistro); + if (paradaExistente == null) return (false, "Registro de parada no encontrado."); + if (paradaExistente.VigenciaH == null) + return (false, "No se puede eliminar una parada activa. Primero debe cerrarla o crear una nueva que la reemplace."); + + using var connection = _connectionFactory.CreateConnection(); + if (connection.State != ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } + using var transaction = connection.BeginTransaction(); + try + { + bool eliminado = await _paradaRepo.DeleteAsync(idRegistro, idUsuario, transaction); + if (!eliminado) throw new DataException("Error al eliminar el registro de parada."); + + transaction.Commit(); + _logger.LogInformation("Registro de Parada ID {IdRegistro} eliminado por Usuario ID {IdUsuario}", idRegistro, idUsuario); + return (true, null); + } + catch (Exception ex) + { + try { transaction.Rollback(); } catch { } + _logger.LogError(ex, "Error al eliminar Parada ID {IdRegistro}", idRegistro); + return (false, $"Error interno: {ex.Message}"); + } + finally { if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); } } + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/CanillaService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/CanillaService.cs index a9a4456..5ccd0f8 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/CanillaService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/CanillaService.cs @@ -1,5 +1,6 @@ using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Distribucion; +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Models.Distribucion; using Microsoft.Extensions.Logging; @@ -261,5 +262,31 @@ namespace GestionIntegral.Api.Services.Distribucion return (false, $"Error interno al cambiar estado de baja: {ex.Message}"); } } + + public async Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idCanillaAfectado) + { + var historialData = await _canillaRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idCanillaAfectado); + + return historialData.Select(h => new CanillaHistorialDto + { + Id_Canilla = h.Historial.Id_Canilla, + Legajo = h.Historial.Legajo, + NomApe = h.Historial.NomApe, + Parada = h.Historial.Parada, + Id_Zona = h.Historial.Id_Zona, + Accionista = h.Historial.Accionista, + Obs = h.Historial.Obs, + Empresa = h.Historial.Empresa, + Baja = h.Historial.Baja, + FechaBaja = h.Historial.FechaBaja, + Id_Usuario = h.Historial.Id_Usuario, + NombreUsuarioModifico = h.NombreUsuarioModifico, + FechaMod = h.Historial.FechaMod, + TipoMod = h.Historial.TipoMod + }).ToList(); + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/DistribuidorService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/DistribuidorService.cs index 1a897c7..0f02456 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/DistribuidorService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/DistribuidorService.cs @@ -2,6 +2,7 @@ using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Distribucion; +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Models.Distribucion; using Microsoft.Extensions.Logging; @@ -85,7 +86,7 @@ namespace GestionIntegral.Api.Services.Distribucion var data = await _distribuidorRepository.GetByIdAsync(id); // MapToDto ahora devuelve DistribuidorDto? return MapToDto(data); - } + } public async Task ObtenerLookupPorIdAsync(int id) { @@ -104,9 +105,17 @@ namespace GestionIntegral.Api.Services.Distribucion var nuevoDistribuidor = new Distribuidor { - Nombre = createDto.Nombre, Contacto = createDto.Contacto, NroDoc = createDto.NroDoc, IdZona = createDto.IdZona, - Calle = createDto.Calle, Numero = createDto.Numero, Piso = createDto.Piso, Depto = createDto.Depto, - Telefono = createDto.Telefono, Email = createDto.Email, Localidad = createDto.Localidad + Nombre = createDto.Nombre, + Contacto = createDto.Contacto, + NroDoc = createDto.NroDoc, + IdZona = createDto.IdZona, + Calle = createDto.Calle, + Numero = createDto.Numero, + Piso = createDto.Piso, + Depto = createDto.Depto, + Telefono = createDto.Telefono, + Email = createDto.Email, + Localidad = createDto.Localidad }; using var connection = _connectionFactory.CreateConnection(); @@ -136,7 +145,7 @@ namespace GestionIntegral.Api.Services.Distribucion } catch (Exception ex) { - try { transaction.Rollback(); } catch {} + try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error CrearAsync Distribuidor: {Nombre}", createDto.Nombre); return (null, $"Error interno al crear el distribuidor: {ex.Message}"); } @@ -173,10 +182,10 @@ namespace GestionIntegral.Api.Services.Distribucion _logger.LogInformation("Distribuidor ID {IdDistribuidor} actualizado por Usuario ID {IdUsuario}.", id, idUsuario); return (true, null); } - catch (KeyNotFoundException) { try { transaction.Rollback(); } catch {} return (false, "Distribuidor no encontrado."); } + catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Distribuidor no encontrado."); } catch (Exception ex) { - try { transaction.Rollback(); } catch {} + try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error ActualizarAsync Distribuidor ID: {IdDistribuidor}", id); return (false, $"Error interno: {ex.Message}"); } @@ -200,18 +209,47 @@ namespace GestionIntegral.Api.Services.Distribucion // if (!saldosEliminados) throw new DataException("Error al eliminar saldos del distribuidor."); var eliminado = await _distribuidorRepository.DeleteAsync(id, idUsuario, transaction); - if (!eliminado) throw new DataException("Error al eliminar."); + if (!eliminado) throw new DataException("Error al eliminar."); transaction.Commit(); _logger.LogInformation("Distribuidor ID {IdDistribuidor} eliminado por Usuario ID {IdUsuario}.", id, idUsuario); return (true, null); } - catch (KeyNotFoundException) { try { transaction.Rollback(); } catch {} return (false, "Distribuidor no encontrado."); } + catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Distribuidor no encontrado."); } catch (Exception ex) { - try { transaction.Rollback(); } catch {} + try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error EliminarAsync Distribuidor ID: {IdDistribuidor}", id); return (false, $"Error interno: {ex.Message}"); } } + + public async Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idDistribuidorAfectado) + { + var historialData = await _distribuidorRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idDistribuidorAfectado); + + // Si necesitas NombreZona, harías el JOIN en el repo o una llamada aquí a _zonaRepository + return historialData.Select(h => new DistribuidorHistorialDto + { + Id_Distribuidor = h.Historial.Id_Distribuidor, + Nombre = h.Historial.Nombre, + Contacto = h.Historial.Contacto, + NroDoc = h.Historial.NroDoc, + Id_Zona = h.Historial.Id_Zona, + Calle = h.Historial.Calle, + Numero = h.Historial.Numero, + Piso = h.Historial.Piso, + Depto = h.Historial.Depto, + Telefono = h.Historial.Telefono, + Email = h.Historial.Email, + Localidad = h.Historial.Localidad, + Id_Usuario = h.Historial.Id_Usuario, + NombreUsuarioModifico = h.NombreUsuarioModifico, + FechaMod = h.Historial.FechaMod, + TipoMod = h.Historial.TipoMod + }).ToList(); + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/EmpresaService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/EmpresaService.cs index 96a5abb..d9ebca3 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/EmpresaService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/EmpresaService.cs @@ -7,7 +7,8 @@ using System.Data; using GestionIntegral.Api.Data; using GestionIntegral.Api.Models.Distribucion; using GestionIntegral.Api.Data.Repositories.Distribucion; -using GestionIntegral.Api.Data.Repositories.Contables; // Para IDbTransaction, ConnectionState +using GestionIntegral.Api.Data.Repositories.Contables; +using GestionIntegral.Api.Dtos.Auditoria; // Para IDbTransaction, ConnectionState namespace GestionIntegral.Api.Services.Distribucion { @@ -42,7 +43,7 @@ namespace GestionIntegral.Api.Services.Distribucion Detalle = e.Detalle }); } - + public async Task> ObtenerParaDropdown() { // El repositorio ya devuelve solo las activas si es necesario @@ -131,7 +132,7 @@ namespace GestionIntegral.Api.Services.Distribucion { throw new InvalidOperationException($"Falló al crear saldo inicial para distribuidor {idDistribuidor} y nueva empresa {empresaCreada.IdEmpresa}."); } - _logger.LogInformation("Saldo inicial creado para Distribuidor ID {IdDistribuidor}, Empresa ID {IdEmpresa}", idDistribuidor, empresaCreada.IdEmpresa); + _logger.LogInformation("Saldo inicial creado para Distribuidor ID {IdDistribuidor}, Empresa ID {IdEmpresa}", idDistribuidor, empresaCreada.IdEmpresa); } transaction.Commit(); @@ -158,41 +159,41 @@ namespace GestionIntegral.Api.Services.Distribucion public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateEmpresaDto updateDto, int idUsuario) { - var empresaExistente = await _empresaRepository.GetByIdAsync(id); - if (empresaExistente == null) - { - return (false, "Empresa no encontrada."); - } + var empresaExistente = await _empresaRepository.GetByIdAsync(id); + if (empresaExistente == null) + { + return (false, "Empresa no encontrada."); + } - if (await _empresaRepository.ExistsByNameAsync(updateDto.Nombre, id)) - { - return (false, "El nombre de la empresa ya existe para otro registro."); - } + if (await _empresaRepository.ExistsByNameAsync(updateDto.Nombre, id)) + { + return (false, "El nombre de la empresa ya existe para otro registro."); + } - empresaExistente.Nombre = updateDto.Nombre; - empresaExistente.Detalle = updateDto.Detalle; + empresaExistente.Nombre = updateDto.Nombre; + empresaExistente.Detalle = updateDto.Detalle; // --- Transacción --- using (var connection = _connectionFactory.CreateConnection()) { - if (connection is System.Data.Common.DbConnection dbConnection) { await dbConnection.OpenAsync(); } else { connection.Open(); } - using (var transaction = connection.BeginTransaction()) + if (connection is System.Data.Common.DbConnection dbConnection) { await dbConnection.OpenAsync(); } else { connection.Open(); } + using (var transaction = connection.BeginTransaction()) { try { - var actualizado = await _empresaRepository.UpdateAsync(empresaExistente, idUsuario, transaction); - if (!actualizado) - { - throw new InvalidOperationException("La actualización en el repositorio de empresas devolvió false."); - } - transaction.Commit(); - _logger.LogInformation("Empresa ID {IdEmpresa} actualizada exitosamente por Usuario ID {IdUsuario}.", id, idUsuario); - return (true, null); // Éxito + var actualizado = await _empresaRepository.UpdateAsync(empresaExistente, idUsuario, transaction); + if (!actualizado) + { + throw new InvalidOperationException("La actualización en el repositorio de empresas devolvió false."); + } + transaction.Commit(); + _logger.LogInformation("Empresa ID {IdEmpresa} actualizada exitosamente por Usuario ID {IdUsuario}.", id, idUsuario); + return (true, null); // Éxito } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error al intentar hacer rollback en ActualizarAsync Empresa."); } - _logger.LogError(ex, "Error en transacción ActualizarAsync para Empresa ID: {Id}", id); + _logger.LogError(ex, "Error en transacción ActualizarAsync para Empresa ID: {Id}", id); return (false, "Error interno al actualizar la empresa."); } } @@ -215,35 +216,39 @@ namespace GestionIntegral.Api.Services.Distribucion return (false, "No se puede eliminar. Existen publicaciones relacionadas a la empresa."); } - // --- Transacción --- + // --- Transacción --- using (var connection = _connectionFactory.CreateConnection()) { - if (connection is System.Data.Common.DbConnection dbConnection) { await dbConnection.OpenAsync(); } else { connection.Open(); } - using (var transaction = connection.BeginTransaction()) + if (connection is System.Data.Common.DbConnection dbConnection) { await dbConnection.OpenAsync(); } else { connection.Open(); } + using (var transaction = connection.BeginTransaction()) { try { // 1. Eliminar Saldos asociados bool saldosEliminados = await _saldoRepository.DeleteSaldosByEmpresaAsync(id, transaction); // No lanzamos error si saldosEliminados es false, podría no haber tenido saldos. Loggeamos si es necesario. - if (!saldosEliminados && await _saldoRepository.CheckIfSaldosExistForEmpresaAsync(id)) // Necesitarías este método en ISaldoRepository - { - _logger.LogWarning("Se intentó eliminar Empresa ID {IdEmpresa} pero falló la eliminación de saldos asociados.", id); - // Decidir si continuar o fallar. Por ahora, continuamos pero loggeamos. - // throw new InvalidOperationException("Error al intentar eliminar los saldos asociados a la empresa."); - } else if (!saldosEliminados) { - _logger.LogInformation("No se encontraron saldos para eliminar de la Empresa ID {IdEmpresa}.", id); - } else { - _logger.LogInformation("Saldos eliminados para Empresa ID {IdEmpresa}.", id); - } + if (!saldosEliminados && await _saldoRepository.CheckIfSaldosExistForEmpresaAsync(id)) // Necesitarías este método en ISaldoRepository + { + _logger.LogWarning("Se intentó eliminar Empresa ID {IdEmpresa} pero falló la eliminación de saldos asociados.", id); + // Decidir si continuar o fallar. Por ahora, continuamos pero loggeamos. + // throw new InvalidOperationException("Error al intentar eliminar los saldos asociados a la empresa."); + } + else if (!saldosEliminados) + { + _logger.LogInformation("No se encontraron saldos para eliminar de la Empresa ID {IdEmpresa}.", id); + } + else + { + _logger.LogInformation("Saldos eliminados para Empresa ID {IdEmpresa}.", id); + } // 2. Eliminar Empresa (Repo maneja historial) - var eliminado = await _empresaRepository.DeleteAsync(id, idUsuario, transaction); - if (!eliminado) - { - throw new InvalidOperationException("La eliminación en el repositorio de empresas devolvió false."); - } + var eliminado = await _empresaRepository.DeleteAsync(id, idUsuario, transaction); + if (!eliminado) + { + throw new InvalidOperationException("La eliminación en el repositorio de empresas devolvió false."); + } transaction.Commit(); _logger.LogInformation("Empresa ID {IdEmpresa} eliminada exitosamente por Usuario ID {IdUsuario}.", id, idUsuario); @@ -252,12 +257,30 @@ namespace GestionIntegral.Api.Services.Distribucion catch (Exception ex) { try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error al intentar hacer rollback en EliminarAsync Empresa."); } - _logger.LogError(ex, "Error en transacción EliminarAsync para Empresa ID: {Id}", id); + _logger.LogError(ex, "Error en transacción EliminarAsync para Empresa ID: {Id}", id); return (false, "Error interno al eliminar la empresa."); } } } - // --- Fin Transacción --- } - } + + public async Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idEmpresaAfectada) + { + var historialData = await _empresaRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idEmpresaAfectada); + + return historialData.Select(h => new EmpresaHistorialDto + { + Id_Empresa = h.Historial.Id_Empresa, + Nombre = h.Historial.Nombre, + Detalle = h.Historial.Detalle, + Id_Usuario = h.Historial.Id_Usuario, + NombreUsuarioModifico = h.NombreUsuarioModifico, + FechaMod = h.Historial.FechaMod, + TipoMod = h.Historial.TipoMod + }).ToList(); + } + } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/EntradaSalidaCanillaService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/EntradaSalidaCanillaService.cs index ca614cf..373bfc0 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/EntradaSalidaCanillaService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/EntradaSalidaCanillaService.cs @@ -11,6 +11,7 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; using System.Security.Claims; +using GestionIntegral.Api.Dtos.Auditoria; namespace GestionIntegral.Api.Services.Distribucion { @@ -476,5 +477,32 @@ namespace GestionIntegral.Api.Services.Distribucion } } } + + public async Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idParteAfectada) + { + // Asumiendo que _esCanillaRepository es tu IEntradaSalidaCanillaRepository + var historialData = await _esCanillaRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idParteAfectada); + + return historialData.Select(h => new EntradaSalidaCanillaHistorialDto + { + Id_Parte = h.Historial.Id_Parte, + Id_Publicacion = h.Historial.Id_Publicacion, + Id_Canilla = h.Historial.Id_Canilla, + Fecha = h.Historial.Fecha, + CantSalida = h.Historial.CantSalida, + CantEntrada = h.Historial.CantEntrada, + Id_Precio = h.Historial.Id_Precio, + Id_Recargo = h.Historial.Id_Recargo, + Id_PorcMon = h.Historial.Id_PorcMon, + Observacion = h.Historial.Observacion, + Id_Usuario = h.Historial.Id_Usuario, + NombreUsuarioModifico = h.NombreUsuarioModifico, + FechaMod = h.Historial.FechaMod, + TipoMod = h.Historial.TipoMod + }).ToList(); + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/EntradaSalidaDistService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/EntradaSalidaDistService.cs index 75f300d..83aca61 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/EntradaSalidaDistService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/EntradaSalidaDistService.cs @@ -1,6 +1,7 @@ using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Distribucion; +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Models.Distribucion; using Microsoft.Extensions.Logging; @@ -56,30 +57,15 @@ namespace GestionIntegral.Api.Services.Distribucion var publicacionData = await _publicacionRepository.GetByIdAsync(es.IdPublicacion); var distribuidorData = await _distribuidorRepository.GetByIdAsync(es.IdDistribuidor); - + // Obtener el valor bruto del movimiento decimal valorBrutoMovimiento = await CalcularMontoMovimiento( - es.IdPublicacion, es.IdDistribuidor, es.Fecha, es.Cantidad, + es.IdPublicacion, es.IdDistribuidor, es.Fecha, es.Cantidad, es.TipoMovimiento, // Pasamos el tipo de movimiento original aquí es.IdPrecio, es.IdRecargo, es.IdPorcentaje, distribuidorData.Distribuidor?.IdZona ); - // Ajustar para el DTO: si es "Entrada", el monto calculado es un crédito (negativo o positivo según convención) - // Para consistencia con el ajuste de saldo, si es Entrada, el MontoCalculado para el DTO puede ser el valor - // que se le "acredita" al distribuidor (o sea, el valor de la mercadería devuelta). - // La lógica de +/- para el saldo ya está en Crear/Actualizar/Eliminar. - // Aquí solo mostramos el valor del movimiento. Si es entrada, es el valor de lo devuelto. - // Si es salida, es el valor de lo que se le factura. - // El método CalcularMonto ya devuelve el monto que el distribuidor DEBE pagar por una SALIDA. - // Para una ENTRADA (devolución), el valor de esa mercadería es el mismo, pero opera en sentido contrario al saldo. - decimal montoCalculadoParaDto = valorBrutoMovimiento; - // Si queremos que el DTO muestre las entradas como un valor que "reduce la deuda", - // podría ser positivo. Si queremos que refleje el impacto directo en la factura (salidas suman, entradas restan), - // podríamos hacerlo negativo. - // Por ahora, dejaremos que CalcularMontoMovimiento devuelva el valor de una "Salida", - // y si es "Entrada", este mismo valor es el que se acredita. - // La columna `MontoCalculado` en el DTO representará el valor de la transacción. return new EntradaSalidaDistDto { @@ -98,13 +84,10 @@ namespace GestionIntegral.Api.Services.Distribucion MontoCalculado = montoCalculadoParaDto // Representa el valor de los N ejemplares }; } - + private async Task CalcularMontoMovimiento(int idPublicacion, int idDistribuidor, DateTime fecha, int cantidad, string tipoMovimiento, int idPrecio, int idRecargo, int idPorcentaje, int? idZonaDistribuidor) { - // YA NO SE DEVUELVE 0 PARA ENTRADA AQUÍ - // if (tipoMovimiento == "Entrada") return 0; - var precioConfig = await _precioRepository.GetByIdAsync(idPrecio); // Es crucial que idPrecio sea válido y se haya determinado correctamente antes de llamar aquí. // Si precioConfig es null, se lanzará una excepción abajo, lo cual está bien si es un estado inesperado. @@ -118,7 +101,6 @@ namespace GestionIntegral.Api.Services.Distribucion throw new InvalidOperationException($"Configuración de precio ID {idPrecio} no encontrada. No se puede calcular el monto."); } - decimal precioDia = 0; DayOfWeek diaSemana = fecha.DayOfWeek; switch (diaSemana) @@ -135,13 +117,13 @@ namespace GestionIntegral.Api.Services.Distribucion decimal valorRecargo = 0; if (idRecargo > 0 && idZonaDistribuidor.HasValue) { - var recargoConfig = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(idPublicacion, idZonaDistribuidor.Value, fecha); + var recargoConfig = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(idPublicacion, idZonaDistribuidor.Value, fecha); if (recargoConfig != null) { valorRecargo = recargoConfig.Valor; } } - + decimal precioConRecargo = precioDia + valorRecargo; decimal montoBase = precioConRecargo * cantidad; @@ -197,7 +179,7 @@ namespace GestionIntegral.Api.Services.Distribucion RecargoZona? recargoActivo = null; if (distribuidor.IdZona.HasValue) { - recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(createDto.IdPublicacion, distribuidor.IdZona.Value, createDto.Fecha.Date); + recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(createDto.IdPublicacion, distribuidor.IdZona.Value, createDto.Fecha.Date); } var porcPagoActivo = await _porcPagoRepository.GetActiveByPublicacionDistribuidorAndDateAsync(createDto.IdPublicacion, createDto.IdDistribuidor, createDto.Fecha.Date); @@ -228,13 +210,13 @@ namespace GestionIntegral.Api.Services.Distribucion decimal montoAfectacion = await CalcularMontoMovimiento( esCreada.IdPublicacion, esCreada.IdDistribuidor, esCreada.Fecha, esCreada.Cantidad, esCreada.TipoMovimiento, esCreada.IdPrecio, esCreada.IdRecargo, esCreada.IdPorcentaje, distribuidor.IdZona); - + // Si es Salida, montoAfectacion es positivo (aumenta deuda). Si es Entrada, es 0 por CalcularMontoMovimiento, // pero para el saldo, una Entrada (devolución) debería restar del saldo deudor. // Por lo tanto, el monto real a restar del saldo si es Entrada sería el valor de esos ejemplares devueltos. - if(esCreada.TipoMovimiento == "Entrada") + if (esCreada.TipoMovimiento == "Entrada") { - // Recalcular como si fuera salida para obtener el valor a acreditar/restar del saldo + // Recalcular como si fuera salida para obtener el valor a acreditar/restar del saldo montoAfectacion = await CalcularMontoMovimiento( esCreada.IdPublicacion, esCreada.IdDistribuidor, esCreada.Fecha, esCreada.Cantidad, "Salida", // Forzar tipo Salida para cálculo de valor esCreada.IdPrecio, esCreada.IdRecargo, esCreada.IdPorcentaje, distribuidor.IdZona); @@ -259,12 +241,12 @@ namespace GestionIntegral.Api.Services.Distribucion public async Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaDistDto updateDto, int idUsuario) { - // La actualización de un movimiento que afecta saldos es compleja. - // Si cambia la cantidad, el monto original y el nuevo deben calcularse, - // y la diferencia debe aplicarse al saldo. - // Por ahora, este DTO solo permite cambiar Cantidad y Observacion. - // Cambiar otros campos como Fecha, Publicacion, Distribuidor implicaría recalcular todo - // y posiblemente anular el movimiento original y crear uno nuevo. + // La actualización de un movimiento que afecta saldos es compleja. + // Si cambia la cantidad, el monto original y el nuevo deben calcularse, + // y la diferencia debe aplicarse al saldo. + // Por ahora, este DTO solo permite cambiar Cantidad y Observacion. + // Cambiar otros campos como Fecha, Publicacion, Distribuidor implicaría recalcular todo + // y posiblemente anular el movimiento original y crear uno nuevo. using var connection = _connectionFactory.CreateConnection(); if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); @@ -275,16 +257,16 @@ namespace GestionIntegral.Api.Services.Distribucion if (esExistente == null) return (false, "Movimiento no encontrado."); var publicacion = await _publicacionRepository.GetByIdSimpleAsync(esExistente.IdPublicacion); - if (publicacion == null) return (false, "Publicación asociada no encontrada."); // Muy raro + if (publicacion == null) return (false, "Publicación asociada no encontrada."); // Muy raro var distribuidor = await _distribuidorRepository.GetByIdSimpleAsync(esExistente.IdDistribuidor); - if (distribuidor == null) return (false, "Distribuidor asociado no encontrado."); + if (distribuidor == null) return (false, "Distribuidor asociado no encontrado."); // 1. Calcular monto del movimiento original (antes de la actualización) decimal montoOriginal = await CalcularMontoMovimiento( esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, esExistente.TipoMovimiento, esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona); - if(esExistente.TipoMovimiento == "Entrada") montoOriginal *= -1; // Para revertir + if (esExistente.TipoMovimiento == "Entrada") montoOriginal *= -1; // Para revertir // 2. Actualizar la entidad en la BD (esto también guarda en historial) var esParaActualizar = new EntradaSalidaDist @@ -308,7 +290,7 @@ namespace GestionIntegral.Api.Services.Distribucion decimal montoNuevo = await CalcularMontoMovimiento( esParaActualizar.IdPublicacion, esParaActualizar.IdDistribuidor, esParaActualizar.Fecha, esParaActualizar.Cantidad, esParaActualizar.TipoMovimiento, esParaActualizar.IdPrecio, esParaActualizar.IdRecargo, esParaActualizar.IdPorcentaje, distribuidor.IdZona); - if(esParaActualizar.TipoMovimiento == "Entrada") montoNuevo *= -1; + if (esParaActualizar.TipoMovimiento == "Entrada") montoNuevo *= -1; // 4. Ajustar saldo con la diferencia @@ -351,16 +333,19 @@ namespace GestionIntegral.Api.Services.Distribucion decimal montoReversion = await CalcularMontoMovimiento( esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, esExistente.TipoMovimiento, esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona); - + // Si es Salida, el monto es positivo, al revertir restamos. // Si es Entrada, el monto es 0 (por Calcular...), pero su efecto en saldo fue negativo, al revertir sumamos el valor. - if(esExistente.TipoMovimiento == "Salida") { + if (esExistente.TipoMovimiento == "Salida") + { montoReversion *= -1; // Para restar del saldo lo que se sumó - } else if (esExistente.TipoMovimiento == "Entrada") { + } + else if (esExistente.TipoMovimiento == "Entrada") + { // Recalcular el valor como si fuera salida para saber cuánto se restó del saldo - montoReversion = await CalcularMontoMovimiento( - esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, "Salida", - esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona); + montoReversion = await CalcularMontoMovimiento( + esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, "Salida", + esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona); // No se multiplica por -1 aquí, porque el saldo ya lo tiene restado, al eliminar revertimos sumando. } @@ -385,5 +370,32 @@ namespace GestionIntegral.Api.Services.Distribucion return (false, $"Error interno: {ex.Message}"); } } + + public async Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idParteAfectada) + { + var historialData = await _esRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idParteAfectada); + + return historialData.Select(h => new EntradaSalidaDistHistorialDto + { + Id_Parte = h.Historial.Id_Parte, + Id_Publicacion = h.Historial.Id_Publicacion, + Id_Distribuidor = h.Historial.Id_Distribuidor, + Fecha = h.Historial.Fecha, + TipoMovimiento = h.Historial.TipoMovimiento, + Cantidad = h.Historial.Cantidad, + Remito = h.Historial.Remito, + Observacion = h.Historial.Observacion, + Id_Precio = h.Historial.Id_Precio, + Id_Recargo = h.Historial.Id_Recargo, + Id_Porcentaje = h.Historial.Id_Porcentaje, + Id_Usuario = h.Historial.Id_Usuario, + NombreUsuarioModifico = h.NombreUsuarioModifico, + FechaMod = h.Historial.FechaMod, + TipoMod = h.Historial.TipoMod + }).ToList(); + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/ICambioParadaService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/ICambioParadaService.cs new file mode 100644 index 0000000..5f44df9 --- /dev/null +++ b/Backend/GestionIntegral.Api/Services/Distribucion/ICambioParadaService.cs @@ -0,0 +1,17 @@ +using GestionIntegral.Api.Dtos.Distribucion; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace GestionIntegral.Api.Services.Distribucion +{ + public interface ICambioParadaService + { + Task> ObtenerPorCanillaAsync(int idCanilla); + Task ObtenerPorIdAsync(int idRegistro); + Task<(CambioParadaDto? Parada, string? Error)> CrearNuevaParadaAsync(int idCanilla, CreateCambioParadaDto createDto, int idUsuario); + // Update podría ser solo para cerrar la vigencia, no para cambiar la dirección de una parada histórica. + Task<(bool Exito, string? Error)> CerrarParadaAsync(int idRegistro, UpdateCambioParadaDto updateDto, int idUsuario); + Task<(bool Exito, string? Error)> EliminarParadaAsync(int idRegistro, int idUsuario); // Si se permite + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/ICanillaService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/ICanillaService.cs index 89307ae..e23b396 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/ICanillaService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/ICanillaService.cs @@ -1,3 +1,4 @@ +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Distribucion; using System.Collections.Generic; using System.Threading.Tasks; @@ -11,5 +12,9 @@ namespace GestionIntegral.Api.Services.Distribucion Task<(CanillaDto? Canilla, string? Error)> CrearAsync(CreateCanillaDto createDto, int idUsuario); Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateCanillaDto updateDto, int idUsuario); Task<(bool Exito, string? Error)> ToggleBajaAsync(int id, bool darDeBaja, int idUsuario); + Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idCanillaAfectado); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/IDistribuidorService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/IDistribuidorService.cs index 40a55c7..bb14439 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/IDistribuidorService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/IDistribuidorService.cs @@ -1,3 +1,4 @@ +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Distribucion; using System.Collections.Generic; using System.Threading.Tasks; @@ -13,5 +14,9 @@ namespace GestionIntegral.Api.Services.Distribucion Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario); Task> GetAllDropdownAsync(); Task ObtenerLookupPorIdAsync(int id); + Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idDistribuidorAfectado); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/IEmpresaService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/IEmpresaService.cs index 9974495..75b58d1 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/IEmpresaService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/IEmpresaService.cs @@ -1,4 +1,5 @@ using GestionIntegral.Api.Dtos.Empresas; // DTOs de Empresas +using GestionIntegral.Api.Dtos.Auditoria; using System.Collections.Generic; using System.Threading.Tasks; @@ -13,5 +14,9 @@ namespace GestionIntegral.Api.Services.Distribucion Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario); Task> ObtenerParaDropdown(); Task ObtenerLookupPorIdAsync(int id); + Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idEmpresaAfectada); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/IEntradaSalidaCanillaService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/IEntradaSalidaCanillaService.cs index 3339197..49e6296 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/IEntradaSalidaCanillaService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/IEntradaSalidaCanillaService.cs @@ -1,3 +1,4 @@ +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Distribucion; using System; using System.Collections.Generic; @@ -19,5 +20,9 @@ namespace GestionIntegral.Api.Services.Distribucion Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario, ClaimsPrincipal userPrincipal); Task<(bool Exito, string? Error)> LiquidarMovimientosAsync(LiquidarMovimientosCanillaRequestDto liquidarDto, int idUsuarioLiquidador); + Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idParteAfectada); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/IEntradaSalidaDistService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/IEntradaSalidaDistService.cs index ecce864..4c8f063 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/IEntradaSalidaDistService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/IEntradaSalidaDistService.cs @@ -1,3 +1,4 @@ +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Distribucion; using System; using System.Collections.Generic; @@ -12,5 +13,9 @@ namespace GestionIntegral.Api.Services.Distribucion Task<(EntradaSalidaDistDto? Movimiento, string? Error)> CrearMovimientoAsync(CreateEntradaSalidaDistDto createDto, int idUsuario); Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaDistDto updateDto, int idUsuario); Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario); + Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idParteAfectada); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/INovedadCanillaService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/INovedadCanillaService.cs index 83d8ead..cc9a6d6 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/INovedadCanillaService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/INovedadCanillaService.cs @@ -1,3 +1,4 @@ +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Reportes; using System; @@ -15,5 +16,9 @@ namespace GestionIntegral.Api.Services.Distribucion Task<(bool Exito, string? Error)> EliminarAsync(int idNovedad, int idUsuario); Task> ObtenerReporteNovedadesAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); Task> ObtenerReporteGananciasAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); + Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idNovedadAfectada); } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Distribucion/NovedadCanillaService.cs b/Backend/GestionIntegral.Api/Services/Distribucion/NovedadCanillaService.cs index 6089dd7..0eea75e 100644 --- a/Backend/GestionIntegral.Api/Services/Distribucion/NovedadCanillaService.cs +++ b/Backend/GestionIntegral.Api/Services/Distribucion/NovedadCanillaService.cs @@ -1,6 +1,7 @@ // En Services/Distribucion (o donde corresponda) using GestionIntegral.Api.Data; // Para DbConnectionFactory using GestionIntegral.Api.Data.Repositories.Distribucion; +using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Reportes; using GestionIntegral.Api.Models.Distribucion; // Asegúrate que el modelo Canilla tenga NomApe @@ -250,5 +251,26 @@ namespace GestionIntegral.Api.Services.Distribucion throw; } } + + public async Task> ObtenerHistorialAsync( + DateTime? fechaDesde, DateTime? fechaHasta, + int? idUsuarioModifico, string? tipoModificacion, + int? idNovedadAfectada) + { + var historialData = await _novedadRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idNovedadAfectada); + + return historialData.Select(h => new NovedadCanillaHistorialDto + { + Id_Novedad = h.Historial.Id_Novedad, + Id_Canilla = h.Historial.Id_Canilla, + Fecha = h.Historial.Fecha, + Detalle = h.Historial.Detalle, + Id_Usuario = h.Historial.Id_Usuario, + NombreUsuarioModifico = h.NombreUsuarioModifico, + FechaMod = h.Historial.FechaMod, + TipoMod = h.Historial.TipoMod + // Si decides traer NombreCanilla desde el repo, mapealo aquí. + }).ToList(); + } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/appsettings.Development.json b/Backend/GestionIntegral.Api/appsettings.Development.json index 73a4d75..64354e1 100644 --- a/Backend/GestionIntegral.Api/appsettings.Development.json +++ b/Backend/GestionIntegral.Api/appsettings.Development.json @@ -6,7 +6,7 @@ } }, "ConnectionStrings": { - "DefaultConnection": "Server=TECNICA3;Database=gestionvbnet;User ID=apigestion;Password=1351;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;" + "DefaultConnection": "Server=TECNICA3;Database=SistemaGestion;User ID=apigestion;Password=1351;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;" }, "Jwt": { "Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2", diff --git a/Backend/GestionIntegral.Api/bin/Debug/net9.0/appsettings.Development.json b/Backend/GestionIntegral.Api/bin/Debug/net9.0/appsettings.Development.json index 73a4d75..64354e1 100644 --- a/Backend/GestionIntegral.Api/bin/Debug/net9.0/appsettings.Development.json +++ b/Backend/GestionIntegral.Api/bin/Debug/net9.0/appsettings.Development.json @@ -6,7 +6,7 @@ } }, "ConnectionStrings": { - "DefaultConnection": "Server=TECNICA3;Database=gestionvbnet;User ID=apigestion;Password=1351;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;" + "DefaultConnection": "Server=TECNICA3;Database=SistemaGestion;User ID=apigestion;Password=1351;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;" }, "Jwt": { "Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2", diff --git a/Backend/GestionIntegral.Api/obj/Debug/net9.0/GestionIntegral.Api.AssemblyInfo.cs b/Backend/GestionIntegral.Api/obj/Debug/net9.0/GestionIntegral.Api.AssemblyInfo.cs index d9225ca..aa0ca02 100644 --- a/Backend/GestionIntegral.Api/obj/Debug/net9.0/GestionIntegral.Api.AssemblyInfo.cs +++ b/Backend/GestionIntegral.Api/obj/Debug/net9.0/GestionIntegral.Api.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8fb94f8cefc3b498397ffcbb9b9a2e66c13b25b9")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+35e24ab7d2df29216a350a70ab6322c209221a4f")] [assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json b/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json index 408a79b..3da0c04 100644 --- a/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json +++ b/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","k3qzLxTWHeeJhAuWKMdta6j24bmJ9BMRMjuFEEVCRu0=","x/sHyso3gy4zVCu3ljpnTYCqu8IGZNRok1JoXiabIP8=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","ko3Qcj1qg4o0KikPIBL6WHcUA8sCBGtBIyzr8DuluqQ="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ZZivTt9Zh03vN/jzywHdSIjldJk\u002BW/DgTu7TlHDqhsY=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","k3qzLxTWHeeJhAuWKMdta6j24bmJ9BMRMjuFEEVCRu0=","x/sHyso3gy4zVCu3ljpnTYCqu8IGZNRok1JoXiabIP8=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","LSHkdvHGc9l/RgeSMksGWU1acjMl4KnCD0Uh6IcMfRo="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json b/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json index c2be6f1..950942a 100644 --- a/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json +++ b/Backend/GestionIntegral.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","k3qzLxTWHeeJhAuWKMdta6j24bmJ9BMRMjuFEEVCRu0=","x/sHyso3gy4zVCu3ljpnTYCqu8IGZNRok1JoXiabIP8=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","ko3Qcj1qg4o0KikPIBL6WHcUA8sCBGtBIyzr8DuluqQ="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ZZivTt9Zh03vN/jzywHdSIjldJk\u002BW/DgTu7TlHDqhsY=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","k3qzLxTWHeeJhAuWKMdta6j24bmJ9BMRMjuFEEVCRu0=","x/sHyso3gy4zVCu3ljpnTYCqu8IGZNRok1JoXiabIP8=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","LSHkdvHGc9l/RgeSMksGWU1acjMl4KnCD0Uh6IcMfRo="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/obj/Debug/net9.0/rpswa.dswa.cache.json b/Backend/GestionIntegral.Api/obj/Debug/net9.0/rpswa.dswa.cache.json index e22d90e..12b5bbd 100644 --- a/Backend/GestionIntegral.Api/obj/Debug/net9.0/rpswa.dswa.cache.json +++ b/Backend/GestionIntegral.Api/obj/Debug/net9.0/rpswa.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"nueagD6vos1qa5Z6EdwL+uix/UGN3umfwM2JskZDeIQ=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"nueagD6vos1qa5Z6EdwL+uix/UGN3umfwM2JskZDeIQ=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ZZivTt9Zh03vN/jzywHdSIjldJk\u002BW/DgTu7TlHDqhsY=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/Frontend/src/components/Modals/Distribucion/CambioParadaFormModal.tsx b/Frontend/src/components/Modals/Distribucion/CambioParadaFormModal.tsx new file mode 100644 index 0000000..e2d2841 --- /dev/null +++ b/Frontend/src/components/Modals/Distribucion/CambioParadaFormModal.tsx @@ -0,0 +1,170 @@ +import React, { useState, useEffect } from 'react'; +import { + Modal, Box, Typography, TextField, Button, CircularProgress, Alert +} from '@mui/material'; +import type { CambioParadaDto } from '../../../models/dtos/Distribucion/CambioParadaDto'; +import type { CreateCambioParadaDto } from '../../../models/dtos/Distribucion/CreateCambioParadaDto'; +import type { UpdateCambioParadaDto } from '../../../models/dtos/Distribucion/UpdateCambioParadaDto'; + +const modalStyle = { + position: 'absolute' as 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: { xs: '90%', sm: 500 }, + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 3, +}; + +interface CambioParadaFormModalProps { + open: boolean; + onClose: () => void; + // onSubmit se usará para crear o para cerrar una parada. + // El ID del registro solo es relevante al cerrar. + onSubmit: (data: CreateCambioParadaDto | UpdateCambioParadaDto, idRegistroParada?: number) => Promise; + idCanilla: number | null; // Necesario para crear una nueva parada para este canillita + nombreCanilla?: string; // Para mostrar en el título + paradaParaCerrar?: CambioParadaDto | null; // Si se está cerrando una parada existente + errorMessage?: string | null; + clearErrorMessage: () => void; +} + +const CambioParadaFormModal: React.FC = ({ + open, + onClose, + onSubmit, + idCanilla, + nombreCanilla, + paradaParaCerrar, // Si este tiene valor, estamos en modo "Cerrar Vigencia" + errorMessage, + clearErrorMessage +}) => { + const [parada, setParada] = useState(''); // Solo para nueva parada + const [vigenciaD, setVigenciaD] = useState(''); // Solo para nueva parada + const [vigenciaHCierre, setVigenciaHCierre] = useState(''); // Solo para cerrar parada existente + + const [loading, setLoading] = useState(false); + const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); + + const isModoCerrar = Boolean(paradaParaCerrar && paradaParaCerrar.idRegistro); + + useEffect(() => { + if (open) { + clearErrorMessage(); + setLocalErrors({}); + if (isModoCerrar && paradaParaCerrar) { + setParada(paradaParaCerrar.parada); // Mostrar la parada que se está cerrando (readonly) + setVigenciaD(paradaParaCerrar.vigenciaD.split('T')[0]); // Mostrar VigenciaD (readonly) + setVigenciaHCierre(''); // Limpiar para nuevo input + } else { // Modo Crear Nueva Parada + setParada(''); + setVigenciaD(new Date().toISOString().split('T')[0]); // Default a hoy + setVigenciaHCierre(''); + } + } + }, [open, paradaParaCerrar, isModoCerrar, clearErrorMessage]); + + const validate = (): boolean => { + const errors: { [key: string]: string | null } = {}; + if (isModoCerrar) { + if (!vigenciaHCierre.trim()) errors.vigenciaHCierre = 'La Vigencia Hasta es obligatoria para cerrar.'; + else if (!/^\d{4}-\d{2}-\d{2}$/.test(vigenciaHCierre)) errors.vigenciaHCierre = 'Formato de fecha inválido.'; + else if (paradaParaCerrar && new Date(vigenciaHCierre) < new Date(paradaParaCerrar.vigenciaD.split('T')[0])) { + errors.vigenciaHCierre = 'Vigencia Hasta no puede ser anterior a la Vigencia Desde de esta parada.'; + } + } else { // Modo Crear + if (!idCanilla) errors.general = "ID de Canillita no especificado."; // Error si no hay idCanilla + if (!parada.trim()) errors.parada = 'La dirección de parada es obligatoria.'; + else if (parada.trim().length > 150) errors.parada = 'Máximo 150 caracteres.'; + if (!vigenciaD.trim()) errors.vigenciaD = 'La Vigencia Desde es obligatoria.'; + else if (!/^\d{4}-\d{2}-\d{2}$/.test(vigenciaD)) errors.vigenciaD = 'Formato de fecha inválido.'; + } + setLocalErrors(errors); + return Object.keys(errors).length === 0; + }; + + const handleInputChange = (fieldName: string) => { + if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null })); + if (errorMessage) clearErrorMessage(); + }; + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + clearErrorMessage(); + if (!validate()) return; + + setLoading(true); + try { + if (isModoCerrar && paradaParaCerrar) { + const dataToSubmit: UpdateCambioParadaDto = { vigenciaH: vigenciaHCierre }; + await onSubmit(dataToSubmit, paradaParaCerrar.idRegistro); + } else if (idCanilla) { // Modo Crear + const dataToSubmit: CreateCambioParadaDto = { parada, vigenciaD }; + // El idCanilla se pasará al servicio desde la página padre o ya está en el contexto del servicio + await onSubmit(dataToSubmit); // El onSubmit de la página se encargará de pasar idCanilla al servicio correcto + } + onClose(); + } catch (error: any) { + console.error("Error en submit de CambioParadaFormModal:", error); + } finally { + setLoading(false); + } + }; + + return ( + + + + {isModoCerrar ? 'Cerrar Vigencia de Parada' : `Nueva Parada para ${nombreCanilla || 'Canillita'}`} + + {isModoCerrar && paradaParaCerrar && ( + + Parada Actual: {paradaParaCerrar.parada} + Vigente Desde: {new Date(paradaParaCerrar.vigenciaD + 'T00:00:00Z').toLocaleDateString('es-AR', {timeZone:'UTC'})} + + )} + + + {!isModoCerrar && ( + <> + {setParada(e.target.value); handleInputChange('parada');}} + margin="normal" fullWidth multiline rows={2} + error={!!localErrors.parada} helperText={localErrors.parada || (parada ? `${150 - parada.length} caracteres restantes` : '')} + disabled={loading} autoFocus inputProps={{maxLength: 150}} + /> + {setVigenciaD(e.target.value); handleInputChange('vigenciaD');}} + margin="normal" fullWidth + error={!!localErrors.vigenciaD} helperText={localErrors.vigenciaD || ''} + disabled={loading} InputLabelProps={{ shrink: true }} + /> + + )} + {isModoCerrar && ( + {setVigenciaHCierre(e.target.value); handleInputChange('vigenciaHCierre');}} + margin="normal" fullWidth + error={!!localErrors.vigenciaHCierre} helperText={localErrors.vigenciaHCierre || ''} + disabled={loading} InputLabelProps={{ shrink: true }} autoFocus + /> + )} + + {errorMessage && {errorMessage}} + {localErrors.general && {localErrors.general}} + + + + + + + + + ); +}; + +export default CambioParadaFormModal; \ No newline at end of file diff --git a/Frontend/src/layouts/MainLayout.tsx b/Frontend/src/layouts/MainLayout.tsx index 5382c65..f99c916 100644 --- a/Frontend/src/layouts/MainLayout.tsx +++ b/Frontend/src/layouts/MainLayout.tsx @@ -12,7 +12,7 @@ import LogoutIcon from '@mui/icons-material/Logout'; import { useAuth } from '../contexts/AuthContext'; import ChangePasswordModal from '../components/Modals/Usuarios/ChangePasswordModal'; import { useNavigate, useLocation } from 'react-router-dom'; -import { usePermissions } from '../hooks/usePermissions'; // <<--- AÑADIR ESTA LÍNEA +import { usePermissions } from '../hooks/usePermissions'; interface MainLayoutProps { children: ReactNode; @@ -27,6 +27,7 @@ const allAppModules = [ { label: 'Reportes', path: '/reportes', requiredPermission: 'SS004' }, { label: 'Radios', path: '/radios', requiredPermission: 'SS005' }, { label: 'Usuarios', path: '/usuarios', requiredPermission: 'SS006' }, + { label: 'Auditoría', path: '/auditoria', requiredPermission: null, onlySuperAdmin: true }, ]; const MainLayout: React.FC = ({ children }) => { @@ -47,33 +48,41 @@ const MainLayout: React.FC = ({ children }) => { const [selectedTab, setSelectedTab] = useState(false); const [anchorElUserMenu, setAnchorElUserMenu] = useState(null); - // --- INICIO DE CAMBIO: Filtrar módulos basados en permisos --- const accessibleModules = useMemo(() => { - if (!isAuthenticated) return []; // Si no está autenticado, ningún módulo excepto quizás login (que no está aquí) + if (!isAuthenticated) return []; return allAppModules.filter(module => { - if (module.requiredPermission === null) return true; // Inicio siempre accesible + if (module.onlySuperAdmin) { // Si el módulo es solo para SuperAdmin + return isSuperAdmin; + } + if (module.requiredPermission === null) return true; return isSuperAdmin || tienePermiso(module.requiredPermission); }); }, [isAuthenticated, isSuperAdmin, tienePermiso]); - // --- FIN DE CAMBIO --- useEffect(() => { - // --- INICIO DE CAMBIO: Usar accessibleModules para encontrar el tab --- const currentModulePath = accessibleModules.findIndex(module => location.pathname === module.path || (module.path !== '/' && location.pathname.startsWith(module.path + '/')) ); if (currentModulePath !== -1) { setSelectedTab(currentModulePath); } else if (location.pathname === '/') { - // Asegurar que Inicio se seleccione si es accesible const inicioIndex = accessibleModules.findIndex(m => m.path === '/'); if (inicioIndex !== -1) setSelectedTab(inicioIndex); else setSelectedTab(false); } else { - setSelectedTab(false); + // Si la ruta actual no coincide con ningún módulo accesible, + // y no es la raíz, podría ser una subruta de un módulo no accesible. + // O podría ser una ruta inválida. + // Podríamos intentar encontrar el módulo base y ver si es accesible. + const basePath = "/" + (location.pathname.split('/')[1] || ""); + const parentModuleIndex = accessibleModules.findIndex(m => m.path === basePath); + if (parentModuleIndex !== -1) { + setSelectedTab(parentModuleIndex); + } else { + setSelectedTab(false); + } } - // --- FIN DE CAMBIO --- - }, [location.pathname, accessibleModules]); // << CAMBIO: dependencia a accessibleModules + }, [location.pathname, accessibleModules]); const handleOpenUserMenu = (event: React.MouseEvent) => { setAnchorElUserMenu(event.currentTarget); @@ -100,25 +109,20 @@ const MainLayout: React.FC = ({ children }) => { if (isPasswordChangeForced) { logout(); // Si es forzado y cancela/falla, desloguear } else { - setShowForcedPasswordChangeModal(false); // Si no es forzado, solo cerrar modal + setShowForcedPasswordChangeModal(false); // Si no es forzado, solo cerrar modal } } }; const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { - // --- INICIO DE CAMBIO: Navegar usando accessibleModules --- if (accessibleModules[newValue]) { setSelectedTab(newValue); navigate(accessibleModules[newValue].path); } - // --- FIN DE CAMBIO --- }; - const isReportesModule = location.pathname.startsWith('/reportes'); - if (showForcedPasswordChangeModal && isPasswordChangeForced) { - // ... (sin cambios) - return ( + return ( = ({ children }) => { Sistema de Gestión - El Día - {/* ... (Menú de usuario sin cambios) ... */} - {user && ( + {user && ( Hola, {user.nombreCompleto} @@ -168,7 +171,7 @@ const MainLayout: React.FC = ({ children }) => { onClick={handleOpenUserMenu} color="inherit" > - + = ({ children }) => { )} - {/* --- INICIO DE CAMBIO: Renderizar Tabs solo si hay módulos accesibles y está autenticado --- */} {isAuthenticated && accessibleModules.length > 0 && ( = ({ children }) => { } }} > - {/* Mapear sobre accessibleModules en lugar de allAppModules */} {accessibleModules.map((module) => ( ))} )} - {/* --- FIN DE CAMBIO --- */} = ({ children }) => { {children} - `1px solid ${theme.palette.divider}` + `1px solid ${theme.palette.divider}` }}> Usuario: {user?.username} | Acceso: {user?.esSuperAdmin ? 'Super Administrador' : (user?.perfil || `ID ${user?.idPerfil}`)} @@ -265,5 +265,4 @@ const MainLayout: React.FC = ({ children }) => { ); }; - export default MainLayout; \ No newline at end of file diff --git a/Frontend/src/models/dtos/Auditoria/CanillaHistorialDto.ts b/Frontend/src/models/dtos/Auditoria/CanillaHistorialDto.ts new file mode 100644 index 0000000..006cd60 --- /dev/null +++ b/Frontend/src/models/dtos/Auditoria/CanillaHistorialDto.ts @@ -0,0 +1,21 @@ +export interface CanillaHistorialDto { + id_Canilla: number; + legajo?: number | null; + nomApe: string; + parada?: string | null; + id_Zona: number; + // nombreZona?: string; + accionista: boolean; + obs?: string | null; + empresa: number; // id de la empresa + // nombreEmpresa?: string; + baja: boolean; + fechaBaja?: string | null; // "yyyy-MM-ddTHH:mm:ss" o solo fecha + + id_Usuario: number; + nombreUsuarioModifico: string; + fechaMod: string; + tipoMod: string; + + id?: string; // Para DataGrid +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Auditoria/DistribuidorHistorialDto.ts b/Frontend/src/models/dtos/Auditoria/DistribuidorHistorialDto.ts new file mode 100644 index 0000000..af035db --- /dev/null +++ b/Frontend/src/models/dtos/Auditoria/DistribuidorHistorialDto.ts @@ -0,0 +1,22 @@ +export interface DistribuidorHistorialDto { + id_Distribuidor: number; + nombre: string; + contacto?: string | null; + nroDoc: string; + id_Zona?: number | null; + // nombreZona?: string; + calle?: string | null; + numero?: string | null; + piso?: string | null; + depto?: string | null; + telefono?: string | null; + email?: string | null; + localidad?: string | null; + + id_Usuario: number; + nombreUsuarioModifico: string; + fechaMod: string; + tipoMod: string; + + id?: string; // Para DataGrid +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Auditoria/EmpresaHistorialDto.ts b/Frontend/src/models/dtos/Auditoria/EmpresaHistorialDto.ts new file mode 100644 index 0000000..c3193b4 --- /dev/null +++ b/Frontend/src/models/dtos/Auditoria/EmpresaHistorialDto.ts @@ -0,0 +1,12 @@ +export interface EmpresaHistorialDto { + id_Empresa: number; + nombre: string; + detalle?: string | null; + + id_Usuario: number; + nombreUsuarioModifico: string; + fechaMod: string; + tipoMod: string; + + id?: string; // Para DataGrid +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Auditoria/EntradaSalidaCanillaHistorialDto.ts b/Frontend/src/models/dtos/Auditoria/EntradaSalidaCanillaHistorialDto.ts new file mode 100644 index 0000000..8fb9f8f --- /dev/null +++ b/Frontend/src/models/dtos/Auditoria/EntradaSalidaCanillaHistorialDto.ts @@ -0,0 +1,21 @@ +export interface EntradaSalidaCanillaHistorialDto { + id_Parte: number; + id_Publicacion: number; + // nombrePublicacion?: string; + id_Canilla: number; + // nombreCanilla?: string; + fecha: string; // Fecha original del movimiento + cantSalida: number; + cantEntrada: number; + id_Precio: number; + id_Recargo: number; + id_PorcMon: number; + observacion?: string | null; + + id_Usuario: number; + nombreUsuarioModifico: string; + fechaMod: string; // Fecha de la modificación + tipoMod: string; + + id?: string; // Para DataGrid +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Auditoria/EntradaSalidaDistHistorialDto.ts b/Frontend/src/models/dtos/Auditoria/EntradaSalidaDistHistorialDto.ts new file mode 100644 index 0000000..b060cc3 --- /dev/null +++ b/Frontend/src/models/dtos/Auditoria/EntradaSalidaDistHistorialDto.ts @@ -0,0 +1,22 @@ +export interface EntradaSalidaDistHistorialDto { + id_Parte: number; + id_Publicacion: number; + // nombrePublicacion?: string; + id_Distribuidor: number; + // nombreDistribuidor?: string; + fecha: string; // Fecha original del movimiento + tipoMovimiento: string; + cantidad: number; + remito: number; + observacion?: string | null; + id_Precio: number; + id_Recargo: number; + id_Porcentaje: number; + + id_Usuario: number; + nombreUsuarioModifico: string; + fechaMod: string; // Fecha de la modificación + tipoMod: string; + + id?: string; // Para DataGrid +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Auditoria/NotaCreditoDebitoHistorialDto.ts b/Frontend/src/models/dtos/Auditoria/NotaCreditoDebitoHistorialDto.ts new file mode 100644 index 0000000..cfda373 --- /dev/null +++ b/Frontend/src/models/dtos/Auditoria/NotaCreditoDebitoHistorialDto.ts @@ -0,0 +1,20 @@ +export interface NotaCreditoDebitoHistorialDto { + id_Nota: number; + destino: string; + id_Destino: number; + // nombreDestinatario?: string; // Opcional + referencia?: string | null; + tipo: string; + fecha: string; // Fecha original de la nota + monto: number; + observaciones?: string | null; + id_Empresa: number; + // nombreEmpresa?: string; // Opcional + + id_Usuario: number; + nombreUsuarioModifico: string; + fechaMod: string; // Fecha de la modificación + tipoMod: string; + + id?: string; // Para DataGrid +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Auditoria/NovedadCanillaHistorialDto.ts b/Frontend/src/models/dtos/Auditoria/NovedadCanillaHistorialDto.ts new file mode 100644 index 0000000..0b5ddc2 --- /dev/null +++ b/Frontend/src/models/dtos/Auditoria/NovedadCanillaHistorialDto.ts @@ -0,0 +1,14 @@ +export interface NovedadCanillaHistorialDto { + id_Novedad: number; + id_Canilla: number; + // nombreCanilla?: string; + fecha: string; // Fecha original + detalle?: string | null; + + id_Usuario: number; + nombreUsuarioModifico: string; + fechaMod: string; // Fecha de auditoría + tipoMod: string; + + id?: string; // Para DataGrid +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Auditoria/PagoDistribuidorHistorialDto.ts b/Frontend/src/models/dtos/Auditoria/PagoDistribuidorHistorialDto.ts new file mode 100644 index 0000000..b61146b --- /dev/null +++ b/Frontend/src/models/dtos/Auditoria/PagoDistribuidorHistorialDto.ts @@ -0,0 +1,19 @@ +export interface PagoDistribuidorHistorialDto { + id_Pago: number; // ID del pago original + id_Distribuidor: number; + fecha: string; // Fecha del pago original (YYYY-MM-DDTHH:mm:ss) + tipoMovimiento: string; + recibo: number; + monto: number; + id_TipoPago: number; + detalle?: string | null; + id_Empresa: number; + + // Campos de auditoría + id_Usuario: number; + nombreUsuarioModifico: string; + fechaMod: string; // Fecha de la modificación (YYYY-MM-DDTHH:mm:ss) + tipoMod: string; + + id?: string; // Para el DataGrid, se generará en el frontend +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Auditoria/SaldoAjusteHistorialDto.ts b/Frontend/src/models/dtos/Auditoria/SaldoAjusteHistorialDto.ts new file mode 100644 index 0000000..5676a48 --- /dev/null +++ b/Frontend/src/models/dtos/Auditoria/SaldoAjusteHistorialDto.ts @@ -0,0 +1,19 @@ +export interface SaldoAjusteHistorialDto { + idSaldoAjusteHist: number; + destino: string; + id_Destino: number; + // nombreDestinatario?: string; + id_Empresa: number; + // nombreEmpresa?: string; + montoAjuste: number; + saldoAnterior: number; + saldoNuevo: number; + justificacion: string; + fechaAjuste: string; // Es la FechaMod, pero la llamamos FechaAjuste para claridad + + id_UsuarioAjuste: number; // Corresponde a Id_Usuario en el DTO de C# + nombreUsuarioModifico: string; + // tipoMod?: string; // Podrías añadirlo fijo como "Ajuste Manual" en el frontend si es necesario + + id?: string; // Para DataGrid +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Auditoria/TipoPagoHistorialDto.ts b/Frontend/src/models/dtos/Auditoria/TipoPagoHistorialDto.ts new file mode 100644 index 0000000..96f4f95 --- /dev/null +++ b/Frontend/src/models/dtos/Auditoria/TipoPagoHistorialDto.ts @@ -0,0 +1,12 @@ +export interface TipoPagoHistorialDto { + id_TipoPago: number; + nombre: string; // Nombre del TipoPago en ese momento + detalle?: string | null; // Detalle en ese momento + + id_Usuario: number; + nombreUsuarioModifico: string; + fechaMod: string; + tipoMod: string; + + id?: string; // Para DataGrid +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Distribucion/CambioParadaDto.ts b/Frontend/src/models/dtos/Distribucion/CambioParadaDto.ts new file mode 100644 index 0000000..ad4de05 --- /dev/null +++ b/Frontend/src/models/dtos/Distribucion/CambioParadaDto.ts @@ -0,0 +1,9 @@ +export interface CambioParadaDto { + idRegistro: number; + idCanilla: number; + nombreCanilla: string; + parada: string; + vigenciaD: string; // "yyyy-MM-dd" + vigenciaH?: string | null; // "yyyy-MM-dd" + esActual: boolean; // Calculada en el backend o frontend +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Distribucion/CreateCambioParadaDto.ts b/Frontend/src/models/dtos/Distribucion/CreateCambioParadaDto.ts new file mode 100644 index 0000000..a994ea3 --- /dev/null +++ b/Frontend/src/models/dtos/Distribucion/CreateCambioParadaDto.ts @@ -0,0 +1,5 @@ +export interface CreateCambioParadaDto { + // idCanilla se pasa por la ruta + parada: string; + vigenciaD: string; // "yyyy-MM-dd" +} \ No newline at end of file diff --git a/Frontend/src/models/dtos/Distribucion/UpdateCambioParadaDto.ts b/Frontend/src/models/dtos/Distribucion/UpdateCambioParadaDto.ts new file mode 100644 index 0000000..22651a0 --- /dev/null +++ b/Frontend/src/models/dtos/Distribucion/UpdateCambioParadaDto.ts @@ -0,0 +1,3 @@ +export interface UpdateCambioParadaDto { + vigenciaH: string; // "yyyy-MM-dd" +} \ No newline at end of file diff --git a/Frontend/src/pages/Auditoria/AuditoriaGeneralPage.tsx b/Frontend/src/pages/Auditoria/AuditoriaGeneralPage.tsx new file mode 100644 index 0000000..36c2a30 --- /dev/null +++ b/Frontend/src/pages/Auditoria/AuditoriaGeneralPage.tsx @@ -0,0 +1,471 @@ +// src/pages/Auditoria/AuditoriaGeneralPage.tsx +import React, { useState, useEffect, useCallback } from 'react'; +import { + Box, Typography, Paper, TextField, Button, CircularProgress, Alert, + FormControl, InputLabel, Select, MenuItem, Tooltip +} from '@mui/material'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import { DataGrid, type GridColDef } from '@mui/x-data-grid'; +import { esES } from '@mui/x-data-grid/locales'; + +import auditoriaService from '../../services/Auditoria/auditoriaService'; + +import type { UsuarioDto } from '../../models/dtos/Usuarios/UsuarioDto'; +import usuarioService from '../../services/Usuarios/usuarioService'; +import { usePermissions } from '../../hooks/usePermissions'; +//import axios from 'axios'; // Para el tipo de error de Axios + +// Lista de tipos de entidad para el filtro +const TIPOS_ENTIDAD_AUDITABLES = [ + { value: "PagoDistribuidor", label: "Pagos de Distribuidores (cue_PagosDistribuidor_H)" }, + { value: "NotaCreditoDebito", label: "Notas C/D (cue_CreditosDebitos_H)" }, + { value: "EntradaSalidaDist", label: "E/S Distribuidores (dist_EntradasSalidas_H)" }, + { value: "EntradaSalidaCanilla", label: "E/S Canillitas (dist_EntradasSalidasCanillas_H)" }, + { value: "NovedadCanilla", label: "Novedades Canillitas (dist_dtNovedadesCanillas_H)" }, + { value: "SaldoAjuste", label: "Ajustes Manuales de Saldo (cue_SaldoAjustesHistorial)" }, + { value: "TipoPago", label: "Tipos de Pago (cue_dtTipopago_H)" }, + { value: "Canillita", label: "Canillitas (Maestro) (dist_dtCanillas_H)" }, + { value: "Distribuidor", label: "Distribuidores (Maestro) (dist_dtDistribuidores_H)" }, + { value: "Empresa", label: "Empresas (Maestro) (dist_dtEmpresas_H)" }, + { value: "Zona", label: "Zonas (Maestro) (dist_dtZonas_H)" }, + { value: "OtroDestino", label: "Otros Destinos (Maestro) (dist_dtOtrosDestinos_H)" }, + { value: "Publicacion", label: "Publicaciones (Maestro) (dist_dtPublicaciones_H)" }, + { value: "PubliSeccion", label: "Secciones de Publicación (dist_dtPubliSecciones_H)" }, + { value: "PrecioPublicacion", label: "Precios de Publicación (dist_Precios_H)" }, + { value: "RecargoZona", label: "Recargos por Zona (dist_RecargoZona_H)" }, + { value: "PorcPagoDistribuidor", label: "Porcentajes Pago Dist. (dist_PorcPago_H)" }, + { value: "PorcMonCanilla", label: "Porcentajes/Montos Canillita (dist_PorcMonPagoCanilla_H)" }, + { value: "ControlDevoluciones", label: "Control Devoluciones (dist_dtCtrlDevoluciones_H)" }, + { value: "TipoBobina", label: "Tipos de Bobina (bob_dtBobinas_H)" }, + { value: "EstadoBobina", label: "Estados de Bobina (bob_dtEstadosBobinas_H)" }, + { value: "PlantaImpresion", label: "Plantas de Impresión (bob_dtPlantas_H)" }, + { value: "StockBobina", label: "Stock de Bobinas (bob_StockBobinas_H)" }, + { value: "RegPublicacionesTirada", label: "Secciones de Tirada (bob_RegPublicaciones_H)" }, + { value: "RegTirada", label: "Registro de Tirada (bob_RegTiradas_H)" }, +].sort((a, b) => a.label.localeCompare(b.label)); + +const TIPOS_MODIFICACION = [ + "Creado", "Creada", "Actualizado", "Actualizada", "Modificado", "Modificada", + "Eliminado", "Eliminada", "Insertada", "Baja", "Alta", "Liquidada", "AjusteManualSaldo" + // Añadir más si es necesario (ej. "AjusteManualSaldo") +].sort(); + + +const AuditoriaGeneralPage: React.FC = () => { + const [datosAuditoria, setDatosAuditoria] = useState([]); // Tipo genérico para los datos de la tabla + const [columnasActuales, setColumnasActuales] = useState([]); + const [loading, setLoading] = useState(false); // Un solo loading para la búsqueda + const [error, setError] = useState(null); // Error general de la página o de la búsqueda + + const [filtroFechaDesde, setFiltroFechaDesde] = useState(new Date().toISOString().split('T')[0]); + const [filtroFechaHasta, setFiltroFechaHasta] = useState(new Date().toISOString().split('T')[0]); + const [filtroIdUsuarioMod, setFiltroIdUsuarioMod] = useState(''); + const [filtroTipoEntidad, setFiltroTipoEntidad] = useState(''); + const [filtroIdEntidad, setFiltroIdEntidad] = useState(''); + const [filtroTipoMod, setFiltroTipoMod] = useState(''); + + const [usuariosDropdown, setUsuariosDropdown] = useState([]); + const [loadingDropdowns, setLoadingDropdowns] = useState(false); + + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(25); + + const { tienePermiso, isSuperAdmin } = usePermissions(); + const puedeVerAuditoria = isSuperAdmin || tienePermiso("AU_GENERAL_VIEW"); // Define este permiso + + const formatDate = (dateString?: string | null): string => { + if (!dateString) return '-'; + const datePart = dateString.split('T')[0]; + const parts = datePart.split('-'); + if (parts.length === 3) { return `${parts[2]}/${parts[1]}/${parts[0]}`; } + return datePart; + }; + const currencyFormatter = (value?: number | null) => value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : '-'; + const numberFormatter = (value?: number | null) => value != null ? value.toLocaleString('es-AR') : '-'; + + + useEffect(() => { + const fetchDropdowns = async () => { + if (!puedeVerAuditoria) return; + setLoadingDropdowns(true); + try { + const users = await usuarioService.getAllUsuarios(); + setUsuariosDropdown(users); + } catch (e) { + console.error("Error cargando usuarios para filtro auditoría", e); + setError("Error al cargar usuarios para filtro."); + } finally { + setLoadingDropdowns(false); + } + }; + fetchDropdowns(); + }, [puedeVerAuditoria]); + + const getCommonAuditColumns = (): GridColDef[] => [ + { field: 'fechaMod', headerName: 'Fecha Mod.', width: 170, type: 'dateTime', valueFormatter: (value) => formatDate(value as string) }, + { field: 'nombreUsuarioModifico', headerName: 'Modificado Por', width: 180, flex: 0.8 }, + { field: 'tipoMod', headerName: 'Acción', width: 120, flex: 0.5 }, + // Se pueden añadir más columnas comunes aquí si aplican a TODOS los historiales + ]; + + const cargarHistorial = useCallback(async () => { + if (!puedeVerAuditoria) { + setError("No tiene permiso para ver la auditoría."); setLoading(false); return; + } + if (!filtroTipoEntidad) { + setError("Debe seleccionar un 'Tipo de Entidad a Auditar' para buscar."); + setDatosAuditoria([]); setColumnasActuales(getCommonAuditColumns()); setLoading(false); return; + } + setLoading(true); setError(null); + + try { + const commonParams = { + fechaDesde: filtroFechaDesde || undefined, + fechaHasta: filtroFechaHasta || undefined, + idUsuarioModificador: filtroIdUsuarioMod ? Number(filtroIdUsuarioMod) : undefined, + tipoModificacion: filtroTipoMod || undefined, + }; + + let rawData: any[] = []; + let cols: GridColDef[] = getCommonAuditColumns(); + + switch (filtroTipoEntidad) { + case "PagoDistribuidor": + const pagoHist = await auditoriaService.getHistorialPagosDistribuidor({ + ...commonParams, + idPagoAfectado: filtroIdEntidad ? Number(filtroIdEntidad) : undefined + }); + rawData = pagoHist; + cols = [ + ...getCommonAuditColumns(), + { field: 'id_Pago', headerName: 'ID Pago', width: 100, align: 'center', headerAlign: 'center' }, + { field: 'id_Distribuidor', headerName: 'ID Dist.', width: 100, align: 'center', headerAlign: 'center' }, // Podrías añadir Nombre si el DTO lo trae + { field: 'id_Empresa', headerName: 'ID Emp.', width: 100, align: 'center', headerAlign: 'center' }, // Podrías añadir Nombre + { field: 'fecha', headerName: 'Fecha Pago', width: 120, type: 'date', valueFormatter: (value) => formatDate(value as string) }, + { field: 'recibo', headerName: 'Recibo', width: 100 }, + { field: 'tipoMovimiento', headerName: 'Tipo Mov.', width: 120 }, + { field: 'monto', headerName: 'Monto Pago', width: 130, type: 'number', valueFormatter: (v) => currencyFormatter(v as number) }, + { field: 'id_TipoPago', headerName: 'ID Tipo Pago', width: 110, align: 'center', headerAlign: 'center' }, // Podrías añadir Nombre + { field: 'detalle', headerName: 'Detalle Pago', flex: 1, minWidth: 150, renderCell: (params) => ({params.value || '-'}) }, + ]; + break; + case "NotaCreditoDebito": + const notaCDHist = await auditoriaService.getHistorialNotasCD({ + ...commonParams, + idNotaAfectada: filtroIdEntidad ? Number(filtroIdEntidad) : undefined + }); + rawData = notaCDHist; + cols = [ + ...getCommonAuditColumns(), + { field: 'id_Nota', headerName: 'ID Nota Orig.', width: 110, align: 'center', headerAlign: 'center' }, + { field: 'destino', headerName: 'Destino', width: 120 }, + { field: 'id_Destino', headerName: 'ID Dest.', width: 100, align: 'center', headerAlign: 'center' }, + // NombreDestinatario y NombreEmpresa se podrían traer si el DTO de historial los incluyera + { field: 'tipo', headerName: 'Tipo Nota', width: 100 }, + { field: 'fecha', headerName: 'Fecha Nota', width: 120, type: 'date', valueFormatter: (value) => formatDate(value as string) }, + { field: 'monto', headerName: 'Monto Nota', width: 130, type: 'number', valueFormatter: (v) => currencyFormatter(v as number) }, + { field: 'referencia', headerName: 'Referencia', width: 150, renderCell: (params) => ({params.value || '-'}) }, + { field: 'observaciones', headerName: 'Obs. Nota', flex: 1, minWidth: 150, renderCell: (params) => ({params.value || '-'}) }, + ]; + break; + case "EntradaSalidaDist": + const esDistHist = await auditoriaService.getHistorialEntradasSalidasDist({ + ...commonParams, + idParteAfectada: filtroIdEntidad ? Number(filtroIdEntidad) : undefined + // Si añades más filtros al servicio, pásalos aquí + }); + rawData = esDistHist; + cols = [ + ...getCommonAuditColumns(), + { field: 'id_Parte', headerName: 'ID Mov.', width: 100, align: 'center', headerAlign: 'center' }, + { field: 'id_Publicacion', headerName: 'ID Pub.', width: 100, align: 'center', headerAlign: 'center' }, + { field: 'id_Distribuidor', headerName: 'ID Dist.', width: 100, align: 'center', headerAlign: 'center' }, + { field: 'fecha', headerName: 'Fecha Mov.', width: 120, type: 'date', valueFormatter: (value) => formatDate(value as string) }, + { field: 'tipoMovimiento', headerName: 'Tipo', width: 100 }, + { field: 'cantidad', headerName: 'Cantidad', width: 100, type: 'number', valueFormatter: (v) => numberFormatter(v as number) }, + { field: 'remito', headerName: 'Remito', width: 100, type: 'number' }, + { field: 'observacion', headerName: 'Obs. Mov.', flex: 1, minWidth: 150, renderCell: (params) => ({params.value || '-'}) }, + // Podrías mostrar Id_Precio, Id_Recargo, Id_Porcentaje si es útil + ]; + break; + case "EntradaSalidaCanilla": + const esCanillaHist = await auditoriaService.getHistorialEntradasSalidasCanilla({ + ...commonParams, + idParteAfectada: filtroIdEntidad ? Number(filtroIdEntidad) : undefined + }); + rawData = esCanillaHist; + cols = [ + ...getCommonAuditColumns(), + { field: 'id_Parte', headerName: 'ID Mov.', width: 100, align: 'center', headerAlign: 'center' }, + { field: 'id_Publicacion', headerName: 'ID Pub.', width: 100, align: 'center', headerAlign: 'center' }, + { field: 'id_Canilla', headerName: 'ID Can.', width: 100, align: 'center', headerAlign: 'center' }, + { field: 'fecha', headerName: 'Fecha Mov.', width: 120, type: 'date', valueFormatter: (value) => formatDate(value as string) }, + { field: 'cantSalida', headerName: 'Salida', width: 90, type: 'number', valueFormatter: (v) => numberFormatter(v as number) }, + { field: 'cantEntrada', headerName: 'Entrada', width: 90, type: 'number', valueFormatter: (v) => numberFormatter(v as number) }, + { field: 'observacion', headerName: 'Obs. Mov.', flex: 1, minWidth: 150, renderCell: (params) => ({params.value || '-'}) }, + // Considera mostrar Id_Precio, Id_Recargo, Id_PorcMon si es relevante + ]; + break; + case "NovedadCanilla": + const novedadHist = await auditoriaService.getHistorialNovedadesCanilla({ + ...commonParams, + idNovedadAfectada: filtroIdEntidad ? Number(filtroIdEntidad) : undefined + // También podrías pasar un filtro de idCanilla si lo añades a HistorialNovedadesCanillaParams y al backend + }); + rawData = novedadHist; + cols = [ + ...getCommonAuditColumns(), + { field: 'id_Novedad', headerName: 'ID Novedad', width: 110, align: 'center', headerAlign: 'center' }, + { field: 'id_Canilla', headerName: 'ID Canillita', width: 110, align: 'center', headerAlign: 'center' }, + // Aquí podrías querer mostrar el NombreCanilla, necesitarías un JOIN o una llamada extra en el servicio + { field: 'fecha', headerName: 'Fecha Novedad', width: 120, type: 'date', valueFormatter: (value) => formatDate(value as string) }, + { + field: 'detalle', + headerName: 'Detalle Novedad', + flex: 1, + minWidth: 250, + renderCell: (params) => ( + + {params.value || '-'} + + ) + }, + ]; + break; + case "SaldoAjuste": + const ajusteHist = await auditoriaService.getHistorialAjustesSaldo({ + // Pasar los filtros comunes + fechaDesde: commonParams.fechaDesde, + fechaHasta: commonParams.fechaHasta, + idUsuarioModificador: commonParams.idUsuarioModificador, + // Filtros específicos para este historial (si el backend los usa para este endpoint en particular) + // Si no, estos se ignorarán o podrías omitirlos si el endpoint no los toma. + // El endpoint actual que definimos sí los toma. + destino: filtroIdEntidad ? (TIPOS_ENTIDAD_AUDITABLES.find(t => t.value === filtroTipoEntidad)?.label.includes("Dist") ? "Distribuidores" : "Canillas") : undefined, // Lógica para determinar Destino + idDestino: filtroIdEntidad ? Number(filtroIdEntidad) : undefined, // ID Entidad Afectada aquí sería idDestino + // idEmpresa: si tienes un filtro de empresa general, pásalo + }); + rawData = ajusteHist; + cols = [ + // Reutilizar 'fechaMod' como 'Fecha Ajuste' y 'nombreUsuarioModifico' + { field: 'fechaAjuste', headerName: 'Fecha Ajuste', width: 170, type: 'dateTime', valueFormatter: (value) => formatDate(value as string) }, + { field: 'nombreUsuarioModifico', headerName: 'Ajustado Por', width: 180, flex: 0.8 }, + { field: 'destino', headerName: 'Tipo Dest.', width: 120 }, + { field: 'id_Destino', headerName: 'ID Dest.', width: 100, align: 'center', headerAlign: 'center' }, + // Aquí podrías querer mostrar NombreDestinatario y NombreEmpresa, + // requeriría que SaldoAjusteHistorialDto los traiga (JOINs en backend) + { field: 'id_Empresa', headerName: 'ID Emp.', width: 100, align: 'center', headerAlign: 'center' }, + { field: 'montoAjuste', headerName: 'Monto Ajuste', width: 130, type: 'number', valueFormatter: (v) => currencyFormatter(v as number) }, + { field: 'saldoAnterior', headerName: 'Saldo Ant.', width: 130, type: 'number', valueFormatter: (v) => currencyFormatter(v as number) }, + { field: 'saldoNuevo', headerName: 'Saldo Nvo.', width: 130, type: 'number', valueFormatter: (v) => currencyFormatter(v as number) }, + { + field: 'justificacion', + headerName: 'Justificación', + flex: 1, + minWidth: 200, + renderCell: (params) => ({params.value || '-'}) + }, + ]; + break; + case "TipoPago": + const tipoPagoHist = await auditoriaService.getHistorialTiposPago({ + ...commonParams, + idTipoPagoAfectado: filtroIdEntidad ? Number(filtroIdEntidad) : undefined + }); + rawData = tipoPagoHist; + cols = [ + ...getCommonAuditColumns(), + { field: 'id_TipoPago', headerName: 'ID Tipo Pago', width: 110, align: 'center', headerAlign: 'center' }, + { field: 'nombre', headerName: 'Nombre Tipo Pago', width: 200 }, + { + field: 'detalle', + headerName: 'Detalle', + flex: 1, + minWidth: 250, + renderCell: (params) => ({params.value || '-'}) + }, + ]; + break; + case "Canillita": // Historial del maestro de Canillitas + const canMaestroHist = await auditoriaService.getHistorialCanillitasMaestro({ + ...commonParams, + idCanillaAfectado: filtroIdEntidad ? Number(filtroIdEntidad) : undefined + }); + rawData = canMaestroHist; + cols = [ + ...getCommonAuditColumns(), + { field: 'id_Canilla', headerName: 'ID Canillita', width: 110, align: 'center', headerAlign: 'center' }, + { field: 'nomApe', headerName: 'Nombre y Apellido', width: 200 }, + { field: 'legajo', headerName: 'Legajo', width: 100, type: 'number' }, + { field: 'parada', headerName: 'Parada', width: 180, renderCell: (params) => ({params.value || '-'}) }, + { field: 'id_Zona', headerName: 'ID Zona', width: 100, align: 'center', headerAlign: 'center' }, + { field: 'accionista', headerName: 'Accionista', width: 100, type: 'boolean' }, + { field: 'empresa', headerName: 'ID Empresa', width: 100, align: 'center', headerAlign: 'center' }, // ID de la empresa + { field: 'baja', headerName: 'Baja', width: 80, type: 'boolean' }, + { field: 'fechaBaja', headerName: 'Fecha Baja', width: 120, type: 'date', valueFormatter: (value) => formatDate(value as string) }, + { field: 'obs', headerName: 'Obs.', flex: 1, minWidth: 150, renderCell: (params) => ({params.value || '-'}) }, + ]; + break; + case "Distribuidor": // Historial del maestro de Distribuidores + const distMaestroHist = await auditoriaService.getHistorialDistribuidoresMaestro({ + ...commonParams, + idDistribuidorAfectado: filtroIdEntidad ? Number(filtroIdEntidad) : undefined + }); + rawData = distMaestroHist; + cols = [ + ...getCommonAuditColumns(), + { field: 'id_Distribuidor', headerName: 'ID Distribuidor', width: 110, align: 'center', headerAlign: 'center' }, + { field: 'nombre', headerName: 'Nombre', width: 200 }, + { field: 'nroDoc', headerName: 'Nro. Doc.', width: 120 }, + { field: 'contacto', headerName: 'Contacto', width: 150, renderCell: (params) => ({params.value || '-'}) }, + { field: 'id_Zona', headerName: 'ID Zona', width: 90, align: 'center', headerAlign: 'center' }, + // Podrías añadir más campos como Calle, Localidad, etc. si son importantes para la auditoría visual + { field: 'email', headerName: 'Email', width: 180, renderCell: (params) => ({params.value || '-'}) }, + { field: 'telefono', headerName: 'Teléfono', width: 130 }, + ]; + break; + case "Empresa": // Historial del maestro de Empresas + const empMaestroHist = await auditoriaService.getHistorialEmpresasMaestro({ + ...commonParams, + idEmpresaAfectada: filtroIdEntidad ? Number(filtroIdEntidad) : undefined + }); + rawData = empMaestroHist; + cols = [ + ...getCommonAuditColumns(), + { field: 'id_Empresa', headerName: 'ID Empresa', width: 110, align:'center', headerAlign:'center' }, + { field: 'nombre', headerName: 'Nombre Empresa', width: 250 }, + { + field: 'detalle', + headerName: 'Detalle', + flex:1, + minWidth:250, + renderCell: (params) => ( {params.value || '-'})}, + ]; + break; + default: + setError(`La vista de auditoría para '${filtroTipoEntidad}' aún no está implementada.`); + setDatosAuditoria([]); + setColumnasActuales(getCommonAuditColumns()); + setLoading(false); + return; + } + // Asignar un ID único a cada fila para el DataGrid si no viene del DTO + setDatosAuditoria(rawData.map((item, index) => ({ ...item, id: item.id_NotaOriginal || item.id_Nota || item.idHist || `hist-${filtroTipoEntidad}-${index}-${Math.random()}` }))); // Asegurar ID único + setColumnasActuales(cols); + + } catch (err: any) { + console.error(err); + setError(err.response?.data?.message || 'Error al cargar el historial.'); + setDatosAuditoria([]); + setColumnasActuales(getCommonAuditColumns()); + } finally { + setLoading(false); + } + }, [ + puedeVerAuditoria, filtroTipoEntidad, filtroFechaDesde, filtroFechaHasta, + filtroIdUsuarioMod, filtroIdEntidad, filtroTipoMod + ]); + + const handleBuscar = () => { + setPage(0); + cargarHistorial(); + } + + if (!puedeVerAuditoria && !loadingDropdowns) { // Si ya terminaron de cargar los dropdowns y no tiene permiso + return No tiene permiso para acceder a la Auditoría General.; + } + if (loadingDropdowns && !usuariosDropdown.length) { // Spinner inicial para dropdowns + return ; + } + + + return ( + + Auditoría General del Sistema + + Filtros + + setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} /> + setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} /> + + Modificado Por + + + + Tipo de Entidad a Auditar + + {!filtroTipoEntidad && error && error.includes("Debe seleccionar un 'Tipo de Entidad") && {error}} + + setFiltroIdEntidad(e.target.value)} sx={{ minWidth: 150 }} + InputProps={{ inputProps: { min: 1 } }} + disabled={!filtroTipoEntidad} + /> + + Tipo Modificación + + + + + + + {/* Mostrar error general si no es un error de "seleccione tipo de entidad" */} + {error && !error.includes("Debe seleccionar un 'Tipo de Entidad") && !loading && {error}} + + {!loading && !error && filtroTipoEntidad && ( + {/* Ajustar altura */} + { + setPage(model.page); + setRowsPerPage(model.pageSize); + }} + pageSizeOptions={[25, 50, 100]} + rowHeight={48} + sx={{ + '& .MuiDataGrid-cell': { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, + '& .MuiDataGrid-columnHeaderTitleContainer': { overflow: 'hidden' }, + }} + getRowId={(row) => row.id} // Asegurar que getRowId use el 'id' que generamos + /> + + )} + {!loading && !error && datosAuditoria.length === 0 && filtroTipoEntidad && ( + + No se encontraron registros de auditoría para '{TIPOS_ENTIDAD_AUDITABLES.find(t => t.value === filtroTipoEntidad)?.label || filtroTipoEntidad}' con los filtros aplicados. + + )} + {!loading && !error && !filtroTipoEntidad && ( + + Seleccione un "Tipo de Entidad a Auditar" y presione "Buscar". + + )} + + ); +}; + +export default AuditoriaGeneralPage; \ No newline at end of file diff --git a/Frontend/src/pages/Distribucion/GestionarCanillitasPage.tsx b/Frontend/src/pages/Distribucion/GestionarCanillitasPage.tsx index dc848c3..c193899 100644 --- a/Frontend/src/pages/Distribucion/GestionarCanillitasPage.tsx +++ b/Frontend/src/pages/Distribucion/GestionarCanillitasPage.tsx @@ -7,6 +7,7 @@ import { import AddIcon from '@mui/icons-material/Add'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import ToggleOnIcon from '@mui/icons-material/ToggleOn'; +import HistoryIcon from '@mui/icons-material/History'; import ToggleOffIcon from '@mui/icons-material/ToggleOff'; import EditIcon from '@mui/icons-material/Edit'; import EventNoteIcon from '@mui/icons-material/EventNote'; // << AÑADIR IMPORTACIÓN DEL ICONO @@ -46,10 +47,8 @@ const GestionarCanillitasPage: React.FC = () => { const puedeCrear = isSuperAdmin || tienePermiso("CG002"); const puedeModificar = isSuperAdmin || tienePermiso("CG003"); const puedeDarBaja = isSuperAdmin || tienePermiso("CG005"); - // Permisos para Novedades - const puedeGestionarNovedades = isSuperAdmin || tienePermiso("CG006"); // << DEFINIR PERMISO - // Para la opción "Ver Novedades", podemos usar el permiso de ver canillitas (CG001) - // O si solo se quiere mostrar si puede gestionarlas, usar puedeGestionarNovedades + const puedeGestionarParadas = isSuperAdmin || tienePermiso("CG007"); + const puedeGestionarNovedades = isSuperAdmin || tienePermiso("CG006"); const puedeVerNovedadesCanilla = puedeVer || puedeGestionarNovedades; // << LÓGICA PARA MOSTRAR LA OPCIÓN @@ -131,6 +130,12 @@ const GestionarCanillitasPage: React.FC = () => { const handleChangeRowsPerPage = (event: React.ChangeEvent) => { setRowsPerPage(parseInt(event.target.value, 10)); setPage(0); }; + + const handleOpenParadas = (idCan: number) => { + navigate(`/distribucion/canillas/${idCan}/paradas`); + handleMenuClose(); + }; + const displayData = canillitas.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); if (!loading && !puedeVer) { @@ -181,7 +186,7 @@ const GestionarCanillitasPage: React.FC = () => { {error && !apiErrorMessage && !loading && {error}} {apiErrorMessage && {apiErrorMessage}} - {!loading && !error && puedeVer && ( // Asegurar que puedeVer sea true también aquí + {!loading && !error && puedeVer && ( // Asegurar que puedeVer sea true también acá @@ -231,6 +236,12 @@ const GestionarCanillitasPage: React.FC = () => { Novedades )} + {puedeGestionarParadas && selectedCanillitaRow && ( + handleOpenParadas(selectedCanillitaRow.idCanilla)}> + {/* Cambiar ícono si es necesario */} + Gestionar Paradas + + )} {puedeModificar && selectedCanillitaRow && ( // Asegurar que selectedCanillitaRow existe { handleOpenModal(selectedCanillitaRow); handleMenuClose(); }}> @@ -243,7 +254,7 @@ const GestionarCanillitasPage: React.FC = () => { {selectedCanillitaRow.baja ? 'Reactivar' : 'Dar de Baja'} )} - + {/* Mostrar "Sin acciones" si no hay ninguna acción permitida para la fila seleccionada */} {selectedCanillitaRow && !puedeModificar && !puedeDarBaja && !puedeVerNovedadesCanilla && ( Sin acciones diff --git a/Frontend/src/pages/Distribucion/GestionarParadasCanillaPage.tsx b/Frontend/src/pages/Distribucion/GestionarParadasCanillaPage.tsx new file mode 100644 index 0000000..f30160c --- /dev/null +++ b/Frontend/src/pages/Distribucion/GestionarParadasCanillaPage.tsx @@ -0,0 +1,218 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { + Box, Typography, Button, Paper, IconButton, Menu, MenuItem, + Table, TableBody, TableCell, TableContainer, TableHead, TableRow, + CircularProgress, Alert, Chip +} from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import EditIcon from '@mui/icons-material/Edit'; // Para "Cerrar Vigencia" +import DeleteIcon from '@mui/icons-material/Delete'; + +import cambioParadaService from '../../services/Distribucion/cambioParadaService'; +import canillaService from '../../services/Distribucion/canillaService'; +import type { CambioParadaDto } from '../../models/dtos/Distribucion/CambioParadaDto'; +import type { CreateCambioParadaDto } from '../../models/dtos/Distribucion/CreateCambioParadaDto'; +import type { UpdateCambioParadaDto } from '../../models/dtos/Distribucion/UpdateCambioParadaDto'; +import type { CanillaDto } from '../../models/dtos/Distribucion/CanillaDto'; +import CambioParadaFormModal from '../../components/Modals/Distribucion/CambioParadaFormModal'; +import { usePermissions } from '../../hooks/usePermissions'; +import axios from 'axios'; + +const GestionarParadasCanillaPage: React.FC = () => { + const { idCanilla: idCanillaStr } = useParams<{ idCanilla: string }>(); + const navigate = useNavigate(); + const idCanilla = Number(idCanillaStr); + + const [canillita, setCanillita] = useState(null); + const [paradas, setParadas] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [modalOpen, setModalOpen] = useState(false); + const [paradaParaCerrar, setParadaParaCerrar] = useState(null); // Para el modo "Cerrar" del modal + const [apiErrorMessage, setApiErrorMessage] = useState(null); + + const [anchorEl, setAnchorEl] = useState(null); + const [selectedParadaRow, setSelectedParadaRow] = useState(null); + + const { tienePermiso, isSuperAdmin } = usePermissions(); + const puedeGestionarParadas = isSuperAdmin || tienePermiso("CG007"); + const puedeVerCanillitas = isSuperAdmin || tienePermiso("CG001"); + + + const cargarDatos = useCallback(async () => { + if (isNaN(idCanilla)) { + setError("ID de Canillita inválido."); setLoading(false); return; + } + if (!puedeGestionarParadas && !puedeVerCanillitas) { + setError("No tiene permiso para acceder a esta sección."); setLoading(false); return; + } + setLoading(true); setError(null); setApiErrorMessage(null); + try { + const [canData, paradasData] = await Promise.all([ + puedeVerCanillitas ? canillaService.getCanillaById(idCanilla) : Promise.resolve(null), + (puedeGestionarParadas || puedeVerCanillitas) ? cambioParadaService.getParadasPorCanilla(idCanilla) : Promise.resolve([]) + ]); + + if (canData) setCanillita(canData); + else if (puedeGestionarParadas || puedeVerCanillitas) setCanillita({ idCanilla, nomApe: `ID ${idCanilla}` } as CanillaDto); + + setParadas(paradasData.sort((a,b) => new Date(b.vigenciaD).getTime() - new Date(a.vigenciaD).getTime())); + + } catch (err: any) { + console.error(err); + setError(axios.isAxiosError(err) && err.response?.status === 404 ? `Canillita ID ${idCanilla} no encontrado.` : 'Error al cargar datos.'); + } finally { setLoading(false); } + }, [idCanilla, puedeGestionarParadas, puedeVerCanillitas]); + + useEffect(() => { cargarDatos(); }, [cargarDatos]); + + const handleOpenModalParaCrear = () => { + if (!puedeGestionarParadas) { + setApiErrorMessage("No tiene permiso para agregar paradas."); return; + } + setParadaParaCerrar(null); // Asegurar que es modo creación + setApiErrorMessage(null); + setModalOpen(true); + }; + + const handleOpenModalParaCerrar = (parada: CambioParadaDto) => { + if (!puedeGestionarParadas) { + setApiErrorMessage("No tiene permiso para modificar paradas."); return; + } + if (parada.vigenciaH) { // Ya está cerrada + setApiErrorMessage("Esta parada ya tiene una fecha de Vigencia Hasta."); return; + } + setParadaParaCerrar(parada); + setApiErrorMessage(null); + setModalOpen(true); + }; + + + const handleCloseModal = () => { + setModalOpen(false); setParadaParaCerrar(null); + }; + + const handleSubmitModal = async (data: CreateCambioParadaDto | UpdateCambioParadaDto, idRegistroParada?: number) => { + if (!puedeGestionarParadas || !idCanilla) return; // idCanilla es necesario para crear + setApiErrorMessage(null); + try { + if (isModoCerrar && idRegistroParada) { // Es UpdateCambioParadaDto (para cerrar) + await cambioParadaService.cerrarParada(idRegistroParada, data as UpdateCambioParadaDto); + } else { // Es CreateCambioParadaDto + await cambioParadaService.createParada(idCanilla, data as CreateCambioParadaDto); + } + cargarDatos(); + } catch (err: any) { + const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar la parada.'; + setApiErrorMessage(message); throw err; + } + }; + + const handleDelete = async (idRegistro: number) => { + if (!puedeGestionarParadas) return; + if (window.confirm(`¿Seguro de eliminar este registro de parada (ID: ${idRegistro})? Esta acción no se puede deshacer.`)) { + setApiErrorMessage(null); + try { + await cambioParadaService.deleteParada(idRegistro); + cargarDatos(); + } catch (err: any) { + const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar el registro de parada.'; + setApiErrorMessage(message); + } + } + handleMenuClose(); + }; + + const handleMenuOpen = (event: React.MouseEvent, item: CambioParadaDto) => { + setAnchorEl(event.currentTarget); setSelectedParadaRow(item); + }; + const handleMenuClose = () => { + setAnchorEl(null); setSelectedParadaRow(null); + }; + + const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR', {timeZone:'UTC'}) : '-'; + const isModoCerrar = Boolean(paradaParaCerrar && paradaParaCerrar.idRegistro); + + + if (loading) return ; + if (error) return {error}; + if (!puedeGestionarParadas && !puedeVerCanillitas) return Acceso denegado.; + + return ( + + + + Historial de Paradas de: {canillita?.nomApe || `Canillita ID ${idCanilla}`} + + + + {puedeGestionarParadas && ( + + )} + + + {apiErrorMessage && {apiErrorMessage}} + + +
+ + Dirección de Parada + Vigencia Desde + Vigencia Hasta + Estado + {puedeGestionarParadas && Acciones} + + + {paradas.length === 0 ? ( + No hay historial de paradas para este canillita. + ) : ( + paradas.map((p) => ( + + {p.parada} + {formatDate(p.vigenciaD)} + {formatDate(p.vigenciaH)} + {p.esActual ? : } + {puedeGestionarParadas && ( + + handleMenuOpen(e, p)} disabled={!puedeGestionarParadas}> + + )} + + )))} + +
+
+ + + {puedeGestionarParadas && selectedParadaRow && selectedParadaRow.esActual && ( + { handleOpenModalParaCerrar(selectedParadaRow); handleMenuClose(); }}> Cerrar Vigencia)} + {/* La eliminación de paradas históricas puede ser delicada, considerar si es necesaria */} + {puedeGestionarParadas && selectedParadaRow && selectedParadaRow.vigenciaH && ( /* Solo eliminar si está cerrada */ + handleDelete(selectedParadaRow.idRegistro)}> Eliminar Registro)} + + + {idCanilla && + setApiErrorMessage(null)} + /> + } +
+ ); +}; + +export default GestionarParadasCanillaPage; \ No newline at end of file diff --git a/Frontend/src/pages/Radios/RadiosIndexPage.tsx b/Frontend/src/pages/Radios/RadiosIndexPage.tsx index 668c71f..c35e344 100644 --- a/Frontend/src/pages/Radios/RadiosIndexPage.tsx +++ b/Frontend/src/pages/Radios/RadiosIndexPage.tsx @@ -3,32 +3,30 @@ import { Box, Tabs, Tab, Paper, Typography } from '@mui/material'; import { Outlet, useNavigate, useLocation } from 'react-router-dom'; const radiosSubModules = [ - { label: 'Ritmos', path: 'ritmos' }, - { label: 'Canciones', path: 'canciones' }, { label: 'Generar Listas', path: 'generar-listas' }, + { label: 'Ritmos', path: 'ritmos' }, + { label: 'Canciones', path: 'canciones' }, ]; const RadiosIndexPage: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); - const [selectedSubTab, setSelectedSubTab] = useState(false); + const [selectedSubTab, setSelectedSubTab] = useState(0); // Inicializa en 0 useEffect(() => { const currentBasePath = '/radios'; const subPath = location.pathname.startsWith(currentBasePath + '/') - ? location.pathname.substring(currentBasePath.length + 1).split('/')[0] - : (location.pathname === currentBasePath ? radiosSubModules[0]?.path : undefined); + ? location.pathname.substring(currentBasePath.length + 1).split('/')[0] + : undefined; + const activeTabIndex = radiosSubModules.findIndex(sm => sm.path === subPath); - if (activeTabIndex !== -1) { + if (location.pathname === currentBasePath) { + // Si está en /radios, redirige a la primera subruta + navigate(radiosSubModules[0].path, { replace: true }); + setSelectedSubTab(0); + } else if (activeTabIndex !== -1) { setSelectedSubTab(activeTabIndex); - } else { - if (location.pathname === currentBasePath && radiosSubModules.length > 0) { - navigate(radiosSubModules[0].path, { replace: true }); - setSelectedSubTab(0); - } else { - setSelectedSubTab(false); - } } }, [location.pathname, navigate]); diff --git a/Frontend/src/routes/AppRoutes.tsx b/Frontend/src/routes/AppRoutes.tsx index da44445..095966e 100644 --- a/Frontend/src/routes/AppRoutes.tsx +++ b/Frontend/src/routes/AppRoutes.tsx @@ -25,6 +25,7 @@ import GestionarSalidasOtrosDestinosPage from '../pages/Distribucion/GestionarSa import GestionarEntradasSalidasDistPage from '../pages/Distribucion/GestionarEntradasSalidasDistPage'; import GestionarEntradasSalidasCanillaPage from '../pages/Distribucion/GestionarEntradasSalidasCanillaPage'; import GestionarControlDevolucionesPage from '../pages/Distribucion/GestionarControlDevolucionesPage'; +import GestionarParadasCanillaPage from '../pages/Distribucion/GestionarParadasCanillaPage'; // Impresión import ImpresionIndexPage from '../pages/Impresion/ImpresionIndexPage'; @@ -77,6 +78,7 @@ import ReporteListadoDistMensualPage from '../pages/Reportes/ReporteListadoDistM // Auditorias import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage'; +import AuditoriaGeneralPage from '../pages/Auditoria/AuditoriaGeneralPage'; // --- ProtectedRoute y PublicRoute SIN CAMBIOS --- @@ -144,6 +146,7 @@ const AppRoutes = () => { } /> } /> } /> + } /> } /> } /> } /> @@ -245,17 +248,21 @@ const AppRoutes = () => { } /> + {/* Módulo de Auditoías (anidado) */} + + + + } + > + } /> + } /> + {/* Ruta catch-all DENTRO del layout protegido */} } /> {/* Cierre de la ruta padre "/" */} - {/* Podrías tener un catch-all global aquí si una ruta no coincide EN ABSOLUTO, - pero el path="*" dentro de la ruta "/" ya debería manejar la mayoría de los casos - después de un login exitoso. - Si un usuario no autenticado intenta una ruta inválida, ProtectedRoute lo manda a /login. - */} - {/* } /> */} - ); diff --git a/Frontend/src/routes/SectionProtectedRoute.tsx b/Frontend/src/routes/SectionProtectedRoute.tsx index fc2b3ce..ef7c264 100644 --- a/Frontend/src/routes/SectionProtectedRoute.tsx +++ b/Frontend/src/routes/SectionProtectedRoute.tsx @@ -1,21 +1,26 @@ -// src/routes/SectionProtectedRoute.tsx import React from 'react'; import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { usePermissions } from '../hooks/usePermissions'; -import { Box, CircularProgress } from '@mui/material'; +import { Alert, Box, CircularProgress } from '@mui/material'; interface SectionProtectedRouteProps { - requiredPermission: string; + requiredPermission?: string | null; // Hacerlo opcional + onlySuperAdmin?: boolean; // Nueva prop sectionName: string; children?: React.ReactNode; } -const SectionProtectedRoute: React.FC = ({ requiredPermission, sectionName, children }) => { - const { isAuthenticated, isLoading: authIsLoading } = useAuth(); // isLoading de AuthContext +const SectionProtectedRoute: React.FC = ({ + requiredPermission, + onlySuperAdmin = false, // Default a false + sectionName, + children +}) => { + const { isAuthenticated, isLoading: authIsLoading } = useAuth(); const { tienePermiso, isSuperAdmin, currentUser } = usePermissions(); - if (authIsLoading) { // Esperar a que el AuthContext termine su carga inicial + if (authIsLoading) { return ( @@ -26,26 +31,37 @@ const SectionProtectedRoute: React.FC = ({ requiredP if (!isAuthenticated) { return ; } - - // En este punto, si está autenticado, currentUser debería estar disponible. - // Si currentUser pudiera ser null aun estando autenticado (poco probable con tu AuthContext), - // se necesitaría un manejo adicional o un spinner aquí. if (!currentUser) { - // Esto sería un estado inesperado si isAuthenticated es true. - // Podrías redirigir a login o mostrar un error genérico. console.error("SectionProtectedRoute: Usuario autenticado pero currentUser es null."); - return ; // O un error más específico + return ; + } + + let canAccessSection = false; + if (onlySuperAdmin) { + canAccessSection = isSuperAdmin; + } else if (requiredPermission) { + canAccessSection = isSuperAdmin || tienePermiso(requiredPermission); + } else { + // Si no es onlySuperAdmin y no hay requiredPermission, por defecto se permite si está autenticado + // Esto podría ser para secciones públicas post-login pero sin permiso específico. + // O podrías querer que siempre haya un requiredPermission o onlySuperAdmin. + // Por ahora, lo dejaremos pasar si no se especifica ninguno y no es onlySuperAdmin. + // Sin embargo, para los SSxxx, siempre habrá un requiredPermission. + // Este else es más un fallback teórico. + canAccessSection = true; } - const canAccessSection = isSuperAdmin || tienePermiso(requiredPermission); if (!canAccessSection) { - console.error('SectionProtectedRoute: Usuario autenticado pero sin acceso a sección ', sectionName); - return ; + return ( + + + No tiene permiso para acceder a la sección de {sectionName}. + + + ); } - // Si children se proporciona (como ), renderiza children. - // Si no (como } > ), renderiza Outlet. return children ? <>{children} : ; }; diff --git a/Frontend/src/services/Auditoria/auditoriaService.ts b/Frontend/src/services/Auditoria/auditoriaService.ts new file mode 100644 index 0000000..b50d688 --- /dev/null +++ b/Frontend/src/services/Auditoria/auditoriaService.ts @@ -0,0 +1,190 @@ +import apiClient from '../apiClient'; +import type { UsuarioHistorialDto } from '../../models/dtos/Usuarios/Auditoria/UsuarioHistorialDto'; +import type { PagoDistribuidorHistorialDto } from '../../models/dtos/Auditoria/PagoDistribuidorHistorialDto'; +import type { NotaCreditoDebitoHistorialDto } from '../../models/dtos/Auditoria/NotaCreditoDebitoHistorialDto'; +import type { EntradaSalidaDistHistorialDto } from '../../models/dtos/Auditoria/EntradaSalidaDistHistorialDto'; +import type { EntradaSalidaCanillaHistorialDto } from '../../models/dtos/Auditoria/EntradaSalidaCanillaHistorialDto'; +import type { NovedadCanillaHistorialDto } from '../../models/dtos/Auditoria/NovedadCanillaHistorialDto'; +import type { SaldoAjusteHistorialDto } from '../../models/dtos/Auditoria/SaldoAjusteHistorialDto'; +import type { TipoPagoHistorialDto } from '../../models/dtos/Auditoria/TipoPagoHistorialDto'; +import type { CanillaHistorialDto } from '../../models/dtos/Auditoria/CanillaHistorialDto'; +import type { DistribuidorHistorialDto } from '../../models/dtos/Auditoria/DistribuidorHistorialDto'; +import type { EmpresaHistorialDto } from '../../models/dtos/Auditoria/EmpresaHistorialDto'; + +interface HistorialParamsComunes { + fechaDesde?: string; // "yyyy-MM-dd" + fechaHasta?: string; // "yyyy-MM-dd" + idUsuarioModificador?: number; // Cambiado de idUsuarioModifico + tipoModificacion?: string; // Cambiado de tipoMod +} + +interface HistorialEmpresasMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ + idEmpresaAfectada?: number; +} + +interface HistorialDistribuidoresMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ + idDistribuidorAfectado?: number; +} + +interface HistorialCanillitasMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ + idCanillaAfectado?: number; +} + +interface HistorialTiposPagoParams extends HistorialParamsComunes { // << NUEVA INTERFAZ + idTipoPagoAfectado?: number; +} + +interface HistorialAjustesSaldoParams extends HistorialParamsComunes { // << NUEVA INTERFAZ + destino?: string; + idDestino?: number; + idEmpresa?: number; +} + +interface HistorialNovedadesCanillaParams extends HistorialParamsComunes { // << NUEVA INTERFAZ + idNovedadAfectada?: number; + // idCanilla?: number; // Si quieres filtrar por canillita directamente aquí +} + +interface HistorialEntradaSalidaDistParams extends HistorialParamsComunes { // << NUEVA INTERFAZ + idParteAfectada?: number; // ID del movimiento original + // Filtros adicionales si el backend los soporta para este historial específico + // idPublicacion?: number; + // idDistribuidor?: number; +} + +interface HistorialNotasCDParams extends HistorialParamsComunes { + idNotaAfectada?: number; // ID de la nota original + // Podrías añadir más filtros específicos si fueran necesarios +} + +interface HistorialUsuariosParams extends HistorialParamsComunes { + idUsuarioAfectado?: number; +} + +interface HistorialPagosDistribuidorParams extends HistorialParamsComunes { + idPagoAfectado?: number; // ID del pago original + // Podría añadir filtros específicos para pagos si es necesario, como idDistribuidor, idEmpresa +} + +interface HistorialEntradaSalidaCanillaParams extends HistorialParamsComunes { // << NUEVA INTERFAZ + idParteAfectada?: number; + // idPublicacion?: number; // Si quieres añadir más filtros específicos + // idCanilla?: number; +} + +const getHistorialUsuarios = async (params: HistorialUsuariosParams): Promise => { + const queryParams: any = { ...params }; + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; // Renombrar para el backend si es diferente + + const response = await apiClient.get('/auditoria/usuarios', { params: queryParams }); + return response.data; +}; + +const getHistorialPagosDistribuidor = async (params: HistorialPagosDistribuidorParams): Promise => { + const queryParams: any = { ...params }; + // Asegurar que los nombres de parámetros coincidan con el backend + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; + + const response = await apiClient.get('/auditoria/pagos-distribuidores', { params: queryParams }); + return response.data; +}; + +const getHistorialNotasCD = async (params: HistorialNotasCDParams): Promise => { + const queryParams: any = { ...params }; + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; + + const response = await apiClient.get('/auditoria/notas-credito-debito', { params: queryParams }); + return response.data; +}; + +const getHistorialEntradasSalidasDist = async (params: HistorialEntradaSalidaDistParams): Promise => { + const queryParams: any = { ...params }; + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; + + const response = await apiClient.get('/auditoria/entradas-salidas-dist', { params: queryParams }); + return response.data; +}; + +const getHistorialEntradasSalidasCanilla = async (params: HistorialEntradaSalidaCanillaParams): Promise => { + const queryParams: any = { ...params }; + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; + + const response = await apiClient.get('/auditoria/entradas-salidas-canilla', { params: queryParams }); + return response.data; +}; + +const getHistorialNovedadesCanilla = async (params: HistorialNovedadesCanillaParams): Promise => { + const queryParams: any = { ...params }; + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; + + const response = await apiClient.get('/auditoria/novedades-canilla', { params: queryParams }); + return response.data; +}; + +const getHistorialAjustesSaldo = async (params: HistorialAjustesSaldoParams): Promise => { + const queryParams: any = { ...params }; + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; + // Los otros filtros (destino, idDestino, idEmpresa) ya tienen los nombres correctos + + const response = await apiClient.get('/auditoria/ajustes-saldo', { params: queryParams }); + return response.data; +}; + +const getHistorialTiposPago = async (params: HistorialTiposPagoParams): Promise => { + const queryParams: any = { ...params }; + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; + + const response = await apiClient.get('/auditoria/tipos-pago', { params: queryParams }); + return response.data; +}; + +const getHistorialCanillitasMaestro = async (params: HistorialCanillitasMaestroParams): Promise => { + const queryParams: any = { ...params }; + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; + + const response = await apiClient.get('/auditoria/canillitas-maestro', { params: queryParams }); + return response.data; +}; + +const getHistorialDistribuidoresMaestro = async (params: HistorialDistribuidoresMaestroParams): Promise => { + const queryParams: any = { ...params }; + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; + + const response = await apiClient.get('/auditoria/distribuidores-maestro', { params: queryParams }); + return response.data; +}; + +const getHistorialEmpresasMaestro = async (params: HistorialEmpresasMaestroParams): Promise => { + const queryParams: any = { ...params }; + if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador; + delete queryParams.idUsuarioModificador; + + const response = await apiClient.get('/auditoria/empresas-maestro', { params: queryParams }); + return response.data; +}; + +const auditoriaService = { + getHistorialUsuarios, + getHistorialPagosDistribuidor, + getHistorialNotasCD, + getHistorialEntradasSalidasDist, + getHistorialEntradasSalidasCanilla, + getHistorialNovedadesCanilla, + getHistorialAjustesSaldo, + getHistorialTiposPago, + getHistorialCanillitasMaestro, + getHistorialDistribuidoresMaestro, + getHistorialEmpresasMaestro, +}; + +export default auditoriaService; \ No newline at end of file diff --git a/Frontend/src/services/Distribucion/cambioParadaService.ts b/Frontend/src/services/Distribucion/cambioParadaService.ts new file mode 100644 index 0000000..50b1833 --- /dev/null +++ b/Frontend/src/services/Distribucion/cambioParadaService.ts @@ -0,0 +1,39 @@ +import apiClient from '../apiClient'; +import type { CambioParadaDto } from '../../models/dtos/Distribucion/CambioParadaDto'; +import type { CreateCambioParadaDto } from '../../models/dtos/Distribucion/CreateCambioParadaDto'; +import type { UpdateCambioParadaDto } from '../../models/dtos/Distribucion/UpdateCambioParadaDto'; + +// Obtiene todos los registros de cambio de parada para un canillita. +const getParadasPorCanilla = async (idCanilla: number): Promise => { + const response = await apiClient.get(`/canillas/${idCanilla}/paradas`); + return response.data; +}; + +// Crea un nuevo registro de cambio de parada (y cierra el anterior si aplica en backend). +const createParada = async (idCanilla: number, data: CreateCambioParadaDto): Promise => { + const response = await apiClient.post(`/canillas/${idCanilla}/paradas`, data); + return response.data; +}; + +// Cierra la vigencia de una parada específica (actualiza VigenciaH). +// Nota: La ruta podría ser /paradas/{idRegistroParada}/cerrar si el idRegistroParada es globalmente único. +// O /canillas/{idCanilla}/paradas/{idRegistroParada}/cerrar si necesitas el contexto del canillita. +// Asumiré la ruta que usaste en el controlador: /api/paradas/{idRegistroParada}/cerrar +const cerrarParada = async (idRegistroParada: number, data: UpdateCambioParadaDto): Promise => { + await apiClient.put(`/paradas/${idRegistroParada}/cerrar`, data); +}; + +// Elimina un registro de cambio de parada (si se permite esta acción). +const deleteParada = async (idRegistroParada: number): Promise => { + await apiClient.delete(`/paradas/${idRegistroParada}`); +}; + + +const cambioParadaService = { + getParadasPorCanilla, + createParada, + cerrarParada, + deleteParada, +}; + +export default cambioParadaService; \ No newline at end of file diff --git a/Frontend/src/services/apiClient.ts b/Frontend/src/services/apiClient.ts index d68fbaa..0db2df9 100644 --- a/Frontend/src/services/apiClient.ts +++ b/Frontend/src/services/apiClient.ts @@ -25,6 +25,34 @@ apiClient.interceptors.request.use( } ); +apiClient.interceptors.response.use( + (response) => { + // Cualquier código de estado que este dentro del rango de 2xx causa la ejecución de esta función + return response; + }, + (error) => { + // Cualquier código de estado que este fuera del rango de 2xx causa la ejecución de esta función + if (axios.isAxiosError(error) && error.response) { + if (error.response.status === 401) { + // Token inválido o expirado + console.warn("Error 401: Token inválido o expirado. Deslogueando..."); + + // Limpiar localStorage y recargar la página. + // AuthContext se encargará de redirigir a /login al recargar porque no encontrará token. + localStorage.removeItem('authToken'); + localStorage.removeItem('authUser'); // Asegurar limpiar también el usuario + // Forzar un hard refresh para que AuthContext se reinicialice y redirija + // Esto también limpiará cualquier estado de React. + // --- Mostrar mensaje antes de redirigir --- + alert("Tu sesión ha expirado o no es válida. Serás redirigido a la página de inicio de sesión."); + window.location.href = '/login'; // Redirección más directa + } + } + // Es importante devolver el error para que el componente que hizo la llamada pueda manejarlo también si es necesario + return Promise.reject(error); + } +); + // Puedes añadir interceptores de respuesta para manejar errores globales (ej: 401 Unauthorized) export default apiClient; \ No newline at end of file