Diseño de un AuditoriaController con un patrón para añadir endpoints de historial para diferentes entidades.
Implementación de la lógica de servicio y repositorio para obtener datos de las tablas _H para:
Usuarios (gral_Usuarios_H)
Pagos de Distribuidores (cue_PagosDistribuidor_H)
Notas de Crédito/Débito (cue_CreditosDebitos_H)
Entradas/Salidas de Distribuidores (dist_EntradasSalidas_H)
Entradas/Salidas de Canillitas (dist_EntradasSalidasCanillas_H)
Novedades de Canillitas (dist_dtNovedadesCanillas_H)
Ajustes Manuales de Saldo (cue_SaldoAjustesHistorial)
Tipos de Pago (cue_dtTipopago_H)
Canillitas (Maestro) (dist_dtCanillas_H)
Distribuidores (Maestro) (dist_dtDistribuidores_H)
Empresas (Maestro) (dist_dtEmpresas_H)
DTOs específicos para cada tipo de historial, incluyendo NombreUsuarioModifico.
Frontend:
Servicio auditoriaService.ts con métodos para llamar a cada endpoint de historial.
Página AuditoriaGeneralPage.tsx con:
Selector de "Tipo de Entidad a Auditar".
Filtros comunes (Fechas, Usuario Modificador, Tipo de Modificación, ID Entidad).
Un DataGrid que muestra las columnas dinámicamente según el tipo de entidad seleccionada.
Lógica para cargar los datos correspondientes.
DTOs de historial en TypeScript.
Actualizaciones en AppRoutes.tsx y MainLayout.tsx para la nueva sección de Auditoría (restringida a SuperAdmin).
This commit is contained in:
2025-06-09 19:37:07 -03:00
parent 35e24ab7d2
commit 437b1e8864
98 changed files with 3683 additions and 325 deletions

View File

@@ -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<AuditoriaController> _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<AuditoriaController> 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<PagoDistribuidorHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<PagoDistribuidorHistorialDto>());
}
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<NotaCreditoDebitoHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<NotaCreditoDebitoHistorialDto>());
}
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<EntradaSalidaDistHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<EntradaSalidaDistHistorialDto>());
}
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<EntradaSalidaCanillaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<EntradaSalidaCanillaHistorialDto>());
}
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<NovedadCanillaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<NovedadCanillaHistorialDto>());
}
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<SaldoAjusteHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<SaldoAjusteHistorialDto>());
}
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<TipoPagoHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<TipoPagoHistorialDto>());
}
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<CanillaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<CanillaHistorialDto>());
}
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<DistribuidorHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<DistribuidorHistorialDto>());
}
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<EmpresaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<EmpresaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Empresas (Maestro).");
return StatusCode(500, "Error interno al obtener historial de Empresas (Maestro).");
}
}
}
}

View File

@@ -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<CambiosParadaController> _logger;
private const string PermisoGestionarParadas = "CG007";
public CambiosParadaController(ICambioParadaService paradaService, ILogger<CambiosParadaController> 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<CambioParadaDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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();
}
}
}

View File

@@ -16,6 +16,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
Task<NotaCreditoDebito?> CreateAsync(NotaCreditoDebito nuevaNota, int idUsuario, IDbTransaction transaction); Task<NotaCreditoDebito?> CreateAsync(NotaCreditoDebito nuevaNota, int idUsuario, IDbTransaction transaction);
Task<bool> UpdateAsync(NotaCreditoDebito notaAActualizar, int idUsuario, IDbTransaction transaction); Task<bool> UpdateAsync(NotaCreditoDebito notaAActualizar, int idUsuario, IDbTransaction transaction);
Task<bool> DeleteAsync(int idNota, int idUsuario, IDbTransaction transaction); Task<bool> DeleteAsync(int idNota, int idUsuario, IDbTransaction transaction);
// No se suele validar unicidad por referencia, ya que podría repetirse. Task<IEnumerable<(NotaCreditoDebitoHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idNotaOriginal); // Para filtrar por una nota específica
} }
} }

View File

@@ -17,5 +17,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction); Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction);
Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction); Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction);
Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null); Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null);
Task<IEnumerable<(PagoDistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idPagoOriginal); // Para filtrar por un pago específico
} }
} }

View File

@@ -24,5 +24,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
Task<Saldo?> GetSaldoAsync(string destino, int idDestino, int idEmpresa, IDbTransaction? transaction = null); Task<Saldo?> GetSaldoAsync(string destino, int idDestino, int idEmpresa, IDbTransaction? transaction = null);
// Para registrar el historial de ajuste // Para registrar el historial de ajuste
Task CreateSaldoAjusteHistorialAsync(SaldoAjusteHistorial historialEntry, IDbTransaction transaction); Task CreateSaldoAjusteHistorialAsync(SaldoAjusteHistorial historialEntry, IDbTransaction transaction);
Task<IEnumerable<(SaldoAjusteHistorial Historial, string NombreUsuarioModifico)>> GetHistorialAjustesAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico,
string? destino, int? idDestino, int? idEmpresa);
} }
} }

View File

@@ -13,5 +13,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
Task<bool> DeleteAsync(int id, int idUsuario); // Devuelve true si fue exitoso Task<bool> DeleteAsync(int id, int idUsuario); // Devuelve true si fue exitoso
Task<bool> ExistsByNameAsync(string nombre, int? excludeId = null); Task<bool> ExistsByNameAsync(string nombre, int? excludeId = null);
Task<bool> IsInUseAsync(int id); Task<bool> IsInUseAsync(int id);
Task<IEnumerable<(TipoPagoHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idTipoPagoOriginal);
} }
} }

View File

@@ -86,11 +86,20 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
var inserted = await transaction.Connection!.QuerySingleAsync<NotaCreditoDebito>(sqlInsert, nuevaNota, transaction); var inserted = await transaction.Connection!.QuerySingleAsync<NotaCreditoDebito>(sqlInsert, nuevaNota, transaction);
if (inserted == null || inserted.IdNota == 0) throw new DataException("Error al crear la nota o ID no generado."); if (inserted == null || inserted.IdNota == 0) throw new DataException("Error al crear la nota o ID no generado.");
await transaction.Connection!.ExecuteAsync(sqlHistorico, new { 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, IdNotaHist = inserted.IdNota,
ObservacionesHist = inserted.Observaciones, IdEmpresaHist = inserted.IdEmpresa, DestinoHist = inserted.Destino,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creada" 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); }, transaction);
return inserted; return inserted;
} }
@@ -112,18 +121,28 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
(Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod) (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);"; VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new { 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 IdNotaHist = actual.IdNota,
ObservacionesHist = actual.Observaciones, IdEmpresaHist = actual.IdEmpresa, // Valor ANTERIOR DestinoHist = actual.Destino,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizada" 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); }, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new { var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new
{
notaAActualizar.Monto, notaAActualizar.Monto,
notaAActualizar.Observaciones, notaAActualizar.Observaciones,
notaAActualizar.IdNota notaAActualizar.IdNota
} , transaction); }, transaction);
return rowsAffected == 1; 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) (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);"; VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new { 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, IdNotaHist = actual.IdNota,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada" 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); }, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdNotaParam = idNota }, transaction); var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdNotaParam = idNota }, transaction);
return rowsAffected == 1; return rowsAffected == 1;
} }
public async Task<IEnumerable<(NotaCreditoDebitoHistorico Historial, string NombreUsuarioModifico)>> 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<NotaCreditoDebitoHistorico, string, (NotaCreditoDebitoHistorico, string)>(
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)>();
}
}
} }
} }

View File

@@ -109,11 +109,20 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
var inserted = await transaction.Connection!.QuerySingleAsync<PagoDistribuidor>(sqlInsert, nuevoPago, transaction); var inserted = await transaction.Connection!.QuerySingleAsync<PagoDistribuidor>(sqlInsert, nuevoPago, transaction);
if (inserted == null || inserted.IdPago == 0) throw new DataException("Error al crear pago o ID no generado."); if (inserted == null || inserted.IdPago == 0) throw new DataException("Error al crear pago o ID no generado.");
await transaction.Connection!.ExecuteAsync(sqlHistorico, new { await transaction.Connection!.ExecuteAsync(sqlHistorico, new
IdPagoHist = inserted.IdPago, IdDistribuidorHist = inserted.IdDistribuidor, FechaHist = inserted.Fecha, {
TipoMovimientoHist = inserted.TipoMovimiento, ReciboHist = inserted.Recibo, MontoHist = inserted.Monto, IdPagoHist = inserted.IdPago,
IdTipoPagoHist = inserted.IdTipoPago, DetalleHist = inserted.Detalle, IdEmpresaHist = inserted.IdEmpresa, IdDistribuidorHist = inserted.IdDistribuidor,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creado" 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); }, transaction);
return inserted; return inserted;
} }
@@ -137,11 +146,20 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
(Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod) (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);"; VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new { 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 IdPagoHist = actual.IdPago,
IdTipoPagoHist = actual.IdTipoPago, DetalleHist = actual.Detalle, IdEmpresaHist = actual.IdEmpresa, // Valores ANTERIORES IdDistribuidorHist = actual.IdDistribuidor,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizado" 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); }, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, pagoAActualizar, 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) (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);"; VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new { await transaction.Connection!.ExecuteAsync(sqlHistorico, new
IdPagoHist = actual.IdPago, IdDistribuidorHist = actual.IdDistribuidor, FechaHist = actual.Fecha, {
TipoMovimientoHist = actual.TipoMovimiento, ReciboHist = actual.Recibo, MontoHist = actual.Monto, IdPagoHist = actual.IdPago,
IdTipoPagoHist = actual.IdTipoPago, DetalleHist = actual.Detalle, IdEmpresaHist = actual.IdEmpresa, IdDistribuidorHist = actual.IdDistribuidor,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminado" 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); }, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPagoParam = idPago }, transaction); var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPagoParam = idPago }, transaction);
return rowsAffected == 1; return rowsAffected == 1;
} }
public async Task<IEnumerable<(PagoDistribuidorHistorico Historial, string NombreUsuarioModifico)>> 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<PagoDistribuidorHistorico, string, (PagoDistribuidorHistorico, string)>(
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)>();
}
}
} }
} }

View File

@@ -188,5 +188,49 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
await transaction.Connection!.ExecuteAsync(sql, historialEntry, transaction); await transaction.Connection!.ExecuteAsync(sql, historialEntry, transaction);
} }
public async Task<IEnumerable<(SaldoAjusteHistorial Historial, string NombreUsuarioModifico)>> 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<SaldoAjusteHistorial, string, (SaldoAjusteHistorial, string)>(
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)>();
}
}
} }
} }

View File

@@ -2,6 +2,7 @@ using Dapper;
using GestionIntegral.Api.Models.Contables; using GestionIntegral.Api.Models.Contables;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Contables 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 return true; // Asumir que está en uso si hay error para prevenir borrado incorrecto
} }
} }
public async Task<IEnumerable<(TipoPagoHistorico Historial, string NombreUsuarioModifico)>> 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<TipoPagoHistorico, string, (TipoPagoHistorico, string)>(
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)>();
}
}
} }
} }

