Fase 3:
- Backend API: Autenticación y autorización básicas con JWT implementadas. Cambio de contraseña funcional. Módulo "Tipos de Pago" (CRUD completo) implementado en el backend (Controlador, Servicio, Repositorio) usando Dapper, transacciones y con lógica de historial. Se incluyen permisos en el token JWT. - Frontend React: Estructura base con Vite, TypeScript, MUI. Contexto de autenticación (AuthContext) que maneja el estado del usuario y el token. Página de Login. Modal de Cambio de Contraseña (forzado y opcional). Hook usePermissions para verificar permisos. Página GestionarTiposPagoPage con tabla, paginación, filtro, modal para crear/editar, y menú de acciones, respetando permisos. Layout principal (MainLayout) con navegación por Tabs (funcionalidad básica de navegación). Estructura de enrutamiento (AppRoutes) que maneja rutas públicas, protegidas y anidadas para módulos.
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
using GestionIntegral.Api.Models.Contables; // Para TipoPago y TipoPagoHistorico
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Data.Repositories
|
||||
{
|
||||
public interface ITipoPagoRepository
|
||||
{
|
||||
Task<IEnumerable<TipoPago>> GetAllAsync(string? nombreFilter);
|
||||
Task<TipoPago?> GetByIdAsync(int id);
|
||||
Task<TipoPago?> CreateAsync(TipoPago nuevoTipoPago, int idUsuario); // Devuelve el objeto creado o null si falla
|
||||
Task<bool> UpdateAsync(TipoPago tipoPagoAActualizar, int idUsuario);
|
||||
Task<bool> DeleteAsync(int id, int idUsuario); // Devuelve true si fue exitoso
|
||||
Task<bool> ExistsByNameAsync(string nombre, int? excludeId = null);
|
||||
Task<bool> IsInUseAsync(int id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
using Dapper;
|
||||
using GestionIntegral.Api.Models.Contables;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Data.Repositories // O GestionIntegral.Api.Repositories
|
||||
{
|
||||
public class TipoPagoRepository : ITipoPagoRepository
|
||||
{
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<TipoPagoRepository> _logger; // Para logging
|
||||
|
||||
public TipoPagoRepository(DbConnectionFactory connectionFactory, ILogger<TipoPagoRepository> logger)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TipoPago>> GetAllAsync(string? nombreFilter)
|
||||
{
|
||||
// Construcción segura de la cláusula WHERE
|
||||
var sqlBuilder = new System.Text.StringBuilder("SELECT Id_TipoPago AS IdTipoPago, Nombre, Detalle FROM dbo.cue_dtTipopago WHERE 1=1");
|
||||
var parameters = new DynamicParameters();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(nombreFilter))
|
||||
{
|
||||
sqlBuilder.Append(" AND Nombre LIKE @NombreFilter");
|
||||
parameters.Add("NombreFilter", $"%{nombreFilter}%");
|
||||
}
|
||||
sqlBuilder.Append(" ORDER BY Nombre;");
|
||||
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
return await connection.QueryAsync<TipoPago>(sqlBuilder.ToString(), parameters);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener todos los Tipos de Pago. Filtro: {NombreFilter}", nombreFilter);
|
||||
return Enumerable.Empty<TipoPago>(); // Devolver lista vacía en caso de error
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TipoPago?> GetByIdAsync(int id)
|
||||
{
|
||||
var sql = "SELECT Id_TipoPago AS IdTipoPago, Nombre, Detalle FROM dbo.cue_dtTipopago WHERE Id_TipoPago = @Id";
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
return await connection.QuerySingleOrDefaultAsync<TipoPago>(sql, new { Id = id });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener Tipo de Pago por ID: {IdTipoPago}", id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsByNameAsync(string nombre, int? excludeId = null)
|
||||
{
|
||||
var sqlBuilder = new System.Text.StringBuilder("SELECT COUNT(1) FROM dbo.cue_dtTipopago WHERE Nombre = @Nombre");
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("Nombre", nombre);
|
||||
|
||||
if (excludeId.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(" AND Id_TipoPago != @ExcludeId");
|
||||
parameters.Add("ExcludeId", excludeId.Value);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
var count = await connection.ExecuteScalarAsync<int>(sqlBuilder.ToString(), parameters);
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error en ExistsByNameAsync para Tipo de Pago con nombre: {Nombre}", nombre);
|
||||
return true; // Asumir que existe para prevenir duplicados si hay error de BD
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TipoPago?> CreateAsync(TipoPago nuevoTipoPago, int idUsuario)
|
||||
{
|
||||
var sqlInsertPrincipal = @"
|
||||
INSERT INTO dbo.cue_dtTipopago (Nombre, Detalle)
|
||||
VALUES (@Nombre, @Detalle);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);"; // Obtener el ID generado
|
||||
|
||||
var sqlInsertHistorico = @"
|
||||
INSERT INTO dbo.cue_dtTipopago_H (Id_TipoPago, Nombre, Detalle, Id_Usuario, FechaMod, TipoMod)
|
||||
VALUES (@IdTipoPago, @Nombre, @Detalle, @IdUsuario, @FechaMod, @TipoMod);";
|
||||
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
connection.Open();
|
||||
using (var transaction = connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Insertar en la tabla principal y obtener el ID
|
||||
int nuevoId = await connection.ExecuteScalarAsync<int>(
|
||||
sqlInsertPrincipal,
|
||||
new { nuevoTipoPago.Nombre, nuevoTipoPago.Detalle },
|
||||
transaction: transaction
|
||||
);
|
||||
|
||||
if (nuevoId > 0)
|
||||
{
|
||||
nuevoTipoPago.IdTipoPago = nuevoId; // Asignar el ID al objeto
|
||||
|
||||
// Insertar en la tabla de historial
|
||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||
{
|
||||
IdTipoPago = nuevoId, // Usar el ID obtenido
|
||||
nuevoTipoPago.Nombre,
|
||||
nuevoTipoPago.Detalle,
|
||||
IdUsuario = idUsuario,
|
||||
FechaMod = DateTime.Now,
|
||||
TipoMod = "Insertada"
|
||||
}, transaction: transaction);
|
||||
|
||||
transaction.Commit();
|
||||
return nuevoTipoPago; // Devolver el objeto completo con ID
|
||||
}
|
||||
else
|
||||
{
|
||||
transaction.Rollback();
|
||||
_logger.LogError("SCOPE_IDENTITY() devolvió 0 o menos después de insertar TipoPago. Nombre: {Nombre}", nuevoTipoPago.Nombre);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception exTrans)
|
||||
{
|
||||
transaction.Rollback();
|
||||
_logger.LogError(exTrans, "Error en transacción CreateAsync para TipoPago. Nombre: {Nombre}", nuevoTipoPago.Nombre);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error general en CreateAsync para TipoPago. Nombre: {Nombre}", nuevoTipoPago.Nombre);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(TipoPago tipoPagoAActualizar, int idUsuario)
|
||||
{
|
||||
// Primero, obtenemos el estado actual para el historial
|
||||
var tipoPagoActual = await GetByIdAsync(tipoPagoAActualizar.IdTipoPago);
|
||||
if (tipoPagoActual == null) return false; // No se encontró para actualizar
|
||||
|
||||
var sqlUpdate = @"
|
||||
UPDATE dbo.cue_dtTipopago
|
||||
SET Nombre = @Nombre, Detalle = @Detalle
|
||||
WHERE Id_TipoPago = @IdTipoPago;";
|
||||
|
||||
var sqlInsertHistorico = @"
|
||||
INSERT INTO dbo.cue_dtTipopago_H (Id_TipoPago, Nombre, Detalle, Id_Usuario, FechaMod, TipoMod)
|
||||
VALUES (@IdTipoPago, @NombreActual, @DetalleActual, @IdUsuario, @FechaMod, @TipoMod);";
|
||||
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
connection.Open();
|
||||
using (var transaction = connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Insertar en historial CON LOS VALORES ANTERIORES
|
||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||
{
|
||||
IdTipoPago = tipoPagoActual.IdTipoPago,
|
||||
NombreActual = tipoPagoActual.Nombre, // Valor antes del update
|
||||
DetalleActual = tipoPagoActual.Detalle, // Valor antes del update
|
||||
IdUsuario = idUsuario,
|
||||
FechaMod = DateTime.Now,
|
||||
TipoMod = "Modificada"
|
||||
}, transaction: transaction);
|
||||
|
||||
// Actualizar la tabla principal
|
||||
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new
|
||||
{
|
||||
tipoPagoAActualizar.Nombre,
|
||||
tipoPagoAActualizar.Detalle,
|
||||
tipoPagoAActualizar.IdTipoPago
|
||||
}, transaction: transaction);
|
||||
|
||||
transaction.Commit();
|
||||
return rowsAffected == 1;
|
||||
}
|
||||
catch (Exception exTrans)
|
||||
{
|
||||
transaction.Rollback();
|
||||
_logger.LogError(exTrans, "Error en transacción UpdateAsync para TipoPago ID: {IdTipoPago}", tipoPagoAActualizar.IdTipoPago);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error general en UpdateAsync para TipoPago ID: {IdTipoPago}", tipoPagoAActualizar.IdTipoPago);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(int id, int idUsuario)
|
||||
{
|
||||
var tipoPagoAEliminar = await GetByIdAsync(id);
|
||||
if (tipoPagoAEliminar == null) return false; // No existe
|
||||
|
||||
var sqlDelete = "DELETE FROM dbo.cue_dtTipopago WHERE Id_TipoPago = @Id";
|
||||
var sqlInsertHistorico = @"
|
||||
INSERT INTO dbo.cue_dtTipopago_H (Id_TipoPago, Nombre, Detalle, Id_Usuario, FechaMod, TipoMod)
|
||||
VALUES (@IdTipoPago, @Nombre, @Detalle, @IdUsuario, @FechaMod, @TipoMod);";
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
connection.Open();
|
||||
using (var transaction = connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Insertar en historial ANTES de eliminar
|
||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||
{
|
||||
IdTipoPago = tipoPagoAEliminar.IdTipoPago,
|
||||
tipoPagoAEliminar.Nombre,
|
||||
tipoPagoAEliminar.Detalle,
|
||||
IdUsuario = idUsuario,
|
||||
FechaMod = DateTime.Now,
|
||||
TipoMod = "Eliminada"
|
||||
}, transaction: transaction);
|
||||
|
||||
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { Id = id }, transaction: transaction);
|
||||
transaction.Commit();
|
||||
return rowsAffected == 1;
|
||||
}
|
||||
catch (Exception exTrans)
|
||||
{
|
||||
transaction.Rollback();
|
||||
_logger.LogError(exTrans, "Error en transacción DeleteAsync para TipoPago ID: {IdTipoPago}", id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error general en DeleteAsync para TipoPago ID: {IdTipoPago}", id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> IsInUseAsync(int id)
|
||||
{
|
||||
var sql = "SELECT COUNT(1) FROM dbo.cue_PagosDistribuidor WHERE Id_TipoPago = @IdTipoPago";
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
var count = await connection.ExecuteScalarAsync<int>(sql, new { IdTipoPago = id });
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error en IsInUseAsync para TipoPago ID: {IdTipoPago}", id);
|
||||
return true; // Asumir que está en uso si hay error para prevenir borrado incorrecto
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user