< Summary

Information
Class: FAU.Logica.Services.AguinaldoPadronService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/AguinaldoPadronService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 251
Coverable lines: 251
Total lines: 356
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 54
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/AguinaldoPadronService.cs

#LineLine coverage
 1using ClosedXML.Excel;
 2using FAU.DataAccess;
 3using FAU.Entidades;
 4using Microsoft.EntityFrameworkCore;
 5
 6namespace FAU.Logica.Services;
 7
 8public class AguinaldoPadronService : IAguinaldoPadronService
 9{
 10    private readonly ApplicationDbContext _context;
 11
 012    public AguinaldoPadronService(ApplicationDbContext context)
 13    {
 014        _context = context;
 015    }
 16
 17    public async Task<(byte[] Bytes, string FileName)> GenerarPadronExcelAsync(int anio, int mes, bool esLeyVieja)
 18    {
 019        if (mes != 6 && mes != 12)
 020            throw new ArgumentException("El mes debe ser 6 o 12.", nameof(mes));
 21
 022        var semestre = (short)(mes == 6 ? 1 : 2);
 023        var periodoDate = new DateTime(anio, mes, 1);
 24
 25        // Cargar tramos de sanidad vigentes al período (mismo patrón que InsumosPeriodoLoader)
 026        var tramosRaw = await _context.TramosSanidadMilitar
 027            .AsNoTracking()
 028            .Where(t => t.VigenteDesde <= periodoDate && (t.VigenteHasta == null || t.VigenteHasta >= periodoDate))
 029            .ToListAsync();
 030        var tramos = tramosRaw
 031            .GroupBy(t => t.CodigoRetencion)
 032            .Select(g => g.OrderByDescending(t => t.VigenteDesde).First())
 033            .OrderBy(t => t.Desde)
 034            .ToList();
 35
 036        var rawData = await (
 037            from a in _context.Aguinaldos
 038            where a.Anio == (short)anio && a.Semestre == semestre
 039            join rl in _context.RelacionesLaborales on a.PersonaId equals rl.PersonaId
 040            where rl.Estado == EstadoRelacion.Activo && rl.Regimen.EsLeyVieja == esLeyVieja
 041            select new
 042            {
 043                rl.Persona.Cedula,
 044                rl.Persona.PrimerNombre,
 045                rl.Persona.SegundoNombre,
 046                rl.Persona.PrimerApellido,
 047                rl.Persona.SegundoApellido,
 048                GradoDenominacion = rl.Grado.Denominacion,
 049                GradoOrden = rl.Grado.Orden,
 050                EsOficial = rl.Grado.EsOficial,
 051                EsSubalterno = rl.Grado.EsSubalterno,
 052                SituacionCodigo = rl.Situacion.Codigo,
 053                SituacionDenominacion = rl.Situacion.Denominacion,
 054                ProgramaCodigo = rl.Programa.Codigo,
 055                ProgramaDenominacion = rl.Programa.Denominacion,
 056                Nominal = a.Nominal ?? 0m,
 057                Montepio = a.Montepio ?? 0m,
 058                Sanidad = a.Sanidad ?? 0m,
 059            }
 060        ).ToListAsync();
 61
 62        const decimal TasaSueldoRetiro = 0.01m;
 63
 064        var filas = rawData.Select(d =>
 065        {
 066            var nominal = d.Nominal;
 067            var sueldoRetiro = Math.Round(nominal * TasaSueldoRetiro, 2);
 068            var codigoSanidad = ObtenerCodigoSanidad(nominal, tramos);
 069            var liquido = Math.Round(nominal - sueldoRetiro - d.Montepio - d.Sanidad, 2);
 070            return new FilaPadron(
 071                d.Cedula,
 072                FormatApellidosNombres(d.PrimerApellido, d.SegundoApellido, d.PrimerNombre, d.SegundoNombre),
 073                d.GradoDenominacion,
 074                d.GradoOrden,
 075                d.EsOficial,
 076                d.EsSubalterno,
 077                d.SituacionCodigo,
 078                d.SituacionDenominacion,
 079                d.ProgramaCodigo,
 080                d.ProgramaDenominacion,
 081                nominal,
 082                sueldoRetiro,
 083                d.Montepio,
 084                d.Sanidad,
 085                codigoSanidad,
 086                liquido);
 087        }).ToList();
 88
 089        var leyLabel = esLeyVieja ? "ley_vieja" : "ley_nueva";
 090        var bytes = GenerarExcel(filas, anio, mes, esLeyVieja);
 091        return (bytes, $"padron_aguinaldo_{anio}_{mes:D2}_{leyLabel}.xlsx");
 092    }
 93
 94    // Devuelve el CodigoRetencion del tramo que contiene el nominal (5010 / 5011 / 5012)
 95    private static int ObtenerCodigoSanidad(decimal nominal, IReadOnlyList<TramoSanidadMilitar> tramos)
 96    {
 097        foreach (var t in tramos)
 098            if (nominal >= t.Desde && (t.Hasta == null || nominal <= t.Hasta))
 099                return t.CodigoRetencion;
 0100        return tramos.Count > 0 ? tramos[^1].CodigoRetencion : 5012;
 0101    }
 102
 103    private static byte[] GenerarExcel(List<FilaPadron> filas, int anio, int mes, bool esLeyVieja)
 104    {
 0105        using var wb = new XLWorkbook();
 0106        var ws = wb.Worksheets.Add("Padrón");
 107
 0108        var semNombre = mes == 6 ? "1er Semestre" : "2do Semestre";
 0109        var leyNombre = esLeyVieja ? "Ley Vieja (14.157)" : "Ley Nueva (19.695)";
 110        const int TotalCols = 13;
 111        const int RightCol = 11;
 0112        int row = 1;
 113
 114        // ── Cabezal ──────────────────────────────────────────────────────────
 0115        ws.Cell(row, 1).Value = "F.A.U. Dirección de Economía y Finanzas";
 0116        ws.Range(row, 1, row, RightCol - 1).Merge();
 0117        ws.Cell(row, RightCol).Value = $"Fecha: {DateTime.Now:dd/MM/yyyy}";
 0118        ws.Range(row, RightCol, row, TotalCols).Merge();
 0119        ws.Cell(row, RightCol).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right;
 0120        row++;
 121
 0122        ws.Cell(row, 1).Value = "Departamento de Administración Presupuestal";
 0123        ws.Range(row, 1, row, RightCol - 1).Merge();
 0124        ws.Cell(row, RightCol).Value = $"Hora : {DateTime.Now:HH:mm:ss}";
 0125        ws.Range(row, RightCol, row, TotalCols).Merge();
 0126        ws.Cell(row, RightCol).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right;
 0127        row++;
 128
 0129        ws.Cell(row, 1).Value = $"PADRÓN DE AGUINALDO | {leyNombre} | {semNombre} {anio}";
 0130        ws.Range(row, 1, row, TotalCols).Merge();
 0131        ws.Cell(row, 1).Style.Font.Bold = true;
 0132        ws.Cell(row, 1).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
 0133        row += 2;
 134
 135        // ── Cabecera de columnas (dos filas: grupos + individuales) ───────────
 0136        int groupRow = row;
 0137        int colRow = row + 1;
 0138        int dataStartRow = row + 2;
 139
 0140        var headerBg = XLColor.FromHtml("#4472C4");
 141
 0142        ws.Range(groupRow, 1, colRow, 1).Merge();
 0143        ws.Cell(groupRow, 1).Value = "Cédula";
 144
 0145        ws.Range(groupRow, 2, colRow, 2).Merge();
 0146        ws.Cell(groupRow, 2).Value = "Apellido y Nombre";
 147
 0148        ws.Cell(groupRow, 3).Value = "DISPON./";
 0149        ws.Range(groupRow, 3, groupRow, 6).Merge();
 150
 0151        ws.Cell(groupRow, 7).Value = "Montepíos";
 0152        ws.Range(groupRow, 7, groupRow, 9).Merge();
 153
 0154        ws.Cell(groupRow, 10).Value = "Sanidad";
 0155        ws.Range(groupRow, 10, groupRow, 12).Merge();
 156
 0157        ws.Range(groupRow, 13, colRow, 13).Merge();
 0158        ws.Cell(groupRow, 13).Value = "Líquido";
 159
 0160        ws.Cell(colRow, 3).Value = "Duodécimo";
 0161        ws.Cell(colRow, 4).Value = "SUB.TRAN.";
 0162        ws.Cell(colRow, 5).Value = "Suel. Ret.";
 0163        ws.Cell(colRow, 6).Value = "S.Fun";
 0164        ws.Cell(colRow, 7).Value = "Oficial";
 0165        ws.Cell(colRow, 8).Value = "Tropa";
 0166        ws.Cell(colRow, 9).Value = "Civil";
 0167        ws.Cell(colRow, 10).Value = "5.010";
 0168        ws.Cell(colRow, 11).Value = "5.011";
 0169        ws.Cell(colRow, 12).Value = "5.012";
 170
 0171        foreach (var r in new[] { groupRow, colRow })
 172        {
 0173            var rng = ws.Range(r, 1, r, TotalCols);
 0174            rng.Style.Font.Bold = true;
 0175            rng.Style.Fill.BackgroundColor = headerBg;
 0176            rng.Style.Font.FontColor = XLColor.White;
 0177            rng.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
 0178            rng.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
 0179            rng.Style.Alignment.WrapText = true;
 180        }
 181
 0182        row = dataStartRow;
 183
 0184        var totalGeneral = new TotalesPadron();
 185
 0186        var porPrograma = filas
 0187            .GroupBy(f => (f.ProgramaCodigo, f.ProgramaDenominacion))
 0188            .OrderBy(g => g.Key.ProgramaCodigo);
 189
 0190        foreach (var programaGroup in porPrograma)
 191        {
 0192            ws.Cell(row, 1).Value = $"Programa: {programaGroup.Key.ProgramaCodigo} - {programaGroup.Key.ProgramaDenomina
 0193            ws.Range(row, 1, row, TotalCols).Merge();
 0194            ws.Cell(row, 1).Style.Font.Bold = true;
 0195            ws.Cell(row, 1).Style.Fill.BackgroundColor = XLColor.FromHtml("#D9E1F2");
 0196            row++;
 197
 0198            var totalPrograma = new TotalesPadron();
 199
 0200            var porSituacion = programaGroup
 0201                .GroupBy(f => (f.SituacionCodigo, f.SituacionDenominacion))
 0202                .OrderBy(g => g.Key.SituacionCodigo);
 203
 0204            foreach (var situacionGroup in porSituacion)
 205            {
 0206                ws.Cell(row, 1).Value = $"  Situación: {situacionGroup.Key.SituacionCodigo} - {situacionGroup.Key.Situac
 0207                ws.Range(row, 1, row, TotalCols).Merge();
 0208                ws.Cell(row, 1).Style.Font.Italic = true;
 0209                ws.Cell(row, 1).Style.Fill.BackgroundColor = XLColor.FromHtml("#EEF2F9");
 0210                row++;
 211
 0212                var totalSituacion = new TotalesPadron();
 213
 0214                foreach (var fila in situacionGroup.OrderBy(f => f.GradoOrden).ThenBy(f => f.NombreCompleto))
 215                {
 0216                    EscribirFilaPadron(ws, row, fila);
 0217                    totalSituacion.Acumular(fila);
 0218                    row++;
 219                }
 220
 0221                EscribirSubtotalPadron(ws, row, TotalCols, $"Subtotal {situacionGroup.Key.SituacionDenominacion}", total
 0222                totalPrograma.Acumular(totalSituacion);
 0223                row++;
 224            }
 225
 0226            EscribirSubtotalPadron(ws, row, TotalCols, $"Subtotal Programa {programaGroup.Key.ProgramaCodigo}", totalPro
 0227            totalGeneral.Acumular(totalPrograma);
 0228            row++;
 0229            row++;
 230        }
 231
 0232        EscribirSubtotalPadron(ws, row, TotalCols, "TOTAL GENERAL", totalGeneral, XLColor.FromHtml("#4472C4"), XLColor.W
 233
 0234        ws.Range(dataStartRow, 3, row, TotalCols).Style.NumberFormat.Format = "#,##0.00";
 0235        ws.Columns().AdjustToContents();
 236
 0237        using var ms = new MemoryStream();
 0238        wb.SaveAs(ms);
 0239        return ms.ToArray();
 0240    }
 241
 242    private static void EscribirFilaPadron(IXLWorksheet ws, int row, FilaPadron f)
 243    {
 0244        ws.Cell(row, 1).Value = f.Cedula;
 0245        ws.Cell(row, 2).Value = f.NombreCompleto;
 0246        ws.Cell(row, 3).Value = f.Nominal;
 247        // col 4: DISPON./SUB.TRAN — reservada (vacía por ahora)
 0248        ws.Cell(row, 5).Value = f.SueldoRetiro;
 249
 250        // Montepío: solo una columna aplica por persona
 0251        if (f.EsOficial)
 0252            ws.Cell(row, 6).Value = f.Montepio;       // S.Fun
 0253        else if (f.EsSubalterno)
 0254            ws.Cell(row, 7).Value = f.Montepio;       // Oficial
 255        else
 0256            ws.Cell(row, 9).Value = f.Montepio;       // Civil (col 8 = Tropa, vacía)
 257
 258        // Sanidad: columna determinada por tramo (CodigoRetencion)
 0259        if (f.CodigoSanidad == 5010) ws.Cell(row, 10).Value = f.Sanidad;
 0260        else if (f.CodigoSanidad == 5011) ws.Cell(row, 11).Value = f.Sanidad;
 0261        else ws.Cell(row, 12).Value = f.Sanidad;
 262
 0263        ws.Cell(row, 13).Value = f.Liquido;
 0264    }
 265
 266    private static void EscribirSubtotalPadron(
 267        IXLWorksheet ws, int row, int totalCols, string etiqueta,
 268        TotalesPadron t, XLColor bgColor,
 269        XLColor? fontColor = null, int? fontSize = null)
 270    {
 0271        ws.Cell(row, 1).Value = etiqueta;
 0272        ws.Cell(row, 3).Value = t.Nominal;
 0273        ws.Cell(row, 5).Value = t.SueldoRetiro;
 0274        ws.Cell(row, 6).Value = t.MontepiSFun;
 0275        ws.Cell(row, 7).Value = t.MontepiOficial;
 0276        ws.Cell(row, 9).Value = t.MontepiCivil;
 0277        ws.Cell(row, 10).Value = t.San5010;
 0278        ws.Cell(row, 11).Value = t.San5011;
 0279        ws.Cell(row, 12).Value = t.San5012;
 0280        ws.Cell(row, 13).Value = t.Liquido;
 281
 0282        var rango = ws.Range(row, 1, row, totalCols);
 0283        rango.Style.Font.Bold = true;
 0284        rango.Style.Fill.BackgroundColor = bgColor;
 0285        if (fontColor != null)
 0286            rango.Style.Font.FontColor = fontColor;
 0287        if (fontSize.HasValue)
 0288            rango.Style.Font.FontSize = fontSize.Value;
 0289    }
 290
 291    private static string FormatApellidosNombres(
 292        string primerApellido, string? segundoApellido,
 293        string primerNombre, string? segundoNombre)
 294    {
 0295        var apellidos = primerApellido + (string.IsNullOrWhiteSpace(segundoApellido) ? "" : " " + segundoApellido);
 0296        var nombres = primerNombre + (string.IsNullOrWhiteSpace(segundoNombre) ? "" : " " + segundoNombre);
 0297        return $"{apellidos}, {nombres}";
 298    }
 299
 0300    private record FilaPadron(
 0301        string Cedula,
 0302        string NombreCompleto,
 0303        string GradoDenominacion,
 0304        short GradoOrden,
 0305        bool EsOficial,
 0306        bool EsSubalterno,
 0307        string SituacionCodigo,
 0308        string SituacionDenominacion,
 0309        string ProgramaCodigo,
 0310        string ProgramaDenominacion,
 0311        decimal Nominal,
 0312        decimal SueldoRetiro,
 0313        decimal Montepio,
 0314        decimal Sanidad,
 0315        int CodigoSanidad,   // 5010 / 5011 / 5012 según tramo
 0316        decimal Liquido);
 317
 318    private class TotalesPadron
 319    {
 0320        public decimal Nominal { get; private set; }
 0321        public decimal SueldoRetiro { get; private set; }
 0322        public decimal MontepiSFun { get; private set; }    // EsOficial → col S.Fun
 0323        public decimal MontepiOficial { get; private set; } // EsSubalterno → col Oficial
 0324        public decimal MontepiCivil { get; private set; }   // ni oficial ni subalterno → col Civil
 0325        public decimal San5010 { get; private set; }
 0326        public decimal San5011 { get; private set; }
 0327        public decimal San5012 { get; private set; }
 0328        public decimal Liquido { get; private set; }
 329
 330        public void Acumular(FilaPadron f)
 331        {
 0332            Nominal += f.Nominal;
 0333            SueldoRetiro += f.SueldoRetiro;
 0334            if (f.EsOficial) MontepiSFun += f.Montepio;
 0335            else if (f.EsSubalterno) MontepiOficial += f.Montepio;
 0336            else MontepiCivil += f.Montepio;
 0337            if (f.CodigoSanidad == 5010) San5010 += f.Sanidad;
 0338            else if (f.CodigoSanidad == 5011) San5011 += f.Sanidad;
 0339            else San5012 += f.Sanidad;
 0340            Liquido += f.Liquido;
 0341        }
 342
 343        public void Acumular(TotalesPadron t)
 344        {
 0345            Nominal += t.Nominal;
 0346            SueldoRetiro += t.SueldoRetiro;
 0347            MontepiSFun += t.MontepiSFun;
 0348            MontepiOficial += t.MontepiOficial;
 0349            MontepiCivil += t.MontepiCivil;
 0350            San5010 += t.San5010;
 0351            San5011 += t.San5011;
 0352            San5012 += t.San5012;
 0353            Liquido += t.Liquido;
 0354        }
 355    }
 356}

Methods/Properties

.ctor(FAU.DataAccess.ApplicationDbContext)
GenerarPadronExcelAsync()
ObtenerCodigoSanidad(System.Decimal,System.Collections.Generic.IReadOnlyList`1<FAU.Entidades.TramoSanidadMilitar>)
GenerarExcel(System.Collections.Generic.List`1<FAU.Logica.Services.AguinaldoPadronService/FilaPadron>,System.Int32,System.Int32,System.Boolean)
EscribirFilaPadron(ClosedXML.Excel.IXLWorksheet,System.Int32,FAU.Logica.Services.AguinaldoPadronService/FilaPadron)
EscribirSubtotalPadron(ClosedXML.Excel.IXLWorksheet,System.Int32,System.Int32,System.String,FAU.Logica.Services.AguinaldoPadronService/TotalesPadron,ClosedXML.Excel.XLColor,ClosedXML.Excel.XLColor,System.Nullable`1<System.Int32>)
FormatApellidosNombres(System.String,System.String,System.String,System.String)
.ctor(System.String,System.String,System.String,System.Int16,System.Boolean,System.Boolean,System.String,System.String,System.String,System.String,System.Decimal,System.Decimal,System.Decimal,System.Decimal,System.Int32,System.Decimal)
get_Cedula()
get_NombreCompleto()
get_GradoDenominacion()
get_GradoOrden()
get_EsOficial()
get_EsSubalterno()
get_SituacionCodigo()
get_SituacionDenominacion()
get_ProgramaCodigo()
get_ProgramaDenominacion()
get_Nominal()
get_SueldoRetiro()
get_Montepio()
get_Sanidad()
get_CodigoSanidad()
get_Liquido()
get_Nominal()
get_SueldoRetiro()
get_MontepiSFun()
get_MontepiOficial()
get_MontepiCivil()
get_San5010()
get_San5011()
get_San5012()
get_Liquido()
Acumular(FAU.Logica.Services.AguinaldoPadronService/FilaPadron)
Acumular(FAU.Logica.Services.AguinaldoPadronService/TotalesPadron)