View File

@@ -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<CambioParadaRepository> _logger;
public CambioParadaRepository(DbConnectionFactory cf, ILogger<CambioParadaRepository> 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<IEnumerable<CambioParadaCanilla>> 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<CambioParadaCanilla>(sql, new { IdCanilla = idCanilla });
}
public async Task<CambioParadaCanilla?> 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<CambioParadaCanilla>(sql, new { IdRegistro = idRegistro });
}
public async Task<CambioParadaCanilla?> 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<CambioParadaCanilla>(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<CambioParadaCanilla?> 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<CambioParadaCanilla>(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<bool> 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<bool> 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;
}
}
}

View File

@@ -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); var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new { BajaParam = darDeBaja, FechaBajaParam = (darDeBaja ? fechaBaja : null), IdCanillaParam = id }, transaction);
return rowsAffected == 1; return rowsAffected == 1;
} }
public async Task<IEnumerable<(CanillaHistorico Historial, string NombreUsuarioModifico)>> 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<CanillaHistorico, string, (CanillaHistorico, string)>(
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)>();
}
}
} }
} }

View File

@@ -239,9 +239,21 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
await connection.ExecuteAsync(sqlInsertHistorico, new await connection.ExecuteAsync(sqlInsertHistorico, new
{ {
IdDistribuidorParam = inserted.IdDistribuidor, NombreParam = inserted.Nombre, ContactoParam = inserted.Contacto, NroDocParam = inserted.NroDoc, IdZonaParam = inserted.IdZona, IdDistribuidorParam = inserted.IdDistribuidor,
CalleParam = inserted.Calle, NumeroParam = inserted.Numero, PisoParam = inserted.Piso, DeptoParam = inserted.Depto, TelefonoParam = inserted.Telefono, EmailParam = inserted.Email, LocalidadParam = inserted.Localidad, NombreParam = inserted.Nombre,
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado" 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); }, transaction);
return inserted; return inserted;
} }
@@ -268,9 +280,21 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
await connection.ExecuteAsync(sqlInsertHistorico, new await connection.ExecuteAsync(sqlInsertHistorico, new
{ {
IdDistribuidorParam = actual.IdDistribuidor, NombreParam = actual.Nombre, ContactoParam = actual.Contacto, NroDocParam = actual.NroDoc, IdZonaParam = actual.IdZona, IdDistribuidorParam = actual.IdDistribuidor,
CalleParam = actual.Calle, NumeroParam = actual.Numero, PisoParam = actual.Piso, DeptoParam = actual.Depto, TelefonoParam = actual.Telefono, EmailParam = actual.Email, LocalidadParam = actual.Localidad, NombreParam = actual.Nombre,
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado" 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); }, transaction);
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, distribuidorAActualizar, transaction); var rowsAffected = await connection.ExecuteAsync(sqlUpdate, distribuidorAActualizar, transaction);
@@ -295,13 +319,68 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
await connection.ExecuteAsync(sqlInsertHistorico, new await connection.ExecuteAsync(sqlInsertHistorico, new
{ {
IdDistribuidorParam = actual.IdDistribuidor, NombreParam = actual.Nombre, ContactoParam = actual.Contacto, NroDocParam = actual.NroDoc, IdZonaParam = actual.IdZona, IdDistribuidorParam = actual.IdDistribuidor,
CalleParam = actual.Calle, NumeroParam = actual.Numero, PisoParam = actual.Piso, DeptoParam = actual.Depto, TelefonoParam = actual.Telefono, EmailParam = actual.Email, LocalidadParam = actual.Localidad, NombreParam = actual.Nombre,
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado" 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); }, transaction);
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id }, transaction); var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id }, transaction);
return rowsAffected == 1; return rowsAffected == 1;
} }
public async Task<IEnumerable<(DistribuidorHistorico Historial, string NombreUsuarioModifico)>> 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<DistribuidorHistorico, string, (DistribuidorHistorico, string)>(
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)>();
}
}
} }
} }

View File

@@ -257,5 +257,47 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
return rowsAffected == 1; return rowsAffected == 1;
} }
public async Task<IEnumerable<(EmpresaHistorico Historial, string NombreUsuarioModifico)>> 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<EmpresaHistorico, string, (EmpresaHistorico, string)>(
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)>();
}
}
} }
} }

View File

@@ -292,5 +292,48 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction); var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction);
return rowsAffected == 1; return rowsAffected == 1;
} }
public async Task<IEnumerable<(EntradaSalidaCanillaHistorico Historial, string NombreUsuarioModifico)>> 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<EntradaSalidaCanillaHistorico, string, (EntradaSalidaCanillaHistorico, string)>(
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)>();
}
}
} }
} }

View File

@@ -114,11 +114,22 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
var inserted = await transaction.Connection!.QuerySingleAsync<EntradaSalidaDist>(sqlInsert, nuevaES, transaction); var inserted = await transaction.Connection!.QuerySingleAsync<EntradaSalidaDist>(sqlInsert, nuevaES, transaction);
if (inserted == null || inserted.IdParte == 0) throw new DataException("Error al crear Entrada/Salida o ID no generado."); if (inserted == null || inserted.IdParte == 0) throw new DataException("Error al crear Entrada/Salida o ID no generado.");
await transaction.Connection!.ExecuteAsync(sqlHistorico, new { 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, IdParteHist = inserted.IdParte,
IdPrecioHist = inserted.IdPrecio, IdRecargoHist = inserted.IdRecargo, IdPorcentajeHist = inserted.IdPorcentaje, IdPublicacionHist = inserted.IdPublicacion,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creada" 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); }, transaction);
return inserted; return inserted;
} }
@@ -141,11 +152,22 @@ 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) (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);"; VALUES (@IdParteHist, @IdPublicacionHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @CantidadHist, @RemitoHist, @ObservacionHist, @IdPrecioHist, @IdRecargoHist, @IdPorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new { 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, IdParteHist = actual.IdParte,
IdPrecioHist = actual.IdPrecio, IdRecargoHist = actual.IdRecargo, IdPorcentajeHist = actual.IdPorcentaje, // Valores ANTERIORES IdPublicacionHist = actual.IdPublicacion,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizada" 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); }, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, esAActualizar, 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) (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);"; VALUES (@IdParteHist, @IdPublicacionHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @CantidadHist, @RemitoHist, @ObservacionHist, @IdPrecioHist, @IdRecargoHist, @IdPorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new { 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, IdParteHist = actual.IdParte,
IdPrecioHist = actual.IdPrecio, IdRecargoHist = actual.IdRecargo, IdPorcentajeHist = actual.IdPorcentaje, IdPublicacionHist = actual.IdPublicacion,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada" 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); }, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction); var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction);
return rowsAffected == 1; return rowsAffected == 1;
} }
public async Task<IEnumerable<(EntradaSalidaDistHistorico Historial, string NombreUsuarioModifico)>> 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<EntradaSalidaDistHistorico, string, (EntradaSalidaDistHistorico, string)>(
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)>();
}
}
} }
} }

View File

@@ -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<IEnumerable<CambioParadaCanilla>> GetByCanillaAsync(int idCanilla);
Task<CambioParadaCanilla?> GetByIdAsync(int idRegistro);
Task<CambioParadaCanilla?> GetCurrentParadaAsync(int idCanilla, IDbTransaction? transaction = null);
Task<CambioParadaCanilla?> CreateAsync(CambioParadaCanilla nuevaParada, int idUsuario, IDbTransaction transaction);
Task<bool> UpdateVigenciaHAsync(int idRegistro, DateTime vigenciaH, int idUsuario, IDbTransaction transaction);
Task<bool> DeleteAsync(int idRegistro, int idUsuario, IDbTransaction transaction);
}
}

View File

@@ -14,6 +14,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
Task<bool> UpdateAsync(Canilla canillaAActualizar, int idUsuario, IDbTransaction transaction); Task<bool> UpdateAsync(Canilla canillaAActualizar, int idUsuario, IDbTransaction transaction);
Task<bool> ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario, IDbTransaction transaction); Task<bool> ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario, IDbTransaction transaction);
Task<bool> ExistsByLegajoAsync(int legajo, int? excludeIdCanilla = null); Task<bool> 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<IEnumerable<(CanillaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idCanillaOriginal); // Para filtrar por un canillita específico
} }
} }

View File

@@ -19,5 +19,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
Task<bool> IsInUseAsync(int id); // Verificar en dist_EntradasSalidas, cue_PagosDistribuidor, dist_PorcPago Task<bool> IsInUseAsync(int id); // Verificar en dist_EntradasSalidas, cue_PagosDistribuidor, dist_PorcPago
Task<IEnumerable<DistribuidorDropdownDto?>> GetAllDropdownAsync(); Task<IEnumerable<DistribuidorDropdownDto?>> GetAllDropdownAsync();
Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id); Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id);
Task<IEnumerable<(DistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idDistribuidorOriginal);
} }
} }

View File

@@ -17,5 +17,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
Task<bool> IsInUseAsync(int id); Task<bool> IsInUseAsync(int id);
Task<IEnumerable<EmpresaDropdownDto>> GetAllDropdownAsync(); Task<IEnumerable<EmpresaDropdownDto>> GetAllDropdownAsync();
Task<Empresa?> ObtenerLookupPorIdAsync(int id); Task<Empresa?> ObtenerLookupPorIdAsync(int id);
Task<IEnumerable<(EmpresaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idEmpresaOriginal);
} }
} }

View File

@@ -19,5 +19,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction); Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction);
Task<bool> LiquidarAsync(IEnumerable<int> idsPartes, DateTime fechaLiquidacion, int idUsuarioLiquidador, IDbTransaction transaction); Task<bool> LiquidarAsync(IEnumerable<int> idsPartes, DateTime fechaLiquidacion, int idUsuarioLiquidador, IDbTransaction transaction);
Task<bool> ExistsByPublicacionCanillaFechaAsync(int idPublicacion, int idCanilla, DateTime fecha, IDbTransaction? transaction = null, int? excludeIdParte = null); Task<bool> ExistsByPublicacionCanillaFechaAsync(int idPublicacion, int idCanilla, DateTime fecha, IDbTransaction? transaction = null, int? excludeIdParte = null);
Task<IEnumerable<(EntradaSalidaCanillaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idParteOriginal);
} }
} }

View File

@@ -14,5 +14,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
Task<bool> UpdateAsync(EntradaSalidaDist esAActualizar, int idUsuario, IDbTransaction transaction); Task<bool> UpdateAsync(EntradaSalidaDist esAActualizar, int idUsuario, IDbTransaction transaction);
Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction); Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction);
Task<bool> ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null); Task<bool> ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null);
Task<IEnumerable<(EntradaSalidaDistHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idParteOriginal); // Para filtrar por un movimiento específico
} }
} }

View File

