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

@@ -1,6 +1,7 @@
using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging;
@@ -56,30 +57,15 @@ namespace GestionIntegral.Api.Services.Distribucion
var publicacionData = await _publicacionRepository.GetByIdAsync(es.IdPublicacion);
var distribuidorData = await _distribuidorRepository.GetByIdAsync(es.IdDistribuidor);
// Obtener el valor bruto del movimiento
decimal valorBrutoMovimiento = await CalcularMontoMovimiento(
es.IdPublicacion, es.IdDistribuidor, es.Fecha, es.Cantidad,
es.IdPublicacion, es.IdDistribuidor, es.Fecha, es.Cantidad,
es.TipoMovimiento, // Pasamos el tipo de movimiento original aquí
es.IdPrecio, es.IdRecargo, es.IdPorcentaje, distribuidorData.Distribuidor?.IdZona
);
// Ajustar para el DTO: si es "Entrada", el monto calculado es un crédito (negativo o positivo según convención)
// Para consistencia con el ajuste de saldo, si es Entrada, el MontoCalculado para el DTO puede ser el valor
// que se le "acredita" al distribuidor (o sea, el valor de la mercadería devuelta).
// La lógica de +/- para el saldo ya está en Crear/Actualizar/Eliminar.
// Aquí solo mostramos el valor del movimiento. Si es entrada, es el valor de lo devuelto.
// Si es salida, es el valor de lo que se le factura.
// El método CalcularMonto ya devuelve el monto que el distribuidor DEBE pagar por una SALIDA.
// Para una ENTRADA (devolución), el valor de esa mercadería es el mismo, pero opera en sentido contrario al saldo.
decimal montoCalculadoParaDto = valorBrutoMovimiento;
// Si queremos que el DTO muestre las entradas como un valor que "reduce la deuda",
// podría ser positivo. Si queremos que refleje el impacto directo en la factura (salidas suman, entradas restan),
// podríamos hacerlo negativo.
// Por ahora, dejaremos que CalcularMontoMovimiento devuelva el valor de una "Salida",
// y si es "Entrada", este mismo valor es el que se acredita.
// La columna `MontoCalculado` en el DTO representará el valor de la transacción.
return new EntradaSalidaDistDto
{
@@ -98,13 +84,10 @@ namespace GestionIntegral.Api.Services.Distribucion
MontoCalculado = montoCalculadoParaDto // Representa el valor de los N ejemplares
};
}
private async Task<decimal> CalcularMontoMovimiento(int idPublicacion, int idDistribuidor, DateTime fecha, int cantidad, string tipoMovimiento,
int idPrecio, int idRecargo, int idPorcentaje, int? idZonaDistribuidor)
{
// YA NO SE DEVUELVE 0 PARA ENTRADA AQUÍ
// if (tipoMovimiento == "Entrada") return 0;
var precioConfig = await _precioRepository.GetByIdAsync(idPrecio);
// Es crucial que idPrecio sea válido y se haya determinado correctamente antes de llamar aquí.
// Si precioConfig es null, se lanzará una excepción abajo, lo cual está bien si es un estado inesperado.
@@ -118,7 +101,6 @@ namespace GestionIntegral.Api.Services.Distribucion
throw new InvalidOperationException($"Configuración de precio ID {idPrecio} no encontrada. No se puede calcular el monto.");
}
decimal precioDia = 0;
DayOfWeek diaSemana = fecha.DayOfWeek;
switch (diaSemana)
@@ -135,13 +117,13 @@ namespace GestionIntegral.Api.Services.Distribucion
decimal valorRecargo = 0;
if (idRecargo > 0 && idZonaDistribuidor.HasValue)
{
var recargoConfig = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(idPublicacion, idZonaDistribuidor.Value, fecha);
var recargoConfig = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(idPublicacion, idZonaDistribuidor.Value, fecha);
if (recargoConfig != null)
{
valorRecargo = recargoConfig.Valor;
}
}
decimal precioConRecargo = precioDia + valorRecargo;
decimal montoBase = precioConRecargo * cantidad;
@@ -197,7 +179,7 @@ namespace GestionIntegral.Api.Services.Distribucion
RecargoZona? recargoActivo = null;
if (distribuidor.IdZona.HasValue)
{
recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(createDto.IdPublicacion, distribuidor.IdZona.Value, createDto.Fecha.Date);
recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(createDto.IdPublicacion, distribuidor.IdZona.Value, createDto.Fecha.Date);
}
var porcPagoActivo = await _porcPagoRepository.GetActiveByPublicacionDistribuidorAndDateAsync(createDto.IdPublicacion, createDto.IdDistribuidor, createDto.Fecha.Date);
@@ -228,13 +210,13 @@ namespace GestionIntegral.Api.Services.Distribucion
decimal montoAfectacion = await CalcularMontoMovimiento(
esCreada.IdPublicacion, esCreada.IdDistribuidor, esCreada.Fecha, esCreada.Cantidad, esCreada.TipoMovimiento,
esCreada.IdPrecio, esCreada.IdRecargo, esCreada.IdPorcentaje, distribuidor.IdZona);
// Si es Salida, montoAfectacion es positivo (aumenta deuda). Si es Entrada, es 0 por CalcularMontoMovimiento,
// pero para el saldo, una Entrada (devolución) debería restar del saldo deudor.
// Por lo tanto, el monto real a restar del saldo si es Entrada sería el valor de esos ejemplares devueltos.
if(esCreada.TipoMovimiento == "Entrada")
if (esCreada.TipoMovimiento == "Entrada")
{
// Recalcular como si fuera salida para obtener el valor a acreditar/restar del saldo
// Recalcular como si fuera salida para obtener el valor a acreditar/restar del saldo
montoAfectacion = await CalcularMontoMovimiento(
esCreada.IdPublicacion, esCreada.IdDistribuidor, esCreada.Fecha, esCreada.Cantidad, "Salida", // Forzar tipo Salida para cálculo de valor
esCreada.IdPrecio, esCreada.IdRecargo, esCreada.IdPorcentaje, distribuidor.IdZona);
@@ -259,12 +241,12 @@ namespace GestionIntegral.Api.Services.Distribucion
public async Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaDistDto updateDto, int idUsuario)
{
// La actualización de un movimiento que afecta saldos es compleja.
// Si cambia la cantidad, el monto original y el nuevo deben calcularse,
// y la diferencia debe aplicarse al saldo.
// Por ahora, este DTO solo permite cambiar Cantidad y Observacion.
// Cambiar otros campos como Fecha, Publicacion, Distribuidor implicaría recalcular todo
// y posiblemente anular el movimiento original y crear uno nuevo.
// La actualización de un movimiento que afecta saldos es compleja.
// Si cambia la cantidad, el monto original y el nuevo deben calcularse,
// y la diferencia debe aplicarse al saldo.
// Por ahora, este DTO solo permite cambiar Cantidad y Observacion.
// Cambiar otros campos como Fecha, Publicacion, Distribuidor implicaría recalcular todo
// y posiblemente anular el movimiento original y crear uno nuevo.
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
@@ -275,16 +257,16 @@ namespace GestionIntegral.Api.Services.Distribucion
if (esExistente == null) return (false, "Movimiento no encontrado.");
var publicacion = await _publicacionRepository.GetByIdSimpleAsync(esExistente.IdPublicacion);
if (publicacion == null) return (false, "Publicación asociada no encontrada."); // Muy raro
if (publicacion == null) return (false, "Publicación asociada no encontrada."); // Muy raro
var distribuidor = await _distribuidorRepository.GetByIdSimpleAsync(esExistente.IdDistribuidor);
if (distribuidor == null) return (false, "Distribuidor asociado no encontrado.");
if (distribuidor == null) return (false, "Distribuidor asociado no encontrado.");
// 1. Calcular monto del movimiento original (antes de la actualización)
decimal montoOriginal = await CalcularMontoMovimiento(
esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, esExistente.TipoMovimiento,
esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona);
if(esExistente.TipoMovimiento == "Entrada") montoOriginal *= -1; // Para revertir
if (esExistente.TipoMovimiento == "Entrada") montoOriginal *= -1; // Para revertir
// 2. Actualizar la entidad en la BD (esto también guarda en historial)
var esParaActualizar = new EntradaSalidaDist
@@ -308,7 +290,7 @@ namespace GestionIntegral.Api.Services.Distribucion
decimal montoNuevo = await CalcularMontoMovimiento(
esParaActualizar.IdPublicacion, esParaActualizar.IdDistribuidor, esParaActualizar.Fecha, esParaActualizar.Cantidad, esParaActualizar.TipoMovimiento,
esParaActualizar.IdPrecio, esParaActualizar.IdRecargo, esParaActualizar.IdPorcentaje, distribuidor.IdZona);
if(esParaActualizar.TipoMovimiento == "Entrada") montoNuevo *= -1;
if (esParaActualizar.TipoMovimiento == "Entrada") montoNuevo *= -1;
// 4. Ajustar saldo con la diferencia
@@ -351,16 +333,19 @@ namespace GestionIntegral.Api.Services.Distribucion
decimal montoReversion = await CalcularMontoMovimiento(
esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, esExistente.TipoMovimiento,
esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona);
// Si es Salida, el monto es positivo, al revertir restamos.
// Si es Entrada, el monto es 0 (por Calcular...), pero su efecto en saldo fue negativo, al revertir sumamos el valor.
if(esExistente.TipoMovimiento == "Salida") {
if (esExistente.TipoMovimiento == "Salida")
{
montoReversion *= -1; // Para restar del saldo lo que se sumó
} else if (esExistente.TipoMovimiento == "Entrada") {
}
else if (esExistente.TipoMovimiento == "Entrada")
{
// Recalcular el valor como si fuera salida para saber cuánto se restó del saldo
montoReversion = await CalcularMontoMovimiento(
esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, "Salida",
esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona);
montoReversion = await CalcularMontoMovimiento(
esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, "Salida",
esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona);
// No se multiplica por -1 aquí, porque el saldo ya lo tiene restado, al eliminar revertimos sumando.
}
@@ -385,5 +370,32 @@ namespace GestionIntegral.Api.Services.Distribucion
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<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();
}
}
}