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:
@@ -1,6 +1,8 @@
|
||||
using GestionIntegral.Api.Dtos;
|
||||
using GestionIntegral.Api.Services;
|
||||
using Microsoft.AspNetCore.Authorization; // Para [Authorize]
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims; // Para leer claims del token
|
||||
|
||||
namespace GestionIntegral.Api.Controllers
|
||||
{
|
||||
@@ -50,7 +52,49 @@ namespace GestionIntegral.Api.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Añadir endpoint para cambiar clave [HttpPost("change-password")]
|
||||
// Probablemente requerirá [Authorize] para que solo usuarios logueados puedan usarlo.
|
||||
[HttpPost("change-password")]
|
||||
[Authorize] // <-- Solo usuarios autenticados pueden cambiar su clave
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)] // Éxito sin contenido
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)] // Si el token es inválido
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)] // Si el usuario del token no existe
|
||||
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequestDto changePasswordRequest)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
// El [ApiController] y el [FromBody] ya validan DTOs (Required, StringLength, Compare)
|
||||
// y devuelven 400 automáticamente si falla.
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
// Obtener el ID del usuario desde el token JWT
|
||||
var userIdString = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"); // "sub" es el claim estándar para ID
|
||||
if (!int.TryParse(userIdString, out int userId))
|
||||
{
|
||||
_logger.LogWarning("ChangePassword failed: Could not parse UserId from token.");
|
||||
// Esto no debería pasar si el token es válido y generado por nosotros
|
||||
return Unauthorized(new { message = "Token de usuario inválido." });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var success = await _authService.ChangePasswordAsync(userId, changePasswordRequest);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// AuthService ya loggeó la razón específica (usuario no encontrado, clave actual inválida, etc.)
|
||||
// Devolvemos un BadRequest genérico para no dar pistas
|
||||
return BadRequest(new { message = "No se pudo cambiar la contraseña. Verifique la contraseña actual." });
|
||||
}
|
||||
|
||||
// Éxito
|
||||
return NoContent(); // Código 204: Éxito, sin contenido que devolver
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during password change for user ID {UserId}", userId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Ocurrió un error interno al cambiar la contraseña." });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using GestionIntegral.Api.Dtos.Contables; // Para los DTOs
|
||||
using GestionIntegral.Api.Services.Contables; // Para ITipoPagoService
|
||||
using Microsoft.AspNetCore.Authorization; // Para [Authorize]
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims; // Para obtener el IdUsuario del token
|
||||
|
||||
namespace GestionIntegral.Api.Controllers.Contables
|
||||
{
|
||||
[Route("api/[controller]")] // Ruta base: /api/tipospago
|
||||
[ApiController]
|
||||
[Authorize] // <-- Proteger todos los endpoints de este controlador por defecto
|
||||
public class TiposPagoController : ControllerBase
|
||||
{
|
||||
private readonly ITipoPagoService _tipoPagoService;
|
||||
private readonly ILogger<TiposPagoController> _logger;
|
||||
|
||||
public TiposPagoController(ITipoPagoService tipoPagoService, ILogger<TiposPagoController> logger)
|
||||
{
|
||||
_tipoPagoService = tipoPagoService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// GET: api/tipospago
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<TipoPagoDto>), StatusCodes.Status200OK)]
|
||||
// Podrías añadir un permiso específico si lo tienes, ej: [Authorize(Policy = "VerTiposPago")]
|
||||
// Por ahora, [Authorize] a nivel de controlador es suficiente si todos los métodos lo requieren.
|
||||
public async Task<IActionResult> GetAllTiposPago([FromQuery] string? nombre) // Filtro opcional por nombre
|
||||
{
|
||||
try
|
||||
{
|
||||
var tiposPago = await _tipoPagoService.ObtenerTodosAsync(nombre);
|
||||
return Ok(tiposPago);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener todos los Tipos de Pago.");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al procesar la solicitud.");
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/tipospago/{id}
|
||||
[HttpGet("{id:int}", Name = "GetTipoPagoById")] // Darle un nombre a la ruta para CreatedAtRoute
|
||||
[ProducesResponseType(typeof(TipoPagoDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetTipoPagoById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tipoPago = await _tipoPagoService.ObtenerPorIdAsync(id);
|
||||
if (tipoPago == null)
|
||||
{
|
||||
return NotFound(new { message = $"Tipo de pago con ID {id} no encontrado." });
|
||||
}
|
||||
return Ok(tipoPago);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener Tipo de Pago por ID: {Id}", id);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al procesar la solicitud.");
|
||||
}
|
||||
}
|
||||
|
||||
// POST: api/tipospago
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(TipoPagoDto), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
// Aquí podrías requerir un permiso más específico: [Authorize(Policy = "CrearTiposPago")]
|
||||
public async Task<IActionResult> CreateTipoPago([FromBody] CreateTipoPagoDto createDto)
|
||||
{
|
||||
if (!ModelState.IsValid) // Validaciones del DTO (Required, StringLength)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var idUsuario = GetCurrentUserId();
|
||||
if (idUsuario == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var (tipoPagoCreado, error) = await _tipoPagoService.CrearAsync(createDto, idUsuario.Value);
|
||||
|
||||
if (error != null)
|
||||
{
|
||||
// Error de lógica de negocio (ej: nombre duplicado)
|
||||
return BadRequest(new { message = error });
|
||||
}
|
||||
if (tipoPagoCreado == null) // Fallo inesperado en el repositorio/servicio
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear el tipo de pago.");
|
||||
}
|
||||
|
||||
// Devuelve 201 Created con la ubicación del nuevo recurso y el recurso mismo
|
||||
return CreatedAtRoute("GetTipoPagoById", new { id = tipoPagoCreado.IdTipoPago }, tipoPagoCreado);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al crear Tipo de Pago. Nombre: {Nombre}", createDto.Nombre);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al procesar la solicitud.");
|
||||
}
|
||||
}
|
||||
|
||||
// PUT: api/tipospago/{id}
|
||||
[HttpPut("{id:int}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)] // Éxito sin contenido
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> UpdateTipoPago(int id, [FromBody] UpdateTipoPagoDto updateDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var idUsuario = GetCurrentUserId();
|
||||
if (idUsuario == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
|
||||
|
||||
try
|
||||
{
|
||||
var (exito, error) = await _tipoPagoService.ActualizarAsync(id, updateDto, idUsuario.Value);
|
||||
|
||||
if (!exito)
|
||||
{
|
||||
if (error == "Tipo de pago no encontrado.") return NotFound(new { message = error });
|
||||
return BadRequest(new { message = error }); // Otro error de lógica de negocio
|
||||
}
|
||||
return NoContent(); // Éxito
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al actualizar Tipo de Pago ID: {Id}", id);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al procesar la solicitud.");
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE: api/tipospago/{id}
|
||||
[HttpDelete("{id:int}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)] // Si está en uso
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> DeleteTipoPago(int id)
|
||||
{
|
||||
var idUsuario = GetCurrentUserId();
|
||||
if (idUsuario == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
|
||||
|
||||
try
|
||||
{
|
||||
var (exito, error) = await _tipoPagoService.EliminarAsync(id, idUsuario.Value);
|
||||
|
||||
if (!exito)
|
||||
{
|
||||
if (error == "Tipo de pago no encontrado.") return NotFound(new { message = error });
|
||||
return BadRequest(new { message = error }); // Ej: "En uso"
|
||||
}
|
||||
return NoContent(); // Éxito
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al eliminar Tipo de Pago ID: {Id}", id);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al procesar la solicitud.");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper para obtener el ID del usuario del token
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub");
|
||||
if (int.TryParse(userIdClaim, out int userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
_logger.LogWarning("No se pudo obtener el UserId del token JWT.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,21 @@ namespace GestionIntegral.Api.Data
|
||||
public class AuthRepository : IAuthRepository
|
||||
{
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<AuthRepository> _logger;
|
||||
|
||||
public AuthRepository(DbConnectionFactory connectionFactory)
|
||||
public AuthRepository(DbConnectionFactory connectionFactory, ILogger<AuthRepository> logger)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
|
||||
}
|
||||
|
||||
public async Task<Usuario?> GetUserByUsernameAsync(string username)
|
||||
{
|
||||
var sql = @"SELECT Id, [User], ClaveHash, ClaveSalt, Habilitada,
|
||||
SupAdmin, Nombre, Apellido, IdPerfil, VerLog, DebeCambiarClave
|
||||
FROM gral_Usuarios
|
||||
WHERE [User] = @Username";
|
||||
SupAdmin, Nombre, Apellido, IdPerfil, VerLog, DebeCambiarClave
|
||||
FROM gral_Usuarios
|
||||
WHERE [User] = @Username";
|
||||
|
||||
try
|
||||
{
|
||||
@@ -30,13 +33,81 @@ namespace GestionIntegral.Api.Data
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Loggear el error ex.Message
|
||||
Console.WriteLine($"Error fetching user: {ex.Message}");
|
||||
Console.WriteLine($"Error fetching user by username: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implementar métodos para cambiar clave (UPDATE seguro con parámetros)
|
||||
// y para crear usuario (INSERT seguro con parámetros, usando el hasher)
|
||||
public async Task<Usuario?> GetUserByIdAsync(int userId)
|
||||
{
|
||||
var sql = @"SELECT Id, [User], ClaveHash, ClaveSalt, Habilitada,
|
||||
SupAdmin, Nombre, Apellido, IdPerfil, VerLog, DebeCambiarClave
|
||||
FROM gral_Usuarios
|
||||
WHERE Id = @UserId";
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
var user = await connection.QuerySingleOrDefaultAsync<Usuario>(sql, new { UserId = userId });
|
||||
Console.WriteLine($"Repo - User {user?.Id} - DebeCambiarClave leído de BD: {user?.DebeCambiarClave}");
|
||||
return user;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error fetching user by ID: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdatePasswordAsync(int userId, string newHash, string newSalt)
|
||||
{
|
||||
// Actualiza hash, salt y pone DebeCambiarClave a 0 (false)
|
||||
var sql = @"UPDATE dbo.gral_Usuarios
|
||||
SET ClaveHash = @HashedPassword, ClaveSalt = @Salt, DebeCambiarClave = 0
|
||||
WHERE Id = @UserId";
|
||||
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
var parameters = new
|
||||
{
|
||||
HashedPassword = newHash,
|
||||
Salt = newSalt,
|
||||
UserId = userId
|
||||
};
|
||||
int rowsAffected = await connection.ExecuteAsync(sql, parameters);
|
||||
return rowsAffected == 1; // Devuelve true si se actualizó exactamente una fila
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error updating password for user {userId}: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public async Task<IEnumerable<string>> GetPermisosCodAccByPerfilIdAsync(int idPerfil)
|
||||
{
|
||||
// Esta consulta es similar a la que tenías en gestion.vb -> verPermisosPerfil
|
||||
var sql = @"
|
||||
SELECT p.codAcc
|
||||
FROM dbo.gral_Perfiles pf
|
||||
INNER JOIN dbo.gral_PermisosPerfiles pp ON pf.id = pp.idPerfil
|
||||
INNER JOIN dbo.gral_Permisos p ON pp.idPermiso = p.id
|
||||
WHERE pf.id = @IdPerfil;";
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
return await connection.QueryAsync<string>(sql, new { IdPerfil = idPerfil });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener códigos de acceso para el perfil ID: {IdPerfil}", idPerfil); // Asumiendo que tienes _logger en AuthRepository
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ namespace GestionIntegral.Api.Data
|
||||
public interface IAuthRepository
|
||||
{
|
||||
Task<Usuario?> GetUserByUsernameAsync(string username);
|
||||
// Añadiremos métodos para cambiar clave, etc., más adelante
|
||||
Task<bool> UpdatePasswordAsync(int userId, string newHash, string newSalt);
|
||||
Task<Usuario?> GetUserByIdAsync(int userId); // Método útil para cambio de clav
|
||||
Task<IEnumerable<string>> GetPermisosCodAccByPerfilIdAsync(int idPerfil);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Backend/GestionIntegral.Api/Models/Contables/TipoPago.cs
Normal file
9
Backend/GestionIntegral.Api/Models/Contables/TipoPago.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace GestionIntegral.Api.Models.Contables
|
||||
{
|
||||
public class TipoPago
|
||||
{
|
||||
public int IdTipoPago { get; set; } // Coincide con la PK de cue_dtTipopago
|
||||
public string Nombre { get; set; } = string.Empty;
|
||||
public string? Detalle { get; set; } // Permite nulo
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace GestionIntegral.Api.Models.Contables
|
||||
{
|
||||
public class TipoPagoHistorico
|
||||
{
|
||||
public int IdTipoPago { get; set; } // Este NO es IDENTITY
|
||||
public string Nombre { get; set; } = string.Empty;
|
||||
public string? Detalle { get; set; }
|
||||
public int IdUsuario { get; set; }
|
||||
public DateTime FechaMod { get; set; }
|
||||
public string TipoMod { get; set; } = string.Empty; // "Insertada", "Modificada", "Eliminada"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace GestionIntegral.Api.Dtos
|
||||
{
|
||||
public class ChangePasswordRequestDto
|
||||
{
|
||||
[Required]
|
||||
public string CurrentPassword { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(50, MinimumLength = 6)] // Validaciones
|
||||
public string NewPassword { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[Compare("NewPassword", ErrorMessage = "La nueva contraseña y la confirmación no coinciden.")] // Validación de confirmación
|
||||
public string ConfirmNewPassword { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace GestionIntegral.Api.Dtos.Contables
|
||||
{
|
||||
public class CreateTipoPagoDto
|
||||
{
|
||||
[Required(ErrorMessage = "El nombre del tipo de pago es obligatorio.")]
|
||||
[StringLength(50, ErrorMessage = "El nombre no puede exceder los 50 caracteres.")]
|
||||
public string Nombre { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(150, ErrorMessage = "El detalle no puede exceder los 150 caracteres.")]
|
||||
public string? Detalle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GestionIntegral.Api.Dtos.Contables
|
||||
{
|
||||
public class TipoPagoDto
|
||||
{
|
||||
public int IdTipoPago { get; set; }
|
||||
public string Nombre { get; set; } = string.Empty;
|
||||
public string? Detalle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace GestionIntegral.Api.Dtos.Contables
|
||||
{
|
||||
public class UpdateTipoPagoDto
|
||||
{
|
||||
[Required(ErrorMessage = "El nombre del tipo de pago es obligatorio.")]
|
||||
[StringLength(50, ErrorMessage = "El nombre no puede exceder los 50 caracteres.")]
|
||||
public string Nombre { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(150, ErrorMessage = "El detalle no puede exceder los 150 caracteres.")]
|
||||
public string? Detalle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,18 @@ using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Services;
|
||||
using GestionIntegral.Api.Services.Contables;
|
||||
using GestionIntegral.Api.Data.Repositories;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// --- Registros de Servicios ---
|
||||
builder.Services.AddSingleton<DbConnectionFactory>();
|
||||
builder.Services.AddScoped<PasswordHasherService>();
|
||||
builder.Services.AddScoped<IAuthRepository, AuthRepository>(); // Asegúrate que el namespace sea correcto
|
||||
builder.Services.AddScoped<IAuthRepository, AuthRepository>();
|
||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||
builder.Services.AddScoped<ITipoPagoRepository, TipoPagoRepository>();
|
||||
builder.Services.AddScoped<ITipoPagoService, TipoPagoService>();
|
||||
|
||||
// --- Configuración de Autenticación JWT ---
|
||||
var jwtSettings = builder.Configuration.GetSection("Jwt");
|
||||
|
||||
@@ -34,34 +34,35 @@ namespace GestionIntegral.Api.Services
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("Login attempt failed: User {Username} not found.", loginRequest.Username);
|
||||
return null;
|
||||
_logger.LogWarning("Login attempt failed: User {Username} not found.", loginRequest.Username);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!user.Habilitada)
|
||||
{
|
||||
_logger.LogWarning("Login attempt failed: User {Username} is disabled.", loginRequest.Username);
|
||||
return null;
|
||||
_logger.LogWarning("Login attempt failed: User {Username} is disabled.", loginRequest.Username);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verificar contraseña usando el hash y salt de la BD
|
||||
bool isPasswordValid = _passwordHasher.VerifyPassword(loginRequest.Password, user.ClaveHash, user.ClaveSalt);
|
||||
|
||||
if (!isPasswordValid)
|
||||
{
|
||||
_logger.LogWarning("Login attempt failed: Invalid password for user {Username}.", loginRequest.Username);
|
||||
return null;
|
||||
_logger.LogWarning("Login attempt failed: Invalid password for user {Username}.", loginRequest.Username);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generar Token JWT
|
||||
var token = GenerateJwtToken(user);
|
||||
// --- OBTENER PERMISOS ---
|
||||
IEnumerable<string> permisosDelUsuario = new List<string>();
|
||||
if (!user.SupAdmin && user.IdPerfil > 0) // Solo si no es SuperAdmin y tiene un perfil válido
|
||||
{
|
||||
permisosDelUsuario = await _authRepository.GetPermisosCodAccByPerfilIdAsync(user.IdPerfil);
|
||||
}
|
||||
// --- FIN OBTENER PERMISOS ---
|
||||
|
||||
// Determinar si debe cambiar clave (leyendo de la BD via el repo/modelo)
|
||||
var token = GenerateJwtToken(user, permisosDelUsuario); // Pasar permisos
|
||||
bool debeCambiar = user.DebeCambiarClave;
|
||||
|
||||
_logger.LogInformation("User {Username} logged in successfully.", loginRequest.Username);
|
||||
_logger.LogInformation("User {Username} logged in successfully.", loginRequest.Username);
|
||||
|
||||
// Crear y devolver la respuesta
|
||||
return new LoginResponseDto
|
||||
{
|
||||
Token = token,
|
||||
@@ -70,30 +71,96 @@ namespace GestionIntegral.Api.Services
|
||||
NombreCompleto = $"{user.Nombre} {user.Apellido}",
|
||||
EsSuperAdmin = user.SupAdmin,
|
||||
DebeCambiarClave = debeCambiar
|
||||
// No necesitamos pasar la lista de permisos aquí, ya irán en el token
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> ChangePasswordAsync(int userId, ChangePasswordRequestDto changePasswordRequest)
|
||||
{
|
||||
// 1. Obtener el usuario actual (necesitamos su hash/salt actual)
|
||||
var user = await _authRepository.GetUserByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("ChangePassword attempt failed: User with ID {UserId} not found.", userId);
|
||||
return false; // Usuario no encontrado
|
||||
}
|
||||
|
||||
// 2. Verificar la contraseña ACTUAL
|
||||
if (!_passwordHasher.VerifyPassword(changePasswordRequest.CurrentPassword, user.ClaveHash, user.ClaveSalt))
|
||||
{
|
||||
_logger.LogWarning("ChangePassword attempt failed: Invalid current password for user ID {UserId}.", userId);
|
||||
return false; // Contraseña actual incorrecta
|
||||
}
|
||||
|
||||
// 3. Verificar que la nueva contraseña no sea igual al nombre de usuario
|
||||
if (user.User == changePasswordRequest.NewPassword)
|
||||
{
|
||||
_logger.LogWarning("ChangePassword attempt failed: New password cannot be the same as username for user ID {UserId}.", userId);
|
||||
// Podrías lanzar una excepción o devolver un código de error específico
|
||||
// Por simplicidad, devolvemos false
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Generar nuevo hash y salt para la NUEVA contraseña
|
||||
(string newHash, string newSalt) = _passwordHasher.HashPassword(changePasswordRequest.NewPassword);
|
||||
|
||||
// 5. Actualizar en la base de datos (el repositorio también pone DebeCambiarClave = 0)
|
||||
bool success = await _authRepository.UpdatePasswordAsync(userId, newHash, newSalt);
|
||||
|
||||
if (success)
|
||||
{
|
||||
_logger.LogInformation("Password changed successfully for user ID {UserId}.", userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("Failed to update password in database for user ID {UserId}.", userId);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// --- GenerateJwtToken sin cambios ---
|
||||
private string GenerateJwtToken(Usuario user)
|
||||
private string GenerateJwtToken(Usuario user, IEnumerable<string> permisosCodAcc) // Recibir permisos
|
||||
{
|
||||
var jwtSettings = _configuration.GetSection("Jwt");
|
||||
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"]
|
||||
?? throw new ArgumentNullException("Jwt:Key", "JWT Key not configured"));
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Name, user.User),
|
||||
new Claim(JwtRegisteredClaimNames.GivenName, user.Nombre),
|
||||
new Claim(JwtRegisteredClaimNames.FamilyName, user.Apellido),
|
||||
new Claim("idPerfil", user.IdPerfil.ToString()),
|
||||
new Claim("debeCambiarClave", user.DebeCambiarClave.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||
};
|
||||
|
||||
// Añadir rol basado en SupAdmin o IdPerfil
|
||||
if (user.SupAdmin)
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, "SuperAdmin"));
|
||||
// Opcional: Si SuperAdmin tiene todos los permisos, podrías añadir un claim especial
|
||||
// claims.Add(new Claim("permission", "*"));
|
||||
}
|
||||
else
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, $"Perfil_{user.IdPerfil}"));
|
||||
// Añadir cada código de permiso como un claim "permission"
|
||||
if (permisosCodAcc != null)
|
||||
{
|
||||
foreach (var codAcc in permisosCodAcc)
|
||||
{
|
||||
claims.Add(new Claim("permission", codAcc)); // Usar "permission" como tipo de claim
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Name, user.User),
|
||||
new Claim(JwtRegisteredClaimNames.GivenName, user.Nombre),
|
||||
new Claim(JwtRegisteredClaimNames.FamilyName, user.Apellido),
|
||||
new Claim(ClaimTypes.Role, user.SupAdmin ? "SuperAdmin" : $"Perfil_{user.IdPerfil}"),
|
||||
new Claim("idPerfil", user.IdPerfil.ToString()), // Añadir IdPerfil como claim explícito
|
||||
new Claim("debeCambiarClave", user.DebeCambiarClave.ToString()), // Añadir flag como claim
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||
}),
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Expires = DateTime.UtcNow.AddHours(Convert.ToInt32(jwtSettings["DurationInHours"] ?? "1")),
|
||||
Issuer = jwtSettings["Issuer"],
|
||||
Audience = jwtSettings["Audience"],
|
||||
@@ -103,7 +170,5 @@ namespace GestionIntegral.Api.Services
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
return tokenHandler.WriteToken(token);
|
||||
}
|
||||
|
||||
// TODO: Implementar ChangePasswordAsync
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using GestionIntegral.Api.Dtos.Contables;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Contables
|
||||
{
|
||||
public interface ITipoPagoService
|
||||
{
|
||||
Task<IEnumerable<TipoPagoDto>> ObtenerTodosAsync(string? nombreFilter);
|
||||
Task<TipoPagoDto?> ObtenerPorIdAsync(int id);
|
||||
Task<(TipoPagoDto? TipoPago, string? Error)> CrearAsync(CreateTipoPagoDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateTipoPagoDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using GestionIntegral.Api.Data.Repositories; // Para ITipoPagoRepository
|
||||
using GestionIntegral.Api.Dtos.Contables;
|
||||
using GestionIntegral.Api.Models.Contables; // Para TipoPago
|
||||
using GestionIntegral.Api.Services.Contables;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Contables
|
||||
{
|
||||
public class TipoPagoService : ITipoPagoService
|
||||
{
|
||||
private readonly ITipoPagoRepository _tipoPagoRepository;
|
||||
private readonly ILogger<TipoPagoService> _logger;
|
||||
|
||||
public TipoPagoService(ITipoPagoRepository tipoPagoRepository, ILogger<TipoPagoService> logger)
|
||||
{
|
||||
_tipoPagoRepository = tipoPagoRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TipoPagoDto>> ObtenerTodosAsync(string? nombreFilter)
|
||||
{
|
||||
var tiposPago = await _tipoPagoRepository.GetAllAsync(nombreFilter);
|
||||
// Mapeo de Entidad a DTO
|
||||
return tiposPago.Select(tp => new TipoPagoDto
|
||||
{
|
||||
IdTipoPago = tp.IdTipoPago,
|
||||
Nombre = tp.Nombre,
|
||||
Detalle = tp.Detalle
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<TipoPagoDto?> ObtenerPorIdAsync(int id)
|
||||
{
|
||||
var tipoPago = await _tipoPagoRepository.GetByIdAsync(id);
|
||||
if (tipoPago == null) return null;
|
||||
|
||||
// Mapeo de Entidad a DTO
|
||||
return new TipoPagoDto
|
||||
{
|
||||
IdTipoPago = tipoPago.IdTipoPago,
|
||||
Nombre = tipoPago.Nombre,
|
||||
Detalle = tipoPago.Detalle
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<(TipoPagoDto? TipoPago, string? Error)> CrearAsync(CreateTipoPagoDto createDto, int idUsuario)
|
||||
{
|
||||
// Validación: Nombre no puede estar duplicado
|
||||
if (await _tipoPagoRepository.ExistsByNameAsync(createDto.Nombre))
|
||||
{
|
||||
return (null, "El nombre del tipo de pago ya existe.");
|
||||
}
|
||||
|
||||
var nuevoTipoPago = new TipoPago
|
||||
{
|
||||
Nombre = createDto.Nombre,
|
||||
Detalle = createDto.Detalle
|
||||
};
|
||||
|
||||
var tipoPagoCreado = await _tipoPagoRepository.CreateAsync(nuevoTipoPago, idUsuario);
|
||||
|
||||
if (tipoPagoCreado == null)
|
||||
{
|
||||
_logger.LogError("Falló la creación del Tipo de Pago en el repositorio para el nombre: {Nombre}", createDto.Nombre);
|
||||
return (null, "Error al crear el tipo de pago en la base de datos.");
|
||||
}
|
||||
|
||||
// Mapeo de Entidad creada (con ID) a DTO
|
||||
var tipoPagoDto = new TipoPagoDto
|
||||
{
|
||||
IdTipoPago = tipoPagoCreado.IdTipoPago,
|
||||
Nombre = tipoPagoCreado.Nombre,
|
||||
Detalle = tipoPagoCreado.Detalle
|
||||
};
|
||||
|
||||
return (tipoPagoDto, null); // Éxito
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateTipoPagoDto updateDto, int idUsuario)
|
||||
{
|
||||
// Verificar si el tipo de pago existe
|
||||
var tipoPagoExistente = await _tipoPagoRepository.GetByIdAsync(id);
|
||||
if (tipoPagoExistente == null)
|
||||
{
|
||||
return (false, "Tipo de pago no encontrado.");
|
||||
}
|
||||
|
||||
// Validación: Nombre no puede estar duplicado (excluyendo el ID actual)
|
||||
if (await _tipoPagoRepository.ExistsByNameAsync(updateDto.Nombre, id))
|
||||
{
|
||||
return (false, "El nombre del tipo de pago ya existe para otro registro.");
|
||||
}
|
||||
|
||||
// Mapeo de DTO a Entidad para actualizar
|
||||
tipoPagoExistente.Nombre = updateDto.Nombre;
|
||||
tipoPagoExistente.Detalle = updateDto.Detalle;
|
||||
|
||||
var actualizado = await _tipoPagoRepository.UpdateAsync(tipoPagoExistente, idUsuario);
|
||||
|
||||
if (!actualizado)
|
||||
{
|
||||
_logger.LogError("Falló la actualización del Tipo de Pago en el repositorio para el ID: {Id}", id);
|
||||
return (false, "Error al actualizar el tipo de pago en la base de datos.");
|
||||
}
|
||||
return (true, null); // Éxito
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
|
||||
{
|
||||
// Verificar si el tipo de pago existe
|
||||
var tipoPagoExistente = await _tipoPagoRepository.GetByIdAsync(id);
|
||||
if (tipoPagoExistente == null)
|
||||
{
|
||||
return (false, "Tipo de pago no encontrado.");
|
||||
}
|
||||
|
||||
// Validación: No se puede eliminar si está en uso
|
||||
if (await _tipoPagoRepository.IsInUseAsync(id))
|
||||
{
|
||||
return (false, "No se puede eliminar. El tipo de pago está siendo utilizado en pagos registrados.");
|
||||
}
|
||||
|
||||
var eliminado = await _tipoPagoRepository.DeleteAsync(id, idUsuario);
|
||||
if (!eliminado)
|
||||
{
|
||||
_logger.LogError("Falló la eliminación del Tipo de Pago en el repositorio para el ID: {Id}", id);
|
||||
return (false, "Error al eliminar el tipo de pago de la base de datos.");
|
||||
}
|
||||
return (true, null); // Éxito
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,6 @@ namespace GestionIntegral.Api.Services
|
||||
public interface IAuthService
|
||||
{
|
||||
Task<LoginResponseDto?> LoginAsync(LoginRequestDto loginRequest);
|
||||
// Añadiremos cambio de clave, etc.
|
||||
Task<bool> ChangePasswordAsync(int userId, ChangePasswordRequestDto changePasswordRequest);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9b1de95404118dad24e3e848866c48fce6e0c08e")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+da7b544372d72fd6e4a82a3d95626e9cd273f4a4")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
Reference in New Issue
Block a user