@@ -19,5 +19,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
Task<bool> ExistsByCanillaAndFechaAsync(int idCanilla, DateTime fecha, int? excludeIdNovedad = null); Task<bool> ExistsByCanillaAndFechaAsync(int idCanilla, DateTime fecha, int? excludeIdNovedad = null);
Task<IEnumerable<NovedadesCanillasReporteDto>> GetReporteNovedadesAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); Task<IEnumerable<NovedadesCanillasReporteDto>> GetReporteNovedadesAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta);
Task<IEnumerable<CanillaGananciaReporteDto>> GetReporteGananciasAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); Task<IEnumerable<CanillaGananciaReporteDto>> GetReporteGananciasAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta);
Task<IEnumerable<(NovedadCanillaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idNovedadOriginal);
} }
} }

View File

@@ -9,6 +9,7 @@ using Microsoft.Data.SqlClient; // O el proveedor de tu BD
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using GestionIntegral.Api.Dtos.Reportes; using GestionIntegral.Api.Dtos.Reportes;
using System.Text;
namespace GestionIntegral.Api.Data.Repositories.Distribucion namespace GestionIntegral.Api.Data.Repositories.Distribucion
{ {
@@ -232,5 +233,46 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
commandType: CommandType.StoredProcedure commandType: CommandType.StoredProcedure
); );
} }
public async Task<IEnumerable<(NovedadCanillaHistorico Historial, string NombreUsuarioModifico)>> 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<NovedadCanillaHistorico, string, (NovedadCanillaHistorico, string)>(
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)>();
}
}
} }
} }

View File

@@ -1,12 +1,14 @@
using System;
namespace GestionIntegral.Api.Models.Contables namespace GestionIntegral.Api.Models.Contables
{ {
public class TipoPagoHistorico 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 Nombre { get; set; } = string.Empty;
public string? Detalle { get; set; } public string? Detalle { get; set; }
public int IdUsuario { get; set; } public int Id_Usuario { get; set; }
public DateTime FechaMod { get; set; } public DateTime FechaMod { get; set; }
public string TipoMod { get; set; } = string.Empty; // "Insertada", "Modificada", "Eliminada" public string TipoMod { get; set; } = string.Empty;
} }
} }

View File

@@ -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)
}
}

View File

@@ -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;
}
}

View File

@@ -1,19 +1,19 @@
using System;
namespace GestionIntegral.Api.Models.Distribucion 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 int? Legajo { get; set; }
public string NomApe { get; set; } = string.Empty; public string NomApe { get; set; } = string.Empty;
public string? Parada { get; set; } public string? Parada { get; set; }
public int IdZona { get; set; } public int Id_Zona { get; set; }
public bool Accionista { get; set; } public bool Accionista { get; set; }
public string? Obs { get; set; } public string? Obs { get; set; }
public int Empresa { get; set; } public int Empresa { get; set; }
public bool Baja { get; set; } public bool Baja { get; set; }
public DateTime? FechaBaja { get; set; } public DateTime? FechaBaja { get; set; }
// Campos de Auditoría
public int Id_Usuario { get; set; } public int Id_Usuario { get; set; }
public DateTime FechaMod { get; set; } public DateTime FechaMod { get; set; }
public string TipoMod { get; set; } = string.Empty; public string TipoMod { get; set; } = string.Empty;

View File

@@ -1,12 +1,14 @@
using System;
namespace GestionIntegral.Api.Models.Distribucion 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 Nombre { get; set; } = string.Empty;
public string? Contacto { get; set; } public string? Contacto { get; set; }
public string NroDoc { get; set; } = string.Empty; 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? Calle { get; set; }
public string? Numero { get; set; } public string? Numero { get; set; }
public string? Piso { get; set; } public string? Piso { get; set; }
@@ -14,8 +16,6 @@ namespace GestionIntegral.Api.Models.Distribucion
public string? Telefono { get; set; } public string? Telefono { get; set; }
public string? Email { get; set; } public string? Email { get; set; }
public string? Localidad { get; set; } public string? Localidad { get; set; }
// Campos de Auditoría
public int Id_Usuario { get; set; } public int Id_Usuario { get; set; }
public DateTime FechaMod { get; set; } public DateTime FechaMod { get; set; }
public string TipoMod { get; set; } = string.Empty; public string TipoMod { get; set; } = string.Empty;

View File

@@ -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 Nombre { get; set; } = string.Empty;
public string? Detalle { get; set; } public string? Detalle { get; set; }
public int IdUsuario { get; set; } public int Id_Usuario { get; set; }
public DateTime FechaMod { get; set; } public DateTime FechaMod { get; set; }
public string TipoMod { get; set; } = string.Empty; public string TipoMod { get; set; } = string.Empty;
} }

View File

@@ -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.
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -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; }
}
}

View File

@@ -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.
}
}

View File

@@ -84,6 +84,8 @@ builder.Services.AddScoped<ICancionService, CancionService>();
builder.Services.AddScoped<IRadioListaService, RadioListaService>(); builder.Services.AddScoped<IRadioListaService, RadioListaService>();
builder.Services.AddScoped<INovedadCanillaRepository, NovedadCanillaRepository>(); builder.Services.AddScoped<INovedadCanillaRepository, NovedadCanillaRepository>();
builder.Services.AddScoped<INovedadCanillaService, NovedadCanillaService>(); builder.Services.AddScoped<INovedadCanillaService, NovedadCanillaService>();
builder.Services.AddScoped<ICambioParadaRepository, CambioParadaRepository>();
builder.Services.AddScoped<ICambioParadaService, CambioParadaService>();
// Servicio de Saldos // Servicio de Saldos
builder.Services.AddScoped<ISaldoService, SaldoService>(); builder.Services.AddScoped<ISaldoService, SaldoService>();
// Repositorios de Reportes // Repositorios de Reportes

View File

@@ -1,3 +1,4 @@
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Dtos.Contables;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -10,10 +11,13 @@ namespace GestionIntegral.Api.Services.Contables
Task<IEnumerable<NotaCreditoDebitoDto>> ObtenerTodosAsync( Task<IEnumerable<NotaCreditoDebitoDto>> ObtenerTodosAsync(
DateTime? fechaDesde, DateTime? fechaHasta, DateTime? fechaDesde, DateTime? fechaHasta,
string? destino, int? idDestino, int? idEmpresa, string? tipoNota); string? destino, int? idDestino, int? idEmpresa, string? tipoNota);
Task<NotaCreditoDebitoDto?> ObtenerPorIdAsync(int idNota); Task<NotaCreditoDebitoDto?> ObtenerPorIdAsync(int idNota);
Task<(NotaCreditoDebitoDto? Nota, string? Error)> CrearAsync(CreateNotaDto createDto, int idUsuario); 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)> ActualizarAsync(int idNota, UpdateNotaDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarAsync(int idNota, int idUsuario); Task<(bool Exito, string? Error)> EliminarAsync(int idNota, int idUsuario);
Task<IEnumerable<NotaCreditoDebitoHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idNotaAfectada);
} }
} }

View File

@@ -1,3 +1,4 @@
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Dtos.Contables;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -15,5 +16,9 @@ namespace GestionIntegral.Api.Services.Contables
Task<(PagoDistribuidorDto? Pago, string? Error)> CrearAsync(CreatePagoDistribuidorDto createDto, int idUsuario); 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)> ActualizarAsync(int idPago, UpdatePagoDistribuidorDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarAsync(int idPago, int idUsuario); Task<(bool Exito, string? Error)> EliminarAsync(int idPago, int idUsuario);
Task<IEnumerable<PagoDistribuidorHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idPagoAfectado);
} }
} }

View File

@@ -1,3 +1,4 @@
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Dtos.Contables;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -8,5 +9,9 @@ namespace GestionIntegral.Api.Services.Contables
{ {
Task<IEnumerable<SaldoGestionDto>> ObtenerSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter); Task<IEnumerable<SaldoGestionDto>> ObtenerSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter);
Task<(bool Exito, string? Error, SaldoGestionDto? SaldoActualizado)> RealizarAjusteManualSaldoAsync(AjusteSaldoRequestDto ajusteDto, int idUsuarioAjuste); Task<(bool Exito, string? Error, SaldoGestionDto? SaldoActualizado)> RealizarAjusteManualSaldoAsync(AjusteSaldoRequestDto ajusteDto, int idUsuarioAjuste);
Task<IEnumerable<SaldoAjusteHistorialDto>> ObtenerHistorialAjustesAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico,
string? destino, int? idDestino, int? idEmpresa);
} }
} }

View File

@@ -1,3 +1,4 @@
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Dtos.Contables;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -11,5 +12,9 @@ namespace GestionIntegral.Api.Services.Contables
Task<(TipoPagoDto? TipoPago, string? Error)> CrearAsync(CreateTipoPagoDto createDto, int idUsuario); 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)> ActualizarAsync(int id, UpdateTipoPagoDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario); Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
Task<IEnumerable<TipoPagoHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idTipoPagoAfectado);
} }
} }

View File

@@ -1,6 +1,7 @@
using GestionIntegral.Api.Data; using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Dtos.Contables;
using GestionIntegral.Api.Models.Contables; using GestionIntegral.Api.Models.Contables;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -278,5 +279,30 @@ namespace GestionIntegral.Api.Services.Contables
} }
} }
} }
public async Task<IEnumerable<NotaCreditoDebitoHistorialDto>> 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();
}
} }
} }

View File

@@ -1,6 +1,7 @@
using GestionIntegral.Api.Data; using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Dtos.Contables;
using GestionIntegral.Api.Models.Contables; using GestionIntegral.Api.Models.Contables;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -269,5 +270,30 @@ namespace GestionIntegral.Api.Services.Contables
} }
} }
} }
public async Task<IEnumerable<PagoDistribuidorHistorialDto>> 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();
}
} }
} }

View File

@@ -1,6 +1,7 @@
using GestionIntegral.Api.Data; using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion; // Para IDistribuidorRepository, ICanillaRepository using GestionIntegral.Api.Data.Repositories.Distribucion; // Para IDistribuidorRepository, ICanillaRepository
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Dtos.Contables;
using GestionIntegral.Api.Models.Contables; using GestionIntegral.Api.Models.Contables;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -94,7 +95,9 @@ namespace GestionIntegral.Api.Services.Contables
{ {
if (await _canillaRepo.GetByIdSimpleAsync(ajusteDto.IdDestino) == null) if (await _canillaRepo.GetByIdSimpleAsync(ajusteDto.IdDestino) == null)
return (false, "El canillita especificado no existe.", null); return (false, "El canillita especificado no existe.", null);
} else { }
else
{
return (false, "Tipo de destino inválido.", null); return (false, "Tipo de destino inválido.", null);
} }
if (await _empresaRepo.GetByIdAsync(ajusteDto.IdEmpresa) == null) if (await _empresaRepo.GetByIdAsync(ajusteDto.IdEmpresa) == null)
@@ -125,7 +128,7 @@ namespace GestionIntegral.Api.Services.Contables
// Obtener el saldo después de la modificación para el historial // Obtener el saldo después de la modificación para el historial
var saldoDespuesDeModificacion = await _saldoRepo.GetSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, transaction); 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 var historial = new SaldoAjusteHistorial
@@ -151,7 +154,7 @@ namespace GestionIntegral.Api.Services.Contables
} }
catch (Exception ex) 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."); _logger.LogError(ex, "Error en RealizarAjusteManualSaldoAsync.");
return (false, $"Error interno al realizar el ajuste: {ex.Message}", null); return (false, $"Error interno al realizar el ajuste: {ex.Message}", null);
} }
@@ -160,5 +163,29 @@ 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.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); }
} }
} }
public async Task<IEnumerable<SaldoAjusteHistorialDto>> 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();
}
} }
} }

