feat(contables): cierre mensual de cuenta corriente de distribuidor

Permite congelar el saldo de un distribuidor por empresa a una fecha de
corte y bloquear modificaciones retroactivas sobre el período cerrado.
El saldo se calcula sumando movimientos en rango (sin tocar cue_Saldos).
Incluye reapertura controlada exclusivamente por SuperAdmin, reporte con
saldo inicial, atajo "Desde último cierre", y auditoría del ciclo de
vida _H. Permisos CC001/CC002/CC003. Middleware global mapea bloqueos
por período cerrado a HTTP 409.
This commit is contained in:
2026-05-07 12:03:26 -03:00
parent 7e274ef114
commit 24eaf18fd9
62 changed files with 2813 additions and 162 deletions

View File

@@ -0,0 +1,26 @@
using System;
namespace GestionIntegral.Api.Dtos.Auditoria
{
public class CierreCuentaCorrienteHistorialDto
{
public int Id_Historial { get; set; }
public int Id_Cierre { get; set; }
public int Id_Distribuidor { get; set; }
public int Id_Empresa { get; set; }
public DateTime FechaCorte { get; set; }
public DateTime FechaCierre { get; set; }
public decimal SaldoCierre { get; set; }
public string Estado { get; set; } = string.Empty;
public string? Justificacion { get; set; }
public int Id_Usuario_Cierre { get; set; }
public int? Id_Usuario_Anula { get; set; }
public DateTime? FechaAnulacion { get; set; }
public string? Justificacion_Anulacion { get; set; }
public string TipoMod { get; set; } = string.Empty; // 'Creacion' | 'Reapertura' | 'Modificacion'
public int Id_Usuario_Mod { get; set; }
public string NombreUsuarioModifico { get; set; } = string.Empty;
public DateTime FechaMod { get; set; }
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Contables
@@ -19,10 +20,16 @@ namespace GestionIntegral.Api.Dtos.Contables
[Required(ErrorMessage = "El monto del ajuste es obligatorio.")]
// Permitir montos negativos para disminuir deuda o positivos para aumentarla
// No se usa Range aquí para permitir ambos signos. La validación de que no sea cero se puede hacer en el servicio.
public decimal MontoAjuste { get; set; }
public decimal MontoAjuste { get; set; }
[Required(ErrorMessage = "La justificación del ajuste es obligatoria.")]
[StringLength(250, MinimumLength = 5, ErrorMessage = "La justificación debe tener entre 5 y 250 caracteres.")]
public string Justificacion { get; set; } = string.Empty;
// Fecha lógica de la operación. Se valida contra el último cierre vigente
// del par (Distribuidor + Empresa) para bloquear ajustes en períodos cerrados.
// Distinta de FechaAjuste, que es el momento de ejecución del ajuste en el sistema.
[Required(ErrorMessage = "La fecha de operación es obligatoria.")]
public DateTime FechaOperacion { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace GestionIntegral.Api.Dtos.Contables
{
public class CierreCuentaCorrienteDto
{
public int IdCierre { get; set; }
public int IdDistribuidor { get; set; }
public string NombreDistribuidor { get; set; } = string.Empty;
public int IdEmpresa { get; set; }
public string NombreEmpresa { get; set; } = string.Empty;
public string FechaCorte { get; set; } = string.Empty; // yyyy-MM-dd
public DateTime FechaCierre { get; set; }
public decimal SaldoCierre { get; set; }
public string Estado { get; set; } = string.Empty;
public string? Justificacion { get; set; }
public int IdUsuarioCierre { get; set; }
public string NombreUsuarioCierre { get; set; } = string.Empty;
public int? IdUsuarioAnula { get; set; }
public string? NombreUsuarioAnula { get; set; }
public DateTime? FechaAnulacion { get; set; }
public string? JustificacionAnulacion { get; set; }
public bool EsUltimoVigente { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Contables
{
public class CrearCierreDto
{
[Required(ErrorMessage = "El distribuidor es obligatorio.")]
[Range(1, int.MaxValue, ErrorMessage = "ID de Distribuidor inválido.")]
public int IdDistribuidor { get; set; }
[Required(ErrorMessage = "La empresa es obligatoria.")]
[Range(1, int.MaxValue, ErrorMessage = "ID de Empresa inválido.")]
public int IdEmpresa { get; set; }
[Required(ErrorMessage = "La fecha de corte es obligatoria.")]
public DateTime FechaCorte { get; set; }
[StringLength(500, ErrorMessage = "La justificación no puede superar los 500 caracteres.")]
public string? Justificacion { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Contables
{
public class ReabrirCierreDto
{
[Required(ErrorMessage = "La justificación es obligatoria al reabrir un cierre.")]
[StringLength(500, MinimumLength = 10, ErrorMessage = "La justificación debe tener entre 10 y 500 caracteres.")]
public string Justificacion { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace GestionIntegral.Api.Dtos.Contables
{
public class UltimoCierreDto
{
public int IdCierre { get; set; }
public string FechaCorte { get; set; } = string.Empty; // yyyy-MM-dd
public decimal SaldoCierre { get; set; }
public string Estado { get; set; } = string.Empty;
}
}

View File

@@ -7,8 +7,12 @@ namespace GestionIntegral.Api.Dtos.Reportes
public IEnumerable<BalanceCuentaDistDto> EntradasSalidas { get; set; } = new List<BalanceCuentaDistDto>();
public IEnumerable<BalanceCuentaDebCredDto> DebitosCreditos { get; set; } = new List<BalanceCuentaDebCredDto>();
public IEnumerable<BalanceCuentaPagosDto> Pagos { get; set; } = new List<BalanceCuentaPagosDto>();
public IEnumerable<SaldoDto> Saldos { get; set; } = new List<SaldoDto>(); // O podría ser SaldoDto SaldoActual si siempre es uno
public string? NombreDistribuidor { get; set; } // Para el título del reporte
public string? NombreEmpresa { get; set; } // Para el título del reporte
public string? NombreDistribuidor { get; set; }
public string? NombreEmpresa { get; set; }
// Saldo a la fecha desde elegida en el filtro:
// - Si existe cierre con FechaCorte < FechaDesde: SaldoCierre + movimientos netos entre fechaCierre+1 y fechaDesde-1.
// - Sin cierres previos: 0.
public decimal SaldoInicial { get; set; }
}
}

View File

@@ -10,9 +10,10 @@ namespace GestionIntegral.Api.Dtos.Reportes.ViewModels
public IEnumerable<BalanceCuentaDistDto> Movimientos { get; set; } = new List<BalanceCuentaDistDto>();
public IEnumerable<BalanceCuentaPagosDto> Pagos { get; set; } = new List<BalanceCuentaPagosDto>();
public IEnumerable<BalanceCuentaDebCredDto> DebitosCreditos { get; set; } = new List<BalanceCuentaDebCredDto>();
// Saldo real de la cuenta, se muestra al final sin usarse en cálculos intermedios.
public decimal SaldoDeCuenta { get; set; }
// Saldo inicial del período: snapshot del último cierre + movimientos netos hasta fechaDesde.
// 0 si no hay cierre previo.
public decimal SaldoInicial { get; set; }
// --- Parámetros del reporte ---
public string NombreDistribuidor { get; set; } = string.Empty;
@@ -24,6 +25,8 @@ namespace GestionIntegral.Api.Dtos.Reportes.ViewModels
public decimal TotalMovimientos => Movimientos.Sum(m => m.Debe - m.Haber);
public decimal TotalPagos => Pagos.Sum(p => p.Debe - p.Haber);
public decimal TotalDebitosCreditos => DebitosCreditos.Sum(d => d.Debe - d.Haber);
public decimal TotalPeriodo => TotalMovimientos + TotalPagos + TotalDebitosCreditos;
// Saldo Final = Saldo Inicial + suma neta del período (Debe - Haber por sección).
public decimal SaldoFinal => SaldoInicial + TotalMovimientos + TotalPagos + TotalDebitosCreditos;
}
}