< Summary

Information
Class: FAU.Logica.Services.ReporteControlService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/ReporteControlService.cs
Line coverage
92%
Covered lines: 266
Uncovered lines: 21
Coverable lines: 287
Total lines: 462
Line coverage: 92.6%
Branch coverage
82%
Covered branches: 79
Total branches: 96
Branch coverage: 82.2%
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/ReporteControlService.cs

#LineLine coverage
 1using System.Globalization;
 2using System.Text;
 3using FAU.DataAccess.Repositories;
 4using FAU.Entidades;
 5
 6namespace FAU.Logica.Services;
 7
 8public class ReporteControlService : IReporteControlService
 9{
 10    private readonly IReporteControlRepository _repo;
 11
 1112    public ReporteControlService(IReporteControlRepository repo)
 13    {
 1114        _repo = repo;
 1115    }
 16
 17    // ─── Cuadro de Efectivos Superior ───────────────────────────────────────
 18
 19    public async Task<CuadroEfectivosDto> ObtenerEfectivosSuperiorAsync(
 20        long periodoId, string? programa, string? regimen)
 21    {
 322        var relaciones = await _repo.ObtenerRelacionesActivasAsync();
 23
 24        // Personal superior: oficiales (EsOficial == true)
 325        var superior = relaciones
 626            .Where(r => r.Grado.EsOficial)
 527            .Where(r => FiltrarPrograma(r, programa))
 428            .Where(r => FiltrarRegimen(r, regimen))
 429            .Where(r => EsSituacionActividad(r))
 330            .ToList();
 31
 32        // Cuadro principal: agrupado por Grado × Escalafón
 333        var filas = superior
 434            .GroupBy(r => (GradoDenom: r.Grado.Denominacion, EscalafonDenom: r.Escalafon.Denominacion, Orden: r.Grado.Or
 335            .OrderBy(g => g.Key.Orden)
 336            .Select(g => new CuadroEfectivosFilaDto
 337            {
 338                Grado      = g.Key.GradoDenom,
 339                Escalafon  = g.Key.EscalafonDenom,
 440                EnCuadro   = g.Count(r => r.Situacion.Calculador == "Sit10Calculator" || r.Situacion.Calculador == "SitC
 441                FueraCuadro = g.Count(r => r.Situacion.Calculador == "Sit30Calculator")
 342            })
 343            .ToList();
 44
 45        // Totales por escalafón
 346        var totalesEscalafon = filas
 347            .GroupBy(f => f.Escalafon)
 348            .Select(g => new CuadroEfectivosTotalesEscalafonDto
 349            {
 350                Escalafon = g.Key,
 351                Total     = g.Sum(f => f.Total)
 352            })
 353            .ToList();
 54
 55        // Cuadro secundario: reincorporados, equiparados, reservistas
 356        var secundarioGrupos = relaciones
 657            .Where(r => r.Grado.EsOficial)
 558            .Where(r => FiltrarPrograma(r, programa))
 459            .Where(r => FiltrarRegimen(r, regimen))
 460            .Where(r => EsSituacionSecundariaSuperior(r))
 061            .GroupBy(r => (r.Grado.Denominacion, r.Grado.Orden))
 062            .OrderBy(g => g.Key.Orden)
 063            .Select(g => new CuadroEfectivosSecundarioFilaDto
 064            {
 065                Grado         = g.Key.Denominacion,
 066                Reincorporados = g.Count(r => EsReincorporado(r)),
 067                Equiparados   = g.Count(r => EsEquiparado(r)),
 068                Reservistas   = g.Count(r => EsReservista(r))
 069            })
 370            .ToList();
 71
 372        return new CuadroEfectivosDto
 373        {
 374            Filas             = filas,
 375            TotalesPorEscalafon = totalesEscalafon,
 376            TotalGeneral      = filas.Sum(f => f.Total),
 377            CuadroSecundario  = secundarioGrupos
 378        };
 379    }
 80
 81    public async Task<byte[]> GenerarCsvEfectivosSuperiorAsync(
 82        long periodoId, string? programa, string? regimen)
 83    {
 184        var dto = await ObtenerEfectivosSuperiorAsync(periodoId, programa, regimen);
 85
 186        var sb = new StringBuilder();
 187        sb.AppendLine("Grado;Escalafón;En Cuadro;Fuera de Cuadro;Total");
 88
 489        foreach (var fila in dto.Filas)
 190            sb.AppendLine($"{fila.Grado};{fila.Escalafon};{fila.EnCuadro};{fila.FueraCuadro};{fila.Total}");
 91
 192        return Encoding.UTF8.GetBytes(sb.ToString());
 193    }
 94
 95    // ─── Cuadro de Efectivos Subalterno ─────────────────────────────────────
 96
 97    public async Task<CuadroEfectivosSubalternoDto> ObtenerEfectivosSubalternoAsync(
 98        long periodoId, string? programa, string? regimen)
 99    {
 3100        var relaciones = await _repo.ObtenerRelacionesActivasAsync();
 101
 102        // Personal subalterno: grados 1–7, cadetes, aprendices (EsSubalterno == true)
 3103        var subalterno = relaciones
 6104            .Where(r => r.Grado.EsSubalterno)
 5105            .Where(r => FiltrarPrograma(r, programa))
 5106            .Where(r => FiltrarRegimen(r, regimen))
 4107            .Where(r => EsSituacionActividad(r))
 3108            .ToList();
 109
 3110        var filas = subalterno
 4111            .GroupBy(r => (r.Grado.Denominacion, r.Grado.Orden))
 3112            .OrderBy(g => g.Key.Orden)
 3113            .Select(g => new CuadroEfectivosSubalternoFilaDto
 3114            {
 3115                Grado     = g.Key.Denominacion,
 3116                Efectivos = g.Count(),
 3117                Asignados = 0  // TODO: cargar desde tabla de plazas presupuestales cuando esté disponible
 3118            })
 3119            .ToList();
 120
 121        // Cuadro secundario: en reserva, reincorporados, civiles equiparados
 3122        var secundario = relaciones
 6123            .Where(r => r.Grado.EsSubalterno)
 5124            .Where(r => FiltrarPrograma(r, programa))
 5125            .Where(r => FiltrarRegimen(r, regimen))
 4126            .Where(r => EsSituacionSecundariaSubalterno(r))
 0127            .GroupBy(r => (r.Grado.Denominacion, r.Grado.Orden))
 0128            .OrderBy(g => g.Key.Orden)
 0129            .Select(g => new CuadroEfectivosSubalternoSecundarioFilaDto
 0130            {
 0131                Grado         = g.Key.Denominacion,
 0132                EnReserva     = g.Count(r => EsReservista(r)),
 0133                Reincorporados = g.Count(r => EsReincorporado(r)),
 0134                Equiparados   = g.Count(r => EsEquiparado(r))
 0135            })
 3136            .ToList();
 137
 3138        return new CuadroEfectivosSubalternoDto
 3139        {
 3140            Filas          = filas,
 3141            TotalEfectivos = filas.Sum(f => f.Efectivos),
 3142            TotalAsignados = filas.Sum(f => f.Asignados),
 3143            CuadroSecundario = secundario
 3144        };
 3145    }
 146
 147    public async Task<byte[]> GenerarCsvEfectivosSubalternoAsync(
 148        long periodoId, string? programa, string? regimen)
 149    {
 1150        var dto = await ObtenerEfectivosSubalternoAsync(periodoId, programa, regimen);
 151
 1152        var sb = new StringBuilder();
 1153        sb.AppendLine("Grado;Efectivos;Asignados;Diferencia");
 154
 4155        foreach (var fila in dto.Filas)
 1156            sb.AppendLine($"{fila.Grado};{fila.Efectivos};{fila.Asignados};{fila.Diferencia}");
 157
 1158        return Encoding.UTF8.GetBytes(sb.ToString());
 1159    }
 160
 161    // ─── Listín de Contralor ─────────────────────────────────────────────────
 162
 163    public async Task<ListinContralorDto> ObtenerListinAsync(
 164        long periodoId, int? situacionDesde, int? situacionHasta)
 165    {
 3166        var items     = await _repo.ObtenerItemsLiquidacionAsync(periodoId);
 3167        var relaciones = await _repo.ObtenerRelacionesActivasAsync();
 168
 3169        var relPorCedula = relaciones
 4170            .GroupBy(r => r.Persona.Cedula)
 11171            .ToDictionary(g => g.Key, g => g.First());
 172
 173        // Agrupar items por cédula
 3174        var itemsPorCedula = items
 7175            .GroupBy(i => i.Cedula)
 11176            .ToDictionary(g => g.Key, g => g.ToList());
 177
 178        // Agrupar cédulas por situación
 3179        var porSituacion = new Dictionary<string, (Situacion Sit, List<(RelacionLaboral Rel, List<LiquidacionItem> Items
 180
 14181        foreach (var (cedula, itemsList) in itemsPorCedula)
 182        {
 4183            if (!relPorCedula.TryGetValue(cedula, out var rel))
 184                continue;
 185
 4186            var sit = rel.Situacion;
 187
 188            // Filtrar por rango de situación (se extrae el número del código, ej: "SIT10" → 10)
 4189            var numCodigo = ExtraerNumeroSituacion(sit.Codigo);
 4190            if (situacionDesde.HasValue && numCodigo < situacionDesde.Value)
 191                continue;
 3192            if (situacionHasta.HasValue && numCodigo > situacionHasta.Value)
 193                continue;
 194
 3195            if (!porSituacion.TryGetValue(sit.Codigo, out var entry))
 196            {
 3197                entry = (sit, new List<(RelacionLaboral, List<LiquidacionItem>)>());
 3198                porSituacion[sit.Codigo] = entry;
 199            }
 3200            entry.Funcs.Add((rel, itemsList));
 201        }
 202
 3203        var listaSituaciones = new List<ListinSituacionDto>();
 204
 15205        foreach (var (codigo, (sit, funcs)) in porSituacion.OrderBy(kvp => ExtraerNumeroSituacion(kvp.Key)))
 206        {
 3207            var funcionarios = funcs.Select(f =>
 3208            {
 9209                var haberes    = f.Items.Where(i => EsHaber(i)).Select(MapItem).ToList();
 9210                var beneficios = f.Items.Where(i => EsBeneficio(i)).Select(MapItem).ToList();
 9211                var descuentos = f.Items.Where(i => EsDescuento(i)).Select(MapItem).ToList();
 3212
 6213                var nominal    = haberes.Sum(h => h.Importe);
 4214                var totalBen   = beneficios.Sum(b => b.Importe);
 5215                var totalDesc  = Math.Abs(descuentos.Sum(d => d.Importe));
 3216                var montepio   = Math.Abs(descuentos
 2217                    .Where(d => d.NombreConcepto.Contains("MONTEPIO", StringComparison.OrdinalIgnoreCase))
 5218                    .Sum(d => d.Importe));
 3219                var liquido    = nominal + totalBen - totalDesc;
 3220
 3221                return new ListinFuncionarioDto
 3222                {
 3223                    Cedula    = f.Rel.Persona.Cedula,
 3224                    Nombre    = f.Rel.Persona.NombreCompleto,
 3225                    Grado     = f.Rel.Grado.Denominacion,
 3226                    Escalafon = f.Rel.Escalafon.Denominacion,
 3227                    Haberes    = haberes,
 3228                    Beneficios = beneficios,
 3229                    Descuentos = descuentos,
 3230                    Totales   = new ListinTotalesDto
 3231                    {
 3232                        Nominal    = nominal,
 3233                        Montepio   = montepio,
 3234                        Descuentos = totalDesc,
 3235                        Liquido    = liquido
 3236                    }
 3237                };
 3238            }).ToList();
 239
 3240            var subtotales = new ListinTotalesDto
 3241            {
 3242                Nominal    = funcionarios.Sum(f => f.Totales.Nominal),
 3243                Montepio   = funcionarios.Sum(f => f.Totales.Montepio),
 3244                Descuentos = funcionarios.Sum(f => f.Totales.Descuentos),
 3245                Liquido    = funcionarios.Sum(f => f.Totales.Liquido)
 3246            };
 247
 3248            listaSituaciones.Add(new ListinSituacionDto
 3249            {
 3250                Situacion      = sit.Codigo,
 3251                NombreSituacion = sit.Denominacion,
 3252                Funcionarios   = funcionarios,
 3253                Subtotales     = subtotales
 3254            });
 255        }
 256
 3257        var resumen = new ListinTotalesDto
 3258        {
 3259            Nominal    = listaSituaciones.Sum(s => s.Subtotales.Nominal),
 3260            Montepio   = listaSituaciones.Sum(s => s.Subtotales.Montepio),
 3261            Descuentos = listaSituaciones.Sum(s => s.Subtotales.Descuentos),
 3262            Liquido    = listaSituaciones.Sum(s => s.Subtotales.Liquido)
 3263        };
 264
 3265        return new ListinContralorDto
 3266        {
 3267            PorSituacion = listaSituaciones,
 3268            ResumenFinal = resumen
 3269        };
 3270    }
 271
 272    public async Task<byte[]> GenerarCsvListinAsync(
 273        long periodoId, int? situacionDesde, int? situacionHasta)
 274    {
 1275        var dto = await ObtenerListinAsync(periodoId, situacionDesde, situacionHasta);
 276
 1277        var sb = new StringBuilder();
 1278        sb.AppendLine("Situación;Cédula;Nombre;Grado;Escalafón;Código Concepto;Rubro;Importe");
 279
 4280        foreach (var sit in dto.PorSituacion)
 281        {
 4282            foreach (var func in sit.Funcionarios)
 283            {
 6284                foreach (var item in func.Haberes.Concat(func.Descuentos))
 285                {
 2286                    sb.AppendLine($"{sit.Situacion};{func.Cedula};{func.Nombre};" +
 2287                                  $"{func.Grado};{func.Escalafon};" +
 2288                                  $"{item.CodigoConcepto};{item.NombreConcepto};{F(item.Importe)}");
 289                }
 290            }
 291
 1292            sb.AppendLine($"SUBTOTAL;{sit.Situacion};;;;;;" +
 1293                          $"{F(sit.Subtotales.Liquido)}");
 294        }
 295
 1296        sb.AppendLine($"TOTAL;;;;;;;{F(dto.ResumenFinal.Liquido)}");
 297
 1298        return Encoding.UTF8.GetBytes(sb.ToString());
 1299    }
 300
 301    // ─── Comparativo SGH ─────────────────────────────────────────────────────
 302
 303    public async Task<ComparativoSghDto> ObtenerComparativoSghAsync(
 304        long periodoId, List<PadronSghEntradaDto> filas)
 305    {
 2306        var relaciones = await _repo.ObtenerRelacionesActivasAsync();
 307
 7308        var sghPorCedula = filas.ToDictionary(p => p.Cedula);
 2309        var sistemaPorCedula = relaciones
 5310            .GroupBy(r => r.Persona.Cedula)
 12311            .ToDictionary(g => g.Key, g => g.First());
 312
 2313        var soloEnSistema = new List<ComparativoSoloSistemaDto>();
 2314        var soloEnSgh     = new List<ComparativoSoloSghDto>();
 2315        var diferencias   = new List<ComparativoDiferenciaDto>();
 2316        int coincidencias = 0;
 317
 318        // Cédulas en el sistema
 14319        foreach (var (cedula, rel) in sistemaPorCedula)
 320        {
 5321            if (!sghPorCedula.TryGetValue(cedula, out var sghFila))
 322            {
 2323                soloEnSistema.Add(new ComparativoSoloSistemaDto
 2324                {
 2325                    Cedula    = cedula,
 2326                    Nombre    = rel.Persona.NombreCompleto,
 2327                    Situacion = rel.Situacion.Codigo,
 2328                    Grado     = rel.Grado.Codigo
 2329                });
 330            }
 331            else
 332            {
 3333                bool mismaSituacion = rel.Situacion.Codigo == sghFila.SituacionCodigo;
 3334                bool mismoGrado     = rel.Grado.Codigo     == sghFila.GradoCodigo;
 335
 3336                if (mismaSituacion && mismoGrado)
 337                {
 2338                    coincidencias++;
 339                }
 340                else
 341                {
 1342                    diferencias.Add(new ComparativoDiferenciaDto
 1343                    {
 1344                        Cedula          = cedula,
 1345                        Nombre          = rel.Persona.NombreCompleto,
 1346                        SituacionSistema = rel.Situacion.Codigo,
 1347                        SituacionSgh    = sghFila.SituacionCodigo,
 1348                        GradoSistema    = rel.Grado.Codigo,
 1349                        GradoSgh        = sghFila.GradoCodigo
 1350                    });
 351                }
 352            }
 353        }
 354
 355        // Cédulas solo en SGH
 14356        foreach (var (cedula, sghFila) in sghPorCedula)
 357        {
 5358            if (!sistemaPorCedula.ContainsKey(cedula))
 359            {
 2360                soloEnSgh.Add(new ComparativoSoloSghDto
 2361                {
 2362                    Cedula          = cedula,
 2363                    SituacionCodigo = sghFila.SituacionCodigo,
 2364                    GradoCodigo     = sghFila.GradoCodigo
 2365                });
 366            }
 367        }
 368
 2369        return new ComparativoSghDto
 2370        {
 2371            SoloEnSistema    = soloEnSistema,
 2372            SoloEnSgh        = soloEnSgh,
 2373            Diferencias      = diferencias,
 2374            TotalCoincidencias = coincidencias
 2375        };
 2376    }
 377
 378    public async Task<byte[]> GenerarCsvComparativoSghAsync(
 379        long periodoId, List<PadronSghEntradaDto> filas)
 380    {
 1381        var dto = await ObtenerComparativoSghAsync(periodoId, filas);
 382
 1383        var sb = new StringBuilder();
 1384        sb.AppendLine("Tipo;Cédula;Nombre;Situación Sistema;Situación SGH;Grado Sistema;Grado SGH");
 385
 4386        foreach (var r in dto.SoloEnSistema)
 1387            sb.AppendLine($"SOLO_SISTEMA;{r.Cedula};{r.Nombre};{r.Situacion};;{r.Grado};");
 388
 4389        foreach (var r in dto.SoloEnSgh)
 1390            sb.AppendLine($"SOLO_SGH;{r.Cedula};;;{r.SituacionCodigo};;{r.GradoCodigo}");
 391
 2392        foreach (var r in dto.Diferencias)
 0393            sb.AppendLine($"DIFERENCIA;{r.Cedula};{r.Nombre};{r.SituacionSistema};{r.SituacionSgh};{r.GradoSistema};{r.G
 394
 1395        return Encoding.UTF8.GetBytes(sb.ToString());
 1396    }
 397
 398    // ─── Helpers privados ────────────────────────────────────────────────────
 399
 400    private static bool FiltrarPrograma(RelacionLaboral r, string? programa)
 401    {
 20402        if (string.IsNullOrEmpty(programa))
 16403            return true;
 4404        return r.Programa.Codigo == programa;
 405    }
 406
 407    private static bool FiltrarRegimen(RelacionLaboral r, string? regimen)
 408    {
 18409        if (string.IsNullOrEmpty(regimen))
 14410            return true;
 4411        return regimen switch
 4412        {
 0413            "ley-vieja" => r.Regimen.EsLeyVieja,
 4414            "ley-nueva" => !r.Regimen.EsLeyVieja,
 0415            _ => true
 4416        };
 417    }
 418
 419    private static bool EsSituacionActividad(RelacionLaboral r) =>
 8420        r.Situacion.Calculador is "Sit10Calculator" or "Sit30Calculator" or "SitCadeteCalculator";
 421
 422    private static bool EsSituacionSecundariaSuperior(RelacionLaboral r) =>
 4423        EsReincorporado(r) || EsEquiparado(r) || EsReservista(r);
 424
 425    private static bool EsSituacionSecundariaSubalterno(RelacionLaboral r) =>
 4426        EsReservista(r) || EsReincorporado(r) || EsEquiparado(r);
 427
 428    private static bool EsReincorporado(RelacionLaboral r) =>
 8429        r.Situacion.Calculador is "Sit50Calculator";
 430
 431    private static bool EsEquiparado(RelacionLaboral r) =>
 8432        r.Situacion.Calculador is "Sit70Calculator" or "CivilCalculator";
 433
 434    private static bool EsReservista(RelacionLaboral r) =>
 8435        r.Situacion.Calculador is "ReservaCalculator" or "ReservaSuperiorCalculator";
 436
 437    private static int ExtraerNumeroSituacion(string codigo)
 438    {
 439        // "SIT10" → 10, "SIT07" → 7, etc.
 7440        var num = new string(codigo.Where(char.IsDigit).ToArray());
 7441        return int.TryParse(num, out var n) ? n : 0;
 442    }
 443
 444    private static bool EsHaber(LiquidacionItem i) =>
 6445        i.RubroContable is "HabNorGrab" or "HabsAnGrab" or "HabsNoGrab";
 446
 447    private static bool EsBeneficio(LiquidacionItem i) =>
 6448        i.RubroContable is "BenSocGrab" or "BenSocNoGr";
 449
 450    private static bool EsDescuento(LiquidacionItem i) =>
 6451        i.RubroContable is "DtoLegGrab" or "DtoLegNoGr" or "DtoPerGrab";
 452
 6453    private static ListinItemDto MapItem(LiquidacionItem i) => new()
 6454    {
 6455        CodigoConcepto = i.CodigoConcepto,
 6456        NombreConcepto = i.NombreConcepto,
 6457        Importe        = i.Importe
 6458    };
 459
 460    private static string F(decimal value) =>
 4461        value.ToString("F2", CultureInfo.InvariantCulture);
 462}