View File

@@ -1,4 +1,5 @@
using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Dtos.Contables;
using GestionIntegral.Api.Models.Contables; // Para TipoPago using GestionIntegral.Api.Models.Contables; // Para TipoPago
using GestionIntegral.Api.Services.Contables; using GestionIntegral.Api.Services.Contables;
@@ -130,5 +131,24 @@ namespace GestionIntegral.Api.Services.Contables
} }
return (true, null); // Éxito return (true, null); // Éxito
} }
public async Task<IEnumerable<TipoPagoHistorialDto>> 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();
}
} }
} }

View File

@@ -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<CambioParadaService> _logger;
public CambioParadaService(
ICambioParadaRepository paradaRepo,
ICanillaRepository canillaRepo,
DbConnectionFactory connectionFactory,
ILogger<CambioParadaService> logger)
{
_paradaRepo = paradaRepo;
_canillaRepo = canillaRepo;
_connectionFactory = connectionFactory;
_logger = logger;
}
private async Task<CambioParadaDto> 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<IEnumerable<CambioParadaDto>> ObtenerPorCanillaAsync(int idCanilla)
{
var paradas = await _paradaRepo.GetByCanillaAsync(idCanilla);
var dtos = new List<CambioParadaDto>();
foreach (var p in paradas) { dtos.Add(await MapToDto(p)); }
return dtos;
}
public async Task<CambioParadaDto?> 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(); } }
}
}
}

View File

@@ -1,5 +1,6 @@
using GestionIntegral.Api.Data; using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Models.Distribucion; using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -261,5 +262,31 @@ namespace GestionIntegral.Api.Services.Distribucion
return (false, $"Error interno al cambiar estado de baja: {ex.Message}"); return (false, $"Error interno al cambiar estado de baja: {ex.Message}");
} }
} }
public async Task<IEnumerable<CanillaHistorialDto>> 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();
}
} }
} }

View File

@@ -2,6 +2,7 @@
using GestionIntegral.Api.Data; using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Models.Distribucion; using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -104,9 +105,17 @@ namespace GestionIntegral.Api.Services.Distribucion
var nuevoDistribuidor = new Distribuidor var nuevoDistribuidor = new Distribuidor
{ {
Nombre = createDto.Nombre, Contacto = createDto.Contacto, NroDoc = createDto.NroDoc, IdZona = createDto.IdZona, Nombre = createDto.Nombre,
Calle = createDto.Calle, Numero = createDto.Numero, Piso = createDto.Piso, Depto = createDto.Depto, Contacto = createDto.Contacto,
Telefono = createDto.Telefono, Email = createDto.Email, Localidad = createDto.Localidad 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(); using var connection = _connectionFactory.CreateConnection();
@@ -136,7 +145,7 @@ namespace GestionIntegral.Api.Services.Distribucion
} }
catch (Exception ex) catch (Exception ex)
{ {
try { transaction.Rollback(); } catch {} try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error CrearAsync Distribuidor: {Nombre}", createDto.Nombre); _logger.LogError(ex, "Error CrearAsync Distribuidor: {Nombre}", createDto.Nombre);
return (null, $"Error interno al crear el distribuidor: {ex.Message}"); 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); _logger.LogInformation("Distribuidor ID {IdDistribuidor} actualizado por Usuario ID {IdUsuario}.", id, idUsuario);
return (true, null); 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) catch (Exception ex)
{ {
try { transaction.Rollback(); } catch {} try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error ActualizarAsync Distribuidor ID: {IdDistribuidor}", id); _logger.LogError(ex, "Error ActualizarAsync Distribuidor ID: {IdDistribuidor}", id);
return (false, $"Error interno: {ex.Message}"); return (false, $"Error interno: {ex.Message}");
} }
@@ -205,13 +214,42 @@ namespace GestionIntegral.Api.Services.Distribucion
_logger.LogInformation("Distribuidor ID {IdDistribuidor} eliminado por Usuario ID {IdUsuario}.", id, idUsuario); _logger.LogInformation("Distribuidor ID {IdDistribuidor} eliminado por Usuario ID {IdUsuario}.", id, idUsuario);
return (true, null); 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) catch (Exception ex)
{ {
try { transaction.Rollback(); } catch {} try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error EliminarAsync Distribuidor ID: {IdDistribuidor}", id); _logger.LogError(ex, "Error EliminarAsync Distribuidor ID: {IdDistribuidor}", id);
return (false, $"Error interno: {ex.Message}"); return (false, $"Error interno: {ex.Message}");
} }
} }
public async Task<IEnumerable<DistribuidorHistorialDto>> 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();
}
} }
} }

View File

@@ -7,7 +7,8 @@ using System.Data;
using GestionIntegral.Api.Data; using GestionIntegral.Api.Data;
using GestionIntegral.Api.Models.Distribucion; using GestionIntegral.Api.Models.Distribucion;
using GestionIntegral.Api.Data.Repositories.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 namespace GestionIntegral.Api.Services.Distribucion
{ {
@@ -231,9 +232,13 @@ namespace GestionIntegral.Api.Services.Distribucion
_logger.LogWarning("Se intentó eliminar Empresa ID {IdEmpresa} pero falló la eliminación de saldos asociados.", id); _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. // Decidir si continuar o fallar. Por ahora, continuamos pero loggeamos.
// throw new InvalidOperationException("Error al intentar eliminar los saldos asociados a la empresa."); // throw new InvalidOperationException("Error al intentar eliminar los saldos asociados a la empresa.");
} else if (!saldosEliminados) { }
else if (!saldosEliminados)
{
_logger.LogInformation("No se encontraron saldos para eliminar de la Empresa ID {IdEmpresa}.", id); _logger.LogInformation("No se encontraron saldos para eliminar de la Empresa ID {IdEmpresa}.", id);
} else { }
else
{
_logger.LogInformation("Saldos eliminados para Empresa ID {IdEmpresa}.", id); _logger.LogInformation("Saldos eliminados para Empresa ID {IdEmpresa}.", id);
} }
@@ -257,7 +262,25 @@ namespace GestionIntegral.Api.Services.Distribucion
} }
} }
} }
// --- Fin Transacción --- }
public async Task<IEnumerable<EmpresaHistorialDto>> 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();
} }
} }
} }

View File

@@ -11,6 +11,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Security.Claims; using System.Security.Claims;
using GestionIntegral.Api.Dtos.Auditoria;
namespace GestionIntegral.Api.Services.Distribucion namespace GestionIntegral.Api.Services.Distribucion
{ {
@@ -476,5 +477,32 @@ namespace GestionIntegral.Api.Services.Distribucion
} }
} }
} }
public async Task<IEnumerable<EntradaSalidaCanillaHistorialDto>> 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();
}
} }
} }

View File

@@ -1,6 +1,7 @@
using GestionIntegral.Api.Data; using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Models.Distribucion; using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -64,22 +65,7 @@ namespace GestionIntegral.Api.Services.Distribucion
es.IdPrecio, es.IdRecargo, es.IdPorcentaje, distribuidorData.Distribuidor?.IdZona 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; 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 return new EntradaSalidaDistDto
{ {
@@ -102,9 +88,6 @@ namespace GestionIntegral.Api.Services.Distribucion
private async Task<decimal> CalcularMontoMovimiento(int idPublicacion, int idDistribuidor, DateTime fecha, int cantidad, string tipoMovimiento, private async Task<decimal> CalcularMontoMovimiento(int idPublicacion, int idDistribuidor, DateTime fecha, int cantidad, string tipoMovimiento,
int idPrecio, int idRecargo, int idPorcentaje, int? idZonaDistribuidor) 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); var precioConfig = await _precioRepository.GetByIdAsync(idPrecio);
// Es crucial que idPrecio sea válido y se haya determinado correctamente antes de llamar aquí. // 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. // 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."); throw new InvalidOperationException($"Configuración de precio ID {idPrecio} no encontrada. No se puede calcular el monto.");
} }
decimal precioDia = 0; decimal precioDia = 0;
DayOfWeek diaSemana = fecha.DayOfWeek; DayOfWeek diaSemana = fecha.DayOfWeek;
switch (diaSemana) switch (diaSemana)
@@ -232,7 +214,7 @@ namespace GestionIntegral.Api.Services.Distribucion
// Si es Salida, montoAfectacion es positivo (aumenta deuda). Si es Entrada, es 0 por CalcularMontoMovimiento, // 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. // 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. // 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( montoAfectacion = await CalcularMontoMovimiento(
@@ -284,7 +266,7 @@ namespace GestionIntegral.Api.Services.Distribucion
decimal montoOriginal = await CalcularMontoMovimiento( decimal montoOriginal = await CalcularMontoMovimiento(
esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, esExistente.TipoMovimiento, esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, esExistente.TipoMovimiento,
esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona); 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) // 2. Actualizar la entidad en la BD (esto también guarda en historial)
var esParaActualizar = new EntradaSalidaDist var esParaActualizar = new EntradaSalidaDist
@@ -308,7 +290,7 @@ namespace GestionIntegral.Api.Services.Distribucion
decimal montoNuevo = await CalcularMontoMovimiento( decimal montoNuevo = await CalcularMontoMovimiento(
esParaActualizar.IdPublicacion, esParaActualizar.IdDistribuidor, esParaActualizar.Fecha, esParaActualizar.Cantidad, esParaActualizar.TipoMovimiento, esParaActualizar.IdPublicacion, esParaActualizar.IdDistribuidor, esParaActualizar.Fecha, esParaActualizar.Cantidad, esParaActualizar.TipoMovimiento,
esParaActualizar.IdPrecio, esParaActualizar.IdRecargo, esParaActualizar.IdPorcentaje, distribuidor.IdZona); 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 // 4. Ajustar saldo con la diferencia
@@ -354,9 +336,12 @@ namespace GestionIntegral.Api.Services.Distribucion
// Si es Salida, el monto es positivo, al revertir restamos. // 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. // 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ó 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 // Recalcular el valor como si fuera salida para saber cuánto se restó del saldo
montoReversion = await CalcularMontoMovimiento( montoReversion = await CalcularMontoMovimiento(
esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, "Salida", esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, "Salida",
@@ -385,5 +370,32 @@ namespace GestionIntegral.Api.Services.Distribucion
return (false, $"Error interno: {ex.Message}"); return (false, $"Error interno: {ex.Message}");
} }
} }
public async Task<IEnumerable<EntradaSalidaDistHistorialDto>> 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();
}
} }
} }

