Ya perdí el hilo de los cambios pero ahi van.

This commit is contained in:
2025-05-23 15:47:39 -03:00
parent e7e185a9cb
commit 3c1fe15b1f
141 changed files with 9764 additions and 190 deletions

View File

@@ -0,0 +1,130 @@
using GestionIntegral.Api.Dtos.Contables;
using GestionIntegral.Api.Services.Contables;
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.Contables
{
[Route("api/notascreditodebito")] // Ruta base
[ApiController]
[Authorize]
public class NotasCreditoDebitoController : ControllerBase
{
private readonly INotaCreditoDebitoService _notaService;
private readonly ILogger<NotasCreditoDebitoController> _logger;
// Permisos para Notas C/D (CN001 a CN004)
private const string PermisoVer = "CN001";
private const string PermisoCrear = "CN002";
private const string PermisoModificar = "CN003";
private const string PermisoEliminar = "CN004";
public NotasCreditoDebitoController(INotaCreditoDebitoService notaService, ILogger<NotasCreditoDebitoController> logger)
{
_notaService = notaService;
_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 NotasCreditoDebitoController.");
return null;
}
// GET: api/notascreditodebito
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<NotaCreditoDebitoDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAll(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] string? destino, [FromQuery] int? idDestino,
[FromQuery] int? idEmpresa, [FromQuery] string? tipoNota)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var notas = await _notaService.ObtenerTodosAsync(fechaDesde, fechaHasta, destino, idDestino, idEmpresa, tipoNota);
return Ok(notas);
}
// GET: api/notascreditodebito/{idNota}
[HttpGet("{idNota:int}", Name = "GetNotaCreditoDebitoById")]
[ProducesResponseType(typeof(NotaCreditoDebitoDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int idNota)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var nota = await _notaService.ObtenerPorIdAsync(idNota);
if (nota == null) return NotFound(new { message = $"Nota con ID {idNota} no encontrada." });
return Ok(nota);
}
// POST: api/notascreditodebito
[HttpPost]
[ProducesResponseType(typeof(NotaCreditoDebitoDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Create([FromBody] CreateNotaDto createDto)
{
if (!TienePermiso(PermisoCrear)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dto, error) = await _notaService.CrearAsync(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al registrar la nota.");
return CreatedAtRoute("GetNotaCreditoDebitoById", new { idNota = dto.IdNota }, dto);
}
// PUT: api/notascreditodebito/{idNota}
[HttpPut("{idNota:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(int idNota, [FromBody] UpdateNotaDto updateDto)
{
if (!TienePermiso(PermisoModificar)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _notaService.ActualizarAsync(idNota, updateDto, userId.Value);
if (!exito)
{
if (error == "Nota no encontrada.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// DELETE: api/notascreditodebito/{idNota}
[HttpDelete("{idNota:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(int idNota)
{
if (!TienePermiso(PermisoEliminar)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _notaService.EliminarAsync(idNota, userId.Value);
if (!exito)
{
if (error == "Nota no encontrada.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
}
}

View File

@@ -0,0 +1,130 @@
using GestionIntegral.Api.Dtos.Contables;
using GestionIntegral.Api.Services.Contables;
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.Contables
{
[Route("api/pagosdistribuidor")] // Ruta base
[ApiController]
[Authorize]
public class PagosDistribuidorController : ControllerBase
{
private readonly IPagoDistribuidorService _pagoService;
private readonly ILogger<PagosDistribuidorController> _logger;
// Permisos para Pagos Distribuidores (CP001 a CP004)
private const string PermisoVer = "CP001";
private const string PermisoCrear = "CP002";
private const string PermisoModificar = "CP003";
private const string PermisoEliminar = "CP004";
public PagosDistribuidorController(IPagoDistribuidorService pagoService, ILogger<PagosDistribuidorController> logger)
{
_pagoService = pagoService;
_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 PagosDistribuidorController.");
return null;
}
// GET: api/pagosdistribuidor
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<PagoDistribuidorDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAll(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idDistribuidor, [FromQuery] int? idEmpresa,
[FromQuery] string? tipoMovimiento)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var pagos = await _pagoService.ObtenerTodosAsync(fechaDesde, fechaHasta, idDistribuidor, idEmpresa, tipoMovimiento);
return Ok(pagos);
}
// GET: api/pagosdistribuidor/{idPago}
[HttpGet("{idPago:int}", Name = "GetPagoDistribuidorById")]
[ProducesResponseType(typeof(PagoDistribuidorDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int idPago)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var pago = await _pagoService.ObtenerPorIdAsync(idPago);
if (pago == null) return NotFound(new { message = $"Pago con ID {idPago} no encontrado." });
return Ok(pago);
}
// POST: api/pagosdistribuidor
[HttpPost]
[ProducesResponseType(typeof(PagoDistribuidorDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Create([FromBody] CreatePagoDistribuidorDto createDto)
{
if (!TienePermiso(PermisoCrear)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dto, error) = await _pagoService.CrearAsync(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al registrar el pago.");
return CreatedAtRoute("GetPagoDistribuidorById", new { idPago = dto.IdPago }, dto);
}
// PUT: api/pagosdistribuidor/{idPago}
[HttpPut("{idPago:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(int idPago, [FromBody] UpdatePagoDistribuidorDto updateDto)
{
if (!TienePermiso(PermisoModificar)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _pagoService.ActualizarAsync(idPago, updateDto, userId.Value);
if (!exito)
{
if (error == "Pago no encontrado.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// DELETE: api/pagosdistribuidor/{idPago}
[HttpDelete("{idPago:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(int idPago)
{
if (!TienePermiso(PermisoEliminar)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _pagoService.EliminarAsync(idPago, userId.Value);
if (!exito)
{
if (error == "Pago no encontrado.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
}
}

View File

@@ -0,0 +1,130 @@
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/controldevoluciones")] // Ruta base
[ApiController]
[Authorize]
public class ControlDevolucionesController : ControllerBase
{
private readonly IControlDevolucionesService _controlDevService;
private readonly ILogger<ControlDevolucionesController> _logger;
// Permisos para Control Devoluciones (CD001 a CD003)
private const string PermisoVer = "CD001";
private const string PermisoCrear = "CD002";
private const string PermisoModificar = "CD003";
// Asumo que no hay un permiso específico para eliminar, se podría usar CD003 o crear CD004
public ControlDevolucionesController(IControlDevolucionesService controlDevService, ILogger<ControlDevolucionesController> logger)
{
_controlDevService = controlDevService;
_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 ControlDevolucionesController.");
return null;
}
// GET: api/controldevoluciones
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<ControlDevolucionesDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAll(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idEmpresa)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var controles = await _controlDevService.ObtenerTodosAsync(fechaDesde, fechaHasta, idEmpresa);
return Ok(controles);
}
// GET: api/controldevoluciones/{idControl}
[HttpGet("{idControl:int}", Name = "GetControlDevolucionesById")]
[ProducesResponseType(typeof(ControlDevolucionesDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int idControl)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var control = await _controlDevService.ObtenerPorIdAsync(idControl);
if (control == null) return NotFound(new { message = $"Control de devoluciones con ID {idControl} no encontrado." });
return Ok(control);
}
// POST: api/controldevoluciones
[HttpPost]
[ProducesResponseType(typeof(ControlDevolucionesDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Create([FromBody] CreateControlDevolucionesDto createDto)
{
if (!TienePermiso(PermisoCrear)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dto, error) = await _controlDevService.CrearAsync(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al registrar el control.");
return CreatedAtRoute("GetControlDevolucionesById", new { idControl = dto.IdControl }, dto);
}
// PUT: api/controldevoluciones/{idControl}
[HttpPut("{idControl:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(int idControl, [FromBody] UpdateControlDevolucionesDto updateDto)
{
if (!TienePermiso(PermisoModificar)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _controlDevService.ActualizarAsync(idControl, updateDto, userId.Value);
if (!exito)
{
if (error == "Control de devoluciones no encontrado.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// DELETE: api/controldevoluciones/{idControl}
[HttpDelete("{idControl:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] // Podría usar PermisoModificar o un permiso de borrado específico
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(int idControl)
{
// Asumimos que CD003 (Modificar) también permite eliminar, o se define un permiso específico
if (!TienePermiso(PermisoModificar)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _controlDevService.EliminarAsync(idControl, userId.Value);
if (!exito)
{
if (error == "Control de devoluciones no encontrado.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
}
}

View File

@@ -0,0 +1,182 @@
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/entradassalidascanilla")] // Ruta base
[ApiController]
[Authorize]
public class EntradasSalidasCanillaController : ControllerBase
{
private readonly IEntradaSalidaCanillaService _esCanillaService;
private readonly ILogger<EntradasSalidasCanillaController> _logger;
// Permisos para E/S Canillitas (MC001 a MC005)
private const string PermisoVerMovimientos = "MC001";
private const string PermisoCrearMovimiento = "MC002";
private const string PermisoModificarMovimiento = "MC003";
private const string PermisoEliminarMovimiento = "MC004"; // (si no está liquidado)
private const string PermisoLiquidar = "MC005"; // Asumo que ver comprobante implica poder liquidar.
public EntradasSalidasCanillaController(IEntradaSalidaCanillaService esCanillaService, ILogger<EntradasSalidasCanillaController> logger)
{
_esCanillaService = esCanillaService;
_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 EntradasSalidasCanillaController.");
return null;
}
// GET: api/entradassalidascanilla
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<EntradaSalidaCanillaDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAll(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idPublicacion, [FromQuery] int? idCanilla,
[FromQuery] bool? liquidados, [FromQuery] bool? incluirNoLiquidados = true) // incluirNoLiquidados por defecto true para ver todo
{
if (!TienePermiso(PermisoVerMovimientos)) return Forbid();
try
{
var movimientos = await _esCanillaService.ObtenerTodosAsync(fechaDesde, fechaHasta, idPublicacion, idCanilla, liquidados, incluirNoLiquidados);
return Ok(movimientos);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener listado de E/S Canillitas.");
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno.");
}
}
// GET: api/entradassalidascanilla/{idParte}
[HttpGet("{idParte:int}", Name = "GetEntradaSalidaCanillaById")]
[ProducesResponseType(typeof(EntradaSalidaCanillaDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int idParte)
{
if (!TienePermiso(PermisoVerMovimientos)) return Forbid();
var movimiento = await _esCanillaService.ObtenerPorIdAsync(idParte);
if (movimiento == null) return NotFound(new { message = $"Movimiento con ID {idParte} no encontrado." });
return Ok(movimiento);
}
// PUT: api/entradassalidascanilla/{idParte}
[HttpPut("{idParte:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> UpdateMovimiento(int idParte, [FromBody] UpdateEntradaSalidaCanillaDto updateDto)
{
if (!TienePermiso(PermisoModificarMovimiento)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _esCanillaService.ActualizarMovimientoAsync(idParte, updateDto, userId.Value);
if (!exito)
{
if (error == "Movimiento no encontrado." || error == "No se puede modificar un movimiento ya liquidado.")
return NotFound(new { message = error }); // Podría ser 404 o 400 dependiendo del error
return BadRequest(new { message = error });
}
return NoContent();
}
// DELETE: api/entradassalidascanilla/{idParte}
[HttpDelete("{idParte:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteMovimiento(int idParte)
{
// Permiso base MC004
// El servicio ahora usa User (ClaimsPrincipal) para verificar MC006 si es necesario
string permisoBaseRequerido = "MC004"; // Asumiendo que esta constante existe o la defines
if (!TienePermiso(permisoBaseRequerido))
{
_logger.LogWarning("Acceso denegado a DeleteMovimiento (IDParte: {IdParte}) para Usuario ID {UserId}. Permiso {Permiso} requerido.", idParte, GetCurrentUserId() ?? 0, permisoBaseRequerido);
return Forbid();
}
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
var (exito, error) = await _esCanillaService.EliminarMovimientoAsync(idParte, userId.Value, User); // Pasar ClaimsPrincipal (User)
if (!exito)
{
if (error == "Movimiento no encontrado.") return NotFound(new { message = error });
// Otros errores como "no tiene permiso para eliminar liquidados" o errores internos.
// El servicio podría devolver un código de estado más específico o el controlador interpretarlo.
// Por ahora, un BadRequest es genérico para fallos de lógica de negocio o permisos no cumplidos en el servicio.
if (error != null && error.Contains("No tiene permiso"))
{
_logger.LogWarning("Intento fallido de eliminar movimiento ID {IdParte} por Usuario ID {UserId}. Razón: {Error}", idParte, userId, error);
return StatusCode(StatusCodes.Status403Forbidden, new { message = error });
}
return BadRequest(new { message = error ?? "Error desconocido al eliminar." });
}
return NoContent();
}
// POST: api/entradassalidascanilla/liquidar
[HttpPost("liquidar")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> LiquidarMovimientos([FromBody] LiquidarMovimientosCanillaRequestDto liquidarDto)
{
if (!TienePermiso(PermisoLiquidar)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _esCanillaService.LiquidarMovimientosAsync(liquidarDto, userId.Value);
if (!exito)
{
return BadRequest(new { message = error });
}
return NoContent();
}
[HttpPost("bulk")] // Nueva ruta o ajustar la existente y diferenciar por DTO
[ProducesResponseType(typeof(IEnumerable<EntradaSalidaCanillaDto>), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> CreateBulkMovimientos([FromBody] CreateBulkEntradaSalidaCanillaDto createBulkDto)
{
// Mantener PermisoCrearMovimiento = "MC002"
if (!TienePermiso(PermisoCrearMovimiento)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dtos, error) = await _esCanillaService.CrearMovimientosEnLoteAsync(createBulkDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dtos == null || !dtos.Any()) return StatusCode(StatusCodes.Status500InternalServerError, "No se pudo registrar ningún movimiento o hubo un error.");
// Podrías devolver solo un 201 Created si la lista de DTOs es muy grande
// o si no necesitas los detalles de cada uno inmediatamente.
// O devolver la lista como se hace aquí.
return StatusCode(StatusCodes.Status201Created, dtos);
}
}
}

View File

@@ -0,0 +1,124 @@
using GestionIntegral.Api.Dtos.Radios;
using GestionIntegral.Api.Services.Radios;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Controllers.Radios
{
[Route("api/[controller]")] // Ruta base: /api/canciones
[ApiController]
[Authorize]
public class CancionesController : ControllerBase
{
private readonly ICancionService _cancionService;
private readonly ILogger<CancionesController> _logger;
// Asumir permisos para Canciones (ej. RC001-RC004 o usar SS005)
private const string PermisoVerCanciones = "SS005";
private const string PermisoGestionarCanciones = "SS005";
public CancionesController(ICancionService cancionService, ILogger<CancionesController> logger)
{
_cancionService = cancionService;
_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 CancionesController.");
return null;
}
// GET: api/canciones
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<CancionDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAll([FromQuery] string? tema, [FromQuery] string? interprete, [FromQuery] int? idRitmo)
{
if (!TienePermiso(PermisoVerCanciones)) return Forbid();
var canciones = await _cancionService.ObtenerTodasAsync(tema, interprete, idRitmo);
return Ok(canciones);
}
// GET: api/canciones/{id}
[HttpGet("{id:int}", Name = "GetCancionById")]
[ProducesResponseType(typeof(CancionDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id)
{
if (!TienePermiso(PermisoVerCanciones)) return Forbid();
var cancion = await _cancionService.ObtenerPorIdAsync(id);
if (cancion == null) return NotFound(new { message = $"Canción con ID {id} no encontrada." });
return Ok(cancion);
}
// POST: api/canciones
[HttpPost]
[ProducesResponseType(typeof(CancionDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Create([FromBody] CreateCancionDto createDto)
{
if (!TienePermiso(PermisoGestionarCanciones)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dto, error) = await _cancionService.CrearAsync(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear la canción.");
return CreatedAtRoute("GetCancionById", new { id = dto.Id }, dto);
}
// PUT: api/canciones/{id}
[HttpPut("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(int id, [FromBody] UpdateCancionDto updateDto)
{
if (!TienePermiso(PermisoGestionarCanciones)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _cancionService.ActualizarAsync(id, updateDto, userId.Value);
if (!exito)
{
if (error == "Canción no encontrada.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// DELETE: api/canciones/{id}
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(int id)
{
if (!TienePermiso(PermisoGestionarCanciones)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _cancionService.EliminarAsync(id, userId.Value);
if (!exito)
{
if (error == "Canción no encontrada.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
}
}

View File

@@ -0,0 +1,65 @@
using GestionIntegral.Api.Dtos.Radios;
using GestionIntegral.Api.Services.Radios;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Security.Claims;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Controllers.Radios
{
[Route("api/radios/listas")] // Ruta base, ej: /api/radios/listas
[ApiController]
[Authorize]
public class RadioListasController : ControllerBase
{
private readonly IRadioListaService _radioListaService;
private readonly ILogger<RadioListasController> _logger;
// Asumir permiso general de Radios o uno específico para generar listas
private const string PermisoGenerarListas = "SS005"; // Usando el permiso general de la sección Radios
public RadioListasController(IRadioListaService radioListaService, ILogger<RadioListasController> logger)
{
_radioListaService = radioListaService;
_logger = logger;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
// GetCurrentUserId no es estrictamente necesario aquí si la acción no modifica datos persistentes auditables por usuario.
// POST: api/radios/listas/generar
[HttpPost("generar")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] // Devuelve un archivo
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> GenerarListaRadio([FromBody] GenerarListaRadioRequestDto requestDto)
{
if (!TienePermiso(PermisoGenerarListas)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
_logger.LogInformation("Solicitud de generación de lista de radio recibida: {@RequestDto}", requestDto);
var (fileContents, contentType, fileName, error) = await _radioListaService.GenerarListaRadioAsync(requestDto);
if (error != null)
{
_logger.LogWarning("Error al generar lista de radio: {Error}", error);
// Devolver un JSON con el error podría ser más útil para el frontend que un simple BadRequest
return BadRequest(new { message = error });
}
if (fileContents == null || fileContents.Length == 0)
{
_logger.LogWarning("La generación de la lista de radio no produjo contenido.");
// Similar al anterior, un JSON con mensaje puede ser mejor
return NotFound(new { message = "No se pudo generar la lista, o no hay datos suficientes." });
}
_logger.LogInformation("Lista de radio generada exitosamente: {FileName}", fileName);
// Devuelve el archivo ZIP para descarga
return File(fileContents, contentType, fileName);
}
}
}

View File

@@ -0,0 +1,125 @@
using GestionIntegral.Api.Dtos.Radios;
using GestionIntegral.Api.Services.Radios;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Controllers.Radios
{
[Route("api/[controller]")] // Ruta base: /api/ritmos
[ApiController]
[Authorize] // Proteger todos los endpoints
public class RitmosController : ControllerBase
{
private readonly IRitmoService _ritmoService;
private readonly ILogger<RitmosController> _logger;
// Asumir códigos de permiso para Ritmos (ej. RR001-RR004)
// O usar permisos más genéricos de "Gestión Radios" si no hay específicos
private const string PermisoVerRitmos = "SS005"; // Usando el de acceso a la sección radios por ahora
private const string PermisoGestionarRitmos = "SS005"; // Idem para crear/mod/elim
public RitmosController(IRitmoService ritmoService, ILogger<RitmosController> logger)
{
_ritmoService = ritmoService;
_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 RitmosController.");
return null;
}
// GET: api/ritmos
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<RitmoDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAll([FromQuery] string? nombre)
{
if (!TienePermiso(PermisoVerRitmos)) return Forbid();
var ritmos = await _ritmoService.ObtenerTodosAsync(nombre);
return Ok(ritmos);
}
// GET: api/ritmos/{id}
[HttpGet("{id:int}", Name = "GetRitmoById")]
[ProducesResponseType(typeof(RitmoDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id)
{
if (!TienePermiso(PermisoVerRitmos)) return Forbid();
var ritmo = await _ritmoService.ObtenerPorIdAsync(id);
if (ritmo == null) return NotFound(new { message = $"Ritmo con ID {id} no encontrado." });
return Ok(ritmo);
}
// POST: api/ritmos
[HttpPost]
[ProducesResponseType(typeof(RitmoDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Create([FromBody] CreateRitmoDto createDto)
{
if (!TienePermiso(PermisoGestionarRitmos)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId(); // Aunque no se use en el repo sin historial, es bueno tenerlo
if (userId == null) return Unauthorized();
var (dto, error) = await _ritmoService.CrearAsync(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear el ritmo.");
return CreatedAtRoute("GetRitmoById", new { id = dto.Id }, dto);
}
// PUT: api/ritmos/{id}
[HttpPut("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(int id, [FromBody] UpdateRitmoDto updateDto)
{
if (!TienePermiso(PermisoGestionarRitmos)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _ritmoService.ActualizarAsync(id, updateDto, userId.Value);
if (!exito)
{
if (error == "Ritmo no encontrado.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// DELETE: api/ritmos/{id}
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)] // Si está en uso
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(int id)
{
if (!TienePermiso(PermisoGestionarRitmos)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _ritmoService.EliminarAsync(id, userId.Value);
if (!exito)
{
if (error == "Ritmo no encontrado.") return NotFound(new { message = error });
return BadRequest(new { message = error }); // Ej: "En uso"
}
return NoContent();
}
}
}

View File

@@ -1,4 +1,5 @@
using GestionIntegral.Api.Dtos.Usuarios;
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
using GestionIntegral.Api.Services.Usuarios;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -141,18 +142,63 @@ namespace GestionIntegral.Api.Controllers.Usuarios
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> ToggleHabilitado(int id, [FromBody] bool habilitar)
{
if (!TienePermiso(PermisoModificarUsuarios)) return Forbid();
if (!TienePermiso(PermisoModificarUsuarios)) return Forbid();
var idAdmin = GetCurrentUserId();
if (idAdmin == null) return Unauthorized("Token inválido.");
var (exito, error) = await _usuarioService.CambiarEstadoHabilitadoAsync(id, habilitar, idAdmin.Value);
if (!exito)
if (!exito)
{
if (error == "Usuario no encontrado.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// GET: api/usuarios/{idUsuarioAfectado}/historial
[HttpGet("{idUsuarioAfectado:int}/historial")]
[ProducesResponseType(typeof(IEnumerable<UsuarioHistorialDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetHistorialDeUsuario(int idUsuarioAfectado, [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta)
{
// Necesitas un permiso para ver historial, ej: "AU001" o uno más granular "AU_USR_VIEW_SINGLE"
// O si solo SuperAdmin puede ver historiales específicos.
if (!TienePermiso("AU001")) // Asumiendo AU001 = Ver Historial de Auditoría (General o Usuarios)
{
_logger.LogWarning("Acceso denegado a GetHistorialDeUsuario para Usuario ID {UserId}", GetCurrentUserId() ?? 0);
return Forbid();
}
var usuarioExiste = await _usuarioService.ObtenerPorIdAsync(idUsuarioAfectado);
if (usuarioExiste == null)
{
return NotFound(new { message = $"Usuario con ID {idUsuarioAfectado} no encontrado." });
}
var historial = await _usuarioService.ObtenerHistorialPorUsuarioIdAsync(idUsuarioAfectado, fechaDesde, fechaHasta);
return Ok(historial);
}
// GET: api/usuarios/historial (Para todos los usuarios, con filtros)
[HttpGet("historial")]
[ProducesResponseType(typeof(IEnumerable<UsuarioHistorialDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetTodoElHistorialDeUsuarios(
[FromQuery] DateTime? fechaDesde,
[FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico,
[FromQuery] string? tipoModificacion)
{
if (!TienePermiso("AU001")) // Mismo permiso general de auditoría
{
_logger.LogWarning("Acceso denegado a GetTodoElHistorialDeUsuarios para Usuario ID {UserId}", GetCurrentUserId() ?? 0);
return Forbid();
}
var historial = await _usuarioService.ObtenerTodoElHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion);
return Ok(historial);
}
}
}

View File

@@ -0,0 +1,21 @@
using GestionIntegral.Api.Models.Contables;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Contables
{
public interface INotaCreditoDebitoRepository
{
Task<IEnumerable<NotaCreditoDebito>> GetAllAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
string? destino, int? idDestino, int? idEmpresa, string? tipoNota);
Task<NotaCreditoDebito?> GetByIdAsync(int idNota);
Task<NotaCreditoDebito?> CreateAsync(NotaCreditoDebito nuevaNota, int idUsuario, IDbTransaction transaction);
Task<bool> UpdateAsync(NotaCreditoDebito notaAActualizar, 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.
}
}

View File

@@ -0,0 +1,21 @@
using GestionIntegral.Api.Models.Contables;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Contables
{
public interface IPagoDistribuidorRepository
{
Task<IEnumerable<PagoDistribuidor>> GetAllAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idDistribuidor, int? idEmpresa, string? tipoMovimiento);
Task<PagoDistribuidor?> GetByIdAsync(int idPago);
Task<PagoDistribuidor?> CreateAsync(PagoDistribuidor nuevoPago, int idUsuario, IDbTransaction transaction);
Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction);
Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction);
Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null);
}
}

View File

@@ -0,0 +1,153 @@
using Dapper;
using GestionIntegral.Api.Models.Contables;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Contables
{
public class NotaCreditoDebitoRepository : INotaCreditoDebitoRepository
{
private readonly DbConnectionFactory _cf;
private readonly ILogger<NotaCreditoDebitoRepository> _log;
public NotaCreditoDebitoRepository(DbConnectionFactory cf, ILogger<NotaCreditoDebitoRepository> log)
{
_cf = cf;
_log = log;
}
private string SelectQueryBase() => @"
SELECT
Id_Nota AS IdNota, Destino, Id_Destino AS IdDestino, Referencia, Tipo, Fecha,
Monto, Observaciones, Id_Empresa AS IdEmpresa
FROM dbo.cue_CreditosDebitos";
public async Task<IEnumerable<NotaCreditoDebito>> GetAllAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
string? destino, int? idDestino, int? idEmpresa, string? tipoNota)
{
var sqlBuilder = new StringBuilder(SelectQueryBase());
sqlBuilder.Append(" WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND Fecha >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND Fecha <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date); }
if (!string.IsNullOrWhiteSpace(destino)) { sqlBuilder.Append(" AND Destino = @DestinoParam"); parameters.Add("DestinoParam", destino); }
if (idDestino.HasValue) { sqlBuilder.Append(" AND Id_Destino = @IdDestinoParam"); parameters.Add("IdDestinoParam", idDestino.Value); }
if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
if (!string.IsNullOrWhiteSpace(tipoNota)) { sqlBuilder.Append(" AND Tipo = @TipoParam"); parameters.Add("TipoParam", tipoNota); }
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Nota DESC;");
try
{
using var connection = _cf.CreateConnection();
return await connection.QueryAsync<NotaCreditoDebito>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener todas las Notas de Crédito/Débito.");
return Enumerable.Empty<NotaCreditoDebito>();
}
}
public async Task<NotaCreditoDebito?> GetByIdAsync(int idNota)
{
var sql = SelectQueryBase() + " WHERE Id_Nota = @IdNotaParam";
try
{
using var connection = _cf.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<NotaCreditoDebito>(sql, new { IdNotaParam = idNota });
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener Nota C/D por ID: {IdNota}", idNota);
return null;
}
}
public async Task<NotaCreditoDebito?> CreateAsync(NotaCreditoDebito nuevaNota, int idUsuario, IDbTransaction transaction)
{
const string sqlInsert = @"
INSERT INTO dbo.cue_CreditosDebitos (Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa)
OUTPUT INSERTED.Id_Nota AS IdNota, INSERTED.Destino, INSERTED.Id_Destino AS IdDestino, INSERTED.Referencia,
INSERTED.Tipo, INSERTED.Fecha, INSERTED.Monto, INSERTED.Observaciones, INSERTED.Id_Empresa AS IdEmpresa
VALUES (@Destino, @IdDestino, @Referencia, @Tipo, @Fecha, @Monto, @Observaciones, @IdEmpresa);";
const string sqlHistorico = @"
INSERT INTO dbo.cue_CreditosDebitos_H
(Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
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.");
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdNotaHist = inserted.IdNota, DestinoHist = inserted.Destino, IdDestinoHist = inserted.IdDestino,
ReferenciaHist = inserted.Referencia, TipoHist = inserted.Tipo, FechaHist = inserted.Fecha, MontoHist = inserted.Monto,
ObservacionesHist = inserted.Observaciones, IdEmpresaHist = inserted.IdEmpresa,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creada"
}, transaction);
return inserted;
}
public async Task<bool> UpdateAsync(NotaCreditoDebito notaAActualizar, int idUsuario, IDbTransaction transaction)
{
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<NotaCreditoDebito>(
SelectQueryBase() + " WHERE Id_Nota = @IdNotaParam",
new { IdNotaParam = notaAActualizar.IdNota }, transaction);
if (actual == null) throw new KeyNotFoundException("Nota de Crédito/Débito no encontrada.");
// Solo se permite actualizar Monto y Observaciones
const string sqlUpdate = @"
UPDATE dbo.cue_CreditosDebitos SET
Monto = @Monto, Observaciones = @Observaciones
WHERE Id_Nota = @IdNota;";
const string sqlHistorico = @"
INSERT INTO dbo.cue_CreditosDebitos_H
(Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdNotaHist = actual.IdNota, DestinoHist = actual.Destino, IdDestinoHist = actual.IdDestino, ReferenciaHist = actual.Referencia,
TipoHist = actual.Tipo, FechaHist = actual.Fecha, MontoHist = actual.Monto, // Valor ANTERIOR
ObservacionesHist = actual.Observaciones, IdEmpresaHist = actual.IdEmpresa, // Valor ANTERIOR
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizada"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new {
notaAActualizar.Monto,
notaAActualizar.Observaciones,
notaAActualizar.IdNota
} , transaction);
return rowsAffected == 1;
}
public async Task<bool> DeleteAsync(int idNota, int idUsuario, IDbTransaction transaction)
{
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<NotaCreditoDebito>(
SelectQueryBase() + " WHERE Id_Nota = @IdNotaParam",
new { IdNotaParam = idNota }, transaction);
if (actual == null) throw new KeyNotFoundException("Nota de Crédito/Débito no encontrada para eliminar.");
const string sqlDelete = "DELETE FROM dbo.cue_CreditosDebitos WHERE Id_Nota = @IdNotaParam";
const string sqlHistorico = @"
INSERT INTO dbo.cue_CreditosDebitos_H
(Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdNotaHist = actual.IdNota, DestinoHist = actual.Destino, IdDestinoHist = actual.IdDestino, ReferenciaHist = actual.Referencia,
TipoHist = actual.Tipo, FechaHist = actual.Fecha, MontoHist = actual.Monto, ObservacionesHist = actual.Observaciones, IdEmpresaHist = actual.IdEmpresa,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdNotaParam = idNota }, transaction);
return rowsAffected == 1;
}
}
}

View File

@@ -0,0 +1,175 @@
using Dapper;
using GestionIntegral.Api.Models.Contables;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Contables
{
public class PagoDistribuidorRepository : IPagoDistribuidorRepository
{
private readonly DbConnectionFactory _cf;
private readonly ILogger<PagoDistribuidorRepository> _log;
public PagoDistribuidorRepository(DbConnectionFactory cf, ILogger<PagoDistribuidorRepository> log)
{
_cf = cf;
_log = log;
}
private string SelectQueryBase() => @"
SELECT
Id_Pago AS IdPago, Id_Distribuidor AS IdDistribuidor, Fecha, TipoMovimiento, Recibo, Monto,
Id_TipoPago AS IdTipoPago, Detalle, Id_Empresa AS IdEmpresa
FROM dbo.cue_PagosDistribuidor";
public async Task<IEnumerable<PagoDistribuidor>> GetAllAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idDistribuidor, int? idEmpresa, string? tipoMovimiento)
{
var sqlBuilder = new StringBuilder(SelectQueryBase());
sqlBuilder.Append(" WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND Fecha >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND Fecha <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date); }
if (idDistribuidor.HasValue) { sqlBuilder.Append(" AND Id_Distribuidor = @IdDistribuidorParam"); parameters.Add("IdDistribuidorParam", idDistribuidor.Value); }
if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
if (!string.IsNullOrWhiteSpace(tipoMovimiento)) { sqlBuilder.Append(" AND TipoMovimiento = @TipoMovParam"); parameters.Add("TipoMovParam", tipoMovimiento); }
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Pago DESC;");
try
{
using var connection = _cf.CreateConnection();
return await connection.QueryAsync<PagoDistribuidor>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener todos los Pagos de Distribuidores.");
return Enumerable.Empty<PagoDistribuidor>();
}
}
public async Task<PagoDistribuidor?> GetByIdAsync(int idPago)
{
var sql = SelectQueryBase() + " WHERE Id_Pago = @IdPagoParam";
try
{
using var connection = _cf.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<PagoDistribuidor>(sql, new { IdPagoParam = idPago });
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener PagoDistribuidor por ID: {IdPago}", idPago);
return null;
}
}
public async Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null)
{
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.cue_PagosDistribuidor WHERE Recibo = @ReciboParam AND TipoMovimiento = @TipoMovParam");
var parameters = new DynamicParameters();
parameters.Add("ReciboParam", recibo);
parameters.Add("TipoMovParam", tipoMovimiento);
if (excludeIdPago.HasValue)
{
sqlBuilder.Append(" AND Id_Pago != @ExcludeIdPagoParam");
parameters.Add("ExcludeIdPagoParam", excludeIdPago.Value);
}
try
{
using var connection = _cf.CreateConnection();
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error en ExistsByReciboAndTipoMovimientoAsync. Recibo: {Recibo}, Tipo: {Tipo}", recibo, tipoMovimiento);
return true; // Asumir que existe en caso de error para prevenir duplicados
}
}
public async Task<PagoDistribuidor?> CreateAsync(PagoDistribuidor nuevoPago, int idUsuario, IDbTransaction transaction)
{
const string sqlInsert = @"
INSERT INTO dbo.cue_PagosDistribuidor (Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa)
OUTPUT INSERTED.Id_Pago AS IdPago, INSERTED.Id_Distribuidor AS IdDistribuidor, INSERTED.Fecha, INSERTED.TipoMovimiento,
INSERTED.Recibo, INSERTED.Monto, INSERTED.Id_TipoPago AS IdTipoPago, INSERTED.Detalle, INSERTED.Id_Empresa AS IdEmpresa
VALUES (@IdDistribuidor, @Fecha, @TipoMovimiento, @Recibo, @Monto, @IdTipoPago, @Detalle, @IdEmpresa);";
const string sqlHistorico = @"
INSERT INTO dbo.cue_PagosDistribuidor_H
(Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
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.");
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdPagoHist = inserted.IdPago, IdDistribuidorHist = inserted.IdDistribuidor, FechaHist = inserted.Fecha,
TipoMovimientoHist = inserted.TipoMovimiento, ReciboHist = inserted.Recibo, MontoHist = inserted.Monto,
IdTipoPagoHist = inserted.IdTipoPago, DetalleHist = inserted.Detalle, IdEmpresaHist = inserted.IdEmpresa,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creado"
}, transaction);
return inserted;
}
public async Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction)
{
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PagoDistribuidor>(
SelectQueryBase() + " WHERE Id_Pago = @IdPagoParam",
new { IdPagoParam = pagoAActualizar.IdPago }, transaction);
if (actual == null) throw new KeyNotFoundException("Pago no encontrado.");
// Campos que se permiten actualizar: Monto, Id_TipoPago, Detalle
// Otros campos como IdDistribuidor, Fecha, TipoMovimiento, Recibo, IdEmpresa no deberían cambiar
// para un pago ya registrado. Si necesitan cambiar, se debería anular/eliminar y crear uno nuevo.
const string sqlUpdate = @"
UPDATE dbo.cue_PagosDistribuidor SET
Monto = @Monto, Id_TipoPago = @IdTipoPago, Detalle = @Detalle
WHERE Id_Pago = @IdPago;";
const string sqlHistorico = @"
INSERT INTO dbo.cue_PagosDistribuidor_H
(Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdPagoHist = actual.IdPago, IdDistribuidorHist = actual.IdDistribuidor, FechaHist = actual.Fecha,
TipoMovimientoHist = actual.TipoMovimiento, ReciboHist = actual.Recibo, MontoHist = actual.Monto, // Valor ANTERIOR
IdTipoPagoHist = actual.IdTipoPago, DetalleHist = actual.Detalle, IdEmpresaHist = actual.IdEmpresa, // Valores ANTERIORES
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizado"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, pagoAActualizar, transaction);
return rowsAffected == 1;
}
public async Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction)
{
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PagoDistribuidor>(
SelectQueryBase() + " WHERE Id_Pago = @IdPagoParam",
new { IdPagoParam = idPago }, transaction);
if (actual == null) throw new KeyNotFoundException("Pago no encontrado para eliminar.");
const string sqlDelete = "DELETE FROM dbo.cue_PagosDistribuidor WHERE Id_Pago = @IdPagoParam";
const string sqlHistorico = @"
INSERT INTO dbo.cue_PagosDistribuidor_H
(Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdPagoHist = actual.IdPago, IdDistribuidorHist = actual.IdDistribuidor, FechaHist = actual.Fecha,
TipoMovimientoHist = actual.TipoMovimiento, ReciboHist = actual.Recibo, MontoHist = actual.Monto,
IdTipoPagoHist = actual.IdTipoPago, DetalleHist = actual.Detalle, IdEmpresaHist = actual.IdEmpresa,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminado"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPagoParam = idPago }, transaction);
return rowsAffected == 1;
}
}
}

View File

@@ -0,0 +1,162 @@
using Dapper;
using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Distribucion
{
public class ControlDevolucionesRepository : IControlDevolucionesRepository
{
private readonly DbConnectionFactory _cf;
private readonly ILogger<ControlDevolucionesRepository> _log;
public ControlDevolucionesRepository(DbConnectionFactory cf, ILogger<ControlDevolucionesRepository> log)
{
_cf = cf;
_log = log;
}
private string SelectQueryBase() => @"
SELECT
Id_Control AS IdControl, Id_Empresa AS IdEmpresa, Fecha,
Entrada, Sobrantes, Detalle, SinCargo
FROM dbo.dist_dtCtrlDevoluciones";
public async Task<IEnumerable<ControlDevoluciones>> GetAllAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idEmpresa)
{
var sqlBuilder = new StringBuilder(SelectQueryBase());
sqlBuilder.Append(" WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND Fecha >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND Fecha <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date); }
if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Empresa;");
try
{
using var connection = _cf.CreateConnection();
return await connection.QueryAsync<ControlDevoluciones>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener todos los Controles de Devoluciones.");
return Enumerable.Empty<ControlDevoluciones>();
}
}
public async Task<ControlDevoluciones?> GetByIdAsync(int idControl)
{
var sql = SelectQueryBase() + " WHERE Id_Control = @IdControlParam";
try
{
using var connection = _cf.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<ControlDevoluciones>(sql, new { IdControlParam = idControl });
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener ControlDevoluciones por ID: {IdControl}", idControl);
return null;
}
}
public async Task<ControlDevoluciones?> GetByEmpresaAndFechaAsync(int idEmpresa, DateTime fecha, IDbTransaction? transaction = null)
{
var sql = SelectQueryBase() + " WHERE Id_Empresa = @IdEmpresaParam AND Fecha = @FechaParam";
var cn = transaction?.Connection ?? _cf.CreateConnection();
bool ownConnection = transaction == null;
ControlDevoluciones? result = null;
try
{
if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
result = await cn.QuerySingleOrDefaultAsync<ControlDevoluciones>(sql, new { IdEmpresaParam = idEmpresa, FechaParam = fecha.Date }, transaction);
}
finally
{
if (ownConnection && cn.State == ConnectionState.Open && cn is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync();
if (ownConnection) (cn as IDisposable)?.Dispose();
}
return result;
}
public async Task<ControlDevoluciones?> CreateAsync(ControlDevoluciones nuevoControl, int idUsuario, IDbTransaction transaction)
{
const string sqlInsert = @"
INSERT INTO dbo.dist_dtCtrlDevoluciones (Id_Empresa, Fecha, Entrada, Sobrantes, Detalle, SinCargo)
OUTPUT INSERTED.Id_Control AS IdControl, INSERTED.Id_Empresa AS IdEmpresa, INSERTED.Fecha,
INSERTED.Entrada, INSERTED.Sobrantes, INSERTED.Detalle, INSERTED.SinCargo
VALUES (@IdEmpresa, @Fecha, @Entrada, @Sobrantes, @Detalle, @SinCargo);";
const string sqlHistorico = @"
INSERT INTO dbo.dist_dtCtrlDevoluciones_H
(Id_Control, Id_Empresa, Fecha, Entrada, Sobrantes, Detalle, SinCargo, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdControlParam, @IdEmpresaParam, @FechaParam, @EntradaParam, @SobrantesParam, @DetalleParam, @SinCargoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
var inserted = await transaction.Connection!.QuerySingleAsync<ControlDevoluciones>(sqlInsert, nuevoControl, transaction);
if (inserted == null || inserted.IdControl == 0) throw new DataException("Error al crear control de devoluciones o ID no generado.");
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdControlParam = inserted.IdControl, IdEmpresaParam = inserted.IdEmpresa, FechaParam = inserted.Fecha,
EntradaParam = inserted.Entrada, SobrantesParam = inserted.Sobrantes, DetalleParam = inserted.Detalle, SinCargoParam = inserted.SinCargo,
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado"
}, transaction);
return inserted;
}
public async Task<bool> UpdateAsync(ControlDevoluciones controlAActualizar, int idUsuario, IDbTransaction transaction)
{
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<ControlDevoluciones>(
SelectQueryBase() + " WHERE Id_Control = @IdControlParam",
new { IdControlParam = controlAActualizar.IdControl }, transaction);
if (actual == null) throw new KeyNotFoundException("Control de devoluciones no encontrado.");
// En este caso, Id_Empresa y Fecha no deberían cambiar para un registro existente
const string sqlUpdate = @"
UPDATE dbo.dist_dtCtrlDevoluciones SET
Entrada = @Entrada, Sobrantes = @Sobrantes, Detalle = @Detalle, SinCargo = @SinCargo
WHERE Id_Control = @IdControl;";
const string sqlHistorico = @"
INSERT INTO dbo.dist_dtCtrlDevoluciones_H
(Id_Control, Id_Empresa, Fecha, Entrada, Sobrantes, Detalle, SinCargo, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdControlParam, @IdEmpresaParam, @FechaParam, @EntradaParam, @SobrantesParam, @DetalleParam, @SinCargoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdControlParam = actual.IdControl, IdEmpresaParam = actual.IdEmpresa, FechaParam = actual.Fecha, // Datos originales
EntradaParam = actual.Entrada, SobrantesParam = actual.Sobrantes, DetalleParam = actual.Detalle, SinCargoParam = actual.SinCargo, // Valores ANTERIORES
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, controlAActualizar, transaction);
return rowsAffected == 1;
}
public async Task<bool> DeleteAsync(int idControl, int idUsuario, IDbTransaction transaction)
{
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<ControlDevoluciones>(
SelectQueryBase() + " WHERE Id_Control = @IdControlParam",
new { IdControlParam = idControl }, transaction);
if (actual == null) throw new KeyNotFoundException("Control de devoluciones no encontrado para eliminar.");
const string sqlDelete = "DELETE FROM dbo.dist_dtCtrlDevoluciones WHERE Id_Control = @IdControlParam";
const string sqlHistorico = @"
INSERT INTO dbo.dist_dtCtrlDevoluciones_H
(Id_Control, Id_Empresa, Fecha, Entrada, Sobrantes, Detalle, SinCargo, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdControlParam, @IdEmpresaParam, @FechaParam, @EntradaParam, @SobrantesParam, @DetalleParam, @SinCargoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdControlParam = actual.IdControl, IdEmpresaParam = actual.IdEmpresa, FechaParam = actual.Fecha,
EntradaParam = actual.Entrada, SobrantesParam = actual.Sobrantes, DetalleParam = actual.Detalle, SinCargoParam = actual.SinCargo,
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdControlParam = idControl }, transaction);
return rowsAffected == 1;
}
}
}

View File

@@ -0,0 +1,298 @@
using Dapper;
using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Distribucion
{
public class EntradaSalidaCanillaRepository : IEntradaSalidaCanillaRepository
{
private readonly DbConnectionFactory _cf;
private readonly ILogger<EntradaSalidaCanillaRepository> _log;
public EntradaSalidaCanillaRepository(DbConnectionFactory cf, ILogger<EntradaSalidaCanillaRepository> log)
{
_cf = cf;
_log = log;
}
private string SelectQueryBase() => @"
SELECT
Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Canilla AS IdCanilla,
Fecha, CantSalida, CantEntrada, Id_Precio AS IdPrecio, Id_Recargo AS IdRecargo,
Id_PorcMon AS IdPorcMon, Observacion, Liquidado, FechaLiquidado, UserLiq
FROM dbo.dist_EntradasSalidasCanillas";
public async Task<IEnumerable<EntradaSalidaCanilla>> GetAllAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idPublicacion, int? idCanilla, bool? liquidados)
{
var sqlBuilder = new StringBuilder(SelectQueryBase());
sqlBuilder.Append(" WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND Fecha >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND Fecha <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date); }
if (idPublicacion.HasValue) { sqlBuilder.Append(" AND Id_Publicacion = @IdPublicacionParam"); parameters.Add("IdPublicacionParam", idPublicacion.Value); }
if (idCanilla.HasValue) { sqlBuilder.Append(" AND Id_Canilla = @IdCanillaParam"); parameters.Add("IdCanillaParam", idCanilla.Value); }
if (liquidados.HasValue) { sqlBuilder.Append(" AND Liquidado = @LiquidadoParam"); parameters.Add("LiquidadoParam", liquidados.Value); }
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Canilla, Id_Publicacion, Id_Parte DESC;");
try
{
using var connection = _cf.CreateConnection();
return await connection.QueryAsync<EntradaSalidaCanilla>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener Entradas/Salidas de Canillitas.");
return Enumerable.Empty<EntradaSalidaCanilla>();
}
}
public async Task<EntradaSalidaCanilla?> GetByIdAsync(int idParte)
{
var sql = SelectQueryBase() + " WHERE Id_Parte = @IdParteParam";
try
{
using var connection = _cf.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<EntradaSalidaCanilla>(sql, new { IdParteParam = idParte });
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener EntradaSalidaCanilla por ID: {IdParte}", idParte);
return null;
}
}
public async Task<IEnumerable<EntradaSalidaCanilla>> GetByIdsAsync(IEnumerable<int> idsPartes, IDbTransaction? transaction = null)
{
if (idsPartes == null || !idsPartes.Any()) return Enumerable.Empty<EntradaSalidaCanilla>();
var sql = SelectQueryBase() + " WHERE Id_Parte IN @IdsPartesParam";
var cn = transaction?.Connection ?? _cf.CreateConnection();
bool ownConnection = transaction == null;
IEnumerable<EntradaSalidaCanilla> result = Enumerable.Empty<EntradaSalidaCanilla>();
try
{
if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
result = await cn.QueryAsync<EntradaSalidaCanilla>(sql, new { IdsPartesParam = idsPartes }, transaction);
}
finally
{
if (ownConnection && cn.State == ConnectionState.Open && cn is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync();
if (ownConnection) (cn as IDisposable)?.Dispose();
}
return result;
}
public async Task<bool> ExistsByPublicacionCanillaFechaAsync(int idPublicacion, int idCanilla, DateTime fecha, IDbTransaction? transaction = null, int? excludeIdParte = null)
{
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_EntradasSalidasCanillas WHERE Id_Publicacion = @IdPubParam AND Id_Canilla = @IdCanParam AND Fecha = @FechaParam");
var parameters = new DynamicParameters();
parameters.Add("IdPubParam", idPublicacion);
parameters.Add("IdCanParam", idCanilla);
parameters.Add("FechaParam", fecha.Date);
if (excludeIdParte.HasValue)
{
sqlBuilder.Append(" AND Id_Parte != @ExcludeIdParteParam");
parameters.Add("ExcludeIdParteParam", excludeIdParte.Value);
}
var connection = transaction?.Connection ?? _cf.CreateConnection();
bool ownConnection = transaction == null;
bool result = false;
try
{
if (ownConnection && connection.State == ConnectionState.Closed && connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
result = await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters, transaction);
}
catch (Exception ex)
{
_log.LogError(ex, "Error en ExistsByPublicacionCanillaFechaAsync.");
return true; // Asumir que existe en caso de error
}
finally
{
if (ownConnection && connection.State == ConnectionState.Open && connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync();
if (ownConnection) (connection as IDisposable)?.Dispose();
}
return result;
}
public async Task<EntradaSalidaCanilla?> CreateAsync(EntradaSalidaCanilla nuevoES, int idUsuario, IDbTransaction transaction)
{
const string sqlInsert = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas
(Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Liquidado, FechaLiquidado, UserLiq)
OUTPUT INSERTED.Id_Parte AS IdParte, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Canilla AS IdCanilla, INSERTED.Fecha,
INSERTED.CantSalida, INSERTED.CantEntrada, INSERTED.Id_Precio AS IdPrecio, INSERTED.Id_Recargo AS IdRecargo,
INSERTED.Id_PorcMon AS IdPorcMon, INSERTED.Observacion, INSERTED.Liquidado, INSERTED.FechaLiquidado, INSERTED.UserLiq
VALUES (@IdPublicacion, @IdCanilla, @Fecha, @CantSalida, @CantEntrada, @IdPrecio, @IdRecargo, @IdPorcMon, @Observacion, @Liquidado, @FechaLiquidado, @UserLiq);";
var inserted = await transaction.Connection!.QuerySingleAsync<EntradaSalidaCanilla>(sqlInsert, nuevoES, transaction);
if (inserted == null || inserted.IdParte == 0) throw new DataException("Error al crear E/S Canilla o ID no generado.");
const string sqlHistorico = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
// Liquidado, FechaLiquidado, UserLiq no van al historial de creación, sino al de liquidación.
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = inserted.IdParte,
IdPubHist = inserted.IdPublicacion,
IdCanillaHist = inserted.IdCanilla,
FechaHist = inserted.Fecha,
CantSalidaHist = inserted.CantSalida,
CantEntradaHist = inserted.CantEntrada,
IdPrecioHist = inserted.IdPrecio,
IdRecargoHist = inserted.IdRecargo,
IdPorcMonHist = inserted.IdPorcMon,
ObsHist = inserted.Observacion,
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Creada"
}, transaction);
return inserted;
}
public async Task<bool> UpdateAsync(EntradaSalidaCanilla esAActualizar, int idUsuario, IDbTransaction transaction, string tipoMod = "Actualizada")
{
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<EntradaSalidaCanilla>(SelectQueryBase() + " WHERE Id_Parte = @IdParteParam",
new { IdParteParam = esAActualizar.IdParte }, transaction);
if (actual == null) throw new KeyNotFoundException("Registro E/S Canilla no encontrado.");
const string sqlUpdate = @"
UPDATE dbo.dist_EntradasSalidasCanillas SET
CantSalida = @CantSalida, CantEntrada = @CantEntrada, Observacion = @Observacion,
Liquidado = @Liquidado, FechaLiquidado = @FechaLiquidado, UserLiq = @UserLiq,
Id_Precio = @IdPrecio, Id_Recargo = @IdRecargo, Id_PorcMon = @IdPorcMon
-- No se permite cambiar Publicacion, Canilla, Fecha directamente aquí.
WHERE Id_Parte = @IdParte;";
const string sqlHistorico = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = actual.IdParte,
IdPubHist = actual.IdPublicacion,
IdCanillaHist = actual.IdCanilla,
FechaHist = actual.Fecha,
CantSalidaHist = actual.CantSalida,
CantEntradaHist = actual.CantEntrada,
IdPrecioHist = actual.IdPrecio,
IdRecargoHist = actual.IdRecargo,
IdPorcMonHist = actual.IdPorcMon,
ObsHist = actual.Observacion, // Valores ANTERIORES
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = tipoMod
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, esAActualizar, transaction);
return rowsAffected == 1;
}
public async Task<bool> LiquidarAsync(IEnumerable<int> idsPartes, DateTime fechaLiquidacion, int idUsuarioLiquidador, IDbTransaction transaction)
{
// Primero, obtener los registros actuales para el historial
var movimientosALiquidar = await GetByIdsAsync(idsPartes, transaction);
if (!movimientosALiquidar.Any() || movimientosALiquidar.Count() != idsPartes.Distinct().Count())
{
_log.LogWarning("Intento de liquidar IdsPartes no encontrados o inconsistentes.");
return false; // O lanzar excepción
}
const string sqlUpdate = @"
UPDATE dbo.dist_EntradasSalidasCanillas SET
Liquidado = 1, FechaLiquidado = @FechaLiquidacionParam, UserLiq = @UserLiqParam
WHERE Id_Parte = @IdParteParam AND Liquidado = 0;"; // Solo liquidar los no liquidados
const string sqlHistorico = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
int totalRowsAffected = 0;
foreach (var mov in movimientosALiquidar)
{
if (mov.Liquidado) continue; // Ya estaba liquidado, no hacer nada ni registrar historial
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = mov.IdParte,
IdPubHist = mov.IdPublicacion,
IdCanillaHist = mov.IdCanilla,
FechaHist = mov.Fecha,
CantSalidaHist = mov.CantSalida,
CantEntradaHist = mov.CantEntrada,
IdPrecioHist = mov.IdPrecio,
IdRecargoHist = mov.IdRecargo,
IdPorcMonHist = mov.IdPorcMon,
ObsHist = mov.Observacion,
IdUsuarioHist = idUsuarioLiquidador,
FechaModHist = DateTime.Now,
TipoModHist = "Liquidada"
}, transaction);
var rows = await transaction.Connection!.ExecuteAsync(sqlUpdate,
new { FechaLiquidacionParam = fechaLiquidacion.Date, UserLiqParam = idUsuarioLiquidador, IdParteParam = mov.IdParte },
transaction);
totalRowsAffected += rows;
}
// Se considera éxito si al menos una fila fue afectada (o si todas ya estaban liquidadas y no hubo errores)
// Si se pasaron IDs que no existen, GetByIdsAsync ya devolvería menos elementos.
return totalRowsAffected >= 0;
}
public async Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction)
{
var actual = await GetByIdAsync(idParte); // No necesita TX, solo para el historial
if (actual == null) throw new KeyNotFoundException("Registro E/S Canilla no encontrado para eliminar.");
if (actual.Liquidado) throw new InvalidOperationException("No se puede eliminar un movimiento liquidado.");
const string sqlDelete = "DELETE FROM dbo.dist_EntradasSalidasCanillas WHERE Id_Parte = @IdParteParam";
const string sqlHistorico = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = actual.IdParte,
IdPubHist = actual.IdPublicacion,
IdCanillaHist = actual.IdCanilla,
FechaHist = actual.Fecha,
CantSalidaHist = actual.CantSalida,
CantEntradaHist = actual.CantEntrada,
IdPrecioHist = actual.IdPrecio,
IdRecargoHist = actual.IdRecargo,
IdPorcMonHist = actual.IdPorcMon,
ObsHist = actual.Observacion,
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Eliminada"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction);
return rowsAffected == 1;
}
}
}

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 IControlDevolucionesRepository
{
Task<IEnumerable<ControlDevoluciones>> GetAllAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idEmpresa);
Task<ControlDevoluciones?> GetByIdAsync(int idControl);
Task<ControlDevoluciones?> GetByEmpresaAndFechaAsync(int idEmpresa, DateTime fecha, IDbTransaction? transaction = null); // Para validar unicidad
Task<ControlDevoluciones?> CreateAsync(ControlDevoluciones nuevoControl, int idUsuario, IDbTransaction transaction);
Task<bool> UpdateAsync(ControlDevoluciones controlAActualizar, int idUsuario, IDbTransaction transaction);
Task<bool> DeleteAsync(int idControl, int idUsuario, IDbTransaction transaction);
}
}

View File

@@ -0,0 +1,23 @@
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 IEntradaSalidaCanillaRepository
{
Task<IEnumerable<EntradaSalidaCanilla>> GetAllAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idPublicacion, int? idCanilla, bool? liquidados);
Task<EntradaSalidaCanilla?> GetByIdAsync(int idParte);
Task<IEnumerable<EntradaSalidaCanilla>> GetByIdsAsync(IEnumerable<int> idsPartes, IDbTransaction? transaction = null); // Para liquidación masiva
Task<EntradaSalidaCanilla?> CreateAsync(EntradaSalidaCanilla nuevoES, int idUsuario, IDbTransaction transaction);
Task<bool> UpdateAsync(EntradaSalidaCanilla esAActualizar, int idUsuario, IDbTransaction transaction, string tipoMod = "Actualizada");
Task<bool> DeleteAsync(int idParte, int idUsuario, 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);
}
}

View File

@@ -0,0 +1,196 @@
using Dapper;
using GestionIntegral.Api.Models.Radios;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Radios
{
public class CancionRepository : ICancionRepository
{
private readonly DbConnectionFactory _cf;
private readonly ILogger<CancionRepository> _log;
public CancionRepository(DbConnectionFactory cf, ILogger<CancionRepository> log)
{
_cf = cf;
_log = log;
}
private string SelectWithAlias() => @"
SELECT
Id, Tema, CompositorAutor, Interprete, Sello, Placa, Pista, Introduccion,
Ritmo AS IdRitmo, -- Mapear columna Ritmo a propiedad IdRitmo del modelo Cancion
Formato, Album
FROM dbo.rad_dtCanciones";
public async Task<IEnumerable<Cancion>> GetAllAsync(string? temaFilter, string? interpreteFilter, int? idRitmoFilter)
{
var sqlBuilder = new StringBuilder(SelectWithAlias());
sqlBuilder.Append(" WHERE 1=1");
var parameters = new DynamicParameters();
if (!string.IsNullOrWhiteSpace(temaFilter))
{
sqlBuilder.Append(" AND Tema LIKE @TemaParam");
parameters.Add("TemaParam", $"%{temaFilter}%");
}
if (!string.IsNullOrWhiteSpace(interpreteFilter))
{
sqlBuilder.Append(" AND Interprete LIKE @InterpreteParam");
parameters.Add("InterpreteParam", $"%{interpreteFilter}%");
}
if (idRitmoFilter.HasValue)
{
sqlBuilder.Append(" AND Ritmo = @IdRitmoParam"); // La columna en DB es "Ritmo"
parameters.Add("IdRitmoParam", idRitmoFilter.Value);
}
sqlBuilder.Append(" ORDER BY Tema, Interprete;");
try
{
using var connection = _cf.CreateConnection();
return await connection.QueryAsync<Cancion>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener todas las Canciones.");
return Enumerable.Empty<Cancion>();
}
}
public async Task<Cancion?> GetByIdAsync(int id)
{
var sql = SelectWithAlias() + " WHERE Id = @IdParam";
try
{
using var connection = _cf.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<Cancion>(sql, new { IdParam = id });
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener Canción por ID: {IdCancion}", id);
return null;
}
}
public async Task<bool> ExistsByTemaAndInterpreteAsync(string tema, string interprete, int? excludeId = null)
{
// La unicidad puede ser más compleja si se consideran otros campos como Álbum.
// Por ahora, un ejemplo simple.
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.rad_dtCanciones WHERE Tema = @TemaParam AND Interprete = @InterpreteParam");
var parameters = new DynamicParameters();
parameters.Add("TemaParam", tema);
parameters.Add("InterpreteParam", interprete);
if (excludeId.HasValue)
{
sqlBuilder.Append(" AND Id != @ExcludeIdParam");
parameters.Add("ExcludeIdParam", excludeId.Value);
}
try
{
using var connection = _cf.CreateConnection();
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error en ExistsByTemaAndInterpreteAsync. Tema: {Tema}, Interprete: {Interprete}", tema, interprete);
return true; // Asumir que existe en caso de error
}
}
public async Task<Cancion?> CreateAsync(Cancion nuevaCancion /*, int idUsuario, IDbTransaction transaction */)
{
// El modelo Cancion usa IdRitmo, pero la tabla rad_dtCanciones usa la columna "Ritmo" para el FK a rad_dtRitmos.Id
const string sqlInsert = @"
INSERT INTO dbo.rad_dtCanciones (Tema, CompositorAutor, Interprete, Sello, Placa, Pista, Introduccion, Ritmo, Formato, Album)
OUTPUT INSERTED.Id, INSERTED.Tema, INSERTED.CompositorAutor, INSERTED.Interprete, INSERTED.Sello, INSERTED.Placa,
INSERTED.Pista, INSERTED.Introduccion, INSERTED.Ritmo AS IdRitmo, INSERTED.Formato, INSERTED.Album
VALUES (@Tema, @CompositorAutor, @Interprete, @Sello, @Placa, @Pista, @Introduccion, @IdRitmo, @Formato, @Album);";
try
{
using var connection = _cf.CreateConnection();
// El objeto nuevaCancion tiene la propiedad IdRitmo, que Dapper mapeará al parámetro @IdRitmo
return await connection.QuerySingleAsync<Cancion>(sqlInsert, nuevaCancion);
}
catch (Exception ex)
{
_log.LogError(ex, "Error al crear Canción: {Tema} por {Interprete}", nuevaCancion.Tema, nuevaCancion.Interprete);
return null;
}
}
public async Task<bool> UpdateAsync(Cancion cancionAActualizar /*, int idUsuario, IDbTransaction transaction */)
{
// Similar a Create, la columna en DB es "Ritmo"
const string sqlUpdate = @"
UPDATE dbo.rad_dtCanciones SET
Tema = @Tema, CompositorAutor = @CompositorAutor, Interprete = @Interprete, Sello = @Sello,
Placa = @Placa, Pista = @Pista, Introduccion = @Introduccion, Ritmo = @IdRitmo,
Formato = @Formato, Album = @Album
WHERE Id = @Id;";
try
{
using var connection = _cf.CreateConnection();
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, cancionAActualizar);
return rowsAffected == 1;
}
catch (Exception ex)
{
_log.LogError(ex, "Error al actualizar Canción ID: {IdCancion}", cancionAActualizar.Id);
return false;
}
}
public async Task<bool> DeleteAsync(int id /*, int idUsuario, IDbTransaction transaction */)
{
// IsInUseAsync no se implementó porque no hay dependencias directas obvias que impidan borrar una canción
// (a menos que las listas generadas se guarden y referencien IDs de canciones).
const string sqlDelete = "DELETE FROM dbo.rad_dtCanciones WHERE Id = @IdParam";
try
{
using var connection = _cf.CreateConnection();
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id });
return rowsAffected == 1;
}
catch (Exception ex)
{
_log.LogError(ex, "Error al eliminar Canción ID: {IdCancion}", id);
return false;
}
}
// IsInUseAsync podría ser relevante si las listas generadas referencian el Id de la canción
// Por ahora, lo omitimos.
public Task<bool> IsInUseAsync(int id)
{
_log.LogWarning("IsInUseAsync no implementado para CancionRepository.");
return Task.FromResult(false); // Asumir que no está en uso por ahora
}
public async Task<IEnumerable<Cancion>> GetRandomCancionesAsync(int count)
{
// El modelo Cancion tiene la propiedad "Ritmo" que es el IdRitmo.
// El SelectWithAlias no es necesario aquí si solo tomamos las columnas que ya tiene Cancion.
// Si el modelo Cancion no tuviera la propiedad Ritmo (IdRitmo), entonces necesitarías el alias.
// Por simplicidad, y dado que el modelo Cancion sí tiene Ritmo (que es IdRitmo), no necesitamos un alias específico aquí.
var sql = $"SELECT TOP ({count}) Id, Tema, CompositorAutor, Interprete, Sello, Placa, Pista, Introduccion, Ritmo, Formato, Album FROM dbo.rad_dtCanciones ORDER BY NEWID()";
try
{
using var connection = _cf.CreateConnection();
// Dapper mapeará la columna "Ritmo" de la BD a la propiedad "Ritmo" del modelo Cancion.
return await connection.QueryAsync<Cancion>(sql);
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener {Count} canciones aleatorias.", count);
return Enumerable.Empty<Cancion>();
}
}
}
}

View File

@@ -0,0 +1,18 @@
using GestionIntegral.Api.Models.Radios;
using System.Collections.Generic;
using System.Threading.Tasks;
// using System.Data; // Solo si se usa transacción para historial
namespace GestionIntegral.Api.Data.Repositories.Radios
{
public interface ICancionRepository
{
Task<IEnumerable<Cancion>> GetAllAsync(string? temaFilter, string? interpreteFilter, int? idRitmoFilter);
Task<Cancion?> GetByIdAsync(int id);
Task<Cancion?> CreateAsync(Cancion nuevaCancion /*, int idUsuario, IDbTransaction transaction - si hay historial */);
Task<bool> UpdateAsync(Cancion cancionAActualizar /*, int idUsuario, IDbTransaction transaction */);
Task<bool> DeleteAsync(int id /*, int idUsuario, IDbTransaction transaction */);
Task<bool> ExistsByTemaAndInterpreteAsync(string tema, string interprete, int? excludeId = null); // Ejemplo de unicidad
Task<IEnumerable<Cancion>> GetRandomCancionesAsync(int count);
}
}

View File

@@ -0,0 +1,18 @@
using GestionIntegral.Api.Models.Radios;
using System.Collections.Generic;
using System.Threading.Tasks;
// using System.Data; // Solo si se usa transacción para historial
namespace GestionIntegral.Api.Data.Repositories.Radios
{
public interface IRitmoRepository
{
Task<IEnumerable<Ritmo>> GetAllAsync(string? nombreFilter);
Task<Ritmo?> GetByIdAsync(int id);
Task<Ritmo?> CreateAsync(Ritmo nuevoRitmo /*, int idUsuario, IDbTransaction transaction - si hay historial */);
Task<bool> UpdateAsync(Ritmo ritmoAActualizar /*, int idUsuario, IDbTransaction transaction */);
Task<bool> DeleteAsync(int id /*, int idUsuario, IDbTransaction transaction */);
Task<bool> ExistsByNameAsync(string nombreRitmo, int? excludeId = null);
Task<bool> IsInUseAsync(int id); // Verificar si se usa en rad_dtCanciones
}
}

View File

@@ -0,0 +1,149 @@
using Dapper;
using GestionIntegral.Api.Models.Radios;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Radios
{
public class RitmoRepository : IRitmoRepository
{
private readonly DbConnectionFactory _cf;
private readonly ILogger<RitmoRepository> _log;
public RitmoRepository(DbConnectionFactory cf, ILogger<RitmoRepository> log)
{
_cf = cf;
_log = log;
}
public async Task<IEnumerable<Ritmo>> GetAllAsync(string? nombreFilter)
{
var sqlBuilder = new StringBuilder("SELECT Id, Ritmo AS NombreRitmo FROM dbo.rad_dtRitmos WHERE 1=1");
var parameters = new DynamicParameters();
if (!string.IsNullOrWhiteSpace(nombreFilter))
{
sqlBuilder.Append(" AND Ritmo LIKE @NombreFilterParam");
parameters.Add("NombreFilterParam", $"%{nombreFilter}%");
}
sqlBuilder.Append(" ORDER BY Ritmo;");
try
{
using var connection = _cf.CreateConnection();
return await connection.QueryAsync<Ritmo>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener todos los Ritmos.");
return Enumerable.Empty<Ritmo>();
}
}
public async Task<Ritmo?> GetByIdAsync(int id)
{
const string sql = "SELECT Id, Ritmo AS NombreRitmo FROM dbo.rad_dtRitmos WHERE Id = @IdParam";
try
{
using var connection = _cf.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<Ritmo>(sql, new { IdParam = id });
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener Ritmo por ID: {IdRitmo}", id);
return null;
}
}
public async Task<bool> ExistsByNameAsync(string nombreRitmo, int? excludeId = null)
{
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.rad_dtRitmos WHERE Ritmo = @NombreRitmoParam");
var parameters = new DynamicParameters();
parameters.Add("NombreRitmoParam", nombreRitmo);
if (excludeId.HasValue)
{
sqlBuilder.Append(" AND Id != @ExcludeIdParam");
parameters.Add("ExcludeIdParam", excludeId.Value);
}
try
{
using var connection = _cf.CreateConnection();
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error en ExistsByNameAsync para Ritmo: {NombreRitmo}", nombreRitmo);
return true;
}
}
public async Task<bool> IsInUseAsync(int id)
{
const string sql = "SELECT TOP 1 1 FROM dbo.rad_dtCanciones WHERE Ritmo = @IdParam"; // Columna 'Ritmo' en Canciones es FK a 'Id' en Ritmos
try
{
using var connection = _cf.CreateConnection();
return await connection.ExecuteScalarAsync<bool>(sql, new { IdParam = id });
}
catch (Exception ex)
{
_log.LogError(ex, "Error en IsInUseAsync para Ritmo ID: {IdRitmo}", id);
return true;
}
}
public async Task<Ritmo?> CreateAsync(Ritmo nuevoRitmo /*, int idUsuario, IDbTransaction transaction */)
{
// Sin historial, no se necesita transacción aquí para una sola inserción.
const string sqlInsert = @"
INSERT INTO dbo.rad_dtRitmos (Ritmo)
OUTPUT INSERTED.Id, INSERTED.Ritmo AS NombreRitmo
VALUES (@NombreRitmo);";
try
{
using var connection = _cf.CreateConnection();
return await connection.QuerySingleAsync<Ritmo>(sqlInsert, nuevoRitmo);
}
catch(Exception ex)
{
_log.LogError(ex, "Error al crear Ritmo: {NombreRitmo}", nuevoRitmo.NombreRitmo);
return null;
}
}
public async Task<bool> UpdateAsync(Ritmo ritmoAActualizar /*, int idUsuario, IDbTransaction transaction */)
{
const string sqlUpdate = "UPDATE dbo.rad_dtRitmos SET Ritmo = @NombreRitmo WHERE Id = @Id;";
try
{
using var connection = _cf.CreateConnection();
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, ritmoAActualizar);
return rowsAffected == 1;
}
catch(Exception ex)
{
_log.LogError(ex, "Error al actualizar Ritmo ID: {IdRitmo}", ritmoAActualizar.Id);
return false;
}
}
public async Task<bool> DeleteAsync(int id /*, int idUsuario, IDbTransaction transaction */)
{
const string sqlDelete = "DELETE FROM dbo.rad_dtRitmos WHERE Id = @IdParam";
try
{
using var connection = _cf.CreateConnection();
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id });
return rowsAffected == 1;
}
catch(Exception ex)
{
_log.LogError(ex, "Error al eliminar Ritmo ID: {IdRitmo}", id);
return false;
}
}
}
}

View File

@@ -1,4 +1,5 @@
using GestionIntegral.Api.Models.Usuarios; // Para Usuario
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Data;
@@ -16,10 +17,10 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
// Task<bool> DeleteAsync(int id, int idUsuarioModificador, IDbTransaction transaction);
Task<bool> SetPasswordAsync(int userId, string newHash, string newSalt, bool debeCambiarClave, int idUsuarioModificador, IDbTransaction transaction);
Task<bool> UserExistsAsync(string username, int? excludeId = null);
// Para el DTO de listado
Task<IEnumerable<(Usuario Usuario, string NombrePerfil)>> GetAllWithProfileNameAsync(string? userFilter, string? nombreFilter);
Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id);
Task<IEnumerable<UsuarioHistorialDto>> GetHistorialByUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta);
Task<IEnumerable<UsuarioHistorialDto>> GetAllHistorialAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModificoFilter, string? tipoModFilter);
}
}

View File

@@ -1,5 +1,6 @@
using Dapper;
using GestionIntegral.Api.Models.Usuarios;
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Data;
@@ -102,7 +103,7 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
return null;
}
}
public async Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id)
public async Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id)
{
const string sql = @"
SELECT u.*, p.perfil AS NombrePerfil
@@ -162,7 +163,7 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
}
catch (Exception ex)
{
_logger.LogError(ex, "Error en UserExistsAsync para username: {Username}", username);
_logger.LogError(ex, "Error en UserExistsAsync para username: {Username}", username);
return true; // Asumir que existe para prevenir duplicados
}
}
@@ -229,13 +230,20 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
await connection.ExecuteAsync(sqlInsertHistorico, new
{
IdUsuarioHist = usuarioActual.Id,
UserAntHist = usuarioActual.User, UserNvoHist = usuarioAActualizar.User, // Aunque no cambiemos User, lo registramos
HabilitadaAntHist = usuarioActual.Habilitada, HabilitadaNvaHist = usuarioAActualizar.Habilitada,
SupAdminAntHist = usuarioActual.SupAdmin, SupAdminNvoHist = usuarioAActualizar.SupAdmin,
NombreAntHist = usuarioActual.Nombre, NombreNvoHist = usuarioAActualizar.Nombre,
ApellidoAntHist = usuarioActual.Apellido, ApellidoNvoHist = usuarioAActualizar.Apellido,
IdPerfilAntHist = usuarioActual.IdPerfil, IdPerfilNvoHist = usuarioAActualizar.IdPerfil,
DebeCambiarClaveAntHist = usuarioActual.DebeCambiarClave, DebeCambiarClaveNvaHist = usuarioAActualizar.DebeCambiarClave,
UserAntHist = usuarioActual.User,
UserNvoHist = usuarioAActualizar.User, // Aunque no cambiemos User, lo registramos
HabilitadaAntHist = usuarioActual.Habilitada,
HabilitadaNvaHist = usuarioAActualizar.Habilitada,
SupAdminAntHist = usuarioActual.SupAdmin,
SupAdminNvoHist = usuarioAActualizar.SupAdmin,
NombreAntHist = usuarioActual.Nombre,
NombreNvoHist = usuarioAActualizar.Nombre,
ApellidoAntHist = usuarioActual.Apellido,
ApellidoNvoHist = usuarioAActualizar.Apellido,
IdPerfilAntHist = usuarioActual.IdPerfil,
IdPerfilNvoHist = usuarioAActualizar.IdPerfil,
DebeCambiarClaveAntHist = usuarioActual.DebeCambiarClave,
DebeCambiarClaveNvaHist = usuarioAActualizar.DebeCambiarClave,
IdUsuarioModHist = idUsuarioModificador,
FechaModHist = DateTime.Now,
TipoModHist = "Actualizado"
@@ -247,9 +255,9 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
public async Task<bool> SetPasswordAsync(int userId, string newHash, string newSalt, bool debeCambiarClave, int idUsuarioModificador, IDbTransaction transaction)
{
var connection = transaction.Connection!;
var usuarioActual = await connection.QuerySingleOrDefaultAsync<Usuario>(
"SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id", new { Id = userId }, transaction);
var connection = transaction.Connection!;
var usuarioActual = await connection.QuerySingleOrDefaultAsync<Usuario>(
"SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id", new { Id = userId }, transaction);
if (usuarioActual == null) throw new KeyNotFoundException("Usuario no encontrado para cambiar contraseña.");
@@ -289,5 +297,122 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
}, transaction);
return rowsAffected == 1;
}
public async Task<IEnumerable<UsuarioHistorialDto>> GetHistorialByUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta)
{
var sqlBuilder = new StringBuilder(@"
SELECT
h.IdHist,
h.IdUsuario AS IdUsuarioAfectado,
uPrincipal.[User] AS UserAfectado, -- DELIMITADO CON []
h.UserAnt, h.UserNvo,
h.HabilitadaAnt, h.HabilitadaNva,
h.SupAdminAnt, h.SupAdminNvo,
h.NombreAnt, h.NombreNvo,
h.ApellidoAnt, h.ApellidoNvo,
h.IdPerfilAnt, h.IdPerfilNvo,
pAnt.perfil AS NombrePerfilAnt,
pNvo.perfil AS NombrePerfilNvo,
h.DebeCambiarClaveAnt, h.DebeCambiarClaveNva,
h.Id_UsuarioMod AS IdUsuarioModifico,
ISNULL(uMod.Nombre + ' ' + uMod.Apellido, 'Sistema') AS NombreUsuarioModifico,
h.FechaMod AS FechaModificacion,
h.TipoMod AS TipoModificacion
FROM dbo.gral_Usuarios_H h
INNER JOIN dbo.gral_Usuarios uPrincipal ON h.IdUsuario = uPrincipal.Id -- No necesita delimitador aquí si 'Id' es el nombre de la columna PK en gral_Usuarios
LEFT JOIN dbo.gral_Usuarios uMod ON h.Id_UsuarioMod = uMod.Id
LEFT JOIN dbo.gral_Perfiles pAnt ON h.IdPerfilAnt = pAnt.id
LEFT JOIN dbo.gral_Perfiles pNvo ON h.IdPerfilNvo = pNvo.id
WHERE h.IdUsuario = @IdUsuarioAfectadoParam");
var parameters = new DynamicParameters();
parameters.Add("IdUsuarioAfectadoParam", idUsuarioAfectado);
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)); // Hasta el final del día
}
sqlBuilder.Append(" ORDER BY h.FechaMod DESC, h.IdHist DESC;");
try
{
using var connection = _connectionFactory.CreateConnection();
return await connection.QueryAsync<UsuarioHistorialDto>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener historial para Usuario ID: {IdUsuarioAfectado}", idUsuarioAfectado);
return Enumerable.Empty<UsuarioHistorialDto>();
}
}
public async Task<IEnumerable<UsuarioHistorialDto>> GetAllHistorialAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModificoFilter, string? tipoModFilter)
{
var sqlBuilder = new StringBuilder(@"
SELECT
h.IdHist,
h.IdUsuario AS IdUsuarioAfectado,
uPrincipal.[User] AS UserAfectado, -- DELIMITADO CON []
h.UserAnt, h.UserNvo,
h.HabilitadaAnt, h.HabilitadaNva,
h.SupAdminAnt, h.SupAdminNvo,
h.NombreAnt, h.NombreNvo,
h.ApellidoAnt, h.ApellidoNvo,
h.IdPerfilAnt, h.IdPerfilNvo,
pAnt.perfil AS NombrePerfilAnt,
pNvo.perfil AS NombrePerfilNvo,
h.DebeCambiarClaveAnt, h.DebeCambiarClaveNva,
h.Id_UsuarioMod AS IdUsuarioModifico,
ISNULL(uMod.Nombre + ' ' + uMod.Apellido, 'Sistema') AS NombreUsuarioModifico,
h.FechaMod AS FechaModificacion,
h.TipoMod AS TipoModificacion
FROM dbo.gral_Usuarios_H h
INNER JOIN dbo.gral_Usuarios uPrincipal ON h.IdUsuario = uPrincipal.Id
LEFT JOIN dbo.gral_Usuarios uMod ON h.Id_UsuarioMod = uMod.Id
LEFT JOIN dbo.gral_Perfiles pAnt ON h.IdPerfilAnt = pAnt.id
LEFT JOIN dbo.gral_Perfiles pNvo ON h.IdPerfilNvo = pNvo.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 (idUsuarioModificoFilter.HasValue)
{
sqlBuilder.Append(" AND h.Id_UsuarioMod = @IdUsuarioModFilterParam");
parameters.Add("IdUsuarioModFilterParam", idUsuarioModificoFilter.Value);
}
if (!string.IsNullOrWhiteSpace(tipoModFilter))
{
sqlBuilder.Append(" AND h.TipoMod LIKE @TipoModFilterParam");
parameters.Add("TipoModFilterParam", $"%{tipoModFilter}%");
}
sqlBuilder.Append(" ORDER BY h.FechaMod DESC, h.IdHist DESC;");
try
{
using var connection = _connectionFactory.CreateConnection();
return await connection.QueryAsync<UsuarioHistorialDto>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener todo el historial de Usuarios.");
return Enumerable.Empty<UsuarioHistorialDto>();
}
}
}
}

View File

@@ -11,6 +11,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.3" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageReference Include="NPOI" Version="2.7.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.9.0" />
</ItemGroup>

View File

@@ -0,0 +1,16 @@
using System;
namespace GestionIntegral.Api.Models.Contables
{
public class NotaCreditoDebito // Corresponde a cue_CreditosDebitos
{
public int IdNota { get; set; } // Id_Nota (PK, Identity)
public string Destino { get; set; } = string.Empty; // "Distribuidores" o "Canillas"
public int IdDestino { get; set; } // Id del distribuidor o canillita
public string? Referencia { get; set; } // varchar(50), NULL
public string Tipo { get; set; } = string.Empty; // "Debito" o "Credito"
public DateTime Fecha { get; set; }
public decimal Monto { get; set; }
public string? Observaciones { get; set; } // varchar(250), NULL
public int IdEmpresa { get; set; } // Empresa a la que corresponde el saldo afectado
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace GestionIntegral.Api.Models.Contables
{
public class NotaCreditoDebitoHistorico // Corresponde a cue_CreditosDebitos_H
{
// No hay PK explícita en _H
public int Id_Nota { get; set; } // Coincide con columna en _H
public string Destino { get; set; } = string.Empty;
public int Id_Destino { get; set; }
public string? Referencia { get; set; }
public string Tipo { get; set; } = string.Empty;
public DateTime Fecha { get; set; }
public decimal Monto { get; set; }
public string? Observaciones { get; set; }
public int Id_Empresa { get; set; }
public int Id_Usuario { get; set; }
public DateTime FechaMod { get; set; }
public string TipoMod { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace GestionIntegral.Api.Models.Contables
{
public class PagoDistribuidor // Corresponde a cue_PagosDistribuidor
{
public int IdPago { get; set; } // Id_Pago (PK, Identity)
public int IdDistribuidor { get; set; }
public DateTime Fecha { get; set; }
public string TipoMovimiento { get; set; } = string.Empty; // "Recibido" o "Realizado"
public int Recibo { get; set; } // Nro de recibo
public decimal Monto { get; set; }
public int IdTipoPago { get; set; } // FK a cue_dtTipopago
public string? Detalle { get; set; }
public int IdEmpresa { get; set; } // Empresa a la que corresponde el saldo afectado
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace GestionIntegral.Api.Models.Contables
{
public class PagoDistribuidorHistorico // Corresponde a cue_PagosDistribuidor_H
{
// No hay PK explícita en _H
public int Id_Pago { get; set; } // Coincide con columna en _H
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; }
public int Id_Usuario { get; set; }
public DateTime FechaMod { get; set; }
public string TipoMod { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace GestionIntegral.Api.Models.Distribucion
{
public class ControlDevoluciones // Corresponde a dist_dtCtrlDevoluciones
{
public int IdControl { get; set; } // Id_Control (PK, Identity)
public int IdEmpresa { get; set; }
public DateTime Fecha { get; set; }
public int Entrada { get; set; } // Cantidad total de ejemplares que ingresaron como devolución
public int Sobrantes { get; set; }
public string? Detalle { get; set; }
public int SinCargo { get; set; } // DEFAULT 0 NOT NULL
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace GestionIntegral.Api.Models.Distribucion
{
public class ControlDevolucionesHistorico // Corresponde a dist_dtCtrlDevoluciones_H
{
// No hay PK explícita en _H
public int Id_Control { get; set; } // Coincide con columna en _H
public int Id_Empresa { get; set; }
public DateTime Fecha { get; set; }
public int Entrada { get; set; }
public int Sobrantes { get; set; }
public string? Detalle { get; set; }
public int SinCargo { get; set; }
public int Id_Usuario { get; set; }
public DateTime FechaMod { get; set; }
public string TipoMod { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace GestionIntegral.Api.Models.Distribucion
{
public class EntradaSalidaCanilla // Corresponde a dist_EntradasSalidasCanillas
{
public int IdParte { get; set; }
public int IdPublicacion { get; set; }
public int IdCanilla { get; set; }
public DateTime Fecha { get; set; }
public int CantSalida { get; set; }
public int CantEntrada { get; set; }
public int IdPrecio { get; set; } // FK a dist_Precios
public int IdRecargo { get; set; } // FK a dist_RecargoZona (0 si no aplica)
public int IdPorcMon { get; set; } // FK a dist_PorcMonPagoCanilla (0 si no aplica)
public string? Observacion { get; set; }
public bool Liquidado { get; set; }
public DateTime? FechaLiquidado { get; set; }
public int? UserLiq { get; set; } // Usuario que liquidó
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Contables
{
public class CreateNotaDto
{
[Required(ErrorMessage = "El tipo de destino es obligatorio ('Distribuidores' o 'Canillas').")]
[RegularExpression("^(Distribuidores|Canillas)$", ErrorMessage = "Destino debe ser 'Distribuidores' o 'Canillas'.")]
public string Destino { get; set; } = string.Empty;
[Required(ErrorMessage = "El destinatario es obligatorio.")]
[Range(1, int.MaxValue)]
public int IdDestino { get; set; }
[StringLength(50)]
public string? Referencia { get; set; }
[Required(ErrorMessage = "El tipo de nota es obligatorio ('Debito' o 'Credito').")]
[RegularExpression("^(Debito|Credito)$", ErrorMessage = "Tipo debe ser 'Debito' o 'Credito'.")]
public string Tipo { get; set; } = string.Empty;
[Required]
public DateTime Fecha { get; set; }
[Required, Range(0.01, (double)decimal.MaxValue, ErrorMessage = "El monto debe ser mayor a cero.")]
public decimal Monto { get; set; }
[StringLength(250)]
public string? Observaciones { get; set; }
[Required]
public int IdEmpresa { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Contables
{
public class CreatePagoDistribuidorDto
{
[Required]
public int IdDistribuidor { get; set; }
[Required]
public DateTime Fecha { get; set; }
[Required]
[RegularExpression("^(Recibido|Realizado)$", ErrorMessage = "Tipo de movimiento debe ser 'Recibido' o 'Realizado'.")]
public string TipoMovimiento { get; set; } = string.Empty;
[Required, Range(1, int.MaxValue)]
public int Recibo { get; set; } // Nro de Recibo
[Required, Range(0.01, (double)decimal.MaxValue, ErrorMessage = "El monto debe ser mayor a cero.")]
public decimal Monto { get; set; }
[Required]
public int IdTipoPago { get; set; }
[StringLength(150)]
public string? Detalle { get; set; }
[Required]
public int IdEmpresa { get; set; } // Empresa cuyo saldo se verá afectado
}
}

View File

@@ -0,0 +1,17 @@
namespace GestionIntegral.Api.Dtos.Contables
{
public class NotaCreditoDebitoDto
{
public int IdNota { get; set; }
public string Destino { get; set; } = string.Empty; // "Distribuidores", "Canillas"
public int IdDestino { get; set; }
public string NombreDestinatario { get; set; } = string.Empty; // Nombre del Distribuidor o Canillita
public string? Referencia { get; set; }
public string Tipo { get; set; } = string.Empty; // "Debito", "Credito"
public string Fecha { get; set; } = string.Empty; // yyyy-MM-dd
public decimal Monto { get; set; }
public string? Observaciones { get; set; }
public int IdEmpresa { get; set; }
public string NombreEmpresa { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,18 @@
namespace GestionIntegral.Api.Dtos.Contables
{
public class PagoDistribuidorDto
{
public int IdPago { get; set; }
public int IdDistribuidor { get; set; }
public string NombreDistribuidor { get; set; } = string.Empty;
public string Fecha { get; set; } = string.Empty; // yyyy-MM-dd
public string TipoMovimiento { get; set; } = string.Empty; // "Recibido" / "Realizado"
public int Recibo { get; set; }
public decimal Monto { get; set; }
public int IdTipoPago { get; set; }
public string NombreTipoPago { get; set; } = string.Empty;
public string? Detalle { get; set; }
public int IdEmpresa { get; set; }
public string NombreEmpresa { get; set; } = string.Empty; // Empresa del saldo afectado
}
}

View File

@@ -0,0 +1,15 @@
// Para las notas, la edición es limitada. Si se comete un error grave, se anula y se crea una nueva.
// Podríamos permitir cambiar Monto y Observaciones.
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Contables
{
public class UpdateNotaDto
{
[Required, Range(0.01, (double)decimal.MaxValue, ErrorMessage = "El monto debe ser mayor a cero.")]
public decimal Monto { get; set; }
[StringLength(250)]
public string? Observaciones { get; set; }
// No se permite cambiar Destino, IdDestino, Tipo, Fecha, Referencia, IdEmpresa de una nota existente.
}
}

View File

@@ -0,0 +1,17 @@
// La edición de un pago puede ser delicada por la afectación de saldos.
// Podríamos permitir cambiar Monto, TipoPago, Detalle.
// Cambiar Fecha, Distribuidor, Empresa, TipoMovimiento, Recibo podría requerir anular y recrear.
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Contables
{
public class UpdatePagoDistribuidorDto
{
[Required, Range(0.01, (double)decimal.MaxValue, ErrorMessage = "El monto debe ser mayor a cero.")]
public decimal Monto { get; set; }
[Required]
public int IdTipoPago { get; set; }
[StringLength(150)]
public string? Detalle { get; set; }
// Los campos IdDistribuidor, Fecha, TipoMovimiento, Recibo, IdEmpresa no se cambian aquí.
}
}

View File

@@ -0,0 +1,14 @@
namespace GestionIntegral.Api.Dtos.Distribucion
{
public class ControlDevolucionesDto
{
public int IdControl { get; set; }
public int IdEmpresa { get; set; }
public string NombreEmpresa { get; set; } = string.Empty;
public string Fecha { get; set; } = string.Empty; // yyyy-MM-dd
public int Entrada { get; set; }
public int Sobrantes { get; set; }
public string? Detalle { get; set; }
public int SinCargo { get; set; }
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Distribucion
{
public class CreateBulkEntradaSalidaCanillaDto
{
[Required(ErrorMessage = "El ID del canillita es obligatorio.")]
public int IdCanilla { get; set; }
[Required(ErrorMessage = "La fecha del movimiento es obligatoria.")]
public DateTime Fecha { get; set; } // Fecha común para todos los ítems
[Required(ErrorMessage = "Debe haber al menos un ítem de movimiento.")]
[MinLength(1, ErrorMessage = "Debe agregar al menos una publicación.")]
public List<EntradaSalidaCanillaItemDto> Items { get; set; } = new List<EntradaSalidaCanillaItemDto>();
// Validar que no haya publicaciones duplicadas en la lista de items
[CustomValidation(typeof(CreateBulkEntradaSalidaCanillaDto), nameof(ValidateNoDuplicatePublications))]
public string? DuplicateError { get; set; }
public static ValidationResult? ValidateNoDuplicatePublications(CreateBulkEntradaSalidaCanillaDto dto, ValidationContext context)
{
if (dto.Items != null)
{
var duplicatePublications = dto.Items
.GroupBy(item => item.IdPublicacion)
.Where(group => group.Count() > 1)
.Select(group => group.Key)
.ToList();
if (duplicatePublications.Any())
{
return new ValidationResult($"No puede agregar la misma publicación varias veces. Publicaciones duplicadas: {string.Join(", ", duplicatePublications)}");
}
}
return ValidationResult.Success;
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Distribucion
{
public class CreateControlDevolucionesDto
{
[Required]
public int IdEmpresa { get; set; }
[Required]
public DateTime Fecha { get; set; }
[Required, Range(0, int.MaxValue)] // Entrada puede ser 0 si solo hay sobrantes/sin cargo
public int Entrada { get; set; }
[Required, Range(0, int.MaxValue)]
public int Sobrantes { get; set; }
[StringLength(250)]
public string? Detalle { get; set; }
[Required, Range(0, int.MaxValue)]
public int SinCargo { get; set; } = 0; // Default 0
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Distribucion
{
public class CreateEntradaSalidaCanillaDto
{
[Required]
public int IdPublicacion { get; set; }
[Required]
public int IdCanilla { get; set; }
[Required]
public DateTime Fecha { get; set; } // Fecha del movimiento (retiro/devolución)
[Required, Range(0, int.MaxValue)] // Puede retirar 0 si es solo para registrar devolución
public int CantSalida { get; set; }
[Required, Range(0, int.MaxValue)]
public int CantEntrada { get; set; }
[StringLength(150)]
public string? Observacion { get; set; }
// Liquidado, FechaLiquidado, UserLiq se manejan por una acción de "Liquidar" separada.
// IdPrecio, IdRecargo, IdPorcMon se determinan en el backend.
[CustomValidation(typeof(CreateEntradaSalidaCanillaDto), nameof(ValidateCantidades))]
public string? CantidadesError { get; set; } // Dummy para validación
public static ValidationResult? ValidateCantidades(CreateEntradaSalidaCanillaDto dto, ValidationContext context)
{
if (dto.CantEntrada > dto.CantSalida)
{
return new ValidationResult("La cantidad de entrada (devolución) no puede ser mayor a la cantidad de salida (retiro).");
}
return ValidationResult.Success;
}
}
}

View File

@@ -0,0 +1,26 @@
namespace GestionIntegral.Api.Dtos.Distribucion
{
public class EntradaSalidaCanillaDto
{
public int IdParte { get; set; }
public int IdPublicacion { get; set; }
public string NombrePublicacion { get; set; } = string.Empty;
public int IdCanilla { get; set; }
public string NomApeCanilla { get; set; } = string.Empty;
public bool CanillaEsAccionista { get; set; }
public string Fecha { get; set; } = string.Empty; // yyyy-MM-dd
public int CantSalida { get; set; }
public int CantEntrada { get; set; }
public int Vendidos { get; set; } // Calculado
public string? Observacion { get; set; }
public bool Liquidado { get; set; }
public string? FechaLiquidado { get; set; } // yyyy-MM-dd
public int? UserLiq { get; set; }
public string? NombreUserLiq { get; set; } // Para mostrar en UI
public decimal MontoARendir { get; set; } // Calculado por el backend
public decimal PrecioUnitarioAplicado { get; set; } // Info para UI
public decimal RecargoAplicado { get; set; } // Info para UI
public decimal PorcentajeOMontoCanillaAplicado { get; set; } // Info para UI
public bool EsPorcentajeCanilla { get; set; } // Info para UI
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Distribucion
{
public class EntradaSalidaCanillaItemDto
{
[Required(ErrorMessage = "El ID de la publicación es obligatorio.")]
public int IdPublicacion { get; set; }
[Required(ErrorMessage = "La cantidad de salida es obligatoria.")]
[Range(0, int.MaxValue, ErrorMessage = "La cantidad de salida debe ser un número positivo o cero.")]
public int CantSalida { get; set; }
[Required(ErrorMessage = "La cantidad de entrada es obligatoria.")]
[Range(0, int.MaxValue, ErrorMessage = "La cantidad de entrada debe ser un número positivo o cero.")]
public int CantEntrada { get; set; }
[StringLength(150)]
public string? Observacion { get; set; } // Observación por línea
// Validar que CantEntrada no sea mayor que CantSalida
[CustomValidation(typeof(EntradaSalidaCanillaItemDto), nameof(ValidateCantidades))]
public string? CantidadesError { get; set; }
public static ValidationResult? ValidateCantidades(EntradaSalidaCanillaItemDto item, ValidationContext context)
{
if (item.CantEntrada > item.CantSalida)
{
return new ValidationResult("La cantidad de entrada (devolución) no puede ser mayor a la cantidad de salida (retiro) para esta publicación.", new[] { nameof(CantEntrada) });
}
return ValidationResult.Success;
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Distribucion
{
public class LiquidarMovimientosCanillaRequestDto // Para liquidar uno o más movimientos
{
[Required]
[MinLength(1)]
public List<int> IdsPartesALiquidar { get; set; } = new List<int>();
[Required]
public DateTime FechaLiquidacion { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Distribucion
{
public class UpdateControlDevolucionesDto
{
// IdEmpresa y Fecha no deberían cambiar para un registro existente. Si cambian, es un nuevo registro.
[Required, Range(0, int.MaxValue)]
public int Entrada { get; set; }
[Required, Range(0, int.MaxValue)]
public int Sobrantes { get; set; }
[StringLength(250)]
public string? Detalle { get; set; }
[Required, Range(0, int.MaxValue)]
public int SinCargo { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
// Similar a E/S Distribuidores, la edición es limitada para no afectar cálculos complejos ya hechos.
// Principalmente para corregir cantidades si aún no está liquidado.
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Distribucion
{
public class UpdateEntradaSalidaCanillaDto
{
[Required, Range(0, int.MaxValue)]
public int CantSalida { get; set; }
[Required, Range(0, int.MaxValue)]
public int CantEntrada { get; set; }
[StringLength(150)]
public string? Observacion { get; set; }
[CustomValidation(typeof(UpdateEntradaSalidaCanillaDto), nameof(ValidateCantidades))]
public string? CantidadesError { get; set; } // Dummy para validación
public static ValidationResult? ValidateCantidades(UpdateEntradaSalidaCanillaDto dto, ValidationContext context)
{
if (dto.CantEntrada > dto.CantSalida)
{
return new ValidationResult("La cantidad de entrada no puede ser mayor a la de salida.");
}
return ValidationResult.Success;
}
}
}

View File

@@ -0,0 +1,18 @@
namespace GestionIntegral.Api.Dtos.Radios
{
public class CancionDto
{
public int Id { get; set; }
public string? Tema { get; set; }
public string? CompositorAutor { get; set; }
public string? Interprete { get; set; }
public string? Sello { get; set; }
public string? Placa { get; set; }
public int? Pista { get; set; }
public string? Introduccion { get; set; }
public int? IdRitmo { get; set; } // Renombrado de Ritmo a IdRitmo para claridad en DTO
public string? NombreRitmo { get; set; } // Para mostrar en UI
public string? Formato { get; set; }
public string? Album { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
namespace GestionIntegral.Api.Dtos.Radios
{
public class CancionEnListaDto // Lo que se va a escribir en cada fila del Excel
{
public string? Tema { get; set; }
public string? Interprete { get; set; }
public string? CompositorAutor { get; set; }
public string? Album { get; set; }
public string? Sello { get; set; }
public string? Placa { get; set; } // Nro de Catálogo / Identificador
public int? Pista { get; set; }
public string? Ritmo { get; set; } // Nombre del ritmo
public string? Formato { get; set; }
public string? Introduccion { get; set; } // Para saber duración o cues
// Podrías añadir campos como "HoraProgramada" si la generación es por franjas
}
}

View File

@@ -0,0 +1,34 @@
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Radios
{
public class CreateCancionDto
{
[StringLength(255)]
public string? Tema { get; set; } // Tema podría ser el campo más importante
[StringLength(255)]
public string? CompositorAutor { get; set; }
[StringLength(255)]
public string? Interprete { get; set; }
[StringLength(255)]
public string? Sello { get; set; }
[StringLength(255)]
public string? Placa { get; set; }
public int? Pista { get; set; }
[StringLength(255)]
public string? Introduccion { get; set; }
public int? IdRitmo { get; set; } // FK
[StringLength(255)]
public string? Formato { get; set; }
[StringLength(255)]
public string? Album { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Radios
{
public class CreateRitmoDto
{
[StringLength(255, ErrorMessage = "El nombre del ritmo no puede exceder los 255 caracteres.")]
// Aunque la BD permite NULL, para crear usualmente se requeriría un nombre.
// Si se permite crear ritmos sin nombre, quitar [Required]
[Required(ErrorMessage = "El nombre del ritmo es obligatorio.")]
public string NombreRitmo { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Radios
{
public class GenerarListaRadioRequestDto
{
[Required(ErrorMessage = "El mes es obligatorio.")]
[Range(1, 12, ErrorMessage = "El mes debe estar entre 1 y 12.")]
public int Mes { get; set; }
[Required(ErrorMessage = "El año es obligatorio.")]
[Range(2000, 2999, ErrorMessage = "El año debe ser un valor válido (ej. 2024).")] // Ajusta el rango si es necesario
public int Anio { get; set; }
[Required(ErrorMessage = "La institución es obligatoria.")]
[RegularExpression("^(AADI|SADAIC)$", ErrorMessage = "La institución debe ser 'AADI' o 'SADAIC'.")]
public string Institucion { get; set; } = string.Empty;
[Required(ErrorMessage = "La radio es obligatoria.")]
[RegularExpression("^(FM 99.1|FM 100.3)$", ErrorMessage = "La radio debe ser 'FM 99.1' o 'FM 100.3'.")]
public string Radio { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,16 @@
namespace GestionIntegral.Api.Dtos.Radios
{
public class ProgramacionHorariaExcelDto
{
public int Dia { get; set; }
public int Hora { get; set; }
public string? TituloObra { get; set; } // Mapeado desde Cancion.Tema
public string? CompositorAutor { get; set; }
public string? Interprete { get; set; }
public string? Sello { get; set; }
public string? Album { get; set; }
// No se incluyen Pista, Introducción, Formato, Placa, NombreRitmo
// porque el Excel original de VB.NET no los tenía.
// Si se decide mantenerlos en el futuro, se podrían añadir aquí.
}
}

View File

@@ -0,0 +1,8 @@
namespace GestionIntegral.Api.Dtos.Radios
{
public class RitmoDto
{
public int Id { get; set; }
public string? NombreRitmo { get; set; }
}
}

View File

@@ -0,0 +1,34 @@
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Radios
{
public class UpdateCancionDto
{
[StringLength(255)]
public string? Tema { get; set; }
[StringLength(255)]
public string? CompositorAutor { get; set; }
[StringLength(255)]
public string? Interprete { get; set; }
[StringLength(255)]
public string? Sello { get; set; }
[StringLength(255)]
public string? Placa { get; set; }
public int? Pista { get; set; }
[StringLength(255)]
public string? Introduccion { get; set; }
public int? IdRitmo { get; set; }
[StringLength(255)]
public string? Formato { get; set; }
[StringLength(255)]
public string? Album { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Radios
{
public class UpdateRitmoDto
{
[StringLength(255, ErrorMessage = "El nombre del ritmo no puede exceder los 255 caracteres.")]
[Required(ErrorMessage = "El nombre del ritmo es obligatorio.")]
public string NombreRitmo { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace GestionIntegral.Api.Dtos.Usuarios.Auditoria
{
public class UsuarioHistorialDto
{
public int IdHist { get; set; } // PK de la tabla gral_Usuarios_H
public int IdUsuarioAfectado { get; set; } // IdUsuario (el usuario cuyos datos cambiaron)
public string UserAfectado { get; set; } = string.Empty; // UserNvo (username del usuario afectado)
// Campos del historial
public string? UserAnt { get; set; }
public string UserNvo { get; set; } = string.Empty;
public bool? HabilitadaAnt { get; set; }
public bool HabilitadaNva { get; set; }
public bool? SupAdminAnt { get; set; }
public bool SupAdminNvo { get; set; }
public string? NombreAnt { get; set; }
public string NombreNvo { get; set; } = string.Empty;
public string? ApellidoAnt { get; set; }
public string ApellidoNvo { get; set; } = string.Empty;
public int? IdPerfilAnt { get; set; }
public int IdPerfilNvo { get; set; }
public string? NombrePerfilAnt { get; set; } // Join con gral_Perfiles
public string NombrePerfilNvo { get; set; } = string.Empty; // Join con gral_Perfiles
public bool? DebeCambiarClaveAnt { get; set; }
public bool DebeCambiarClaveNva { get; set; }
// Auditoría del registro de historial
public int IdUsuarioModifico { get; set; } // Id_UsuarioMod
public string NombreUsuarioModifico { get; set; } = string.Empty; // Nombre del usuario que hizo el cambio
public DateTime FechaModificacion { get; set; } // FechaMod
public string TipoModificacion { get; set; } = string.Empty; // TipoMod
}
}

View File

@@ -0,0 +1,17 @@
namespace GestionIntegral.Api.Models.Radios
{
public class Cancion // Corresponde a rad_dtCanciones
{
public int Id { get; set; } // Id (PK, Identity)
public string? Tema { get; set; }
public string? CompositorAutor { get; set; }
public string? Interprete { get; set; }
public string? Sello { get; set; }
public string? Placa { get; set; }
public int? Pista { get; set; }
public string? Introduccion { get; set; }
public int? Ritmo { get; set; } // FK a rad_dtRitmos.Id
public string? Formato { get; set; }
public string? Album { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace GestionIntegral.Api.Models.Radios
{
public class Ritmo // Corresponde a rad_dtRitmos
{
public int Id { get; set; } // Id (PK, Identity)
public string? NombreRitmo { get; set; } // Columna "Ritmo" (nvarchar(255), NULL) - Renombrado para claridad
}
}

View File

@@ -4,9 +4,11 @@ using System.Text;
using GestionIntegral.Api.Data;
using GestionIntegral.Api.Services.Contables;
using GestionIntegral.Api.Services.Distribucion;
using GestionIntegral.Api.Services.Radios;
using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Data.Repositories.Impresion;
using GestionIntegral.Api.Data.Repositories.Radios;
using GestionIntegral.Api.Services.Impresion;
using GestionIntegral.Api.Services.Usuarios;
using GestionIntegral.Api.Data.Repositories.Usuarios;
@@ -65,6 +67,19 @@ builder.Services.AddScoped<ISalidaOtroDestinoRepository, SalidaOtroDestinoReposi
builder.Services.AddScoped<ISalidaOtroDestinoService, SalidaOtroDestinoService>();
builder.Services.AddScoped<IEntradaSalidaDistRepository, EntradaSalidaDistRepository>();
builder.Services.AddScoped<IEntradaSalidaDistService, EntradaSalidaDistService>();
builder.Services.AddScoped<IEntradaSalidaCanillaRepository, EntradaSalidaCanillaRepository>();
builder.Services.AddScoped<IEntradaSalidaCanillaService, EntradaSalidaCanillaService>();
builder.Services.AddScoped<IControlDevolucionesRepository, ControlDevolucionesRepository>();
builder.Services.AddScoped<IControlDevolucionesService, ControlDevolucionesService>();
builder.Services.AddScoped<IPagoDistribuidorRepository, PagoDistribuidorRepository>();
builder.Services.AddScoped<IPagoDistribuidorService, PagoDistribuidorService>();
builder.Services.AddScoped<INotaCreditoDebitoRepository, NotaCreditoDebitoRepository>();
builder.Services.AddScoped<INotaCreditoDebitoService, NotaCreditoDebitoService>();
builder.Services.AddScoped<IRitmoRepository, RitmoRepository>();
builder.Services.AddScoped<IRitmoService, RitmoService>();
builder.Services.AddScoped<ICancionRepository, CancionRepository>();
builder.Services.AddScoped<ICancionService, CancionService>();
builder.Services.AddScoped<IRadioListaService, RadioListaService>();
// --- Configuración de Autenticación JWT ---
var jwtSettings = builder.Configuration.GetSection("Jwt");

View File

@@ -0,0 +1,19 @@
using GestionIntegral.Api.Dtos.Contables;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Contables
{
public interface INotaCreditoDebitoService
{
Task<IEnumerable<NotaCreditoDebitoDto>> ObtenerTodosAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
string? destino, int? idDestino, int? idEmpresa, string? tipoNota);
Task<NotaCreditoDebitoDto?> ObtenerPorIdAsync(int idNota);
Task<(NotaCreditoDebitoDto? Nota, string? Error)> CrearAsync(CreateNotaDto createDto, int idUsuario);
Task<(bool Exito, string? Error)> ActualizarAsync(int idNota, UpdateNotaDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarAsync(int idNota, int idUsuario);
}
}

View File

@@ -0,0 +1,19 @@
using GestionIntegral.Api.Dtos.Contables;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Contables
{
public interface IPagoDistribuidorService
{
Task<IEnumerable<PagoDistribuidorDto>> ObtenerTodosAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idDistribuidor, int? idEmpresa, string? tipoMovimiento);
Task<PagoDistribuidorDto?> ObtenerPorIdAsync(int idPago);
Task<(PagoDistribuidorDto? Pago, string? Error)> CrearAsync(CreatePagoDistribuidorDto createDto, int idUsuario);
Task<(bool Exito, string? Error)> ActualizarAsync(int idPago, UpdatePagoDistribuidorDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarAsync(int idPago, int idUsuario);
}
}

View File

@@ -0,0 +1,226 @@
using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Contables;
using GestionIntegral.Api.Models.Contables;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Contables
{
public class NotaCreditoDebitoService : INotaCreditoDebitoService
{
private readonly INotaCreditoDebitoRepository _notaRepo;
private readonly IDistribuidorRepository _distribuidorRepo;
private readonly ICanillaRepository _canillaRepo;
private readonly IEmpresaRepository _empresaRepo;
private readonly ISaldoRepository _saldoRepo;
private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<NotaCreditoDebitoService> _logger;
public NotaCreditoDebitoService(
INotaCreditoDebitoRepository notaRepo,
IDistribuidorRepository distribuidorRepo,
ICanillaRepository canillaRepo,
IEmpresaRepository empresaRepo,
ISaldoRepository saldoRepo,
DbConnectionFactory connectionFactory,
ILogger<NotaCreditoDebitoService> logger)
{
_notaRepo = notaRepo;
_distribuidorRepo = distribuidorRepo;
_canillaRepo = canillaRepo;
_empresaRepo = empresaRepo;
_saldoRepo = saldoRepo;
_connectionFactory = connectionFactory;
_logger = logger;
}
private async Task<NotaCreditoDebitoDto> MapToDto(NotaCreditoDebito nota)
{
if (nota == null) return null!;
string nombreDestinatario = "N/A";
if (nota.Destino == "Distribuidores")
{
var distData = await _distribuidorRepo.GetByIdAsync(nota.IdDestino);
nombreDestinatario = distData.Distribuidor?.Nombre ?? "Distribuidor Desconocido";
}
else if (nota.Destino == "Canillas")
{
var canData = await _canillaRepo.GetByIdAsync(nota.IdDestino); // Asumiendo que GetByIdAsync devuelve una tupla
nombreDestinatario = canData.Canilla?.NomApe ?? "Canillita Desconocido";
}
var empresa = await _empresaRepo.GetByIdAsync(nota.IdEmpresa);
return new NotaCreditoDebitoDto
{
IdNota = nota.IdNota,
Destino = nota.Destino,
IdDestino = nota.IdDestino,
NombreDestinatario = nombreDestinatario,
Referencia = nota.Referencia,
Tipo = nota.Tipo,
Fecha = nota.Fecha.ToString("yyyy-MM-dd"),
Monto = nota.Monto,
Observaciones = nota.Observaciones,
IdEmpresa = nota.IdEmpresa,
NombreEmpresa = empresa?.Nombre ?? "Empresa Desconocida"
};
}
public async Task<IEnumerable<NotaCreditoDebitoDto>> ObtenerTodosAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
string? destino, int? idDestino, int? idEmpresa, string? tipoNota)
{
var notas = await _notaRepo.GetAllAsync(fechaDesde, fechaHasta, destino, idDestino, idEmpresa, tipoNota);
var dtos = new List<NotaCreditoDebitoDto>();
foreach (var nota in notas)
{
dtos.Add(await MapToDto(nota));
}
return dtos;
}
public async Task<NotaCreditoDebitoDto?> ObtenerPorIdAsync(int idNota)
{
var nota = await _notaRepo.GetByIdAsync(idNota);
return nota == null ? null : await MapToDto(nota);
}
public async Task<(NotaCreditoDebitoDto? Nota, string? Error)> CrearAsync(CreateNotaDto createDto, int idUsuario)
{
// Validar Destinatario
if (createDto.Destino == "Distribuidores")
{
if (await _distribuidorRepo.GetByIdSimpleAsync(createDto.IdDestino) == null)
return (null, "El distribuidor especificado no existe.");
}
else if (createDto.Destino == "Canillas")
{
if (await _canillaRepo.GetByIdSimpleAsync(createDto.IdDestino) == null) // Asumiendo GetByIdSimpleAsync en ICanillaRepository
return (null, "El canillita especificado no existe.");
}
else { return (null, "Tipo de destino inválido."); }
if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null)
return (null, "La empresa especificada no existe.");
var nuevaNota = new NotaCreditoDebito
{
Destino = createDto.Destino,
IdDestino = createDto.IdDestino,
Referencia = createDto.Referencia,
Tipo = createDto.Tipo,
Fecha = createDto.Fecha.Date,
Monto = createDto.Monto,
Observaciones = createDto.Observaciones,
IdEmpresa = createDto.IdEmpresa
};
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var notaCreada = await _notaRepo.CreateAsync(nuevaNota, idUsuario, transaction);
if (notaCreada == null) throw new DataException("Error al registrar la nota.");
// Afectar Saldo
// Nota de Crédito: Disminuye la deuda del destinatario (monto positivo para el servicio de saldo)
// Nota de Débito: Aumenta la deuda del destinatario (monto negativo para el servicio de saldo)
decimal montoAjusteSaldo = createDto.Tipo == "Credito" ? createDto.Monto : -createDto.Monto;
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaCreada.Destino, notaCreada.IdDestino, notaCreada.IdEmpresa, montoAjusteSaldo, transaction);
if (!saldoActualizado) throw new DataException($"Error al actualizar el saldo para {notaCreada.Destino} ID {notaCreada.IdDestino}.");
transaction.Commit();
_logger.LogInformation("NotaC/D ID {Id} creada y saldo afectado por Usuario ID {UserId}.", notaCreada.IdNota, idUsuario);
return (await MapToDto(notaCreada), null);
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error CrearAsync NotaCreditoDebito.");
return (null, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idNota, UpdateNotaDto updateDto, int idUsuario)
{
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var notaExistente = await _notaRepo.GetByIdAsync(idNota);
if (notaExistente == null) return (false, "Nota no encontrada.");
// Calcular diferencia de monto para ajustar saldo
decimal montoOriginal = notaExistente.Tipo == "Credito" ? notaExistente.Monto : -notaExistente.Monto;
decimal montoNuevo = notaExistente.Tipo == "Credito" ? updateDto.Monto : -updateDto.Monto; // Tipo no cambia
decimal diferenciaAjusteSaldo = montoNuevo - montoOriginal;
notaExistente.Monto = updateDto.Monto;
notaExistente.Observaciones = updateDto.Observaciones;
var actualizado = await _notaRepo.UpdateAsync(notaExistente, idUsuario, transaction);
if (!actualizado) throw new DataException("Error al actualizar la nota.");
if (diferenciaAjusteSaldo != 0)
{
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaExistente.Destino, notaExistente.IdDestino, notaExistente.IdEmpresa, diferenciaAjusteSaldo, transaction);
if (!saldoActualizado) throw new DataException("Error al ajustar el saldo tras la actualización de la nota.");
}
transaction.Commit();
_logger.LogInformation("NotaC/D ID {Id} actualizada por Usuario ID {UserId}.", idNota, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Nota no encontrada."); }
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error ActualizarAsync NotaC/D ID: {Id}", idNota);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> EliminarAsync(int idNota, int idUsuario)
{
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var notaExistente = await _notaRepo.GetByIdAsync(idNota);
if (notaExistente == null) return (false, "Nota no encontrada.");
// Revertir el efecto en el saldo
decimal montoReversion = notaExistente.Tipo == "Credito" ? -notaExistente.Monto : notaExistente.Monto;
var eliminado = await _notaRepo.DeleteAsync(idNota, idUsuario, transaction);
if (!eliminado) throw new DataException("Error al eliminar la nota.");
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaExistente.Destino, notaExistente.IdDestino, notaExistente.IdEmpresa, montoReversion, transaction);
if (!saldoActualizado) throw new DataException("Error al revertir el saldo tras la eliminación de la nota.");
transaction.Commit();
_logger.LogInformation("NotaC/D ID {Id} eliminada y saldo revertido por Usuario ID {UserId}.", idNota, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Nota no encontrada."); }
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error EliminarAsync NotaC/D ID: {Id}", idNota);
return (false, $"Error interno: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,218 @@
using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables;
using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Contables;
using GestionIntegral.Api.Models.Contables;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Contables
{
public class PagoDistribuidorService : IPagoDistribuidorService
{
private readonly IPagoDistribuidorRepository _pagoRepo;
private readonly IDistribuidorRepository _distribuidorRepo;
private readonly ITipoPagoRepository _tipoPagoRepo;
private readonly IEmpresaRepository _empresaRepo;
private readonly ISaldoRepository _saldoRepo;
private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<PagoDistribuidorService> _logger;
public PagoDistribuidorService(
IPagoDistribuidorRepository pagoRepo,
IDistribuidorRepository distribuidorRepo,
ITipoPagoRepository tipoPagoRepo,
IEmpresaRepository empresaRepo,
ISaldoRepository saldoRepo,
DbConnectionFactory connectionFactory,
ILogger<PagoDistribuidorService> logger)
{
_pagoRepo = pagoRepo;
_distribuidorRepo = distribuidorRepo;
_tipoPagoRepo = tipoPagoRepo;
_empresaRepo = empresaRepo;
_saldoRepo = saldoRepo;
_connectionFactory = connectionFactory;
_logger = logger;
}
private async Task<PagoDistribuidorDto> MapToDto(PagoDistribuidor pago)
{
if (pago == null) return null!;
var distribuidorData = await _distribuidorRepo.GetByIdAsync(pago.IdDistribuidor);
var tipoPago = await _tipoPagoRepo.GetByIdAsync(pago.IdTipoPago);
var empresa = await _empresaRepo.GetByIdAsync(pago.IdEmpresa);
return new PagoDistribuidorDto
{
IdPago = pago.IdPago,
IdDistribuidor = pago.IdDistribuidor,
NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "N/A",
Fecha = pago.Fecha.ToString("yyyy-MM-dd"),
TipoMovimiento = pago.TipoMovimiento,
Recibo = pago.Recibo,
Monto = pago.Monto,
IdTipoPago = pago.IdTipoPago,
NombreTipoPago = tipoPago?.Nombre ?? "N/A",
Detalle = pago.Detalle,
IdEmpresa = pago.IdEmpresa,
NombreEmpresa = empresa?.Nombre ?? "N/A"
};
}
public async Task<IEnumerable<PagoDistribuidorDto>> ObtenerTodosAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idDistribuidor, int? idEmpresa, string? tipoMovimiento)
{
var pagos = await _pagoRepo.GetAllAsync(fechaDesde, fechaHasta, idDistribuidor, idEmpresa, tipoMovimiento);
var dtos = new List<PagoDistribuidorDto>();
foreach (var pago in pagos)
{
dtos.Add(await MapToDto(pago));
}
return dtos;
}
public async Task<PagoDistribuidorDto?> ObtenerPorIdAsync(int idPago)
{
var pago = await _pagoRepo.GetByIdAsync(idPago);
return pago == null ? null : await MapToDto(pago);
}
public async Task<(PagoDistribuidorDto? Pago, string? Error)> CrearAsync(CreatePagoDistribuidorDto createDto, int idUsuario)
{
if (await _distribuidorRepo.GetByIdSimpleAsync(createDto.IdDistribuidor) == null)
return (null, "Distribuidor no válido.");
if (await _tipoPagoRepo.GetByIdAsync(createDto.IdTipoPago) == null)
return (null, "Tipo de pago no válido.");
if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null)
return (null, "Empresa no válida.");
if (await _pagoRepo.ExistsByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento))
return (null, $"Ya existe un pago '{createDto.TipoMovimiento}' con el número de recibo '{createDto.Recibo}'.");
var nuevoPago = new PagoDistribuidor
{
IdDistribuidor = createDto.IdDistribuidor,
Fecha = createDto.Fecha.Date,
TipoMovimiento = createDto.TipoMovimiento,
Recibo = createDto.Recibo,
Monto = createDto.Monto,
IdTipoPago = createDto.IdTipoPago,
Detalle = createDto.Detalle,
IdEmpresa = createDto.IdEmpresa
};
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var pagoCreado = await _pagoRepo.CreateAsync(nuevoPago, idUsuario, transaction);
if (pagoCreado == null) throw new DataException("Error al registrar el pago.");
// Afectar Saldo
// Si TipoMovimiento es "Recibido", el monto DISMINUYE la deuda del distribuidor (monto positivo para el servicio de saldo).
// Si TipoMovimiento es "Realizado" (empresa paga a distribuidor), el monto AUMENTA la deuda (monto negativo para el servicio de saldo).
decimal montoAjusteSaldo = createDto.TipoMovimiento == "Recibido" ? createDto.Monto : -createDto.Monto;
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoCreado.IdDistribuidor, pagoCreado.IdEmpresa, montoAjusteSaldo, transaction);
if (!saldoActualizado) throw new DataException("Error al actualizar el saldo del distribuidor.");
transaction.Commit();
_logger.LogInformation("PagoDistribuidor ID {Id} creado y saldo afectado por Usuario ID {UserId}.", pagoCreado.IdPago, idUsuario);
return (await MapToDto(pagoCreado), null);
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error CrearAsync PagoDistribuidor.");
return (null, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPago, UpdatePagoDistribuidorDto updateDto, int idUsuario)
{
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var pagoExistente = await _pagoRepo.GetByIdAsync(idPago);
if (pagoExistente == null) return (false, "Pago no encontrado.");
if (await _tipoPagoRepo.GetByIdAsync(updateDto.IdTipoPago) == null)
return (false, "Tipo de pago no válido.");
// Calcular la diferencia de monto para ajustar el saldo
decimal montoOriginal = pagoExistente.TipoMovimiento == "Recibido" ? pagoExistente.Monto : -pagoExistente.Monto;
decimal montoNuevo = pagoExistente.TipoMovimiento == "Recibido" ? updateDto.Monto : -updateDto.Monto;
decimal diferenciaAjusteSaldo = montoNuevo - montoOriginal;
// Actualizar campos permitidos
pagoExistente.Monto = updateDto.Monto;
pagoExistente.IdTipoPago = updateDto.IdTipoPago;
pagoExistente.Detalle = updateDto.Detalle;
var actualizado = await _pagoRepo.UpdateAsync(pagoExistente, idUsuario, transaction);
if (!actualizado) throw new DataException("Error al actualizar el pago.");
if (diferenciaAjusteSaldo != 0)
{
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, diferenciaAjusteSaldo, transaction);
if (!saldoActualizado) throw new DataException("Error al ajustar el saldo del distribuidor tras la actualización del pago.");
}
transaction.Commit();
_logger.LogInformation("PagoDistribuidor ID {Id} actualizado por Usuario ID {UserId}.", idPago, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Pago no encontrado."); }
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error ActualizarAsync PagoDistribuidor ID: {Id}", idPago);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> EliminarAsync(int idPago, int idUsuario)
{
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var pagoExistente = await _pagoRepo.GetByIdAsync(idPago);
if (pagoExistente == null) return (false, "Pago no encontrado.");
// Revertir el efecto en el saldo
// Si fue "Recibido", el saldo disminuyó (montoAjusteSaldo fue +Monto). Al eliminar, revertimos sumando -Monto (o restando +Monto).
// Si fue "Realizado", el saldo aumentó (montoAjusteSaldo fue -Monto). Al eliminar, revertimos sumando +Monto (o restando -Monto).
decimal montoReversion = pagoExistente.TipoMovimiento == "Recibido" ? -pagoExistente.Monto : pagoExistente.Monto;
var eliminado = await _pagoRepo.DeleteAsync(idPago, idUsuario, transaction);
if (!eliminado) throw new DataException("Error al eliminar el pago.");
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, montoReversion, transaction);
if (!saldoActualizado) throw new DataException("Error al revertir el saldo del distribuidor tras la eliminación del pago.");
transaction.Commit();
_logger.LogInformation("PagoDistribuidor ID {Id} eliminado por Usuario ID {UserId}.", idPago, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Pago no encontrado."); }
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error EliminarAsync PagoDistribuidor ID: {Id}", idPago);
return (false, $"Error interno: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,171 @@
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 ControlDevolucionesService : IControlDevolucionesService
{
private readonly IControlDevolucionesRepository _controlDevRepo;
private readonly IEmpresaRepository _empresaRepository; // Para validar IdEmpresa y obtener nombre
private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<ControlDevolucionesService> _logger;
public ControlDevolucionesService(
IControlDevolucionesRepository controlDevRepo,
IEmpresaRepository empresaRepository,
DbConnectionFactory connectionFactory,
ILogger<ControlDevolucionesService> logger)
{
_controlDevRepo = controlDevRepo;
_empresaRepository = empresaRepository;
_connectionFactory = connectionFactory;
_logger = logger;
}
private async Task<ControlDevolucionesDto> MapToDto(ControlDevoluciones control)
{
if (control == null) return null!;
var empresa = await _empresaRepository.GetByIdAsync(control.IdEmpresa);
return new ControlDevolucionesDto
{
IdControl = control.IdControl,
IdEmpresa = control.IdEmpresa,
NombreEmpresa = empresa?.Nombre ?? "Empresa Desconocida",
Fecha = control.Fecha.ToString("yyyy-MM-dd"),
Entrada = control.Entrada,
Sobrantes = control.Sobrantes,
Detalle = control.Detalle,
SinCargo = control.SinCargo
};
}
public async Task<IEnumerable<ControlDevolucionesDto>> ObtenerTodosAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idEmpresa)
{
var controles = await _controlDevRepo.GetAllAsync(fechaDesde, fechaHasta, idEmpresa);
var dtos = new List<ControlDevolucionesDto>();
foreach (var control in controles)
{
dtos.Add(await MapToDto(control));
}
return dtos;
}
public async Task<ControlDevolucionesDto?> ObtenerPorIdAsync(int idControl)
{
var control = await _controlDevRepo.GetByIdAsync(idControl);
return control == null ? null : await MapToDto(control);
}
public async Task<(ControlDevolucionesDto? Control, string? Error)> CrearAsync(CreateControlDevolucionesDto createDto, int idUsuario)
{
var empresa = await _empresaRepository.GetByIdAsync(createDto.IdEmpresa);
if (empresa == null)
return (null, "La empresa especificada no existe.");
// Validar que no exista ya un control para esa empresa y fecha
if (await _controlDevRepo.GetByEmpresaAndFechaAsync(createDto.IdEmpresa, createDto.Fecha.Date) != null)
{
return (null, $"Ya existe un control de devoluciones para la empresa '{empresa.Nombre}' en la fecha {createDto.Fecha:dd/MM/yyyy}.");
}
var nuevoControl = new ControlDevoluciones
{
IdEmpresa = createDto.IdEmpresa,
Fecha = createDto.Fecha.Date,
Entrada = createDto.Entrada,
Sobrantes = createDto.Sobrantes,
Detalle = createDto.Detalle,
SinCargo = createDto.SinCargo
};
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var controlCreado = await _controlDevRepo.CreateAsync(nuevoControl, idUsuario, transaction);
if (controlCreado == null) throw new DataException("Error al registrar el control de devoluciones.");
transaction.Commit();
_logger.LogInformation("ControlDevoluciones ID {Id} creado por Usuario ID {UserId}.", controlCreado.IdControl, idUsuario);
return (await MapToDto(controlCreado), null);
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error CrearAsync ControlDevoluciones para Empresa ID {IdEmpresa}, Fecha {Fecha}", createDto.IdEmpresa, createDto.Fecha);
return (null, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idControl, UpdateControlDevolucionesDto updateDto, int idUsuario)
{
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var controlExistente = await _controlDevRepo.GetByIdAsync(idControl); // Obtener dentro de TX
if (controlExistente == null) return (false, "Control de devoluciones no encontrado.");
// IdEmpresa y Fecha no se modifican
controlExistente.Entrada = updateDto.Entrada;
controlExistente.Sobrantes = updateDto.Sobrantes;
controlExistente.Detalle = updateDto.Detalle;
controlExistente.SinCargo = updateDto.SinCargo;
var actualizado = await _controlDevRepo.UpdateAsync(controlExistente, idUsuario, transaction);
if (!actualizado) throw new DataException("Error al actualizar el control de devoluciones.");
transaction.Commit();
_logger.LogInformation("ControlDevoluciones ID {Id} actualizado por Usuario ID {UserId}.", idControl, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Registro no encontrado."); }
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error ActualizarAsync ControlDevoluciones ID: {Id}", idControl);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> EliminarAsync(int idControl, int idUsuario)
{
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var controlExistente = await _controlDevRepo.GetByIdAsync(idControl); // Obtener dentro de TX
if (controlExistente == null) return (false, "Control de devoluciones no encontrado.");
// Aquí no hay dependencias directas que afecten saldos, es un registro informativo.
// Se podría verificar si está "en uso" si alguna lógica de reporte o cierre depende de él,
// pero por ahora, la eliminación es directa.
var eliminado = await _controlDevRepo.DeleteAsync(idControl, idUsuario, transaction);
if (!eliminado) throw new DataException("Error al eliminar el control de devoluciones.");
transaction.Commit();
_logger.LogInformation("ControlDevoluciones ID {Id} eliminado por Usuario ID {UserId}.", idControl, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Registro no encontrado."); }
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error EliminarAsync ControlDevoluciones ID: {Id}", idControl);
return (false, $"Error interno: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,481 @@
using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Data.Repositories.Usuarios;
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.Globalization;
using System.Linq;
using System.Threading.Tasks;
using System.Security.Claims;
namespace GestionIntegral.Api.Services.Distribucion
{
public class EntradaSalidaCanillaService : IEntradaSalidaCanillaService
{
private readonly IEntradaSalidaCanillaRepository _esCanillaRepository;
private readonly IPublicacionRepository _publicacionRepository;
private readonly ICanillaRepository _canillaRepository;
private readonly IPrecioRepository _precioRepository;
private readonly IRecargoZonaRepository _recargoZonaRepository;
private readonly IPorcMonCanillaRepository _porcMonCanillaRepository;
private readonly IUsuarioRepository _usuarioRepository;
private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<EntradaSalidaCanillaService> _logger;
public EntradaSalidaCanillaService(
IEntradaSalidaCanillaRepository esCanillaRepository,
IPublicacionRepository publicacionRepository,
ICanillaRepository canillaRepository,
IPrecioRepository precioRepository,
IRecargoZonaRepository recargoZonaRepository,
IPorcMonCanillaRepository porcMonCanillaRepository,
IUsuarioRepository usuarioRepository,
DbConnectionFactory connectionFactory,
ILogger<EntradaSalidaCanillaService> logger)
{
_esCanillaRepository = esCanillaRepository;
_publicacionRepository = publicacionRepository;
_canillaRepository = canillaRepository;
_precioRepository = precioRepository;
_recargoZonaRepository = recargoZonaRepository;
_porcMonCanillaRepository = porcMonCanillaRepository;
_usuarioRepository = usuarioRepository;
_connectionFactory = connectionFactory;
_logger = logger;
}
private async Task<EntradaSalidaCanillaDto?> MapToDto(EntradaSalidaCanilla? es)
{
if (es == null) return null;
var publicacionDataResult = await _publicacionRepository.GetByIdAsync(es.IdPublicacion);
var canillaDataResult = await _canillaRepository.GetByIdAsync(es.IdCanilla); // Devuelve tupla
Publicacion? publicacionEntity = publicacionDataResult.Publicacion; // Componente nullable de la tupla
Canilla? canillaEntity = canillaDataResult.Canilla; // Componente nullable de la tupla
var usuarioLiq = es.UserLiq.HasValue ? await _usuarioRepository.GetByIdAsync(es.UserLiq.Value) : null;
decimal montoARendir = await CalcularMontoARendir(es, canillaEntity, publicacionEntity);
return new EntradaSalidaCanillaDto
{
IdParte = es.IdParte,
IdPublicacion = es.IdPublicacion,
NombrePublicacion = publicacionEntity?.Nombre ?? "Pub. Desconocida",
IdCanilla = es.IdCanilla,
NomApeCanilla = canillaEntity?.NomApe ?? "Can. Desconocido",
CanillaEsAccionista = canillaEntity?.Accionista ?? false,
Fecha = es.Fecha.ToString("yyyy-MM-dd"),
CantSalida = es.CantSalida,
CantEntrada = es.CantEntrada,
Vendidos = es.CantSalida - es.CantEntrada,
Observacion = es.Observacion,
Liquidado = es.Liquidado,
FechaLiquidado = es.FechaLiquidado?.ToString("yyyy-MM-dd"),
UserLiq = es.UserLiq,
NombreUserLiq = usuarioLiq != null ? $"{usuarioLiq.Nombre} {usuarioLiq.Apellido}" : null,
MontoARendir = montoARendir,
PrecioUnitarioAplicado = (await _precioRepository.GetByIdAsync(es.IdPrecio))?.Lunes ?? 0,
RecargoAplicado = es.IdRecargo > 0 ? (await _recargoZonaRepository.GetByIdAsync(es.IdRecargo))?.Valor ?? 0 : 0,
PorcentajeOMontoCanillaAplicado = es.IdPorcMon > 0 ? (await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon))?.PorcMon ?? 0 : 0,
EsPorcentajeCanilla = es.IdPorcMon > 0 ? (await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon))?.EsPorcentaje ?? false : false
};
}
private async Task<decimal> CalcularMontoARendir(EntradaSalidaCanilla es, Canilla? canilla, Publicacion? publicacion) // Acepta nullable Canilla y Publicacion
{
if (es.CantSalida - es.CantEntrada <= 0) return 0;
var precioConfig = await _precioRepository.GetByIdAsync(es.IdPrecio);
if (precioConfig == null)
{
_logger.LogError("Configuración de precio ID {IdPrecio} no encontrada para movimiento ID {IdParte}.", es.IdPrecio, es.IdParte);
throw new InvalidOperationException($"Configuración de precio ID {es.IdPrecio} no encontrada para el movimiento.");
}
decimal precioDia = 0;
DayOfWeek diaSemana = es.Fecha.DayOfWeek;
switch (diaSemana)
{
case DayOfWeek.Monday: precioDia = precioConfig.Lunes ?? 0; break;
case DayOfWeek.Tuesday: precioDia = precioConfig.Martes ?? 0; break;
case DayOfWeek.Wednesday: precioDia = precioConfig.Miercoles ?? 0; break;
case DayOfWeek.Thursday: precioDia = precioConfig.Jueves ?? 0; break;
case DayOfWeek.Friday: precioDia = precioConfig.Viernes ?? 0; break;
case DayOfWeek.Saturday: precioDia = precioConfig.Sabado ?? 0; break;
case DayOfWeek.Sunday: precioDia = precioConfig.Domingo ?? 0; break;
}
decimal valorRecargo = 0;
if (es.IdRecargo > 0)
{
var recargoConfig = await _recargoZonaRepository.GetByIdAsync(es.IdRecargo);
if (recargoConfig != null) valorRecargo = recargoConfig.Valor;
}
decimal precioFinalUnitario = precioDia + valorRecargo;
int cantidadVendida = es.CantSalida - es.CantEntrada;
if (canilla != null && canilla.Accionista && es.IdPorcMon > 0) // Check null para canilla
{
var porcMonConfig = await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon);
if (porcMonConfig != null)
{
if (porcMonConfig.EsPorcentaje)
{
return Math.Round((precioFinalUnitario * cantidadVendida * porcMonConfig.PorcMon) / 100, 2);
}
else
{
return Math.Round(cantidadVendida * porcMonConfig.PorcMon, 2);
}
}
}
return Math.Round(precioFinalUnitario * cantidadVendida, 2);
}
public async Task<IEnumerable<EntradaSalidaCanillaDto>> ObtenerTodosAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idPublicacion, int? idCanilla, bool? liquidados, bool? incluirNoLiquidados)
{
bool? filtroLiquidadoFinal = null;
if (liquidados.HasValue)
{
filtroLiquidadoFinal = liquidados.Value;
}
else
{
if (incluirNoLiquidados.HasValue && !incluirNoLiquidados.Value)
{
filtroLiquidadoFinal = true;
}
}
var movimientos = await _esCanillaRepository.GetAllAsync(fechaDesde, fechaHasta, idPublicacion, idCanilla, filtroLiquidadoFinal);
var dtos = new List<EntradaSalidaCanillaDto>();
foreach (var mov in movimientos)
{
var dto = await MapToDto(mov);
if (dto != null) dtos.Add(dto);
}
return dtos;
}
public async Task<EntradaSalidaCanillaDto?> ObtenerPorIdAsync(int idParte)
{
var movimiento = await _esCanillaRepository.GetByIdAsync(idParte);
return await MapToDto(movimiento);
}
public async Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaCanillaDto updateDto, int idUsuario)
{
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte);
if (esExistente == null) return (false, "Movimiento no encontrado.");
if (esExistente.Liquidado) return (false, "No se puede modificar un movimiento ya liquidado.");
esExistente.CantSalida = updateDto.CantSalida;
esExistente.CantEntrada = updateDto.CantEntrada;
esExistente.Observacion = updateDto.Observacion;
var actualizado = await _esCanillaRepository.UpdateAsync(esExistente, idUsuario, transaction);
if (!actualizado) throw new DataException("Error al actualizar el movimiento.");
transaction.Commit();
_logger.LogInformation("Movimiento Canillita ID {Id} actualizado por Usuario ID {UserId}.", idParte, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { if (transaction.Connection != null) try { transaction.Rollback(); } catch { } return (false, "Movimiento no encontrado."); }
catch (Exception ex)
{
if (transaction.Connection != null) try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error ActualizarMovimientoAsync Canillita ID: {Id}", idParte);
return (false, $"Error interno: {ex.Message}");
}
finally
{
if (connection?.State == ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
}
}
}
public async Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario, ClaimsPrincipal userPrincipal)
{
// Helper interno para verificar permisos desde el ClaimsPrincipal proporcionado
bool TienePermisoEspecifico(string codAcc) => userPrincipal.IsInRole("SuperAdmin") || userPrincipal.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
using var connection = _connectionFactory.CreateConnection();
IDbTransaction? transaction = null;
try
{
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync();
else if (connection.State == ConnectionState.Closed) connection.Open();
transaction = connection.BeginTransaction();
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte);
if (esExistente == null)
{
if (transaction?.Connection != null) transaction.Rollback();
return (false, "Movimiento no encontrado.");
}
if (esExistente.Liquidado)
{
// Permiso MC006 es para "Eliminar Movimientos de Canillita Liquidados"
if (!TienePermisoEspecifico("MC006"))
{
if (transaction?.Connection != null) transaction.Rollback();
return (false, "No tiene permiso para eliminar movimientos ya liquidados. Se requiere permiso especial (MC006) o ser SuperAdmin.");
}
_logger.LogWarning("Usuario ID {IdUsuario} está eliminando un movimiento LIQUIDADO (IDParte: {IdParte}). Permiso MC006 verificado.", idUsuario, idParte);
}
// Si no está liquidado, el permiso MC004 ya fue verificado en el controlador.
var eliminado = await _esCanillaRepository.DeleteAsync(idParte, idUsuario, transaction);
if (!eliminado)
{
// No es necesario hacer rollback aquí si DeleteAsync lanza una excepción,
// ya que el bloque catch lo manejará. Si DeleteAsync devuelve false sin lanzar,
// entonces sí sería necesario un rollback.
if (transaction?.Connection != null) transaction.Rollback();
throw new DataException("Error al eliminar el movimiento desde el repositorio.");
}
if (transaction?.Connection != null) transaction.Commit();
_logger.LogInformation("Movimiento Canillita ID {IdParte} eliminado por Usuario ID {IdUsuario}.", idParte, idUsuario);
return (true, null);
}
catch (KeyNotFoundException)
{
if (transaction?.Connection != null) try { transaction.Rollback(); } catch (Exception exR) { _logger.LogError(exR, "Rollback fallido KeyNotFoundException."); }
return (false, "Movimiento no encontrado.");
}
catch (Exception ex)
{
if (transaction?.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
_logger.LogError(ex, "Error EliminarMovimientoAsync Canillita ID: {IdParte}", idParte);
return (false, $"Error interno: {ex.Message}");
}
finally
{
if (transaction != null) transaction.Dispose();
if (connection?.State == ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
}
}
}
public async Task<(bool Exito, string? Error)> LiquidarMovimientosAsync(LiquidarMovimientosCanillaRequestDto liquidarDto, int idUsuarioLiquidador)
{
if (liquidarDto.IdsPartesALiquidar == null || !liquidarDto.IdsPartesALiquidar.Any())
return (false, "No se especificaron movimientos para liquidar.");
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
using var transaction = connection.BeginTransaction();
try
{
bool liquidacionExitosa = await _esCanillaRepository.LiquidarAsync(liquidarDto.IdsPartesALiquidar, liquidarDto.FechaLiquidacion.Date, idUsuarioLiquidador, transaction);
if (!liquidacionExitosa)
{
_logger.LogWarning("Liquidación de movimientos de canillita pudo no haber afectado a todos los IDs solicitados. IDs: {Ids}", string.Join(",", liquidarDto.IdsPartesALiquidar));
}
if (transaction.Connection != null) transaction.Commit();
_logger.LogInformation("Movimientos de Canillita liquidados. Ids: {Ids} por Usuario ID {UserId}.", string.Join(",", liquidarDto.IdsPartesALiquidar), idUsuarioLiquidador);
return (true, null);
}
catch (Exception ex)
{
if (transaction.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
_logger.LogError(ex, "Error al liquidar movimientos de canillita.");
return (false, $"Error interno al liquidar movimientos: {ex.Message}");
}
finally
{
if (connection?.State == ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
}
}
}
public async Task<(IEnumerable<EntradaSalidaCanillaDto>? MovimientosCreados, string? Error)> CrearMovimientosEnLoteAsync(CreateBulkEntradaSalidaCanillaDto createBulkDto, int idUsuario)
{
var canillaDataResult = await _canillaRepository.GetByIdAsync(createBulkDto.IdCanilla);
Canilla? canillaActual = canillaDataResult.Canilla;
if (canillaActual == null) return (null, "Canillita no válido.");
if (canillaActual.Baja) return (null, "El canillita está dado de baja.");
List<EntradaSalidaCanilla> movimientosCreadosEntidades = new List<EntradaSalidaCanilla>();
List<string> erroresItems = new List<string>();
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
using var transaction = connection.BeginTransaction();
try
{
foreach (var itemDto in createBulkDto.Items.Where(i => i.IdPublicacion > 0))
{
if (await _esCanillaRepository.ExistsByPublicacionCanillaFechaAsync(itemDto.IdPublicacion, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, transaction: transaction))
{
var pubInfo = await _publicacionRepository.GetByIdSimpleAsync(itemDto.IdPublicacion);
erroresItems.Add($"Ya existe un registro para la publicación '{pubInfo?.Nombre ?? itemDto.IdPublicacion.ToString()}' para {canillaActual.NomApe} en la fecha {createBulkDto.Fecha:dd/MM/yyyy}.");
}
}
if (erroresItems.Any())
{
if (transaction.Connection != null) transaction.Rollback();
return (null, string.Join(" ", erroresItems));
}
foreach (var itemDto in createBulkDto.Items)
{
if (itemDto.IdPublicacion == 0 && itemDto.CantSalida == 0 && itemDto.CantEntrada == 0 && string.IsNullOrWhiteSpace(itemDto.Observacion)) continue;
if (itemDto.IdPublicacion == 0)
{
if (itemDto.CantSalida > 0 || itemDto.CantEntrada > 0 || !string.IsNullOrWhiteSpace(itemDto.Observacion))
{
erroresItems.Add($"Falta seleccionar la publicación para una de las líneas con cantidades/observación.");
}
continue;
}
var publicacionItem = await _publicacionRepository.GetByIdSimpleAsync(itemDto.IdPublicacion);
bool noEsValidaONoHabilitada = false;
if (publicacionItem == null)
{
noEsValidaONoHabilitada = true;
}
else
{
// Si Habilitada es bool? y NULL significa que toma el DEFAULT 1 (true) de la BD
// entonces solo consideramos error si es explícitamente false.
if (publicacionItem.Habilitada.HasValue && publicacionItem.Habilitada.Value == false)
{
noEsValidaONoHabilitada = true;
}
// Si publicacionItem.Habilitada es null o true, noEsValidaONoHabilitada permanece false.
}
if (noEsValidaONoHabilitada)
{
erroresItems.Add($"Publicación ID {itemDto.IdPublicacion} no es válida o no está habilitada.");
continue;
}
var precioActivo = await _precioRepository.GetActiveByPublicacionAndDateAsync(itemDto.IdPublicacion, createBulkDto.Fecha.Date, transaction);
if (precioActivo == null)
{
string nombrePubParaError = publicacionItem?.Nombre ?? $"ID {itemDto.IdPublicacion}";
erroresItems.Add($"No hay precio definido para '{nombrePubParaError}' en {createBulkDto.Fecha:dd/MM/yyyy}.");
continue;
}
RecargoZona? recargoActivo = null;
// Aquí usamos canillaActual! porque ya verificamos que no es null al inicio del método.
if (canillaActual!.IdZona > 0)
{
recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(itemDto.IdPublicacion, canillaActual.IdZona, createBulkDto.Fecha.Date, transaction);
}
PorcMonCanilla? porcMonActivo = null;
// Aquí usamos canillaActual! porque ya verificamos que no es null al inicio del método.
if (canillaActual!.Accionista)
{
porcMonActivo = await _porcMonCanillaRepository.GetActiveByPublicacionCanillaAndDateAsync(itemDto.IdPublicacion, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, transaction);
if (porcMonActivo == null)
{
// Dentro de este bloque, canillaActual.NomApe es seguro porque Accionista era true.
string nombreCanParaError = canillaActual.NomApe;
string nombrePubParaError = publicacionItem?.Nombre ?? $"Publicación ID {itemDto.IdPublicacion}";
erroresItems.Add($"'{nombreCanParaError}' es accionista pero no tiene %/monto para '{nombrePubParaError}' en {createBulkDto.Fecha:dd/MM/yyyy}.");
continue;
}
}
var nuevoES = new EntradaSalidaCanilla
{
IdPublicacion = itemDto.IdPublicacion,
IdCanilla = createBulkDto.IdCanilla,
Fecha = createBulkDto.Fecha.Date,
CantSalida = itemDto.CantSalida,
CantEntrada = itemDto.CantEntrada,
Observacion = itemDto.Observacion,
IdPrecio = precioActivo.IdPrecio,
IdRecargo = recargoActivo?.IdRecargo ?? 0,
IdPorcMon = porcMonActivo?.IdPorcMon ?? 0,
Liquidado = false,
FechaLiquidado = null,
UserLiq = null
};
var esCreada = await _esCanillaRepository.CreateAsync(nuevoES, idUsuario, transaction);
if (esCreada == null) throw new DataException($"Error al registrar movimiento para Publicación ID {itemDto.IdPublicacion}.");
movimientosCreadosEntidades.Add(esCreada);
}
if (erroresItems.Any())
{
if (transaction.Connection != null) transaction.Rollback();
return (null, string.Join(" ", erroresItems));
}
// CORRECCIÓN PARA CS0019 (línea 394 original):
bool tieneItemsSignificativos = false;
if (createBulkDto.Items != null) // Checkear si Items es null antes de llamar a Any()
{
tieneItemsSignificativos = createBulkDto.Items.Any(i => i.IdPublicacion > 0 &&
(i.CantSalida > 0 || i.CantEntrada > 0 || !string.IsNullOrWhiteSpace(i.Observacion)));
}
if (!movimientosCreadosEntidades.Any() && tieneItemsSignificativos)
{
if (transaction.Connection != null) transaction.Rollback();
return (null, "No se pudo procesar ningún ítem válido con datos significativos.");
}
if (transaction.Connection != null) transaction.Commit();
_logger.LogInformation("Lote de {Count} movimientos Canillita para Canilla ID {IdCanilla} en Fecha {Fecha} creados por Usuario ID {UserId}.",
movimientosCreadosEntidades.Count, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, idUsuario);
var dtosCreados = new List<EntradaSalidaCanillaDto>();
foreach (var entidad in movimientosCreadosEntidades)
{
var dto = await MapToDto(entidad);
if (dto != null) dtosCreados.Add(dto);
}
return (dtosCreados, null);
}
catch (Exception ex)
{
if (transaction.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
_logger.LogError(ex, "Error CrearMovimientosEnLoteAsync para Canilla ID {IdCanilla}, Fecha {Fecha}", createBulkDto.IdCanilla, createBulkDto.Fecha);
return (null, $"Error interno al procesar el lote: {ex.Message}");
}
finally
{
if (connection?.State == ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
}
}
}
}
}

View File

@@ -57,9 +57,29 @@ namespace GestionIntegral.Api.Services.Distribucion
var publicacionData = await _publicacionRepository.GetByIdAsync(es.IdPublicacion);
var distribuidorData = await _distribuidorRepository.GetByIdAsync(es.IdDistribuidor);
decimal montoCalculado = await CalcularMontoMovimiento(es.IdPublicacion, es.IdDistribuidor, es.Fecha, es.Cantidad, es.TipoMovimiento,
es.IdPrecio, es.IdRecargo, es.IdPorcentaje, distribuidorData.Distribuidor?.IdZona);
// Obtener el valor bruto del movimiento
decimal valorBrutoMovimiento = await CalcularMontoMovimiento(
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
{
@@ -75,17 +95,29 @@ namespace GestionIntegral.Api.Services.Distribucion
Cantidad = es.Cantidad,
Remito = es.Remito,
Observacion = es.Observacion,
MontoCalculado = montoCalculado
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)
{
if (tipoMovimiento == "Entrada") return 0; // Las entradas (devoluciones) no generan "debe" inmediato, ajustan el saldo.
// YA NO SE DEVUELVE 0 PARA ENTRADA AQUÍ
// if (tipoMovimiento == "Entrada") return 0;
var precioConfig = await _precioRepository.GetByIdAsync(idPrecio);
if (precioConfig == null) throw new InvalidOperationException("Configuración de precio no encontrada para el movimiento.");
// 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.
if (precioConfig == null)
{
_logger.LogError("Configuración de precio ID {IdPrecio} no encontrada al calcular monto para Pub {IdPublicacion}, Dist {IdDistribuidor}, Fecha {Fecha}", idPrecio, idPublicacion, idDistribuidor, fecha);
// Dependiendo de la regla de negocio, podrías devolver 0 o lanzar una excepción.
// Si un precio es OBLIGATORIO para cualquier movimiento, lanzar excepción es más apropiado.
// Si puede haber movimientos sin precio (ej. cortesía que no se factura), entonces 0.
// En este contexto, un precio es fundamental para el cálculo.
throw new InvalidOperationException($"Configuración de precio ID {idPrecio} no encontrada. No se puede calcular el monto.");
}
decimal precioDia = 0;
DayOfWeek diaSemana = fecha.DayOfWeek;
@@ -101,9 +133,8 @@ namespace GestionIntegral.Api.Services.Distribucion
}
decimal valorRecargo = 0;
if (idRecargo > 0 && idZonaDistribuidor.HasValue) // El recargo se aplica por la zona del distribuidor
if (idRecargo > 0 && idZonaDistribuidor.HasValue)
{
// Necesitamos encontrar el recargo activo para la publicación, la zona del distribuidor y la fecha
var recargoConfig = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(idPublicacion, idZonaDistribuidor.Value, fecha);
if (recargoConfig != null)
{
@@ -119,10 +150,13 @@ namespace GestionIntegral.Api.Services.Distribucion
var porcConfig = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
if (porcConfig != null)
{
return (montoBase / 100) * porcConfig.Porcentaje;
// El porcentaje de pago del distribuidor es lo que ÉL PAGA a la editorial.
// Entonces, el monto es (precio_tapa_con_recargo * cantidad) * (porcentaje_pago_dist / 100)
return (montoBase * porcConfig.Porcentaje) / 100;
}
}
return montoBase; // Si no hay porcentaje, se factura el 100% del precio con recargo
// Si no hay porcentaje de pago específico, se asume que el distribuidor paga el 100% del monto base.
return montoBase;
}

View File

@@ -0,0 +1,16 @@
using GestionIntegral.Api.Dtos.Distribucion;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Distribucion
{
public interface IControlDevolucionesService
{
Task<IEnumerable<ControlDevolucionesDto>> ObtenerTodosAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idEmpresa);
Task<ControlDevolucionesDto?> ObtenerPorIdAsync(int idControl);
Task<(ControlDevolucionesDto? Control, string? Error)> CrearAsync(CreateControlDevolucionesDto createDto, int idUsuario);
Task<(bool Exito, string? Error)> ActualizarAsync(int idControl, UpdateControlDevolucionesDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarAsync(int idControl, int idUsuario);
}
}

View File

@@ -0,0 +1,23 @@
using GestionIntegral.Api.Dtos.Distribucion;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Distribucion
{
public interface IEntradaSalidaCanillaService
{
Task<IEnumerable<EntradaSalidaCanillaDto>> ObtenerTodosAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idPublicacion, int? idCanilla, bool? liquidados, bool? incluirNoLiquidados);
Task<EntradaSalidaCanillaDto?> ObtenerPorIdAsync(int idParte);
Task<(IEnumerable<EntradaSalidaCanillaDto>? MovimientosCreados, string? Error)> CrearMovimientosEnLoteAsync(CreateBulkEntradaSalidaCanillaDto createBulkDto, int idUsuario);
Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaCanillaDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario, ClaimsPrincipal userPrincipal);
Task<(bool Exito, string? Error)> LiquidarMovimientosAsync(LiquidarMovimientosCanillaRequestDto liquidarDto, int idUsuarioLiquidador);
}
}

View File

@@ -0,0 +1,173 @@
using GestionIntegral.Api.Data.Repositories.Radios;
using GestionIntegral.Api.Dtos.Radios;
using GestionIntegral.Api.Models.Radios;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
// using GestionIntegral.Api.Data; // Para DbConnectionFactory si se usa transacción
// using System.Data; // Para IsolationLevel si se usa transacción
namespace GestionIntegral.Api.Services.Radios
{
public class CancionService : ICancionService
{
private readonly ICancionRepository _cancionRepository;
private readonly IRitmoRepository _ritmoRepository;
private readonly ILogger<CancionService> _logger;
public CancionService(
ICancionRepository cancionRepository,
IRitmoRepository ritmoRepository,
ILogger<CancionService> logger)
{
_cancionRepository = cancionRepository;
_ritmoRepository = ritmoRepository;
_logger = logger;
}
private async Task<CancionDto> MapToDto(Cancion cancion)
{
if (cancion == null) return null!;
string? nombreRitmo = null;
if (cancion.Ritmo.HasValue && cancion.Ritmo.Value > 0)
{
var ritmoDb = await _ritmoRepository.GetByIdAsync(cancion.Ritmo.Value);
nombreRitmo = ritmoDb?.NombreRitmo;
}
return new CancionDto
{
Id = cancion.Id,
Tema = cancion.Tema,
CompositorAutor = cancion.CompositorAutor,
Interprete = cancion.Interprete,
Sello = cancion.Sello,
Placa = cancion.Placa,
Pista = cancion.Pista,
Introduccion = cancion.Introduccion,
IdRitmo = cancion.Ritmo, // Pasar el valor de cancion.Ritmo
NombreRitmo = nombreRitmo,
Formato = cancion.Formato,
Album = cancion.Album
};
}
public async Task<IEnumerable<CancionDto>> ObtenerTodasAsync(string? temaFilter, string? interpreteFilter, int? idRitmoFilter)
{
var canciones = await _cancionRepository.GetAllAsync(temaFilter, interpreteFilter, idRitmoFilter);
var dtos = new List<CancionDto>();
foreach (var cancion in canciones)
{
dtos.Add(await MapToDto(cancion));
}
return dtos;
}
public async Task<CancionDto?> ObtenerPorIdAsync(int id)
{
var cancion = await _cancionRepository.GetByIdAsync(id);
return cancion == null ? null : await MapToDto(cancion);
}
public async Task<(CancionDto? Cancion, string? Error)> CrearAsync(CreateCancionDto createDto, int idUsuario)
{
if (createDto.IdRitmo.HasValue && createDto.IdRitmo.Value > 0) // Asegurar que > 0 para evitar buscar ritmo con ID 0
{
if(await _ritmoRepository.GetByIdAsync(createDto.IdRitmo.Value) == null)
return (null, "El ritmo seleccionado no es válido.");
}
if (!string.IsNullOrWhiteSpace(createDto.Tema) && !string.IsNullOrWhiteSpace(createDto.Interprete) &&
await _cancionRepository.ExistsByTemaAndInterpreteAsync(createDto.Tema, createDto.Interprete))
{
return (null, "Ya existe una canción con el mismo tema e intérprete.");
}
var nuevaCancion = new Cancion
{
Tema = createDto.Tema, CompositorAutor = createDto.CompositorAutor, Interprete = createDto.Interprete,
Sello = createDto.Sello, Placa = createDto.Placa, Pista = createDto.Pista,
Introduccion = createDto.Introduccion,
Ritmo = createDto.IdRitmo, // Asignar createDto.IdRitmo a la propiedad Ritmo del modelo
Formato = createDto.Formato, Album = createDto.Album
};
try
{
var cancionCreada = await _cancionRepository.CreateAsync(nuevaCancion);
if (cancionCreada == null) return (null, "Error al crear la canción.");
_logger.LogInformation("Canción ID {Id} creada por Usuario ID {UserId}.", cancionCreada.Id, idUsuario);
return (await MapToDto(cancionCreada), null);
}
catch (System.Exception ex)
{
_logger.LogError(ex, "Error CrearAsync Cancion: {Tema}", createDto.Tema);
return (null, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateCancionDto updateDto, int idUsuario)
{
var cancionExistente = await _cancionRepository.GetByIdAsync(id);
if (cancionExistente == null) return (false, "Canción no encontrada.");
if (updateDto.IdRitmo.HasValue && updateDto.IdRitmo.Value > 0) // Asegurar que > 0
{
if (await _ritmoRepository.GetByIdAsync(updateDto.IdRitmo.Value) == null)
return (false, "El ritmo seleccionado no es válido.");
}
if ((!string.IsNullOrWhiteSpace(updateDto.Tema) && !string.IsNullOrWhiteSpace(updateDto.Interprete)) &&
(cancionExistente.Tema != updateDto.Tema || cancionExistente.Interprete != updateDto.Interprete) &&
await _cancionRepository.ExistsByTemaAndInterpreteAsync(updateDto.Tema!, updateDto.Interprete!, id))
{
return (false, "Ya existe otra canción con el mismo tema e intérprete."); // Devolver tupla bool,string
}
cancionExistente.Tema = updateDto.Tema; cancionExistente.CompositorAutor = updateDto.CompositorAutor;
cancionExistente.Interprete = updateDto.Interprete; cancionExistente.Sello = updateDto.Sello;
cancionExistente.Placa = updateDto.Placa; cancionExistente.Pista = updateDto.Pista;
cancionExistente.Introduccion = updateDto.Introduccion;
cancionExistente.Ritmo = updateDto.IdRitmo; // Asignar updateDto.IdRitmo a la propiedad Ritmo del modelo
cancionExistente.Formato = updateDto.Formato; cancionExistente.Album = updateDto.Album;
try
{
var actualizado = await _cancionRepository.UpdateAsync(cancionExistente);
if (!actualizado) return (false, "Error al actualizar la canción.");
_logger.LogInformation("Canción ID {Id} actualizada por Usuario ID {UserId}.", id, idUsuario);
return (true, null);
}
catch (System.Exception ex)
{
_logger.LogError(ex, "Error ActualizarAsync Canción ID: {Id}", id);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
{
var cancionExistente = await _cancionRepository.GetByIdAsync(id);
if (cancionExistente == null) return (false, "Canción no encontrada.");
try
{
var eliminado = await _cancionRepository.DeleteAsync(id);
if (!eliminado) return (false, "Error al eliminar la canción.");
_logger.LogInformation("Canción ID {Id} eliminada por Usuario ID {UserId}.", id, idUsuario);
return (true, null);
}
catch (System.Exception ex)
{
_logger.LogError(ex, "Error EliminarAsync Canción ID: {Id}", id);
return (false, $"Error interno: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,15 @@
using GestionIntegral.Api.Dtos.Radios;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Radios
{
public interface ICancionService
{
Task<IEnumerable<CancionDto>> ObtenerTodasAsync(string? temaFilter, string? interpreteFilter, int? idRitmoFilter);
Task<CancionDto?> ObtenerPorIdAsync(int id);
Task<(CancionDto? Cancion, string? Error)> CrearAsync(CreateCancionDto createDto, int idUsuario);
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateCancionDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
}
}

View File

@@ -0,0 +1,12 @@
using GestionIntegral.Api.Dtos.Radios;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; // Para IActionResult (o devolver byte[])
namespace GestionIntegral.Api.Services.Radios
{
public interface IRadioListaService
{
// Devuelve byte[] para el archivo y el nombre del archivo sugerido
Task<(byte[] FileContents, string ContentType, string FileName, string? Error)> GenerarListaRadioAsync(GenerarListaRadioRequestDto request);
}
}

View File

@@ -0,0 +1,15 @@
using GestionIntegral.Api.Dtos.Radios;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Radios
{
public interface IRitmoService
{
Task<IEnumerable<RitmoDto>> ObtenerTodosAsync(string? nombreFilter);
Task<RitmoDto?> ObtenerPorIdAsync(int id);
Task<(RitmoDto? Ritmo, string? Error)> CrearAsync(CreateRitmoDto createDto, int idUsuario);
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateRitmoDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
}
}

View File

@@ -0,0 +1,224 @@
using GestionIntegral.Api.Data.Repositories.Radios;
using GestionIntegral.Api.Dtos.Radios;
using GestionIntegral.Api.Models.Radios; // Para Cancion
using Microsoft.Extensions.Logging;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Radios
{
public class RadioListaService : IRadioListaService
{
private readonly ICancionRepository _cancionRepository;
// private readonly IRitmoRepository _ritmoRepository; // No se usa en la nueva lógica de generación de Excel
private readonly ILogger<RadioListaService> _logger;
public RadioListaService(
ICancionRepository cancionRepository,
// IRitmoRepository ritmoRepository, // Puede ser removido si no se usa en otro lado
ILogger<RadioListaService> logger)
{
_cancionRepository = cancionRepository;
// _ritmoRepository = ritmoRepository;
_logger = logger;
}
public async Task<(byte[] FileContents, string ContentType, string FileName, string? Error)> GenerarListaRadioAsync(GenerarListaRadioRequestDto request)
{
try
{
_logger.LogInformation("Iniciando generación de lista de radio para Mes: {Mes}, Año: {Anio}, Institución: {Institucion}, Radio: {Radio}",
request.Mes, request.Anio, request.Institucion, request.Radio);
var programacionParaExcel = new List<ProgramacionHorariaExcelDto>();
int diasEnMes = DateTime.DaysInMonth(request.Anio, request.Mes);
for (int dia = 1; dia <= diasEnMes; dia++)
{
int horaInicio = (request.Radio == "FM 99.1") ? 0 : 9;
int horaFin = 23;
for (int hora = horaInicio; hora <= horaFin; hora++)
{
int cantidadRegistros;
if (request.Radio == "FM 99.1")
{
cantidadRegistros = (hora == 23 || hora == 7 || hora == 15) ? 1 : 5;
}
else // FM 100.3
{
cantidadRegistros = (hora == 23 || hora == 15) ? 1 : 2;
}
if (cantidadRegistros > 0)
{
var cancionesSeleccionadas = await _cancionRepository.GetRandomCancionesAsync(cantidadRegistros);
foreach (var cancion in cancionesSeleccionadas)
{
programacionParaExcel.Add(new ProgramacionHorariaExcelDto
{
Dia = dia,
Hora = hora,
TituloObra = cancion.Tema,
CompositorAutor = cancion.CompositorAutor,
Interprete = cancion.Interprete,
Sello = cancion.Sello,
Album = cancion.Album
});
}
}
}
}
if (!programacionParaExcel.Any())
{
_logger.LogWarning("No se generaron datos para la lista de radio con los criterios: {@Request}", request);
return (Array.Empty<byte>(), "", "", "No se generaron datos para la lista con los criterios dados.");
}
string mesText = request.Mes.ToString("00");
string anioFullText = request.Anio.ToString();
string anioShortText = anioFullText.Length > 2 ? anioFullText.Substring(anioFullText.Length - 2) : anioFullText;
byte[] excelBytes = GenerarExcel(programacionParaExcel, request.Institucion, request.Radio, mesText, anioFullText, anioShortText);
string baseFileName;
if (request.Institucion == "AADI")
{
baseFileName = request.Radio == "FM 99.1" ? $"AADI-FM99.1-FM-{mesText}{anioShortText}" : $"AADI-FM100.3-FM-{mesText}{anioShortText}";
}
else // SADAIC
{
baseFileName = request.Radio == "FM 99.1" ? $"FM99.1-FM-{mesText}{anioShortText}" : $"FM100.3-FM-{mesText}{anioShortText}";
}
string excelFileNameInZip = $"{baseFileName}.xlsx";
string zipFileName = $"{baseFileName}.xlsx.zip"; // Para replicar nombre original del zip
using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var excelEntry = archive.CreateEntry(excelFileNameInZip, CompressionLevel.Optimal);
using (var entryStream = excelEntry.Open())
{
await entryStream.WriteAsync(excelBytes, 0, excelBytes.Length);
}
}
_logger.LogInformation("Lista de radio generada y empaquetada en ZIP exitosamente: {ZipFileName}", zipFileName);
return (memoryStream.ToArray(), "application/zip", zipFileName, null);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al generar la lista de radio para Mes: {Mes}, Año: {Anio}, Institucion: {Institucion}, Radio: {Radio}", request.Mes, request.Anio, request.Institucion, request.Radio);
return (Array.Empty<byte>(), "", "", $"Error interno al generar la lista: {ex.Message}");
}
}
private byte[] GenerarExcel(List<ProgramacionHorariaExcelDto> programacion, string institucion, string radio, string mesText, string anioFullText, string anioShortText)
{
IWorkbook workbook = new XSSFWorkbook();
var coreProps = ((XSSFWorkbook)workbook).GetProperties().CoreProperties;
coreProps.Creator = "GestionIntegral"; // Puedes cambiarlo
coreProps.Title = $"Reporte {institucion} - {radio} - {mesText}/{anioFullText}";
// coreProps.Description = "Descripción del reporte"; // Opcional
// var extendedProps = ((XSSFWorkbook)workbook).GetProperties().ExtendedProperties.GetUnderlyingProperties();
// extendedProps.Application = "GestionIntegral System"; // Opcional
// extendedProps.AppVersion = "1.0.0"; // Opcional
string sheetName;
if (institucion == "AADI")
{
sheetName = radio == "FM 99.1" ? $"SA99{mesText}{anioShortText}" : $"SARE{mesText}{anioShortText}";
}
else // SADAIC
{
sheetName = radio == "FM 99.1" ? $"FM99{mesText}{anioShortText}" : $"FMRE{mesText}{anioShortText}";
}
ISheet sheet = workbook.CreateSheet(sheetName);
int currentRowIdx = 0;
if (institucion == "AADI")
{
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue(radio == "FM 99.1" ? "Nombre: FM LA 99.1" : "Nombre: FM 100.3 LA REDONDA");
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue("Localidad: La Plata");
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue("FM");
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue(radio == "FM 99.1" ? "Frecuencia: 99.1" : "Frecuencia: 100.3");
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue($"Mes: {mesText}/{anioFullText}");
}
IRow headerDataRow = sheet.CreateRow(currentRowIdx++);
headerDataRow.CreateCell(0).SetCellValue("Día");
headerDataRow.CreateCell(1).SetCellValue("Hora");
headerDataRow.CreateCell(2).SetCellValue("Título de la Obra");
headerDataRow.CreateCell(3).SetCellValue("Compositor-Autor");
headerDataRow.CreateCell(4).SetCellValue("Intérprete");
headerDataRow.CreateCell(5).SetCellValue("Sello");
headerDataRow.CreateCell(6).SetCellValue("Álbum");
IFont font = workbook.CreateFont();
font.FontHeightInPoints = 10;
font.FontName = "Arial";
ICellStyle cellStyle = workbook.CreateCellStyle();
cellStyle.SetFont(font);
foreach (var item in programacion)
{
IRow dataRow = sheet.CreateRow(currentRowIdx++);
dataRow.CreateCell(0).SetCellValue(item.Dia);
dataRow.CreateCell(1).SetCellValue(item.Hora);
dataRow.CreateCell(2).SetCellValue(item.TituloObra);
dataRow.CreateCell(3).SetCellValue(item.CompositorAutor);
dataRow.CreateCell(4).SetCellValue(item.Interprete);
dataRow.CreateCell(5).SetCellValue(item.Sello);
dataRow.CreateCell(6).SetCellValue(item.Album);
for (int i = 0; i < 7; i++)
{
ICell cell = dataRow.GetCell(i) ?? dataRow.CreateCell(i); // Asegurarse que la celda exista
cell.CellStyle = cellStyle;
}
}
sheet.SetColumnWidth(0, 4 * 256);
sheet.SetColumnWidth(1, 5 * 256);
sheet.SetColumnWidth(2, 25 * 256);
sheet.SetColumnWidth(3, 14 * 256);
sheet.SetColumnWidth(4, 11 * 256);
sheet.SetColumnWidth(5, 11 * 256);
sheet.SetColumnWidth(6, 30 * 256);
short rowHeight = 255; // 12.75 puntos
for (int i = 0; i < currentRowIdx; i++)
{
IRow row = sheet.GetRow(i) ?? sheet.CreateRow(i);
row.Height = rowHeight;
// Aplicar estilo a todas las celdas de las filas de encabezado también
if (i < (institucion == "AADI" ? 5 : 0) || i == (institucion == "AADI" ? 5 : 0)) // Filas de cabecera de AADI o la fila de títulos de columnas
{
// Iterar sobre las celdas que realmente existen o deberían existir
int lastCellNum = (institucion == "AADI" && i < 5) ? 1 : 7; // Cabecera AADI solo tiene 1 celda, títulos de datos tienen 7
for (int j = 0; j < lastCellNum; j++)
{
ICell cell = row.GetCell(j) ?? row.CreateCell(j);
cell.CellStyle = cellStyle;
}
}
}
using (var memoryStream = new MemoryStream())
{
workbook.Write(memoryStream, true); // El 'true' es para dejar el stream abierto si se necesita
return memoryStream.ToArray();
}
}
}
}

View File

@@ -0,0 +1,133 @@
using GestionIntegral.Api.Data.Repositories.Radios;
using GestionIntegral.Api.Dtos.Radios;
using GestionIntegral.Api.Models.Radios;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
// using GestionIntegral.Api.Data; // Para DbConnectionFactory si se usa transacción
// using System.Data; // Para IsolationLevel si se usa transacción
namespace GestionIntegral.Api.Services.Radios
{
public class RitmoService : IRitmoService
{
private readonly IRitmoRepository _ritmoRepository;
private readonly ILogger<RitmoService> _logger;
// private readonly DbConnectionFactory _connectionFactory; // Si se implementa historial
public RitmoService(IRitmoRepository ritmoRepository, ILogger<RitmoService> logger /*, DbConnectionFactory cf */)
{
_ritmoRepository = ritmoRepository;
_logger = logger;
// _connectionFactory = cf;
}
private RitmoDto MapToDto(Ritmo ritmo) => new RitmoDto
{
Id = ritmo.Id,
NombreRitmo = ritmo.NombreRitmo
};
public async Task<IEnumerable<RitmoDto>> ObtenerTodosAsync(string? nombreFilter)
{
var ritmos = await _ritmoRepository.GetAllAsync(nombreFilter);
return ritmos.Select(MapToDto);
}
public async Task<RitmoDto?> ObtenerPorIdAsync(int id)
{
var ritmo = await _ritmoRepository.GetByIdAsync(id);
return ritmo == null ? null : MapToDto(ritmo);
}
public async Task<(RitmoDto? Ritmo, string? Error)> CrearAsync(CreateRitmoDto createDto, int idUsuario)
{
if (await _ritmoRepository.ExistsByNameAsync(createDto.NombreRitmo))
{
return (null, "El nombre del ritmo ya existe.");
}
var nuevoRitmo = new Ritmo { NombreRitmo = createDto.NombreRitmo };
// Sin historial, la transacción es opcional para una sola operación,
// pero se deja la estructura por si se añade más lógica o historial.
// using var connection = _connectionFactory.CreateConnection();
// if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
// using var transaction = connection.BeginTransaction();
try
{
var ritmoCreado = await _ritmoRepository.CreateAsync(nuevoRitmo /*, idUsuario, transaction */);
if (ritmoCreado == null) return (null, "Error al crear el ritmo.");
// transaction.Commit();
_logger.LogInformation("Ritmo ID {Id} creado por Usuario ID {UserId}.", ritmoCreado.Id, idUsuario);
return (MapToDto(ritmoCreado), null);
}
catch (System.Exception ex)
{
// try { transaction.Rollback(); } catch {}
_logger.LogError(ex, "Error CrearAsync Ritmo: {NombreRitmo}", createDto.NombreRitmo);
return (null, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateRitmoDto updateDto, int idUsuario)
{
var ritmoExistente = await _ritmoRepository.GetByIdAsync(id);
if (ritmoExistente == null) return (false, "Ritmo no encontrado.");
if (ritmoExistente.NombreRitmo != updateDto.NombreRitmo && await _ritmoRepository.ExistsByNameAsync(updateDto.NombreRitmo, id))
{
return (false, "El nombre del ritmo ya existe para otro registro.");
}
ritmoExistente.NombreRitmo = updateDto.NombreRitmo;
// Sin historial, la transacción es opcional...
try
{
var actualizado = await _ritmoRepository.UpdateAsync(ritmoExistente /*, idUsuario, transaction */);
if (!actualizado) return (false, "Error al actualizar el ritmo.");
// transaction.Commit();
_logger.LogInformation("Ritmo ID {Id} actualizado por Usuario ID {UserId}.", id, idUsuario);
return (true, null);
}
catch (System.Exception ex)
{
// try { transaction.Rollback(); } catch {}
_logger.LogError(ex, "Error ActualizarAsync Ritmo ID: {Id}", id);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
{
var ritmoExistente = await _ritmoRepository.GetByIdAsync(id);
if (ritmoExistente == null) return (false, "Ritmo no encontrado.");
if (await _ritmoRepository.IsInUseAsync(id))
{
return (false, "No se puede eliminar. El ritmo está asignado a una o más canciones.");
}
// Sin historial, la transacción es opcional...
try
{
var eliminado = await _ritmoRepository.DeleteAsync(id /*, idUsuario, transaction */);
if (!eliminado) return (false, "Error al eliminar el ritmo.");
// transaction.Commit();
_logger.LogInformation("Ritmo ID {Id} eliminado por Usuario ID {UserId}.", id, idUsuario);
return (true, null);
}
catch (System.Exception ex)
{
// try { transaction.Rollback(); } catch {}
_logger.LogError(ex, "Error EliminarAsync Ritmo ID: {Id}", id);
return (false, $"Error interno: {ex.Message}");
}
}
}
}

View File

@@ -1,4 +1,5 @@
using GestionIntegral.Api.Dtos.Usuarios;
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -13,6 +14,7 @@ namespace GestionIntegral.Api.Services.Usuarios
Task<(bool Exito, string? Error)> SetPasswordAsync(int userId, SetPasswordRequestDto setPasswordDto, int idUsuarioModificador);
// Habilitar/Deshabilitar podría ser un método separado o parte de UpdateAsync
Task<(bool Exito, string? Error)> CambiarEstadoHabilitadoAsync(int userId, bool habilitar, int idUsuarioModificador);
Task<IEnumerable<UsuarioHistorialDto>> ObtenerHistorialPorUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta);
Task<IEnumerable<UsuarioHistorialDto>> ObtenerTodoElHistorialAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModificoFilter, string? tipoModFilter);
}
}

View File

@@ -1,6 +1,7 @@
using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Usuarios;
using GestionIntegral.Api.Dtos.Usuarios;
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
using GestionIntegral.Api.Models.Usuarios; // Para Usuario
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
@@ -79,7 +80,7 @@ namespace GestionIntegral.Api.Services.Usuarios
{
return (null, "El perfil seleccionado no es válido.");
}
if(createDto.User.Equals(createDto.Password, System.StringComparison.OrdinalIgnoreCase))
if (createDto.User.Equals(createDto.Password, System.StringComparison.OrdinalIgnoreCase))
{
return (null, "La contraseña no puede ser igual al nombre de usuario.");
}
@@ -112,11 +113,18 @@ namespace GestionIntegral.Api.Services.Usuarios
transaction.Commit();
// Construir el DTO para la respuesta
var dto = new UsuarioDto {
Id = usuarioCreado.Id, User = usuarioCreado.User, Habilitada = usuarioCreado.Habilitada, SupAdmin = usuarioCreado.SupAdmin,
Nombre = usuarioCreado.Nombre, Apellido = usuarioCreado.Apellido, IdPerfil = usuarioCreado.IdPerfil,
var dto = new UsuarioDto
{
Id = usuarioCreado.Id,
User = usuarioCreado.User,
Habilitada = usuarioCreado.Habilitada,
SupAdmin = usuarioCreado.SupAdmin,
Nombre = usuarioCreado.Nombre,
Apellido = usuarioCreado.Apellido,
IdPerfil = usuarioCreado.IdPerfil,
NombrePerfil = perfilSeleccionado.NombrePerfil, // Usamos el nombre del perfil ya obtenido
DebeCambiarClave = usuarioCreado.DebeCambiarClave, VerLog = usuarioCreado.VerLog
DebeCambiarClave = usuarioCreado.DebeCambiarClave,
VerLog = usuarioCreado.VerLog
};
_logger.LogInformation("Usuario ID {UsuarioId} creado por Usuario ID {CreadorId}.", usuarioCreado.Id, idUsuarioCreador);
return (dto, null);
@@ -160,8 +168,9 @@ namespace GestionIntegral.Api.Services.Usuarios
_logger.LogInformation("Usuario ID {UsuarioId} actualizado por Usuario ID {ModificadorId}.", id, idUsuarioModificador);
return (true, null);
}
catch (KeyNotFoundException) {
try { transaction.Rollback(); } catch { /* Log */ }
catch (KeyNotFoundException)
{
try { transaction.Rollback(); } catch { /* Log */ }
return (false, "Usuario no encontrado durante la actualización.");
}
catch (Exception ex)
@@ -176,9 +185,9 @@ namespace GestionIntegral.Api.Services.Usuarios
var usuario = await _usuarioRepository.GetByIdAsync(userId);
if (usuario == null) return (false, "Usuario no encontrado.");
if(usuario.User.Equals(setPasswordDto.NewPassword, System.StringComparison.OrdinalIgnoreCase))
if (usuario.User.Equals(setPasswordDto.NewPassword, System.StringComparison.OrdinalIgnoreCase))
{
return (false, "La nueva contraseña no puede ser igual al nombre de usuario.");
return (false, "La nueva contraseña no puede ser igual al nombre de usuario.");
}
(string hash, string salt) = _passwordHasher.HashPassword(setPasswordDto.NewPassword);
@@ -189,14 +198,15 @@ namespace GestionIntegral.Api.Services.Usuarios
try
{
var success = await _usuarioRepository.SetPasswordAsync(userId, hash, salt, setPasswordDto.ForceChangeOnNextLogin, idUsuarioModificador, transaction);
if(!success) throw new DataException("Error al actualizar la contraseña en el repositorio.");
if (!success) throw new DataException("Error al actualizar la contraseña en el repositorio.");
transaction.Commit();
_logger.LogInformation("Contraseña establecida para Usuario ID {TargetUserId} por Usuario ID {AdminUserId}.", userId, idUsuarioModificador);
return (true, null);
}
catch (KeyNotFoundException) {
try { transaction.Rollback(); } catch { /* Log */ }
catch (KeyNotFoundException)
{
try { transaction.Rollback(); } catch { /* Log */ }
return (false, "Usuario no encontrado durante el cambio de contraseña.");
}
catch (Exception ex)
@@ -207,7 +217,7 @@ namespace GestionIntegral.Api.Services.Usuarios
}
}
public async Task<(bool Exito, string? Error)> CambiarEstadoHabilitadoAsync(int userId, bool habilitar, int idUsuarioModificador)
public async Task<(bool Exito, string? Error)> CambiarEstadoHabilitadoAsync(int userId, bool habilitar, int idUsuarioModificador)
{
var usuario = await _usuarioRepository.GetByIdAsync(userId);
if (usuario == null) return (false, "Usuario no encontrado.");
@@ -225,22 +235,34 @@ namespace GestionIntegral.Api.Services.Usuarios
try
{
var actualizado = await _usuarioRepository.UpdateAsync(usuario, idUsuarioModificador, transaction);
if (!actualizado) throw new DataException("Error al cambiar estado de habilitación del usuario en el repositorio.");
if (!actualizado) throw new DataException("Error al cambiar estado de habilitación del usuario en el repositorio.");
transaction.Commit();
_logger.LogInformation("Estado de habilitación cambiado a {Estado} para Usuario ID {TargetUserId} por Usuario ID {AdminUserId}.", habilitar, userId, idUsuarioModificador);
return (true, null);
}
catch (KeyNotFoundException) {
try { transaction.Rollback(); } catch { /* Log */ }
catch (KeyNotFoundException)
{
try { transaction.Rollback(); } catch { /* Log */ }
return (false, "Usuario no encontrado durante el cambio de estado.");
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { /* Log */ }
try { transaction.Rollback(); } catch { /* Log */ }
_logger.LogError(ex, "Error al cambiar estado de habilitación para Usuario ID {TargetUserId}.", userId);
return (false, $"Error interno al cambiar estado de habilitación: {ex.Message}");
}
}
public async Task<IEnumerable<UsuarioHistorialDto>> ObtenerHistorialPorUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta)
{
// Aquí podrías añadir validaciones extra si fuera necesario antes de llamar al repo
return await _usuarioRepository.GetHistorialByUsuarioIdAsync(idUsuarioAfectado, fechaDesde, fechaHasta);
}
public async Task<IEnumerable<UsuarioHistorialDto>> ObtenerTodoElHistorialAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModificoFilter, string? tipoModFilter)
{
// Aquí podrías añadir validaciones extra
return await _usuarioRepository.GetAllHistorialAsync(fechaDesde, fechaHasta, idUsuarioModificoFilter, tipoModFilter);
}
}
}

View File

@@ -12,6 +12,7 @@
"Microsoft.AspNetCore.Authentication.JwtBearer": "9.0.4",
"Microsoft.AspNetCore.OpenApi": "9.0.3",
"Microsoft.Data.SqlClient": "6.0.2",
"NPOI": "2.7.3",
"Swashbuckle.AspNetCore": "8.1.1",
"System.IdentityModel.Tokens.Jwt": "8.9.0"
},
@@ -54,6 +55,14 @@
}
}
},
"BouncyCastle.Cryptography/2.3.1": {
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.3.1.17862"
}
}
},
"Dapper/2.1.66": {
"runtime": {
"lib/net8.0/Dapper.dll": {
@@ -62,6 +71,30 @@
}
}
},
"Enums.NET/4.0.1": {
"runtime": {
"lib/netcoreapp3.0/Enums.NET.dll": {
"assemblyVersion": "4.0.0.0",
"fileVersion": "4.0.1.0"
}
}
},
"ExtendedNumerics.BigDecimal/2025.1001.2.129": {
"runtime": {
"lib/net8.0/ExtendedNumerics.BigDecimal.dll": {
"assemblyVersion": "2025.1001.2.129",
"fileVersion": "2025.1001.2.129"
}
}
},
"MathNet.Numerics.Signed/5.0.0": {
"runtime": {
"lib/net6.0/MathNet.Numerics.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.0.0"
}
}
},
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
"dependencies": {
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.0.1"
@@ -348,6 +381,15 @@
}
}
},
"Microsoft.IO.RecyclableMemoryStream/3.0.0": {
"runtime": {
"lib/net6.0/Microsoft.IO.RecyclableMemoryStream.dll": {
"assemblyVersion": "3.0.0.0",
"fileVersion": "3.0.0.0"
}
}
},
"Microsoft.NETCore.Platforms/5.0.0": {},
"Microsoft.OpenApi/1.6.23": {
"runtime": {
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
@@ -364,6 +406,66 @@
}
}
},
"NPOI/2.7.3": {
"dependencies": {
"BouncyCastle.Cryptography": "2.3.1",
"Enums.NET": "4.0.1",
"ExtendedNumerics.BigDecimal": "2025.1001.2.129",
"MathNet.Numerics.Signed": "5.0.0",
"Microsoft.IO.RecyclableMemoryStream": "3.0.0",
"SharpZipLib": "1.4.2",
"SixLabors.Fonts": "1.0.1",
"SixLabors.ImageSharp": "2.1.10",
"System.Security.Cryptography.Pkcs": "9.0.4",
"System.Security.Cryptography.Xml": "8.0.2"
},
"runtime": {
"lib/net8.0/NPOI.Core.dll": {
"assemblyVersion": "2.7.3.0",
"fileVersion": "2.7.3.0"
},
"lib/net8.0/NPOI.OOXML.dll": {
"assemblyVersion": "2.7.3.0",
"fileVersion": "2.7.3.0"
},
"lib/net8.0/NPOI.OpenXml4Net.dll": {
"assemblyVersion": "2.7.3.0",
"fileVersion": "2.7.3.0"
},
"lib/net8.0/NPOI.OpenXmlFormats.dll": {
"assemblyVersion": "2.7.3.0",
"fileVersion": "2.7.3.0"
}
}
},
"SharpZipLib/1.4.2": {
"runtime": {
"lib/net6.0/ICSharpCode.SharpZipLib.dll": {
"assemblyVersion": "1.4.2.13",
"fileVersion": "1.4.2.13"
}
}
},
"SixLabors.Fonts/1.0.1": {
"runtime": {
"lib/netcoreapp3.1/SixLabors.Fonts.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.1.0"
}
}
},
"SixLabors.ImageSharp/2.1.10": {
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encoding.CodePages": "5.0.0"
},
"runtime": {
"lib/netcoreapp3.1/SixLabors.ImageSharp.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.1.10.0"
}
}
},
"Swashbuckle.AspNetCore/8.1.1": {
"dependencies": {
"Microsoft.Extensions.ApiDescription.Server": "6.0.5",
@@ -498,6 +600,16 @@
}
}
},
"System.Security.Cryptography.Xml/8.0.2": {
"dependencies": {
"System.Security.Cryptography.Pkcs": "9.0.4"
}
},
"System.Text.Encoding.CodePages/5.0.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "5.0.0"
}
},
"System.Text.Encodings.Web/4.7.2": {},
"System.Text.Json/4.7.2": {},
"System.Threading.Tasks.Extensions/4.5.4": {}
@@ -523,6 +635,13 @@
"path": "azure.identity/1.11.4",
"hashPath": "azure.identity.1.11.4.nupkg.sha512"
},
"BouncyCastle.Cryptography/2.3.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-buwoISwecYke3CmgG1AQSg+sNZjJeIb93vTAtJiHZX35hP/teYMxsfg0NDXGUKjGx6BKBTNKc77O2M3vKvlXZQ==",
"path": "bouncycastle.cryptography/2.3.1",
"hashPath": "bouncycastle.cryptography.2.3.1.nupkg.sha512"
},
"Dapper/2.1.66": {
"type": "package",
"serviceable": true,
@@ -530,6 +649,27 @@
"path": "dapper/2.1.66",
"hashPath": "dapper.2.1.66.nupkg.sha512"
},
"Enums.NET/4.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-OUGCd5L8zHZ61GAf436G0gf/H6yrSUkEpV5vm2CbCUuz9Rx7iLFLP5iHSSfmOtqNpuyo4vYte0CvYEmPZXRmRQ==",
"path": "enums.net/4.0.1",
"hashPath": "enums.net.4.0.1.nupkg.sha512"
},
"ExtendedNumerics.BigDecimal/2025.1001.2.129": {
"type": "package",
"serviceable": true,
"sha512": "sha512-+woGT1lsBtwkntOpx2EZbdbySv0aWPefE0vrfvclxVdbi4oa2bbtphFPWgMiQe+kRCPICbfFJwp6w1DuR7Ge2Q==",
"path": "extendednumerics.bigdecimal/2025.1001.2.129",
"hashPath": "extendednumerics.bigdecimal.2025.1001.2.129.nupkg.sha512"
},
"MathNet.Numerics.Signed/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-PSrHBVMf41SjbhlnpOMnoir8YgkyEJ6/nwxvjYpH+vJCexNcx2ms6zRww5yLVqLet1xLJgZ39swtKRTLhWdnAw==",
"path": "mathnet.numerics.signed/5.0.0",
"hashPath": "mathnet.numerics.signed.5.0.0.nupkg.sha512"
},
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
"type": "package",
"serviceable": true,
@@ -677,6 +817,20 @@
"path": "microsoft.identitymodel.tokens/8.9.0",
"hashPath": "microsoft.identitymodel.tokens.8.9.0.nupkg.sha512"
},
"Microsoft.IO.RecyclableMemoryStream/3.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==",
"path": "microsoft.io.recyclablememorystream/3.0.0",
"hashPath": "microsoft.io.recyclablememorystream.3.0.0.nupkg.sha512"
},
"Microsoft.NETCore.Platforms/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==",
"path": "microsoft.netcore.platforms/5.0.0",
"hashPath": "microsoft.netcore.platforms.5.0.0.nupkg.sha512"
},
"Microsoft.OpenApi/1.6.23": {
"type": "package",
"serviceable": true,
@@ -691,6 +845,34 @@
"path": "microsoft.sqlserver.server/1.0.0",
"hashPath": "microsoft.sqlserver.server.1.0.0.nupkg.sha512"
},
"NPOI/2.7.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-iCZx3DSwUSwaV61E8tXgPlPuxYmcYV/Zi405nGlxQvWaGTAbuc0KvSBjsLucQUJ92iMeetT8iK9makLfF4uZ3g==",
"path": "npoi/2.7.3",
"hashPath": "npoi.2.7.3.nupkg.sha512"
},
"SharpZipLib/1.4.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A==",
"path": "sharpziplib/1.4.2",
"hashPath": "sharpziplib.1.4.2.nupkg.sha512"
},
"SixLabors.Fonts/1.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ljezRHWc7N0azdQViib7Aa5v+DagRVkKI2/93kEbtjVczLs+yTkSq6gtGmvOcx4IqyNbO3GjLt7SAQTpLkySNw==",
"path": "sixlabors.fonts/1.0.1",
"hashPath": "sixlabors.fonts.1.0.1.nupkg.sha512"
},
"SixLabors.ImageSharp/2.1.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-hk1E7U3RSlxrBVo6Gb6OjeM52fChpFYH+SZvyT/M2vzSGlzAaKE33hc3V/Pvnjcnn1opT8/Z+0QfqdM5HsIaeA==",
"path": "sixlabors.imagesharp/2.1.10",
"hashPath": "sixlabors.imagesharp.2.1.10.nupkg.sha512"
},
"Swashbuckle.AspNetCore/8.1.1": {
"type": "package",
"serviceable": true,
@@ -796,6 +978,20 @@
"path": "system.security.cryptography.protecteddata/9.0.4",
"hashPath": "system.security.cryptography.protecteddata.9.0.4.nupkg.sha512"
},
"System.Security.Cryptography.Xml/8.0.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-aDM/wm0ZGEZ6ZYJLzgqjp2FZdHbDHh6/OmpGfb7AdZ105zYmPn/83JRU2xLIbwgoNz9U1SLUTJN0v5th3qmvjA==",
"path": "system.security.cryptography.xml/8.0.2",
"hashPath": "system.security.cryptography.xml.8.0.2.nupkg.sha512"
},
"System.Text.Encoding.CodePages/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
"path": "system.text.encoding.codepages/5.0.0",
"hashPath": "system.text.encoding.codepages.5.0.0.nupkg.sha512"
},
"System.Text.Encodings.Web/4.7.2": {
"type": "package",
"serviceable": true,

View File

@@ -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+b6ba52f074807c7a2fddc76ab3cc2c45c446c1f8")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+e7e185a9cb2950cb77c96951d4c04ca768955d27")]
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -78,3 +78,15 @@ E:\GestionIntegralWeb\backend\gestionintegral.api\obj\Debug\net9.0\GestionIntegr
E:\GestionIntegralWeb\backend\gestionintegral.api\obj\Debug\net9.0\GestionIntegral.Api.genruntimeconfig.cache
E:\GestionIntegralWeb\backend\gestionintegral.api\obj\Debug\net9.0\ref\GestionIntegral.Api.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\obj\Debug\net9.0\staticwebassets.build.json.cache
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\BouncyCastle.Cryptography.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\Enums.NET.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\ExtendedNumerics.BigDecimal.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\MathNet.Numerics.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\Microsoft.IO.RecyclableMemoryStream.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\NPOI.Core.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\NPOI.OOXML.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\NPOI.OpenXml4Net.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\NPOI.OpenXmlFormats.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\ICSharpCode.SharpZipLib.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\SixLabors.Fonts.dll
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\SixLabors.ImageSharp.dll

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","kULolnJcJq9du0a0dBwZaPVupTEFX15sai6mOONU2qk="],"CachedAssets":{},"CachedCopyCandidates":{}}
{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","898tiJH7z5LyOA6iz/6l2n6u\u002Bf/3Afm3R6QKhRmMlf0="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","kULolnJcJq9du0a0dBwZaPVupTEFX15sai6mOONU2qk="],"CachedAssets":{},"CachedCopyCandidates":{}}
{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","898tiJH7z5LyOA6iz/6l2n6u\u002Bf/3Afm3R6QKhRmMlf0="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -66,6 +66,10 @@
"target": "Package",
"version": "[6.0.2, )"
},
"NPOI": {
"target": "Package",
"version": "[2.7.3, )"
},
"Swashbuckle.AspNetCore": {
"target": "Package",
"version": "[8.1.1, )"

View File

@@ -47,6 +47,19 @@
}
}
},
"BouncyCastle.Cryptography/2.3.1": {
"type": "package",
"compile": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"related": ".xml"
}
}
},
"Dapper/2.1.66": {
"type": "package",
"compile": {
@@ -60,6 +73,45 @@
}
}
},
"Enums.NET/4.0.1": {
"type": "package",
"compile": {
"lib/netcoreapp3.0/Enums.NET.dll": {
"related": ".pdb;.xml"
}
},
"runtime": {
"lib/netcoreapp3.0/Enums.NET.dll": {
"related": ".pdb;.xml"
}
}
},
"ExtendedNumerics.BigDecimal/2025.1001.2.129": {
"type": "package",
"compile": {
"lib/net8.0/ExtendedNumerics.BigDecimal.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net8.0/ExtendedNumerics.BigDecimal.dll": {
"related": ".xml"
}
}
},
"MathNet.Numerics.Signed/5.0.0": {
"type": "package",
"compile": {
"lib/net6.0/MathNet.Numerics.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net6.0/MathNet.Numerics.dll": {
"related": ".xml"
}
}
},
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
"type": "package",
"dependencies": {
@@ -470,6 +522,28 @@
}
}
},
"Microsoft.IO.RecyclableMemoryStream/3.0.0": {
"type": "package",
"compile": {
"lib/net6.0/Microsoft.IO.RecyclableMemoryStream.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net6.0/Microsoft.IO.RecyclableMemoryStream.dll": {
"related": ".xml"
}
}
},
"Microsoft.NETCore.Platforms/5.0.0": {
"type": "package",
"compile": {
"lib/netstandard1.0/_._": {}
},
"runtime": {
"lib/netstandard1.0/_._": {}
}
},
"Microsoft.OpenApi/1.6.23": {
"type": "package",
"compile": {
@@ -496,6 +570,92 @@
}
}
},
"NPOI/2.7.3": {
"type": "package",
"dependencies": {
"BouncyCastle.Cryptography": "2.3.1",
"Enums.NET": "4.0.1",
"ExtendedNumerics.BigDecimal": "2025.1001.2.129",
"MathNet.Numerics.Signed": "5.0.0",
"Microsoft.IO.RecyclableMemoryStream": "3.0.0",
"SharpZipLib": "1.4.2",
"SixLabors.Fonts": "1.0.1",
"SixLabors.ImageSharp": "2.1.10",
"System.Security.Cryptography.Pkcs": "8.0.1",
"System.Security.Cryptography.Xml": "8.0.2"
},
"compile": {
"lib/net8.0/NPOI.Core.dll": {
"related": ".pdb;.xml"
},
"lib/net8.0/NPOI.OOXML.dll": {
"related": ".pdb;.xml"
},
"lib/net8.0/NPOI.OpenXml4Net.dll": {
"related": ".pdb;.xml"
},
"lib/net8.0/NPOI.OpenXmlFormats.dll": {
"related": ".pdb;.xml"
}
},
"runtime": {
"lib/net8.0/NPOI.Core.dll": {
"related": ".pdb;.xml"
},
"lib/net8.0/NPOI.OOXML.dll": {
"related": ".pdb;.xml"
},
"lib/net8.0/NPOI.OpenXml4Net.dll": {
"related": ".pdb;.xml"
},
"lib/net8.0/NPOI.OpenXmlFormats.dll": {
"related": ".pdb;.xml"
}
}
},
"SharpZipLib/1.4.2": {
"type": "package",
"compile": {
"lib/net6.0/ICSharpCode.SharpZipLib.dll": {
"related": ".pdb;.xml"
}
},
"runtime": {
"lib/net6.0/ICSharpCode.SharpZipLib.dll": {
"related": ".pdb;.xml"
}
}
},
"SixLabors.Fonts/1.0.1": {
"type": "package",
"compile": {
"lib/netcoreapp3.1/SixLabors.Fonts.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/netcoreapp3.1/SixLabors.Fonts.dll": {
"related": ".xml"
}
}
},
"SixLabors.ImageSharp/2.1.10": {
"type": "package",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "5.0.0",
"System.Text.Encoding.CodePages": "5.0.0"
},
"compile": {
"lib/netcoreapp3.1/SixLabors.ImageSharp.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/netcoreapp3.1/SixLabors.ImageSharp.dll": {
"related": ".xml"
}
}
},
"Swashbuckle.AspNetCore/8.1.1": {
"type": "package",
"dependencies": {
@@ -750,6 +910,47 @@
"buildTransitive/net8.0/_._": {}
}
},
"System.Security.Cryptography.Xml/8.0.2": {
"type": "package",
"dependencies": {
"System.Security.Cryptography.Pkcs": "8.0.1"
},
"compile": {
"lib/net8.0/System.Security.Cryptography.Xml.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net8.0/System.Security.Cryptography.Xml.dll": {
"related": ".xml"
}
},
"build": {
"buildTransitive/net6.0/_._": {}
}
},
"System.Text.Encoding.CodePages/5.0.0": {
"type": "package",
"dependencies": {
"Microsoft.NETCore.Platforms": "5.0.0"
},
"compile": {
"lib/netstandard2.0/System.Text.Encoding.CodePages.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/netstandard2.0/System.Text.Encoding.CodePages.dll": {
"related": ".xml"
}
},
"runtimeTargets": {
"runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": {
"assetType": "runtime",
"rid": "win"
}
}
},
"System.Text.Encodings.Web/4.7.2": {
"type": "package",
"compile": {
@@ -826,6 +1027,26 @@
"lib/netstandard2.0/Azure.Identity.xml"
]
},
"BouncyCastle.Cryptography/2.3.1": {
"sha512": "buwoISwecYke3CmgG1AQSg+sNZjJeIb93vTAtJiHZX35hP/teYMxsfg0NDXGUKjGx6BKBTNKc77O2M3vKvlXZQ==",
"type": "package",
"path": "bouncycastle.cryptography/2.3.1",
"files": [
".nupkg.metadata",
".signature.p7s",
"LICENSE.md",
"README.md",
"bouncycastle.cryptography.2.3.1.nupkg.sha512",
"bouncycastle.cryptography.nuspec",
"lib/net461/BouncyCastle.Cryptography.dll",
"lib/net461/BouncyCastle.Cryptography.xml",
"lib/net6.0/BouncyCastle.Cryptography.dll",
"lib/net6.0/BouncyCastle.Cryptography.xml",
"lib/netstandard2.0/BouncyCastle.Cryptography.dll",
"lib/netstandard2.0/BouncyCastle.Cryptography.xml",
"packageIcon.png"
]
},
"Dapper/2.1.66": {
"sha512": "/q77jUgDOS+bzkmk3Vy9SiWMaetTw+NOoPAV0xPBsGVAyljd5S6P+4RUW7R3ZUGGr9lDRyPKgAMj2UAOwvqZYw==",
"type": "package",
@@ -845,6 +1066,94 @@
"readme.md"
]
},
"Enums.NET/4.0.1": {
"sha512": "OUGCd5L8zHZ61GAf436G0gf/H6yrSUkEpV5vm2CbCUuz9Rx7iLFLP5iHSSfmOtqNpuyo4vYte0CvYEmPZXRmRQ==",
"type": "package",
"path": "enums.net/4.0.1",
"files": [
".nupkg.metadata",
".signature.p7s",
"enums.net.4.0.1.nupkg.sha512",
"enums.net.nuspec",
"lib/net45/Enums.NET.dll",
"lib/net45/Enums.NET.pdb",
"lib/net45/Enums.NET.xml",
"lib/netcoreapp3.0/Enums.NET.dll",
"lib/netcoreapp3.0/Enums.NET.pdb",
"lib/netcoreapp3.0/Enums.NET.xml",
"lib/netstandard1.0/Enums.NET.dll",
"lib/netstandard1.0/Enums.NET.pdb",
"lib/netstandard1.0/Enums.NET.xml",
"lib/netstandard1.1/Enums.NET.dll",
"lib/netstandard1.1/Enums.NET.pdb",
"lib/netstandard1.1/Enums.NET.xml",
"lib/netstandard1.3/Enums.NET.dll",
"lib/netstandard1.3/Enums.NET.pdb",
"lib/netstandard1.3/Enums.NET.xml",
"lib/netstandard2.0/Enums.NET.dll",
"lib/netstandard2.0/Enums.NET.pdb",
"lib/netstandard2.0/Enums.NET.xml",
"lib/netstandard2.1/Enums.NET.dll",
"lib/netstandard2.1/Enums.NET.pdb",
"lib/netstandard2.1/Enums.NET.xml"
]
},
"ExtendedNumerics.BigDecimal/2025.1001.2.129": {
"sha512": "+woGT1lsBtwkntOpx2EZbdbySv0aWPefE0vrfvclxVdbi4oa2bbtphFPWgMiQe+kRCPICbfFJwp6w1DuR7Ge2Q==",
"type": "package",
"path": "extendednumerics.bigdecimal/2025.1001.2.129",
"files": [
".nupkg.metadata",
".signature.p7s",
"README.md",
"extendednumerics.bigdecimal.2025.1001.2.129.nupkg.sha512",
"extendednumerics.bigdecimal.nuspec",
"lib/net45/ExtendedNumerics.BigDecimal.dll",
"lib/net45/ExtendedNumerics.BigDecimal.xml",
"lib/net46/ExtendedNumerics.BigDecimal.dll",
"lib/net46/ExtendedNumerics.BigDecimal.xml",
"lib/net472/ExtendedNumerics.BigDecimal.dll",
"lib/net472/ExtendedNumerics.BigDecimal.xml",
"lib/net48/ExtendedNumerics.BigDecimal.dll",
"lib/net48/ExtendedNumerics.BigDecimal.xml",
"lib/net5.0/ExtendedNumerics.BigDecimal.dll",
"lib/net5.0/ExtendedNumerics.BigDecimal.xml",
"lib/net6.0/ExtendedNumerics.BigDecimal.dll",
"lib/net6.0/ExtendedNumerics.BigDecimal.xml",
"lib/net7.0/ExtendedNumerics.BigDecimal.dll",
"lib/net7.0/ExtendedNumerics.BigDecimal.xml",
"lib/net8.0/ExtendedNumerics.BigDecimal.dll",
"lib/net8.0/ExtendedNumerics.BigDecimal.xml",
"lib/netcoreapp3.1/ExtendedNumerics.BigDecimal.dll",
"lib/netcoreapp3.1/ExtendedNumerics.BigDecimal.xml",
"lib/netstandard2.0/ExtendedNumerics.BigDecimal.dll",
"lib/netstandard2.0/ExtendedNumerics.BigDecimal.xml",
"lib/netstandard2.1/ExtendedNumerics.BigDecimal.dll",
"lib/netstandard2.1/ExtendedNumerics.BigDecimal.xml"
]
},
"MathNet.Numerics.Signed/5.0.0": {
"sha512": "PSrHBVMf41SjbhlnpOMnoir8YgkyEJ6/nwxvjYpH+vJCexNcx2ms6zRww5yLVqLet1xLJgZ39swtKRTLhWdnAw==",
"type": "package",
"path": "mathnet.numerics.signed/5.0.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"icon.png",
"lib/net461/MathNet.Numerics.dll",
"lib/net461/MathNet.Numerics.xml",
"lib/net48/MathNet.Numerics.dll",
"lib/net48/MathNet.Numerics.xml",
"lib/net5.0/MathNet.Numerics.dll",
"lib/net5.0/MathNet.Numerics.xml",
"lib/net6.0/MathNet.Numerics.dll",
"lib/net6.0/MathNet.Numerics.xml",
"lib/netstandard2.0/MathNet.Numerics.dll",
"lib/netstandard2.0/MathNet.Numerics.xml",
"mathnet.numerics.signed.5.0.0.nupkg.sha512",
"mathnet.numerics.signed.nuspec"
]
},
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
"sha512": "0HgfWPfnjlzWFbW4pw6FYNuIMV8obVU+MUkiZ33g4UOpvZcmdWzdayfheKPZ5+EUly8SvfgW0dJwwIrW4IVLZQ==",
"type": "package",
@@ -1655,6 +1964,42 @@
"microsoft.identitymodel.tokens.nuspec"
]
},
"Microsoft.IO.RecyclableMemoryStream/3.0.0": {
"sha512": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==",
"type": "package",
"path": "microsoft.io.recyclablememorystream/3.0.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"README.md",
"lib/net6.0/Microsoft.IO.RecyclableMemoryStream.dll",
"lib/net6.0/Microsoft.IO.RecyclableMemoryStream.xml",
"lib/netstandard2.0/Microsoft.IO.RecyclableMemoryStream.dll",
"lib/netstandard2.0/Microsoft.IO.RecyclableMemoryStream.xml",
"lib/netstandard2.1/Microsoft.IO.RecyclableMemoryStream.dll",
"lib/netstandard2.1/Microsoft.IO.RecyclableMemoryStream.xml",
"microsoft.io.recyclablememorystream.3.0.0.nupkg.sha512",
"microsoft.io.recyclablememorystream.nuspec"
]
},
"Microsoft.NETCore.Platforms/5.0.0": {
"sha512": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==",
"type": "package",
"path": "microsoft.netcore.platforms/5.0.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"Icon.png",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"lib/netstandard1.0/_._",
"microsoft.netcore.platforms.5.0.0.nupkg.sha512",
"microsoft.netcore.platforms.nuspec",
"runtime.json",
"useSharedDesignerContext.txt",
"version.txt"
]
},
"Microsoft.OpenApi/1.6.23": {
"sha512": "tZ1I0KXnn98CWuV8cpI247A17jaY+ILS9vvF7yhI0uPPEqF4P1d7BWL5Uwtel10w9NucllHB3nTkfYTAcHAh8g==",
"type": "package",
@@ -1688,6 +2033,132 @@
"microsoft.sqlserver.server.nuspec"
]
},
"NPOI/2.7.3": {
"sha512": "iCZx3DSwUSwaV61E8tXgPlPuxYmcYV/Zi405nGlxQvWaGTAbuc0KvSBjsLucQUJ92iMeetT8iK9makLfF4uZ3g==",
"type": "package",
"path": "npoi/2.7.3",
"files": [
".nupkg.metadata",
".signature.p7s",
"LICENSE",
"README.md",
"lib/net472/NPOI.Core.dll",
"lib/net472/NPOI.Core.pdb",
"lib/net472/NPOI.Core.xml",
"lib/net472/NPOI.OOXML.dll",
"lib/net472/NPOI.OOXML.pdb",
"lib/net472/NPOI.OOXML.xml",
"lib/net472/NPOI.OpenXml4Net.dll",
"lib/net472/NPOI.OpenXml4Net.pdb",
"lib/net472/NPOI.OpenXml4Net.xml",
"lib/net472/NPOI.OpenXmlFormats.dll",
"lib/net472/NPOI.OpenXmlFormats.pdb",
"lib/net472/NPOI.OpenXmlFormats.xml",
"lib/net8.0/NPOI.Core.dll",
"lib/net8.0/NPOI.Core.pdb",
"lib/net8.0/NPOI.Core.xml",
"lib/net8.0/NPOI.OOXML.dll",
"lib/net8.0/NPOI.OOXML.pdb",
"lib/net8.0/NPOI.OOXML.xml",
"lib/net8.0/NPOI.OpenXml4Net.dll",
"lib/net8.0/NPOI.OpenXml4Net.pdb",
"lib/net8.0/NPOI.OpenXml4Net.xml",
"lib/net8.0/NPOI.OpenXmlFormats.dll",
"lib/net8.0/NPOI.OpenXmlFormats.pdb",
"lib/net8.0/NPOI.OpenXmlFormats.xml",
"lib/netstandard2.0/NPOI.Core.dll",
"lib/netstandard2.0/NPOI.Core.pdb",
"lib/netstandard2.0/NPOI.Core.xml",
"lib/netstandard2.0/NPOI.OOXML.dll",
"lib/netstandard2.0/NPOI.OOXML.pdb",
"lib/netstandard2.0/NPOI.OOXML.xml",
"lib/netstandard2.0/NPOI.OpenXml4Net.dll",
"lib/netstandard2.0/NPOI.OpenXml4Net.pdb",
"lib/netstandard2.0/NPOI.OpenXml4Net.xml",
"lib/netstandard2.0/NPOI.OpenXmlFormats.dll",
"lib/netstandard2.0/NPOI.OpenXmlFormats.pdb",
"lib/netstandard2.0/NPOI.OpenXmlFormats.xml",
"lib/netstandard2.1/NPOI.Core.dll",
"lib/netstandard2.1/NPOI.Core.pdb",
"lib/netstandard2.1/NPOI.Core.xml",
"lib/netstandard2.1/NPOI.OOXML.dll",
"lib/netstandard2.1/NPOI.OOXML.pdb",
"lib/netstandard2.1/NPOI.OOXML.xml",
"lib/netstandard2.1/NPOI.OpenXml4Net.dll",
"lib/netstandard2.1/NPOI.OpenXml4Net.pdb",
"lib/netstandard2.1/NPOI.OpenXml4Net.xml",
"lib/netstandard2.1/NPOI.OpenXmlFormats.dll",
"lib/netstandard2.1/NPOI.OpenXmlFormats.pdb",
"lib/netstandard2.1/NPOI.OpenXmlFormats.xml",
"logo/120_120.jpg",
"logo/240_240.png",
"logo/32_32.jpg",
"logo/60_60.jpg",
"npoi.2.7.3.nupkg.sha512",
"npoi.nuspec"
]
},
"SharpZipLib/1.4.2": {
"sha512": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A==",
"type": "package",
"path": "sharpziplib/1.4.2",
"files": [
".nupkg.metadata",
".signature.p7s",
"images/sharpziplib-nuget-256x256.png",
"lib/net6.0/ICSharpCode.SharpZipLib.dll",
"lib/net6.0/ICSharpCode.SharpZipLib.pdb",
"lib/net6.0/ICSharpCode.SharpZipLib.xml",
"lib/netstandard2.0/ICSharpCode.SharpZipLib.dll",
"lib/netstandard2.0/ICSharpCode.SharpZipLib.pdb",
"lib/netstandard2.0/ICSharpCode.SharpZipLib.xml",
"lib/netstandard2.1/ICSharpCode.SharpZipLib.dll",
"lib/netstandard2.1/ICSharpCode.SharpZipLib.pdb",
"lib/netstandard2.1/ICSharpCode.SharpZipLib.xml",
"sharpziplib.1.4.2.nupkg.sha512",
"sharpziplib.nuspec"
]
},
"SixLabors.Fonts/1.0.1": {
"sha512": "ljezRHWc7N0azdQViib7Aa5v+DagRVkKI2/93kEbtjVczLs+yTkSq6gtGmvOcx4IqyNbO3GjLt7SAQTpLkySNw==",
"type": "package",
"path": "sixlabors.fonts/1.0.1",
"files": [
".nupkg.metadata",
".signature.p7s",
"lib/netcoreapp3.1/SixLabors.Fonts.dll",
"lib/netcoreapp3.1/SixLabors.Fonts.xml",
"lib/netstandard2.0/SixLabors.Fonts.dll",
"lib/netstandard2.0/SixLabors.Fonts.xml",
"lib/netstandard2.1/SixLabors.Fonts.dll",
"lib/netstandard2.1/SixLabors.Fonts.xml",
"sixlabors.fonts.1.0.1.nupkg.sha512",
"sixlabors.fonts.128.png",
"sixlabors.fonts.nuspec"
]
},
"SixLabors.ImageSharp/2.1.10": {
"sha512": "hk1E7U3RSlxrBVo6Gb6OjeM52fChpFYH+SZvyT/M2vzSGlzAaKE33hc3V/Pvnjcnn1opT8/Z+0QfqdM5HsIaeA==",
"type": "package",
"path": "sixlabors.imagesharp/2.1.10",
"files": [
".nupkg.metadata",
".signature.p7s",
"lib/net472/SixLabors.ImageSharp.dll",
"lib/net472/SixLabors.ImageSharp.xml",
"lib/netcoreapp2.1/SixLabors.ImageSharp.dll",
"lib/netcoreapp2.1/SixLabors.ImageSharp.xml",
"lib/netcoreapp3.1/SixLabors.ImageSharp.dll",
"lib/netcoreapp3.1/SixLabors.ImageSharp.xml",
"lib/netstandard2.0/SixLabors.ImageSharp.dll",
"lib/netstandard2.0/SixLabors.ImageSharp.xml",
"lib/netstandard2.1/SixLabors.ImageSharp.dll",
"lib/netstandard2.1/SixLabors.ImageSharp.xml",
"sixlabors.imagesharp.128.png",
"sixlabors.imagesharp.2.1.10.nupkg.sha512",
"sixlabors.imagesharp.nuspec"
]
},
"Swashbuckle.AspNetCore/8.1.1": {
"sha512": "HJHexmU0PiYevgTLvKjYkxEtclF2w4O7iTd3Ef3p6KeT0kcYLpkFVgCw6glpGS57h8769anv8G+NFi9Kge+/yw==",
"type": "package",
@@ -2074,6 +2545,76 @@
"useSharedDesignerContext.txt"
]
},
"System.Security.Cryptography.Xml/8.0.2": {
"sha512": "aDM/wm0ZGEZ6ZYJLzgqjp2FZdHbDHh6/OmpGfb7AdZ105zYmPn/83JRU2xLIbwgoNz9U1SLUTJN0v5th3qmvjA==",
"type": "package",
"path": "system.security.cryptography.xml/8.0.2",
"files": [
".nupkg.metadata",
".signature.p7s",
"Icon.png",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"buildTransitive/net461/System.Security.Cryptography.Xml.targets",
"buildTransitive/net462/_._",
"buildTransitive/net6.0/_._",
"buildTransitive/netcoreapp2.0/System.Security.Cryptography.Xml.targets",
"lib/net462/System.Security.Cryptography.Xml.dll",
"lib/net462/System.Security.Cryptography.Xml.xml",
"lib/net6.0/System.Security.Cryptography.Xml.dll",
"lib/net6.0/System.Security.Cryptography.Xml.xml",
"lib/net7.0/System.Security.Cryptography.Xml.dll",
"lib/net7.0/System.Security.Cryptography.Xml.xml",
"lib/net8.0/System.Security.Cryptography.Xml.dll",
"lib/net8.0/System.Security.Cryptography.Xml.xml",
"lib/netstandard2.0/System.Security.Cryptography.Xml.dll",
"lib/netstandard2.0/System.Security.Cryptography.Xml.xml",
"system.security.cryptography.xml.8.0.2.nupkg.sha512",
"system.security.cryptography.xml.nuspec",
"useSharedDesignerContext.txt"
]
},
"System.Text.Encoding.CodePages/5.0.0": {
"sha512": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
"type": "package",
"path": "system.text.encoding.codepages/5.0.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"Icon.png",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"lib/MonoAndroid10/_._",
"lib/MonoTouch10/_._",
"lib/net46/System.Text.Encoding.CodePages.dll",
"lib/net461/System.Text.Encoding.CodePages.dll",
"lib/net461/System.Text.Encoding.CodePages.xml",
"lib/netstandard1.3/System.Text.Encoding.CodePages.dll",
"lib/netstandard2.0/System.Text.Encoding.CodePages.dll",
"lib/netstandard2.0/System.Text.Encoding.CodePages.xml",
"lib/xamarinios10/_._",
"lib/xamarinmac20/_._",
"lib/xamarintvos10/_._",
"lib/xamarinwatchos10/_._",
"ref/MonoAndroid10/_._",
"ref/MonoTouch10/_._",
"ref/xamarinios10/_._",
"ref/xamarinmac20/_._",
"ref/xamarintvos10/_._",
"ref/xamarinwatchos10/_._",
"runtimes/win/lib/net461/System.Text.Encoding.CodePages.dll",
"runtimes/win/lib/net461/System.Text.Encoding.CodePages.xml",
"runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll",
"runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.xml",
"runtimes/win/lib/netstandard1.3/System.Text.Encoding.CodePages.dll",
"runtimes/win/lib/netstandard2.0/System.Text.Encoding.CodePages.dll",
"runtimes/win/lib/netstandard2.0/System.Text.Encoding.CodePages.xml",
"system.text.encoding.codepages.5.0.0.nupkg.sha512",
"system.text.encoding.codepages.nuspec",
"useSharedDesignerContext.txt",
"version.txt"
]
},
"System.Text.Encodings.Web/4.7.2": {
"sha512": "iTUgB/WtrZ1sWZs84F2hwyQhiRH6QNjQv2DkwrH+WP6RoFga2Q1m3f9/Q7FG8cck8AdHitQkmkXSY8qylcDmuA==",
"type": "package",
@@ -2164,6 +2705,7 @@
"Microsoft.AspNetCore.Authentication.JwtBearer >= 9.0.4",
"Microsoft.AspNetCore.OpenApi >= 9.0.3",
"Microsoft.Data.SqlClient >= 6.0.2",
"NPOI >= 2.7.3",
"Swashbuckle.AspNetCore >= 8.1.1",
"System.IdentityModel.Tokens.Jwt >= 8.9.0"
]
@@ -2234,6 +2776,10 @@
"target": "Package",
"version": "[6.0.2, )"
},
"NPOI": {
"target": "Package",
"version": "[2.7.3, )"
},
"Swashbuckle.AspNetCore": {
"target": "Package",
"version": "[8.1.1, )"