View File

@@ -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<IEnumerable<CambioParadaDto>> ObtenerPorCanillaAsync(int idCanilla);
Task<CambioParadaDto?> 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
}
}

View File

@@ -1,3 +1,4 @@
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Distribucion;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -11,5 +12,9 @@ namespace GestionIntegral.Api.Services.Distribucion
Task<(CanillaDto? Canilla, string? Error)> CrearAsync(CreateCanillaDto createDto, int idUsuario); 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)> ActualizarAsync(int id, UpdateCanillaDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> ToggleBajaAsync(int id, bool darDeBaja, int idUsuario); Task<(bool Exito, string? Error)> ToggleBajaAsync(int id, bool darDeBaja, int idUsuario);
Task<IEnumerable<CanillaHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idCanillaAfectado);
} }
} }

View File

@@ -1,3 +1,4 @@
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Distribucion;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -13,5 +14,9 @@ namespace GestionIntegral.Api.Services.Distribucion
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario); Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
Task<IEnumerable<DistribuidorDropdownDto>> GetAllDropdownAsync(); Task<IEnumerable<DistribuidorDropdownDto>> GetAllDropdownAsync();
Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id); Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id);
Task<IEnumerable<DistribuidorHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idDistribuidorAfectado);
} }
} }

View File

@@ -1,4 +1,5 @@
using GestionIntegral.Api.Dtos.Empresas; // DTOs de Empresas using GestionIntegral.Api.Dtos.Empresas; // DTOs de Empresas
using GestionIntegral.Api.Dtos.Auditoria;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -13,5 +14,9 @@ namespace GestionIntegral.Api.Services.Distribucion
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario); Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
Task<IEnumerable<EmpresaDropdownDto>> ObtenerParaDropdown(); Task<IEnumerable<EmpresaDropdownDto>> ObtenerParaDropdown();
Task<EmpresaLookupDto?> ObtenerLookupPorIdAsync(int id); Task<EmpresaLookupDto?> ObtenerLookupPorIdAsync(int id);
Task<IEnumerable<EmpresaHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idEmpresaAfectada);
} }
} }

View File

@@ -1,3 +1,4 @@
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Distribucion;
using System; using System;
using System.Collections.Generic; 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)> EliminarMovimientoAsync(int idParte, int idUsuario, ClaimsPrincipal userPrincipal);
Task<(bool Exito, string? Error)> LiquidarMovimientosAsync(LiquidarMovimientosCanillaRequestDto liquidarDto, int idUsuarioLiquidador); Task<(bool Exito, string? Error)> LiquidarMovimientosAsync(LiquidarMovimientosCanillaRequestDto liquidarDto, int idUsuarioLiquidador);
Task<IEnumerable<EntradaSalidaCanillaHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idParteAfectada);
} }
} }

View File

@@ -1,3 +1,4 @@
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Distribucion;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -12,5 +13,9 @@ namespace GestionIntegral.Api.Services.Distribucion
Task<(EntradaSalidaDistDto? Movimiento, string? Error)> CrearMovimientoAsync(CreateEntradaSalidaDistDto createDto, int idUsuario); 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)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaDistDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario); Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario);
Task<IEnumerable<EntradaSalidaDistHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idParteAfectada);
} }
} }

View File

@@ -1,3 +1,4 @@
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Dtos.Reportes; using GestionIntegral.Api.Dtos.Reportes;
using System; using System;
@@ -15,5 +16,9 @@ namespace GestionIntegral.Api.Services.Distribucion
Task<(bool Exito, string? Error)> EliminarAsync(int idNovedad, int idUsuario); Task<(bool Exito, string? Error)> EliminarAsync(int idNovedad, int idUsuario);
Task<IEnumerable<NovedadesCanillasReporteDto>> ObtenerReporteNovedadesAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); Task<IEnumerable<NovedadesCanillasReporteDto>> ObtenerReporteNovedadesAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta);
Task<IEnumerable<CanillaGananciaReporteDto>> ObtenerReporteGananciasAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta); Task<IEnumerable<CanillaGananciaReporteDto>> ObtenerReporteGananciasAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta);
Task<IEnumerable<NovedadCanillaHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idNovedadAfectada);
} }
} }

View File

@@ -1,6 +1,7 @@
// En Services/Distribucion (o donde corresponda) // En Services/Distribucion (o donde corresponda)
using GestionIntegral.Api.Data; // Para DbConnectionFactory using GestionIntegral.Api.Data; // Para DbConnectionFactory
using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion; using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Dtos.Reportes; using GestionIntegral.Api.Dtos.Reportes;
using GestionIntegral.Api.Models.Distribucion; // Asegúrate que el modelo Canilla tenga NomApe using GestionIntegral.Api.Models.Distribucion; // Asegúrate que el modelo Canilla tenga NomApe
@@ -250,5 +251,26 @@ namespace GestionIntegral.Api.Services.Distribucion
throw; throw;
} }
} }
public async Task<IEnumerable<NovedadCanillaHistorialDto>> 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();
}
} }
} }

View File

@@ -6,7 +6,7 @@
} }
}, },
"ConnectionStrings": { "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": { "Jwt": {
"Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2", "Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2",

View File

@@ -6,7 +6,7 @@
} }
}, },
"ConnectionStrings": { "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": { "Jwt": {
"Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2", "Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2",

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")] [assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [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.AssemblyProductAttribute("GestionIntegral.Api")]
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -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":{}} {"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":{}}

View File

@@ -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":{}} {"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":{}}

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"nueagD6vos1qa5Z6EdwL+uix/UGN3umfwM2JskZDeIQ=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y="],"CachedAssets":{},"CachedCopyCandidates":{}} {"GlobalPropertiesHash":"nueagD6vos1qa5Z6EdwL+uix/UGN3umfwM2JskZDeIQ=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ZZivTt9Zh03vN/jzywHdSIjldJk\u002BW/DgTu7TlHDqhsY=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -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<void>;
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<CambioParadaFormModalProps> = ({
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<HTMLFormElement>) => {
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 (
<Modal open={open} onClose={onClose}>
<Box sx={modalStyle}>
<Typography variant="h6" component="h2" gutterBottom>
{isModoCerrar ? 'Cerrar Vigencia de Parada' : `Nueva Parada para ${nombreCanilla || 'Canillita'}`}
</Typography>
{isModoCerrar && paradaParaCerrar && (
<Box sx={{mb:2, p:1, backgroundColor: 'grey.100', borderRadius:1}}>
<Typography variant="body2">Parada Actual: <strong>{paradaParaCerrar.parada}</strong></Typography>
<Typography variant="body2">Vigente Desde: <strong>{new Date(paradaParaCerrar.vigenciaD + 'T00:00:00Z').toLocaleDateString('es-AR', {timeZone:'UTC'})}</strong></Typography>
</Box>
)}
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
{!isModoCerrar && (
<>
<TextField label="Nueva Dirección de Parada" value={parada} required
onChange={(e) => {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}}
/>
<TextField label="Vigencia Desde" type="date" value={vigenciaD} required
onChange={(e) => {setVigenciaD(e.target.value); handleInputChange('vigenciaD');}}
margin="normal" fullWidth
error={!!localErrors.vigenciaD} helperText={localErrors.vigenciaD || ''}
disabled={loading} InputLabelProps={{ shrink: true }}
/>
</>
)}
{isModoCerrar && (
<TextField label="Vigencia Hasta (Cierre)" type="date" value={vigenciaHCierre} required
onChange={(e) => {setVigenciaHCierre(e.target.value); handleInputChange('vigenciaHCierre');}}
margin="normal" fullWidth
error={!!localErrors.vigenciaHCierre} helperText={localErrors.vigenciaHCierre || ''}
disabled={loading} InputLabelProps={{ shrink: true }} autoFocus
/>
)}
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
{localErrors.general && <Alert severity="error" sx={{ mt: 1 }}>{localErrors.general}</Alert>}
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
<Button type="submit" variant="contained" disabled={loading}>
{loading ? <CircularProgress size={24} /> : (isModoCerrar ? 'Confirmar Cierre' : 'Agregar Parada')}
</Button>
</Box>
</Box>
</Box>
</Modal>
);
};
export default CambioParadaFormModal;

View File

@@ -12,7 +12,7 @@ import LogoutIcon from '@mui/icons-material/Logout';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
import ChangePasswordModal from '../components/Modals/Usuarios/ChangePasswordModal'; import ChangePasswordModal from '../components/Modals/Usuarios/ChangePasswordModal';
import { useNavigate, useLocation } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { usePermissions } from '../hooks/usePermissions'; // <<--- AÑADIR ESTA LÍNEA import { usePermissions } from '../hooks/usePermissions';
interface MainLayoutProps { interface MainLayoutProps {
children: ReactNode; children: ReactNode;
@@ -27,6 +27,7 @@ const allAppModules = [
{ label: 'Reportes', path: '/reportes', requiredPermission: 'SS004' }, { label: 'Reportes', path: '/reportes', requiredPermission: 'SS004' },
{ label: 'Radios', path: '/radios', requiredPermission: 'SS005' }, { label: 'Radios', path: '/radios', requiredPermission: 'SS005' },
{ label: 'Usuarios', path: '/usuarios', requiredPermission: 'SS006' }, { label: 'Usuarios', path: '/usuarios', requiredPermission: 'SS006' },
{ label: 'Auditoría', path: '/auditoria', requiredPermission: null, onlySuperAdmin: true },
]; ];
const MainLayout: React.FC<MainLayoutProps> = ({ children }) => { const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
@@ -47,33 +48,41 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const [selectedTab, setSelectedTab] = useState<number | false>(false); const [selectedTab, setSelectedTab] = useState<number | false>(false);
const [anchorElUserMenu, setAnchorElUserMenu] = useState<null | HTMLElement>(null); const [anchorElUserMenu, setAnchorElUserMenu] = useState<null | HTMLElement>(null);
// --- INICIO DE CAMBIO: Filtrar módulos basados en permisos ---
const accessibleModules = useMemo(() => { 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 => { 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); return isSuperAdmin || tienePermiso(module.requiredPermission);
}); });
}, [isAuthenticated, isSuperAdmin, tienePermiso]); }, [isAuthenticated, isSuperAdmin, tienePermiso]);
// --- FIN DE CAMBIO ---
useEffect(() => { useEffect(() => {
// --- INICIO DE CAMBIO: Usar accessibleModules para encontrar el tab ---
const currentModulePath = accessibleModules.findIndex(module => const currentModulePath = accessibleModules.findIndex(module =>
location.pathname === module.path || (module.path !== '/' && location.pathname.startsWith(module.path + '/')) location.pathname === module.path || (module.path !== '/' && location.pathname.startsWith(module.path + '/'))
); );
if (currentModulePath !== -1) { if (currentModulePath !== -1) {
setSelectedTab(currentModulePath); setSelectedTab(currentModulePath);
} else if (location.pathname === '/') { } else if (location.pathname === '/') {
// Asegurar que Inicio se seleccione si es accesible
const inicioIndex = accessibleModules.findIndex(m => m.path === '/'); const inicioIndex = accessibleModules.findIndex(m => m.path === '/');
if (inicioIndex !== -1) setSelectedTab(inicioIndex); if (inicioIndex !== -1) setSelectedTab(inicioIndex);
else setSelectedTab(false); else setSelectedTab(false);
} else {
// 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 { } else {
setSelectedTab(false); setSelectedTab(false);
} }
// --- FIN DE CAMBIO --- }
}, [location.pathname, accessibleModules]); // << CAMBIO: dependencia a accessibleModules }, [location.pathname, accessibleModules]);
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => { const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElUserMenu(event.currentTarget); setAnchorElUserMenu(event.currentTarget);
@@ -106,18 +115,13 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
}; };
const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
// --- INICIO DE CAMBIO: Navegar usando accessibleModules ---
if (accessibleModules[newValue]) { if (accessibleModules[newValue]) {
setSelectedTab(newValue); setSelectedTab(newValue);
navigate(accessibleModules[newValue].path); navigate(accessibleModules[newValue].path);
} }
// --- FIN DE CAMBIO ---
}; };
const isReportesModule = location.pathname.startsWith('/reportes');
if (showForcedPasswordChangeModal && isPasswordChangeForced) { if (showForcedPasswordChangeModal && isPasswordChangeForced) {
// ... (sin cambios)
return ( return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}> <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}>
<ChangePasswordModal <ChangePasswordModal
@@ -151,7 +155,6 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
Sistema de Gestión - El Día Sistema de Gestión - El Día
</Typography> </Typography>
<Box sx={{ display: 'flex', alignItems: 'center' }}> <Box sx={{ display: 'flex', alignItems: 'center' }}>
{/* ... (Menú de usuario sin cambios) ... */}
{user && ( {user && (
<Typography sx={{ mr: 2, display: { xs: 'none', sm: 'block' } }} > <Typography sx={{ mr: 2, display: { xs: 'none', sm: 'block' } }} >
Hola, {user.nombreCompleto} Hola, {user.nombreCompleto}
@@ -202,7 +205,6 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
)} )}
</Box> </Box>
</Toolbar> </Toolbar>
{/* --- INICIO DE CAMBIO: Renderizar Tabs solo si hay módulos accesibles y está autenticado --- */}
{isAuthenticated && accessibleModules.length > 0 && ( {isAuthenticated && accessibleModules.length > 0 && (
<Paper square elevation={0} > <Paper square elevation={0} >
<Tabs <Tabs
@@ -225,22 +227,20 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
} }
}} }}
> >
{/* Mapear sobre accessibleModules en lugar de allAppModules */}
{accessibleModules.map((module) => ( {accessibleModules.map((module) => (
<Tab key={module.path} label={module.label} /> <Tab key={module.path} label={module.label} />
))} ))}
</Tabs> </Tabs>
</Paper> </Paper>
)} )}
{/* --- FIN DE CAMBIO --- */}
</AppBar> </AppBar>
<Box <Box
component="main" component="main"
sx={{ /* ... (estilos sin cambios) ... */ sx={{
flexGrow: 1, flexGrow: 1,
py: isReportesModule ? 0 : { xs: 1.5, sm: 2, md: 2.5 }, py: location.pathname.startsWith('/reportes') ? 0 : { xs: 1.5, sm: 2, md: 2.5 },
px: isReportesModule ? 0 : { xs: 1.5, sm: 2, md: 2.5 }, px: location.pathname.startsWith('/reportes') ? 0 : { xs: 1.5, sm: 2, md: 2.5 },
display: 'flex', display: 'flex',
flexDirection: 'column' flexDirection: 'column'
}} }}
@@ -248,7 +248,7 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
{children} {children}
</Box> </Box>
<Box component="footer" sx={{ /* ... (estilos sin cambios) ... */ <Box component="footer" sx={{
p: 1, backgroundColor: 'grey.200', color: 'text.secondary', p: 1, backgroundColor: 'grey.200', color: 'text.secondary',
textAlign: 'left', borderTop: (theme) => `1px solid ${theme.palette.divider}` textAlign: 'left', borderTop: (theme) => `1px solid ${theme.palette.divider}`
}}> }}>
@@ -265,5 +265,4 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
</Box> </Box>
); );
}; };
export default MainLayout; export default MainLayout;

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -0,0 +1,5 @@
export interface CreateCambioParadaDto {
// idCanilla se pasa por la ruta
parada: string;
vigenciaD: string; // "yyyy-MM-dd"
}

View File

@@ -0,0 +1,3 @@
export interface UpdateCambioParadaDto {
vigenciaH: string; // "yyyy-MM-dd"
}

View File

@@ -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<any[]>([]); // Tipo genérico para los datos de la tabla
const [columnasActuales, setColumnasActuales] = useState<GridColDef[]>([]);
const [loading, setLoading] = useState(false); // Un solo loading para la búsqueda
const [error, setError] = useState<string | null>(null); // Error general de la página o de la búsqueda
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
const [filtroIdUsuarioMod, setFiltroIdUsuarioMod] = useState<number | string>('');
const [filtroTipoEntidad, setFiltroTipoEntidad] = useState<string>('');
const [filtroIdEntidad, setFiltroIdEntidad] = useState<string>('');
const [filtroTipoMod, setFiltroTipoMod] = useState<string>('');
const [usuariosDropdown, setUsuariosDropdown] = useState<UsuarioDto[]>([]);
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) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2">{params.value || '-'}</Typography></Tooltip>) },
];
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) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2">{params.value || '-'}</Typography></Tooltip>) },
{ field: 'observaciones', headerName: 'Obs. Nota', flex: 1, minWidth: 150, renderCell: (params) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2">{params.value || '-'}</Typography></Tooltip>) },
];
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) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2">{params.value || '-'}</Typography></Tooltip>) },
// 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) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2">{params.value || '-'}</Typography></Tooltip>) },
// 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) => (
<Tooltip title={params.value || ''} arrow placement="top">
<Typography noWrap variant="body2" sx={{ width: '100%' }}>{params.value || '-'}</Typography>
</Tooltip>
)
},
];
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) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2" sx={{ width: '100%' }}>{params.value || '-'}</Typography></Tooltip>)
},
];
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) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2" sx={{ width: '100%' }}>{params.value || '-'}</Typography></Tooltip>)
},
];
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) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2">{params.value || '-'}</Typography></Tooltip>) },
{ 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) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2">{params.value || '-'}</Typography></Tooltip>) },
];
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) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2">{params.value || '-'}</Typography></Tooltip>) },
{ 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) => (<Tooltip title={params.value || ''}><Typography noWrap variant="body2">{params.value || '-'}</Typography></Tooltip>) },
{ 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) => ( <Tooltip title={params.value || ''}><Typography noWrap variant="body2" sx={{width:'100%'}}>{params.value || '-'}</Typography></Tooltip>)},
];
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 <Box sx={{ p: 2 }}><Alert severity="error">No tiene permiso para acceder a la Auditoría General.</Alert></Box>;
}
if (loadingDropdowns && !usuariosDropdown.length) { // Spinner inicial para dropdowns
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}><CircularProgress /></Box>;
}
return (
<Box sx={{ p: 1 }}>
<Typography variant="h5" gutterBottom>Auditoría General del Sistema</Typography>
<Paper sx={{ p: 2, mb: 2 }}>
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
<TextField label="Fecha Mod. Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} />
<TextField label="Fecha Mod. Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} />
<FormControl size="small" sx={{ minWidth: 200, flexGrow: 1 }} disabled={loadingDropdowns}>
<InputLabel>Modificado Por</InputLabel>
<Select value={filtroIdUsuarioMod} label="Modificado Por" onChange={(e) => setFiltroIdUsuarioMod(e.target.value)}>
<MenuItem value=""><em>Todos</em></MenuItem>
{usuariosDropdown.map((u) => (<MenuItem key={u.id} value={u.id}>{u.nombre} {u.apellido} ({u.user})</MenuItem>))}
</Select>
</FormControl>
<FormControl size="small" sx={{ minWidth: 220, flexGrow: 1 }} required error={!filtroTipoEntidad && !!error && error.includes("Debe seleccionar un 'Tipo de Entidad")}>
<InputLabel>Tipo de Entidad a Auditar</InputLabel>
<Select value={filtroTipoEntidad} label="Tipo de Entidad a Auditar"
onChange={(e) => {
setFiltroTipoEntidad(e.target.value);
setFiltroIdEntidad('');
setDatosAuditoria([]);
setColumnasActuales(getCommonAuditColumns());
if (error && error.includes("Debe seleccionar un 'Tipo de Entidad")) setError(null);
}}
>
<MenuItem value=""><em>Seleccione un tipo</em></MenuItem>
{TIPOS_ENTIDAD_AUDITABLES.map((t) => (<MenuItem key={t.value} value={t.value}>{t.label}</MenuItem>))}
</Select>
{!filtroTipoEntidad && error && error.includes("Debe seleccionar un 'Tipo de Entidad") && <Typography color="error" variant="caption" sx={{ ml: 1.5, mt: 0.5 }}>{error}</Typography>}
</FormControl>
<TextField label="ID Entidad Afectada" type="number" size="small" value={filtroIdEntidad}
onChange={(e) => setFiltroIdEntidad(e.target.value)} sx={{ minWidth: 150 }}
InputProps={{ inputProps: { min: 1 } }}
disabled={!filtroTipoEntidad}
/>
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }}>
<InputLabel>Tipo Modificación</InputLabel>
<Select value={filtroTipoMod} label="Tipo Modificación" onChange={(e) => setFiltroTipoMod(e.target.value)}>
<MenuItem value=""><em>Todas</em></MenuItem>
{TIPOS_MODIFICACION.map((t) => (<MenuItem key={t} value={t}>{t}</MenuItem>))}
</Select>
</FormControl>
<Button variant="contained" onClick={handleBuscar} disabled={loading || loadingDropdowns || !filtroTipoEntidad}>
{loading ? <CircularProgress size={24} color="inherit" /> : "Buscar"}
</Button>
</Box>
</Paper>
{/* Mostrar error general si no es un error de "seleccione tipo de entidad" */}
{error && !error.includes("Debe seleccionar un 'Tipo de Entidad") && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{!loading && !error && filtroTipoEntidad && (
<Paper sx={{ height: 'calc(100vh - 360px)', width: '100%' }}> {/* Ajustar altura */}
<DataGrid
rows={datosAuditoria}
columns={columnasActuales}
localeText={esES.components.MuiDataGrid.defaultProps.localeText}
density="compact"
rowCount={datosAuditoria.length}
paginationModel={{ page, pageSize: rowsPerPage }}
onPaginationModelChange={(model) => {
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
/>
</Paper>
)}
{!loading && !error && datosAuditoria.length === 0 && filtroTipoEntidad && (
<Typography sx={{ mt: 2, fontStyle: 'italic', textAlign: 'center' }}>
No se encontraron registros de auditoría para '{TIPOS_ENTIDAD_AUDITABLES.find(t => t.value === filtroTipoEntidad)?.label || filtroTipoEntidad}' con los filtros aplicados.
</Typography>
)}
{!loading && !error && !filtroTipoEntidad && (
<Typography sx={{ mt: 2, fontStyle: 'italic', textAlign: 'center' }}>
Seleccione un "Tipo de Entidad a Auditar" y presione "Buscar".
</Typography>
)}
</Box>
);
};
export default AuditoriaGeneralPage;

View File

@@ -7,6 +7,7 @@ import {
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import MoreVertIcon from '@mui/icons-material/MoreVert'; import MoreVertIcon from '@mui/icons-material/MoreVert';
import ToggleOnIcon from '@mui/icons-material/ToggleOn'; import ToggleOnIcon from '@mui/icons-material/ToggleOn';
import HistoryIcon from '@mui/icons-material/History';
import ToggleOffIcon from '@mui/icons-material/ToggleOff'; import ToggleOffIcon from '@mui/icons-material/ToggleOff';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import EventNoteIcon from '@mui/icons-material/EventNote'; // << AÑADIR IMPORTACIÓN DEL ICONO 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 puedeCrear = isSuperAdmin || tienePermiso("CG002");
const puedeModificar = isSuperAdmin || tienePermiso("CG003"); const puedeModificar = isSuperAdmin || tienePermiso("CG003");
const puedeDarBaja = isSuperAdmin || tienePermiso("CG005"); const puedeDarBaja = isSuperAdmin || tienePermiso("CG005");
// Permisos para Novedades const puedeGestionarParadas = isSuperAdmin || tienePermiso("CG007");
const puedeGestionarNovedades = isSuperAdmin || tienePermiso("CG006"); // << DEFINIR PERMISO const puedeGestionarNovedades = isSuperAdmin || tienePermiso("CG006");
// 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 puedeVerNovedadesCanilla = puedeVer || puedeGestionarNovedades; // << LÓGICA PARA MOSTRAR LA OPCIÓN const puedeVerNovedadesCanilla = puedeVer || puedeGestionarNovedades; // << LÓGICA PARA MOSTRAR LA OPCIÓN
@@ -131,6 +130,12 @@ const GestionarCanillitasPage: React.FC = () => {
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => { const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0); 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); const displayData = canillitas.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
if (!loading && !puedeVer) { if (!loading && !puedeVer) {
@@ -181,7 +186,7 @@ const GestionarCanillitasPage: React.FC = () => {
{error && !apiErrorMessage && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>} {error && !apiErrorMessage && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>} {apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
{!loading && !error && puedeVer && ( // Asegurar que puedeVer sea true también aquí {!loading && !error && puedeVer && ( // Asegurar que puedeVer sea true también a
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table size="small"> <Table size="small">
<TableHead><TableRow> <TableHead><TableRow>
@@ -231,6 +236,12 @@ const GestionarCanillitasPage: React.FC = () => {
<ListItemText>Novedades</ListItemText> <ListItemText>Novedades</ListItemText>
</MenuItem> </MenuItem>
)} )}
{puedeGestionarParadas && selectedCanillitaRow && (
<MenuItem onClick={() => handleOpenParadas(selectedCanillitaRow.idCanilla)}>
<ListItemIcon><HistoryIcon /></ListItemIcon> {/* Cambiar ícono si es necesario */}
<ListItemText>Gestionar Paradas</ListItemText>
</MenuItem>
)}
{puedeModificar && selectedCanillitaRow && ( // Asegurar que selectedCanillitaRow existe {puedeModificar && selectedCanillitaRow && ( // Asegurar que selectedCanillitaRow existe
<MenuItem onClick={() => { handleOpenModal(selectedCanillitaRow); handleMenuClose(); }}> <MenuItem onClick={() => { handleOpenModal(selectedCanillitaRow); handleMenuClose(); }}>
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon> <ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>

View File

@@ -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<CanillaDto | null>(null);
const [paradas, setParadas] = useState<CambioParadaDto[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [modalOpen, setModalOpen] = useState(false);
const [paradaParaCerrar, setParadaParaCerrar] = useState<CambioParadaDto | null>(null); // Para el modo "Cerrar" del modal
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [selectedParadaRow, setSelectedParadaRow] = useState<CambioParadaDto | null>(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<HTMLElement>, 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 <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}><CircularProgress /></Box>;
if (error) return <Alert severity="error" sx={{ m: 2 }}>{error}</Alert>;
if (!puedeGestionarParadas && !puedeVerCanillitas) return <Alert severity="error" sx={{ m: 2 }}>Acceso denegado.</Alert>;
return (
<Box sx={{ p: 2 }}>
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate(`/distribucion/canillas`)} sx={{ mb: 2 }}>
Volver a Canillitas
</Button>
<Typography variant="h5" gutterBottom>
Historial de Paradas de: {canillita?.nomApe || `Canillita ID ${idCanilla}`}
</Typography>
<Paper sx={{ p: 2, mb: 2 }}>
{puedeGestionarParadas && (
<Button variant="contained" startIcon={<AddIcon />} onClick={handleOpenModalParaCrear} sx={{ mb: {xs: 2, sm:0} }}>
Registrar Nueva Parada
</Button>
)}
</Paper>
{apiErrorMessage && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>}
<TableContainer component={Paper}>
<Table size="small">
<TableHead><TableRow>
<TableCell sx={{fontWeight: 'bold'}}>Dirección de Parada</TableCell>
<TableCell sx={{fontWeight: 'bold'}}>Vigencia Desde</TableCell>
<TableCell sx={{fontWeight: 'bold'}}>Vigencia Hasta</TableCell>
<TableCell align="center" sx={{fontWeight: 'bold'}}>Estado</TableCell>
{puedeGestionarParadas && <TableCell align="right" sx={{fontWeight: 'bold'}}>Acciones</TableCell>}
</TableRow></TableHead>
<TableBody>
{paradas.length === 0 ? (
<TableRow><TableCell colSpan={puedeGestionarParadas ? 5 : 4} align="center">No hay historial de paradas para este canillita.</TableCell></TableRow>
) : (
paradas.map((p) => (
<TableRow key={p.idRegistro} hover>
<TableCell>{p.parada}</TableCell>
<TableCell>{formatDate(p.vigenciaD)}</TableCell>
<TableCell>{formatDate(p.vigenciaH)}</TableCell>
<TableCell align="center">{p.esActual ? <Chip label="Activa" color="success" size="small" /> : <Chip label="Histórica" size="small" />}</TableCell>
{puedeGestionarParadas && (
<TableCell align="right">
<IconButton onClick={(e) => handleMenuOpen(e, p)} disabled={!puedeGestionarParadas}><MoreVertIcon /></IconButton>
</TableCell>
)}
</TableRow>
)))}
</TableBody>
</Table>
</TableContainer>
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
{puedeGestionarParadas && selectedParadaRow && selectedParadaRow.esActual && (
<MenuItem onClick={() => { handleOpenModalParaCerrar(selectedParadaRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{mr:1}}/> Cerrar Vigencia</MenuItem>)}
{/* La eliminación de paradas históricas puede ser delicada, considerar si es necesaria */}
{puedeGestionarParadas && selectedParadaRow && selectedParadaRow.vigenciaH && ( /* Solo eliminar si está cerrada */
<MenuItem onClick={() => handleDelete(selectedParadaRow.idRegistro)}><DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar Registro</MenuItem>)}
</Menu>
{idCanilla &&
<CambioParadaFormModal
open={modalOpen}
onClose={handleCloseModal}
onSubmit={handleSubmitModal}
idCanilla={idCanilla}
nombreCanilla={canillita?.nomApe}
paradaParaCerrar={paradaParaCerrar} // Para diferenciar modo crear vs cerrar
errorMessage={apiErrorMessage}
clearErrorMessage={() => setApiErrorMessage(null)}
/>
}
</Box>
);
};
export default GestionarParadasCanillaPage;

View File

@@ -3,32 +3,30 @@ import { Box, Tabs, Tab, Paper, Typography } from '@mui/material';
import { Outlet, useNavigate, useLocation } from 'react-router-dom'; import { Outlet, useNavigate, useLocation } from 'react-router-dom';
const radiosSubModules = [ const radiosSubModules = [
{ label: 'Generar Listas', path: 'generar-listas' },
{ label: 'Ritmos', path: 'ritmos' }, { label: 'Ritmos', path: 'ritmos' },
{ label: 'Canciones', path: 'canciones' }, { label: 'Canciones', path: 'canciones' },
{ label: 'Generar Listas', path: 'generar-listas' },
]; ];
const RadiosIndexPage: React.FC = () => { const RadiosIndexPage: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const [selectedSubTab, setSelectedSubTab] = useState<number | false>(false); const [selectedSubTab, setSelectedSubTab] = useState<number>(0); // Inicializa en 0
useEffect(() => { useEffect(() => {
const currentBasePath = '/radios'; const currentBasePath = '/radios';
const subPath = location.pathname.startsWith(currentBasePath + '/') const subPath = location.pathname.startsWith(currentBasePath + '/')
? location.pathname.substring(currentBasePath.length + 1).split('/')[0] ? location.pathname.substring(currentBasePath.length + 1).split('/')[0]
: (location.pathname === currentBasePath ? radiosSubModules[0]?.path : undefined); : undefined;
const activeTabIndex = radiosSubModules.findIndex(sm => sm.path === subPath); const activeTabIndex = radiosSubModules.findIndex(sm => sm.path === subPath);
if (activeTabIndex !== -1) { if (location.pathname === currentBasePath) {
setSelectedSubTab(activeTabIndex); // Si está en /radios, redirige a la primera subruta
} else {
if (location.pathname === currentBasePath && radiosSubModules.length > 0) {
navigate(radiosSubModules[0].path, { replace: true }); navigate(radiosSubModules[0].path, { replace: true });
setSelectedSubTab(0); setSelectedSubTab(0);
} else { } else if (activeTabIndex !== -1) {
setSelectedSubTab(false); setSelectedSubTab(activeTabIndex);
}
} }
}, [location.pathname, navigate]); }, [location.pathname, navigate]);

View File

@@ -25,6 +25,7 @@ import GestionarSalidasOtrosDestinosPage from '../pages/Distribucion/GestionarSa
import GestionarEntradasSalidasDistPage from '../pages/Distribucion/GestionarEntradasSalidasDistPage'; import GestionarEntradasSalidasDistPage from '../pages/Distribucion/GestionarEntradasSalidasDistPage';
import GestionarEntradasSalidasCanillaPage from '../pages/Distribucion/GestionarEntradasSalidasCanillaPage'; import GestionarEntradasSalidasCanillaPage from '../pages/Distribucion/GestionarEntradasSalidasCanillaPage';
import GestionarControlDevolucionesPage from '../pages/Distribucion/GestionarControlDevolucionesPage'; import GestionarControlDevolucionesPage from '../pages/Distribucion/GestionarControlDevolucionesPage';
import GestionarParadasCanillaPage from '../pages/Distribucion/GestionarParadasCanillaPage';
// Impresión // Impresión
import ImpresionIndexPage from '../pages/Impresion/ImpresionIndexPage'; import ImpresionIndexPage from '../pages/Impresion/ImpresionIndexPage';
@@ -77,6 +78,7 @@ import ReporteListadoDistMensualPage from '../pages/Reportes/ReporteListadoDistM
// Auditorias // Auditorias
import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage'; import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage';
import AuditoriaGeneralPage from '../pages/Auditoria/AuditoriaGeneralPage';
// --- ProtectedRoute y PublicRoute SIN CAMBIOS --- // --- ProtectedRoute y PublicRoute SIN CAMBIOS ---
@@ -144,6 +146,7 @@ const AppRoutes = () => {
<Route path="salidas-otros-destinos" element={<GestionarSalidasOtrosDestinosPage />} /> <Route path="salidas-otros-destinos" element={<GestionarSalidasOtrosDestinosPage />} />
<Route path="canillas" element={<GestionarCanillitasPage />} /> <Route path="canillas" element={<GestionarCanillitasPage />} />
<Route path="canillas/:idCanilla/novedades" element={<GestionarNovedadesCanillaPage />} /> <Route path="canillas/:idCanilla/novedades" element={<GestionarNovedadesCanillaPage />} />
<Route path="canillas/:idCanilla/paradas" element={<GestionarParadasCanillaPage />} />
<Route path="distribuidores" element={<GestionarDistribuidoresPage />} /> <Route path="distribuidores" element={<GestionarDistribuidoresPage />} />
<Route path="otros-destinos" element={<GestionarOtrosDestinosPage />} /> <Route path="otros-destinos" element={<GestionarOtrosDestinosPage />} />
<Route path="zonas" element={<GestionarZonasPage />} /> <Route path="zonas" element={<GestionarZonasPage />} />
@@ -245,17 +248,21 @@ const AppRoutes = () => {
<Route path="auditoria-usuarios" element={<GestionarAuditoriaUsuariosPage />} /> <Route path="auditoria-usuarios" element={<GestionarAuditoriaUsuariosPage />} />
</Route> </Route>
{/* Módulo de Auditoías (anidado) */}
<Route path="auditoria"
element={
<SectionProtectedRoute onlySuperAdmin={true} sectionName="Auditoría">
<Outlet />
</SectionProtectedRoute>
}
>
<Route index element={<Navigate to="general" replace />} />
<Route path="general" element={<AuditoriaGeneralPage />} />
</Route>
{/* Ruta catch-all DENTRO del layout protegido */} {/* Ruta catch-all DENTRO del layout protegido */}
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
</Route> {/* Cierre de la ruta padre "/" */} </Route> {/* 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.
*/}
{/* <Route path="*" element={<Navigate to="/login" replace />} /> */}
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
); );

View File

@@ -1,21 +1,26 @@
// src/routes/SectionProtectedRoute.tsx
import React from 'react'; import React from 'react';
import { Navigate, Outlet } from 'react-router-dom'; import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
import { usePermissions } from '../hooks/usePermissions'; import { usePermissions } from '../hooks/usePermissions';
import { Box, CircularProgress } from '@mui/material'; import { Alert, Box, CircularProgress } from '@mui/material';
interface SectionProtectedRouteProps { interface SectionProtectedRouteProps {
requiredPermission: string; requiredPermission?: string | null; // Hacerlo opcional
onlySuperAdmin?: boolean; // Nueva prop
sectionName: string; sectionName: string;
children?: React.ReactNode; children?: React.ReactNode;
} }
const SectionProtectedRoute: React.FC<SectionProtectedRouteProps> = ({ requiredPermission, sectionName, children }) => { const SectionProtectedRoute: React.FC<SectionProtectedRouteProps> = ({
const { isAuthenticated, isLoading: authIsLoading } = useAuth(); // isLoading de AuthContext requiredPermission,
onlySuperAdmin = false, // Default a false
sectionName,
children
}) => {
const { isAuthenticated, isLoading: authIsLoading } = useAuth();
const { tienePermiso, isSuperAdmin, currentUser } = usePermissions(); const { tienePermiso, isSuperAdmin, currentUser } = usePermissions();
if (authIsLoading) { // Esperar a que el AuthContext termine su carga inicial if (authIsLoading) {
return ( return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '80vh' }}> <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '80vh' }}>
<CircularProgress /> <CircularProgress />
@@ -26,26 +31,37 @@ const SectionProtectedRoute: React.FC<SectionProtectedRouteProps> = ({ requiredP
if (!isAuthenticated) { if (!isAuthenticated) {
return <Navigate to="/login" replace />; return <Navigate to="/login" replace />;
} }
// 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) { 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."); console.error("SectionProtectedRoute: Usuario autenticado pero currentUser es null.");
return <Navigate to="/login" replace />; // O un error más específico return <Navigate to="/login" replace />;
}
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) { if (!canAccessSection) {
console.error('SectionProtectedRoute: Usuario autenticado pero sin acceso a sección ', sectionName); return (
return <Navigate to="/" replace />; <Box sx={{p: 3, display:'flex', justifyContent:'center', mt: 2}}>
<Alert severity="error" sx={{width: '100%', maxWidth: 'md'}}>
No tiene permiso para acceder a la sección de {sectionName}.
</Alert>
</Box>
);
} }
// Si children se proporciona (como <SectionProtectedRoute><IndexPage/></SectionProtectedRoute>), renderiza children.
// Si no (como <Route element={<SectionProtectedRoute ... />} > <Route .../> </Route>), renderiza Outlet.
return children ? <>{children}</> : <Outlet />; return children ? <>{children}</> : <Outlet />;
}; };

View File

@@ -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<UsuarioHistorialDto[]> => {
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<UsuarioHistorialDto[]>('/auditoria/usuarios', { params: queryParams });
return response.data;
};
const getHistorialPagosDistribuidor = async (params: HistorialPagosDistribuidorParams): Promise<PagoDistribuidorHistorialDto[]> => {
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<PagoDistribuidorHistorialDto[]>('/auditoria/pagos-distribuidores', { params: queryParams });
return response.data;
};
const getHistorialNotasCD = async (params: HistorialNotasCDParams): Promise<NotaCreditoDebitoHistorialDto[]> => {
const queryParams: any = { ...params };
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
delete queryParams.idUsuarioModificador;
const response = await apiClient.get<NotaCreditoDebitoHistorialDto[]>('/auditoria/notas-credito-debito', { params: queryParams });
return response.data;
};
const getHistorialEntradasSalidasDist = async (params: HistorialEntradaSalidaDistParams): Promise<EntradaSalidaDistHistorialDto[]> => {
const queryParams: any = { ...params };
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
delete queryParams.idUsuarioModificador;
const response = await apiClient.get<EntradaSalidaDistHistorialDto[]>('/auditoria/entradas-salidas-dist', { params: queryParams });
return response.data;
};
const getHistorialEntradasSalidasCanilla = async (params: HistorialEntradaSalidaCanillaParams): Promise<EntradaSalidaCanillaHistorialDto[]> => {
const queryParams: any = { ...params };
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
delete queryParams.idUsuarioModificador;
const response = await apiClient.get<EntradaSalidaCanillaHistorialDto[]>('/auditoria/entradas-salidas-canilla', { params: queryParams });
return response.data;
};
const getHistorialNovedadesCanilla = async (params: HistorialNovedadesCanillaParams): Promise<NovedadCanillaHistorialDto[]> => {
const queryParams: any = { ...params };
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
delete queryParams.idUsuarioModificador;
const response = await apiClient.get<NovedadCanillaHistorialDto[]>('/auditoria/novedades-canilla', { params: queryParams });
return response.data;
};
const getHistorialAjustesSaldo = async (params: HistorialAjustesSaldoParams): Promise<SaldoAjusteHistorialDto[]> => {
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<SaldoAjusteHistorialDto[]>('/auditoria/ajustes-saldo', { params: queryParams });
return response.data;
};
const getHistorialTiposPago = async (params: HistorialTiposPagoParams): Promise<TipoPagoHistorialDto[]> => {
const queryParams: any = { ...params };
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
delete queryParams.idUsuarioModificador;
const response = await apiClient.get<TipoPagoHistorialDto[]>('/auditoria/tipos-pago', { params: queryParams });
return response.data;
};
const getHistorialCanillitasMaestro = async (params: HistorialCanillitasMaestroParams): Promise<CanillaHistorialDto[]> => {
const queryParams: any = { ...params };
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
delete queryParams.idUsuarioModificador;
const response = await apiClient.get<CanillaHistorialDto[]>('/auditoria/canillitas-maestro', { params: queryParams });
return response.data;
};
const getHistorialDistribuidoresMaestro = async (params: HistorialDistribuidoresMaestroParams): Promise<DistribuidorHistorialDto[]> => {
const queryParams: any = { ...params };
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
delete queryParams.idUsuarioModificador;
const response = await apiClient.get<DistribuidorHistorialDto[]>('/auditoria/distribuidores-maestro', { params: queryParams });
return response.data;
};
const getHistorialEmpresasMaestro = async (params: HistorialEmpresasMaestroParams): Promise<EmpresaHistorialDto[]> => {
const queryParams: any = { ...params };
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
delete queryParams.idUsuarioModificador;
const response = await apiClient.get<EmpresaHistorialDto[]>('/auditoria/empresas-maestro', { params: queryParams });
return response.data;
};
const auditoriaService = {
getHistorialUsuarios,
getHistorialPagosDistribuidor,
getHistorialNotasCD,
getHistorialEntradasSalidasDist,
getHistorialEntradasSalidasCanilla,
getHistorialNovedadesCanilla,
getHistorialAjustesSaldo,
getHistorialTiposPago,
getHistorialCanillitasMaestro,
getHistorialDistribuidoresMaestro,
getHistorialEmpresasMaestro,
};
export default auditoriaService;

View File

@@ -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<CambioParadaDto[]> => {
const response = await apiClient.get<CambioParadaDto[]>(`/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<CambioParadaDto> => {
const response = await apiClient.post<CambioParadaDto>(`/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<void> => {
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<void> => {
await apiClient.delete(`/paradas/${idRegistroParada}`);
};
const cambioParadaService = {
getParadasPorCanilla,
createParada,
cerrarParada,
deleteParada,
};
export default cambioParadaService;

View File

@@ -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) // Puedes añadir interceptores de respuesta para manejar errores globales (ej: 401 Unauthorized)
export default apiClient; export default apiClient;